@better-openclaw/core 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bare-metal-partition.d.mts.map +1 -1
- package/dist/bare-metal-partition.mjs +1 -0
- package/dist/bare-metal-partition.mjs.map +1 -1
- package/dist/composer.d.mts.map +1 -1
- package/dist/composer.mjs +11 -1
- package/dist/composer.mjs.map +1 -1
- package/dist/errors.d.mts +17 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +24 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/generate.d.mts +4 -3
- package/dist/generate.d.mts.map +1 -1
- package/dist/generate.mjs +13 -5
- package/dist/generate.mjs.map +1 -1
- package/dist/generators/traefik.d.mts +19 -0
- package/dist/generators/traefik.d.mts.map +1 -0
- package/dist/generators/traefik.mjs +86 -0
- package/dist/generators/traefik.mjs.map +1 -0
- package/dist/generators/traefik.test.d.mts +1 -0
- package/dist/generators/traefik.test.mjs +69 -0
- package/dist/generators/traefik.test.mjs.map +1 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +4 -2
- package/dist/migrations.d.mts +14 -0
- package/dist/migrations.d.mts.map +1 -0
- package/dist/migrations.mjs +33 -0
- package/dist/migrations.mjs.map +1 -0
- package/dist/migrations.test.d.mts +1 -0
- package/dist/migrations.test.mjs +42 -0
- package/dist/migrations.test.mjs.map +1 -0
- package/dist/resolver.d.mts +6 -1
- package/dist/resolver.d.mts.map +1 -1
- package/dist/resolver.mjs +21 -3
- package/dist/resolver.mjs.map +1 -1
- package/dist/schema.d.mts +9 -0
- package/dist/schema.d.mts.map +1 -1
- package/dist/schema.mjs +4 -1
- package/dist/schema.mjs.map +1 -1
- package/dist/services/definitions/caddy.mjs +20 -1
- package/dist/services/definitions/caddy.mjs.map +1 -1
- package/dist/services/definitions/cal-com.d.mts +7 -0
- package/dist/services/definitions/cal-com.d.mts.map +1 -0
- package/dist/services/definitions/cal-com.mjs +88 -0
- package/dist/services/definitions/cal-com.mjs.map +1 -0
- package/dist/services/definitions/comfyui.d.mts +7 -0
- package/dist/services/definitions/comfyui.d.mts.map +1 -0
- package/dist/services/definitions/comfyui.mjs +83 -0
- package/dist/services/definitions/comfyui.mjs.map +1 -0
- package/dist/services/definitions/desktop-environment.d.mts +7 -0
- package/dist/services/definitions/desktop-environment.d.mts.map +1 -0
- package/dist/services/definitions/desktop-environment.mjs +153 -0
- package/dist/services/definitions/desktop-environment.mjs.map +1 -0
- package/dist/services/definitions/grafana.mjs +13 -1
- package/dist/services/definitions/grafana.mjs.map +1 -1
- package/dist/services/definitions/index.d.mts +7 -1
- package/dist/services/definitions/index.d.mts.map +1 -1
- package/dist/services/definitions/index.mjs +14 -2
- package/dist/services/definitions/index.mjs.map +1 -1
- package/dist/services/definitions/neo4j.d.mts +7 -0
- package/dist/services/definitions/neo4j.d.mts.map +1 -0
- package/dist/services/definitions/neo4j.mjs +91 -0
- package/dist/services/definitions/neo4j.mjs.map +1 -0
- package/dist/services/definitions/stream-gateway.d.mts +7 -0
- package/dist/services/definitions/stream-gateway.d.mts.map +1 -0
- package/dist/services/definitions/stream-gateway.mjs +133 -0
- package/dist/services/definitions/stream-gateway.mjs.map +1 -0
- package/dist/services/definitions/traefik.mjs +0 -1
- package/dist/services/definitions/traefik.mjs.map +1 -1
- package/dist/services/definitions/xyops.d.mts +7 -0
- package/dist/services/definitions/xyops.d.mts.map +1 -0
- package/dist/services/definitions/xyops.mjs +86 -0
- package/dist/services/definitions/xyops.mjs.map +1 -0
- package/dist/types.d.mts +8 -1
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +10 -0
- package/dist/types.mjs.map +1 -1
- package/dist/validator.mjs +11 -0
- package/dist/validator.mjs.map +1 -1
- package/dist/version-manager.d.mts +1 -1
- package/dist/version-manager.d.mts.map +1 -1
- package/dist/version-manager.mjs +11 -5
- package/dist/version-manager.mjs.map +1 -1
- package/dist/version-manager.test.d.mts +1 -0
- package/dist/version-manager.test.mjs +102 -0
- package/dist/version-manager.test.mjs.map +1 -0
- package/package.json +1 -1
- package/src/__snapshots__/composer.snapshot.test.ts.snap +15 -1
- package/src/bare-metal-partition.ts +1 -0
- package/src/composer.ts +22 -1
- package/src/errors.ts +23 -0
- package/src/generate.ts +22 -4
- package/src/generators/traefik.test.ts +97 -0
- package/src/generators/traefik.ts +104 -0
- package/src/index.ts +7 -1
- package/src/migrations.test.ts +36 -0
- package/src/migrations.ts +49 -0
- package/src/resolver.ts +37 -3
- package/src/schema.ts +3 -0
- package/src/services/definitions/caddy.ts +23 -1
- package/src/services/definitions/cal-com.ts +91 -0
- package/src/services/definitions/comfyui.ts +90 -0
- package/src/services/definitions/desktop-environment.ts +163 -0
- package/src/services/definitions/grafana.ts +16 -1
- package/src/services/definitions/index.ts +18 -0
- package/src/services/definitions/neo4j.ts +96 -0
- package/src/services/definitions/stream-gateway.ts +148 -0
- package/src/services/definitions/traefik.ts +0 -2
- package/src/services/definitions/xyops.ts +94 -0
- package/src/types.ts +7 -1
- package/src/validator.ts +16 -0
- package/src/version-manager.test.ts +134 -0
- package/src/version-manager.ts +12 -5
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ServiceDefinition } from "../../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/services/definitions/stream-gateway.d.ts
|
|
4
|
+
declare const streamGatewayDefinition: ServiceDefinition;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { streamGatewayDefinition };
|
|
7
|
+
//# sourceMappingURL=stream-gateway.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-gateway.d.mts","names":[],"sources":["../../../src/services/definitions/stream-gateway.ts"],"mappings":";;;cAEa,uBAAA,EAAyB,iBAAA"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/services/definitions/stream-gateway.ts
|
|
2
|
+
const streamGatewayDefinition = {
|
|
3
|
+
id: "stream-gateway",
|
|
4
|
+
name: "Stream Gateway",
|
|
5
|
+
description: "NGINX-RTMP relay server that receives a local RTMP stream (e.g. from OBS in the desktop-environment) and fans it out to YouTube, Twitch, TikTok, and Telegram simultaneously. Also serves an HLS preview on HTTP for local viewing.",
|
|
6
|
+
category: "streaming",
|
|
7
|
+
icon: "📺",
|
|
8
|
+
image: "tiangolo/nginx-rtmp",
|
|
9
|
+
imageTag: "latest",
|
|
10
|
+
ports: [{
|
|
11
|
+
host: 1935,
|
|
12
|
+
container: 1935,
|
|
13
|
+
description: "RTMP ingest (receives stream from OBS or ffmpeg)",
|
|
14
|
+
exposed: true
|
|
15
|
+
}, {
|
|
16
|
+
host: 8080,
|
|
17
|
+
container: 8080,
|
|
18
|
+
description: "HTTP server for HLS preview and stats",
|
|
19
|
+
exposed: true
|
|
20
|
+
}],
|
|
21
|
+
volumes: [{
|
|
22
|
+
name: "stream-gateway-hls",
|
|
23
|
+
containerPath: "/tmp/hls",
|
|
24
|
+
description: "HLS segment storage for live preview playback"
|
|
25
|
+
}],
|
|
26
|
+
environment: [
|
|
27
|
+
{
|
|
28
|
+
key: "YOUTUBE_STREAM_KEY",
|
|
29
|
+
defaultValue: "",
|
|
30
|
+
secret: true,
|
|
31
|
+
description: "YouTube Live stream key (leave empty to skip YouTube relay)",
|
|
32
|
+
required: false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "TWITCH_STREAM_KEY",
|
|
36
|
+
defaultValue: "",
|
|
37
|
+
secret: true,
|
|
38
|
+
description: "Twitch stream key (leave empty to skip Twitch relay)",
|
|
39
|
+
required: false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "TIKTOK_STREAM_URL",
|
|
43
|
+
defaultValue: "",
|
|
44
|
+
secret: true,
|
|
45
|
+
description: "Full TikTok RTMP URL from TikTok Studio (leave empty to skip TikTok relay)",
|
|
46
|
+
required: false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "TELEGRAM_STREAM_URL",
|
|
50
|
+
defaultValue: "",
|
|
51
|
+
secret: true,
|
|
52
|
+
description: "Full Telegram RTMPS URL including stream key (leave empty to skip Telegram relay)",
|
|
53
|
+
required: false
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
healthcheck: {
|
|
57
|
+
test: "curl -sf http://localhost:8080/health || exit 1",
|
|
58
|
+
interval: "15s",
|
|
59
|
+
timeout: "5s",
|
|
60
|
+
retries: 3
|
|
61
|
+
},
|
|
62
|
+
dependsOn: [],
|
|
63
|
+
restartPolicy: "unless-stopped",
|
|
64
|
+
networks: ["openclaw-network"],
|
|
65
|
+
deploy: { resources: {
|
|
66
|
+
limits: {
|
|
67
|
+
cpus: "2.0",
|
|
68
|
+
memory: "2G"
|
|
69
|
+
},
|
|
70
|
+
reservations: {
|
|
71
|
+
cpus: "0.5",
|
|
72
|
+
memory: "512M"
|
|
73
|
+
}
|
|
74
|
+
} },
|
|
75
|
+
skills: [],
|
|
76
|
+
openclawEnvVars: [
|
|
77
|
+
{
|
|
78
|
+
key: "STREAM_GATEWAY_HOST",
|
|
79
|
+
defaultValue: "stream-gateway",
|
|
80
|
+
secret: false,
|
|
81
|
+
description: "Hostname of the stream-gateway container",
|
|
82
|
+
required: true
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "STREAM_GATEWAY_RTMP_PORT",
|
|
86
|
+
defaultValue: "1935",
|
|
87
|
+
secret: false,
|
|
88
|
+
description: "RTMP ingest port on the stream-gateway",
|
|
89
|
+
required: true
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "STREAM_GATEWAY_HLS_PORT",
|
|
93
|
+
defaultValue: "8080",
|
|
94
|
+
secret: false,
|
|
95
|
+
description: "HTTP port for HLS preview on the stream-gateway",
|
|
96
|
+
required: true
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
docsUrl: "https://github.com/tiangolo/nginx-rtmp-docker",
|
|
100
|
+
tags: [
|
|
101
|
+
"streaming",
|
|
102
|
+
"rtmp",
|
|
103
|
+
"hls",
|
|
104
|
+
"relay",
|
|
105
|
+
"youtube",
|
|
106
|
+
"twitch",
|
|
107
|
+
"tiktok",
|
|
108
|
+
"telegram",
|
|
109
|
+
"obs",
|
|
110
|
+
"nginx"
|
|
111
|
+
],
|
|
112
|
+
maturity: "experimental",
|
|
113
|
+
requires: [],
|
|
114
|
+
recommends: ["desktop-environment"],
|
|
115
|
+
conflictsWith: [],
|
|
116
|
+
removalWarning: "⚠️ STREAMING KEYS REQUIRED: To relay to platforms you must provide at least one stream key. Without any keys configured the gateway will still accept RTMP input and serve HLS locally but nothing will be forwarded.",
|
|
117
|
+
minMemoryMB: 512,
|
|
118
|
+
gpuRequired: false,
|
|
119
|
+
nativeSupported: true,
|
|
120
|
+
nativeRecipes: [{
|
|
121
|
+
platform: "linux",
|
|
122
|
+
installSteps: ["command -v nginx >/dev/null 2>&1 || (command -v apt-get >/dev/null 2>&1 && sudo apt-get update -qq && sudo apt-get install -y -qq nginx libnginx-mod-rtmp ffmpeg)", "command -v nginx >/dev/null 2>&1 || (command -v dnf >/dev/null 2>&1 && sudo dnf install -y nginx nginx-mod-rtmp ffmpeg)"],
|
|
123
|
+
startCommand: "sudo systemctl start nginx",
|
|
124
|
+
stopCommand: "sudo systemctl stop nginx",
|
|
125
|
+
configPath: "/etc/nginx/nginx.conf",
|
|
126
|
+
configTemplate: "# Generated for OpenClaw bare-metal\nworker_processes auto;\nrtmp_auto_push on;\n\nevents { worker_connections 1024; }\n\nrtmp {\n server {\n listen 1935;\n chunk_size 4096;\n application live {\n live on;\n record off;\n hls on;\n hls_path /tmp/hls;\n hls_fragment 3;\n hls_playlist_length 60;\n }\n }\n}\n\nhttp {\n server {\n listen 8080;\n location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /tmp; add_header Cache-Control no-cache; }\n location /health { return 200 \"OK\"; }\n }\n}\n",
|
|
127
|
+
systemdUnit: "nginx"
|
|
128
|
+
}]
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
export { streamGatewayDefinition };
|
|
133
|
+
//# sourceMappingURL=stream-gateway.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-gateway.mjs","names":[],"sources":["../../../src/services/definitions/stream-gateway.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const streamGatewayDefinition: ServiceDefinition = {\n\tid: \"stream-gateway\",\n\tname: \"Stream Gateway\",\n\tdescription:\n\t\t\"NGINX-RTMP relay server that receives a local RTMP stream (e.g. from OBS in the desktop-environment) and fans it out to YouTube, Twitch, TikTok, and Telegram simultaneously. Also serves an HLS preview on HTTP for local viewing.\",\n\tcategory: \"streaming\",\n\ticon: \"📺\",\n\n\timage: \"tiangolo/nginx-rtmp\",\n\timageTag: \"latest\",\n\tports: [\n\t\t{\n\t\t\thost: 1935,\n\t\t\tcontainer: 1935,\n\t\t\tdescription: \"RTMP ingest (receives stream from OBS or ffmpeg)\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 8080,\n\t\t\tcontainer: 8080,\n\t\t\tdescription: \"HTTP server for HLS preview and stats\",\n\t\t\texposed: true,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"stream-gateway-hls\",\n\t\t\tcontainerPath: \"/tmp/hls\",\n\t\t\tdescription: \"HLS segment storage for live preview playback\",\n\t\t},\n\t],\n\tenvironment: [\n\t\t{\n\t\t\tkey: \"YOUTUBE_STREAM_KEY\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"YouTube Live stream key (leave empty to skip YouTube relay)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"TWITCH_STREAM_KEY\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"Twitch stream key (leave empty to skip Twitch relay)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"TIKTOK_STREAM_URL\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription:\n\t\t\t\t\"Full TikTok RTMP URL from TikTok Studio (leave empty to skip TikTok relay)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"TELEGRAM_STREAM_URL\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription:\n\t\t\t\t\"Full Telegram RTMPS URL including stream key (leave empty to skip Telegram relay)\",\n\t\t\trequired: false,\n\t\t},\n\t],\n\thealthcheck: {\n\t\ttest: \"curl -sf http://localhost:8080/health || exit 1\",\n\t\tinterval: \"15s\",\n\t\ttimeout: \"5s\",\n\t\tretries: 3,\n\t},\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tdeploy: {\n\t\tresources: {\n\t\t\tlimits: { cpus: \"2.0\", memory: \"2G\" },\n\t\t\treservations: { cpus: \"0.5\", memory: \"512M\" },\n\t\t},\n\t},\n\n\tskills: [],\n\topenclawEnvVars: [\n\t\t{\n\t\t\tkey: \"STREAM_GATEWAY_HOST\",\n\t\t\tdefaultValue: \"stream-gateway\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Hostname of the stream-gateway container\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"STREAM_GATEWAY_RTMP_PORT\",\n\t\t\tdefaultValue: \"1935\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"RTMP ingest port on the stream-gateway\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"STREAM_GATEWAY_HLS_PORT\",\n\t\t\tdefaultValue: \"8080\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"HTTP port for HLS preview on the stream-gateway\",\n\t\t\trequired: true,\n\t\t},\n\t],\n\n\tdocsUrl: \"https://github.com/tiangolo/nginx-rtmp-docker\",\n\ttags: [\n\t\t\"streaming\",\n\t\t\"rtmp\",\n\t\t\"hls\",\n\t\t\"relay\",\n\t\t\"youtube\",\n\t\t\"twitch\",\n\t\t\"tiktok\",\n\t\t\"telegram\",\n\t\t\"obs\",\n\t\t\"nginx\",\n\t],\n\tmaturity: \"experimental\",\n\n\trequires: [],\n\trecommends: [\"desktop-environment\"],\n\tconflictsWith: [],\n\n\tremovalWarning:\n\t\t\"⚠️ STREAMING KEYS REQUIRED: To relay to platforms you must provide at least one stream key. Without any keys configured the gateway will still accept RTMP input and serve HLS locally but nothing will be forwarded.\",\n\tminMemoryMB: 512,\n\tgpuRequired: false,\n\n\tnativeSupported: true,\n\tnativeRecipes: [\n\t\t{\n\t\t\tplatform: \"linux\",\n\t\t\tinstallSteps: [\n\t\t\t\t\"command -v nginx >/dev/null 2>&1 || (command -v apt-get >/dev/null 2>&1 && sudo apt-get update -qq && sudo apt-get install -y -qq nginx libnginx-mod-rtmp ffmpeg)\",\n\t\t\t\t\"command -v nginx >/dev/null 2>&1 || (command -v dnf >/dev/null 2>&1 && sudo dnf install -y nginx nginx-mod-rtmp ffmpeg)\",\n\t\t\t],\n\t\t\tstartCommand: \"sudo systemctl start nginx\",\n\t\t\tstopCommand: \"sudo systemctl stop nginx\",\n\t\t\tconfigPath: \"/etc/nginx/nginx.conf\",\n\t\t\tconfigTemplate:\n\t\t\t\t'# Generated for OpenClaw bare-metal\\nworker_processes auto;\\nrtmp_auto_push on;\\n\\nevents { worker_connections 1024; }\\n\\nrtmp {\\n server {\\n listen 1935;\\n chunk_size 4096;\\n application live {\\n live on;\\n record off;\\n hls on;\\n hls_path /tmp/hls;\\n hls_fragment 3;\\n hls_playlist_length 60;\\n }\\n }\\n}\\n\\nhttp {\\n server {\\n listen 8080;\\n location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /tmp; add_header Cache-Control no-cache; }\\n location /health { return 200 \"OK\"; }\\n }\\n}\\n',\n\t\t\tsystemdUnit: \"nginx\",\n\t\t},\n\t],\n};\n"],"mappings":";AAEA,MAAa,0BAA6C;CACzD,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO,CACN;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,EACD;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,CACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa;EACZ;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;CACD,aAAa;EACZ,MAAM;EACN,UAAU;EACV,SAAS;EACT,SAAS;EACT;CACD,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EACP,WAAW;EACV,QAAQ;GAAE,MAAM;GAAO,QAAQ;GAAM;EACrC,cAAc;GAAE,MAAM;GAAO,QAAQ;GAAQ;EAC7C,EACD;CAED,QAAQ,EAAE;CACV,iBAAiB;EAChB;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;CAED,SAAS;CACT,MAAM;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,CAAC,sBAAsB;CACnC,eAAe,EAAE;CAEjB,gBACC;CACD,aAAa;CACb,aAAa;CAEb,iBAAiB;CACjB,eAAe,CACd;EACC,UAAU;EACV,cAAc,CACb,qKACA,0HACA;EACD,cAAc;EACd,aAAa;EACb,YAAY;EACZ,gBACC;EACD,aAAa;EACb,CACD;CACD"}
|
|
@@ -33,7 +33,6 @@ const traefikDefinition = {
|
|
|
33
33
|
description: "Let's Encrypt certificate storage"
|
|
34
34
|
}],
|
|
35
35
|
environment: [],
|
|
36
|
-
command: "--api.dashboard=true --providers.docker=true --entrypoints.web.address=:80 --entrypoints.websecure.address=:443",
|
|
37
36
|
dependsOn: [],
|
|
38
37
|
restartPolicy: "unless-stopped",
|
|
39
38
|
networks: ["openclaw-network"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traefik.mjs","names":[],"sources":["../../../src/services/definitions/traefik.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const traefikDefinition: ServiceDefinition = {\n\tid: \"traefik\",\n\tname: \"Traefik\",\n\tdescription:\n\t\t\"Cloud-native reverse proxy and load balancer with automatic service discovery, Let's Encrypt integration, and a built-in dashboard.\",\n\tcategory: \"proxy\",\n\ticon: \"🔀\",\n\n\timage: \"traefik\",\n\timageTag: \"v3.3\",\n\tports: [\n\t\t{\n\t\t\thost: 80,\n\t\t\tcontainer: 80,\n\t\t\tdescription: \"HTTP entrypoint\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 443,\n\t\t\tcontainer: 443,\n\t\t\tdescription: \"HTTPS entrypoint\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 8085,\n\t\t\tcontainer: 8080,\n\t\t\tdescription: \"Traefik dashboard\",\n\t\t\texposed: true,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"traefik-certs\",\n\t\t\tcontainerPath: \"/letsencrypt\",\n\t\t\tdescription: \"Let's Encrypt certificate storage\",\n\t\t},\n\t],\n\tenvironment: [],\n\
|
|
1
|
+
{"version":3,"file":"traefik.mjs","names":[],"sources":["../../../src/services/definitions/traefik.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const traefikDefinition: ServiceDefinition = {\n\tid: \"traefik\",\n\tname: \"Traefik\",\n\tdescription:\n\t\t\"Cloud-native reverse proxy and load balancer with automatic service discovery, Let's Encrypt integration, and a built-in dashboard.\",\n\tcategory: \"proxy\",\n\ticon: \"🔀\",\n\n\timage: \"traefik\",\n\timageTag: \"v3.3\",\n\tports: [\n\t\t{\n\t\t\thost: 80,\n\t\t\tcontainer: 80,\n\t\t\tdescription: \"HTTP entrypoint\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 443,\n\t\t\tcontainer: 443,\n\t\t\tdescription: \"HTTPS entrypoint\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 8085,\n\t\t\tcontainer: 8080,\n\t\t\tdescription: \"Traefik dashboard\",\n\t\t\texposed: true,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"traefik-certs\",\n\t\t\tcontainerPath: \"/letsencrypt\",\n\t\t\tdescription: \"Let's Encrypt certificate storage\",\n\t\t},\n\t],\n\tenvironment: [],\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tskills: [],\n\topenclawEnvVars: [],\n\n\tdocsUrl: \"https://doc.traefik.io/traefik/\",\n\ttags: [\"reverse-proxy\", \"load-balancer\", \"service-discovery\"],\n\tmaturity: \"stable\",\n\n\trequires: [],\n\trecommends: [],\n\tconflictsWith: [\"caddy\"],\n\n\tminMemoryMB: 64,\n\tgpuRequired: false,\n};\n"],"mappings":";AAEA,MAAa,oBAAuC;CACnD,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO;EACN;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa,EAAE;CACf,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EAAE;CACV,iBAAiB,EAAE;CAEnB,SAAS;CACT,MAAM;EAAC;EAAiB;EAAiB;EAAoB;CAC7D,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,EAAE;CACd,eAAe,CAAC,QAAQ;CAExB,aAAa;CACb,aAAa;CACb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xyops.d.mts","names":[],"sources":["../../../src/services/definitions/xyops.ts"],"mappings":";;;cAEa,eAAA,EAAiB,iBAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//#region src/services/definitions/xyops.ts
|
|
2
|
+
const xyopsDefinition = {
|
|
3
|
+
id: "xyops",
|
|
4
|
+
name: "xyOps",
|
|
5
|
+
description: "Job scheduling, workflow automation, server monitoring, alerting, and incident response platform with a visual workflow editor and fleet management.",
|
|
6
|
+
category: "automation",
|
|
7
|
+
icon: "⚙️",
|
|
8
|
+
image: "ghcr.io/pixlcore/xyops",
|
|
9
|
+
imageTag: "latest",
|
|
10
|
+
ports: [{
|
|
11
|
+
host: 5522,
|
|
12
|
+
container: 5522,
|
|
13
|
+
description: "xyOps web interface",
|
|
14
|
+
exposed: true
|
|
15
|
+
}, {
|
|
16
|
+
host: 5523,
|
|
17
|
+
container: 5523,
|
|
18
|
+
description: "xyOps secondary service",
|
|
19
|
+
exposed: false
|
|
20
|
+
}],
|
|
21
|
+
volumes: [{
|
|
22
|
+
name: "xyops-data",
|
|
23
|
+
containerPath: "/opt/xyops/data",
|
|
24
|
+
description: "Persistent xyOps data and configuration"
|
|
25
|
+
}, {
|
|
26
|
+
name: "/var/run/docker.sock",
|
|
27
|
+
containerPath: "/var/run/docker.sock",
|
|
28
|
+
description: "Docker socket for container management"
|
|
29
|
+
}],
|
|
30
|
+
environment: [{
|
|
31
|
+
key: "TZ",
|
|
32
|
+
defaultValue: "UTC",
|
|
33
|
+
secret: false,
|
|
34
|
+
description: "Timezone for xyOps",
|
|
35
|
+
required: false
|
|
36
|
+
}, {
|
|
37
|
+
key: "XYOPS_xysat_local",
|
|
38
|
+
defaultValue: "1",
|
|
39
|
+
secret: false,
|
|
40
|
+
description: "Enable local satellite mode for monitoring the host",
|
|
41
|
+
required: false
|
|
42
|
+
}],
|
|
43
|
+
healthcheck: {
|
|
44
|
+
test: "wget -q --spider http://localhost:5522 || exit 1",
|
|
45
|
+
interval: "30s",
|
|
46
|
+
timeout: "10s",
|
|
47
|
+
retries: 3,
|
|
48
|
+
startPeriod: "30s"
|
|
49
|
+
},
|
|
50
|
+
dependsOn: [],
|
|
51
|
+
restartPolicy: "unless-stopped",
|
|
52
|
+
networks: ["openclaw-network"],
|
|
53
|
+
skills: [],
|
|
54
|
+
openclawEnvVars: [{
|
|
55
|
+
key: "XYOPS_HOST",
|
|
56
|
+
defaultValue: "xyops",
|
|
57
|
+
secret: false,
|
|
58
|
+
description: "xyOps hostname for OpenClaw",
|
|
59
|
+
required: true
|
|
60
|
+
}, {
|
|
61
|
+
key: "XYOPS_PORT",
|
|
62
|
+
defaultValue: "5522",
|
|
63
|
+
secret: false,
|
|
64
|
+
description: "xyOps port for OpenClaw",
|
|
65
|
+
required: true
|
|
66
|
+
}],
|
|
67
|
+
docsUrl: "https://github.com/pixlcore/xyops",
|
|
68
|
+
tags: [
|
|
69
|
+
"scheduling",
|
|
70
|
+
"automation",
|
|
71
|
+
"monitoring",
|
|
72
|
+
"alerting",
|
|
73
|
+
"incident-response",
|
|
74
|
+
"workflow"
|
|
75
|
+
],
|
|
76
|
+
maturity: "stable",
|
|
77
|
+
requires: [],
|
|
78
|
+
recommends: [],
|
|
79
|
+
conflictsWith: [],
|
|
80
|
+
minMemoryMB: 256,
|
|
81
|
+
gpuRequired: false
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { xyopsDefinition };
|
|
86
|
+
//# sourceMappingURL=xyops.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xyops.mjs","names":[],"sources":["../../../src/services/definitions/xyops.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const xyopsDefinition: ServiceDefinition = {\n\tid: \"xyops\",\n\tname: \"xyOps\",\n\tdescription:\n\t\t\"Job scheduling, workflow automation, server monitoring, alerting, and incident response platform with a visual workflow editor and fleet management.\",\n\tcategory: \"automation\",\n\ticon: \"⚙️\",\n\n\timage: \"ghcr.io/pixlcore/xyops\",\n\timageTag: \"latest\",\n\tports: [\n\t\t{\n\t\t\thost: 5522,\n\t\t\tcontainer: 5522,\n\t\t\tdescription: \"xyOps web interface\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 5523,\n\t\t\tcontainer: 5523,\n\t\t\tdescription: \"xyOps secondary service\",\n\t\t\texposed: false,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"xyops-data\",\n\t\t\tcontainerPath: \"/opt/xyops/data\",\n\t\t\tdescription: \"Persistent xyOps data and configuration\",\n\t\t},\n\t\t{\n\t\t\tname: \"/var/run/docker.sock\",\n\t\t\tcontainerPath: \"/var/run/docker.sock\",\n\t\t\tdescription: \"Docker socket for container management\",\n\t\t},\n\t],\n\tenvironment: [\n\t\t{\n\t\t\tkey: \"TZ\",\n\t\t\tdefaultValue: \"UTC\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Timezone for xyOps\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"XYOPS_xysat_local\",\n\t\t\tdefaultValue: \"1\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Enable local satellite mode for monitoring the host\",\n\t\t\trequired: false,\n\t\t},\n\t],\n\thealthcheck: {\n\t\ttest: \"wget -q --spider http://localhost:5522 || exit 1\",\n\t\tinterval: \"30s\",\n\t\ttimeout: \"10s\",\n\t\tretries: 3,\n\t\tstartPeriod: \"30s\",\n\t},\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tskills: [],\n\topenclawEnvVars: [\n\t\t{\n\t\t\tkey: \"XYOPS_HOST\",\n\t\t\tdefaultValue: \"xyops\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"xyOps hostname for OpenClaw\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"XYOPS_PORT\",\n\t\t\tdefaultValue: \"5522\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"xyOps port for OpenClaw\",\n\t\t\trequired: true,\n\t\t},\n\t],\n\n\tdocsUrl: \"https://github.com/pixlcore/xyops\",\n\ttags: [\"scheduling\", \"automation\", \"monitoring\", \"alerting\", \"incident-response\", \"workflow\"],\n\tmaturity: \"stable\",\n\n\trequires: [],\n\trecommends: [],\n\tconflictsWith: [],\n\n\tminMemoryMB: 256,\n\tgpuRequired: false,\n};\n"],"mappings":";AAEA,MAAa,kBAAqC;CACjD,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO,CACN;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,EACD;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,CACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,EACD;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa,CACZ;EACC,KAAK;EACL,cAAc;EACd,QAAQ;EACR,aAAa;EACb,UAAU;EACV,EACD;EACC,KAAK;EACL,cAAc;EACd,QAAQ;EACR,aAAa;EACb,UAAU;EACV,CACD;CACD,aAAa;EACZ,MAAM;EACN,UAAU;EACV,SAAS;EACT,SAAS;EACT,aAAa;EACb;CACD,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EAAE;CACV,iBAAiB,CAChB;EACC,KAAK;EACL,cAAc;EACd,QAAQ;EACR,aAAa;EACb,UAAU;EACV,EACD;EACC,KAAK;EACL,cAAc;EACd,QAAQ;EACR,aAAa;EACb,UAAU;EACV,CACD;CAED,SAAS;CACT,MAAM;EAAC;EAAc;EAAc;EAAc;EAAY;EAAqB;EAAW;CAC7F,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,EAAE;CACd,eAAe,EAAE;CAEjB,aAAa;CACb,aAAa;CACb"}
|
package/dist/types.d.mts
CHANGED
|
@@ -23,7 +23,9 @@ type ServiceDefinition = z.infer<typeof ServiceDefinitionSchema>;
|
|
|
23
23
|
type SkillPack = z.infer<typeof SkillPackSchema>;
|
|
24
24
|
type Preset = z.infer<typeof PresetSchema>;
|
|
25
25
|
type GenerationInput = z.infer<typeof GenerationInputSchema>;
|
|
26
|
-
type ComposeOptions = z.infer<typeof ComposeOptionsSchema
|
|
26
|
+
type ComposeOptions = z.infer<typeof ComposeOptionsSchema> & {
|
|
27
|
+
/** Dynamic Traefik labels per service, computed by the Traefik generator. */traefikLabels?: Map<string, Record<string, string>>;
|
|
28
|
+
};
|
|
27
29
|
type ResolvedService = z.infer<typeof ResolvedServiceSchema>;
|
|
28
30
|
type AddedDependency = z.infer<typeof AddedDependencySchema>;
|
|
29
31
|
type Warning = z.infer<typeof WarningSchema>;
|
|
@@ -39,6 +41,11 @@ interface ResolverInput {
|
|
|
39
41
|
gpu?: boolean;
|
|
40
42
|
platform?: Platform;
|
|
41
43
|
monitoring?: boolean;
|
|
44
|
+
memoryThresholds?: {
|
|
45
|
+
info: number;
|
|
46
|
+
warning: number;
|
|
47
|
+
critical: number;
|
|
48
|
+
};
|
|
42
49
|
}
|
|
43
50
|
interface GeneratedFiles {
|
|
44
51
|
[path: string]: string;
|
package/dist/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KAoCY,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,QAAA,GAAW,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,KAC1B,QAAA,GAAW,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,KAC1B,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,KAC/B,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,KAC3B,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KAClC,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAChC,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAChC,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KAC9B,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KAE9B,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,KAC/B,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAChC,MAAA,GAAS,CAAA,CAAE,KAAA,QAAa,YAAA;AAAA,KACxB,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KAE9B,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,uBAAA;AAAA,KACnC,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,KAC3B,MAAA,GAAS,CAAA,CAAE,KAAA,QAAa,YAAA;AAAA,KAExB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KAoCY,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,QAAA,GAAW,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,KAC1B,QAAA,GAAW,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,KAC1B,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,KAC/B,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,KAC3B,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KAClC,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAChC,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAChC,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KAC9B,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KAE9B,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,mBAAA;AAAA,KAC/B,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAChC,MAAA,GAAS,CAAA,CAAE,KAAA,QAAa,YAAA;AAAA,KACxB,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KAE9B,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,uBAAA;AAAA,KACnC,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,KAC3B,MAAA,GAAS,CAAA,CAAE,KAAA,QAAa,YAAA;AAAA,KAExB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;+EAE3C,aAAA,GAAgB,GAAA,SAAY,MAAA;AAAA;AAAA,KAEjB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,OAAA,GAAU,CAAA,CAAE,KAAA,QAAa,aAAA;AAAA,KACzB,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,WAAA;AAAA,KAC/B,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,KAEhC,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KAClC,QAAA,GAAW,CAAA,CAAE,KAAA,QAAa,cAAA;AAAA,UAIrB,aAAA;EAChB,QAAA;EACA,UAAA;EACA,KAAA,GAAQ,SAAA;EACR,GAAA;EACA,QAAA,GAAW,QAAA;EACX,UAAA;EACA,gBAAA;IAAqB,IAAA;IAAc,OAAA;IAAiB,QAAA;EAAA;AAAA;AAAA,UAGpC,cAAA;EAAA,CACf,IAAA;AAAA;AAAA,UAGe,kBAAA;EAChB,YAAA;EACA,UAAA;EACA,iBAAA;EACA,WAAA;AAAA;AAAA,UAGgB,gBAAA;EAChB,KAAA,EAAO,cAAA;EACP,QAAA,EAAU,kBAAA;AAAA;AAAA,UAGM,YAAA;EAChB,EAAA,EAAI,eAAA;EACJ,IAAA;EACA,IAAA;AAAA;AAAA,cAGY,kBAAA,EAAoB,YAAA"}
|
package/dist/types.mjs
CHANGED
|
@@ -84,6 +84,16 @@ const SERVICE_CATEGORIES = [
|
|
|
84
84
|
id: "communication",
|
|
85
85
|
name: "Notifications",
|
|
86
86
|
icon: "🔔"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "desktop",
|
|
90
|
+
name: "Desktop Environment",
|
|
91
|
+
icon: "🖥️"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "streaming",
|
|
95
|
+
name: "Streaming & Relay",
|
|
96
|
+
icon: "📺"
|
|
87
97
|
}
|
|
88
98
|
];
|
|
89
99
|
|
package/dist/types.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { z } from \"zod\";\nimport type {\n\tAddedDependencySchema,\n\tApiErrorSchema,\n\tComposeOptionsSchema,\n\tDeploymentTargetSchema,\n\tDeploymentTypeSchema,\n\tDeploySchema,\n\tEnvVariableSchema,\n\tErrorSchema,\n\tGenerationInputSchema,\n\tHealthCheckSchema,\n\tMaturitySchema,\n\tNativePlatformSchema,\n\tNativeRecipeSchema,\n\tOutputFormatSchema,\n\tPlatformSchema,\n\tPortMappingSchema,\n\tPresetSchema,\n\tProxyTypeSchema,\n\tResolvedServiceSchema,\n\tResolverOutputSchema,\n\tResourceLimitsSchema,\n\tRestartPolicySchema,\n\tServiceCategorySchema,\n\tServiceDefinitionSchema,\n\tSkillBindingSchema,\n\tSkillPackSchema,\n\tValidateRequestSchema,\n\tValidateResponseSchema,\n\tVolumeMappingSchema,\n\tWarningSchema,\n} from \"./schema.js\";\n\n// ─── Inferred Types ─────────────────────────────────────────────────────────\n\nexport type ServiceCategory = z.infer<typeof ServiceCategorySchema>;\nexport type Maturity = z.infer<typeof MaturitySchema>;\nexport type Platform = z.infer<typeof PlatformSchema>;\nexport type RestartPolicy = z.infer<typeof RestartPolicySchema>;\nexport type ProxyType = z.infer<typeof ProxyTypeSchema>;\nexport type DeploymentTarget = z.infer<typeof DeploymentTargetSchema>;\nexport type DeploymentType = z.infer<typeof DeploymentTypeSchema>;\nexport type NativePlatform = z.infer<typeof NativePlatformSchema>;\nexport type NativeRecipe = z.infer<typeof NativeRecipeSchema>;\nexport type OutputFormat = z.infer<typeof OutputFormatSchema>;\n\nexport type PortMapping = z.infer<typeof PortMappingSchema>;\nexport type VolumeMapping = z.infer<typeof VolumeMappingSchema>;\nexport type EnvVariable = z.infer<typeof EnvVariableSchema>;\nexport type HealthCheck = z.infer<typeof HealthCheckSchema>;\nexport type ResourceLimits = z.infer<typeof ResourceLimitsSchema>;\nexport type Deploy = z.infer<typeof DeploySchema>;\nexport type SkillBinding = z.infer<typeof SkillBindingSchema>;\n\nexport type ServiceDefinition = z.infer<typeof ServiceDefinitionSchema>;\nexport type SkillPack = z.infer<typeof SkillPackSchema>;\nexport type Preset = z.infer<typeof PresetSchema>;\n\nexport type GenerationInput = z.infer<typeof GenerationInputSchema>;\nexport type ComposeOptions = z.infer<typeof ComposeOptionsSchema
|
|
1
|
+
{"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { z } from \"zod\";\nimport type {\n\tAddedDependencySchema,\n\tApiErrorSchema,\n\tComposeOptionsSchema,\n\tDeploymentTargetSchema,\n\tDeploymentTypeSchema,\n\tDeploySchema,\n\tEnvVariableSchema,\n\tErrorSchema,\n\tGenerationInputSchema,\n\tHealthCheckSchema,\n\tMaturitySchema,\n\tNativePlatformSchema,\n\tNativeRecipeSchema,\n\tOutputFormatSchema,\n\tPlatformSchema,\n\tPortMappingSchema,\n\tPresetSchema,\n\tProxyTypeSchema,\n\tResolvedServiceSchema,\n\tResolverOutputSchema,\n\tResourceLimitsSchema,\n\tRestartPolicySchema,\n\tServiceCategorySchema,\n\tServiceDefinitionSchema,\n\tSkillBindingSchema,\n\tSkillPackSchema,\n\tValidateRequestSchema,\n\tValidateResponseSchema,\n\tVolumeMappingSchema,\n\tWarningSchema,\n} from \"./schema.js\";\n\n// ─── Inferred Types ─────────────────────────────────────────────────────────\n\nexport type ServiceCategory = z.infer<typeof ServiceCategorySchema>;\nexport type Maturity = z.infer<typeof MaturitySchema>;\nexport type Platform = z.infer<typeof PlatformSchema>;\nexport type RestartPolicy = z.infer<typeof RestartPolicySchema>;\nexport type ProxyType = z.infer<typeof ProxyTypeSchema>;\nexport type DeploymentTarget = z.infer<typeof DeploymentTargetSchema>;\nexport type DeploymentType = z.infer<typeof DeploymentTypeSchema>;\nexport type NativePlatform = z.infer<typeof NativePlatformSchema>;\nexport type NativeRecipe = z.infer<typeof NativeRecipeSchema>;\nexport type OutputFormat = z.infer<typeof OutputFormatSchema>;\n\nexport type PortMapping = z.infer<typeof PortMappingSchema>;\nexport type VolumeMapping = z.infer<typeof VolumeMappingSchema>;\nexport type EnvVariable = z.infer<typeof EnvVariableSchema>;\nexport type HealthCheck = z.infer<typeof HealthCheckSchema>;\nexport type ResourceLimits = z.infer<typeof ResourceLimitsSchema>;\nexport type Deploy = z.infer<typeof DeploySchema>;\nexport type SkillBinding = z.infer<typeof SkillBindingSchema>;\n\nexport type ServiceDefinition = z.infer<typeof ServiceDefinitionSchema>;\nexport type SkillPack = z.infer<typeof SkillPackSchema>;\nexport type Preset = z.infer<typeof PresetSchema>;\n\nexport type GenerationInput = z.infer<typeof GenerationInputSchema>;\nexport type ComposeOptions = z.infer<typeof ComposeOptionsSchema> & {\n\t/** Dynamic Traefik labels per service, computed by the Traefik generator. */\n\ttraefikLabels?: Map<string, Record<string, string>>;\n};\nexport type ResolvedService = z.infer<typeof ResolvedServiceSchema>;\nexport type AddedDependency = z.infer<typeof AddedDependencySchema>;\nexport type Warning = z.infer<typeof WarningSchema>;\nexport type ResolverError = z.infer<typeof ErrorSchema>;\nexport type ResolverOutput = z.infer<typeof ResolverOutputSchema>;\n\nexport type ValidateRequest = z.infer<typeof ValidateRequestSchema>;\nexport type ValidateResponse = z.infer<typeof ValidateResponseSchema>;\nexport type ApiError = z.infer<typeof ApiErrorSchema>;\n\n// ─── Additional Types ───────────────────────────────────────────────────────\n\nexport interface ResolverInput {\n\tservices: string[];\n\tskillPacks: string[];\n\tproxy?: ProxyType;\n\tgpu?: boolean;\n\tplatform?: Platform;\n\tmonitoring?: boolean;\n\tmemoryThresholds?: { info: number; warning: number; critical: number };\n}\n\nexport interface GeneratedFiles {\n\t[path: string]: string;\n}\n\nexport interface GenerationMetadata {\n\tserviceCount: number;\n\tskillCount: number;\n\testimatedMemoryMB: number;\n\tgeneratedAt: string;\n}\n\nexport interface GenerationResult {\n\tfiles: GeneratedFiles;\n\tmetadata: GenerationMetadata;\n}\n\nexport interface CategoryInfo {\n\tid: ServiceCategory;\n\tname: string;\n\ticon: string;\n}\n\nexport const SERVICE_CATEGORIES: CategoryInfo[] = [\n\t{ id: \"coding-agent\", name: \"AI Coding Agents\", icon: \"💻\" },\n\t{ id: \"ai-platform\", name: \"AI Platforms & Chat UIs\", icon: \"🧪\" },\n\t{ id: \"ai\", name: \"AI / Local Models\", icon: \"🤖\" },\n\t{ id: \"automation\", name: \"Automation & Workflows\", icon: \"🔄\" },\n\t{ id: \"vector-db\", name: \"Vector Databases\", icon: \"🧠\" },\n\t{ id: \"media\", name: \"Media & Video\", icon: \"🎬\" },\n\t{ id: \"social-media\", name: \"Social Media\", icon: \"📱\" },\n\t{ id: \"analytics\", name: \"Analytics\", icon: \"📊\" },\n\t{ id: \"knowledge\", name: \"Knowledge & Documents\", icon: \"📚\" },\n\t{ id: \"storage\", name: \"Object Storage\", icon: \"💾\" },\n\t{ id: \"database\", name: \"Databases & Caching\", icon: \"🗄️\" },\n\t{ id: \"dev-tools\", name: \"Developer Tools\", icon: \"🛠️\" },\n\t{ id: \"proxy\", name: \"Reverse Proxy\", icon: \"🌐\" },\n\t{ id: \"monitoring\", name: \"Monitoring\", icon: \"📡\" },\n\t{ id: \"browser\", name: \"Browser Automation\", icon: \"🌐\" },\n\t{ id: \"search\", name: \"Search\", icon: \"🔍\" },\n\t{ id: \"communication\", name: \"Notifications\", icon: \"🔔\" },\n\t{ id: \"desktop\", name: \"Desktop Environment\", icon: \"🖥️\" },\n\t{ id: \"streaming\", name: \"Streaming & Relay\", icon: \"📺\" },\n];\n"],"mappings":";AA4GA,MAAa,qBAAqC;CACjD;EAAE,IAAI;EAAgB,MAAM;EAAoB,MAAM;EAAM;CAC5D;EAAE,IAAI;EAAe,MAAM;EAA2B,MAAM;EAAM;CAClE;EAAE,IAAI;EAAM,MAAM;EAAqB,MAAM;EAAM;CACnD;EAAE,IAAI;EAAc,MAAM;EAA0B,MAAM;EAAM;CAChE;EAAE,IAAI;EAAa,MAAM;EAAoB,MAAM;EAAM;CACzD;EAAE,IAAI;EAAS,MAAM;EAAiB,MAAM;EAAM;CAClD;EAAE,IAAI;EAAgB,MAAM;EAAgB,MAAM;EAAM;CACxD;EAAE,IAAI;EAAa,MAAM;EAAa,MAAM;EAAM;CAClD;EAAE,IAAI;EAAa,MAAM;EAAyB,MAAM;EAAM;CAC9D;EAAE,IAAI;EAAW,MAAM;EAAkB,MAAM;EAAM;CACrD;EAAE,IAAI;EAAY,MAAM;EAAuB,MAAM;EAAO;CAC5D;EAAE,IAAI;EAAa,MAAM;EAAmB,MAAM;EAAO;CACzD;EAAE,IAAI;EAAS,MAAM;EAAiB,MAAM;EAAM;CAClD;EAAE,IAAI;EAAc,MAAM;EAAc,MAAM;EAAM;CACpD;EAAE,IAAI;EAAW,MAAM;EAAsB,MAAM;EAAM;CACzD;EAAE,IAAI;EAAU,MAAM;EAAU,MAAM;EAAM;CAC5C;EAAE,IAAI;EAAiB,MAAM;EAAiB,MAAM;EAAM;CAC1D;EAAE,IAAI;EAAW,MAAM;EAAuB,MAAM;EAAO;CAC3D;EAAE,IAAI;EAAa,MAAM;EAAqB,MAAM;EAAM;CAC1D"}
|
package/dist/validator.mjs
CHANGED
|
@@ -55,6 +55,17 @@ function checkEnvCompleteness(resolved, errors, warnings, generateSecrets) {
|
|
|
55
55
|
type: "secret_needed",
|
|
56
56
|
message: `Secret "${envVar.key}" for "${svc.definition.name}" needs to be configured manually`
|
|
57
57
|
});
|
|
58
|
+
if (envVar.validation && envVar.defaultValue) try {
|
|
59
|
+
if (!new RegExp(envVar.validation).test(envVar.defaultValue)) warnings.push({
|
|
60
|
+
type: "env_validation",
|
|
61
|
+
message: `Environment variable "${envVar.key}" for "${svc.definition.name}" default value does not match validation pattern: ${envVar.validation}`
|
|
62
|
+
});
|
|
63
|
+
} catch {
|
|
64
|
+
warnings.push({
|
|
65
|
+
type: "env_validation",
|
|
66
|
+
message: `Environment variable "${envVar.key}" for "${svc.definition.name}" has invalid validation regex: ${envVar.validation}`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
58
69
|
}
|
|
59
70
|
}
|
|
60
71
|
function checkNetworkConsistency(resolved, warnings) {
|
package/dist/validator.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.mjs","names":[],"sources":["../src/validator.ts"],"sourcesContent":["import { parse } from \"yaml\";\nimport type { ResolverError, ResolverOutput, Warning } from \"./types.js\";\n\nexport interface ValidationResult {\n\tvalid: boolean;\n\terrors: ResolverError[];\n\twarnings: Warning[];\n}\n\n/**\n * Validates a complete generated stack before writing files.\n * Checks for port conflicts, volume uniqueness, env completeness,\n * dependency ordering, YAML validity, and more.\n */\nexport function validate(\n\tresolved: ResolverOutput,\n\tcomposedYaml: string,\n\toptions: { domain?: string; generateSecrets?: boolean } = {},\n): ValidationResult {\n\tconst errors: ResolverError[] = [];\n\tconst warnings: Warning[] = [];\n\n\tcheckPortConflicts(resolved, errors);\n\tcheckVolumeUniqueness(resolved, errors);\n\tcheckEnvCompleteness(resolved, errors, warnings, options.generateSecrets ?? true);\n\tcheckNetworkConsistency(resolved, warnings);\n\tcheckDependencyDAG(resolved, errors);\n\tcheckYamlValidity(composedYaml, errors);\n\n\tif (options.domain) {\n\t\tcheckDomainFormat(options.domain, errors);\n\t}\n\n\treturn {\n\t\tvalid: errors.length === 0,\n\t\terrors,\n\t\twarnings,\n\t};\n}\n\nfunction checkPortConflicts(resolved: ResolverOutput, errors: ResolverError[]): void {\n\tconst hostPorts = new Map<number, string>();\n\tfor (const svc of resolved.services) {\n\t\tfor (const port of svc.definition.ports) {\n\t\t\tif (!port.exposed) continue;\n\t\t\tconst existing = hostPorts.get(port.host);\n\t\t\tif (existing) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"port_conflict\",\n\t\t\t\t\tmessage: `Port ${port.host} is used by both \"${existing}\" and \"${svc.definition.name}\"`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\thostPorts.set(port.host, svc.definition.name);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction checkVolumeUniqueness(resolved: ResolverOutput, errors: ResolverError[]): void {\n\tconst volumeNames = new Map<string, string>();\n\tfor (const svc of resolved.services) {\n\t\tfor (const vol of svc.definition.volumes) {\n\t\t\tconst existing = volumeNames.get(vol.name);\n\t\t\tif (existing && existing !== svc.definition.id) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"volume_conflict\",\n\t\t\t\t\tmessage: `Volume name \"${vol.name}\" is used by both \"${existing}\" and \"${svc.definition.id}\"`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tvolumeNames.set(vol.name, svc.definition.id);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction checkEnvCompleteness(\n\tresolved: ResolverOutput,\n\terrors: ResolverError[],\n\twarnings: Warning[],\n\tgenerateSecrets: boolean,\n): void {\n\tfor (const svc of resolved.services) {\n\t\tfor (const envVar of svc.definition.environment) {\n\t\t\tif (envVar.required && !envVar.defaultValue && !envVar.secret) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"missing_env\",\n\t\t\t\t\tmessage: `Required environment variable \"${envVar.key}\" for \"${svc.definition.name}\" has no default value`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (envVar.secret && !generateSecrets && !envVar.defaultValue) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"secret_needed\",\n\t\t\t\t\tmessage: `Secret \"${envVar.key}\" for \"${svc.definition.name}\" needs to be configured manually`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction checkNetworkConsistency(resolved: ResolverOutput, warnings: Warning[]): void {\n\tfor (const svc of resolved.services) {\n\t\tif (!svc.definition.networks.includes(\"openclaw-network\")) {\n\t\t\twarnings.push({\n\t\t\t\ttype: \"network\",\n\t\t\t\tmessage: `Service \"${svc.definition.name}\" is not on openclaw-network — it may not be reachable from OpenClaw`,\n\t\t\t});\n\t\t}\n\t}\n}\n\nfunction checkDependencyDAG(resolved: ResolverOutput, errors: ResolverError[]): void {\n\tconst ids = new Set(resolved.services.map((s) => s.definition.id));\n\tconst visited = new Set<string>();\n\tconst inStack = new Set<string>();\n\n\tconst adjList = new Map<string, string[]>();\n\tfor (const svc of resolved.services) {\n\t\tconst deps = [...svc.definition.requires, ...svc.definition.dependsOn].filter((d) =>\n\t\t\tids.has(d),\n\t\t);\n\t\tadjList.set(svc.definition.id, deps);\n\t}\n\n\tfunction hasCycle(node: string): boolean {\n\t\tif (inStack.has(node)) return true;\n\t\tif (visited.has(node)) return false;\n\t\tvisited.add(node);\n\t\tinStack.add(node);\n\t\tfor (const dep of adjList.get(node) ?? []) {\n\t\t\tif (hasCycle(dep)) return true;\n\t\t}\n\t\tinStack.delete(node);\n\t\treturn false;\n\t}\n\n\tfor (const id of ids) {\n\t\tif (hasCycle(id)) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"cycle\",\n\t\t\t\tmessage: `Circular dependency detected involving \"${id}\"`,\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nfunction checkYamlValidity(yaml: string, errors: ResolverError[]): void {\n\ttry {\n\t\tparse(yaml);\n\t} catch (e) {\n\t\terrors.push({\n\t\t\ttype: \"yaml_invalid\",\n\t\t\tmessage: `Generated YAML is not valid: ${e instanceof Error ? e.message : String(e)}`,\n\t\t});\n\t}\n}\n\nfunction checkDomainFormat(domain: string, errors: ResolverError[]): void {\n\tconst domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$/;\n\tif (!domainRegex.test(domain)) {\n\t\terrors.push({\n\t\t\ttype: \"invalid_domain\",\n\t\t\tmessage: `\"${domain}\" is not a valid domain name`,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,SACf,UACA,cACA,UAA0D,EAAE,EACzC;CACnB,MAAM,SAA0B,EAAE;CAClC,MAAM,WAAsB,EAAE;AAE9B,oBAAmB,UAAU,OAAO;AACpC,uBAAsB,UAAU,OAAO;AACvC,sBAAqB,UAAU,QAAQ,UAAU,QAAQ,mBAAmB,KAAK;AACjF,yBAAwB,UAAU,SAAS;AAC3C,oBAAmB,UAAU,OAAO;AACpC,mBAAkB,cAAc,OAAO;AAEvC,KAAI,QAAQ,OACX,mBAAkB,QAAQ,QAAQ,OAAO;AAG1C,QAAO;EACN,OAAO,OAAO,WAAW;EACzB;EACA;EACA;;AAGF,SAAS,mBAAmB,UAA0B,QAA+B;CACpF,MAAM,4BAAY,IAAI,KAAqB;AAC3C,MAAK,MAAM,OAAO,SAAS,SAC1B,MAAK,MAAM,QAAQ,IAAI,WAAW,OAAO;AACxC,MAAI,CAAC,KAAK,QAAS;EACnB,MAAM,WAAW,UAAU,IAAI,KAAK,KAAK;AACzC,MAAI,SACH,QAAO,KAAK;GACX,MAAM;GACN,SAAS,QAAQ,KAAK,KAAK,oBAAoB,SAAS,SAAS,IAAI,WAAW,KAAK;GACrF,CAAC;MAEF,WAAU,IAAI,KAAK,MAAM,IAAI,WAAW,KAAK;;;AAMjD,SAAS,sBAAsB,UAA0B,QAA+B;CACvF,MAAM,8BAAc,IAAI,KAAqB;AAC7C,MAAK,MAAM,OAAO,SAAS,SAC1B,MAAK,MAAM,OAAO,IAAI,WAAW,SAAS;EACzC,MAAM,WAAW,YAAY,IAAI,IAAI,KAAK;AAC1C,MAAI,YAAY,aAAa,IAAI,WAAW,GAC3C,QAAO,KAAK;GACX,MAAM;GACN,SAAS,gBAAgB,IAAI,KAAK,qBAAqB,SAAS,SAAS,IAAI,WAAW,GAAG;GAC3F,CAAC;MAEF,aAAY,IAAI,IAAI,MAAM,IAAI,WAAW,GAAG;;;AAMhD,SAAS,qBACR,UACA,QACA,UACA,iBACO;AACP,MAAK,MAAM,OAAO,SAAS,SAC1B,MAAK,MAAM,UAAU,IAAI,WAAW,aAAa;AAChD,MAAI,OAAO,YAAY,CAAC,OAAO,gBAAgB,CAAC,OAAO,OACtD,QAAO,KAAK;GACX,MAAM;GACN,SAAS,kCAAkC,OAAO,IAAI,SAAS,IAAI,WAAW,KAAK;GACnF,CAAC;AAEH,MAAI,OAAO,UAAU,CAAC,mBAAmB,CAAC,OAAO,aAChD,UAAS,KAAK;GACb,MAAM;GACN,SAAS,WAAW,OAAO,IAAI,SAAS,IAAI,WAAW,KAAK;GAC5D,CAAC
|
|
1
|
+
{"version":3,"file":"validator.mjs","names":[],"sources":["../src/validator.ts"],"sourcesContent":["import { parse } from \"yaml\";\nimport type { ResolverError, ResolverOutput, Warning } from \"./types.js\";\n\nexport interface ValidationResult {\n\tvalid: boolean;\n\terrors: ResolverError[];\n\twarnings: Warning[];\n}\n\n/**\n * Validates a complete generated stack before writing files.\n * Checks for port conflicts, volume uniqueness, env completeness,\n * dependency ordering, YAML validity, and more.\n */\nexport function validate(\n\tresolved: ResolverOutput,\n\tcomposedYaml: string,\n\toptions: { domain?: string; generateSecrets?: boolean } = {},\n): ValidationResult {\n\tconst errors: ResolverError[] = [];\n\tconst warnings: Warning[] = [];\n\n\tcheckPortConflicts(resolved, errors);\n\tcheckVolumeUniqueness(resolved, errors);\n\tcheckEnvCompleteness(resolved, errors, warnings, options.generateSecrets ?? true);\n\tcheckNetworkConsistency(resolved, warnings);\n\tcheckDependencyDAG(resolved, errors);\n\tcheckYamlValidity(composedYaml, errors);\n\n\tif (options.domain) {\n\t\tcheckDomainFormat(options.domain, errors);\n\t}\n\n\treturn {\n\t\tvalid: errors.length === 0,\n\t\terrors,\n\t\twarnings,\n\t};\n}\n\nfunction checkPortConflicts(resolved: ResolverOutput, errors: ResolverError[]): void {\n\tconst hostPorts = new Map<number, string>();\n\tfor (const svc of resolved.services) {\n\t\tfor (const port of svc.definition.ports) {\n\t\t\tif (!port.exposed) continue;\n\t\t\tconst existing = hostPorts.get(port.host);\n\t\t\tif (existing) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"port_conflict\",\n\t\t\t\t\tmessage: `Port ${port.host} is used by both \"${existing}\" and \"${svc.definition.name}\"`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\thostPorts.set(port.host, svc.definition.name);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction checkVolumeUniqueness(resolved: ResolverOutput, errors: ResolverError[]): void {\n\tconst volumeNames = new Map<string, string>();\n\tfor (const svc of resolved.services) {\n\t\tfor (const vol of svc.definition.volumes) {\n\t\t\tconst existing = volumeNames.get(vol.name);\n\t\t\tif (existing && existing !== svc.definition.id) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"volume_conflict\",\n\t\t\t\t\tmessage: `Volume name \"${vol.name}\" is used by both \"${existing}\" and \"${svc.definition.id}\"`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tvolumeNames.set(vol.name, svc.definition.id);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction checkEnvCompleteness(\n\tresolved: ResolverOutput,\n\terrors: ResolverError[],\n\twarnings: Warning[],\n\tgenerateSecrets: boolean,\n): void {\n\tfor (const svc of resolved.services) {\n\t\tfor (const envVar of svc.definition.environment) {\n\t\t\tif (envVar.required && !envVar.defaultValue && !envVar.secret) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"missing_env\",\n\t\t\t\t\tmessage: `Required environment variable \"${envVar.key}\" for \"${svc.definition.name}\" has no default value`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (envVar.secret && !generateSecrets && !envVar.defaultValue) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"secret_needed\",\n\t\t\t\t\tmessage: `Secret \"${envVar.key}\" for \"${svc.definition.name}\" needs to be configured manually`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (envVar.validation && envVar.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst regex = new RegExp(envVar.validation);\n\t\t\t\t\tif (!regex.test(envVar.defaultValue)) {\n\t\t\t\t\t\twarnings.push({\n\t\t\t\t\t\t\ttype: \"env_validation\",\n\t\t\t\t\t\t\tmessage: `Environment variable \"${envVar.key}\" for \"${svc.definition.name}\" default value does not match validation pattern: ${envVar.validation}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\twarnings.push({\n\t\t\t\t\t\ttype: \"env_validation\",\n\t\t\t\t\t\tmessage: `Environment variable \"${envVar.key}\" for \"${svc.definition.name}\" has invalid validation regex: ${envVar.validation}`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction checkNetworkConsistency(resolved: ResolverOutput, warnings: Warning[]): void {\n\tfor (const svc of resolved.services) {\n\t\tif (!svc.definition.networks.includes(\"openclaw-network\")) {\n\t\t\twarnings.push({\n\t\t\t\ttype: \"network\",\n\t\t\t\tmessage: `Service \"${svc.definition.name}\" is not on openclaw-network — it may not be reachable from OpenClaw`,\n\t\t\t});\n\t\t}\n\t}\n}\n\nfunction checkDependencyDAG(resolved: ResolverOutput, errors: ResolverError[]): void {\n\tconst ids = new Set(resolved.services.map((s) => s.definition.id));\n\tconst visited = new Set<string>();\n\tconst inStack = new Set<string>();\n\n\tconst adjList = new Map<string, string[]>();\n\tfor (const svc of resolved.services) {\n\t\tconst deps = [...svc.definition.requires, ...svc.definition.dependsOn].filter((d) =>\n\t\t\tids.has(d),\n\t\t);\n\t\tadjList.set(svc.definition.id, deps);\n\t}\n\n\tfunction hasCycle(node: string): boolean {\n\t\tif (inStack.has(node)) return true;\n\t\tif (visited.has(node)) return false;\n\t\tvisited.add(node);\n\t\tinStack.add(node);\n\t\tfor (const dep of adjList.get(node) ?? []) {\n\t\t\tif (hasCycle(dep)) return true;\n\t\t}\n\t\tinStack.delete(node);\n\t\treturn false;\n\t}\n\n\tfor (const id of ids) {\n\t\tif (hasCycle(id)) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"cycle\",\n\t\t\t\tmessage: `Circular dependency detected involving \"${id}\"`,\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nfunction checkYamlValidity(yaml: string, errors: ResolverError[]): void {\n\ttry {\n\t\tparse(yaml);\n\t} catch (e) {\n\t\terrors.push({\n\t\t\ttype: \"yaml_invalid\",\n\t\t\tmessage: `Generated YAML is not valid: ${e instanceof Error ? e.message : String(e)}`,\n\t\t});\n\t}\n}\n\nfunction checkDomainFormat(domain: string, errors: ResolverError[]): void {\n\tconst domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$/;\n\tif (!domainRegex.test(domain)) {\n\t\terrors.push({\n\t\t\ttype: \"invalid_domain\",\n\t\t\tmessage: `\"${domain}\" is not a valid domain name`,\n\t\t});\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { ResolverOutput, ServiceDefinition, Warning } from "./types.mjs";
|
|
|
5
5
|
declare function getImageTag(serviceId: string): string | undefined;
|
|
6
6
|
/** Get the full image reference (image:tag) for a service */
|
|
7
7
|
declare function getImageReference(serviceId: string): string | undefined;
|
|
8
|
-
/** Pin all service image tags
|
|
8
|
+
/** Pin all service image tags to the registry-defined versions (returns a copy) */
|
|
9
9
|
declare function pinImageTags(resolved: ResolverOutput): ResolverOutput;
|
|
10
10
|
/** Check for known compatibility issues between services */
|
|
11
11
|
declare function checkCompatibility(services: ServiceDefinition[]): Warning[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-manager.d.mts","names":[],"sources":["../src/version-manager.ts"],"mappings":";;;;iBAIgB,WAAA,CAAY,SAAA;AAA5B;AAAA,iBAMgB,iBAAA,CAAkB,SAAA;;iBAOlB,YAAA,CAAa,QAAA,EAAU,cAAA,GAAiB,cAAA;;
|
|
1
|
+
{"version":3,"file":"version-manager.d.mts","names":[],"sources":["../src/version-manager.ts"],"mappings":";;;;iBAIgB,WAAA,CAAY,SAAA;AAA5B;AAAA,iBAMgB,iBAAA,CAAkB,SAAA;;iBAOlB,YAAA,CAAa,QAAA,EAAU,cAAA,GAAiB,cAAA;;iBAkBxC,kBAAA,CAAmB,QAAA,EAAU,iBAAA,KAAsB,OAAA"}
|
package/dist/version-manager.mjs
CHANGED
|
@@ -11,14 +11,20 @@ function getImageReference(serviceId) {
|
|
|
11
11
|
if (!svc) return void 0;
|
|
12
12
|
return `${svc.image}:${svc.imageTag}`;
|
|
13
13
|
}
|
|
14
|
-
/** Pin all service image tags
|
|
14
|
+
/** Pin all service image tags to the registry-defined versions (returns a copy) */
|
|
15
15
|
function pinImageTags(resolved) {
|
|
16
16
|
return {
|
|
17
17
|
...resolved,
|
|
18
|
-
services: resolved.services.map((s) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
services: resolved.services.map((s) => {
|
|
19
|
+
const pinnedTag = getServiceById(s.definition.id)?.imageTag ?? s.definition.imageTag;
|
|
20
|
+
return {
|
|
21
|
+
...s,
|
|
22
|
+
definition: {
|
|
23
|
+
...s.definition,
|
|
24
|
+
imageTag: pinnedTag
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
})
|
|
22
28
|
};
|
|
23
29
|
}
|
|
24
30
|
/** Check for known compatibility issues between services */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-manager.mjs","names":[],"sources":["../src/version-manager.ts"],"sourcesContent":["import { getServiceById } from \"./services/registry.js\";\nimport type { ResolverOutput, ServiceDefinition, Warning } from \"./types.js\";\n\n/** Get the pinned image tag for a service */\nexport function getImageTag(serviceId: string): string | undefined {\n\tconst svc = getServiceById(serviceId);\n\treturn svc?.imageTag;\n}\n\n/** Get the full image reference (image:tag) for a service */\nexport function getImageReference(serviceId: string): string | undefined {\n\tconst svc = getServiceById(serviceId);\n\tif (!svc) return undefined;\n\treturn `${svc.image}:${svc.imageTag}`;\n}\n\n/** Pin all service image tags
|
|
1
|
+
{"version":3,"file":"version-manager.mjs","names":[],"sources":["../src/version-manager.ts"],"sourcesContent":["import { getServiceById } from \"./services/registry.js\";\nimport type { ResolverOutput, ServiceDefinition, Warning } from \"./types.js\";\n\n/** Get the pinned image tag for a service */\nexport function getImageTag(serviceId: string): string | undefined {\n\tconst svc = getServiceById(serviceId);\n\treturn svc?.imageTag;\n}\n\n/** Get the full image reference (image:tag) for a service */\nexport function getImageReference(serviceId: string): string | undefined {\n\tconst svc = getServiceById(serviceId);\n\tif (!svc) return undefined;\n\treturn `${svc.image}:${svc.imageTag}`;\n}\n\n/** Pin all service image tags to the registry-defined versions (returns a copy) */\nexport function pinImageTags(resolved: ResolverOutput): ResolverOutput {\n\treturn {\n\t\t...resolved,\n\t\tservices: resolved.services.map((s) => {\n\t\t\tconst registryDef = getServiceById(s.definition.id);\n\t\t\tconst pinnedTag = registryDef?.imageTag ?? s.definition.imageTag;\n\t\t\treturn {\n\t\t\t\t...s,\n\t\t\t\tdefinition: {\n\t\t\t\t\t...s.definition,\n\t\t\t\t\timageTag: pinnedTag,\n\t\t\t\t},\n\t\t\t};\n\t\t}),\n\t};\n}\n\n/** Check for known compatibility issues between services */\nexport function checkCompatibility(services: ServiceDefinition[]): Warning[] {\n\tconst warnings: Warning[] = [];\n\tconst ids = new Set(services.map((s) => s.id));\n\n\t// Redis + Valkey conflict (should already be caught by resolver, but double-check)\n\tif (ids.has(\"redis\") && ids.has(\"valkey\")) {\n\t\twarnings.push({\n\t\t\ttype: \"compatibility\",\n\t\t\tmessage: \"Redis and Valkey cannot coexist. Choose one.\",\n\t\t});\n\t}\n\t// Caddy + Traefik conflict\n\tif (ids.has(\"caddy\") && ids.has(\"traefik\")) {\n\t\twarnings.push({\n\t\t\ttype: \"compatibility\",\n\t\t\tmessage: \"Caddy and Traefik cannot coexist. Choose one reverse proxy.\",\n\t\t});\n\t}\n\t// Multiple vector DBs warning\n\tconst vectorDbs = [\"qdrant\", \"chromadb\", \"weaviate\"].filter((id) => ids.has(id));\n\tif (vectorDbs.length > 1) {\n\t\twarnings.push({\n\t\t\ttype: \"compatibility\",\n\t\t\tmessage: `Multiple vector databases selected (${vectorDbs.join(\", \")}). Consider using just one to reduce resource usage.`,\n\t\t});\n\t}\n\t// GPU services without GPU\n\tconst gpuServices = services.filter((s) => s.gpuRequired);\n\tif (gpuServices.length > 0) {\n\t\twarnings.push({\n\t\t\ttype: \"compatibility\",\n\t\t\tmessage: `Services requiring GPU: ${gpuServices.map((s) => s.name).join(\", \")}. Ensure NVIDIA Container Toolkit is installed.`,\n\t\t});\n\t}\n\n\treturn warnings;\n}\n"],"mappings":";;;;AAIA,SAAgB,YAAY,WAAuC;AAElE,QADY,eAAe,UAAU,EACzB;;;AAIb,SAAgB,kBAAkB,WAAuC;CACxE,MAAM,MAAM,eAAe,UAAU;AACrC,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO,GAAG,IAAI,MAAM,GAAG,IAAI;;;AAI5B,SAAgB,aAAa,UAA0C;AACtE,QAAO;EACN,GAAG;EACH,UAAU,SAAS,SAAS,KAAK,MAAM;GAEtC,MAAM,YADc,eAAe,EAAE,WAAW,GAAG,EACpB,YAAY,EAAE,WAAW;AACxD,UAAO;IACN,GAAG;IACH,YAAY;KACX,GAAG,EAAE;KACL,UAAU;KACV;IACD;IACA;EACF;;;AAIF,SAAgB,mBAAmB,UAA0C;CAC5E,MAAM,WAAsB,EAAE;CAC9B,MAAM,MAAM,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,GAAG,CAAC;AAG9C,KAAI,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI,SAAS,CACxC,UAAS,KAAK;EACb,MAAM;EACN,SAAS;EACT,CAAC;AAGH,KAAI,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI,UAAU,CACzC,UAAS,KAAK;EACb,MAAM;EACN,SAAS;EACT,CAAC;CAGH,MAAM,YAAY;EAAC;EAAU;EAAY;EAAW,CAAC,QAAQ,OAAO,IAAI,IAAI,GAAG,CAAC;AAChF,KAAI,UAAU,SAAS,EACtB,UAAS,KAAK;EACb,MAAM;EACN,SAAS,uCAAuC,UAAU,KAAK,KAAK,CAAC;EACrE,CAAC;CAGH,MAAM,cAAc,SAAS,QAAQ,MAAM,EAAE,YAAY;AACzD,KAAI,YAAY,SAAS,EACxB,UAAS,KAAK;EACb,MAAM;EACN,SAAS,2BAA2B,YAAY,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,CAAC;EAC9E,CAAC;AAGH,QAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-Qk6MgAnK.mjs";
|
|
2
|
+
import { getAllServices, getServiceById } from "./services/registry.mjs";
|
|
3
|
+
import { resolve } from "./resolver.mjs";
|
|
4
|
+
import { checkCompatibility, getImageReference, getImageTag, pinImageTags } from "./version-manager.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/version-manager.test.ts
|
|
7
|
+
describe("getImageTag", () => {
|
|
8
|
+
it("returns the tag for a known service", () => {
|
|
9
|
+
const tag = getImageTag("redis");
|
|
10
|
+
globalExpect(tag).toBeDefined();
|
|
11
|
+
globalExpect(typeof tag).toBe("string");
|
|
12
|
+
});
|
|
13
|
+
it("returns undefined for an unknown service", () => {
|
|
14
|
+
globalExpect(getImageTag("nonexistent-service-xyz")).toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe("getImageReference", () => {
|
|
18
|
+
it("returns image:tag for a known service", () => {
|
|
19
|
+
const ref = getImageReference("redis");
|
|
20
|
+
globalExpect(ref).toBeDefined();
|
|
21
|
+
globalExpect(ref).toContain(":");
|
|
22
|
+
globalExpect(ref).toMatch(/^.+:.+$/);
|
|
23
|
+
});
|
|
24
|
+
it("returns undefined for an unknown service", () => {
|
|
25
|
+
globalExpect(getImageReference("nonexistent-service-xyz")).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("pinImageTags", () => {
|
|
29
|
+
it("pins image tags from the registry for all services", () => {
|
|
30
|
+
const resolved = resolve({
|
|
31
|
+
services: ["redis", "postgresql"],
|
|
32
|
+
skillPacks: [],
|
|
33
|
+
proxy: "none",
|
|
34
|
+
gpu: false,
|
|
35
|
+
platform: "linux/amd64"
|
|
36
|
+
});
|
|
37
|
+
const pinned = pinImageTags(resolved);
|
|
38
|
+
globalExpect(pinned.services).toHaveLength(resolved.services.length);
|
|
39
|
+
for (const svc of pinned.services) {
|
|
40
|
+
globalExpect(svc.definition.imageTag).toBeDefined();
|
|
41
|
+
globalExpect(typeof svc.definition.imageTag).toBe("string");
|
|
42
|
+
globalExpect(svc.definition.imageTag.length).toBeGreaterThan(0);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it("does not mutate the original resolved output", () => {
|
|
46
|
+
const resolved = resolve({
|
|
47
|
+
services: ["redis"],
|
|
48
|
+
skillPacks: [],
|
|
49
|
+
proxy: "none",
|
|
50
|
+
gpu: false,
|
|
51
|
+
platform: "linux/amd64"
|
|
52
|
+
});
|
|
53
|
+
const originalTag = resolved.services[0]?.definition.imageTag;
|
|
54
|
+
pinImageTags(resolved);
|
|
55
|
+
globalExpect(resolved.services[0]?.definition.imageTag).toBe(originalTag);
|
|
56
|
+
});
|
|
57
|
+
it("preserves non-tag properties", () => {
|
|
58
|
+
const resolved = resolve({
|
|
59
|
+
services: ["redis"],
|
|
60
|
+
skillPacks: [],
|
|
61
|
+
proxy: "none",
|
|
62
|
+
gpu: false,
|
|
63
|
+
platform: "linux/amd64"
|
|
64
|
+
});
|
|
65
|
+
const pinned = pinImageTags(resolved);
|
|
66
|
+
globalExpect(pinned.services[0]?.definition.id).toBe("redis");
|
|
67
|
+
globalExpect(pinned.services[0]?.definition.name).toBe("Redis");
|
|
68
|
+
globalExpect(pinned.estimatedMemoryMB).toBe(resolved.estimatedMemoryMB);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("checkCompatibility", () => {
|
|
72
|
+
it("warns when Redis and Valkey are both selected", () => {
|
|
73
|
+
const all = getAllServices();
|
|
74
|
+
const redis = all.find((s) => s.id === "redis");
|
|
75
|
+
const valkey = all.find((s) => s.id === "valkey");
|
|
76
|
+
if (redis && valkey) globalExpect(checkCompatibility([redis, valkey]).some((w) => w.message.includes("Redis") && w.message.includes("Valkey"))).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
it("warns when Caddy and Traefik are both selected", () => {
|
|
79
|
+
const all = getAllServices();
|
|
80
|
+
const caddy = all.find((s) => s.id === "caddy");
|
|
81
|
+
const traefik = all.find((s) => s.id === "traefik");
|
|
82
|
+
if (caddy && traefik) globalExpect(checkCompatibility([caddy, traefik]).some((w) => w.message.includes("Caddy") && w.message.includes("Traefik"))).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it("warns about multiple vector databases", () => {
|
|
85
|
+
const all = getAllServices();
|
|
86
|
+
const qdrant = all.find((s) => s.id === "qdrant");
|
|
87
|
+
const chromadb = all.find((s) => s.id === "chromadb");
|
|
88
|
+
if (qdrant && chromadb) globalExpect(checkCompatibility([qdrant, chromadb]).some((w) => w.message.includes("vector database"))).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
it("warns about GPU services", () => {
|
|
91
|
+
const gpuService = getAllServices().find((s) => s.gpuRequired);
|
|
92
|
+
if (gpuService) globalExpect(checkCompatibility([gpuService]).some((w) => w.message.includes("GPU"))).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it("returns no warnings for a single non-conflicting service", () => {
|
|
95
|
+
const redis = getServiceById("redis");
|
|
96
|
+
if (redis) globalExpect(checkCompatibility([redis]).filter((w) => w.type === "compatibility" && !w.message.includes("GPU"))).toHaveLength(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
export { };
|
|
102
|
+
//# sourceMappingURL=version-manager.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-manager.test.mjs","names":[],"sources":["../src/version-manager.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { resolve } from \"./resolver.js\";\nimport { getAllServices, getServiceById } from \"./services/registry.js\";\nimport { checkCompatibility, getImageReference, getImageTag, pinImageTags } from \"./version-manager.js\";\n\ndescribe(\"getImageTag\", () => {\n\tit(\"returns the tag for a known service\", () => {\n\t\tconst tag = getImageTag(\"redis\");\n\t\texpect(tag).toBeDefined();\n\t\texpect(typeof tag).toBe(\"string\");\n\t});\n\n\tit(\"returns undefined for an unknown service\", () => {\n\t\texpect(getImageTag(\"nonexistent-service-xyz\")).toBeUndefined();\n\t});\n});\n\ndescribe(\"getImageReference\", () => {\n\tit(\"returns image:tag for a known service\", () => {\n\t\tconst ref = getImageReference(\"redis\");\n\t\texpect(ref).toBeDefined();\n\t\texpect(ref).toContain(\":\");\n\t\texpect(ref).toMatch(/^.+:.+$/);\n\t});\n\n\tit(\"returns undefined for an unknown service\", () => {\n\t\texpect(getImageReference(\"nonexistent-service-xyz\")).toBeUndefined();\n\t});\n});\n\ndescribe(\"pinImageTags\", () => {\n\tit(\"pins image tags from the registry for all services\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\", \"postgresql\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t});\n\n\t\tconst pinned = pinImageTags(resolved);\n\n\t\texpect(pinned.services).toHaveLength(resolved.services.length);\n\n\t\tfor (const svc of pinned.services) {\n\t\t\texpect(svc.definition.imageTag).toBeDefined();\n\t\t\texpect(typeof svc.definition.imageTag).toBe(\"string\");\n\t\t\texpect(svc.definition.imageTag.length).toBeGreaterThan(0);\n\t\t}\n\t});\n\n\tit(\"does not mutate the original resolved output\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t});\n\n\t\tconst originalTag = resolved.services[0]?.definition.imageTag;\n\t\tpinImageTags(resolved);\n\n\t\texpect(resolved.services[0]?.definition.imageTag).toBe(originalTag);\n\t});\n\n\tit(\"preserves non-tag properties\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t});\n\n\t\tconst pinned = pinImageTags(resolved);\n\t\texpect(pinned.services[0]?.definition.id).toBe(\"redis\");\n\t\texpect(pinned.services[0]?.definition.name).toBe(\"Redis\");\n\t\texpect(pinned.estimatedMemoryMB).toBe(resolved.estimatedMemoryMB);\n\t});\n});\n\ndescribe(\"checkCompatibility\", () => {\n\tit(\"warns when Redis and Valkey are both selected\", () => {\n\t\tconst all = getAllServices();\n\t\tconst redis = all.find((s) => s.id === \"redis\");\n\t\tconst valkey = all.find((s) => s.id === \"valkey\");\n\n\t\tif (redis && valkey) {\n\t\t\tconst warnings = checkCompatibility([redis, valkey]);\n\t\t\texpect(warnings.some((w) => w.message.includes(\"Redis\") && w.message.includes(\"Valkey\"))).toBe(true);\n\t\t}\n\t});\n\n\tit(\"warns when Caddy and Traefik are both selected\", () => {\n\t\tconst all = getAllServices();\n\t\tconst caddy = all.find((s) => s.id === \"caddy\");\n\t\tconst traefik = all.find((s) => s.id === \"traefik\");\n\n\t\tif (caddy && traefik) {\n\t\t\tconst warnings = checkCompatibility([caddy, traefik]);\n\t\t\texpect(warnings.some((w) => w.message.includes(\"Caddy\") && w.message.includes(\"Traefik\"))).toBe(true);\n\t\t}\n\t});\n\n\tit(\"warns about multiple vector databases\", () => {\n\t\tconst all = getAllServices();\n\t\tconst qdrant = all.find((s) => s.id === \"qdrant\");\n\t\tconst chromadb = all.find((s) => s.id === \"chromadb\");\n\n\t\tif (qdrant && chromadb) {\n\t\t\tconst warnings = checkCompatibility([qdrant, chromadb]);\n\t\t\texpect(warnings.some((w) => w.message.includes(\"vector database\"))).toBe(true);\n\t\t}\n\t});\n\n\tit(\"warns about GPU services\", () => {\n\t\tconst all = getAllServices();\n\t\tconst gpuService = all.find((s) => s.gpuRequired);\n\n\t\tif (gpuService) {\n\t\t\tconst warnings = checkCompatibility([gpuService]);\n\t\t\texpect(warnings.some((w) => w.message.includes(\"GPU\"))).toBe(true);\n\t\t}\n\t});\n\n\tit(\"returns no warnings for a single non-conflicting service\", () => {\n\t\tconst redis = getServiceById(\"redis\");\n\t\tif (redis) {\n\t\t\tconst warnings = checkCompatibility([redis]);\n\t\t\texpect(warnings.filter((w) => w.type === \"compatibility\" && !w.message.includes(\"GPU\"))).toHaveLength(0);\n\t\t}\n\t});\n});\n"],"mappings":"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"}
|