@actiondock/core 2.7.0 → 2.9.0
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/README.md +77 -64
- package/dist/catalog/action-catalog.d.ts +56 -0
- package/dist/catalog/action-catalog.js +202 -0
- package/dist/catalog/discovery.d.ts +60 -0
- package/dist/catalog/discovery.js +119 -0
- package/dist/catalog/graph.d.ts +190 -0
- package/dist/catalog/graph.js +589 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -5
- package/dist/catalog/resolve-action.d.ts +43 -0
- package/dist/catalog/resolve-action.js +119 -0
- package/dist/catalog/resolve-playbook.d.ts +26 -0
- package/dist/catalog/resolve-playbook.js +114 -0
- package/dist/doctor/checks/project.js +27 -8
- package/dist/doctor/checks/registry.js +2 -2
- package/dist/doctor/checks/runtime.js +34 -1
- package/dist/errors.d.ts +106 -10
- package/dist/errors.js +105 -10
- package/dist/execution/service.d.ts +18 -16
- package/dist/execution/service.js +183 -252
- package/dist/execution/types.d.ts +25 -69
- package/dist/execution/types.js +2 -1
- package/dist/filter/intent.d.ts +8 -9
- package/dist/filter/intent.js +49 -10
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +4 -0
- package/dist/host/host.d.ts +74 -32
- package/dist/host/host.js +585 -336
- package/dist/host/routing.d.ts +15 -48
- package/dist/host/routing.js +90 -153
- package/dist/host/types.d.ts +50 -17
- package/dist/index.d.ts +21 -24
- package/dist/index.js +19 -23
- package/dist/input/advice.d.ts +5 -1
- package/dist/input/advice.js +163 -214
- package/dist/input/describe.d.ts +1 -1
- package/dist/input/flat-errors.d.ts +5 -6
- package/dist/input/flat-errors.js +6 -6
- package/dist/input/flat-materializer.js +42 -44
- package/dist/invocation/index.d.ts +2 -0
- package/dist/invocation/index.js +2 -0
- package/dist/invocation/policy.d.ts +157 -0
- package/dist/invocation/policy.js +231 -0
- package/dist/invocation/types.d.ts +172 -0
- package/dist/invocation/types.js +97 -0
- package/dist/ipc/diagnostic.d.ts +0 -12
- package/dist/ipc/diagnostic.js +0 -21
- package/dist/ipc/host.d.ts +4 -4
- package/dist/ipc/host.js +44 -47
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/ipc/service.d.ts +40 -0
- package/dist/ipc/service.js +349 -0
- package/dist/ipc/types.d.ts +21 -15
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/module-loader.d.ts +63 -0
- package/dist/node/module-loader.js +112 -0
- package/dist/package/index.d.ts +23 -0
- package/dist/package/index.js +18 -0
- package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
- package/dist/{app/app.js → package/runtime.js} +140 -95
- package/dist/{app → package}/static-index.js +58 -41
- package/dist/package/storage-view.d.ts +13 -0
- package/dist/package/storage-view.js +22 -0
- package/dist/{app → package}/types.d.ts +76 -57
- package/dist/package/types.js +22 -0
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/node.d.ts +48 -0
- package/dist/platform/node.js +96 -0
- package/dist/platform/types.d.ts +0 -9
- package/dist/process/control-arbiter.d.ts +2 -1
- package/dist/process/control-arbiter.js +1 -2
- package/dist/process/driver.d.ts +9 -8
- package/dist/process/driver.js +11 -22
- package/dist/process/index.d.ts +2 -0
- package/dist/process/index.js +2 -0
- package/dist/process/managed-record.d.ts +2 -2
- package/dist/process/process-driver.d.ts +109 -0
- package/dist/process/process-driver.js +650 -0
- package/dist/process/process-executor.d.ts +47 -0
- package/dist/process/process-executor.js +296 -0
- package/dist/process/process-manager.js +48 -26
- package/dist/process/run-executor.js +12 -12
- package/dist/profile/client-actions.js +2 -2
- package/dist/profile/client-health.js +2 -2
- package/dist/profile/client-query.js +5 -6
- package/dist/profile/client-transport.d.ts +19 -17
- package/dist/profile/client-transport.js +47 -80
- package/dist/profile/client.d.ts +2 -2
- package/dist/profile/client.js +1 -1
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +1 -0
- package/dist/profile/manager.d.ts +5 -0
- package/dist/profile/manager.js +41 -6
- package/dist/project/closure.js +2 -2
- package/dist/project/index.d.ts +8 -1
- package/dist/project/index.js +10 -1
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +1 -1
- package/dist/project/loader.js +26 -29
- package/dist/project/transactions.js +5 -10
- package/dist/project/types.d.ts +5 -0
- package/dist/registry/index.d.ts +3 -1
- package/dist/registry/index.js +2 -1
- package/dist/registry/lock.d.ts +28 -7
- package/dist/registry/lock.js +136 -16
- package/dist/registry/registry.d.ts +1 -5
- package/dist/registry/registry.js +1 -53
- package/dist/registry/scan.d.ts +0 -15
- package/dist/registry/scan.js +0 -59
- package/dist/registry/store.d.ts +42 -0
- package/dist/registry/store.js +43 -0
- package/dist/registry/types.d.ts +0 -24
- package/dist/runtime/action-collection.d.ts +1 -1
- package/dist/runtime/action-registry.d.ts +0 -4
- package/dist/runtime/action-registry.js +0 -7
- package/dist/runtime/context.js +5 -11
- package/dist/runtime/events.js +3 -6
- package/dist/runtime/identity.d.ts +22 -0
- package/dist/runtime/identity.js +10 -0
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/module-loader.d.ts +2 -34
- package/dist/runtime/module-loader.js +1 -16
- package/dist/runtime/process.d.ts +6 -24
- package/dist/runtime/process.js +1 -118
- package/dist/runtime/run-persistence.js +2 -5
- package/dist/runtime/runner.d.ts +33 -93
- package/dist/runtime/runner.js +132 -364
- package/dist/runtime/standalone.d.ts +23 -8
- package/dist/runtime/standalone.js +100 -147
- package/dist/server/body.d.ts +16 -8
- package/dist/server/body.js +33 -16
- package/dist/server/dispatcher.d.ts +6 -10
- package/dist/server/dispatcher.js +24 -32
- package/dist/server/http-server.d.ts +87 -0
- package/dist/server/http-server.js +304 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +11 -0
- package/dist/server/mcp-endpoint.d.ts +61 -0
- package/dist/server/mcp-endpoint.js +118 -0
- package/dist/server/policy.d.ts +138 -0
- package/dist/server/policy.js +368 -0
- package/dist/server/routes/actions.js +114 -64
- package/dist/server/routes/common.d.ts +18 -33
- package/dist/server/routes/common.js +45 -65
- package/dist/server/routes/config.d.ts +1 -1
- package/dist/server/routes/config.js +55 -65
- package/dist/server/routes/doctor.d.ts +1 -1
- package/dist/server/routes/doctor.js +13 -21
- package/dist/server/routes/health.d.ts +1 -1
- package/dist/server/routes/health.js +4 -5
- package/dist/server/routes/info.d.ts +2 -2
- package/dist/server/routes/info.js +17 -24
- package/dist/server/routes/playbooks.js +23 -36
- package/dist/server/routes/runs.js +151 -141
- package/dist/server/routes/state.d.ts +1 -1
- package/dist/server/routes/state.js +64 -63
- package/dist/server/security.d.ts +11 -0
- package/dist/server/security.js +36 -19
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.js +224 -238
- package/dist/server/types.d.ts +68 -21
- package/dist/service/factory.d.ts +12 -0
- package/dist/service/factory.js +85 -0
- package/dist/{target → service}/index.d.ts +1 -1
- package/dist/{target → service}/index.js +1 -1
- package/dist/service/local.d.ts +27 -0
- package/dist/service/local.js +138 -0
- package/dist/{target → service}/remote-errors.d.ts +3 -6
- package/dist/service/remote-errors.js +38 -0
- package/dist/{target → service}/remote-polling.d.ts +2 -2
- package/dist/{target → service}/remote-polling.js +8 -8
- package/dist/service/remote.d.ts +34 -0
- package/dist/service/remote.js +634 -0
- package/dist/service/sse-stream.d.ts +24 -0
- package/dist/service/sse-stream.js +139 -0
- package/dist/service/types.d.ts +272 -0
- package/dist/service/types.js +32 -0
- package/dist/storage/data-dir-lock.js +10 -5
- package/dist/storage/driver.d.ts +2 -1
- package/dist/storage/driver.js +3 -52
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/lock-core.d.ts +2 -6
- package/dist/storage/lock-core.js +4 -17
- package/dist/storage/params.d.ts +2 -8
- package/dist/storage/params.js +2 -8
- package/dist/storage/sqlite-driver.d.ts +40 -0
- package/dist/storage/sqlite-driver.js +116 -0
- package/dist/storage/sqlite.js +21 -6
- package/dist/storage/types.d.ts +4 -4
- package/dist/storage/types.js +2 -2
- package/dist/utils/index.d.ts +53 -0
- package/dist/utils/index.js +99 -4
- package/dist/utils/process.d.ts +10 -0
- package/dist/utils/process.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +41 -3
- package/dist/app/index.d.ts +0 -2
- package/dist/app/index.js +0 -2
- package/dist/app/types.js +0 -1
- package/dist/catalog/action-index.d.ts +0 -9
- package/dist/catalog/action-index.js +0 -95
- package/dist/catalog/action-resolver.d.ts +0 -20
- package/dist/catalog/action-resolver.js +0 -95
- package/dist/catalog/location-registry.d.ts +0 -15
- package/dist/catalog/location-registry.js +0 -138
- package/dist/catalog/package-catalog.d.ts +0 -7
- package/dist/catalog/package-catalog.js +0 -77
- package/dist/catalog/types.d.ts +0 -68
- package/dist/catalog/types.js +0 -1
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/templates.d.ts +0 -71
- package/dist/export/templates.js +0 -635
- package/dist/ipc/target.d.ts +0 -74
- package/dist/ipc/target.js +0 -365
- package/dist/platform/default.d.ts +0 -38
- package/dist/platform/default.js +0 -78
- package/dist/project/resolver.d.ts +0 -116
- package/dist/project/resolver.js +0 -376
- package/dist/registry/resolve.d.ts +0 -62
- package/dist/registry/resolve.js +0 -195
- package/dist/runtime/execution-manager.d.ts +0 -48
- package/dist/runtime/execution-manager.js +0 -66
- package/dist/runtime/package-runner-factory.d.ts +0 -82
- package/dist/runtime/package-runner-factory.js +0 -186
- package/dist/target/local.d.ts +0 -53
- package/dist/target/local.js +0 -349
- package/dist/target/remote-errors.js +0 -51
- package/dist/target/remote.d.ts +0 -86
- package/dist/target/remote.js +0 -818
- package/dist/target/target.d.ts +0 -6
- package/dist/target/target.js +0 -37
- package/dist/target/types.d.ts +0 -261
- package/dist/target/types.js +0 -35
- /package/dist/{app → package}/static-index.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.js +0 -0
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { CAPABILITY_UNAVAILABLE } from "../../errors.js";
|
|
1
|
+
import { CAPABILITY_UNAVAILABLE, CONFIG_DELETE_ERROR, CONFIG_ENV_ERROR, CONFIG_LIST_ERROR, CONFIG_SET_ERROR, INVALID_ARGUMENT, INVALID_PACKAGE_ID, PACKAGE_NOT_ALLOWED, PACKAGE_NOT_FOUND, PATH_TRAVERSAL, } from "../../errors.js";
|
|
2
2
|
import { resolveEnvValue } from "../../runtime/index.js";
|
|
3
|
-
import { isSecretConfigKey,
|
|
3
|
+
import { isSecretConfigKey, sanitizeConfigDefinitions } from "../../storage/index.js";
|
|
4
4
|
import { readJsonBody } from "../body.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getSubPath, isManagementAllowedByPolicy, jsonResponse, resolveTargetPackageId, } from "./common.js";
|
|
6
|
+
function isClientConfigError(err) {
|
|
7
|
+
return (err?.code === PACKAGE_NOT_FOUND ||
|
|
8
|
+
err?.code === INVALID_PACKAGE_ID ||
|
|
9
|
+
err?.code === PATH_TRAVERSAL ||
|
|
10
|
+
err?.code === INVALID_ARGUMENT ||
|
|
11
|
+
err?.status === 400 ||
|
|
12
|
+
err?.status === 404);
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* 处理配置元数据与当前值读取、更新及删除接口。
|
|
8
|
-
*
|
|
16
|
+
* 在未显式开启管理功能时返回 403 拒绝。
|
|
9
17
|
*/
|
|
10
18
|
export async function handleConfigRoutes(ctx) {
|
|
11
|
-
const { req, url, pathname, corsHeaders, options,
|
|
19
|
+
const { req, url, pathname, corsHeaders, options, service, activePolicy: policy } = ctx;
|
|
12
20
|
const subpath = getSubPath(pathname);
|
|
13
21
|
const isConfigRoute = subpath === "/config" ||
|
|
14
22
|
subpath.startsWith("/config/") ||
|
|
@@ -17,7 +25,7 @@ export async function handleConfigRoutes(ctx) {
|
|
|
17
25
|
return null;
|
|
18
26
|
}
|
|
19
27
|
// 校验管理能力开关
|
|
20
|
-
if (
|
|
28
|
+
if (!isManagementAllowedByPolicy(policy) || !service.management?.config) {
|
|
21
29
|
return jsonResponse({
|
|
22
30
|
ok: false,
|
|
23
31
|
error: {
|
|
@@ -29,16 +37,14 @@ export async function handleConfigRoutes(ctx) {
|
|
|
29
37
|
// 1. Config Env Check: GET /config/env
|
|
30
38
|
if (subpath === "/config/env" && req.method === "GET") {
|
|
31
39
|
try {
|
|
40
|
+
const pkgs = await service.discovery.listPackages();
|
|
32
41
|
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
const app = resolveAppForPackage(pkgParam, host, target, options);
|
|
37
|
-
assertPackageAllowed(app.packageId, options);
|
|
38
|
-
const declared = app.projectConfig?.config || {};
|
|
42
|
+
const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
|
|
43
|
+
const pkg = pkgs.find((p) => p.id === targetPackageId);
|
|
44
|
+
const declared = pkg?.config || {};
|
|
39
45
|
const envChecks = [];
|
|
40
46
|
for (const [k, def] of Object.entries(declared)) {
|
|
41
|
-
const resolved = resolveEnvValue(k, def,
|
|
47
|
+
const resolved = resolveEnvValue(k, def, targetPackageId);
|
|
42
48
|
const matchedEnv = resolved?.envKey || null;
|
|
43
49
|
envChecks.push({
|
|
44
50
|
key: k,
|
|
@@ -49,61 +55,55 @@ export async function handleConfigRoutes(ctx) {
|
|
|
49
55
|
secret: isSecretConfigKey(k, def),
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
|
-
return jsonResponse({ ok: true, packageId:
|
|
58
|
+
return jsonResponse({ ok: true, packageId: targetPackageId, envChecks }, 200, corsHeaders);
|
|
53
59
|
}
|
|
54
60
|
catch (err) {
|
|
55
|
-
if (err.code ===
|
|
61
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
56
62
|
return jsonResponse({
|
|
57
63
|
ok: false,
|
|
58
64
|
error: {
|
|
59
|
-
code:
|
|
65
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
60
66
|
message: err.message || "Package is not in the allowed package list",
|
|
61
67
|
},
|
|
62
68
|
}, 403, corsHeaders);
|
|
63
69
|
}
|
|
64
|
-
const isClient = err
|
|
65
|
-
|
|
66
|
-
err.message?.includes("escapes boundary");
|
|
67
|
-
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_ENV_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
70
|
+
const isClient = isClientConfigError(err);
|
|
71
|
+
return jsonResponse({ ok: false, error: { code: isClient ? INVALID_ARGUMENT : CONFIG_ENV_ERROR, message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
// 2. Config Query: GET /config
|
|
71
75
|
if (subpath === "/config" && req.method === "GET") {
|
|
72
76
|
try {
|
|
77
|
+
const pkgs = await service.discovery.listPackages();
|
|
73
78
|
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
const app = resolveAppForPackage(pkgParam, host, target, options);
|
|
78
|
-
assertPackageAllowed(app.packageId, options);
|
|
79
|
-
const stored = app.storage.listConfig();
|
|
80
|
-
const rawDeclared = app.projectConfig?.config || {};
|
|
79
|
+
const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
|
|
80
|
+
const pkg = pkgs.find((p) => p.id === targetPackageId);
|
|
81
|
+
const rawDeclared = pkg?.config || {};
|
|
81
82
|
const declared = sanitizeConfigDefinitions(rawDeclared) || {};
|
|
83
|
+
const views = await service.management.config.list(targetPackageId);
|
|
82
84
|
const maskedValues = {};
|
|
83
|
-
for (const
|
|
84
|
-
if (
|
|
85
|
-
maskedValues[
|
|
85
|
+
for (const v of views) {
|
|
86
|
+
if (v.secret) {
|
|
87
|
+
maskedValues[v.key] = "********";
|
|
86
88
|
}
|
|
87
89
|
else {
|
|
88
|
-
maskedValues[
|
|
90
|
+
maskedValues[v.key] = v.value;
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
return jsonResponse({ ok: true, packageId:
|
|
93
|
+
return jsonResponse({ ok: true, packageId: targetPackageId, declared, values: maskedValues }, 200, corsHeaders);
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
94
|
-
if (err.code ===
|
|
96
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
95
97
|
return jsonResponse({
|
|
96
98
|
ok: false,
|
|
97
99
|
error: {
|
|
98
|
-
code:
|
|
100
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
99
101
|
message: err.message || "Package is not in the allowed package list",
|
|
100
102
|
},
|
|
101
103
|
}, 403, corsHeaders);
|
|
102
104
|
}
|
|
103
|
-
const isClient = err
|
|
104
|
-
|
|
105
|
-
err.message?.includes("escapes boundary");
|
|
106
|
-
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_LIST_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
105
|
+
const isClient = isClientConfigError(err);
|
|
106
|
+
return jsonResponse({ ok: false, error: { code: isClient ? INVALID_ARGUMENT : CONFIG_LIST_ERROR, message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
// 3. Config Update: PUT / POST /config
|
|
@@ -111,32 +111,27 @@ export async function handleConfigRoutes(ctx) {
|
|
|
111
111
|
try {
|
|
112
112
|
const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes });
|
|
113
113
|
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || body.package || undefined;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
const app = resolveAppForPackage(pkgParam, host, target, options);
|
|
118
|
-
assertPackageAllowed(app.packageId, options);
|
|
114
|
+
const pkgs = await service.discovery.listPackages();
|
|
115
|
+
const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
|
|
119
116
|
const key = body.key;
|
|
120
117
|
if (!key) {
|
|
121
|
-
return jsonResponse({ ok: false, error: { code:
|
|
118
|
+
return jsonResponse({ ok: false, error: { code: INVALID_ARGUMENT, message: "Config 'key' is required" } }, 400, corsHeaders);
|
|
122
119
|
}
|
|
123
|
-
await
|
|
124
|
-
return jsonResponse({ ok: true, packageId:
|
|
120
|
+
await service.management.config.set(targetPackageId, key, body.value);
|
|
121
|
+
return jsonResponse({ ok: true, packageId: targetPackageId, key, message: "updated" }, 200, corsHeaders);
|
|
125
122
|
}
|
|
126
123
|
catch (err) {
|
|
127
|
-
if (err.code ===
|
|
124
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
128
125
|
return jsonResponse({
|
|
129
126
|
ok: false,
|
|
130
127
|
error: {
|
|
131
|
-
code:
|
|
128
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
132
129
|
message: err.message || "Package is not in the allowed package list",
|
|
133
130
|
},
|
|
134
131
|
}, 403, corsHeaders);
|
|
135
132
|
}
|
|
136
|
-
const isClient = err
|
|
137
|
-
|
|
138
|
-
err.message?.includes("escapes boundary");
|
|
139
|
-
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_SET_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
133
|
+
const isClient = isClientConfigError(err);
|
|
134
|
+
return jsonResponse({ ok: false, error: { code: isClient ? INVALID_ARGUMENT : CONFIG_SET_ERROR, message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
// 4. Config Delete: DELETE /config/:key
|
|
@@ -145,28 +140,23 @@ export async function handleConfigRoutes(ctx) {
|
|
|
145
140
|
try {
|
|
146
141
|
const key = decodeURIComponent(configKeyMatch[1]);
|
|
147
142
|
const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
assertPackageAllowed(app.packageId, options);
|
|
153
|
-
const deleted = app.storage.deleteConfig(key);
|
|
154
|
-
return jsonResponse({ ok: true, packageId: app.packageId, key, deleted }, 200, corsHeaders);
|
|
143
|
+
const pkgs = await service.discovery.listPackages();
|
|
144
|
+
const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
|
|
145
|
+
const deleted = await service.management.config.delete(targetPackageId, key);
|
|
146
|
+
return jsonResponse({ ok: true, packageId: targetPackageId, key, deleted }, 200, corsHeaders);
|
|
155
147
|
}
|
|
156
148
|
catch (err) {
|
|
157
|
-
if (err.code ===
|
|
149
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
158
150
|
return jsonResponse({
|
|
159
151
|
ok: false,
|
|
160
152
|
error: {
|
|
161
|
-
code:
|
|
153
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
162
154
|
message: err.message || "Package is not in the allowed package list",
|
|
163
155
|
},
|
|
164
156
|
}, 403, corsHeaders);
|
|
165
157
|
}
|
|
166
|
-
const isClient = err
|
|
167
|
-
|
|
168
|
-
err.message?.includes("escapes boundary");
|
|
169
|
-
return jsonResponse({ ok: false, error: { code: isClient ? "INVALID_ARGUMENT" : "CONFIG_DELETE_ERROR", message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
158
|
+
const isClient = isClientConfigError(err);
|
|
159
|
+
return jsonResponse({ ok: false, error: { code: isClient ? INVALID_ARGUMENT : CONFIG_DELETE_ERROR, message: err.message } }, isClient ? 400 : 500, corsHeaders);
|
|
170
160
|
}
|
|
171
161
|
}
|
|
172
162
|
return null;
|
|
@@ -1,63 +1,55 @@
|
|
|
1
|
+
import { DOCTOR_ERROR, PACKAGE_NOT_ALLOWED } from "../../errors.js";
|
|
1
2
|
import { runDoctorChecks } from "../../doctor/doctor.js";
|
|
2
3
|
import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
|
|
3
4
|
/**
|
|
4
|
-
* 处理环境与依赖诊断接口(GET /api/v2/doctor
|
|
5
|
+
* 处理环境与依赖诊断接口(GET /api/v2/doctor 与 /doctor)。
|
|
5
6
|
*/
|
|
6
7
|
export async function handleDoctorRoute(ctx) {
|
|
7
|
-
const { req, url, pathname, corsHeaders, projectRoot, customHome,
|
|
8
|
+
const { req, url, pathname, corsHeaders, projectRoot, customHome, service, options, activePolicy: policy } = ctx;
|
|
8
9
|
const subpath = getSubPath(pathname);
|
|
9
10
|
if (subpath !== "/doctor" || req.method !== "GET") {
|
|
10
11
|
return null;
|
|
11
12
|
}
|
|
12
13
|
try {
|
|
13
14
|
const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
14
|
-
if (
|
|
15
|
+
if (policy.packageAllowlist && policy.packageAllowlist.length > 0) {
|
|
15
16
|
if (!targetPkg) {
|
|
16
17
|
return jsonResponse({
|
|
17
18
|
ok: false,
|
|
18
19
|
error: {
|
|
19
|
-
code:
|
|
20
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
20
21
|
message: "Global doctor check is forbidden when package allowlist is active. Please specify an allowed package.",
|
|
21
22
|
},
|
|
22
23
|
}, 403, corsHeaders);
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
if (targetPkg) {
|
|
26
|
-
assertPackageAllowed(targetPkg,
|
|
27
|
+
assertPackageAllowed(targetPkg, policy);
|
|
27
28
|
}
|
|
28
29
|
let packageRoot;
|
|
29
30
|
if (targetPkg) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!packageRoot && target?.unwrap) {
|
|
34
|
-
const inner = target.unwrap();
|
|
35
|
-
if (inner && "getApp" in inner && typeof inner.getApp === "function") {
|
|
36
|
-
packageRoot = inner.getApp(targetPkg)?.packageRoot;
|
|
37
|
-
}
|
|
38
|
-
else if (inner && "packageId" in inner && inner.packageId === targetPkg) {
|
|
39
|
-
packageRoot = inner.packageRoot;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
31
|
+
const pkgs = await service.discovery.listPackages();
|
|
32
|
+
const matched = pkgs.find((p) => p.id === targetPkg || p.packageRoot === targetPkg);
|
|
33
|
+
packageRoot = matched?.packageRoot;
|
|
42
34
|
}
|
|
43
35
|
const report = await runDoctorChecks({
|
|
44
36
|
cwd: packageRoot || projectRoot || process.cwd(),
|
|
45
37
|
packageIdOrPath: packageRoot || targetPkg,
|
|
46
38
|
customHome,
|
|
47
|
-
packageAllowlist:
|
|
39
|
+
packageAllowlist: policy.packageAllowlist,
|
|
48
40
|
});
|
|
49
41
|
return jsonResponse({ ok: true, report }, 200, corsHeaders);
|
|
50
42
|
}
|
|
51
43
|
catch (err) {
|
|
52
|
-
if (err.code ===
|
|
44
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
53
45
|
return jsonResponse({
|
|
54
46
|
ok: false,
|
|
55
47
|
error: {
|
|
56
|
-
code:
|
|
48
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
57
49
|
message: err.message || "Package is not in the allowed package list",
|
|
58
50
|
},
|
|
59
51
|
}, 403, corsHeaders);
|
|
60
52
|
}
|
|
61
|
-
return jsonResponse({ ok: false, error: { code:
|
|
53
|
+
return jsonResponse({ ok: false, error: { code: DOCTOR_ERROR, message: err.message } }, 500, corsHeaders);
|
|
62
54
|
}
|
|
63
55
|
}
|
|
@@ -3,16 +3,15 @@ import { UNAUTHORIZED } from "../../errors.js";
|
|
|
3
3
|
import { verifyBearerToken } from "../security.js";
|
|
4
4
|
import { jsonResponse } from "./common.js";
|
|
5
5
|
/**
|
|
6
|
-
* 处理健康检查与就绪状态接口(支持 /api/v2/health
|
|
6
|
+
* 处理健康检查与就绪状态接口(支持 /api/v2/health 与 /health)。
|
|
7
7
|
*/
|
|
8
8
|
export async function handleHealthRoute(ctx) {
|
|
9
|
-
const { req, pathname, corsHeaders, options, projectRoot } = ctx;
|
|
9
|
+
const { req, pathname, corsHeaders, options, projectRoot, activePolicy: policy } = ctx;
|
|
10
10
|
if (pathname !== "/api/v2/health" &&
|
|
11
|
-
pathname !== "/health"
|
|
12
|
-
pathname !== "/api/v1/health") {
|
|
11
|
+
pathname !== "/health") {
|
|
13
12
|
return null;
|
|
14
13
|
}
|
|
15
|
-
if (!verifyBearerToken(req,
|
|
14
|
+
if (!verifyBearerToken(req, policy.token, options)) {
|
|
16
15
|
return jsonResponse({
|
|
17
16
|
ok: false,
|
|
18
17
|
error: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type RouteContext } from "./common.js";
|
|
2
2
|
/**
|
|
3
3
|
* 处理系统自省与包信息接口:
|
|
4
|
-
* - GET /api/v2/info 与 GET /info -> 委托
|
|
5
|
-
* - GET /api/v2/packages 与 GET /packages -> 委托
|
|
4
|
+
* - GET /api/v2/info 与 GET /info -> 委托 service.info()
|
|
5
|
+
* - GET /api/v2/packages 与 GET /packages -> 委托 service.discovery.listPackages()
|
|
6
6
|
*/
|
|
7
7
|
export declare function handleInfoRoute(ctx: RouteContext): Promise<Response | null>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { filterWithFallbackInfo } from "../../filter/index.js";
|
|
2
|
-
import { PACKAGE_NOT_FOUND } from "../../errors.js";
|
|
2
|
+
import { INFO_ERROR, PACKAGE_NOT_ALLOWED, PACKAGE_NOT_FOUND, PACKAGES_INFO_ERROR } from "../../errors.js";
|
|
3
3
|
import { ACTIONDOCK_VERSION } from "../../version.js";
|
|
4
4
|
import { sanitizeConfigDefinitions } from "../../storage/index.js";
|
|
5
|
-
import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
|
|
5
|
+
import { assertPackageAllowed, filterPackagesByPolicy, getSubPath, jsonResponse } from "./common.js";
|
|
6
6
|
function sanitizePackage(pkg, exposeDebugInfo) {
|
|
7
7
|
if (!pkg)
|
|
8
8
|
return pkg;
|
|
@@ -18,42 +18,39 @@ function sanitizePackage(pkg, exposeDebugInfo) {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* 处理系统自省与包信息接口:
|
|
21
|
-
* - GET /api/v2/info 与 GET /info -> 委托
|
|
22
|
-
* - GET /api/v2/packages 与 GET /packages -> 委托
|
|
21
|
+
* - GET /api/v2/info 与 GET /info -> 委托 service.info()
|
|
22
|
+
* - GET /api/v2/packages 与 GET /packages -> 委托 service.discovery.listPackages()
|
|
23
23
|
*/
|
|
24
24
|
export async function handleInfoRoute(ctx) {
|
|
25
|
-
const { req, url, pathname, corsHeaders, options,
|
|
25
|
+
const { req, url, pathname, corsHeaders, options, service, projectRoot, activePolicy: policy } = ctx;
|
|
26
26
|
const subpath = getSubPath(pathname);
|
|
27
27
|
// 1. Packages List: GET /api/v2/packages, /packages
|
|
28
28
|
if ((subpath === "/packages" || pathname === "/api/v2/packages" || pathname === "/packages") && req.method === "GET") {
|
|
29
29
|
try {
|
|
30
30
|
const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
31
31
|
if (targetPkg) {
|
|
32
|
-
assertPackageAllowed(targetPkg,
|
|
33
|
-
}
|
|
34
|
-
const rawPackages = target ? await target.listPackages() : (host ? await host.info() : []);
|
|
35
|
-
let packages = rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo));
|
|
36
|
-
if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
|
|
37
|
-
packages = packages.filter((p) => p.id && options.packageAllowlist.includes(p.id));
|
|
32
|
+
assertPackageAllowed(targetPkg, policy);
|
|
38
33
|
}
|
|
34
|
+
const rawPackages = await service.discovery.listPackages();
|
|
35
|
+
const packages = filterPackagesByPolicy(rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo)), policy);
|
|
39
36
|
return jsonResponse({
|
|
40
37
|
ok: true,
|
|
41
38
|
packages,
|
|
42
39
|
}, 200, corsHeaders);
|
|
43
40
|
}
|
|
44
41
|
catch (err) {
|
|
45
|
-
if (err.code ===
|
|
42
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
46
43
|
return jsonResponse({
|
|
47
44
|
ok: false,
|
|
48
45
|
error: {
|
|
49
|
-
code:
|
|
46
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
50
47
|
message: err.message || "Package is not in the allowed package list",
|
|
51
48
|
},
|
|
52
49
|
}, 403, corsHeaders);
|
|
53
50
|
}
|
|
54
51
|
return jsonResponse({
|
|
55
52
|
ok: false,
|
|
56
|
-
error: { code:
|
|
53
|
+
error: { code: PACKAGES_INFO_ERROR, message: err.message },
|
|
57
54
|
}, 500, corsHeaders);
|
|
58
55
|
}
|
|
59
56
|
}
|
|
@@ -64,14 +61,10 @@ export async function handleInfoRoute(ctx) {
|
|
|
64
61
|
const intent = url.searchParams.get("intent") || undefined;
|
|
65
62
|
const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
66
63
|
if (targetPkg) {
|
|
67
|
-
assertPackageAllowed(targetPkg,
|
|
68
|
-
}
|
|
69
|
-
const targetInfo = await target.info();
|
|
70
|
-
const rawPackages = targetInfo.packages || [];
|
|
71
|
-
let packages = rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo));
|
|
72
|
-
if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
|
|
73
|
-
packages = packages.filter((p) => p.id && options.packageAllowlist.includes(p.id));
|
|
64
|
+
assertPackageAllowed(targetPkg, policy);
|
|
74
65
|
}
|
|
66
|
+
const rawPackages = await service.info();
|
|
67
|
+
const packages = filterPackagesByPolicy(rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo)), policy);
|
|
75
68
|
if (isTree) {
|
|
76
69
|
return jsonResponse({ ok: true, type: "tree", packages }, 200, corsHeaders);
|
|
77
70
|
}
|
|
@@ -139,18 +132,18 @@ export async function handleInfoRoute(ctx) {
|
|
|
139
132
|
}, 200, corsHeaders);
|
|
140
133
|
}
|
|
141
134
|
catch (err) {
|
|
142
|
-
if (err.code ===
|
|
135
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
143
136
|
return jsonResponse({
|
|
144
137
|
ok: false,
|
|
145
138
|
error: {
|
|
146
|
-
code:
|
|
139
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
147
140
|
message: err.message || "Package is not in the allowed package list",
|
|
148
141
|
},
|
|
149
142
|
}, 403, corsHeaders);
|
|
150
143
|
}
|
|
151
144
|
return jsonResponse({
|
|
152
145
|
ok: false,
|
|
153
|
-
error: { code:
|
|
146
|
+
error: { code: INFO_ERROR, message: err.message },
|
|
154
147
|
}, 500, corsHeaders);
|
|
155
148
|
}
|
|
156
149
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { filterByIntent } from "../../filter/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { PACKAGE_NOT_ALLOWED, PLAYBOOK_NOT_FOUND, PLAYBOOKS_LIST_ERROR } from "../../errors.js";
|
|
3
|
+
import { assertPackageAllowed, getSubPath, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
|
|
3
4
|
/**
|
|
4
5
|
* 处理 Playbook 规程相关的 HTTP 路由(列表与 SOP 详情查询)。
|
|
5
6
|
*/
|
|
6
7
|
export async function handlePlaybooksRoutes(ctx) {
|
|
7
|
-
const { req, url, pathname, corsHeaders,
|
|
8
|
+
const { req, url, pathname, corsHeaders, service, options, activePolicy: policy } = ctx;
|
|
8
9
|
const subpath = getSubPath(pathname);
|
|
9
10
|
// 1. Playbooks List: GET /api/v2/playbooks, GET /playbooks
|
|
10
11
|
if (subpath === "/playbooks" && req.method === "GET") {
|
|
@@ -12,14 +13,14 @@ export async function handlePlaybooksRoutes(ctx) {
|
|
|
12
13
|
const intent = url.searchParams.get("intent") || undefined;
|
|
13
14
|
const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
|
|
14
15
|
if (targetPkg) {
|
|
15
|
-
assertPackageAllowed(targetPkg,
|
|
16
|
+
assertPackageAllowed(targetPkg, policy);
|
|
16
17
|
}
|
|
17
|
-
let pbs = await
|
|
18
|
+
let pbs = await service.discovery.listPlaybooks({ intent, package: targetPkg });
|
|
18
19
|
if (targetPkg) {
|
|
19
20
|
pbs = pbs.filter((p) => p.packageId === targetPkg);
|
|
20
21
|
}
|
|
21
|
-
if (
|
|
22
|
-
pbs = pbs.filter((p) => p.packageId
|
|
22
|
+
if (policy.packageAllowlist && Array.isArray(policy.packageAllowlist) && policy.packageAllowlist.length > 0) {
|
|
23
|
+
pbs = pbs.filter((p) => isPackageAllowedByPolicy(p.packageId, policy));
|
|
23
24
|
}
|
|
24
25
|
if (intent) {
|
|
25
26
|
pbs = filterByIntent(pbs, intent, [(p) => p.id, (p) => p.description || "", (p) => p.packageId || "", (p) => (p.actions || []).join(" ")], false);
|
|
@@ -27,16 +28,16 @@ export async function handlePlaybooksRoutes(ctx) {
|
|
|
27
28
|
return jsonResponse(pbs, 200, corsHeaders);
|
|
28
29
|
}
|
|
29
30
|
catch (err) {
|
|
30
|
-
if (err.code ===
|
|
31
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
31
32
|
return jsonResponse({
|
|
32
33
|
ok: false,
|
|
33
34
|
error: {
|
|
34
|
-
code:
|
|
35
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
35
36
|
message: err.message || "Package is not in the allowed package list",
|
|
36
37
|
},
|
|
37
38
|
}, 403, corsHeaders);
|
|
38
39
|
}
|
|
39
|
-
return jsonResponse({ ok: false, error: { code:
|
|
40
|
+
return jsonResponse({ ok: false, error: { code: PLAYBOOKS_LIST_ERROR, message: err.message } }, 500, corsHeaders);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
// 2. Multi-package Playbook Show: GET /api/v2/packages/:packageId/playbooks/:playbookId
|
|
@@ -45,27 +46,27 @@ export async function handlePlaybooksRoutes(ctx) {
|
|
|
45
46
|
const packageId = decodeURIComponent(pkgPlaybookMatch[1]);
|
|
46
47
|
const playbookId = decodeURIComponent(pkgPlaybookMatch[2]);
|
|
47
48
|
try {
|
|
48
|
-
assertPackageAllowed(packageId,
|
|
49
|
+
assertPackageAllowed(packageId, policy);
|
|
49
50
|
}
|
|
50
51
|
catch (err) {
|
|
51
52
|
return jsonResponse({
|
|
52
53
|
ok: false,
|
|
53
54
|
error: {
|
|
54
|
-
code:
|
|
55
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
55
56
|
message: err.message || `Package '${packageId}' is not in the allowed package list`,
|
|
56
57
|
},
|
|
57
58
|
}, 403, corsHeaders);
|
|
58
59
|
}
|
|
59
60
|
const ref = `${packageId}/${playbookId}`;
|
|
60
61
|
try {
|
|
61
|
-
const pb = await
|
|
62
|
+
const pb = await service.discovery.describePlaybook(ref);
|
|
62
63
|
return jsonResponse(pb, 200, corsHeaders);
|
|
63
64
|
}
|
|
64
65
|
catch (err) {
|
|
65
66
|
return jsonResponse({
|
|
66
67
|
ok: false,
|
|
67
68
|
error: {
|
|
68
|
-
code:
|
|
69
|
+
code: PLAYBOOK_NOT_FOUND,
|
|
69
70
|
message: err.message || `Playbook '${playbookId}' not found in package '${packageId}'`,
|
|
70
71
|
},
|
|
71
72
|
}, 404, corsHeaders);
|
|
@@ -78,56 +79,42 @@ export async function handlePlaybooksRoutes(ctx) {
|
|
|
78
79
|
if (playbookId.includes("/")) {
|
|
79
80
|
const pkgFromRef = playbookId.slice(0, playbookId.lastIndexOf("/"));
|
|
80
81
|
try {
|
|
81
|
-
assertPackageAllowed(pkgFromRef,
|
|
82
|
+
assertPackageAllowed(pkgFromRef, policy);
|
|
82
83
|
}
|
|
83
84
|
catch (err) {
|
|
84
85
|
return jsonResponse({
|
|
85
86
|
ok: false,
|
|
86
87
|
error: {
|
|
87
|
-
code:
|
|
88
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
88
89
|
message: err.message || `Package '${pkgFromRef}' is not in the allowed package list`,
|
|
89
90
|
},
|
|
90
91
|
}, 403, corsHeaders);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
try {
|
|
94
|
-
const pb = await
|
|
95
|
-
|
|
96
|
-
let pkgId = pb.packageId || (unwrapped && "packageId" in unwrapped ? unwrapped.packageId : undefined);
|
|
97
|
-
if (!pkgId && host) {
|
|
98
|
-
for (const app of host.listApps()) {
|
|
99
|
-
try {
|
|
100
|
-
const map = app.getStaticPlaybookMap?.();
|
|
101
|
-
const cleanId = playbookId.replace(/\.md$/, "");
|
|
102
|
-
if (map?.has(cleanId) || map?.has(playbookId)) {
|
|
103
|
-
pkgId = app.packageId;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch { }
|
|
108
|
-
}
|
|
109
|
-
}
|
|
95
|
+
const pb = await service.discovery.describePlaybook(playbookId);
|
|
96
|
+
let pkgId = pb.packageId;
|
|
110
97
|
if (!pkgId) {
|
|
111
|
-
const allPbs = await
|
|
98
|
+
const allPbs = await service.discovery.listPlaybooks().catch(() => []);
|
|
112
99
|
const matched = allPbs.find((p) => p.id === playbookId || p.id.endsWith(`/${playbookId}`));
|
|
113
100
|
if (matched?.packageId) {
|
|
114
101
|
pkgId = matched.packageId;
|
|
115
102
|
}
|
|
116
103
|
}
|
|
117
|
-
assertPackageAllowed(pkgId,
|
|
104
|
+
assertPackageAllowed(pkgId, policy);
|
|
118
105
|
return jsonResponse(pb, 200, corsHeaders);
|
|
119
106
|
}
|
|
120
107
|
catch (err) {
|
|
121
|
-
if (err.code ===
|
|
108
|
+
if (err.code === PACKAGE_NOT_ALLOWED || err.status === 403) {
|
|
122
109
|
return jsonResponse({
|
|
123
110
|
ok: false,
|
|
124
111
|
error: {
|
|
125
|
-
code:
|
|
112
|
+
code: PACKAGE_NOT_ALLOWED,
|
|
126
113
|
message: err.message || "Package is not in the allowed package list",
|
|
127
114
|
},
|
|
128
115
|
}, 403, corsHeaders);
|
|
129
116
|
}
|
|
130
|
-
return jsonResponse({ ok: false, error: { code:
|
|
117
|
+
return jsonResponse({ ok: false, error: { code: PLAYBOOK_NOT_FOUND, message: err.message } }, 404, corsHeaders);
|
|
131
118
|
}
|
|
132
119
|
}
|
|
133
120
|
return null;
|