@better-openclaw/core 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.github/workflows/publish-core.yml +1 -1
  2. package/dist/deployers/coolify.cjs +61 -50
  3. package/dist/deployers/coolify.cjs.map +1 -1
  4. package/dist/deployers/coolify.d.cts +4 -11
  5. package/dist/deployers/coolify.d.cts.map +1 -1
  6. package/dist/deployers/coolify.d.mts +4 -11
  7. package/dist/deployers/coolify.d.mts.map +1 -1
  8. package/dist/deployers/coolify.mjs +62 -50
  9. package/dist/deployers/coolify.mjs.map +1 -1
  10. package/dist/deployers/dokploy.cjs +106 -29
  11. package/dist/deployers/dokploy.cjs.map +1 -1
  12. package/dist/deployers/dokploy.d.cts +2 -1
  13. package/dist/deployers/dokploy.d.cts.map +1 -1
  14. package/dist/deployers/dokploy.d.mts +2 -1
  15. package/dist/deployers/dokploy.d.mts.map +1 -1
  16. package/dist/deployers/dokploy.mjs +107 -29
  17. package/dist/deployers/dokploy.mjs.map +1 -1
  18. package/dist/deployers/index.cjs.map +1 -1
  19. package/dist/deployers/index.d.cts +2 -2
  20. package/dist/deployers/index.d.cts.map +1 -1
  21. package/dist/deployers/index.d.mts +2 -2
  22. package/dist/deployers/index.d.mts.map +1 -1
  23. package/dist/deployers/index.mjs.map +1 -1
  24. package/dist/deployers/strip-host-ports.cjs +138 -0
  25. package/dist/deployers/strip-host-ports.cjs.map +1 -0
  26. package/dist/deployers/strip-host-ports.d.cts +62 -0
  27. package/dist/deployers/strip-host-ports.d.cts.map +1 -0
  28. package/dist/deployers/strip-host-ports.d.mts +62 -0
  29. package/dist/deployers/strip-host-ports.d.mts.map +1 -0
  30. package/dist/deployers/strip-host-ports.mjs +133 -0
  31. package/dist/deployers/strip-host-ports.mjs.map +1 -0
  32. package/dist/deployers/strip-host-ports.test.cjs +89 -0
  33. package/dist/deployers/strip-host-ports.test.cjs.map +1 -0
  34. package/dist/deployers/strip-host-ports.test.d.cts +1 -0
  35. package/dist/deployers/strip-host-ports.test.d.mts +1 -0
  36. package/dist/deployers/strip-host-ports.test.mjs +90 -0
  37. package/dist/deployers/strip-host-ports.test.mjs.map +1 -0
  38. package/dist/deployers/types.d.cts +173 -2
  39. package/dist/deployers/types.d.cts.map +1 -1
  40. package/dist/deployers/types.d.mts +173 -2
  41. package/dist/deployers/types.d.mts.map +1 -1
  42. package/dist/index.d.cts +2 -2
  43. package/dist/index.d.mts +2 -2
  44. package/package.json +2 -1
  45. package/src/deployers/coolify.ts +198 -103
  46. package/src/deployers/dokploy.ts +209 -55
  47. package/src/deployers/index.ts +1 -0
  48. package/src/deployers/strip-host-ports.test.ts +100 -0
  49. package/src/deployers/strip-host-ports.ts +187 -0
  50. package/src/deployers/types.ts +185 -1
  51. package/src/index.ts +19 -4
  52. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,138 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('../chunk-C0xms8kb.cjs');
3
+ let yaml = require("yaml");
4
+
5
+ //#region src/deployers/strip-host-ports.ts
6
+ /**
7
+ * Strips host port bindings from a Docker Compose YAML string.
8
+ *
9
+ * When deploying to a PaaS like Dokploy or Coolify, services don't need
10
+ * host port mappings because routing is handled by the platform's built-in
11
+ * reverse proxy (Traefik). Binding to host ports causes "port already
12
+ * allocated" errors when ports are in use by other services on the server.
13
+ *
14
+ * Transforms port mappings:
15
+ * "8080:8080" → "8080" (container port only)
16
+ * "0.0.0.0:8080:80" → "80" (container port only)
17
+ * "8080:80/tcp" → "80/tcp" (preserves protocol)
18
+ * { published: 8080, target: 80 } → { target: 80 }
19
+ *
20
+ * The `expose` field is left untouched since it only defines internal ports.
21
+ */
22
+ /**
23
+ * Strips host port bindings from a Docker Compose YAML string,
24
+ * keeping only the container (target) ports.
25
+ *
26
+ * Returns the modified YAML string.
27
+ */
28
+ function stripHostPorts(composeYaml) {
29
+ const doc = (0, yaml.parse)(composeYaml);
30
+ if (!doc?.services) return composeYaml;
31
+ for (const [, service] of Object.entries(doc.services)) {
32
+ if (!service.ports || !Array.isArray(service.ports)) continue;
33
+ service.ports = service.ports.map((port) => {
34
+ if (typeof port === "string") return stripStringPort(port);
35
+ if (typeof port === "object" && port !== null) return stripObjectPort(port);
36
+ return port;
37
+ });
38
+ }
39
+ return (0, yaml.stringify)(doc, { lineWidth: 200 });
40
+ }
41
+ /**
42
+ * Strips host portion from a string port mapping.
43
+ *
44
+ * "8080:80" → "80"
45
+ * "0.0.0.0:8080:80" → "80"
46
+ * "80" → "80" (no change)
47
+ * "80/tcp" → "80/tcp"
48
+ * "8080:80/tcp" → "80/tcp"
49
+ */
50
+ function stripStringPort(port) {
51
+ const protocolIdx = port.lastIndexOf("/");
52
+ let protocol = "";
53
+ let portSpec = port;
54
+ if (protocolIdx > 0) {
55
+ protocol = port.substring(protocolIdx);
56
+ portSpec = port.substring(0, protocolIdx);
57
+ }
58
+ const parts = portSpec.split(":");
59
+ return `${parts[parts.length - 1]}${protocol}`;
60
+ }
61
+ /**
62
+ * Strips host/published from an object port mapping.
63
+ *
64
+ * { target: 80, published: 8080 } → { target: 80 }
65
+ */
66
+ function stripObjectPort(port) {
67
+ const { published: _, host_ip: __, ...rest } = port;
68
+ return rest;
69
+ }
70
+ /**
71
+ * Strips local bind mounts (paths starting with `./`) from a Docker
72
+ * Compose YAML string.
73
+ *
74
+ * When deploying to a PaaS like Dokploy or Coolify as a raw compose
75
+ * stack, there is no cloned repository — so host-relative volume mounts
76
+ * like `./postgres/init-databases.sh:/docker-entrypoint-initdb.d/...`
77
+ * will fail because the file doesn't exist on the remote server.
78
+ *
79
+ * Named volumes (e.g. `redis-data:/data`) and absolute system paths
80
+ * (e.g. `/var/run/docker.sock`) are kept intact.
81
+ */
82
+ function stripLocalBindMounts(composeYaml) {
83
+ const doc = (0, yaml.parse)(composeYaml);
84
+ if (!doc?.services) return composeYaml;
85
+ for (const [, service] of Object.entries(doc.services)) {
86
+ if (!service.volumes || !Array.isArray(service.volumes)) continue;
87
+ service.volumes = service.volumes.filter((vol) => {
88
+ if (typeof vol === "string") return !vol.startsWith("./");
89
+ if (typeof vol === "object" && vol !== null) {
90
+ const src = vol.source;
91
+ return !(typeof src === "string" && src.startsWith("./"));
92
+ }
93
+ return true;
94
+ });
95
+ if (service.volumes.length === 0) delete service.volumes;
96
+ }
97
+ return (0, yaml.stringify)(doc, { lineWidth: 200 });
98
+ }
99
+ /**
100
+ * Strips security hardening options (`cap_drop`, `cap_add`, `security_opt`)
101
+ * from all services in a Docker Compose YAML string.
102
+ *
103
+ * Many Docker images (PostgreSQL, Redis, MinIO, etc.) start as root and
104
+ * use `gosu`/`su-exec` to drop privileges to a non-root user. This
105
+ * requires `SETUID`, `SETGID`, and other capabilities. The hardened
106
+ * compose output adds `cap_drop: ALL` + `no-new-privileges`, which
107
+ * prevents this user switch and causes containers to crash with:
108
+ * "failed switching to 'postgres': operation not permitted"
109
+ *
110
+ * PaaS platforms (Dokploy, Coolify) manage their own container security,
111
+ * so these options are unnecessary and should be removed.
112
+ */
113
+ function stripSecurityHardening(composeYaml) {
114
+ const doc = (0, yaml.parse)(composeYaml);
115
+ if (!doc?.services) return composeYaml;
116
+ for (const [, service] of Object.entries(doc.services)) {
117
+ delete service.cap_drop;
118
+ delete service.cap_add;
119
+ delete service.security_opt;
120
+ }
121
+ return (0, yaml.stringify)(doc, { lineWidth: 200 });
122
+ }
123
+ /**
124
+ * Applies all PaaS-specific sanitisations to a Docker Compose YAML string:
125
+ * 1. Strips host port bindings (avoids "port already allocated" errors)
126
+ * 2. Strips local bind mounts (files don't exist on remote PaaS servers)
127
+ * 3. Strips security hardening (cap_drop/security_opt break user switching)
128
+ */
129
+ function sanitizeComposeForPaas(composeYaml) {
130
+ return stripSecurityHardening(stripLocalBindMounts(stripHostPorts(composeYaml)));
131
+ }
132
+
133
+ //#endregion
134
+ exports.sanitizeComposeForPaas = sanitizeComposeForPaas;
135
+ exports.stripHostPorts = stripHostPorts;
136
+ exports.stripLocalBindMounts = stripLocalBindMounts;
137
+ exports.stripSecurityHardening = stripSecurityHardening;
138
+ //# sourceMappingURL=strip-host-ports.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-host-ports.cjs","names":[],"sources":["../../src/deployers/strip-host-ports.ts"],"sourcesContent":["/**\n * Strips host port bindings from a Docker Compose YAML string.\n *\n * When deploying to a PaaS like Dokploy or Coolify, services don't need\n * host port mappings because routing is handled by the platform's built-in\n * reverse proxy (Traefik). Binding to host ports causes \"port already\n * allocated\" errors when ports are in use by other services on the server.\n *\n * Transforms port mappings:\n * \"8080:8080\" → \"8080\" (container port only)\n * \"0.0.0.0:8080:80\" → \"80\" (container port only)\n * \"8080:80/tcp\" → \"80/tcp\" (preserves protocol)\n * { published: 8080, target: 80 } → { target: 80 }\n *\n * The `expose` field is left untouched since it only defines internal ports.\n */\n\nimport { parse, stringify } from \"yaml\";\n\ninterface ComposeService {\n\tports?: (string | PortObject)[];\n\tvolumes?: (string | Record<string, unknown>)[];\n\t[key: string]: unknown;\n}\n\ninterface PortObject {\n\ttarget: number;\n\tpublished?: number | string;\n\thost_ip?: string;\n\tprotocol?: string;\n\t[key: string]: unknown;\n}\n\ninterface ComposeFile {\n\tservices?: Record<string, ComposeService>;\n\t[key: string]: unknown;\n}\n\n/**\n * Strips host port bindings from a Docker Compose YAML string,\n * keeping only the container (target) ports.\n *\n * Returns the modified YAML string.\n */\nexport function stripHostPorts(composeYaml: string): string {\n\tconst doc = parse(composeYaml) as ComposeFile;\n\n\tif (!doc?.services) return composeYaml;\n\n\tfor (const [, service] of Object.entries(doc.services)) {\n\t\tif (!service.ports || !Array.isArray(service.ports)) continue;\n\n\t\tservice.ports = service.ports.map((port) => {\n\t\t\tif (typeof port === \"string\") {\n\t\t\t\treturn stripStringPort(port);\n\t\t\t}\n\t\t\tif (typeof port === \"object\" && port !== null) {\n\t\t\t\treturn stripObjectPort(port);\n\t\t\t}\n\t\t\treturn port;\n\t\t});\n\t}\n\n\treturn stringify(doc, { lineWidth: 200 });\n}\n\n/**\n * Strips host portion from a string port mapping.\n *\n * \"8080:80\" → \"80\"\n * \"0.0.0.0:8080:80\" → \"80\"\n * \"80\" → \"80\" (no change)\n * \"80/tcp\" → \"80/tcp\"\n * \"8080:80/tcp\" → \"80/tcp\"\n */\nfunction stripStringPort(port: string): string {\n\t// Split off protocol if present (e.g. \"/tcp\", \"/udp\")\n\tconst protocolIdx = port.lastIndexOf(\"/\");\n\tlet protocol = \"\";\n\tlet portSpec = port;\n\n\tif (protocolIdx > 0) {\n\t\tprotocol = port.substring(protocolIdx); // includes the \"/\"\n\t\tportSpec = port.substring(0, protocolIdx);\n\t}\n\n\t// Split by \":\" — formats are:\n\t// \"80\" → container only\n\t// \"8080:80\" → host:container\n\t// \"0.0.0.0:8080:80\" → ip:host:container\n\tconst parts = portSpec.split(\":\");\n\n\t// Take the last part as the container port\n\tconst containerPort = parts[parts.length - 1];\n\n\treturn `${containerPort}${protocol}`;\n}\n\n/**\n * Strips host/published from an object port mapping.\n *\n * { target: 80, published: 8080 } → { target: 80 }\n */\nfunction stripObjectPort(port: PortObject): PortObject {\n\tconst { published: _, host_ip: __, ...rest } = port;\n\treturn rest;\n}\n\n/**\n * Strips local bind mounts (paths starting with `./`) from a Docker\n * Compose YAML string.\n *\n * When deploying to a PaaS like Dokploy or Coolify as a raw compose\n * stack, there is no cloned repository — so host-relative volume mounts\n * like `./postgres/init-databases.sh:/docker-entrypoint-initdb.d/...`\n * will fail because the file doesn't exist on the remote server.\n *\n * Named volumes (e.g. `redis-data:/data`) and absolute system paths\n * (e.g. `/var/run/docker.sock`) are kept intact.\n */\nexport function stripLocalBindMounts(composeYaml: string): string {\n\tconst doc = parse(composeYaml) as ComposeFile;\n\n\tif (!doc?.services) return composeYaml;\n\n\tfor (const [, service] of Object.entries(doc.services)) {\n\t\tif (!service.volumes || !Array.isArray(service.volumes)) continue;\n\n\t\tservice.volumes = (service.volumes as (string | Record<string, unknown>)[]).filter((vol) => {\n\t\t\tif (typeof vol === \"string\") {\n\t\t\t\t// Bind mounts starting with \"./\" reference local files\n\t\t\t\treturn !vol.startsWith(\"./\");\n\t\t\t}\n\t\t\t// Object-form: { type: \"bind\", source: \"./...\" }\n\t\t\tif (typeof vol === \"object\" && vol !== null) {\n\t\t\t\tconst src = (vol as Record<string, unknown>).source;\n\t\t\t\treturn !(typeof src === \"string\" && src.startsWith(\"./\"));\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\n\t\t// Remove empty volumes array to keep YAML clean\n\t\tif (service.volumes.length === 0) {\n\t\t\tdelete service.volumes;\n\t\t}\n\t}\n\n\treturn stringify(doc, { lineWidth: 200 });\n}\n\n/**\n * Strips security hardening options (`cap_drop`, `cap_add`, `security_opt`)\n * from all services in a Docker Compose YAML string.\n *\n * Many Docker images (PostgreSQL, Redis, MinIO, etc.) start as root and\n * use `gosu`/`su-exec` to drop privileges to a non-root user. This\n * requires `SETUID`, `SETGID`, and other capabilities. The hardened\n * compose output adds `cap_drop: ALL` + `no-new-privileges`, which\n * prevents this user switch and causes containers to crash with:\n * \"failed switching to 'postgres': operation not permitted\"\n *\n * PaaS platforms (Dokploy, Coolify) manage their own container security,\n * so these options are unnecessary and should be removed.\n */\nexport function stripSecurityHardening(composeYaml: string): string {\n\tconst doc = parse(composeYaml) as ComposeFile;\n\n\tif (!doc?.services) return composeYaml;\n\n\tfor (const [, service] of Object.entries(doc.services)) {\n\t\tdelete service.cap_drop;\n\t\tdelete service.cap_add;\n\t\tdelete service.security_opt;\n\t}\n\n\treturn stringify(doc, { lineWidth: 200 });\n}\n\n/**\n * Applies all PaaS-specific sanitisations to a Docker Compose YAML string:\n * 1. Strips host port bindings (avoids \"port already allocated\" errors)\n * 2. Strips local bind mounts (files don't exist on remote PaaS servers)\n * 3. Strips security hardening (cap_drop/security_opt break user switching)\n */\nexport function sanitizeComposeForPaas(composeYaml: string): string {\n\treturn stripSecurityHardening(stripLocalBindMounts(stripHostPorts(composeYaml)));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,eAAe,aAA6B;CAC3D,MAAM,sBAAY,YAAY;AAE9B,KAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,MAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,IAAI,SAAS,EAAE;AACvD,MAAI,CAAC,QAAQ,SAAS,CAAC,MAAM,QAAQ,QAAQ,MAAM,CAAE;AAErD,UAAQ,QAAQ,QAAQ,MAAM,KAAK,SAAS;AAC3C,OAAI,OAAO,SAAS,SACnB,QAAO,gBAAgB,KAAK;AAE7B,OAAI,OAAO,SAAS,YAAY,SAAS,KACxC,QAAO,gBAAgB,KAAK;AAE7B,UAAO;IACN;;AAGH,4BAAiB,KAAK,EAAE,WAAW,KAAK,CAAC;;;;;;;;;;;AAY1C,SAAS,gBAAgB,MAAsB;CAE9C,MAAM,cAAc,KAAK,YAAY,IAAI;CACzC,IAAI,WAAW;CACf,IAAI,WAAW;AAEf,KAAI,cAAc,GAAG;AACpB,aAAW,KAAK,UAAU,YAAY;AACtC,aAAW,KAAK,UAAU,GAAG,YAAY;;CAO1C,MAAM,QAAQ,SAAS,MAAM,IAAI;AAKjC,QAAO,GAFe,MAAM,MAAM,SAAS,KAEjB;;;;;;;AAQ3B,SAAS,gBAAgB,MAA8B;CACtD,MAAM,EAAE,WAAW,GAAG,SAAS,IAAI,GAAG,SAAS;AAC/C,QAAO;;;;;;;;;;;;;;AAeR,SAAgB,qBAAqB,aAA6B;CACjE,MAAM,sBAAY,YAAY;AAE9B,KAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,MAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,IAAI,SAAS,EAAE;AACvD,MAAI,CAAC,QAAQ,WAAW,CAAC,MAAM,QAAQ,QAAQ,QAAQ,CAAE;AAEzD,UAAQ,UAAW,QAAQ,QAAiD,QAAQ,QAAQ;AAC3F,OAAI,OAAO,QAAQ,SAElB,QAAO,CAAC,IAAI,WAAW,KAAK;AAG7B,OAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;IAC5C,MAAM,MAAO,IAAgC;AAC7C,WAAO,EAAE,OAAO,QAAQ,YAAY,IAAI,WAAW,KAAK;;AAEzD,UAAO;IACN;AAGF,MAAI,QAAQ,QAAQ,WAAW,EAC9B,QAAO,QAAQ;;AAIjB,4BAAiB,KAAK,EAAE,WAAW,KAAK,CAAC;;;;;;;;;;;;;;;;AAiB1C,SAAgB,uBAAuB,aAA6B;CACnE,MAAM,sBAAY,YAAY;AAE9B,KAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,MAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,IAAI,SAAS,EAAE;AACvD,SAAO,QAAQ;AACf,SAAO,QAAQ;AACf,SAAO,QAAQ;;AAGhB,4BAAiB,KAAK,EAAE,WAAW,KAAK,CAAC;;;;;;;;AAS1C,SAAgB,uBAAuB,aAA6B;AACnE,QAAO,uBAAuB,qBAAqB,eAAe,YAAY,CAAC,CAAC"}
@@ -0,0 +1,62 @@
1
+ //#region src/deployers/strip-host-ports.d.ts
2
+ /**
3
+ * Strips host port bindings from a Docker Compose YAML string.
4
+ *
5
+ * When deploying to a PaaS like Dokploy or Coolify, services don't need
6
+ * host port mappings because routing is handled by the platform's built-in
7
+ * reverse proxy (Traefik). Binding to host ports causes "port already
8
+ * allocated" errors when ports are in use by other services on the server.
9
+ *
10
+ * Transforms port mappings:
11
+ * "8080:8080" → "8080" (container port only)
12
+ * "0.0.0.0:8080:80" → "80" (container port only)
13
+ * "8080:80/tcp" → "80/tcp" (preserves protocol)
14
+ * { published: 8080, target: 80 } → { target: 80 }
15
+ *
16
+ * The `expose` field is left untouched since it only defines internal ports.
17
+ */
18
+ /**
19
+ * Strips host port bindings from a Docker Compose YAML string,
20
+ * keeping only the container (target) ports.
21
+ *
22
+ * Returns the modified YAML string.
23
+ */
24
+ declare function stripHostPorts(composeYaml: string): string;
25
+ /**
26
+ * Strips local bind mounts (paths starting with `./`) from a Docker
27
+ * Compose YAML string.
28
+ *
29
+ * When deploying to a PaaS like Dokploy or Coolify as a raw compose
30
+ * stack, there is no cloned repository — so host-relative volume mounts
31
+ * like `./postgres/init-databases.sh:/docker-entrypoint-initdb.d/...`
32
+ * will fail because the file doesn't exist on the remote server.
33
+ *
34
+ * Named volumes (e.g. `redis-data:/data`) and absolute system paths
35
+ * (e.g. `/var/run/docker.sock`) are kept intact.
36
+ */
37
+ declare function stripLocalBindMounts(composeYaml: string): string;
38
+ /**
39
+ * Strips security hardening options (`cap_drop`, `cap_add`, `security_opt`)
40
+ * from all services in a Docker Compose YAML string.
41
+ *
42
+ * Many Docker images (PostgreSQL, Redis, MinIO, etc.) start as root and
43
+ * use `gosu`/`su-exec` to drop privileges to a non-root user. This
44
+ * requires `SETUID`, `SETGID`, and other capabilities. The hardened
45
+ * compose output adds `cap_drop: ALL` + `no-new-privileges`, which
46
+ * prevents this user switch and causes containers to crash with:
47
+ * "failed switching to 'postgres': operation not permitted"
48
+ *
49
+ * PaaS platforms (Dokploy, Coolify) manage their own container security,
50
+ * so these options are unnecessary and should be removed.
51
+ */
52
+ declare function stripSecurityHardening(composeYaml: string): string;
53
+ /**
54
+ * Applies all PaaS-specific sanitisations to a Docker Compose YAML string:
55
+ * 1. Strips host port bindings (avoids "port already allocated" errors)
56
+ * 2. Strips local bind mounts (files don't exist on remote PaaS servers)
57
+ * 3. Strips security hardening (cap_drop/security_opt break user switching)
58
+ */
59
+ declare function sanitizeComposeForPaas(composeYaml: string): string;
60
+ //#endregion
61
+ export { sanitizeComposeForPaas, stripHostPorts, stripLocalBindMounts, stripSecurityHardening };
62
+ //# sourceMappingURL=strip-host-ports.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-host-ports.d.cts","names":[],"sources":["../../src/deployers/strip-host-ports.ts"],"mappings":";;AA4CA;;;;;AA4EA;;;;;AA4CA;;;;;AAoBA;;;;;;AAAA,iBA5IgB,cAAA,CAAe,WAAA;;;;;;;;;;;;;iBA4Ef,oBAAA,CAAqB,WAAA;;;;;;;;;;;;;;;iBA4CrB,sBAAA,CAAuB,WAAA;;;;;;;iBAoBvB,sBAAA,CAAuB,WAAA"}
@@ -0,0 +1,62 @@
1
+ //#region src/deployers/strip-host-ports.d.ts
2
+ /**
3
+ * Strips host port bindings from a Docker Compose YAML string.
4
+ *
5
+ * When deploying to a PaaS like Dokploy or Coolify, services don't need
6
+ * host port mappings because routing is handled by the platform's built-in
7
+ * reverse proxy (Traefik). Binding to host ports causes "port already
8
+ * allocated" errors when ports are in use by other services on the server.
9
+ *
10
+ * Transforms port mappings:
11
+ * "8080:8080" → "8080" (container port only)
12
+ * "0.0.0.0:8080:80" → "80" (container port only)
13
+ * "8080:80/tcp" → "80/tcp" (preserves protocol)
14
+ * { published: 8080, target: 80 } → { target: 80 }
15
+ *
16
+ * The `expose` field is left untouched since it only defines internal ports.
17
+ */
18
+ /**
19
+ * Strips host port bindings from a Docker Compose YAML string,
20
+ * keeping only the container (target) ports.
21
+ *
22
+ * Returns the modified YAML string.
23
+ */
24
+ declare function stripHostPorts(composeYaml: string): string;
25
+ /**
26
+ * Strips local bind mounts (paths starting with `./`) from a Docker
27
+ * Compose YAML string.
28
+ *
29
+ * When deploying to a PaaS like Dokploy or Coolify as a raw compose
30
+ * stack, there is no cloned repository — so host-relative volume mounts
31
+ * like `./postgres/init-databases.sh:/docker-entrypoint-initdb.d/...`
32
+ * will fail because the file doesn't exist on the remote server.
33
+ *
34
+ * Named volumes (e.g. `redis-data:/data`) and absolute system paths
35
+ * (e.g. `/var/run/docker.sock`) are kept intact.
36
+ */
37
+ declare function stripLocalBindMounts(composeYaml: string): string;
38
+ /**
39
+ * Strips security hardening options (`cap_drop`, `cap_add`, `security_opt`)
40
+ * from all services in a Docker Compose YAML string.
41
+ *
42
+ * Many Docker images (PostgreSQL, Redis, MinIO, etc.) start as root and
43
+ * use `gosu`/`su-exec` to drop privileges to a non-root user. This
44
+ * requires `SETUID`, `SETGID`, and other capabilities. The hardened
45
+ * compose output adds `cap_drop: ALL` + `no-new-privileges`, which
46
+ * prevents this user switch and causes containers to crash with:
47
+ * "failed switching to 'postgres': operation not permitted"
48
+ *
49
+ * PaaS platforms (Dokploy, Coolify) manage their own container security,
50
+ * so these options are unnecessary and should be removed.
51
+ */
52
+ declare function stripSecurityHardening(composeYaml: string): string;
53
+ /**
54
+ * Applies all PaaS-specific sanitisations to a Docker Compose YAML string:
55
+ * 1. Strips host port bindings (avoids "port already allocated" errors)
56
+ * 2. Strips local bind mounts (files don't exist on remote PaaS servers)
57
+ * 3. Strips security hardening (cap_drop/security_opt break user switching)
58
+ */
59
+ declare function sanitizeComposeForPaas(composeYaml: string): string;
60
+ //#endregion
61
+ export { sanitizeComposeForPaas, stripHostPorts, stripLocalBindMounts, stripSecurityHardening };
62
+ //# sourceMappingURL=strip-host-ports.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-host-ports.d.mts","names":[],"sources":["../../src/deployers/strip-host-ports.ts"],"mappings":";;AA4CA;;;;;AA4EA;;;;;AA4CA;;;;;AAoBA;;;;;;AAAA,iBA5IgB,cAAA,CAAe,WAAA;;;;;;;;;;;;;iBA4Ef,oBAAA,CAAqB,WAAA;;;;;;;;;;;;;;;iBA4CrB,sBAAA,CAAuB,WAAA;;;;;;;iBAoBvB,sBAAA,CAAuB,WAAA"}
@@ -0,0 +1,133 @@
1
+ import { parse, stringify } from "yaml";
2
+
3
+ //#region src/deployers/strip-host-ports.ts
4
+ /**
5
+ * Strips host port bindings from a Docker Compose YAML string.
6
+ *
7
+ * When deploying to a PaaS like Dokploy or Coolify, services don't need
8
+ * host port mappings because routing is handled by the platform's built-in
9
+ * reverse proxy (Traefik). Binding to host ports causes "port already
10
+ * allocated" errors when ports are in use by other services on the server.
11
+ *
12
+ * Transforms port mappings:
13
+ * "8080:8080" → "8080" (container port only)
14
+ * "0.0.0.0:8080:80" → "80" (container port only)
15
+ * "8080:80/tcp" → "80/tcp" (preserves protocol)
16
+ * { published: 8080, target: 80 } → { target: 80 }
17
+ *
18
+ * The `expose` field is left untouched since it only defines internal ports.
19
+ */
20
+ /**
21
+ * Strips host port bindings from a Docker Compose YAML string,
22
+ * keeping only the container (target) ports.
23
+ *
24
+ * Returns the modified YAML string.
25
+ */
26
+ function stripHostPorts(composeYaml) {
27
+ const doc = parse(composeYaml);
28
+ if (!doc?.services) return composeYaml;
29
+ for (const [, service] of Object.entries(doc.services)) {
30
+ if (!service.ports || !Array.isArray(service.ports)) continue;
31
+ service.ports = service.ports.map((port) => {
32
+ if (typeof port === "string") return stripStringPort(port);
33
+ if (typeof port === "object" && port !== null) return stripObjectPort(port);
34
+ return port;
35
+ });
36
+ }
37
+ return stringify(doc, { lineWidth: 200 });
38
+ }
39
+ /**
40
+ * Strips host portion from a string port mapping.
41
+ *
42
+ * "8080:80" → "80"
43
+ * "0.0.0.0:8080:80" → "80"
44
+ * "80" → "80" (no change)
45
+ * "80/tcp" → "80/tcp"
46
+ * "8080:80/tcp" → "80/tcp"
47
+ */
48
+ function stripStringPort(port) {
49
+ const protocolIdx = port.lastIndexOf("/");
50
+ let protocol = "";
51
+ let portSpec = port;
52
+ if (protocolIdx > 0) {
53
+ protocol = port.substring(protocolIdx);
54
+ portSpec = port.substring(0, protocolIdx);
55
+ }
56
+ const parts = portSpec.split(":");
57
+ return `${parts[parts.length - 1]}${protocol}`;
58
+ }
59
+ /**
60
+ * Strips host/published from an object port mapping.
61
+ *
62
+ * { target: 80, published: 8080 } → { target: 80 }
63
+ */
64
+ function stripObjectPort(port) {
65
+ const { published: _, host_ip: __, ...rest } = port;
66
+ return rest;
67
+ }
68
+ /**
69
+ * Strips local bind mounts (paths starting with `./`) from a Docker
70
+ * Compose YAML string.
71
+ *
72
+ * When deploying to a PaaS like Dokploy or Coolify as a raw compose
73
+ * stack, there is no cloned repository — so host-relative volume mounts
74
+ * like `./postgres/init-databases.sh:/docker-entrypoint-initdb.d/...`
75
+ * will fail because the file doesn't exist on the remote server.
76
+ *
77
+ * Named volumes (e.g. `redis-data:/data`) and absolute system paths
78
+ * (e.g. `/var/run/docker.sock`) are kept intact.
79
+ */
80
+ function stripLocalBindMounts(composeYaml) {
81
+ const doc = parse(composeYaml);
82
+ if (!doc?.services) return composeYaml;
83
+ for (const [, service] of Object.entries(doc.services)) {
84
+ if (!service.volumes || !Array.isArray(service.volumes)) continue;
85
+ service.volumes = service.volumes.filter((vol) => {
86
+ if (typeof vol === "string") return !vol.startsWith("./");
87
+ if (typeof vol === "object" && vol !== null) {
88
+ const src = vol.source;
89
+ return !(typeof src === "string" && src.startsWith("./"));
90
+ }
91
+ return true;
92
+ });
93
+ if (service.volumes.length === 0) delete service.volumes;
94
+ }
95
+ return stringify(doc, { lineWidth: 200 });
96
+ }
97
+ /**
98
+ * Strips security hardening options (`cap_drop`, `cap_add`, `security_opt`)
99
+ * from all services in a Docker Compose YAML string.
100
+ *
101
+ * Many Docker images (PostgreSQL, Redis, MinIO, etc.) start as root and
102
+ * use `gosu`/`su-exec` to drop privileges to a non-root user. This
103
+ * requires `SETUID`, `SETGID`, and other capabilities. The hardened
104
+ * compose output adds `cap_drop: ALL` + `no-new-privileges`, which
105
+ * prevents this user switch and causes containers to crash with:
106
+ * "failed switching to 'postgres': operation not permitted"
107
+ *
108
+ * PaaS platforms (Dokploy, Coolify) manage their own container security,
109
+ * so these options are unnecessary and should be removed.
110
+ */
111
+ function stripSecurityHardening(composeYaml) {
112
+ const doc = parse(composeYaml);
113
+ if (!doc?.services) return composeYaml;
114
+ for (const [, service] of Object.entries(doc.services)) {
115
+ delete service.cap_drop;
116
+ delete service.cap_add;
117
+ delete service.security_opt;
118
+ }
119
+ return stringify(doc, { lineWidth: 200 });
120
+ }
121
+ /**
122
+ * Applies all PaaS-specific sanitisations to a Docker Compose YAML string:
123
+ * 1. Strips host port bindings (avoids "port already allocated" errors)
124
+ * 2. Strips local bind mounts (files don't exist on remote PaaS servers)
125
+ * 3. Strips security hardening (cap_drop/security_opt break user switching)
126
+ */
127
+ function sanitizeComposeForPaas(composeYaml) {
128
+ return stripSecurityHardening(stripLocalBindMounts(stripHostPorts(composeYaml)));
129
+ }
130
+
131
+ //#endregion
132
+ export { sanitizeComposeForPaas, stripHostPorts, stripLocalBindMounts, stripSecurityHardening };
133
+ //# sourceMappingURL=strip-host-ports.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-host-ports.mjs","names":[],"sources":["../../src/deployers/strip-host-ports.ts"],"sourcesContent":["/**\n * Strips host port bindings from a Docker Compose YAML string.\n *\n * When deploying to a PaaS like Dokploy or Coolify, services don't need\n * host port mappings because routing is handled by the platform's built-in\n * reverse proxy (Traefik). Binding to host ports causes \"port already\n * allocated\" errors when ports are in use by other services on the server.\n *\n * Transforms port mappings:\n * \"8080:8080\" → \"8080\" (container port only)\n * \"0.0.0.0:8080:80\" → \"80\" (container port only)\n * \"8080:80/tcp\" → \"80/tcp\" (preserves protocol)\n * { published: 8080, target: 80 } → { target: 80 }\n *\n * The `expose` field is left untouched since it only defines internal ports.\n */\n\nimport { parse, stringify } from \"yaml\";\n\ninterface ComposeService {\n\tports?: (string | PortObject)[];\n\tvolumes?: (string | Record<string, unknown>)[];\n\t[key: string]: unknown;\n}\n\ninterface PortObject {\n\ttarget: number;\n\tpublished?: number | string;\n\thost_ip?: string;\n\tprotocol?: string;\n\t[key: string]: unknown;\n}\n\ninterface ComposeFile {\n\tservices?: Record<string, ComposeService>;\n\t[key: string]: unknown;\n}\n\n/**\n * Strips host port bindings from a Docker Compose YAML string,\n * keeping only the container (target) ports.\n *\n * Returns the modified YAML string.\n */\nexport function stripHostPorts(composeYaml: string): string {\n\tconst doc = parse(composeYaml) as ComposeFile;\n\n\tif (!doc?.services) return composeYaml;\n\n\tfor (const [, service] of Object.entries(doc.services)) {\n\t\tif (!service.ports || !Array.isArray(service.ports)) continue;\n\n\t\tservice.ports = service.ports.map((port) => {\n\t\t\tif (typeof port === \"string\") {\n\t\t\t\treturn stripStringPort(port);\n\t\t\t}\n\t\t\tif (typeof port === \"object\" && port !== null) {\n\t\t\t\treturn stripObjectPort(port);\n\t\t\t}\n\t\t\treturn port;\n\t\t});\n\t}\n\n\treturn stringify(doc, { lineWidth: 200 });\n}\n\n/**\n * Strips host portion from a string port mapping.\n *\n * \"8080:80\" → \"80\"\n * \"0.0.0.0:8080:80\" → \"80\"\n * \"80\" → \"80\" (no change)\n * \"80/tcp\" → \"80/tcp\"\n * \"8080:80/tcp\" → \"80/tcp\"\n */\nfunction stripStringPort(port: string): string {\n\t// Split off protocol if present (e.g. \"/tcp\", \"/udp\")\n\tconst protocolIdx = port.lastIndexOf(\"/\");\n\tlet protocol = \"\";\n\tlet portSpec = port;\n\n\tif (protocolIdx > 0) {\n\t\tprotocol = port.substring(protocolIdx); // includes the \"/\"\n\t\tportSpec = port.substring(0, protocolIdx);\n\t}\n\n\t// Split by \":\" — formats are:\n\t// \"80\" → container only\n\t// \"8080:80\" → host:container\n\t// \"0.0.0.0:8080:80\" → ip:host:container\n\tconst parts = portSpec.split(\":\");\n\n\t// Take the last part as the container port\n\tconst containerPort = parts[parts.length - 1];\n\n\treturn `${containerPort}${protocol}`;\n}\n\n/**\n * Strips host/published from an object port mapping.\n *\n * { target: 80, published: 8080 } → { target: 80 }\n */\nfunction stripObjectPort(port: PortObject): PortObject {\n\tconst { published: _, host_ip: __, ...rest } = port;\n\treturn rest;\n}\n\n/**\n * Strips local bind mounts (paths starting with `./`) from a Docker\n * Compose YAML string.\n *\n * When deploying to a PaaS like Dokploy or Coolify as a raw compose\n * stack, there is no cloned repository — so host-relative volume mounts\n * like `./postgres/init-databases.sh:/docker-entrypoint-initdb.d/...`\n * will fail because the file doesn't exist on the remote server.\n *\n * Named volumes (e.g. `redis-data:/data`) and absolute system paths\n * (e.g. `/var/run/docker.sock`) are kept intact.\n */\nexport function stripLocalBindMounts(composeYaml: string): string {\n\tconst doc = parse(composeYaml) as ComposeFile;\n\n\tif (!doc?.services) return composeYaml;\n\n\tfor (const [, service] of Object.entries(doc.services)) {\n\t\tif (!service.volumes || !Array.isArray(service.volumes)) continue;\n\n\t\tservice.volumes = (service.volumes as (string | Record<string, unknown>)[]).filter((vol) => {\n\t\t\tif (typeof vol === \"string\") {\n\t\t\t\t// Bind mounts starting with \"./\" reference local files\n\t\t\t\treturn !vol.startsWith(\"./\");\n\t\t\t}\n\t\t\t// Object-form: { type: \"bind\", source: \"./...\" }\n\t\t\tif (typeof vol === \"object\" && vol !== null) {\n\t\t\t\tconst src = (vol as Record<string, unknown>).source;\n\t\t\t\treturn !(typeof src === \"string\" && src.startsWith(\"./\"));\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\n\t\t// Remove empty volumes array to keep YAML clean\n\t\tif (service.volumes.length === 0) {\n\t\t\tdelete service.volumes;\n\t\t}\n\t}\n\n\treturn stringify(doc, { lineWidth: 200 });\n}\n\n/**\n * Strips security hardening options (`cap_drop`, `cap_add`, `security_opt`)\n * from all services in a Docker Compose YAML string.\n *\n * Many Docker images (PostgreSQL, Redis, MinIO, etc.) start as root and\n * use `gosu`/`su-exec` to drop privileges to a non-root user. This\n * requires `SETUID`, `SETGID`, and other capabilities. The hardened\n * compose output adds `cap_drop: ALL` + `no-new-privileges`, which\n * prevents this user switch and causes containers to crash with:\n * \"failed switching to 'postgres': operation not permitted\"\n *\n * PaaS platforms (Dokploy, Coolify) manage their own container security,\n * so these options are unnecessary and should be removed.\n */\nexport function stripSecurityHardening(composeYaml: string): string {\n\tconst doc = parse(composeYaml) as ComposeFile;\n\n\tif (!doc?.services) return composeYaml;\n\n\tfor (const [, service] of Object.entries(doc.services)) {\n\t\tdelete service.cap_drop;\n\t\tdelete service.cap_add;\n\t\tdelete service.security_opt;\n\t}\n\n\treturn stringify(doc, { lineWidth: 200 });\n}\n\n/**\n * Applies all PaaS-specific sanitisations to a Docker Compose YAML string:\n * 1. Strips host port bindings (avoids \"port already allocated\" errors)\n * 2. Strips local bind mounts (files don't exist on remote PaaS servers)\n * 3. Strips security hardening (cap_drop/security_opt break user switching)\n */\nexport function sanitizeComposeForPaas(composeYaml: string): string {\n\treturn stripSecurityHardening(stripLocalBindMounts(stripHostPorts(composeYaml)));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,eAAe,aAA6B;CAC3D,MAAM,MAAM,MAAM,YAAY;AAE9B,KAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,MAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,IAAI,SAAS,EAAE;AACvD,MAAI,CAAC,QAAQ,SAAS,CAAC,MAAM,QAAQ,QAAQ,MAAM,CAAE;AAErD,UAAQ,QAAQ,QAAQ,MAAM,KAAK,SAAS;AAC3C,OAAI,OAAO,SAAS,SACnB,QAAO,gBAAgB,KAAK;AAE7B,OAAI,OAAO,SAAS,YAAY,SAAS,KACxC,QAAO,gBAAgB,KAAK;AAE7B,UAAO;IACN;;AAGH,QAAO,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;;;;;;;;;;;AAY1C,SAAS,gBAAgB,MAAsB;CAE9C,MAAM,cAAc,KAAK,YAAY,IAAI;CACzC,IAAI,WAAW;CACf,IAAI,WAAW;AAEf,KAAI,cAAc,GAAG;AACpB,aAAW,KAAK,UAAU,YAAY;AACtC,aAAW,KAAK,UAAU,GAAG,YAAY;;CAO1C,MAAM,QAAQ,SAAS,MAAM,IAAI;AAKjC,QAAO,GAFe,MAAM,MAAM,SAAS,KAEjB;;;;;;;AAQ3B,SAAS,gBAAgB,MAA8B;CACtD,MAAM,EAAE,WAAW,GAAG,SAAS,IAAI,GAAG,SAAS;AAC/C,QAAO;;;;;;;;;;;;;;AAeR,SAAgB,qBAAqB,aAA6B;CACjE,MAAM,MAAM,MAAM,YAAY;AAE9B,KAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,MAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,IAAI,SAAS,EAAE;AACvD,MAAI,CAAC,QAAQ,WAAW,CAAC,MAAM,QAAQ,QAAQ,QAAQ,CAAE;AAEzD,UAAQ,UAAW,QAAQ,QAAiD,QAAQ,QAAQ;AAC3F,OAAI,OAAO,QAAQ,SAElB,QAAO,CAAC,IAAI,WAAW,KAAK;AAG7B,OAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;IAC5C,MAAM,MAAO,IAAgC;AAC7C,WAAO,EAAE,OAAO,QAAQ,YAAY,IAAI,WAAW,KAAK;;AAEzD,UAAO;IACN;AAGF,MAAI,QAAQ,QAAQ,WAAW,EAC9B,QAAO,QAAQ;;AAIjB,QAAO,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;;;;;;;;;;;;;;;;AAiB1C,SAAgB,uBAAuB,aAA6B;CACnE,MAAM,MAAM,MAAM,YAAY;AAE9B,KAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,MAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,IAAI,SAAS,EAAE;AACvD,SAAO,QAAQ;AACf,SAAO,QAAQ;AACf,SAAO,QAAQ;;AAGhB,QAAO,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;;;;;;;;AAS1C,SAAgB,uBAAuB,aAA6B;AACnE,QAAO,uBAAuB,qBAAqB,eAAe,YAAY,CAAC,CAAC"}
@@ -0,0 +1,89 @@
1
+ const require_vi_2VT5v0um = require('../vi.2VT5v0um-BmRMvymT.cjs');
2
+ const require_deployers_strip_host_ports = require('./strip-host-ports.cjs');
3
+
4
+ //#region src/deployers/strip-host-ports.test.ts
5
+ require_vi_2VT5v0um.describe("stripHostPorts", () => {
6
+ require_vi_2VT5v0um.it("strips host port from short syntax (host:container)", () => {
7
+ const result = require_deployers_strip_host_ports.stripHostPorts(`
8
+ services:
9
+ web:
10
+ image: nginx
11
+ ports:
12
+ - "8080:80"
13
+ - "443:443"
14
+ `);
15
+ require_vi_2VT5v0um.globalExpect(result).toContain("\"80\"");
16
+ require_vi_2VT5v0um.globalExpect(result).toContain("\"443\"");
17
+ require_vi_2VT5v0um.globalExpect(result).not.toContain("8080");
18
+ });
19
+ require_vi_2VT5v0um.it("strips host IP and port from extended short syntax", () => {
20
+ const result = require_deployers_strip_host_ports.stripHostPorts(`
21
+ services:
22
+ web:
23
+ image: nginx
24
+ ports:
25
+ - "0.0.0.0:8080:80"
26
+ `);
27
+ require_vi_2VT5v0um.globalExpect(result).toContain("\"80\"");
28
+ require_vi_2VT5v0um.globalExpect(result).not.toContain("8080");
29
+ require_vi_2VT5v0um.globalExpect(result).not.toContain("0.0.0.0");
30
+ });
31
+ require_vi_2VT5v0um.it("preserves protocol suffix", () => {
32
+ const result = require_deployers_strip_host_ports.stripHostPorts(`
33
+ services:
34
+ web:
35
+ image: nginx
36
+ ports:
37
+ - "8080:80/tcp"
38
+ - "5353:53/udp"
39
+ `);
40
+ require_vi_2VT5v0um.globalExpect(result).toContain("80/tcp");
41
+ require_vi_2VT5v0um.globalExpect(result).toContain("53/udp");
42
+ require_vi_2VT5v0um.globalExpect(result).not.toContain("8080");
43
+ require_vi_2VT5v0um.globalExpect(result).not.toContain("5353");
44
+ });
45
+ require_vi_2VT5v0um.it("keeps container-only ports unchanged", () => {
46
+ require_vi_2VT5v0um.globalExpect(require_deployers_strip_host_ports.stripHostPorts(`
47
+ services:
48
+ web:
49
+ image: nginx
50
+ ports:
51
+ - "80"
52
+ `)).toContain("\"80\"");
53
+ });
54
+ require_vi_2VT5v0um.it("handles multiple services", () => {
55
+ const result = require_deployers_strip_host_ports.stripHostPorts(`
56
+ services:
57
+ web:
58
+ image: nginx
59
+ ports:
60
+ - "8080:80"
61
+ redis:
62
+ image: redis
63
+ ports:
64
+ - "6379:6379"
65
+ searxng:
66
+ image: searxng/searxng
67
+ ports:
68
+ - "8888:8080"
69
+ `);
70
+ require_vi_2VT5v0um.globalExpect(result).toContain("\"80\"");
71
+ require_vi_2VT5v0um.globalExpect(result).toContain("\"6379\"");
72
+ require_vi_2VT5v0um.globalExpect(result).toContain("\"8080\"");
73
+ require_vi_2VT5v0um.globalExpect(result).not.toContain("8888");
74
+ });
75
+ require_vi_2VT5v0um.it("returns original YAML if no services section", () => {
76
+ const yaml = `version: "3"`;
77
+ require_vi_2VT5v0um.globalExpect(require_deployers_strip_host_ports.stripHostPorts(yaml)).toBe(yaml);
78
+ });
79
+ require_vi_2VT5v0um.it("handles services with no ports", () => {
80
+ require_vi_2VT5v0um.globalExpect(require_deployers_strip_host_ports.stripHostPorts(`
81
+ services:
82
+ web:
83
+ image: nginx
84
+ `)).toContain("nginx");
85
+ });
86
+ });
87
+
88
+ //#endregion
89
+ //# sourceMappingURL=strip-host-ports.test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-host-ports.test.cjs","names":["describe","stripHostPorts"],"sources":["../../src/deployers/strip-host-ports.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { stripHostPorts } from \"./strip-host-ports.js\";\n\ndescribe(\"stripHostPorts\", () => {\n\tit(\"strips host port from short syntax (host:container)\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n - \"443:443\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"443\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t});\n\n\tit(\"strips host IP and port from extended short syntax\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"0.0.0.0:8080:80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"0.0.0.0\");\n\t});\n\n\tit(\"preserves protocol suffix\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80/tcp\"\n - \"5353:53/udp\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"80/tcp\");\n\t\texpect(result).toContain(\"53/udp\");\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"5353\");\n\t});\n\n\tit(\"keeps container-only ports unchanged\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t});\n\n\tit(\"handles multiple services\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n redis:\n image: redis\n ports:\n - \"6379:6379\"\n searxng:\n image: searxng/searxng\n ports:\n - \"8888:8080\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"6379\"');\n\t\texpect(result).toContain('\"8080\"');\n\t\texpect(result).not.toContain(\"8888\");\n\t});\n\n\tit(\"returns original YAML if no services section\", () => {\n\t\tconst yaml = `version: \"3\"`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toBe(yaml);\n\t});\n\n\tit(\"handles services with no ports\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"nginx\");\n\t});\n});\n"],"mappings":";;;;AAGAA,6BAAS,wBAAwB;AAChC,wBAAG,6DAA6D;EAS/D,MAAM,SAASC,kDARF;;;;;;;EAQsB;AACnC,mCAAO,OAAO,CAAC,UAAU,SAAO;AAChC,mCAAO,OAAO,CAAC,UAAU,UAAQ;AACjC,mCAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,wBAAG,4DAA4D;EAQ9D,MAAM,SAASA,kDAPF;;;;;;EAOsB;AACnC,mCAAO,OAAO,CAAC,UAAU,SAAO;AAChC,mCAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,mCAAO,OAAO,CAAC,IAAI,UAAU,UAAU;GACtC;AAEF,wBAAG,mCAAmC;EASrC,MAAM,SAASA,kDARF;;;;;;;EAQsB;AACnC,mCAAO,OAAO,CAAC,UAAU,SAAS;AAClC,mCAAO,OAAO,CAAC,UAAU,SAAS;AAClC,mCAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,mCAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,wBAAG,8CAA8C;AAShD,mCADeA,kDAPF;;;;;;EAOsB,CACrB,CAAC,UAAU,SAAO;GAC/B;AAEF,wBAAG,mCAAmC;EAgBrC,MAAM,SAASA,kDAfF;;;;;;;;;;;;;;EAesB;AACnC,mCAAO,OAAO,CAAC,UAAU,SAAO;AAChC,mCAAO,OAAO,CAAC,UAAU,WAAS;AAClC,mCAAO,OAAO,CAAC,UAAU,WAAS;AAClC,mCAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,wBAAG,sDAAsD;EACxD,MAAM,OAAO;AAEb,mCADeA,kDAAe,KAAK,CACrB,CAAC,KAAK,KAAK;GACxB;AAEF,wBAAG,wCAAwC;AAO1C,mCADeA,kDALF;;;;EAKsB,CACrB,CAAC,UAAU,QAAQ;GAChC;EACD"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,90 @@
1
+ import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-CFyDIn0m.mjs";
2
+ import { stripHostPorts } from "./strip-host-ports.mjs";
3
+
4
+ //#region src/deployers/strip-host-ports.test.ts
5
+ describe("stripHostPorts", () => {
6
+ it("strips host port from short syntax (host:container)", () => {
7
+ const result = stripHostPorts(`
8
+ services:
9
+ web:
10
+ image: nginx
11
+ ports:
12
+ - "8080:80"
13
+ - "443:443"
14
+ `);
15
+ globalExpect(result).toContain("\"80\"");
16
+ globalExpect(result).toContain("\"443\"");
17
+ globalExpect(result).not.toContain("8080");
18
+ });
19
+ it("strips host IP and port from extended short syntax", () => {
20
+ const result = stripHostPorts(`
21
+ services:
22
+ web:
23
+ image: nginx
24
+ ports:
25
+ - "0.0.0.0:8080:80"
26
+ `);
27
+ globalExpect(result).toContain("\"80\"");
28
+ globalExpect(result).not.toContain("8080");
29
+ globalExpect(result).not.toContain("0.0.0.0");
30
+ });
31
+ it("preserves protocol suffix", () => {
32
+ const result = stripHostPorts(`
33
+ services:
34
+ web:
35
+ image: nginx
36
+ ports:
37
+ - "8080:80/tcp"
38
+ - "5353:53/udp"
39
+ `);
40
+ globalExpect(result).toContain("80/tcp");
41
+ globalExpect(result).toContain("53/udp");
42
+ globalExpect(result).not.toContain("8080");
43
+ globalExpect(result).not.toContain("5353");
44
+ });
45
+ it("keeps container-only ports unchanged", () => {
46
+ globalExpect(stripHostPorts(`
47
+ services:
48
+ web:
49
+ image: nginx
50
+ ports:
51
+ - "80"
52
+ `)).toContain("\"80\"");
53
+ });
54
+ it("handles multiple services", () => {
55
+ const result = stripHostPorts(`
56
+ services:
57
+ web:
58
+ image: nginx
59
+ ports:
60
+ - "8080:80"
61
+ redis:
62
+ image: redis
63
+ ports:
64
+ - "6379:6379"
65
+ searxng:
66
+ image: searxng/searxng
67
+ ports:
68
+ - "8888:8080"
69
+ `);
70
+ globalExpect(result).toContain("\"80\"");
71
+ globalExpect(result).toContain("\"6379\"");
72
+ globalExpect(result).toContain("\"8080\"");
73
+ globalExpect(result).not.toContain("8888");
74
+ });
75
+ it("returns original YAML if no services section", () => {
76
+ const yaml = `version: "3"`;
77
+ globalExpect(stripHostPorts(yaml)).toBe(yaml);
78
+ });
79
+ it("handles services with no ports", () => {
80
+ globalExpect(stripHostPorts(`
81
+ services:
82
+ web:
83
+ image: nginx
84
+ `)).toContain("nginx");
85
+ });
86
+ });
87
+
88
+ //#endregion
89
+ export { };
90
+ //# sourceMappingURL=strip-host-ports.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-host-ports.test.mjs","names":[],"sources":["../../src/deployers/strip-host-ports.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { stripHostPorts } from \"./strip-host-ports.js\";\n\ndescribe(\"stripHostPorts\", () => {\n\tit(\"strips host port from short syntax (host:container)\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n - \"443:443\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"443\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t});\n\n\tit(\"strips host IP and port from extended short syntax\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"0.0.0.0:8080:80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"0.0.0.0\");\n\t});\n\n\tit(\"preserves protocol suffix\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80/tcp\"\n - \"5353:53/udp\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"80/tcp\");\n\t\texpect(result).toContain(\"53/udp\");\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"5353\");\n\t});\n\n\tit(\"keeps container-only ports unchanged\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t});\n\n\tit(\"handles multiple services\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n redis:\n image: redis\n ports:\n - \"6379:6379\"\n searxng:\n image: searxng/searxng\n ports:\n - \"8888:8080\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"6379\"');\n\t\texpect(result).toContain('\"8080\"');\n\t\texpect(result).not.toContain(\"8888\");\n\t});\n\n\tit(\"returns original YAML if no services section\", () => {\n\t\tconst yaml = `version: \"3\"`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toBe(yaml);\n\t});\n\n\tit(\"handles services with no ports\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"nginx\");\n\t});\n});\n"],"mappings":";;;;AAGA,SAAS,wBAAwB;AAChC,IAAG,6DAA6D;EAS/D,MAAM,SAAS,eARF;;;;;;;EAQsB;AACnC,eAAO,OAAO,CAAC,UAAU,SAAO;AAChC,eAAO,OAAO,CAAC,UAAU,UAAQ;AACjC,eAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,IAAG,4DAA4D;EAQ9D,MAAM,SAAS,eAPF;;;;;;EAOsB;AACnC,eAAO,OAAO,CAAC,UAAU,SAAO;AAChC,eAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,eAAO,OAAO,CAAC,IAAI,UAAU,UAAU;GACtC;AAEF,IAAG,mCAAmC;EASrC,MAAM,SAAS,eARF;;;;;;;EAQsB;AACnC,eAAO,OAAO,CAAC,UAAU,SAAS;AAClC,eAAO,OAAO,CAAC,UAAU,SAAS;AAClC,eAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,eAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,IAAG,8CAA8C;AAShD,eADe,eAPF;;;;;;EAOsB,CACrB,CAAC,UAAU,SAAO;GAC/B;AAEF,IAAG,mCAAmC;EAgBrC,MAAM,SAAS,eAfF;;;;;;;;;;;;;;EAesB;AACnC,eAAO,OAAO,CAAC,UAAU,SAAO;AAChC,eAAO,OAAO,CAAC,UAAU,WAAS;AAClC,eAAO,OAAO,CAAC,UAAU,WAAS;AAClC,eAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,IAAG,sDAAsD;EACxD,MAAM,OAAO;AAEb,eADe,eAAe,KAAK,CACrB,CAAC,KAAK,KAAK;GACxB;AAEF,IAAG,wCAAwC;AAO1C,eADe,eALF;;;;EAKsB,CACrB,CAAC,UAAU,QAAQ;GAChC;EACD"}