mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-27 19:17:36 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10489b2c8b | ||
|
|
fdc3eddc8a | ||
|
|
eb15a9b789 | ||
|
|
d74adc2beb |
@@ -444,7 +444,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/location`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -469,7 +469,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/agent`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -481,7 +481,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/agent/${encodeURIComponent(input.agentID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -495,7 +495,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/plugin`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -508,7 +508,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input?.["location"] },
|
||||
body: { target: input?.["target"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -521,7 +521,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { targets: input["targets"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1109,7 +1109,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/model`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1121,7 +1121,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/model/default`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1149,7 +1149,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/provider`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1161,7 +1161,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/provider/${encodeURIComponent(input.providerID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1175,7 +1175,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/integration`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1187,7 +1187,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1201,7 +1201,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { url: input["url"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1216,7 +1216,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { key: input["key"], answer: input["answer"], label: input["label"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1231,7 +1231,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { methodID: input["methodID"], answer: input["answer"], label: input["label"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1243,7 +1243,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1256,7 +1256,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { code: input["code"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1268,7 +1268,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1283,7 +1283,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { methodID: input["methodID"], label: input["label"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1295,7 +1295,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/command/${encodeURIComponent(input.attemptID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1307,7 +1307,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/command/${encodeURIComponent(input.attemptID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1322,7 +1322,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/mcp`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1335,7 +1335,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { config: input["config"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1347,7 +1347,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1359,7 +1359,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}/connect`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1371,7 +1371,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}/disconnect`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1384,7 +1384,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/mcp/resource`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1430,7 +1430,7 @@ export function make(options: ClientOptions) {
|
||||
project: {
|
||||
list: (requestOptions?: RequestOptions) =>
|
||||
request<ProjectListOutput>(
|
||||
{ method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
|
||||
{ method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [400, 401, 403], empty: false },
|
||||
requestOptions,
|
||||
),
|
||||
update: (input: ProjectUpdateInput, requestOptions?: RequestOptions) =>
|
||||
@@ -1445,7 +1445,7 @@ export function make(options: ClientOptions) {
|
||||
commands: input["commands"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1459,7 +1459,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/form`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1474,7 +1474,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/permission/request`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1488,7 +1488,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/permission/saved`,
|
||||
query: { projectID: input?.["projectID"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1499,7 +1499,7 @@ export function make(options: ClientOptions) {
|
||||
method: "DELETE",
|
||||
path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1568,7 +1568,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/fs/read/${encodePath(input.path)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
binary: true,
|
||||
},
|
||||
@@ -1581,7 +1581,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/fs/list`,
|
||||
query: { location: input?.["location"], path: input?.["path"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1593,7 +1593,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/fs/find`,
|
||||
query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1606,7 +1606,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"], path: input["path"] },
|
||||
body: input["payload"],
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
binaryBody: true,
|
||||
},
|
||||
@@ -1621,7 +1621,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/command`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1635,7 +1635,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/skill`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1650,7 +1650,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { input: input["input"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 500],
|
||||
declaredStatuses: [400, 401, 403, 500],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1671,7 +1671,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/pty`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1690,7 +1690,7 @@ export function make(options: ClientOptions) {
|
||||
env: input?.["env"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1702,7 +1702,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1715,7 +1715,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { title: input["title"], size: input["size"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1727,7 +1727,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1881,7 +1881,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1899,7 +1899,7 @@ export function make(options: ClientOptions) {
|
||||
metadata: input["metadata"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1911,7 +1911,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1923,7 +1923,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}/output`,
|
||||
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1935,7 +1935,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1949,7 +1949,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/reference`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2019,7 +2019,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2031,7 +2031,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs/base`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2043,7 +2043,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs/status`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2056,7 +2056,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs/branch`,
|
||||
query: { location: input?.["location"], search: input?.["search"], limit: input?.["limit"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2069,7 +2069,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs/diff`,
|
||||
query: { location: input["location"], mode: input["mode"], base: input["base"], context: input["context"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2125,7 +2125,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/websearch/provider`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2138,7 +2138,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { query: input["query"], providerID: input["providerID"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2152,14 +2152,20 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/config`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
shells: (requestOptions?: RequestOptions) =>
|
||||
request<ConfigShellsOutput>(
|
||||
{ method: "GET", path: `/api/config/shell`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/config/shell`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
update: (input: ConfigUpdateInput, requestOptions?: RequestOptions) =>
|
||||
@@ -2169,7 +2175,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/experimental/config`,
|
||||
body: { shell: input["shell"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
|
||||
@@ -2484,6 +2484,22 @@ export type UnauthorizedError = { readonly _tag: "UnauthorizedError"; readonly m
|
||||
export const isUnauthorizedError = (value: unknown): value is UnauthorizedError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnauthorizedError"
|
||||
|
||||
export type LocationDirectoryNotFoundError = {
|
||||
readonly _tag: "LocationDirectoryNotFoundError"
|
||||
readonly directory: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isLocationDirectoryNotFoundError = (value: unknown): value is LocationDirectoryNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "LocationDirectoryNotFoundError"
|
||||
|
||||
export type LocationPermissionDeniedError = {
|
||||
readonly _tag: "LocationPermissionDeniedError"
|
||||
readonly directory: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isLocationPermissionDeniedError = (value: unknown): value is LocationPermissionDeniedError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "LocationPermissionDeniedError"
|
||||
|
||||
export type ServiceUnavailableError = {
|
||||
readonly _tag: "ServiceUnavailableError"
|
||||
readonly message: string
|
||||
|
||||
@@ -2,6 +2,7 @@ import { buildLocationServiceMap } from "../location-services.js"
|
||||
import { LocationServiceMap } from "../location-service-map.js"
|
||||
import { LayerNode } from "@opencode/util/effect/layer-node"
|
||||
import { makeGlobalNode } from "@opencode/util/effect/app-node"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Instance } from "../instance/service.js"
|
||||
|
||||
@@ -22,7 +23,16 @@ const instances = makeGlobalNode({
|
||||
export function build<A, E>(root: LayerNode.Graph<A, E>, replacements: LayerNode.Replacements = []) {
|
||||
const bindings = [Instance.node.replace(instances), ...replacements]
|
||||
return LayerNode.compile(root, {
|
||||
replacements: [LocationServiceMap.node.replace(buildLocationServiceMap(bindings)), ...bindings],
|
||||
replacements: [
|
||||
LocationServiceMap.node.replace(
|
||||
makeGlobalNode({
|
||||
service: LocationServiceMap.Service,
|
||||
layer: buildLocationServiceMap(bindings),
|
||||
deps: [FSUtil.node],
|
||||
}),
|
||||
),
|
||||
...bindings,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,26 @@ import { Instance } from "./instance.js"
|
||||
import { Location } from "./location.js"
|
||||
import { LocationLifecycle } from "./location-lifecycle.js"
|
||||
import { LocationServiceMap } from "./location-service-map.js"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import type { PlatformError } from "effect/PlatformError"
|
||||
|
||||
export { LocationServiceMap } from "./location-service-map.js"
|
||||
|
||||
export type LocationServices = Instance.Services
|
||||
export type LocationError = Instance.Error
|
||||
|
||||
export class DirectoryNotFoundError extends Error {
|
||||
constructor(readonly directory: string) {
|
||||
super(`Project directory not found: ${directory}`)
|
||||
}
|
||||
}
|
||||
|
||||
export class PermissionDeniedError extends Error {
|
||||
constructor(readonly directory: string) {
|
||||
super(`Cannot access project directory: ${directory}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function buildLocationServiceMap(
|
||||
replacements: LayerNode.Replacements = [],
|
||||
): Layer.Layer<LocationServiceMap.Service> {
|
||||
@@ -17,6 +31,7 @@ export function buildLocationServiceMap(
|
||||
LocationServiceMap.Service,
|
||||
Effect.gen(function* () {
|
||||
const owner = yield* Effect.scope
|
||||
const fs = yield* Effect.serviceOption(FSUtil.Service)
|
||||
const builds = MutableHashMap.empty<Location.Ref, { close?: Effect.Effect<void> }>()
|
||||
const inner: LayerMap.LayerMap<Location.Ref, LocationServices> = yield* LayerMap.make(
|
||||
(ref: Location.Ref) => {
|
||||
@@ -24,7 +39,10 @@ export function buildLocationServiceMap(
|
||||
MutableHashMap.set(builds, ref, build)
|
||||
return Layer.fromBuild((memoMap, scope) =>
|
||||
Effect.suspend(() =>
|
||||
Layer.buildWithMemoMap(Instance.layer(ref, { replacements: bindings }), memoMap, scope),
|
||||
(ref.workspaceID || Option.isNone(fs) ? Effect.void : checkDirectory(fs.value, ref)).pipe(
|
||||
Effect.orDie,
|
||||
Effect.andThen(Layer.buildWithMemoMap(Instance.layer(ref, { replacements: bindings }), memoMap, scope)),
|
||||
),
|
||||
).pipe(
|
||||
Effect.onExit((exit) => {
|
||||
const finish = Effect.suspend(() => {
|
||||
@@ -52,8 +70,7 @@ export function buildLocationServiceMap(
|
||||
),
|
||||
)
|
||||
},
|
||||
// Retain healthy graphs. Boot failures, not local filesystem probes,
|
||||
// decide whether a location (including workspace placement) can retry.
|
||||
// Retain healthy graphs; failed checks and boot failures retry on the next use.
|
||||
{ idleTimeToLive: Duration.infinity },
|
||||
)
|
||||
const map = {
|
||||
@@ -86,3 +103,26 @@ export function buildLocationServiceMap(
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function checkDirectory(fs: FSUtil.Interface, ref: Location.Ref) {
|
||||
return fs.realPath(ref.directory).pipe(
|
||||
Effect.asVoid,
|
||||
Effect.catchReason("PlatformError", "NotFound", () => Effect.fail(new DirectoryNotFoundError(ref.directory))),
|
||||
Effect.catchTag("PlatformError", (error) =>
|
||||
isPermissionDenied(error) ? Effect.fail(new PermissionDeniedError(ref.directory)) : Effect.die(error),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Effect maps EACCES to PermissionDenied, but macOS privacy blocks report EPERM as Unknown.
|
||||
export function isPermissionDenied(error: PlatformError) {
|
||||
if (error.reason._tag === "PermissionDenied") return true
|
||||
const cause = error.cause
|
||||
return (
|
||||
error.reason._tag === "Unknown" &&
|
||||
typeof cause === "object" &&
|
||||
cause !== null &&
|
||||
"code" in cause &&
|
||||
cause.code === "EPERM"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { InvalidRequestError, SessionNotFoundError } from "./errors.js"
|
||||
import {
|
||||
InvalidRequestError,
|
||||
LocationDirectoryNotFoundError,
|
||||
LocationPermissionDeniedError,
|
||||
SessionNotFoundError,
|
||||
} from "./errors.js"
|
||||
import { makeDefaultApi } from "./api.js"
|
||||
import type { Api } from "./api.js"
|
||||
import type { Context } from "effect"
|
||||
@@ -7,6 +12,7 @@ import type { EventGroup } from "./groups/event.js"
|
||||
|
||||
class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware>()(
|
||||
"@opencode/client/LocationMiddleware",
|
||||
{ error: [LocationDirectoryNotFoundError, LocationPermissionDeniedError] },
|
||||
) {}
|
||||
|
||||
class SessionLocationMiddleware extends HttpApiMiddleware.Service<SessionLocationMiddleware>()(
|
||||
|
||||
@@ -11,6 +11,24 @@ export class InvalidRequestError extends Schema.TaggedError<InvalidRequestError>
|
||||
{ httpApiStatus: 400 },
|
||||
) {}
|
||||
|
||||
export class LocationDirectoryNotFoundError extends Schema.TaggedError<LocationDirectoryNotFoundError>()(
|
||||
"LocationDirectoryNotFoundError",
|
||||
{
|
||||
directory: Schema.String,
|
||||
message: Schema.String,
|
||||
},
|
||||
{ httpApiStatus: 400 },
|
||||
) {}
|
||||
|
||||
export class LocationPermissionDeniedError extends Schema.TaggedError<LocationPermissionDeniedError>()(
|
||||
"LocationPermissionDeniedError",
|
||||
{
|
||||
directory: Schema.String,
|
||||
message: Schema.String,
|
||||
},
|
||||
{ httpApiStatus: 403 },
|
||||
) {}
|
||||
|
||||
export class RpcError extends Schema.TaggedError<RpcError>()(
|
||||
"RpcError",
|
||||
{
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Location } from "@opencode/core/location"
|
||||
import { LocationServiceMap } from "@opencode/core/location-services"
|
||||
import { DirectoryNotFoundError, LocationServiceMap, PermissionDeniedError } from "@opencode/core/location-services"
|
||||
import { AbsolutePath } from "@opencode/core/schema"
|
||||
import { Session } from "@opencode/core/session"
|
||||
import { InvalidRequestError } from "@opencode/protocol/errors"
|
||||
import {
|
||||
InvalidRequestError,
|
||||
LocationDirectoryNotFoundError,
|
||||
LocationPermissionDeniedError,
|
||||
} from "@opencode/protocol/errors"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpServerRequest } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
@@ -12,6 +16,7 @@ export type LocationServices = Layer.Success<ReturnType<(typeof LocationServiceM
|
||||
|
||||
export class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware, { provides: LocationServices }>()(
|
||||
"@opencode/HttpApiLocation",
|
||||
{ error: [LocationDirectoryNotFoundError, LocationPermissionDeniedError] },
|
||||
) {}
|
||||
|
||||
export function response<A, E, R>(data: Effect.Effect<A, E, R>) {
|
||||
@@ -61,8 +66,21 @@ export const layer = Layer.effect(
|
||||
return LocationMiddleware.of((effect) =>
|
||||
Effect.gen(function* () {
|
||||
const request = yield* HttpServerRequest.HttpServerRequest
|
||||
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))))
|
||||
return yield* effect.pipe(
|
||||
Effect.provide(locations.get(requestRef(request))),
|
||||
Effect.catchDefect(locationFailure),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
export function locationFailure(
|
||||
defect: unknown,
|
||||
): Effect.Effect<never, LocationDirectoryNotFoundError | LocationPermissionDeniedError> {
|
||||
if (defect instanceof DirectoryNotFoundError)
|
||||
return Effect.fail(new LocationDirectoryNotFoundError({ directory: defect.directory, message: defect.message }))
|
||||
if (defect instanceof PermissionDeniedError)
|
||||
return Effect.fail(new LocationPermissionDeniedError({ directory: defect.directory, message: defect.message }))
|
||||
return Effect.die(defect)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Instance } from "@opencode/core/instance/service"
|
||||
import { LocationServiceMap } from "@opencode/core/location-services"
|
||||
import { Session } from "@opencode/core/session"
|
||||
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors"
|
||||
import {
|
||||
InvalidRequestError,
|
||||
LocationDirectoryNotFoundError,
|
||||
LocationPermissionDeniedError,
|
||||
SessionNotFoundError,
|
||||
} from "@opencode/protocol/errors"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpRouter, HttpServerRequest } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { requestRef, sessionInfo, type LocationServices } from "../location"
|
||||
import { locationFailure, requestRef, sessionInfo, type LocationServices } from "../location"
|
||||
|
||||
export class FormLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
FormLocationMiddleware,
|
||||
{ provides: LocationServices }
|
||||
>()("@opencode/HttpApiFormLocation", {
|
||||
error: [InvalidRequestError, SessionNotFoundError],
|
||||
error: [InvalidRequestError, SessionNotFoundError, LocationDirectoryNotFoundError, LocationPermissionDeniedError],
|
||||
}) {}
|
||||
|
||||
export const formLocationLayer = Layer.effect(
|
||||
@@ -34,7 +39,7 @@ export const formLocationLayer = Layer.effect(
|
||||
|
||||
const session = yield* sessionInfo(sessions, route.params.sessionID)
|
||||
return yield* effect.pipe(instances.provide(session))
|
||||
}),
|
||||
}).pipe(Effect.catchDefect(locationFailure)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -3,14 +3,19 @@ import { Session } from "@opencode/core/session"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpRouter } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors"
|
||||
import { sessionInfo, type LocationServices } from "../location"
|
||||
import {
|
||||
InvalidRequestError,
|
||||
LocationDirectoryNotFoundError,
|
||||
LocationPermissionDeniedError,
|
||||
SessionNotFoundError,
|
||||
} from "@opencode/protocol/errors"
|
||||
import { locationFailure, sessionInfo, type LocationServices } from "../location"
|
||||
|
||||
export class SessionLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
SessionLocationMiddleware,
|
||||
{ provides: LocationServices }
|
||||
>()("@opencode/HttpApiSessionLocation", {
|
||||
error: [InvalidRequestError, SessionNotFoundError],
|
||||
error: [InvalidRequestError, SessionNotFoundError, LocationDirectoryNotFoundError, LocationPermissionDeniedError],
|
||||
}) {}
|
||||
|
||||
export const sessionLocationLayer = Layer.effect(
|
||||
@@ -24,7 +29,7 @@ export const sessionLocationLayer = Layer.effect(
|
||||
const route = yield* HttpRouter.RouteContext
|
||||
const session = yield* sessionInfo(sessions, route.params.sessionID)
|
||||
return yield* effect.pipe(instances.provide(session))
|
||||
}),
|
||||
}).pipe(Effect.catchDefect(locationFailure)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ import { PluginUpdate } from "@opencode/core/plugin/update"
|
||||
import { SdkPlugins } from "@opencode/core/plugin/sdk"
|
||||
import { WellKnown } from "@opencode/core/wellknown"
|
||||
import { Workspace } from "@opencode/core/workspace"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import { Watcher } from "@opencode/core/filesystem/watcher"
|
||||
import { HttpRouter } from "effect/unstable/http"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
@@ -51,6 +52,7 @@ import type { ServerOptions } from "./options"
|
||||
|
||||
const applicationServiceNodes = [
|
||||
Global.node,
|
||||
FSUtil.node,
|
||||
Database.node,
|
||||
Bus.node,
|
||||
EventLogger.node,
|
||||
|
||||
@@ -5,8 +5,10 @@ import path from "node:path"
|
||||
import { Agent } from "@opencode/schema/agent"
|
||||
import { Integration } from "@opencode/schema/integration"
|
||||
import { ServerInfo } from "@opencode/protocol/groups/server"
|
||||
import { Effect, Schedule, Schema } from "effect"
|
||||
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { Context, Effect, Layer, Schedule, Schema } from "effect"
|
||||
import { tmpdir, tmpdirScoped } from "../../core/test/fixture/tmpdir"
|
||||
import { it } from "../../core/test/lib/effect"
|
||||
import { ServerFetch } from "../src/fetch"
|
||||
|
||||
@@ -105,7 +107,9 @@ it.live("serves the HttpApi and enforces Basic auth like the Node server", () =>
|
||||
),
|
||||
)
|
||||
expect(response.status).toBe(200)
|
||||
const body = yield* Effect.promise(() => response.json()).pipe(Effect.flatMap(Schema.decodeUnknownEffect(ServerInfo)))
|
||||
const body = yield* Effect.promise(() => response.json()).pipe(
|
||||
Effect.flatMap(Schema.decodeUnknownEffect(ServerInfo)),
|
||||
)
|
||||
expect(body.version).toBe("test-version")
|
||||
expect(body.paths.tmp).toEndWith("opencode")
|
||||
}),
|
||||
@@ -121,6 +125,103 @@ it.live("activates credentials through the HttpApi", () =>
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("reports a missing project directory and recovers when it returns", () =>
|
||||
Effect.gen(function* () {
|
||||
const handler = yield* ServerFetch.make(options)
|
||||
const directory = yield* tmpdirScoped()
|
||||
const missing = path.join(directory.path, "moved-project")
|
||||
const request = (endpoint: string) =>
|
||||
handler(
|
||||
new Request(`http://opencode.local${endpoint}`, {
|
||||
headers: { "x-opencode-directory": encodeURIComponent(missing) },
|
||||
}),
|
||||
)
|
||||
|
||||
const response = yield* Effect.promise(() => request("/api/integration"))
|
||||
expect(response.status).toBe(400)
|
||||
expect(yield* Effect.promise(() => response.json())).toMatchObject({
|
||||
_tag: "LocationDirectoryNotFoundError",
|
||||
directory: missing,
|
||||
})
|
||||
expect((yield* Effect.promise(() => request("/api/location"))).status).toBe(400)
|
||||
|
||||
yield* Effect.promise(() => fs.mkdir(missing))
|
||||
expect((yield* Effect.promise(() => request("/api/integration"))).status).toBe(200)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("checks a directory once per location boot, not on every request", () =>
|
||||
Effect.gen(function* () {
|
||||
const directory = yield* tmpdirScoped()
|
||||
const checked: string[] = []
|
||||
const filesystem = FSUtil.layer.pipe(
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
Layer.flatMap((context) => {
|
||||
const fs = Context.get(context, FSUtil.Service)
|
||||
return Layer.succeed(
|
||||
FSUtil.Service,
|
||||
FSUtil.Service.of({
|
||||
...fs,
|
||||
realPath: (input) => {
|
||||
if (input === directory.path) checked.push(input)
|
||||
return fs.realPath(input)
|
||||
},
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
const handler = yield* ServerFetch.make(options, { overrides: [FSUtil.node.replace(filesystem)] })
|
||||
const request = (endpoint: string, method = "GET") =>
|
||||
handler(
|
||||
new Request(`http://opencode.local${endpoint}`, {
|
||||
method,
|
||||
headers: { "x-opencode-directory": encodeURIComponent(directory.path) },
|
||||
}),
|
||||
)
|
||||
|
||||
expect((yield* Effect.promise(() => request("/api/location"))).status).toBe(200)
|
||||
const afterBoot = checked.length
|
||||
expect(afterBoot).toBeGreaterThan(0)
|
||||
expect((yield* Effect.promise(() => request("/api/location"))).status).toBe(200)
|
||||
expect(checked).toHaveLength(afterBoot)
|
||||
|
||||
expect((yield* Effect.promise(() => request("/api/location/reload", "POST"))).status).toBe(204)
|
||||
const afterReload = checked.length
|
||||
expect(afterReload).toBeGreaterThan(afterBoot)
|
||||
expect((yield* Effect.promise(() => request("/api/location"))).status).toBe(200)
|
||||
expect(checked).toHaveLength(afterReload)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("reports denied project access without breaking other locations", () =>
|
||||
Effect.gen(function* () {
|
||||
if (process.platform === "win32") return
|
||||
const handler = yield* ServerFetch.make(options)
|
||||
const root = yield* tmpdirScoped()
|
||||
const parent = path.join(root.path, "private")
|
||||
const directory = path.join(parent, "project")
|
||||
yield* Effect.promise(() => fs.mkdir(directory, { recursive: true }))
|
||||
yield* Effect.addFinalizer(() => Effect.promise(() => fs.chmod(parent, 0o700)))
|
||||
yield* Effect.promise(() => fs.chmod(parent, 0o000))
|
||||
|
||||
const request = () =>
|
||||
handler(
|
||||
new Request("http://opencode.local/api/plugin", {
|
||||
headers: { "x-opencode-directory": encodeURIComponent(directory) },
|
||||
}),
|
||||
)
|
||||
const response = yield* Effect.promise(request)
|
||||
expect(response.status).toBe(403)
|
||||
expect(yield* Effect.promise(() => response.json())).toMatchObject({
|
||||
_tag: "LocationPermissionDeniedError",
|
||||
directory,
|
||||
})
|
||||
|
||||
yield* Effect.promise(() => fs.chmod(parent, 0o700))
|
||||
expect((yield* Effect.promise(request)).status).toBe(200)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("serves unauthenticated and answers CORS preflight when no password is configured", () =>
|
||||
Effect.gen(function* () {
|
||||
const handler = yield* ServerFetch.make(options)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { PlatformError } from "effect"
|
||||
import { isPermissionDenied } from "../../core/src/location-services"
|
||||
|
||||
// Reproduces the error macOS returns when privacy settings block a folder.
|
||||
const macPrivacyDenied = () =>
|
||||
PlatformError.systemError({
|
||||
_tag: "Unknown",
|
||||
module: "FileSystem",
|
||||
method: "realPath",
|
||||
pathOrDescriptor: "/Users/example/Documents",
|
||||
cause: Object.assign(new Error("EPERM: operation not permitted, lstat '/Users/example/Documents'"), {
|
||||
code: "EPERM",
|
||||
}),
|
||||
})
|
||||
|
||||
test("recognizes the macOS privacy denial reported as Unknown with an EPERM cause", () => {
|
||||
expect(isPermissionDenied(macPrivacyDenied())).toBe(true)
|
||||
expect(
|
||||
isPermissionDenied(
|
||||
PlatformError.systemError({ _tag: "PermissionDenied", module: "FileSystem", method: "realPath" }),
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("does not treat other filesystem failures as permission denied", () => {
|
||||
expect(
|
||||
isPermissionDenied(PlatformError.systemError({ _tag: "NotFound", module: "FileSystem", method: "realPath" })),
|
||||
).toBe(false)
|
||||
expect(
|
||||
isPermissionDenied(
|
||||
PlatformError.systemError({
|
||||
_tag: "Unknown",
|
||||
module: "FileSystem",
|
||||
method: "realPath",
|
||||
cause: Object.assign(new Error("EIO: i/o error"), { code: "EIO" }),
|
||||
}),
|
||||
),
|
||||
).toBe(false)
|
||||
})
|
||||
Reference in New Issue
Block a user