withvibe 0.1.4

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 (62) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +67 -0
  3. package/dist/api.js +29 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/assets/docker-compose.yml +130 -0
  6. package/dist/config.js +48 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/env-command.js +331 -0
  9. package/dist/env-command.js.map +1 -0
  10. package/dist/exec.js +35 -0
  11. package/dist/exec.js.map +1 -0
  12. package/dist/git-auth.js +64 -0
  13. package/dist/git-auth.js.map +1 -0
  14. package/dist/index.js +42 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/install/anthropic-validate.js +73 -0
  17. package/dist/install/anthropic-validate.js.map +1 -0
  18. package/dist/install/build-images.js +99 -0
  19. package/dist/install/build-images.js.map +1 -0
  20. package/dist/install/commands/build-images-cmd.js +68 -0
  21. package/dist/install/commands/build-images-cmd.js.map +1 -0
  22. package/dist/install/commands/configure.js +276 -0
  23. package/dist/install/commands/configure.js.map +1 -0
  24. package/dist/install/commands/lifecycle.js +129 -0
  25. package/dist/install/commands/lifecycle.js.map +1 -0
  26. package/dist/install/commands/uninstall.js +88 -0
  27. package/dist/install/commands/uninstall.js.map +1 -0
  28. package/dist/install/commands/upgrade.js +164 -0
  29. package/dist/install/commands/upgrade.js.map +1 -0
  30. package/dist/install/compose-rewriter.js +298 -0
  31. package/dist/install/compose-rewriter.js.map +1 -0
  32. package/dist/install/compose.js +118 -0
  33. package/dist/install/compose.js.map +1 -0
  34. package/dist/install/doctor.js +176 -0
  35. package/dist/install/doctor.js.map +1 -0
  36. package/dist/install/env-file.js +63 -0
  37. package/dist/install/env-file.js.map +1 -0
  38. package/dist/install/exec.js +32 -0
  39. package/dist/install/exec.js.map +1 -0
  40. package/dist/install/images.js +78 -0
  41. package/dist/install/images.js.map +1 -0
  42. package/dist/install/init.js +584 -0
  43. package/dist/install/init.js.map +1 -0
  44. package/dist/install/log.js +15 -0
  45. package/dist/install/log.js.map +1 -0
  46. package/dist/install/paths.js +26 -0
  47. package/dist/install/paths.js.map +1 -0
  48. package/dist/install/ports.js +23 -0
  49. package/dist/install/ports.js.map +1 -0
  50. package/dist/install/register.js +155 -0
  51. package/dist/install/register.js.map +1 -0
  52. package/dist/install/secrets.js +9 -0
  53. package/dist/install/secrets.js.map +1 -0
  54. package/dist/install/state.js +29 -0
  55. package/dist/install/state.js.map +1 -0
  56. package/dist/login.js +56 -0
  57. package/dist/login.js.map +1 -0
  58. package/dist/ports.js +33 -0
  59. package/dist/ports.js.map +1 -0
  60. package/dist/preflight.js +150 -0
  61. package/dist/preflight.js.map +1 -0
  62. package/package.json +38 -0
@@ -0,0 +1,88 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import prompts from "prompts";
4
+ import { compose } from "../compose.js";
5
+ import { run } from "../../exec.js";
6
+ import { log } from "../log.js";
7
+ import { DEFAULT_INSTALL_DIR, expandHome } from "../paths.js";
8
+ import { imagesForFeatures, EXTERNAL_IMAGES } from "../images.js";
9
+ import { readState } from "../state.js";
10
+ export async function runUninstall(args) {
11
+ const installDir = args.installDir
12
+ ? path.resolve(expandHome(args.installDir))
13
+ : DEFAULT_INSTALL_DIR;
14
+ const state = await readState(installDir);
15
+ if (!state) {
16
+ log.fail(`No install at ${installDir}.`);
17
+ process.exit(1);
18
+ }
19
+ log.header("withvibe — uninstall");
20
+ log.info(`Install dir: ${installDir}`);
21
+ log.info(`Data: ${args.keepData ? "PRESERVED (volume kept)" : "WILL BE DELETED"}`);
22
+ log.info(`Images: ${args.removeImages ? "WILL BE REMOVED" : "kept"}`);
23
+ if (!args.yes) {
24
+ if (!args.keepData) {
25
+ // Two-step gate for destructive deletes.
26
+ const c1 = await prompts({
27
+ type: "confirm",
28
+ name: "v",
29
+ message: "This will DELETE the postgres volume (every workspace, env, message). Continue?",
30
+ initial: false,
31
+ }, { onCancel: () => process.exit(0) });
32
+ if (!c1.v) {
33
+ log.info("Cancelled.");
34
+ return;
35
+ }
36
+ const phrase = await prompts({
37
+ type: "text",
38
+ name: "v",
39
+ message: 'Type "delete" to confirm:',
40
+ }, { onCancel: () => process.exit(0) });
41
+ if (phrase.v !== "delete") {
42
+ log.info("Cancelled.");
43
+ return;
44
+ }
45
+ }
46
+ else {
47
+ const c = await prompts({
48
+ type: "confirm",
49
+ name: "v",
50
+ message: "Stop the stack and remove containers (data preserved)?",
51
+ initial: true,
52
+ }, { onCancel: () => process.exit(0) });
53
+ if (!c.v)
54
+ return;
55
+ }
56
+ }
57
+ // 1. Tear down compose. -v = also remove named volumes (only when not keeping data).
58
+ const downArgs = ["down"];
59
+ if (!args.keepData)
60
+ downArgs.push("-v");
61
+ log.step(`docker compose ${downArgs.join(" ")}`);
62
+ await compose(installDir, downArgs);
63
+ // 2. Remove images.
64
+ if (args.removeImages) {
65
+ const all = imagesForFeatures(state.features).map((i) => i.localName);
66
+ all.push(EXTERNAL_IMAGES.postgres);
67
+ if (state.features.traefik)
68
+ all.push(EXTERNAL_IMAGES.traefik);
69
+ for (const name of all) {
70
+ log.step(`docker image rm ${name}`);
71
+ await run("docker", ["image", "rm", "-f", name]);
72
+ }
73
+ }
74
+ // 3. Remove install dir state. Preserve backups/ subdir if present.
75
+ const backupsDir = path.join(installDir, "backups");
76
+ const hasBackups = await fs.stat(backupsDir).catch(() => null);
77
+ if (hasBackups?.isDirectory()) {
78
+ log.warn(`Preserving ${backupsDir}.`);
79
+ // Move backups out so we can blow away install dir cleanly. Place under
80
+ // sibling of installDir.
81
+ const sibling = path.join(path.dirname(installDir), `withvibe-backups-${Date.now()}`);
82
+ await fs.rename(backupsDir, sibling);
83
+ log.info(`Backups moved to ${sibling}`);
84
+ }
85
+ await fs.rm(installDir, { recursive: true, force: true });
86
+ log.ok("Uninstall complete.");
87
+ }
88
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../../src/install/commands/uninstall.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import ora from "ora";
4
+ import prompts from "prompts";
5
+ import { run } from "../../exec.js";
6
+ import { compose, readPublicUrls, waitForUrl } from "../compose.js";
7
+ import { composePath, envPath } from "../paths.js";
8
+ import { buildAllImages, pullAllImages } from "../build-images.js";
9
+ import { log } from "../log.js";
10
+ import { DEFAULT_INSTALL_DIR, expandHome } from "../paths.js";
11
+ import { readState } from "../state.js";
12
+ export async function runUpgrade(args) {
13
+ const installDir = args.installDir
14
+ ? path.resolve(expandHome(args.installDir))
15
+ : DEFAULT_INSTALL_DIR;
16
+ const state = await readState(installDir);
17
+ if (!state) {
18
+ log.fail(`No install at ${installDir}.`);
19
+ process.exit(1);
20
+ }
21
+ const mode = args.mode ?? state.mode;
22
+ // 1. Snapshot postgres before touching anything. A failed upgrade with no
23
+ // snapshot is the most painful thing a user can hit.
24
+ const backupPath = await maybeBackup(installDir, args.skipBackup === true);
25
+ // 2. Bring in fresh images.
26
+ log.header("Refreshing images");
27
+ try {
28
+ if (mode === "from-source") {
29
+ const repoPath = args.repoPath ?? state.source?.repoPath;
30
+ if (!repoPath) {
31
+ log.fail("from-source upgrade needs --repo-path or a saved source path.");
32
+ process.exit(1);
33
+ }
34
+ await buildAllImages({ repoPath, features: state.features });
35
+ }
36
+ else if (mode === "from-bundle") {
37
+ const bundlePath = args.bundlePath ?? state.bundle?.bundlePath;
38
+ if (!bundlePath) {
39
+ log.fail("from-bundle upgrade needs --bundle-path.");
40
+ process.exit(1);
41
+ }
42
+ // Re-load the new bundle's images.tar.
43
+ const tar = bundlePath.endsWith(".tar")
44
+ ? bundlePath
45
+ : path.join(bundlePath, "images.tar");
46
+ const r = await run("docker", ["load", "-i", tar], { streamTo: "inherit" });
47
+ if (r.code !== 0)
48
+ throw new Error(`docker load exited ${r.code}`);
49
+ }
50
+ else {
51
+ const namespace = args.registryNamespace ?? state.registry?.namespace;
52
+ const tag = args.registryTag ?? state.registry?.tag;
53
+ if (!namespace || !tag) {
54
+ log.fail("from-registry upgrade needs --registry-namespace and --registry-tag (or saved state).");
55
+ process.exit(1);
56
+ }
57
+ await pullAllImages({ namespace, tag, features: state.features });
58
+ }
59
+ }
60
+ catch (e) {
61
+ log.fail(`Image refresh failed: ${e.message}`);
62
+ if (backupPath)
63
+ log.dim(`Postgres dump preserved at ${backupPath}`);
64
+ process.exit(1);
65
+ }
66
+ // 3. Restart with new images.
67
+ log.header("Restarting services");
68
+ const up = await compose(installDir, ["up", "-d"]);
69
+ if (up.code !== 0) {
70
+ log.fail("compose up failed during upgrade.");
71
+ if (backupPath)
72
+ await offerRollback(installDir, backupPath);
73
+ process.exit(up.code);
74
+ }
75
+ // 4. Health gate. If health doesn't come back, offer rollback.
76
+ const urls = await readPublicUrls(installDir);
77
+ const healthOk = await healthCheck(urls);
78
+ if (!healthOk) {
79
+ log.fail("Health checks failed after upgrade.");
80
+ if (backupPath)
81
+ await offerRollback(installDir, backupPath);
82
+ process.exit(1);
83
+ }
84
+ log.ok("Upgrade complete.");
85
+ if (backupPath)
86
+ log.dim(`Backup retained: ${backupPath}`);
87
+ }
88
+ async function maybeBackup(installDir, skip) {
89
+ if (skip) {
90
+ log.warn("Skipping postgres backup (--skip-backup). No rollback safety net.");
91
+ return null;
92
+ }
93
+ const backupsDir = path.join(installDir, "backups");
94
+ await fs.mkdir(backupsDir, { recursive: true });
95
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
96
+ const file = path.join(backupsDir, `postgres-${stamp}.sql`);
97
+ const spinner = ora(`Snapshotting postgres → ${file}`).start();
98
+ // pg_dump runs INSIDE the postgres container so we don't need the client
99
+ // installed on the host. Pull POSTGRES_USER/DB from the env file via the
100
+ // compose service environment — easiest by exec'ing the container.
101
+ const dump = await compose(installDir, [
102
+ "exec",
103
+ "-T",
104
+ "postgres",
105
+ "sh",
106
+ "-c",
107
+ 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"',
108
+ ], { stream: false });
109
+ if (dump.code !== 0) {
110
+ spinner.fail(`pg_dump failed (exit ${dump.code}). The stack may not be running yet — bringing it up first is fine.`);
111
+ return null;
112
+ }
113
+ await fs.writeFile(file, dump.stdout);
114
+ spinner.succeed(`Postgres dump saved (${formatBytes(dump.stdout.length)})`);
115
+ return file;
116
+ }
117
+ async function healthCheck(urls) {
118
+ let ok = true;
119
+ if (urls.api) {
120
+ const r = await waitForUrl(`${urls.api.replace(/\/+$/, "")}/api/health`, 60_000);
121
+ if (!r.ok)
122
+ ok = false;
123
+ }
124
+ if (urls.web) {
125
+ const r = await waitForUrl(urls.web, 60_000);
126
+ if (!r.ok)
127
+ ok = false;
128
+ }
129
+ return ok;
130
+ }
131
+ async function offerRollback(installDir, backupPath) {
132
+ const ans = await prompts({
133
+ type: "confirm",
134
+ name: "v",
135
+ message: `Restore postgres from ${backupPath}? (re-runs the dump)`,
136
+ initial: true,
137
+ }, { onCancel: () => process.exit(2) });
138
+ if (!ans.v) {
139
+ log.warn("Skipping restore. Backup remains on disk.");
140
+ return;
141
+ }
142
+ // Pipe the dump file into psql inside the container. We have to shell out
143
+ // to `sh -c '... | docker compose exec -T ...'` because Node's run() helper
144
+ // doesn't support piping a file into stdin of a child process.
145
+ const compFile = composePath(installDir);
146
+ const compEnv = envPath(installDir);
147
+ const cmd = `cat ${JSON.stringify(backupPath)} | ` +
148
+ `docker compose -f ${JSON.stringify(compFile)} --env-file ${JSON.stringify(compEnv)} ` +
149
+ `exec -T postgres sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"'`;
150
+ const restore = await run("sh", ["-c", cmd], { streamTo: "inherit" });
151
+ if (restore.code !== 0) {
152
+ log.fail("Automatic restore failed. Restore manually using the dump file.");
153
+ return;
154
+ }
155
+ log.ok("Postgres restored.");
156
+ }
157
+ function formatBytes(n) {
158
+ if (n < 1024)
159
+ return `${n} B`;
160
+ if (n < 1024 * 1024)
161
+ return `${(n / 1024).toFixed(1)} KB`;
162
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
163
+ }
164
+ //# sourceMappingURL=upgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/install/commands/upgrade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,298 @@
1
+ // Tiny, intentional compose-yaml editor: flips the Traefik service block on/off
2
+ // and strips `build:` blocks. Works on the docker-compose.yml the installer
3
+ // copies into the install dir at `init` time.
4
+ //
5
+ // Markers we look for:
6
+ // # >>> withvibe-traefik
7
+ // ... block ...
8
+ // # <<< withvibe-traefik
9
+ // If absent, we append the block at the end (still inside `services:`).
10
+ // Remove every `build:` mapping nested under a service. The installed compose
11
+ // file lives in ~/.withvibe with no source tree alongside it, so leaving the
12
+ // `build:` block in means `docker compose up` either tries to build (and
13
+ // fails with "no such file or directory: ./apps") or, worse, falls back to
14
+ // pulling from a registry that doesn't have these images yet. The installer
15
+ // already builds via `withvibe build-images`, so the runtime compose only
16
+ // needs `image:` references.
17
+ export function stripBuildBlocks(yaml) {
18
+ const lines = yaml.split("\n");
19
+ const out = [];
20
+ let i = 0;
21
+ // Anything indented deeper than the `build:` line itself belongs to the
22
+ // build block and gets dropped. Two-space indented `build:` is the only
23
+ // shape we ever ship.
24
+ const buildLine = /^(\s+)build:\s*$/;
25
+ while (i < lines.length) {
26
+ const line = lines[i];
27
+ const m = line.match(buildLine);
28
+ if (!m) {
29
+ out.push(line);
30
+ i++;
31
+ continue;
32
+ }
33
+ const baseIndent = m[1].length;
34
+ i++;
35
+ while (i < lines.length) {
36
+ const next = lines[i];
37
+ if (next.trim() === "") {
38
+ i++;
39
+ continue;
40
+ }
41
+ const nextIndent = next.match(/^(\s*)/)[1].length;
42
+ if (nextIndent > baseIndent) {
43
+ i++;
44
+ continue;
45
+ }
46
+ break;
47
+ }
48
+ }
49
+ return out.join("\n");
50
+ }
51
+ // Default to `pull_policy: never` for the api/web services so a missing
52
+ // local image fails with a clear error instead of attempting a registry
53
+ // pull. Postgres still pulls normally.
54
+ export function setNoPull(yaml) {
55
+ // Only insert under services that reference our images. Idempotent: skips
56
+ // if a `pull_policy:` line is already present in the next few lines.
57
+ const lines = yaml.split("\n");
58
+ const out = [];
59
+ for (let i = 0; i < lines.length; i++) {
60
+ const line = lines[i];
61
+ out.push(line);
62
+ const m = line.match(/^(\s+)image:\s*(withvibe[\/-][\S]+)\s*$/);
63
+ if (!m)
64
+ continue;
65
+ // Look ahead for an existing pull_policy: at the same indent.
66
+ const indent = m[1];
67
+ let already = false;
68
+ for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
69
+ if (/^\s*pull_policy:/.test(lines[j])) {
70
+ already = true;
71
+ break;
72
+ }
73
+ // Stop when we leave this service's block.
74
+ const next = lines[j];
75
+ if (next.trim() && !next.startsWith(indent))
76
+ break;
77
+ }
78
+ if (!already)
79
+ out.push(`${indent}pull_policy: never`);
80
+ }
81
+ return out.join("\n");
82
+ }
83
+ // Markers must be mutually un-prefix: `indexOf("# >>> withvibe-traefik")`
84
+ // would otherwise match `# >>> withvibe-traefik-volume` too and corrupt
85
+ // later removals. Using `-service` rather than the bare `-traefik` keeps
86
+ // every marker pair unique.
87
+ const TRAEFIK_BEGIN = "# >>> withvibe-traefik-service";
88
+ const TRAEFIK_END = "# <<< withvibe-traefik-service";
89
+ // HTTPS variant — used when the base domain is a real public domain, so
90
+ // Let's Encrypt can successfully complete the TLS challenge.
91
+ //
92
+ // IMPORTANT: routing labels go on the *web* service block (see
93
+ // WEB_LABELS_* constants below), not on the traefik container itself.
94
+ // With Traefik's Docker provider, labels declare the *container they're
95
+ // on* as the backend — putting `traefik.http.services.web.loadbalancer.
96
+ // server.port=3000` on the traefik container makes Traefik try to forward
97
+ // to its own port 3000 (where nothing listens) → 502 on every request.
98
+ const TRAEFIK_BLOCK_TLS = `${TRAEFIK_BEGIN}
99
+ traefik:
100
+ image: traefik:v3.1
101
+ restart: unless-stopped
102
+ command:
103
+ - "--api.dashboard=false"
104
+ - "--providers.docker=true"
105
+ - "--providers.docker.exposedbydefault=false"
106
+ - "--entrypoints.web.address=:80"
107
+ - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
108
+ - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
109
+ - "--entrypoints.websecure.address=:443"
110
+ - "--certificatesresolvers.letsencrypt.acme.email=\${TRAEFIK_ACME_EMAIL}"
111
+ - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
112
+ - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
113
+ ports:
114
+ - "\${TRAEFIK_HTTP_HOST_PORT:-80}:80"
115
+ - "\${TRAEFIK_HTTPS_HOST_PORT:-443}:443"
116
+ volumes:
117
+ - traefik-letsencrypt:/letsencrypt
118
+ - /var/run/docker.sock:/var/run/docker.sock:ro
119
+ networks: [withvibe]
120
+ ${TRAEFIK_END}`;
121
+ // HTTP-only variant — used for local installs (PUBLIC_HOST=localhost) where
122
+ // ACME would fail forever since the host has no public DNS. Still routes
123
+ // traffic by Host header so multi-subdomain envs work behind something like
124
+ // dnsmasq pointing *.localhost at 127.0.0.1.
125
+ const TRAEFIK_BLOCK_PLAIN = `${TRAEFIK_BEGIN}
126
+ traefik:
127
+ image: traefik:v3.1
128
+ restart: unless-stopped
129
+ command:
130
+ - "--api.dashboard=false"
131
+ - "--providers.docker=true"
132
+ - "--providers.docker.exposedbydefault=false"
133
+ - "--entrypoints.web.address=:80"
134
+ ports:
135
+ - "\${TRAEFIK_HTTP_HOST_PORT:-80}:80"
136
+ volumes:
137
+ - /var/run/docker.sock:/var/run/docker.sock:ro
138
+ networks: [withvibe]
139
+ ${TRAEFIK_END}`;
140
+ // Markers + body for the routing labels that get injected into the web
141
+ // service block. Two variants — TLS (websecure entrypoint + cert resolver)
142
+ // vs PLAIN (web entrypoint, no TLS). Marker-bracketed so swapping modes is
143
+ // idempotent and we don't accumulate stale labels on flip.
144
+ const WEB_LABELS_BEGIN = "# >>> withvibe-web-labels";
145
+ const WEB_LABELS_END = "# <<< withvibe-web-labels";
146
+ const WEB_LABELS_TLS = ` ${WEB_LABELS_BEGIN}
147
+ labels:
148
+ - "traefik.enable=true"
149
+ - "traefik.http.routers.web.rule=Host(\`\${TRAEFIK_BASE_DOMAIN}\`)"
150
+ - "traefik.http.routers.web.entrypoints=websecure"
151
+ - "traefik.http.routers.web.tls.certresolver=letsencrypt"
152
+ - "traefik.http.services.web.loadbalancer.server.port=3000"
153
+ ${WEB_LABELS_END}`;
154
+ const WEB_LABELS_PLAIN = ` ${WEB_LABELS_BEGIN}
155
+ labels:
156
+ - "traefik.enable=true"
157
+ - "traefik.http.routers.web.rule=Host(\`\${TRAEFIK_BASE_DOMAIN}\`)"
158
+ - "traefik.http.routers.web.entrypoints=web"
159
+ - "traefik.http.services.web.loadbalancer.server.port=3000"
160
+ ${WEB_LABELS_END}`;
161
+ const TRAEFIK_VOLUME_BEGIN = "# >>> withvibe-traefik-volume";
162
+ const TRAEFIK_VOLUME_END = "# <<< withvibe-traefik-volume";
163
+ const TRAEFIK_VOLUME_BLOCK = `${TRAEFIK_VOLUME_BEGIN}
164
+ traefik-letsencrypt:
165
+ ${TRAEFIK_VOLUME_END}`;
166
+ // Decide which variant to emit based on the base domain. localhost-shaped
167
+ // values can't get a real cert, so fall back to plain HTTP automatically.
168
+ export function pickTraefikMode(baseDomain) {
169
+ const d = (baseDomain ?? "").toLowerCase().trim();
170
+ if (!d)
171
+ return "plain";
172
+ if (d === "localhost" || d.endsWith(".localhost"))
173
+ return "plain";
174
+ if (d.endsWith(".local"))
175
+ return "plain";
176
+ return "tls";
177
+ }
178
+ export function setTraefik(yaml, enabled, baseDomain) {
179
+ const mode = pickTraefikMode(baseDomain);
180
+ const block = mode === "tls" ? TRAEFIK_BLOCK_TLS : TRAEFIK_BLOCK_PLAIN;
181
+ const webLabels = mode === "tls" ? WEB_LABELS_TLS : WEB_LABELS_PLAIN;
182
+ const wantVolume = enabled && mode === "tls";
183
+ // Always strip first so a switch tls↔plain swaps the body cleanly.
184
+ let out = yaml;
185
+ out = setSection(out, TRAEFIK_BEGIN, TRAEFIK_END, block, false, "services");
186
+ out = setSection(out, TRAEFIK_VOLUME_BEGIN, TRAEFIK_VOLUME_END, TRAEFIK_VOLUME_BLOCK, false, "volumes");
187
+ out = setWebLabels(out, "");
188
+ if (!enabled)
189
+ return out;
190
+ out = setSection(out, TRAEFIK_BEGIN, TRAEFIK_END, block, true, "services");
191
+ out = setSection(out, TRAEFIK_VOLUME_BEGIN, TRAEFIK_VOLUME_END, TRAEFIK_VOLUME_BLOCK, wantVolume, "volumes");
192
+ out = setWebLabels(out, webLabels);
193
+ return out;
194
+ }
195
+ // Inject (or remove, by passing "") the marker-bracketed Traefik labels
196
+ // block into the `web` service. Idempotent: running the same call twice
197
+ // is a no-op, and switching tls↔plain swaps the body in place. We attach
198
+ // the labels to the web service rather than the traefik service because
199
+ // Traefik's Docker provider treats container labels as backend
200
+ // declarations — labels on the traefik container itself would route to
201
+ // the traefik container's own port, which is the wrong target.
202
+ function setWebLabels(yaml, block) {
203
+ // Remove any existing block first.
204
+ const beginIdx = yaml.indexOf(WEB_LABELS_BEGIN);
205
+ if (beginIdx !== -1) {
206
+ const endIdx = yaml.indexOf(WEB_LABELS_END, beginIdx);
207
+ if (endIdx !== -1) {
208
+ const after = endIdx + WEB_LABELS_END.length;
209
+ // Eat the trailing newline so we don't leave a blank line behind.
210
+ const eatNewline = yaml[after] === "\n" ? 1 : 0;
211
+ // And eat the indentation that preceded the begin marker on the
212
+ // same line (so removal leaves a clean line break).
213
+ let lineStart = beginIdx;
214
+ while (lineStart > 0 && yaml[lineStart - 1] === " ")
215
+ lineStart--;
216
+ yaml = yaml.slice(0, lineStart) + yaml.slice(after + eatNewline);
217
+ }
218
+ }
219
+ if (!block)
220
+ return yaml;
221
+ // Insert at the end of the `web:` service block. Find the ` web:` line
222
+ // (two-space indent), then walk forward until we hit either another
223
+ // top-level service (line with same indent) or a top-level key.
224
+ const webRe = /(^|\n) web:\s*\n/;
225
+ const m = yaml.match(webRe);
226
+ if (!m)
227
+ return yaml; // no web service to label — leave as-is
228
+ const webStart = m.index + m[0].length;
229
+ const after = yaml.slice(webStart);
230
+ const lines = after.split("\n");
231
+ let cutAt = lines.length;
232
+ for (let i = 0; i < lines.length; i++) {
233
+ const line = lines[i];
234
+ if (line.length === 0)
235
+ continue;
236
+ if (/^ /.test(line) || /^ #/.test(line))
237
+ continue; // child of web
238
+ cutAt = i;
239
+ break;
240
+ }
241
+ const beforeLines = lines.slice(0, cutAt).join("\n").replace(/\n*$/, "");
242
+ const afterLines = lines.slice(cutAt).join("\n");
243
+ return (yaml.slice(0, webStart) +
244
+ beforeLines +
245
+ "\n" +
246
+ block +
247
+ "\n" +
248
+ (afterLines ? afterLines : ""));
249
+ }
250
+ // Insert/remove a marker-bracketed block under a given top-level key.
251
+ function setSection(yaml, begin, end, block, enabled, parentKey) {
252
+ const has = yaml.includes(begin) && yaml.includes(end);
253
+ if (!enabled) {
254
+ if (!has)
255
+ return yaml;
256
+ const startIdx = yaml.indexOf(begin);
257
+ const endIdx = yaml.indexOf(end, startIdx) + end.length;
258
+ let cut = yaml.slice(0, startIdx) + yaml.slice(endIdx);
259
+ // Strip the line-trailing newline we leave behind when removing.
260
+ cut = cut.replace(/\n{3,}/g, "\n\n");
261
+ return cut;
262
+ }
263
+ if (has)
264
+ return yaml; // already enabled; no-op
265
+ // Find the parent key. If `volumes:` doesn't exist yet, create it at the end.
266
+ const keyRe = new RegExp(`(^|\\n)${parentKey}:\\s*\\n`);
267
+ const m = yaml.match(keyRe);
268
+ if (!m) {
269
+ // No parent key — append a new one with this block as its first child.
270
+ return `${yaml.replace(/\n*$/, "")}\n\n${parentKey}:\n${block}\n`;
271
+ }
272
+ // Insert at the END of the parent block. Parent block ends at next
273
+ // top-level key (a non-indented line that isn't blank/comment) or EOF.
274
+ const parentStart = m.index + m[0].length;
275
+ const after = yaml.slice(parentStart);
276
+ const lines = after.split("\n");
277
+ let cutAt = lines.length;
278
+ for (let i = 0; i < lines.length; i++) {
279
+ const line = lines[i];
280
+ if (line.length === 0)
281
+ continue;
282
+ if (/^\s/.test(line))
283
+ continue;
284
+ if (line.startsWith("#"))
285
+ continue;
286
+ cutAt = i;
287
+ break;
288
+ }
289
+ const beforeBlock = lines.slice(0, cutAt).join("\n").replace(/\n*$/, "");
290
+ const afterBlock = lines.slice(cutAt).join("\n");
291
+ return (yaml.slice(0, parentStart) +
292
+ beforeBlock +
293
+ "\n" +
294
+ block +
295
+ "\n" +
296
+ (afterBlock ? afterBlock : ""));
297
+ }
298
+ //# sourceMappingURL=compose-rewriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-rewriter.js","sourceRoot":"","sources":["../../src/install/compose-rewriter.ts"],"names":[],"mappings":"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"}