@smthrs/std 0.0.0-stage → 1.0.0-rc.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +148 -0
- package/LICENSE +21 -0
- package/README.md +156 -2
- package/dist/cjs/ApplyPatch.d.ts +174 -0
- package/dist/cjs/ApplyPatch.d.ts.map +1 -0
- package/dist/cjs/ApplyPatch.js +309 -0
- package/dist/cjs/ApplyPatch.js.map +7 -0
- package/dist/cjs/Bash.d.ts +390 -0
- package/dist/cjs/Bash.d.ts.map +1 -0
- package/dist/cjs/Bash.js +333 -0
- package/dist/cjs/Bash.js.map +7 -0
- package/dist/cjs/Checkpoints.d.ts +322 -0
- package/dist/cjs/Checkpoints.d.ts.map +1 -0
- package/dist/cjs/Checkpoints.js +233 -0
- package/dist/cjs/Checkpoints.js.map +7 -0
- package/dist/cjs/Container.d.ts +132 -0
- package/dist/cjs/Container.d.ts.map +1 -0
- package/dist/cjs/Container.js +113 -0
- package/dist/cjs/Container.js.map +7 -0
- package/dist/cjs/Edit.d.ts +203 -0
- package/dist/cjs/Edit.d.ts.map +1 -0
- package/dist/cjs/Edit.js +258 -0
- package/dist/cjs/Edit.js.map +7 -0
- package/dist/cjs/ExaWebSearch.d.ts +22 -0
- package/dist/cjs/ExaWebSearch.d.ts.map +1 -0
- package/dist/cjs/ExaWebSearch.js +138 -0
- package/dist/cjs/ExaWebSearch.js.map +7 -0
- package/dist/cjs/Explore.d.ts +328 -0
- package/dist/cjs/Explore.d.ts.map +1 -0
- package/dist/cjs/Explore.js +90 -0
- package/dist/cjs/Explore.js.map +7 -0
- package/dist/cjs/Fetch.d.ts +130 -0
- package/dist/cjs/Fetch.d.ts.map +1 -0
- package/dist/cjs/Fetch.js +64 -0
- package/dist/cjs/Fetch.js.map +7 -0
- package/dist/cjs/Glob.d.ts +204 -0
- package/dist/cjs/Glob.d.ts.map +1 -0
- package/dist/cjs/Glob.js +94 -0
- package/dist/cjs/Glob.js.map +7 -0
- package/dist/cjs/Grep.d.ts +347 -0
- package/dist/cjs/Grep.d.ts.map +1 -0
- package/dist/cjs/Grep.js +181 -0
- package/dist/cjs/Grep.js.map +7 -0
- package/dist/cjs/HttpPost.d.ts +130 -0
- package/dist/cjs/HttpPost.d.ts.map +1 -0
- package/dist/cjs/HttpPost.js +72 -0
- package/dist/cjs/HttpPost.js.map +7 -0
- package/dist/cjs/LanguageServer.d.ts +90 -0
- package/dist/cjs/LanguageServer.d.ts.map +1 -0
- package/dist/cjs/LanguageServer.js +60 -0
- package/dist/cjs/LanguageServer.js.map +7 -0
- package/dist/cjs/Ls.d.ts +164 -0
- package/dist/cjs/Ls.d.ts.map +1 -0
- package/dist/cjs/Ls.js +136 -0
- package/dist/cjs/Ls.js.map +7 -0
- package/dist/cjs/Lsp.d.ts +125 -0
- package/dist/cjs/Lsp.d.ts.map +1 -0
- package/dist/cjs/Lsp.js +137 -0
- package/dist/cjs/Lsp.js.map +7 -0
- package/dist/cjs/Manifest.d.ts +918 -0
- package/dist/cjs/Manifest.d.ts.map +1 -0
- package/dist/cjs/Manifest.js +144 -0
- package/dist/cjs/Manifest.js.map +7 -0
- package/dist/cjs/NativeSearch.d.ts +38 -0
- package/dist/cjs/NativeSearch.d.ts.map +1 -0
- package/dist/cjs/NativeSearch.js +326 -0
- package/dist/cjs/NativeSearch.js.map +7 -0
- package/dist/cjs/NodeLanguageServer.d.ts +128 -0
- package/dist/cjs/NodeLanguageServer.d.ts.map +1 -0
- package/dist/cjs/NodeLanguageServer.js +796 -0
- package/dist/cjs/NodeLanguageServer.js.map +7 -0
- package/dist/cjs/PortableSearch.d.ts +36 -0
- package/dist/cjs/PortableSearch.d.ts.map +1 -0
- package/dist/cjs/PortableSearch.js +240 -0
- package/dist/cjs/PortableSearch.js.map +7 -0
- package/dist/cjs/Probe.d.ts +193 -0
- package/dist/cjs/Probe.d.ts.map +1 -0
- package/dist/cjs/Probe.js +134 -0
- package/dist/cjs/Probe.js.map +7 -0
- package/dist/cjs/Read.d.ts +189 -0
- package/dist/cjs/Read.d.ts.map +1 -0
- package/dist/cjs/Read.js +223 -0
- package/dist/cjs/Read.js.map +7 -0
- package/dist/cjs/Relocate.d.ts +73 -0
- package/dist/cjs/Relocate.d.ts.map +1 -0
- package/dist/cjs/Relocate.js +72 -0
- package/dist/cjs/Relocate.js.map +7 -0
- package/dist/cjs/RemoteSearch.d.ts +54 -0
- package/dist/cjs/RemoteSearch.d.ts.map +1 -0
- package/dist/cjs/RemoteSearch.js +134 -0
- package/dist/cjs/RemoteSearch.js.map +7 -0
- package/dist/cjs/Search.d.ts +163 -0
- package/dist/cjs/Search.d.ts.map +1 -0
- package/dist/cjs/Search.js +51 -0
- package/dist/cjs/Search.js.map +7 -0
- package/dist/cjs/SearchConformance.d.ts +115 -0
- package/dist/cjs/SearchConformance.d.ts.map +1 -0
- package/dist/cjs/SearchConformance.js +172 -0
- package/dist/cjs/SearchConformance.js.map +7 -0
- package/dist/cjs/SearchContract.d.ts +114 -0
- package/dist/cjs/SearchContract.d.ts.map +1 -0
- package/dist/cjs/SearchContract.js +339 -0
- package/dist/cjs/SearchContract.js.map +7 -0
- package/dist/cjs/ShellCommand.d.ts +164 -0
- package/dist/cjs/ShellCommand.d.ts.map +1 -0
- package/dist/cjs/ShellCommand.js +102 -0
- package/dist/cjs/ShellCommand.js.map +7 -0
- package/dist/cjs/StdError.d.ts +55 -0
- package/dist/cjs/StdError.d.ts.map +1 -0
- package/dist/cjs/StdError.js +74 -0
- package/dist/cjs/StdError.js.map +7 -0
- package/dist/cjs/TestRun.d.ts +311 -0
- package/dist/cjs/TestRun.d.ts.map +1 -0
- package/dist/cjs/TestRun.js +228 -0
- package/dist/cjs/TestRun.js.map +7 -0
- package/dist/cjs/TestRunner.d.ts +102 -0
- package/dist/cjs/TestRunner.d.ts.map +1 -0
- package/dist/cjs/TestRunner.js +54 -0
- package/dist/cjs/TestRunner.js.map +7 -0
- package/dist/cjs/TreeFingerprint.d.ts +140 -0
- package/dist/cjs/TreeFingerprint.d.ts.map +1 -0
- package/dist/cjs/TreeFingerprint.js +87 -0
- package/dist/cjs/TreeFingerprint.js.map +7 -0
- package/dist/cjs/UpdatePlan.d.ts +152 -0
- package/dist/cjs/UpdatePlan.d.ts.map +1 -0
- package/dist/cjs/UpdatePlan.js +87 -0
- package/dist/cjs/UpdatePlan.js.map +7 -0
- package/dist/cjs/WebFetch.d.ts +134 -0
- package/dist/cjs/WebFetch.d.ts.map +1 -0
- package/dist/cjs/WebFetch.js +140 -0
- package/dist/cjs/WebFetch.js.map +7 -0
- package/dist/cjs/WebSearch.d.ts +181 -0
- package/dist/cjs/WebSearch.d.ts.map +1 -0
- package/dist/cjs/WebSearch.js +86 -0
- package/dist/cjs/WebSearch.js.map +7 -0
- package/dist/cjs/Write.d.ts +147 -0
- package/dist/cjs/Write.d.ts.map +1 -0
- package/dist/cjs/Write.js +122 -0
- package/dist/cjs/Write.js.map +7 -0
- package/dist/cjs/index.d.ts +129 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +101 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ApplyPatch.d.ts +145 -0
- package/dist/cjs/internal/ApplyPatch.d.ts.map +1 -0
- package/dist/cjs/internal/ApplyPatch.js +462 -0
- package/dist/cjs/internal/ApplyPatch.js.map +7 -0
- package/dist/cjs/internal/CodexText.d.ts +73 -0
- package/dist/cjs/internal/CodexText.d.ts.map +1 -0
- package/dist/cjs/internal/CodexText.js +108 -0
- package/dist/cjs/internal/CodexText.js.map +7 -0
- package/dist/cjs/internal/Declaration.d.ts +49 -0
- package/dist/cjs/internal/Declaration.d.ts.map +1 -0
- package/dist/cjs/internal/Declaration.js +46 -0
- package/dist/cjs/internal/Declaration.js.map +7 -0
- package/dist/cjs/internal/Diagnostics.d.ts +77 -0
- package/dist/cjs/internal/Diagnostics.d.ts.map +1 -0
- package/dist/cjs/internal/Diagnostics.js +89 -0
- package/dist/cjs/internal/Diagnostics.js.map +7 -0
- package/dist/cjs/internal/EnvelopePrecheck.d.ts +69 -0
- package/dist/cjs/internal/EnvelopePrecheck.d.ts.map +1 -0
- package/dist/cjs/internal/EnvelopePrecheck.js +323 -0
- package/dist/cjs/internal/EnvelopePrecheck.js.map +7 -0
- package/dist/cjs/internal/Exec.d.ts +151 -0
- package/dist/cjs/internal/Exec.d.ts.map +1 -0
- package/dist/cjs/internal/Exec.js +207 -0
- package/dist/cjs/internal/Exec.js.map +7 -0
- package/dist/cjs/internal/FileMutation.d.ts +109 -0
- package/dist/cjs/internal/FileMutation.d.ts.map +1 -0
- package/dist/cjs/internal/FileMutation.js +116 -0
- package/dist/cjs/internal/FileMutation.js.map +7 -0
- package/dist/cjs/internal/FsFailure.d.ts +38 -0
- package/dist/cjs/internal/FsFailure.d.ts.map +1 -0
- package/dist/cjs/internal/FsFailure.js +40 -0
- package/dist/cjs/internal/FsFailure.js.map +7 -0
- package/dist/cjs/internal/GitWorktree.d.ts +108 -0
- package/dist/cjs/internal/GitWorktree.d.ts.map +1 -0
- package/dist/cjs/internal/GitWorktree.js +276 -0
- package/dist/cjs/internal/GitWorktree.js.map +7 -0
- package/dist/cjs/internal/Grouping.d.ts +51 -0
- package/dist/cjs/internal/Grouping.d.ts.map +1 -0
- package/dist/cjs/internal/Grouping.js +83 -0
- package/dist/cjs/internal/Grouping.js.map +7 -0
- package/dist/cjs/internal/Html.d.ts +20 -0
- package/dist/cjs/internal/Html.d.ts.map +1 -0
- package/dist/cjs/internal/Html.js +81 -0
- package/dist/cjs/internal/Html.js.map +7 -0
- package/dist/cjs/internal/Http.d.ts +95 -0
- package/dist/cjs/internal/Http.d.ts.map +1 -0
- package/dist/cjs/internal/Http.js +153 -0
- package/dist/cjs/internal/Http.js.map +7 -0
- package/dist/cjs/internal/HttpNetwork.d.ts +27 -0
- package/dist/cjs/internal/HttpNetwork.d.ts.map +1 -0
- package/dist/cjs/internal/HttpNetwork.js +146 -0
- package/dist/cjs/internal/HttpNetwork.js.map +7 -0
- package/dist/cjs/internal/Ignore.d.ts +37 -0
- package/dist/cjs/internal/Ignore.d.ts.map +1 -0
- package/dist/cjs/internal/Ignore.js +100 -0
- package/dist/cjs/internal/Ignore.js.map +7 -0
- package/dist/cjs/internal/LinearRegex.d.ts +19 -0
- package/dist/cjs/internal/LinearRegex.d.ts.map +1 -0
- package/dist/cjs/internal/LinearRegex.js +166 -0
- package/dist/cjs/internal/LinearRegex.js.map +7 -0
- package/dist/cjs/internal/Match.d.ts +85 -0
- package/dist/cjs/internal/Match.d.ts.map +1 -0
- package/dist/cjs/internal/Match.js +99 -0
- package/dist/cjs/internal/Match.js.map +7 -0
- package/dist/cjs/internal/Paths.d.ts +23 -0
- package/dist/cjs/internal/Paths.d.ts.map +1 -0
- package/dist/cjs/internal/Paths.js +52 -0
- package/dist/cjs/internal/Paths.js.map +7 -0
- package/dist/cjs/internal/Preserve.d.ts +25 -0
- package/dist/cjs/internal/Preserve.d.ts.map +1 -0
- package/dist/cjs/internal/Preserve.js +88 -0
- package/dist/cjs/internal/Preserve.js.map +7 -0
- package/dist/cjs/internal/Prune.d.ts +35 -0
- package/dist/cjs/internal/Prune.d.ts.map +1 -0
- package/dist/cjs/internal/Prune.js +54 -0
- package/dist/cjs/internal/Prune.js.map +7 -0
- package/dist/cjs/internal/RemoteTree.d.ts +60 -0
- package/dist/cjs/internal/RemoteTree.d.ts.map +1 -0
- package/dist/cjs/internal/RemoteTree.js +123 -0
- package/dist/cjs/internal/RemoteTree.js.map +7 -0
- package/dist/cjs/internal/SearchContract.d.ts +63 -0
- package/dist/cjs/internal/SearchContract.d.ts.map +1 -0
- package/dist/cjs/internal/SearchContract.js +46 -0
- package/dist/cjs/internal/SearchContract.js.map +7 -0
- package/dist/cjs/internal/Symbols.d.ts +39 -0
- package/dist/cjs/internal/Symbols.d.ts.map +1 -0
- package/dist/cjs/internal/Symbols.js +59 -0
- package/dist/cjs/internal/Symbols.js.map +7 -0
- package/dist/cjs/internal/Tar.d.ts +23 -0
- package/dist/cjs/internal/Tar.d.ts.map +1 -0
- package/dist/cjs/internal/Tar.js +86 -0
- package/dist/cjs/internal/Tar.js.map +7 -0
- package/dist/cjs/internal/TestReport.d.ts +52 -0
- package/dist/cjs/internal/TestReport.d.ts.map +1 -0
- package/dist/cjs/internal/TestReport.js +131 -0
- package/dist/cjs/internal/TestReport.js.map +7 -0
- package/dist/cjs/internal/Text.d.ts +125 -0
- package/dist/cjs/internal/Text.d.ts.map +1 -0
- package/dist/cjs/internal/Text.js +85 -0
- package/dist/cjs/internal/Text.js.map +7 -0
- package/dist/cjs/internal/Url.d.ts +13 -0
- package/dist/cjs/internal/Url.d.ts.map +1 -0
- package/dist/cjs/internal/Url.js +35 -0
- package/dist/cjs/internal/Url.js.map +7 -0
- package/dist/cjs/internal/Walk.d.ts +72 -0
- package/dist/cjs/internal/Walk.d.ts.map +1 -0
- package/dist/cjs/internal/Walk.js +108 -0
- package/dist/cjs/internal/Walk.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/ApplyPatch.d.ts +174 -0
- package/dist/esm/ApplyPatch.d.ts.map +1 -0
- package/dist/esm/ApplyPatch.js +346 -0
- package/dist/esm/ApplyPatch.js.map +1 -0
- package/dist/esm/Bash.d.ts +390 -0
- package/dist/esm/Bash.d.ts.map +1 -0
- package/dist/esm/Bash.js +514 -0
- package/dist/esm/Bash.js.map +1 -0
- package/dist/esm/Checkpoints.d.ts +322 -0
- package/dist/esm/Checkpoints.d.ts.map +1 -0
- package/dist/esm/Checkpoints.js +396 -0
- package/dist/esm/Checkpoints.js.map +1 -0
- package/dist/esm/Container.d.ts +132 -0
- package/dist/esm/Container.d.ts.map +1 -0
- package/dist/esm/Container.js +151 -0
- package/dist/esm/Container.js.map +1 -0
- package/dist/esm/Edit.d.ts +203 -0
- package/dist/esm/Edit.d.ts.map +1 -0
- package/dist/esm/Edit.js +296 -0
- package/dist/esm/Edit.js.map +1 -0
- package/dist/esm/ExaWebSearch.d.ts +22 -0
- package/dist/esm/ExaWebSearch.d.ts.map +1 -0
- package/dist/esm/ExaWebSearch.js +107 -0
- package/dist/esm/ExaWebSearch.js.map +1 -0
- package/dist/esm/Explore.d.ts +328 -0
- package/dist/esm/Explore.d.ts.map +1 -0
- package/dist/esm/Explore.js +106 -0
- package/dist/esm/Explore.js.map +1 -0
- package/dist/esm/Fetch.d.ts +130 -0
- package/dist/esm/Fetch.d.ts.map +1 -0
- package/dist/esm/Fetch.js +91 -0
- package/dist/esm/Fetch.js.map +1 -0
- package/dist/esm/Glob.d.ts +204 -0
- package/dist/esm/Glob.d.ts.map +1 -0
- package/dist/esm/Glob.js +177 -0
- package/dist/esm/Glob.js.map +1 -0
- package/dist/esm/Grep.d.ts +347 -0
- package/dist/esm/Grep.d.ts.map +1 -0
- package/dist/esm/Grep.js +289 -0
- package/dist/esm/Grep.js.map +1 -0
- package/dist/esm/HttpPost.d.ts +130 -0
- package/dist/esm/HttpPost.d.ts.map +1 -0
- package/dist/esm/HttpPost.js +89 -0
- package/dist/esm/HttpPost.js.map +1 -0
- package/dist/esm/LanguageServer.d.ts +90 -0
- package/dist/esm/LanguageServer.d.ts.map +1 -0
- package/dist/esm/LanguageServer.js +57 -0
- package/dist/esm/LanguageServer.js.map +1 -0
- package/dist/esm/Ls.d.ts +164 -0
- package/dist/esm/Ls.d.ts.map +1 -0
- package/dist/esm/Ls.js +178 -0
- package/dist/esm/Ls.js.map +1 -0
- package/dist/esm/Lsp.d.ts +125 -0
- package/dist/esm/Lsp.d.ts.map +1 -0
- package/dist/esm/Lsp.js +165 -0
- package/dist/esm/Lsp.js.map +1 -0
- package/dist/esm/Manifest.d.ts +918 -0
- package/dist/esm/Manifest.d.ts.map +1 -0
- package/dist/esm/Manifest.js +153 -0
- package/dist/esm/Manifest.js.map +1 -0
- package/dist/esm/NativeSearch.d.ts +38 -0
- package/dist/esm/NativeSearch.d.ts.map +1 -0
- package/dist/esm/NativeSearch.js +378 -0
- package/dist/esm/NativeSearch.js.map +1 -0
- package/dist/esm/NodeLanguageServer.d.ts +128 -0
- package/dist/esm/NodeLanguageServer.d.ts.map +1 -0
- package/dist/esm/NodeLanguageServer.js +843 -0
- package/dist/esm/NodeLanguageServer.js.map +1 -0
- package/dist/esm/PortableSearch.d.ts +36 -0
- package/dist/esm/PortableSearch.d.ts.map +1 -0
- package/dist/esm/PortableSearch.js +256 -0
- package/dist/esm/PortableSearch.js.map +1 -0
- package/dist/esm/Probe.d.ts +193 -0
- package/dist/esm/Probe.d.ts.map +1 -0
- package/dist/esm/Probe.js +225 -0
- package/dist/esm/Probe.js.map +1 -0
- package/dist/esm/Read.d.ts +189 -0
- package/dist/esm/Read.d.ts.map +1 -0
- package/dist/esm/Read.js +300 -0
- package/dist/esm/Read.js.map +1 -0
- package/dist/esm/Relocate.d.ts +73 -0
- package/dist/esm/Relocate.d.ts.map +1 -0
- package/dist/esm/Relocate.js +153 -0
- package/dist/esm/Relocate.js.map +1 -0
- package/dist/esm/RemoteSearch.d.ts +54 -0
- package/dist/esm/RemoteSearch.d.ts.map +1 -0
- package/dist/esm/RemoteSearch.js +162 -0
- package/dist/esm/RemoteSearch.js.map +1 -0
- package/dist/esm/Search.d.ts +163 -0
- package/dist/esm/Search.d.ts.map +1 -0
- package/dist/esm/Search.js +47 -0
- package/dist/esm/Search.js.map +1 -0
- package/dist/esm/SearchConformance.d.ts +115 -0
- package/dist/esm/SearchConformance.d.ts.map +1 -0
- package/dist/esm/SearchConformance.js +193 -0
- package/dist/esm/SearchConformance.js.map +1 -0
- package/dist/esm/SearchContract.d.ts +114 -0
- package/dist/esm/SearchContract.d.ts.map +1 -0
- package/dist/esm/SearchContract.js +461 -0
- package/dist/esm/SearchContract.js.map +1 -0
- package/dist/esm/ShellCommand.d.ts +164 -0
- package/dist/esm/ShellCommand.d.ts.map +1 -0
- package/dist/esm/ShellCommand.js +159 -0
- package/dist/esm/ShellCommand.js.map +1 -0
- package/dist/esm/StdError.d.ts +55 -0
- package/dist/esm/StdError.d.ts.map +1 -0
- package/dist/esm/StdError.js +71 -0
- package/dist/esm/StdError.js.map +1 -0
- package/dist/esm/TestRun.d.ts +311 -0
- package/dist/esm/TestRun.d.ts.map +1 -0
- package/dist/esm/TestRun.js +316 -0
- package/dist/esm/TestRun.js.map +1 -0
- package/dist/esm/TestRunner.d.ts +102 -0
- package/dist/esm/TestRunner.d.ts.map +1 -0
- package/dist/esm/TestRunner.js +68 -0
- package/dist/esm/TestRunner.js.map +1 -0
- package/dist/esm/TreeFingerprint.d.ts +140 -0
- package/dist/esm/TreeFingerprint.d.ts.map +1 -0
- package/dist/esm/TreeFingerprint.js +170 -0
- package/dist/esm/TreeFingerprint.js.map +1 -0
- package/dist/esm/UpdatePlan.d.ts +152 -0
- package/dist/esm/UpdatePlan.d.ts.map +1 -0
- package/dist/esm/UpdatePlan.js +129 -0
- package/dist/esm/UpdatePlan.js.map +1 -0
- package/dist/esm/WebFetch.d.ts +134 -0
- package/dist/esm/WebFetch.d.ts.map +1 -0
- package/dist/esm/WebFetch.js +151 -0
- package/dist/esm/WebFetch.js.map +1 -0
- package/dist/esm/WebSearch.d.ts +181 -0
- package/dist/esm/WebSearch.d.ts.map +1 -0
- package/dist/esm/WebSearch.js +134 -0
- package/dist/esm/WebSearch.js.map +1 -0
- package/dist/esm/Write.d.ts +147 -0
- package/dist/esm/Write.d.ts.map +1 -0
- package/dist/esm/Write.js +144 -0
- package/dist/esm/Write.js.map +1 -0
- package/dist/esm/index.d.ts +129 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +129 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ApplyPatch.d.ts +145 -0
- package/dist/esm/internal/ApplyPatch.d.ts.map +1 -0
- package/dist/esm/internal/ApplyPatch.js +489 -0
- package/dist/esm/internal/ApplyPatch.js.map +1 -0
- package/dist/esm/internal/CodexText.d.ts +73 -0
- package/dist/esm/internal/CodexText.d.ts.map +1 -0
- package/dist/esm/internal/CodexText.js +156 -0
- package/dist/esm/internal/CodexText.js.map +1 -0
- package/dist/esm/internal/Declaration.d.ts +49 -0
- package/dist/esm/internal/Declaration.d.ts.map +1 -0
- package/dist/esm/internal/Declaration.js +42 -0
- package/dist/esm/internal/Declaration.js.map +1 -0
- package/dist/esm/internal/Diagnostics.d.ts +77 -0
- package/dist/esm/internal/Diagnostics.d.ts.map +1 -0
- package/dist/esm/internal/Diagnostics.js +99 -0
- package/dist/esm/internal/Diagnostics.js.map +1 -0
- package/dist/esm/internal/EnvelopePrecheck.d.ts +69 -0
- package/dist/esm/internal/EnvelopePrecheck.d.ts.map +1 -0
- package/dist/esm/internal/EnvelopePrecheck.js +426 -0
- package/dist/esm/internal/EnvelopePrecheck.js.map +1 -0
- package/dist/esm/internal/Exec.d.ts +151 -0
- package/dist/esm/internal/Exec.d.ts.map +1 -0
- package/dist/esm/internal/Exec.js +285 -0
- package/dist/esm/internal/Exec.js.map +1 -0
- package/dist/esm/internal/FileMutation.d.ts +109 -0
- package/dist/esm/internal/FileMutation.d.ts.map +1 -0
- package/dist/esm/internal/FileMutation.js +123 -0
- package/dist/esm/internal/FileMutation.js.map +1 -0
- package/dist/esm/internal/FsFailure.d.ts +38 -0
- package/dist/esm/internal/FsFailure.d.ts.map +1 -0
- package/dist/esm/internal/FsFailure.js +39 -0
- package/dist/esm/internal/FsFailure.js.map +1 -0
- package/dist/esm/internal/GitWorktree.d.ts +108 -0
- package/dist/esm/internal/GitWorktree.d.ts.map +1 -0
- package/dist/esm/internal/GitWorktree.js +352 -0
- package/dist/esm/internal/GitWorktree.js.map +1 -0
- package/dist/esm/internal/Grouping.d.ts +51 -0
- package/dist/esm/internal/Grouping.d.ts.map +1 -0
- package/dist/esm/internal/Grouping.js +99 -0
- package/dist/esm/internal/Grouping.js.map +1 -0
- package/dist/esm/internal/Html.d.ts +20 -0
- package/dist/esm/internal/Html.d.ts.map +1 -0
- package/dist/esm/internal/Html.js +70 -0
- package/dist/esm/internal/Html.js.map +1 -0
- package/dist/esm/internal/Http.d.ts +95 -0
- package/dist/esm/internal/Http.d.ts.map +1 -0
- package/dist/esm/internal/Http.js +154 -0
- package/dist/esm/internal/Http.js.map +1 -0
- package/dist/esm/internal/HttpNetwork.d.ts +27 -0
- package/dist/esm/internal/HttpNetwork.d.ts.map +1 -0
- package/dist/esm/internal/HttpNetwork.js +114 -0
- package/dist/esm/internal/HttpNetwork.js.map +1 -0
- package/dist/esm/internal/Ignore.d.ts +37 -0
- package/dist/esm/internal/Ignore.d.ts.map +1 -0
- package/dist/esm/internal/Ignore.js +128 -0
- package/dist/esm/internal/Ignore.js.map +1 -0
- package/dist/esm/internal/LinearRegex.d.ts +19 -0
- package/dist/esm/internal/LinearRegex.d.ts.map +1 -0
- package/dist/esm/internal/LinearRegex.js +176 -0
- package/dist/esm/internal/LinearRegex.js.map +1 -0
- package/dist/esm/internal/Match.d.ts +85 -0
- package/dist/esm/internal/Match.d.ts.map +1 -0
- package/dist/esm/internal/Match.js +142 -0
- package/dist/esm/internal/Match.js.map +1 -0
- package/dist/esm/internal/Paths.d.ts +23 -0
- package/dist/esm/internal/Paths.d.ts.map +1 -0
- package/dist/esm/internal/Paths.js +40 -0
- package/dist/esm/internal/Paths.js.map +1 -0
- package/dist/esm/internal/Preserve.d.ts +25 -0
- package/dist/esm/internal/Preserve.d.ts.map +1 -0
- package/dist/esm/internal/Preserve.js +70 -0
- package/dist/esm/internal/Preserve.js.map +1 -0
- package/dist/esm/internal/Prune.d.ts +35 -0
- package/dist/esm/internal/Prune.d.ts.map +1 -0
- package/dist/esm/internal/Prune.js +60 -0
- package/dist/esm/internal/Prune.js.map +1 -0
- package/dist/esm/internal/RemoteTree.d.ts +60 -0
- package/dist/esm/internal/RemoteTree.d.ts.map +1 -0
- package/dist/esm/internal/RemoteTree.js +132 -0
- package/dist/esm/internal/RemoteTree.js.map +1 -0
- package/dist/esm/internal/SearchContract.d.ts +63 -0
- package/dist/esm/internal/SearchContract.d.ts.map +1 -0
- package/dist/esm/internal/SearchContract.js +66 -0
- package/dist/esm/internal/SearchContract.js.map +1 -0
- package/dist/esm/internal/Symbols.d.ts +39 -0
- package/dist/esm/internal/Symbols.d.ts.map +1 -0
- package/dist/esm/internal/Symbols.js +79 -0
- package/dist/esm/internal/Symbols.js.map +1 -0
- package/dist/esm/internal/Tar.d.ts +23 -0
- package/dist/esm/internal/Tar.d.ts.map +1 -0
- package/dist/esm/internal/Tar.js +97 -0
- package/dist/esm/internal/Tar.js.map +1 -0
- package/dist/esm/internal/TestReport.d.ts +52 -0
- package/dist/esm/internal/TestReport.d.ts.map +1 -0
- package/dist/esm/internal/TestReport.js +172 -0
- package/dist/esm/internal/TestReport.js.map +1 -0
- package/dist/esm/internal/Text.d.ts +125 -0
- package/dist/esm/internal/Text.d.ts.map +1 -0
- package/dist/esm/internal/Text.js +141 -0
- package/dist/esm/internal/Text.js.map +1 -0
- package/dist/esm/internal/Url.d.ts +13 -0
- package/dist/esm/internal/Url.d.ts.map +1 -0
- package/dist/esm/internal/Url.js +27 -0
- package/dist/esm/internal/Url.js.map +1 -0
- package/dist/esm/internal/Walk.d.ts +72 -0
- package/dist/esm/internal/Walk.d.ts.map +1 -0
- package/dist/esm/internal/Walk.js +126 -0
- package/dist/esm/internal/Walk.js.map +1 -0
- package/docs/README.md +119 -0
- package/docs/api.md +499 -0
- package/docs/concepts/effects-and-capabilities.md +138 -0
- package/docs/concepts/flows-and-handlers.md +115 -0
- package/docs/concepts/limits-and-disclosure.md +105 -0
- package/docs/concepts/search-contract.md +182 -0
- package/docs/guides/bind-the-standard-flows.md +116 -0
- package/docs/guides/implement-a-search-peer.md +133 -0
- package/docs/guides/pin-a-checkpoint.md +161 -0
- package/docs/guides/query-a-language-server.md +257 -0
- package/docs/guides/reach-the-network.md +141 -0
- package/docs/guides/read-and-edit-a-file.md +240 -0
- package/docs/guides/run-a-shell-command.md +236 -0
- package/docs/guides/run-the-test-suite.md +154 -0
- package/docs/guides/search-a-workspace.md +148 -0
- package/docs/installation.md +115 -0
- package/docs/quickstart.md +125 -0
- package/docs/reference/flows.md +556 -0
- package/docs/troubleshooting.md +347 -0
- package/package.json +449 -3
- package/src/ApplyPatch.ts +458 -0
- package/src/Bash.ts +619 -0
- package/src/Checkpoints.ts +589 -0
- package/src/Container.ts +212 -0
- package/src/Edit.ts +366 -0
- package/src/ExaWebSearch.ts +145 -0
- package/src/Explore.ts +139 -0
- package/src/Fetch.ts +117 -0
- package/src/Glob.ts +200 -0
- package/src/Grep.ts +320 -0
- package/src/HttpPost.ts +120 -0
- package/src/LanguageServer.ts +110 -0
- package/src/Ls.ts +228 -0
- package/src/Lsp.ts +184 -0
- package/src/Manifest.ts +162 -0
- package/src/NativeSearch.ts +457 -0
- package/src/NodeLanguageServer.ts +1148 -0
- package/src/PortableSearch.ts +284 -0
- package/src/Probe.ts +284 -0
- package/src/Read.ts +359 -0
- package/src/Relocate.ts +166 -0
- package/src/RemoteSearch.ts +199 -0
- package/src/Search.ts +185 -0
- package/src/SearchConformance.ts +288 -0
- package/src/SearchContract.ts +489 -0
- package/src/ShellCommand.ts +201 -0
- package/src/StdError.ts +80 -0
- package/src/TestRun.ts +401 -0
- package/src/TestRunner.ts +119 -0
- package/src/TreeFingerprint.ts +200 -0
- package/src/UpdatePlan.ts +171 -0
- package/src/WebFetch.ts +194 -0
- package/src/WebSearch.ts +165 -0
- package/src/Write.ts +195 -0
- package/src/index.ts +157 -0
- package/src/internal/ApplyPatch.ts +601 -0
- package/src/internal/CodexText.ts +182 -0
- package/src/internal/Declaration.ts +60 -0
- package/src/internal/Diagnostics.ts +134 -0
- package/src/internal/EnvelopePrecheck.ts +489 -0
- package/src/internal/Exec.ts +427 -0
- package/src/internal/FileMutation.ts +231 -0
- package/src/internal/FsFailure.ts +51 -0
- package/src/internal/GitWorktree.ts +491 -0
- package/src/internal/Grouping.ts +106 -0
- package/src/internal/Html.ts +87 -0
- package/src/internal/Http.ts +209 -0
- package/src/internal/HttpNetwork.ts +153 -0
- package/src/internal/Ignore.ts +135 -0
- package/src/internal/LinearRegex.ts +173 -0
- package/src/internal/Match.ts +172 -0
- package/src/internal/Paths.ts +42 -0
- package/src/internal/Preserve.ts +108 -0
- package/src/internal/Prune.ts +62 -0
- package/src/internal/RemoteTree.ts +159 -0
- package/src/internal/SearchContract.ts +77 -0
- package/src/internal/Symbols.ts +88 -0
- package/src/internal/Tar.ts +95 -0
- package/src/internal/TestReport.ts +194 -0
- package/src/internal/Text.ts +185 -0
- package/src/internal/Url.ts +23 -0
- package/src/internal/Walk.ts +176 -0
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
|
|
27
|
+
import type * as ChildProcessSpawner from "@smthrs/kernel/ChildProcessSpawner"
|
|
28
|
+
import * as Path from "@smthrs/kernel/Path"
|
|
29
|
+
import { Context, Effect } from "effect"
|
|
30
|
+
import * as Base64 from "effect/encoding/Base64"
|
|
31
|
+
import * as FileSystem from "effect/FileSystem"
|
|
32
|
+
import * as Result from "effect/Result"
|
|
33
|
+
import * as Exec from "./internal/Exec.ts"
|
|
34
|
+
import * as Prune from "./internal/Prune.ts"
|
|
35
|
+
import * as RemoteTree from "./internal/RemoteTree.ts"
|
|
36
|
+
import * as Tar from "./internal/Tar.ts"
|
|
37
|
+
import * as PortableSearch from "./PortableSearch.ts"
|
|
38
|
+
import * as Search from "./Search.ts"
|
|
39
|
+
import * as StdError from "./StdError.ts"
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The most file content one search brings back from the machine.
|
|
43
|
+
*
|
|
44
|
+
* The copy is held in memory while the in-process peer reads it, so the
|
|
45
|
+
* bound is on this process's memory rather than on the search: a root that
|
|
46
|
+
* holds more is refused with `response_too_large` and a narrower root or
|
|
47
|
+
* globs answer it.
|
|
48
|
+
*
|
|
49
|
+
* @category constants
|
|
50
|
+
* @since 1.0.0-rc.8
|
|
51
|
+
*/
|
|
52
|
+
export const maxBytes = 256 * 1024 * 1024
|
|
53
|
+
|
|
54
|
+
/** The most a listing, with its `.gitignore` files, may take. */
|
|
55
|
+
const maxListingBytes = 64 * 1024 * 1024
|
|
56
|
+
|
|
57
|
+
const quoted = (value: string): string => `'${value.replaceAll("'", `'\\''`)}'`
|
|
58
|
+
|
|
59
|
+
const pruned = `\\( ${Prune.repositoryDirectories.map((name) => `-name ${quoted(name)}`).join(" -o ")} \\) -prune`
|
|
60
|
+
|
|
61
|
+
/** The refusal for a machine without one of the tools a search runs. */
|
|
62
|
+
const missing = (tool: string) => `searching this machine needs ${tool}, which it does not have`
|
|
63
|
+
|
|
64
|
+
const requireTools = `for t in find xargs tar head; do command -v "$t" >/dev/null || { ` +
|
|
65
|
+
`echo "${missing("$t")}" >&2; exit 3; }; done`
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `$1` is the root, `$2` the byte bound, and `$3` is `1` to send the
|
|
69
|
+
* `.gitignore` files after the listing. Records are NUL-terminated `<kind><path>`;
|
|
70
|
+
* an `E` record ends the listing, and a tar stream of the ignore files follows.
|
|
71
|
+
*/
|
|
72
|
+
const listing = [
|
|
73
|
+
"set -f",
|
|
74
|
+
requireTools,
|
|
75
|
+
"r=$1",
|
|
76
|
+
"{",
|
|
77
|
+
" if [ -d \"$r\" ]; then if [ -r \"$r\" ] && [ -x \"$r\" ]; then printf 'd%s\\0' \"$r\"; else printf 'u%s\\0' \"$r\"; fi",
|
|
78
|
+
" elif [ -f \"$r\" ]; then printf 'f%s\\0' \"$r\"",
|
|
79
|
+
" elif [ -e \"$r\" ]; then printf 'o%s\\0' \"$r\"",
|
|
80
|
+
" fi",
|
|
81
|
+
// No `-exec … {} +` inside parentheses: BusyBox `find` never runs one there.
|
|
82
|
+
` find -H "$r" -mindepth 1 ${pruned} -o -type d -exec printf 'd%s\\0' {} + ` +
|
|
83
|
+
`-o -type f -exec printf 'f%s\\0' {} + ` +
|
|
84
|
+
`-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; ` +
|
|
85
|
+
`printf "%s%s\\0" "$k" "$p"; done' sh {} + -o -exec printf 'o%s\\0' {} + 2>/dev/null`,
|
|
86
|
+
" printf 'E\\0'",
|
|
87
|
+
` if [ "$3" = 1 ]; then find -H "$r" -mindepth 1 ${pruned} -o -type f -name .gitignore -print0 2>/dev/null | ` +
|
|
88
|
+
"(cd / && COPYFILE_DISABLE=1 xargs -0 -r tar -cf - -- 2>/dev/null); fi",
|
|
89
|
+
"} | head -c \"$2\" | base64"
|
|
90
|
+
].join("\n")
|
|
91
|
+
|
|
92
|
+
/** `$1` is the byte bound; standard input is the NUL-terminated absolute paths to send. */
|
|
93
|
+
const fetching = [
|
|
94
|
+
requireTools,
|
|
95
|
+
"cd / && COPYFILE_DISABLE=1 xargs -0 -r tar -chf - -- 2>/dev/null | head -c \"$1\" | base64"
|
|
96
|
+
].join("\n")
|
|
97
|
+
|
|
98
|
+
const failed = (root: string, reason: string): StdError.StdError =>
|
|
99
|
+
new StdError.StdError({
|
|
100
|
+
code: "command_failed",
|
|
101
|
+
message: `Could not search ${root} on this machine: ${reason}`,
|
|
102
|
+
path: root
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const tooLarge = (root: string, bound: number): StdError.StdError =>
|
|
106
|
+
new StdError.StdError({
|
|
107
|
+
code: "response_too_large",
|
|
108
|
+
message: `The files under ${root} come to more than ${bound} bytes; narrow the root or the globs`,
|
|
109
|
+
path: root
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Reads the program from the first line of standard input, as base64, and
|
|
114
|
+
* runs it with the arguments and the rest of standard input. A program in
|
|
115
|
+
* the argv would be quoted once by the spawner and again by the transport,
|
|
116
|
+
* and a sealed host refuses to start a command line past 4,096 characters;
|
|
117
|
+
* this line stays short whatever the program says.
|
|
118
|
+
*/
|
|
119
|
+
const bootstrap = `command -v base64 >/dev/null || { echo "${missing("base64")}" >&2; exit 3; }; ` +
|
|
120
|
+
"IFS= read -r p && p=$(printf %s \"$p\" | base64 -d) && exec sh -c \"$p\" sh \"$@\""
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Runs one script on the machine and decodes the base64 it prints, refusing
|
|
124
|
+
* output that reached `bound`: `head` cut it, so it is not the whole answer.
|
|
125
|
+
*/
|
|
126
|
+
const run = (
|
|
127
|
+
root: string,
|
|
128
|
+
script: string,
|
|
129
|
+
args: ReadonlyArray<string>,
|
|
130
|
+
bound: number,
|
|
131
|
+
stdin = ""
|
|
132
|
+
): Effect.Effect<Uint8Array, StdError.StdError, ChildProcessSpawner.ChildProcessSpawner> =>
|
|
133
|
+
Effect.gen(function*() {
|
|
134
|
+
const result = yield* Exec.exec("sh", {
|
|
135
|
+
args: ["-c", bootstrap, "sh", ...args],
|
|
136
|
+
stdin: `${Base64.encode(new TextEncoder().encode(script))}\n${stdin}`
|
|
137
|
+
}).pipe(Effect.mapError((error) => failed(root, error.message)))
|
|
138
|
+
if (result.exitCode !== 0) {
|
|
139
|
+
return yield* Effect.fail(failed(root, result.stderr.trim() || `exit ${result.exitCode}`))
|
|
140
|
+
}
|
|
141
|
+
const decoded = Base64.decode(result.stdout.replaceAll(/\s/g, ""))
|
|
142
|
+
if (Result.isFailure(decoded)) return yield* Effect.fail(failed(root, "the machine answered with invalid base64"))
|
|
143
|
+
if (decoded.success.length >= bound) return yield* Effect.fail(tooLarge(root, bound - 1))
|
|
144
|
+
return decoded.success
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Builds the peer over a machine's spawner and its `Path`, which resolves a
|
|
149
|
+
* relative root where the machine's file operations do. `maxBytes` lowers
|
|
150
|
+
* the bound on the file content one search brings back; the default is
|
|
151
|
+
* {@link maxBytes}.
|
|
152
|
+
*
|
|
153
|
+
* @category constructors
|
|
154
|
+
* @since 1.0.0-rc.8
|
|
155
|
+
*/
|
|
156
|
+
export const make = (
|
|
157
|
+
services: Context.Context<ChildProcessSpawner.ChildProcessSpawner | Path.Path>,
|
|
158
|
+
options?: { readonly maxBytes?: number | undefined }
|
|
159
|
+
): Search.Search => {
|
|
160
|
+
const path = Context.get(services, Path.Path)
|
|
161
|
+
const contentBound = (options?.maxBytes ?? maxBytes) + 1
|
|
162
|
+
const copy = (tree: RemoteTree.Tree) =>
|
|
163
|
+
Context.make(FileSystem.FileSystem, RemoteTree.fileSystem(tree, path)).pipe(Context.add(Path.Path, path))
|
|
164
|
+
const listed = (root: string, noIgnore: boolean | undefined) => {
|
|
165
|
+
const resolved = path.resolve(root)
|
|
166
|
+
const bound = maxListingBytes + 1
|
|
167
|
+
return Effect.flatMap(
|
|
168
|
+
run(root, listing, [resolved, String(bound), noIgnore === true ? "0" : "1"], bound),
|
|
169
|
+
(bytes) => {
|
|
170
|
+
const tree = RemoteTree.parse(bytes, resolved, path)
|
|
171
|
+
return tree === undefined ? Effect.fail(failed(root, "the listing came back malformed")) : Effect.succeed(tree)
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
return Search.make({
|
|
176
|
+
grep: (input) =>
|
|
177
|
+
Effect.gen(function*() {
|
|
178
|
+
const tree = yield* listed(input.root, input.noIgnore)
|
|
179
|
+
const read = yield* PortableSearch.searched(input).pipe(Effect.provide(copy(tree)))
|
|
180
|
+
const files = [...new Set(read.map((file) => path.resolve(file)))]
|
|
181
|
+
const bytes = files.length === 0 ? new Uint8Array() : yield* run(
|
|
182
|
+
input.root,
|
|
183
|
+
fetching,
|
|
184
|
+
[String(contentBound)],
|
|
185
|
+
contentBound,
|
|
186
|
+
files.map((file) => `${file}\0`).join("")
|
|
187
|
+
)
|
|
188
|
+
// `tar` ends every archive it writes, even one whose files it could not read.
|
|
189
|
+
const members = files.length === 0 || bytes.length > 0 ? Tar.files(bytes) : undefined
|
|
190
|
+
if (members === undefined) return yield* Effect.fail(failed(input.root, "the files came back malformed"))
|
|
191
|
+
const contents = new Map([...tree.contents, ...RemoteTree.absolute(members)])
|
|
192
|
+
return yield* PortableSearch.make(copy({ ...tree, contents })).grep(input)
|
|
193
|
+
}).pipe(Effect.provide(services)),
|
|
194
|
+
glob: (input) =>
|
|
195
|
+
Effect.flatMap(listed(input.root, input.noIgnore), (tree) => PortableSearch.make(copy(tree)).glob(input)).pipe(
|
|
196
|
+
Effect.provide(services)
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
}
|
package/src/Search.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The implementation seam for the ripgrep search contract.
|
|
3
|
+
*
|
|
4
|
+
* Both implementations answer the same normalized calls. `PortableSearch`
|
|
5
|
+
* performs the work in-process; `NativeSearch` drives the `rg` executable.
|
|
6
|
+
* Callers select an implementation by Layer and cannot observe which one was
|
|
7
|
+
* selected from the result.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Context, Effect, Layer } from "effect"
|
|
13
|
+
import * as StdError from "./StdError.ts"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalized grep input shared by both implementations.
|
|
17
|
+
*
|
|
18
|
+
* @category models
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export interface GrepInput {
|
|
22
|
+
readonly pattern: string
|
|
23
|
+
readonly root: string
|
|
24
|
+
readonly fixedStrings: boolean
|
|
25
|
+
readonly ignoreCase: boolean
|
|
26
|
+
readonly smartCase: boolean
|
|
27
|
+
readonly globs: ReadonlyArray<string>
|
|
28
|
+
readonly beforeContext: number
|
|
29
|
+
readonly afterContext: number
|
|
30
|
+
readonly maxCount?: number | undefined
|
|
31
|
+
readonly filesWithMatches: boolean
|
|
32
|
+
/** Opt out of root-scoped .gitignore rules; defaults to false. */
|
|
33
|
+
readonly noIgnore?: boolean | undefined
|
|
34
|
+
readonly hidden: boolean
|
|
35
|
+
readonly symbols: boolean
|
|
36
|
+
readonly limit: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* One matching or context line, as a peer produces it before grouping.
|
|
41
|
+
*
|
|
42
|
+
* @category models
|
|
43
|
+
* @since 1.0.0
|
|
44
|
+
*/
|
|
45
|
+
export interface GrepLine {
|
|
46
|
+
readonly file: string
|
|
47
|
+
readonly line: number
|
|
48
|
+
readonly text: string
|
|
49
|
+
readonly kind: "match" | "context"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One context line carried inside the match it belongs to.
|
|
54
|
+
*
|
|
55
|
+
* @category models
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
*/
|
|
58
|
+
export interface ContextLine {
|
|
59
|
+
readonly line: number
|
|
60
|
+
readonly text: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The definition enclosing a hit, when the file's shape says so plainly.
|
|
65
|
+
*
|
|
66
|
+
* @category models
|
|
67
|
+
* @since 1.0.0
|
|
68
|
+
*/
|
|
69
|
+
export interface Symbol {
|
|
70
|
+
readonly kind: string
|
|
71
|
+
readonly name: string
|
|
72
|
+
readonly startLine: number
|
|
73
|
+
readonly endLine: number
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* One hit: the matching line, the context that belongs to it, and the
|
|
78
|
+
* definition it sits in.
|
|
79
|
+
*
|
|
80
|
+
* @category models
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
*/
|
|
83
|
+
export interface GrepMatch {
|
|
84
|
+
readonly file: string
|
|
85
|
+
readonly line: number
|
|
86
|
+
readonly text: string
|
|
87
|
+
readonly before: ReadonlyArray<ContextLine>
|
|
88
|
+
readonly after: ReadonlyArray<ContextLine>
|
|
89
|
+
readonly symbol?: Symbol | undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalized grep output.
|
|
94
|
+
*
|
|
95
|
+
* @category models
|
|
96
|
+
* @since 1.0.0
|
|
97
|
+
*/
|
|
98
|
+
export interface GrepOutput {
|
|
99
|
+
readonly matches: ReadonlyArray<GrepMatch>
|
|
100
|
+
readonly files: ReadonlyArray<string>
|
|
101
|
+
readonly filesSearched: number
|
|
102
|
+
readonly skippedBinary: number
|
|
103
|
+
readonly truncated: boolean
|
|
104
|
+
readonly notice?: string | undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Normalized glob input.
|
|
109
|
+
*
|
|
110
|
+
* @category models
|
|
111
|
+
* @since 1.0.0
|
|
112
|
+
*/
|
|
113
|
+
export interface GlobInput {
|
|
114
|
+
readonly pattern: string
|
|
115
|
+
readonly root: string
|
|
116
|
+
/** Opt out of root-scoped .gitignore rules; defaults to false. */
|
|
117
|
+
readonly noIgnore?: boolean | undefined
|
|
118
|
+
readonly hidden: boolean
|
|
119
|
+
readonly limit: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Normalized glob output.
|
|
124
|
+
*
|
|
125
|
+
* @category models
|
|
126
|
+
* @since 1.0.0
|
|
127
|
+
*/
|
|
128
|
+
export interface GlobOutput {
|
|
129
|
+
readonly paths: ReadonlyArray<string>
|
|
130
|
+
readonly total: number
|
|
131
|
+
readonly truncated: boolean
|
|
132
|
+
readonly notice?: string | undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The one search contract implemented by native and portable peers.
|
|
137
|
+
*
|
|
138
|
+
* @category services
|
|
139
|
+
* @since 1.0.0
|
|
140
|
+
*/
|
|
141
|
+
export interface Search {
|
|
142
|
+
readonly grep: (input: GrepInput) => Effect.Effect<GrepOutput, StdError.StdError>
|
|
143
|
+
readonly glob: (input: GlobInput) => Effect.Effect<GlobOutput, StdError.StdError>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The {@link Search} service tag.
|
|
148
|
+
*
|
|
149
|
+
* @category services
|
|
150
|
+
* @since 1.0.0
|
|
151
|
+
*/
|
|
152
|
+
export const Search: Context.Service<Search, Search> = Context.Service("@smthrs/std/Search")
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Builds a search service from its two operations.
|
|
156
|
+
*
|
|
157
|
+
* @category constructors
|
|
158
|
+
* @since 1.0.0
|
|
159
|
+
*/
|
|
160
|
+
export const make = (service: Search): Search => Search.of(service)
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Builds an unavailable search service for compositions that deliberately omit search.
|
|
164
|
+
*
|
|
165
|
+
* @category constructors
|
|
166
|
+
* @since 1.0.0
|
|
167
|
+
*/
|
|
168
|
+
export const makeNoop = (): Search => {
|
|
169
|
+
const unavailable = () =>
|
|
170
|
+
Effect.fail(
|
|
171
|
+
new StdError.StdError({
|
|
172
|
+
code: "provider_unavailable",
|
|
173
|
+
message: "No search implementation is configured"
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
return make({ grep: unavailable, glob: unavailable })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Provides {@link makeNoop}.
|
|
181
|
+
*
|
|
182
|
+
* @category layers
|
|
183
|
+
* @since 1.0.0
|
|
184
|
+
*/
|
|
185
|
+
export const layerNoop: Layer.Layer<Search> = Layer.succeed(Search, makeNoop())
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A differential conformance kit for {@link Search} implementations.
|
|
3
|
+
*
|
|
4
|
+
* `Search` is a public extension seam: a host binds its own implementation and
|
|
5
|
+
* every `grep` and `glob` call in the package goes through it. Two peers ship
|
|
6
|
+
* here, and the contract they are held to used to live only in
|
|
7
|
+
* `test/SearchConformance.test.ts` — a file `package.json` does not publish, so
|
|
8
|
+
* a third-party peer had the seam and no way to prove it filled it. Worse, the
|
|
9
|
+
* cases there are hand-picked: they pin the divergences somebody already
|
|
10
|
+
* found. A literal `?` matched `fo` in one peer and `foo?` in the other for as
|
|
11
|
+
* long as nobody thought to write that case down.
|
|
12
|
+
*
|
|
13
|
+
* This module is the other half. It generates a tree and a batch of calls from
|
|
14
|
+
* a seed, runs them through two implementations, and reports every answer that
|
|
15
|
+
* differs. Nothing here knows which peer is right: a divergence is the finding,
|
|
16
|
+
* and the reference peer to compare against is the caller's choice —
|
|
17
|
+
* `PortableSearch` is the obvious one, because it needs no external binary.
|
|
18
|
+
*
|
|
19
|
+
* The generator deliberately stays inside the ground both peers claim to
|
|
20
|
+
* share: no symlinks, no unreadable directories, no CRLF, no NUL bytes, no
|
|
21
|
+
* filename a shell would have to quote. Those are the cases the hand-written
|
|
22
|
+
* table pins one by one, with an expected value that says which behaviour is
|
|
23
|
+
* intended. A generator cannot say that; it can only say the two disagree.
|
|
24
|
+
*
|
|
25
|
+
* @since 1.0.0
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import * as Effect from "effect/Effect"
|
|
29
|
+
import * as FileSystem from "effect/FileSystem"
|
|
30
|
+
import * as Path from "effect/Path"
|
|
31
|
+
import type * as Search from "./Search.ts"
|
|
32
|
+
import type * as StdError from "./StdError.ts"
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One file the generated tree contains.
|
|
36
|
+
*
|
|
37
|
+
* @category models
|
|
38
|
+
* @since 1.0.0
|
|
39
|
+
*/
|
|
40
|
+
export interface GeneratedFile {
|
|
41
|
+
/** Path relative to the tree root, always with `/` separators. */
|
|
42
|
+
readonly path: string
|
|
43
|
+
readonly content: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A generated tree plus the calls to make against it.
|
|
48
|
+
*
|
|
49
|
+
* @category models
|
|
50
|
+
* @since 1.0.0
|
|
51
|
+
*/
|
|
52
|
+
export interface Plan {
|
|
53
|
+
readonly seed: number
|
|
54
|
+
readonly root: string
|
|
55
|
+
readonly files: ReadonlyArray<GeneratedFile>
|
|
56
|
+
readonly grep: ReadonlyArray<Search.GrepInput>
|
|
57
|
+
readonly glob: ReadonlyArray<Search.GlobInput>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One call two implementations answered differently.
|
|
62
|
+
*
|
|
63
|
+
* `subject` and `reference` are the rendered answers, so a report names the
|
|
64
|
+
* bytes that differ rather than saying only that they did.
|
|
65
|
+
*
|
|
66
|
+
* @category models
|
|
67
|
+
* @since 1.0.0
|
|
68
|
+
*/
|
|
69
|
+
export interface Divergence {
|
|
70
|
+
readonly call: "grep" | "glob"
|
|
71
|
+
readonly input: Search.GrepInput | Search.GlobInput
|
|
72
|
+
readonly subject: string
|
|
73
|
+
readonly reference: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* mulberry32: 32 bits of state, uniform enough for fixture generation and
|
|
78
|
+
* reproducible from a seed on every host, which a shared `Math.random` is not.
|
|
79
|
+
*/
|
|
80
|
+
const random = (seed: number): () => number => {
|
|
81
|
+
let state = seed >>> 0
|
|
82
|
+
return () => {
|
|
83
|
+
state = (state + 0x6D2B79F5) >>> 0
|
|
84
|
+
let value = Math.imul(state ^ (state >>> 15), 1 | state)
|
|
85
|
+
value = (value + Math.imul(value ^ (value >>> 7), 61 | value)) ^ value
|
|
86
|
+
return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const pick = <A>(next: () => number, values: ReadonlyArray<A>): A => values[Math.floor(next() * values.length)]!
|
|
91
|
+
|
|
92
|
+
const chance = (next: () => number, probability: number): boolean => next() < probability
|
|
93
|
+
|
|
94
|
+
// Names both peers walk identically: no separator, no dot prefix outside the
|
|
95
|
+
// two deliberate hidden entries, no name a directory skip list contains, and
|
|
96
|
+
// no character that would need quoting on a command line.
|
|
97
|
+
const stems = ["alpha", "beta", "gamma", "Delta", "e-psilon", "ze_ta", "eta2", "th.eta", "iota", "kappa"] as const
|
|
98
|
+
const extensions = [".ts", ".js", ".txt", ".py", ".md"] as const
|
|
99
|
+
const directories = ["", "one", "one/two", "three", "three/four/five"] as const
|
|
100
|
+
|
|
101
|
+
// Lines drawn from a vocabulary that mixes the needle, near misses, regex
|
|
102
|
+
// metacharacters that must survive `fixedStrings`, and definition headers the
|
|
103
|
+
// `symbols` option reads.
|
|
104
|
+
const lines = [
|
|
105
|
+
"needle",
|
|
106
|
+
"a needle in the line",
|
|
107
|
+
"Needle capitalised",
|
|
108
|
+
"no match here",
|
|
109
|
+
"needle? with a question mark",
|
|
110
|
+
"a+b and x*y and [z]",
|
|
111
|
+
"def widen(value):",
|
|
112
|
+
"class Widget:",
|
|
113
|
+
" needle = value",
|
|
114
|
+
"function widen(value) {",
|
|
115
|
+
"",
|
|
116
|
+
"trailing needle"
|
|
117
|
+
] as const
|
|
118
|
+
|
|
119
|
+
const patterns = ["needle", "Needle", "needle?", "a+b", "[z]", "^needle$", "widen", "no match at all"] as const
|
|
120
|
+
|
|
121
|
+
const globPatterns = ["**/*.ts", "*.ts", "**/*", "one/**/*.ts", "**/*.{ts,js}", "**/alpha*"] as const
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Builds a reproducible tree and call batch from a seed.
|
|
125
|
+
*
|
|
126
|
+
* The same seed and root always produce the same plan, so a divergence a run
|
|
127
|
+
* reports can be replayed exactly by rerunning that seed.
|
|
128
|
+
*
|
|
129
|
+
* @category generators
|
|
130
|
+
* @since 1.0.0
|
|
131
|
+
*/
|
|
132
|
+
export const plan = (options: {
|
|
133
|
+
readonly seed: number
|
|
134
|
+
readonly root: string
|
|
135
|
+
readonly files?: number | undefined
|
|
136
|
+
readonly calls?: number | undefined
|
|
137
|
+
}): Plan => {
|
|
138
|
+
const next = random(options.seed)
|
|
139
|
+
const fileCount = options.files ?? 12
|
|
140
|
+
const callCount = options.calls ?? 12
|
|
141
|
+
const files: Array<GeneratedFile> = []
|
|
142
|
+
for (let index = 0; index < fileCount; index++) {
|
|
143
|
+
const directory = pick(next, directories)
|
|
144
|
+
// The index makes every generated path unique, including repeated random picks.
|
|
145
|
+
const name = `${pick(next, stems)}${index}${pick(next, extensions)}`
|
|
146
|
+
const path = directory === "" ? name : `${directory}/${name}`
|
|
147
|
+
const body: Array<string> = []
|
|
148
|
+
for (let line = 0; line < 1 + Math.floor(next() * 8); line++) body.push(pick(next, lines))
|
|
149
|
+
files.push({ path, content: `${body.join("\n")}${chance(next, 0.8) ? "\n" : ""}` })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Always exercise precedence, anchoring, directory pruning and nested
|
|
153
|
+
// negation alongside each seed's generated content (without a git repo).
|
|
154
|
+
if (fileCount > 0) {
|
|
155
|
+
files.push(
|
|
156
|
+
{ path: ".gitignore", content: "*.ignored\n!keep.ignored\n/root-only.ts\ncache/\n" },
|
|
157
|
+
{ path: "drop.ignored", content: "needle\n" },
|
|
158
|
+
{ path: "keep.ignored", content: "needle\n" },
|
|
159
|
+
{ path: "root-only.ts", content: "needle\n" },
|
|
160
|
+
{ path: "cache/a.ts", content: "needle\n" },
|
|
161
|
+
{ path: "nested/.gitignore", content: "!keep.ignored\n/anchored.ts\nframes/\n" },
|
|
162
|
+
{ path: "nested/keep.ignored", content: "needle\n" },
|
|
163
|
+
{ path: "nested/drop.ignored", content: "needle\n" },
|
|
164
|
+
{ path: "nested/anchored.ts", content: "needle\n" },
|
|
165
|
+
{ path: "nested/deep/anchored.ts", content: "needle\n" },
|
|
166
|
+
{ path: "nested/root-only.ts", content: "needle\n" },
|
|
167
|
+
{ path: "nested/frames/a.ts", content: "needle\n" },
|
|
168
|
+
{ path: "other/frames", content: "needle\n" }
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const roots = [options.root, ...files.map((entry) => `${options.root}/${entry.path}`)]
|
|
173
|
+
const grep: Array<Search.GrepInput> = []
|
|
174
|
+
for (let index = 0; index < callCount; index++) {
|
|
175
|
+
const fixedStrings = chance(next, 0.4)
|
|
176
|
+
// `Grep.run` refuses `ignoreCase` together with `smartCase` as mutually
|
|
177
|
+
// exclusive, so a generated call carrying both would be exploring ground
|
|
178
|
+
// the contract does not describe.
|
|
179
|
+
const ignoreCase = chance(next, 0.25)
|
|
180
|
+
grep.push({
|
|
181
|
+
pattern: pick(next, patterns),
|
|
182
|
+
root: chance(next, 0.75) ? options.root : pick(next, roots),
|
|
183
|
+
fixedStrings,
|
|
184
|
+
ignoreCase,
|
|
185
|
+
smartCase: !ignoreCase && chance(next, 0.3),
|
|
186
|
+
globs: chance(next, 0.4) ? [pick(next, ["*.ts", "*.txt", "!*.js", "**/*.ts"])] : [],
|
|
187
|
+
beforeContext: Math.floor(next() * 3),
|
|
188
|
+
afterContext: Math.floor(next() * 3),
|
|
189
|
+
...(chance(next, 0.3) ? { maxCount: 1 + Math.floor(next() * 2) } : {}),
|
|
190
|
+
filesWithMatches: chance(next, 0.25),
|
|
191
|
+
hidden: chance(next, 0.5),
|
|
192
|
+
noIgnore: chance(next, 0.25),
|
|
193
|
+
symbols: chance(next, 0.3),
|
|
194
|
+
limit: 1 + Math.floor(next() * 40)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const glob: Array<Search.GlobInput> = []
|
|
199
|
+
for (let index = 0; index < callCount; index++) {
|
|
200
|
+
glob.push({
|
|
201
|
+
pattern: pick(next, globPatterns),
|
|
202
|
+
root: options.root,
|
|
203
|
+
hidden: chance(next, 0.5),
|
|
204
|
+
noIgnore: chance(next, 0.25),
|
|
205
|
+
limit: 1 + Math.floor(next() * 20)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { seed: options.seed, root: options.root, files, grep, glob }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Writes a plan's tree under its root.
|
|
214
|
+
*
|
|
215
|
+
* @category generators
|
|
216
|
+
* @since 1.0.0
|
|
217
|
+
*/
|
|
218
|
+
export const materialize = (
|
|
219
|
+
target: Plan
|
|
220
|
+
): Effect.Effect<void, never, FileSystem.FileSystem | Path.Path> =>
|
|
221
|
+
Effect.gen(function*() {
|
|
222
|
+
const fileSystem = yield* FileSystem.FileSystem
|
|
223
|
+
const path = yield* Path.Path
|
|
224
|
+
yield* Effect.forEach(target.files, (entry) => {
|
|
225
|
+
const absolute = path.join(target.root, ...entry.path.split("/"))
|
|
226
|
+
return fileSystem.makeDirectory(path.dirname(absolute), { recursive: true }).pipe(
|
|
227
|
+
Effect.andThen(fileSystem.writeFileString(absolute, entry.content)),
|
|
228
|
+
Effect.orDie
|
|
229
|
+
)
|
|
230
|
+
}, { discard: true })
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
/** A typed answer rendered so two of them can be compared as text. */
|
|
234
|
+
const rendered = <A>(
|
|
235
|
+
effect: Effect.Effect<A, StdError.StdError>
|
|
236
|
+
): Effect.Effect<string> =>
|
|
237
|
+
effect.pipe(
|
|
238
|
+
Effect.match({
|
|
239
|
+
onSuccess: (value) => JSON.stringify(value),
|
|
240
|
+
onFailure: (error) => JSON.stringify({ failure: error.code })
|
|
241
|
+
})
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Runs every call in a plan through two implementations and reports the
|
|
246
|
+
* answers that differ.
|
|
247
|
+
*
|
|
248
|
+
* A failure is compared like any other answer: two peers that refuse the same
|
|
249
|
+
* call with the same code agree, and one that refuses where the other answers
|
|
250
|
+
* has diverged.
|
|
251
|
+
*
|
|
252
|
+
* @category conformance
|
|
253
|
+
* @since 1.0.0
|
|
254
|
+
*/
|
|
255
|
+
export const compare = (options: {
|
|
256
|
+
readonly plan: Plan
|
|
257
|
+
readonly subject: Search.Search
|
|
258
|
+
readonly reference: Search.Search
|
|
259
|
+
}): Effect.Effect<ReadonlyArray<Divergence>> =>
|
|
260
|
+
Effect.gen(function*() {
|
|
261
|
+
const divergences: Array<Divergence> = []
|
|
262
|
+
for (const input of options.plan.grep) {
|
|
263
|
+
const subject = yield* rendered(options.subject.grep(input))
|
|
264
|
+
const reference = yield* rendered(options.reference.grep(input))
|
|
265
|
+
if (subject !== reference) divergences.push({ call: "grep", input, subject, reference })
|
|
266
|
+
}
|
|
267
|
+
for (const input of options.plan.glob) {
|
|
268
|
+
const subject = yield* rendered(options.subject.glob(input))
|
|
269
|
+
const reference = yield* rendered(options.reference.glob(input))
|
|
270
|
+
if (subject !== reference) divergences.push({ call: "glob", input, subject, reference })
|
|
271
|
+
}
|
|
272
|
+
return divergences
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Renders divergences as the report a failing conformance run should print.
|
|
277
|
+
*
|
|
278
|
+
* @category conformance
|
|
279
|
+
* @since 1.0.0
|
|
280
|
+
*/
|
|
281
|
+
export const report = (divergences: ReadonlyArray<Divergence>): string =>
|
|
282
|
+
divergences.map((divergence) =>
|
|
283
|
+
[
|
|
284
|
+
`${divergence.call}(${JSON.stringify(divergence.input)})`,
|
|
285
|
+
` subject: ${divergence.subject}`,
|
|
286
|
+
` reference: ${divergence.reference}`
|
|
287
|
+
].join("\n")
|
|
288
|
+
).join("\n\n")
|