koppler 0.0.0-stage → 0.0.2

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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -2
  3. package/bin/run-logged +16 -0
  4. package/bin/secret-list +7 -0
  5. package/bin/secret-psql +41 -0
  6. package/bin/secret-set +17 -0
  7. package/bin/secret-store +82 -0
  8. package/bin/secret-test +10 -0
  9. package/bin/with-secrets +25 -0
  10. package/dist/.built +1 -0
  11. package/dist/cli/agent.js +144 -0
  12. package/dist/cli/agent.js.map +1 -0
  13. package/dist/cli/doctor.js +173 -0
  14. package/dist/cli/doctor.js.map +1 -0
  15. package/dist/cli/mcp.js +204 -0
  16. package/dist/cli/mcp.js.map +1 -0
  17. package/dist/cli/output.js +37 -0
  18. package/dist/cli/output.js.map +1 -0
  19. package/dist/cli/records.js +121 -0
  20. package/dist/cli/records.js.map +1 -0
  21. package/dist/cli/running.js +270 -0
  22. package/dist/cli/running.js.map +1 -0
  23. package/dist/cli/shell.js +43 -0
  24. package/dist/cli/shell.js.map +1 -0
  25. package/dist/cli/wait.js +59 -0
  26. package/dist/cli/wait.js.map +1 -0
  27. package/dist/core/addresses.js +55 -0
  28. package/dist/core/addresses.js.map +1 -0
  29. package/dist/core/arrange.js +329 -0
  30. package/dist/core/arrange.js.map +1 -0
  31. package/dist/core/cables.js +158 -0
  32. package/dist/core/cables.js.map +1 -0
  33. package/dist/core/expressions.js +72 -0
  34. package/dist/core/expressions.js.map +1 -0
  35. package/dist/core/frameworks.js +13 -0
  36. package/dist/core/frameworks.js.map +1 -0
  37. package/dist/core/kinds.js +244 -0
  38. package/dist/core/kinds.js.map +1 -0
  39. package/dist/core/launch.js +181 -0
  40. package/dist/core/launch.js.map +1 -0
  41. package/dist/core/logfmt.js +144 -0
  42. package/dist/core/logfmt.js.map +1 -0
  43. package/dist/core/mock.js +27 -0
  44. package/dist/core/mock.js.map +1 -0
  45. package/dist/core/plan.js +147 -0
  46. package/dist/core/plan.js.map +1 -0
  47. package/dist/core/ports.js +155 -0
  48. package/dist/core/ports.js.map +1 -0
  49. package/dist/core/presets.js +186 -0
  50. package/dist/core/presets.js.map +1 -0
  51. package/dist/core/secret-types.js +3 -0
  52. package/dist/core/secret-types.js.map +1 -0
  53. package/dist/core/sign-in.js +30 -0
  54. package/dist/core/sign-in.js.map +1 -0
  55. package/dist/core/stack-path.js +64 -0
  56. package/dist/core/stack-path.js.map +1 -0
  57. package/dist/core/stack.js +509 -0
  58. package/dist/core/stack.js.map +1 -0
  59. package/dist/core/terraform.js +393 -0
  60. package/dist/core/terraform.js.map +1 -0
  61. package/dist/data.js +37 -0
  62. package/dist/data.js.map +1 -0
  63. package/dist/engine/actions.js +99 -0
  64. package/dist/engine/actions.js.map +1 -0
  65. package/dist/engine/app-commands.js +106 -0
  66. package/dist/engine/app-commands.js.map +1 -0
  67. package/dist/engine/app-start.js +167 -0
  68. package/dist/engine/app-start.js.map +1 -0
  69. package/dist/engine/app-state.js +43 -0
  70. package/dist/engine/app-state.js.map +1 -0
  71. package/dist/engine/boards.js +219 -0
  72. package/dist/engine/boards.js.map +1 -0
  73. package/dist/engine/branch.js +194 -0
  74. package/dist/engine/branch.js.map +1 -0
  75. package/dist/engine/browser-console.js +45 -0
  76. package/dist/engine/browser-console.js.map +1 -0
  77. package/dist/engine/calls.js +97 -0
  78. package/dist/engine/calls.js.map +1 -0
  79. package/dist/engine/checks.js +270 -0
  80. package/dist/engine/checks.js.map +1 -0
  81. package/dist/engine/clones.js +67 -0
  82. package/dist/engine/clones.js.map +1 -0
  83. package/dist/engine/config.js +26 -0
  84. package/dist/engine/config.js.map +1 -0
  85. package/dist/engine/deployed.js +488 -0
  86. package/dist/engine/deployed.js.map +1 -0
  87. package/dist/engine/devices.js +81 -0
  88. package/dist/engine/devices.js.map +1 -0
  89. package/dist/engine/discover.js +236 -0
  90. package/dist/engine/discover.js.map +1 -0
  91. package/dist/engine/down.js +253 -0
  92. package/dist/engine/down.js.map +1 -0
  93. package/dist/engine/history.js +215 -0
  94. package/dist/engine/history.js.map +1 -0
  95. package/dist/engine/investigate.js +278 -0
  96. package/dist/engine/investigate.js.map +1 -0
  97. package/dist/engine/libraries.js +129 -0
  98. package/dist/engine/libraries.js.map +1 -0
  99. package/dist/engine/linked.js +146 -0
  100. package/dist/engine/linked.js.map +1 -0
  101. package/dist/engine/listen.js +223 -0
  102. package/dist/engine/listen.js.map +1 -0
  103. package/dist/engine/locks.js +38 -0
  104. package/dist/engine/locks.js.map +1 -0
  105. package/dist/engine/messages.js +177 -0
  106. package/dist/engine/messages.js.map +1 -0
  107. package/dist/engine/needs.js +363 -0
  108. package/dist/engine/needs.js.map +1 -0
  109. package/dist/engine/processes.js +350 -0
  110. package/dist/engine/processes.js.map +1 -0
  111. package/dist/engine/query.js +241 -0
  112. package/dist/engine/query.js.map +1 -0
  113. package/dist/engine/queues.js +100 -0
  114. package/dist/engine/queues.js.map +1 -0
  115. package/dist/engine/recipes.js +248 -0
  116. package/dist/engine/recipes.js.map +1 -0
  117. package/dist/engine/reload.js +32 -0
  118. package/dist/engine/reload.js.map +1 -0
  119. package/dist/engine/repos.js +214 -0
  120. package/dist/engine/repos.js.map +1 -0
  121. package/dist/engine/research.js +134 -0
  122. package/dist/engine/research.js.map +1 -0
  123. package/dist/engine/run-logs.js +79 -0
  124. package/dist/engine/run-logs.js.map +1 -0
  125. package/dist/engine/run.js +54 -0
  126. package/dist/engine/run.js.map +1 -0
  127. package/dist/engine/runs.js +223 -0
  128. package/dist/engine/runs.js.map +1 -0
  129. package/dist/engine/session.js +130 -0
  130. package/dist/engine/session.js.map +1 -0
  131. package/dist/engine/share.js +124 -0
  132. package/dist/engine/share.js.map +1 -0
  133. package/dist/engine/stack-file.js +206 -0
  134. package/dist/engine/stack-file.js.map +1 -0
  135. package/dist/engine/stack-share.js +86 -0
  136. package/dist/engine/stack-share.js.map +1 -0
  137. package/dist/engine/stacks.js +143 -0
  138. package/dist/engine/stacks.js.map +1 -0
  139. package/dist/engine/sync.js +214 -0
  140. package/dist/engine/sync.js.map +1 -0
  141. package/dist/engine/traces.js +65 -0
  142. package/dist/engine/traces.js.map +1 -0
  143. package/dist/engine/up.js +621 -0
  144. package/dist/engine/up.js.map +1 -0
  145. package/dist/engine/watch.js +71 -0
  146. package/dist/engine/watch.js.map +1 -0
  147. package/dist/engine/wire.js +120 -0
  148. package/dist/engine/wire.js.map +1 -0
  149. package/dist/failure.js +16 -0
  150. package/dist/failure.js.map +1 -0
  151. package/dist/github.js +95 -0
  152. package/dist/github.js.map +1 -0
  153. package/dist/ids.js +6 -0
  154. package/dist/ids.js.map +1 -0
  155. package/dist/json-files.js +36 -0
  156. package/dist/json-files.js.map +1 -0
  157. package/dist/koppler.mjs +840 -0
  158. package/dist/koppler.mjs.map +1 -0
  159. package/dist/lambda-local.mjs +250 -0
  160. package/dist/lambda-local.mjs.map +1 -0
  161. package/dist/lambda-secrets-proxy.go +31 -0
  162. package/dist/mock-server.mjs +23 -0
  163. package/dist/mock-server.mjs.map +1 -0
  164. package/dist/page/assets/JetBrainsMono-cyrillic-D2jMJ_bk.woff2 +0 -0
  165. package/dist/page/assets/JetBrainsMono-greek-D2nYnAec.woff2 +0 -0
  166. package/dist/page/assets/JetBrainsMono-latin-Db4Uuiha.woff2 +0 -0
  167. package/dist/page/assets/JetBrainsMono-latin-ext-Bnd2h4Br.woff2 +0 -0
  168. package/dist/page/assets/JetBrainsMono-vietnamese-CNlsqbWb.woff2 +0 -0
  169. package/dist/page/assets/SchibstedGrotesk-latin-DxefFeRw.woff2 +0 -0
  170. package/dist/page/assets/SchibstedGrotesk-latin-ext-CnpJE18X.woff2 +0 -0
  171. package/dist/page/assets/index-Bkj_pX35.js +42 -0
  172. package/dist/page/assets/index-Dq45jswH.css +1 -0
  173. package/dist/page/index.html +14 -0
  174. package/dist/page/theme.js +8 -0
  175. package/dist/paths.js +19 -0
  176. package/dist/paths.js.map +1 -0
  177. package/dist/safe-env.js +73 -0
  178. package/dist/safe-env.js.map +1 -0
  179. package/dist/sam-local.mjs +135 -0
  180. package/dist/sam-local.mjs.map +1 -0
  181. package/dist/scrub.js +87 -0
  182. package/dist/scrub.js.map +1 -0
  183. package/dist/secret-check.mjs +262 -0
  184. package/dist/secret-check.mjs.map +1 -0
  185. package/dist/server/browser-console.js +117 -0
  186. package/dist/server/browser-console.js.map +1 -0
  187. package/dist/server/cable-test.js +365 -0
  188. package/dist/server/cable-test.js.map +1 -0
  189. package/dist/server/choices.js +51 -0
  190. package/dist/server/choices.js.map +1 -0
  191. package/dist/server/files.js +107 -0
  192. package/dist/server/files.js.map +1 -0
  193. package/dist/server/gate.js +128 -0
  194. package/dist/server/gate.js.map +1 -0
  195. package/dist/server/logs.js +76 -0
  196. package/dist/server/logs.js.map +1 -0
  197. package/dist/server/mobile.js +113 -0
  198. package/dist/server/mobile.js.map +1 -0
  199. package/dist/server/routes.js +326 -0
  200. package/dist/server/routes.js.map +1 -0
  201. package/dist/server/runs.js +111 -0
  202. package/dist/server/runs.js.map +1 -0
  203. package/dist/server/sanitize.js +242 -0
  204. package/dist/server/sanitize.js.map +1 -0
  205. package/dist/server/secrets.js +111 -0
  206. package/dist/server/secrets.js.map +1 -0
  207. package/dist/server/status.js +321 -0
  208. package/dist/server/status.js.map +1 -0
  209. package/dist/server/token.js +38 -0
  210. package/dist/server/token.js.map +1 -0
  211. package/dist/server/waiting.js +79 -0
  212. package/dist/server/waiting.js.map +1 -0
  213. package/dist/status-watch.mjs +63 -0
  214. package/dist/status-watch.mjs.map +1 -0
  215. package/package.json +74 -4
  216. package/skills/koppler-run/SKILL.md +169 -0
  217. package/skills/koppler-run/VERSION +1 -0
  218. package/skills/koppler-run/references/action-log.md +50 -0
  219. package/skills/koppler-run/references/permissions.md +25 -0
  220. package/skills/koppler-run/references/preparing-nodes.md +62 -0
  221. package/skills/koppler-run/references/reporting-bugs.md +76 -0
  222. package/skills/koppler-run/references/research.md +40 -0
  223. package/skills/koppler-run/references/secrets.md +56 -0
  224. package/skills/koppler-run/references/stack.md +197 -0
  225. package/skills/koppler-run/references/status.md +92 -0
@@ -0,0 +1,350 @@
1
+ // The processes Koppler starts: apps, tunnels and the canvas. Each runs detached, so it outlives the terminal or agent
2
+ // session that started it, writes its log to a file, and has a record that says how to find it and stop it.
3
+ import fs from 'node:fs';
4
+ import http from 'node:http';
5
+ import https from 'node:https';
6
+ import path from 'node:path';
7
+ import { execFileSync, spawn } from 'node:child_process';
8
+ import { fail, Failure } from "../failure.js";
9
+ import { logsDir, stateDir } from "../paths.js";
10
+ import { appEnv } from "../safe-env.js";
11
+ import { redact } from "../scrub.js";
12
+ import { keepRunLog } from "./run-logs.js";
13
+ import { keepPreviousLog } from "./session.js";
14
+ import { APP_ID } from "../ids.js";
15
+ import { writeJson } from "../json-files.js";
16
+ const PROCS = path.join(stateDir(), 'procs');
17
+ /** What a process started by Koppler can be called. */
18
+ export const NAME = APP_ID;
19
+ // How long an app with a port may take to answer: a build can take minutes.
20
+ const START_WAIT = Number(process.env['KOPPLER_START_WAIT']) * 1000 || 300000;
21
+ /** A program's output, or nothing when it fails or is not there. */
22
+ export function sh(command, args) {
23
+ try {
24
+ return execFileSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
25
+ }
26
+ catch {
27
+ return '';
28
+ }
29
+ }
30
+ export const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
31
+ /** A restart wrapper keeps flaky tunnels up; stopping kills the whole process group, wrapper included. */
32
+ export function commandFor(argv, restart) {
33
+ const loop = 'while :; do "$@"; echo "koppler: exited with $?, restarting in 2s"; sleep 2; done';
34
+ return restart ? ['sh', '-c', loop, 'sh', ...argv] : argv;
35
+ }
36
+ const SWITCHES = {
37
+ '--restart': { restart: true },
38
+ '--dry-run': { dryRun: true },
39
+ '--lan': { lan: true },
40
+ '--test': { test: true },
41
+ '--no-test': { test: false },
42
+ '--mock': { mock: true },
43
+ };
44
+ /** Reads `<name> [options] [-- <command...>]`; throws with the reason when it cannot. */
45
+ export function parseRun(args) {
46
+ const dash = args.indexOf('--');
47
+ const head = dash < 0 ? args : args.slice(0, dash);
48
+ const command = dash < 0 ? [] : args.slice(dash + 1);
49
+ const options = { name: head[0] ?? '', command, restart: false };
50
+ for (let at = 1; at < head.length; at++) {
51
+ const option = head[at] ?? '';
52
+ const value = () => head[++at] ?? '';
53
+ if (Object.hasOwn(SWITCHES, option))
54
+ Object.assign(options, SWITCHES[option]);
55
+ else if (option === '--cwd')
56
+ options.cwd = value();
57
+ else if (option === '--port')
58
+ options.port = Number(value());
59
+ else if (option === '--bind')
60
+ options.bind = value();
61
+ else if (option === '--env')
62
+ options.env = value();
63
+ else if (option === '--device')
64
+ options.device = value();
65
+ else if (option === '--ready')
66
+ options.ready = { url: value() };
67
+ else
68
+ throw new Error(`unknown option ${option}`);
69
+ }
70
+ if (!NAME.test(options.name))
71
+ throw new Error('name must be lowercase letters, digits and dashes');
72
+ if (dash >= 0 && command.length === 0)
73
+ throw new Error('nothing to run after --');
74
+ if (options.port !== undefined && !(options.port > 0 && options.port < 65536))
75
+ throw new Error('--port must be a TCP port');
76
+ if (options.bind !== undefined && !/^(localhost|127\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.test(options.bind))
77
+ throw new Error('--bind must be an address of this machine (localhost or 127.x.x.x)');
78
+ return options;
79
+ }
80
+ const recordPath = (name) => path.join(PROCS, `${name}.json`);
81
+ export function readRecord(name) {
82
+ try {
83
+ return JSON.parse(fs.readFileSync(recordPath(name), 'utf8'));
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /** Everything Koppler has started and not yet stopped, running or not. */
90
+ export function records() {
91
+ try {
92
+ const names = fs.readdirSync(PROCS).filter(file => file.endsWith('.json'));
93
+ return names.map(file => readRecord(file.slice(0, -5))).filter(record => record !== null);
94
+ }
95
+ catch {
96
+ return [];
97
+ }
98
+ }
99
+ const psCommand = (pid) => sh('ps', ['-p', String(pid), '-o', 'command=']);
100
+ const psStart = (pid) => sh('ps', ['-p', String(pid), '-o', 'lstart=']);
101
+ /**
102
+ * Whether the process of a record still runs. A pid alone can be reused by an unrelated process, so it must be the one
103
+ * started then. Its start time says so; its command cannot, because it changes when a wrapper hands over to the app.
104
+ */
105
+ export function alive(record) {
106
+ try {
107
+ process.kill(record.pid, 0);
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ return record.psStart ? psStart(record.pid) === record.psStart : psCommand(record.pid) === record.psCommand;
113
+ }
114
+ /**
115
+ * What listens on a port. With an address of its own (a tunnel on 127.0.0.2), only what listens there or on every
116
+ * address counts.
117
+ */
118
+ export function listener(port, bind) {
119
+ const output = sh('lsof', ['-nP', `-iTCP:${String(port)}`, '-sTCP:LISTEN', '-Fpcn']);
120
+ const own = bind && !['localhost', '127.0.0.1'].includes(bind) ? [`${bind}:${String(port)}`, `*:${String(port)}`] : null;
121
+ // One block per process: its pid, its command, then the addresses it listens on.
122
+ for (const block of output.split(/^(?=p\d)/m)) {
123
+ const pid = /^p(\d+)/m.exec(block)?.[1];
124
+ const addresses = [...block.matchAll(/^n(.+)/gm)].map(match => match[1] ?? '');
125
+ if (pid && (!own || addresses.some(address => own.includes(address))))
126
+ return { pid: Number(pid), command: /^c(.+)/m.exec(block)?.[1] };
127
+ }
128
+ return null;
129
+ }
130
+ /** The last lines of a log, with secrets masked. */
131
+ export function tailOf(file, lines) {
132
+ try {
133
+ return redact(fs.readFileSync(file, 'utf8').trimEnd().split('\n').slice(-lines).join('\n'));
134
+ }
135
+ catch {
136
+ return '';
137
+ }
138
+ }
139
+ /** How long ago, as "12m" or "3h05m". */
140
+ export function uptime(since) {
141
+ const seconds = Math.floor((Date.now() - Date.parse(since)) / 1000);
142
+ const minutes = Math.floor(seconds / 60);
143
+ return seconds < 3600
144
+ ? `${String(minutes)}m`
145
+ : `${String(Math.floor(minutes / 60))}h${String(minutes % 60).padStart(2, '0')}m`;
146
+ }
147
+ function refuseWhatIsTaken(options) {
148
+ const existing = readRecord(options.name);
149
+ if (existing && alive(existing))
150
+ fail(`${options.name} is already running (pid ${String(existing.pid)}); koppler stop ${options.name} first`);
151
+ const held = options.port ? listener(options.port, options.bind) : null;
152
+ if (held && options.port)
153
+ fail(`port ${String(options.port)} is already held by pid ${String(held.pid)} (${held.command ?? '?'}); stop it or reuse it`);
154
+ }
155
+ /** Waits until an app with a port answers on it; a crash while it builds is reported, not "started". */
156
+ async function waitForPort(record, port) {
157
+ console.log(`waiting for ${record.name} on port ${String(port)}…`);
158
+ const until = Date.now() + START_WAIT;
159
+ while (Date.now() < until && alive(record) && !listener(port, record.bind))
160
+ await sleep(1000);
161
+ if (!alive(record))
162
+ fail(`${record.name} exited before answering on port ${String(port)}; last lines of ${record.log}:\n${tailOf(record.log, 15)}`);
163
+ if (listener(port, record.bind))
164
+ return;
165
+ const waited = `${String(START_WAIT / 1000)}s`;
166
+ throw new Failure(`${record.name} is running (pid ${String(record.pid)}) but nothing answers on port ${String(port)} after ${waited}; see ${record.log}`, 2);
167
+ }
168
+ export const statusOf = (url) => new Promise(resolve => {
169
+ const get = url.protocol === 'https:' ? https.get : http.get;
170
+ // Its own development certificate is not one this process trusts; the address is on this machine.
171
+ const request = get(url, { rejectUnauthorized: false, timeout: 4000 }, response => {
172
+ response.resume();
173
+ resolve(response.statusCode ?? null);
174
+ });
175
+ request.once('timeout', () => request.destroy());
176
+ request.once('error', () => {
177
+ resolve(null);
178
+ });
179
+ });
180
+ /** Waits until the app says it is ready: a port can answer while startup is still failing. */
181
+ async function waitUntilReady(record, port, ready) {
182
+ const url = new URL(ready.url, `http://${record.bind ?? 'localhost'}:${String(port)}`);
183
+ const wait = ready.timeout ? ready.timeout * 1000 : START_WAIT;
184
+ const wanted = (status) => status !== null && (ready.status === undefined ? status < 400 : status === ready.status);
185
+ console.log(`waiting for ${record.name} to be ready at ${url.pathname}…`);
186
+ const until = Date.now() + wait;
187
+ let last = null;
188
+ while (Date.now() < until && alive(record) && !wanted((last = await statusOf(url))))
189
+ await sleep(1000);
190
+ if (!alive(record))
191
+ fail(`${record.name} exited before it was ready; last lines of ${record.log}:\n${tailOf(record.log, 15)}`);
192
+ if (wanted(last))
193
+ return;
194
+ const said = last === null ? 'nothing answers there' : `it answers ${String(last)}`;
195
+ throw new Failure(`${record.name} is running (pid ${String(record.pid)}) but is not ready: ${said} at ${url.pathname} after ${String(wait / 1000)}s; see ${record.log}`, 2);
196
+ }
197
+ /** Starts a command detached, with its log and its record, and returns once it runs (and answers, when it has a port). */
198
+ export async function startProcess(options) {
199
+ const { name, command } = options;
200
+ refuseWhatIsTaken(options);
201
+ fs.mkdirSync(logsDir(), { recursive: true, mode: 0o700 });
202
+ fs.mkdirSync(PROCS, { recursive: true, mode: 0o700 });
203
+ const log = path.join(logsDir(), `${name}.log`);
204
+ const program = command[0] ?? '';
205
+ const display = [path.isAbsolute(program) ? path.basename(program) : program, ...command.slice(1)].join(' ');
206
+ keepRunLog(name);
207
+ keepPreviousLog(log);
208
+ const fd = fs.openSync(log, 'w', 0o600);
209
+ fs.writeSync(fd, `${new Date().toLocaleString('sv-SE')} $ ${display}\n`);
210
+ const argv = commandFor(command, options.restart);
211
+ const cwd = path.resolve(options.cwd ?? process.cwd());
212
+ // An app started with its secrets does not inherit variables that would make its runtime load the caller's code.
213
+ const given = { ...process.env, ...options.setEnv };
214
+ const safe = options.holdsSecrets ? appEnv(given) : null;
215
+ if (safe && safe.dropped.length > 0)
216
+ console.log(`left out of ${name}'s environment, since they load extra code: ${safe.dropped.join(', ')}`);
217
+ const child = spawn(argv[0] ?? '', argv.slice(1), {
218
+ cwd,
219
+ detached: true,
220
+ stdio: ['ignore', fd, fd],
221
+ env: safe?.env ?? given,
222
+ });
223
+ let spawnError = null;
224
+ child.on('error', error => {
225
+ spawnError = error;
226
+ });
227
+ if (options.once)
228
+ return runToEnd(options, child, fd, log, display, cwd);
229
+ child.unref();
230
+ fs.closeSync(fd);
231
+ await sleep(300);
232
+ if (spawnError || !child.pid)
233
+ fail(`could not start ${name}: ${spawnError?.message ?? 'no pid'}`);
234
+ const record = {
235
+ name,
236
+ pid: child.pid,
237
+ command: display,
238
+ cwd,
239
+ restart: options.restart,
240
+ port: options.port ?? null,
241
+ ...(options.bind && { bind: options.bind }),
242
+ ...(options.env && { env: options.env }),
243
+ onStop: options.onStop ?? [],
244
+ log,
245
+ startedAt: new Date().toISOString(),
246
+ psCommand: psCommand(child.pid),
247
+ psStart: psStart(child.pid),
248
+ start: { ...options, cwd },
249
+ };
250
+ writeJson(recordPath(name), record);
251
+ await sleep(700);
252
+ if (!alive(record))
253
+ fail(`${name} exited right away; last lines of ${log}:\n${tailOf(log, 15)}`);
254
+ if (record.port)
255
+ await waitForPort(record, record.port);
256
+ if (record.port && options.ready)
257
+ await waitUntilReady(record, record.port, options.ready);
258
+ const answering = record.port ? `, answering on port ${String(record.port)}` : '';
259
+ console.log(`started ${name} (pid ${String(record.pid)})${answering}, log ${log}`);
260
+ return record;
261
+ }
262
+ /** Waits for a step that runs to completion; it is a running process meanwhile, so status, wait and stop see it. */
263
+ async function runToEnd(options, child, fd, log, display, cwd) {
264
+ const { name } = options;
265
+ const ended = new Promise(resolve => {
266
+ child.once('exit', code => {
267
+ resolve(code);
268
+ });
269
+ child.once('error', () => {
270
+ resolve(null);
271
+ });
272
+ });
273
+ fs.closeSync(fd);
274
+ const record = {
275
+ name,
276
+ pid: child.pid ?? 0,
277
+ command: display,
278
+ cwd,
279
+ restart: false,
280
+ port: null,
281
+ ...(options.env && { env: options.env }),
282
+ onStop: [],
283
+ log,
284
+ startedAt: new Date().toISOString(),
285
+ psCommand: child.pid ? psCommand(child.pid) : '',
286
+ psStart: child.pid ? psStart(child.pid) : '',
287
+ };
288
+ if (child.pid)
289
+ writeJson(recordPath(name), record);
290
+ const expected = options.once?.exitCode ?? 0;
291
+ const seconds = options.once?.timeout ?? 600;
292
+ let timer;
293
+ const late = new Promise(resolve => {
294
+ timer = setTimeout(() => {
295
+ resolve('late');
296
+ }, seconds * 1000);
297
+ });
298
+ const code = await Promise.race([ended, late]);
299
+ clearTimeout(timer);
300
+ if (code === 'late') {
301
+ await stopOne(record);
302
+ fail(`${name} did not finish within ${String(seconds)}s; last lines of ${log}:\n${tailOf(log, 15)}`);
303
+ }
304
+ fs.rmSync(recordPath(name), { force: true });
305
+ if (code !== expected)
306
+ fail(`${name} ended with exit code ${String(code)}, not ${String(expected)}; last lines of ${log}:\n${tailOf(log, 15)}`);
307
+ console.log(`finished ${name} (exit code ${String(code)}), log ${log}`);
308
+ return record;
309
+ }
310
+ const runAll = (commands) => {
311
+ for (const [command, ...args] of commands)
312
+ if (command)
313
+ sh(command, args);
314
+ };
315
+ /** Stops one process and everything it started, and says in a line what happened. */
316
+ export async function stopOne(record) {
317
+ const forget = () => {
318
+ // Some apps outlive their process, e.g. a mobile app on a simulator: close them too.
319
+ runAll(record.onStop);
320
+ fs.rmSync(recordPath(record.name), { force: true });
321
+ };
322
+ if (!alive(record)) {
323
+ forget();
324
+ return `${record.name}: was not running`;
325
+ }
326
+ const signal = (name) => {
327
+ try {
328
+ process.kill(-record.pid, name);
329
+ return true;
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ };
335
+ // Every process of the app counts, not only the one Koppler started: a worker that outlives it keeps holding its
336
+ // port.
337
+ const running = () => signal(0);
338
+ signal('SIGTERM');
339
+ for (let tenths = 0; tenths < 80 && running(); tenths++)
340
+ await sleep(100);
341
+ const forced = running();
342
+ if (forced) {
343
+ signal('SIGKILL');
344
+ for (let tenths = 0; tenths < 20 && running(); tenths++)
345
+ await sleep(100);
346
+ }
347
+ forget();
348
+ return `${record.name}: stopped${forced ? ' (killed after 8s)' : ''}`;
349
+ }
350
+ //# sourceMappingURL=processes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processes.js","sourceRoot":"","sources":["../../src/engine/processes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,241 @@
1
+ // A read-only database query with an app's stored connection string, for checking data (e.g. after a PROD fix) without
2
+ // writing a script. The value never shows; the session and the transaction are read-only; one statement at a time.
3
+ // The rows are real data: on PROD the user runs it in their own terminal, never the agent.
4
+ // Usage: koppler query <app> [--env <env>] [--secret <NAME>] "<sql>"
5
+ import { execFile, execFileSync } from 'node:child_process';
6
+ import path from 'node:path';
7
+ import { flagValue } from "../cli/output.js";
8
+ import { fail } from "../failure.js";
9
+ import { readStack } from "../data.js";
10
+ import { BIN } from "../paths.js";
11
+ import { clientEnv, findClient } from "../safe-env.js";
12
+ import { parse } from "../secret-check.mjs";
13
+ import { appOn, storeOf } from "../core/stack.js";
14
+ const DATABASES = ['postgres', 'mysql'];
15
+ const MAX_LINES = 500;
16
+ const REFUSED = /password authentication failed|access denied|WRONGPASS/i;
17
+ const UNREACHED = /connection refused|could not connect|can't connect|timeout|timed out|could not translate|unknown (mysql )?server host|ENOTFOUND/i;
18
+ const reason = (text) => {
19
+ if (REFUSED.test(text))
20
+ return 'it refused the user name or password';
21
+ if (UNREACHED.test(text))
22
+ return 'nothing answers at its address (is the tunnel up?)';
23
+ return /read-only|read only/i.test(text) ? 'that statement writes, and this connection is read-only' : null;
24
+ };
25
+ // What only reads. The read-only transaction is a second line of defence: the statement itself must start with a
26
+ // reading keyword and hold nothing that writes, ends the transaction, changes the session or the server, or runs a
27
+ // client command. Words inside strings, quoted names and comments do not count.
28
+ const READS = new Set(['select', 'with', 'explain', 'show', 'values', 'table', 'describe', 'desc']);
29
+ const WRITES = new Set(('insert update delete merge upsert replace drop alter create truncate rename grant revoke ' +
30
+ 'copy call do exec execute prepare deallocate set reset begin start commit rollback end abort savepoint ' +
31
+ 'release lock unlock vacuum analyze analyse cluster reindex refresh listen notify unlisten load handler ' +
32
+ 'into outfile dumpfile checkpoint discard security import kill shutdown flush install uninstall optimize repair').split(' '));
33
+ // Functions that change the server or reach outside it even in a read-only transaction.
34
+ const SIDE_EFFECTS = /^(dblink\w*|pg_terminate_backend|pg_cancel_backend|pg_reload_conf|pg_rotate_logfile|pg_advisory\w*|pg_try_advisory\w*|lo_\w+|set_config|nextval|setval|pg_read\w*file|pg_ls_dir|pg_stat_reset\w*|pg_switch_wal|pg_create_\w+|pg_drop_\w+|pg_sleep\w*|txid_current|sleep|benchmark|load_file|get_lock|release_lock|release_all_locks|sys_exec|sys_eval)$/i;
35
+ /** The statement's own text, with strings, quoted names and comments taken out, in the database's own quoting rules. */
36
+ export function bareSql(sql, type) {
37
+ const mysql = type === 'mysql';
38
+ let out = '';
39
+ let at = 0;
40
+ const here = (text) => sql.startsWith(text, at);
41
+ while (at < sql.length) {
42
+ const char = sql[at] ?? '';
43
+ if (here('--') || (mysql && char === '#')) {
44
+ const newline = sql.indexOf('\n', at);
45
+ at = newline < 0 ? sql.length : newline;
46
+ out += ' ';
47
+ continue;
48
+ }
49
+ if (here('/*')) {
50
+ // Postgres nests block comments; MySQL runs /*! ... */ as code, so it is refused.
51
+ if (mysql && here('/*!'))
52
+ return { error: 'MySQL executable comments (/*! */) are not allowed' };
53
+ let depth = 1;
54
+ at += 2;
55
+ while (at < sql.length && depth) {
56
+ if (!mysql && here('/*')) {
57
+ depth++;
58
+ at += 2;
59
+ }
60
+ else if (here('*/')) {
61
+ depth--;
62
+ at += 2;
63
+ }
64
+ else
65
+ at++;
66
+ }
67
+ if (depth)
68
+ return { error: 'a comment is not closed' };
69
+ out += ' ';
70
+ continue;
71
+ }
72
+ const dollar = !mysql && char === '$' ? /^\$([A-Za-z_]\w*)?\$/.exec(sql.slice(at))?.[0] : undefined;
73
+ if (dollar) {
74
+ const end = sql.indexOf(dollar, at + dollar.length);
75
+ if (end < 0)
76
+ return { error: 'a $$ string is not closed' };
77
+ at = end + dollar.length;
78
+ out += " '' ";
79
+ continue;
80
+ }
81
+ if (char === "'" || char === '"' || (mysql && char === '`')) {
82
+ // MySQL strings take backslash escapes; Postgres only in E'...' strings.
83
+ const before = sql.slice(0, at);
84
+ const escapes = mysql ? char !== '`' : char === "'" && /[eE]$/.test(before) && !/\w[eE]$/.test(before);
85
+ let end = at + 1;
86
+ for (; end < sql.length; end++) {
87
+ if (escapes && sql[end] === '\\')
88
+ end++;
89
+ else if (sql[end] === char) {
90
+ if (sql[end + 1] !== char)
91
+ break;
92
+ end++;
93
+ }
94
+ }
95
+ if (end >= sql.length)
96
+ return { error: 'a quoted string or name is not closed' };
97
+ at = end + 1;
98
+ out += char === "'" ? " '' " : ' "q" ';
99
+ continue;
100
+ }
101
+ out += char;
102
+ at++;
103
+ }
104
+ return { bare: out };
105
+ }
106
+ /** One statement that only reads, without the trailing semicolon; or why not. */
107
+ export function readOnlySql(sql, type) {
108
+ const statement = (sql ?? '').trim().replace(/;\s*$/, '');
109
+ if (!statement)
110
+ return { error: 'give the SQL to run, in quotes' };
111
+ const stripped = bareSql(statement, type);
112
+ if (stripped.error !== undefined)
113
+ return { error: stripped.error };
114
+ const { bare } = stripped;
115
+ if (bare.includes(';'))
116
+ return { error: 'one statement at a time' };
117
+ if (bare.includes('\\'))
118
+ return { error: 'database client commands (a backslash) are not allowed' };
119
+ const words = bare.toLowerCase().match(/[a-z_][a-z0-9_$]*/g) ?? [];
120
+ if (!READS.has(words[0] ?? ''))
121
+ return { error: 'only reading statements: SELECT, WITH, EXPLAIN, SHOW, VALUES, TABLE or DESCRIBE' };
122
+ const writes = words.find(word => WRITES.has(word));
123
+ if (writes)
124
+ return {
125
+ error: `"${writes.toUpperCase()}" can change data or the session, so it is not allowed here (quote a column with that name)`,
126
+ };
127
+ const calls = [...bare.matchAll(/([a-z_][\w$]*)\s*\(/gi)]
128
+ .map(match => match[1] ?? '')
129
+ .find(called => SIDE_EFFECTS.test(called));
130
+ if (calls)
131
+ return { error: `${calls}() changes the server or reaches outside it, so it is not allowed here` };
132
+ return { sql: statement };
133
+ }
134
+ /** The client call for a connection; the password goes in the environment, never on the command line. */
135
+ export function clientCall(to, sql) {
136
+ const port = String(to.port);
137
+ if (to.type === 'postgres')
138
+ return {
139
+ bin: ['psql', 'libpq'],
140
+ env: {
141
+ PGPASSWORD: to.password,
142
+ PGCONNECT_TIMEOUT: '10',
143
+ PGOPTIONS: '-c default_transaction_read_only=on -c statement_timeout=30000',
144
+ },
145
+ args: [
146
+ ...['-h', to.host, '-p', port, '-U', to.user, '-d', to.database || 'postgres', '-X', '-v', 'ON_ERROR_STOP=1'],
147
+ ...['-c', `BEGIN TRANSACTION READ ONLY; ${sql}\n; ROLLBACK;`],
148
+ ],
149
+ };
150
+ if (to.type === 'mysql')
151
+ return {
152
+ bin: ['mysql', 'mysql-client'],
153
+ env: { MYSQL_PWD: to.password },
154
+ args: [
155
+ ...['--connect-timeout=10', '-h', to.host, '-P', port, '-u', to.user],
156
+ ...(to.database ? ['-D', to.database] : []),
157
+ ...['--table', '-e', `SET SESSION TRANSACTION READ ONLY; START TRANSACTION READ ONLY; ${sql}\n; ROLLBACK;`],
158
+ ],
159
+ };
160
+ return null;
161
+ }
162
+ /** The rows, or why not; never the value, and of the server's messages only a plain reason or the SQL error. */
163
+ export async function runQuery(to, sql) {
164
+ const call = clientCall(to, sql);
165
+ if (!call)
166
+ return { error: `Koppler cannot query a ${to.type} connection` };
167
+ const client = findClient(...call.bin);
168
+ if (!client)
169
+ return { error: `the ${call.bin[0]} client is not installed (brew install ${call.bin[1]})` };
170
+ const ran = await new Promise(resolve => {
171
+ const options = { env: clientEnv(call.env), timeout: 60000, maxBuffer: 16 * 1024 * 1024 };
172
+ execFile(client, call.args, options, (error, stdout, stderr) => {
173
+ resolve({ failed: !!error, stdout, stderr });
174
+ });
175
+ });
176
+ if (ran.failed) {
177
+ const sqlError = ran.stderr.split('\n').find(line => /ERROR/i.test(line));
178
+ const why = reason(ran.stderr) ?? sqlError?.replace(/^.*?ERROR[: ]*/i, '') ?? 'see the database client';
179
+ return { error: `the query failed: ${why}` };
180
+ }
181
+ const lines = ran.stdout
182
+ .trimEnd()
183
+ .split('\n')
184
+ .filter(line => !/^(BEGIN|ROLLBACK|SET)$/.test(line.trim()));
185
+ return { rows: lines.slice(0, MAX_LINES).join('\n'), more: Math.max(0, lines.length - MAX_LINES) };
186
+ }
187
+ export async function queryCommand(args) {
188
+ const [app = '', sqlArg] = args.filter((arg, at) => !arg.startsWith('--') && !['--env', '--secret'].includes(args[at - 1] ?? ''));
189
+ const stack = readStack() ?? fail('no stack loaded (koppler init)');
190
+ const base = stack.apps[app] ?? fail('usage: koppler query <app> [--env <env>] [--secret <NAME>] "<sql>"');
191
+ const [first] = stack.envs;
192
+ const env = flagValue(args, '--env') ?? first ?? '';
193
+ if (!stack.envs.includes(env))
194
+ fail(`unknown env ${env}; the stack has ${stack.envs.join(', ')}`);
195
+ // Refused before any stored value is read (on PROD each read asks for the Keychain), whichever database it is for.
196
+ const early = DATABASES.map(type => readOnlySql(sqlArg, type));
197
+ const [refused] = early;
198
+ if (refused?.error !== undefined && early.every(checked => checked.error !== undefined))
199
+ fail(refused.error);
200
+ const store = storeOf(stack, app, env);
201
+ const named = flagValue(args, '--secret');
202
+ const name = base.name;
203
+ // The app's database secret: the one named, else the one the stack types as a database, else the only one that reads as one.
204
+ const candidates = (appOn(stack, app, env)?.secrets ?? []).filter(secret => secret.env && (!named || secret.env === named));
205
+ const read = (secret) => {
206
+ try {
207
+ return execFileSync(path.join(BIN, 'secret-store'), ['value', store, secret], {
208
+ encoding: 'utf8',
209
+ stdio: ['ignore', 'pipe', 'ignore'],
210
+ });
211
+ }
212
+ catch {
213
+ return '';
214
+ }
215
+ };
216
+ const typed = (type) => DATABASES.includes(type ?? '');
217
+ let chosen;
218
+ for (const secret of [...candidates.filter(one => typed(one.type)), ...candidates.filter(one => !one.type)]) {
219
+ const value = read(secret.env ?? '');
220
+ if (!value)
221
+ continue;
222
+ const to = parse(value, typed(secret.type) ? secret.type : undefined);
223
+ if (to && typed(to.type ?? undefined)) {
224
+ chosen = { name: secret.env ?? '', to: to };
225
+ break;
226
+ }
227
+ }
228
+ if (!chosen)
229
+ fail(`no stored database connection for ${name} on ${env.toUpperCase()}${named ? ` named ${named}` : ''} (store it first; koppler doctor ${app} --env ${env})`);
230
+ const statement = readOnlySql(sqlArg, chosen.to.type);
231
+ if (statement.error !== undefined)
232
+ fail(statement.error);
233
+ console.error(`${name} on ${env.toUpperCase()}, read-only, with ${chosen.name}:`);
234
+ const result = await runQuery(chosen.to, statement.sql);
235
+ if (result.error !== undefined)
236
+ fail(result.error);
237
+ console.log(result.rows);
238
+ if (result.more)
239
+ console.error(`(${String(result.more)} more lines not shown; add a LIMIT)`);
240
+ }
241
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/engine/query.ts"],"names":[],"mappings":"AAAA,uHAAuH;AACvH,mHAAmH;AACnH,2FAA2F;AAC3F,qEAAqE;AACrE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAmB,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGlD,MAAM,SAAS,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACxC,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,OAAO,GAAG,yDAAyD,CAAC;AAC1E,MAAM,SAAS,GACb,kIAAkI,CAAC;AACrI,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE;IAC9B,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,sCAAsC,CAAC;IACtE,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,oDAAoD,CAAC;IACtF,OAAO,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,yDAAyD,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9G,CAAC,CAAC;AAEF,iHAAiH;AACjH,mHAAmH;AACnH,gFAAgF;AAChF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AACpG,MAAM,MAAM,GAAG,IAAI,GAAG,CACpB,CACE,2FAA2F;IAC3F,yGAAyG;IACzG,yGAAyG;IACzG,gHAAgH,CACjH,CAAC,KAAK,CAAC,GAAG,CAAC,CACb,CAAC;AACF,wFAAwF;AACxF,MAAM,YAAY,GAChB,0VAA0V,CAAC;AAE7V,wHAAwH;AACxH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,IAAa;IAChD,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxD,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACtC,EAAE,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YACxC,GAAG,IAAI,GAAG,CAAC;YACX,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACf,kFAAkF;YAClF,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,oDAAoD,EAAE,CAAC;YACjG,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,EAAE,IAAI,CAAC,CAAC;YACR,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;gBAChC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,KAAK,EAAE,CAAC;oBACR,EAAE,IAAI,CAAC,CAAC;gBACV,CAAC;qBAAM,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtB,KAAK,EAAE,CAAC;oBACR,EAAE,IAAI,CAAC,CAAC;gBACV,CAAC;;oBAAM,EAAE,EAAE,CAAC;YACd,CAAC;YACD,IAAI,KAAK;gBAAE,OAAO,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;YACvD,GAAG,IAAI,GAAG,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACpG,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YACpD,IAAI,GAAG,GAAG,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC;YAC3D,EAAE,GAAG,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;YACzB,GAAG,IAAI,MAAM,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC5D,yEAAyE;YACzE,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvG,IAAI,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;YACjB,OAAO,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;gBAC/B,IAAI,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI;oBAAE,GAAG,EAAE,CAAC;qBACnC,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;oBAC3B,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI;wBAAE,MAAM;oBACjC,GAAG,EAAE,CAAC;gBACR,CAAC;YACH,CAAC;YACD,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM;gBAAE,OAAO,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC;YACjF,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;YACb,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YACvC,SAAS;QACX,CAAC;QACD,GAAG,IAAI,IAAI,CAAC;QACZ,EAAE,EAAE,CAAC;IACP,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,GAA8B,EAAE,IAAa;IACvE,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;IACnE,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnE,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC1B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;IACpE,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,wDAAwD,EAAE,CAAC;IACpG,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC;IACnE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,EAAE,iFAAiF,EAAE,CAAC;IACtG,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,IAAI,MAAM;QACR,OAAO;YACL,KAAK,EAAE,IAAI,MAAM,CAAC,WAAW,EAAE,6FAA6F;SAC7H,CAAC;IACJ,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC;SACtD,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SAC5B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7C,IAAI,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,wEAAwE,EAAE,CAAC;IAC9G,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AAC5B,CAAC;AAQD,yGAAyG;AACzG,MAAM,UAAU,UAAU,CAAC,EAAc,EAAE,GAAW;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,EAAE,CAAC,IAAI,KAAK,UAAU;QACxB,OAAO;YACL,GAAG,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YACtB,GAAG,EAAE;gBACH,UAAU,EAAE,EAAE,CAAC,QAAQ;gBACvB,iBAAiB,EAAE,IAAI;gBACvB,SAAS,EAAE,gEAAgE;aAC5E;YACD,IAAI,EAAE;gBACJ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,CAAC;gBAC7G,GAAG,CAAC,IAAI,EAAE,gCAAgC,GAAG,eAAe,CAAC;aAC9D;SACF,CAAC;IACJ,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO;QACrB,OAAO;YACL,GAAG,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC;YAC9B,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,QAAQ,EAAE;YAC/B,IAAI,EAAE;gBACJ,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;gBACrE,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3C,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,mEAAmE,GAAG,eAAe,CAAC;aAC5G;SACF,CAAC;IACJ,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gHAAgH;AAChH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,EAAc,EAAE,GAAW;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,IAAI,aAAa,EAAE,CAAC;IAC5E,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,0CAA0C,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAC1G,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAsD,OAAO,CAAC,EAAE;QAC3F,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC1F,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YAC7D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ,EAAE,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,IAAI,yBAAyB,CAAC;QACxG,OAAO,EAAE,KAAK,EAAE,qBAAqB,GAAG,EAAE,EAAE,CAAC;IAC/C,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM;SACrB,OAAO,EAAE;SACT,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;AACrG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAc;IAC/C,MAAM,CAAC,GAAG,GAAG,EAAE,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CACpC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAC1F,CAAC;IACF,MAAM,KAAK,GAAG,SAAS,EAAE,IAAI,IAAI,CAAC,gCAAgC,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,oEAAoE,CAAC,CAAC;IAC3G,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC;IAC3B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;IACpD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,IAAI,CAAC,eAAe,GAAG,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClG,mHAAmH;IACnH,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;IACxB,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC;QAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7G,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,6HAA6H;IAC7H,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAC/D,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,CACzD,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE;gBAC5E,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;aACpC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,IAAwB,EAAkB,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC3F,IAAI,MAAoD,CAAC;IACzD,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5G,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtE,IAAI,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,IAAI,SAAS,CAAC,EAAE,CAAC;YACtC,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,EAAgB,EAAE,CAAC;YAC1D,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM;QACT,IAAI,CACF,qCAAqC,IAAI,OAAO,GAAG,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,oCAAoC,GAAG,UAAU,GAAG,GAAG,CAC1J,CAAC;IACJ,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,WAAW,EAAE,qBAAqB,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IAClF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,MAAM,CAAC,IAAI;QAAE,OAAO,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;AAC/F,CAAC"}