@cicore/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ci.js +13 -0
- package/dist/commands/addon/api-actions.d.ts +45 -0
- package/dist/commands/addon/api-actions.d.ts.map +1 -0
- package/dist/commands/addon/api-actions.js +281 -0
- package/dist/commands/addon/api-actions.js.map +1 -0
- package/dist/commands/addon/build.d.ts +11 -0
- package/dist/commands/addon/build.d.ts.map +1 -0
- package/dist/commands/addon/build.js +182 -0
- package/dist/commands/addon/build.js.map +1 -0
- package/dist/commands/addon/create.d.ts +11 -0
- package/dist/commands/addon/create.d.ts.map +1 -0
- package/dist/commands/addon/create.js +1186 -0
- package/dist/commands/addon/create.js.map +1 -0
- package/dist/commands/addon/delete.d.ts +13 -0
- package/dist/commands/addon/delete.d.ts.map +1 -0
- package/dist/commands/addon/delete.js +83 -0
- package/dist/commands/addon/delete.js.map +1 -0
- package/dist/commands/addon/deploy.d.ts +27 -0
- package/dist/commands/addon/deploy.d.ts.map +1 -0
- package/dist/commands/addon/deploy.js +459 -0
- package/dist/commands/addon/deploy.js.map +1 -0
- package/dist/commands/addon/dev-deploy.d.ts +31 -0
- package/dist/commands/addon/dev-deploy.d.ts.map +1 -0
- package/dist/commands/addon/dev-deploy.js +128 -0
- package/dist/commands/addon/dev-deploy.js.map +1 -0
- package/dist/commands/addon/dev.d.ts +36 -0
- package/dist/commands/addon/dev.d.ts.map +1 -0
- package/dist/commands/addon/dev.js +323 -0
- package/dist/commands/addon/dev.js.map +1 -0
- package/dist/commands/addon/extract-classes.d.ts +23 -0
- package/dist/commands/addon/extract-classes.d.ts.map +1 -0
- package/dist/commands/addon/extract-classes.js +281 -0
- package/dist/commands/addon/extract-classes.js.map +1 -0
- package/dist/commands/addon/generate-safelist.d.ts +24 -0
- package/dist/commands/addon/generate-safelist.d.ts.map +1 -0
- package/dist/commands/addon/generate-safelist.js +276 -0
- package/dist/commands/addon/generate-safelist.js.map +1 -0
- package/dist/commands/addon/index.d.ts +19 -0
- package/dist/commands/addon/index.d.ts.map +1 -0
- package/dist/commands/addon/index.js +296 -0
- package/dist/commands/addon/index.js.map +1 -0
- package/dist/commands/addon/init-repo.d.ts +25 -0
- package/dist/commands/addon/init-repo.d.ts.map +1 -0
- package/dist/commands/addon/init-repo.js +171 -0
- package/dist/commands/addon/init-repo.js.map +1 -0
- package/dist/commands/addon/install.d.ts +23 -0
- package/dist/commands/addon/install.d.ts.map +1 -0
- package/dist/commands/addon/install.js +84 -0
- package/dist/commands/addon/install.js.map +1 -0
- package/dist/commands/addon/list.d.ts +10 -0
- package/dist/commands/addon/list.d.ts.map +1 -0
- package/dist/commands/addon/list.js +102 -0
- package/dist/commands/addon/list.js.map +1 -0
- package/dist/commands/addon/manifest-refresh.d.ts +17 -0
- package/dist/commands/addon/manifest-refresh.d.ts.map +1 -0
- package/dist/commands/addon/manifest-refresh.js +48 -0
- package/dist/commands/addon/manifest-refresh.js.map +1 -0
- package/dist/commands/addon/migrate.d.ts +40 -0
- package/dist/commands/addon/migrate.d.ts.map +1 -0
- package/dist/commands/addon/migrate.js +236 -0
- package/dist/commands/addon/migrate.js.map +1 -0
- package/dist/commands/addon/publish.d.ts +33 -0
- package/dist/commands/addon/publish.d.ts.map +1 -0
- package/dist/commands/addon/publish.js +236 -0
- package/dist/commands/addon/publish.js.map +1 -0
- package/dist/commands/addon/scaffold-quality.d.ts +21 -0
- package/dist/commands/addon/scaffold-quality.d.ts.map +1 -0
- package/dist/commands/addon/scaffold-quality.js +90 -0
- package/dist/commands/addon/scaffold-quality.js.map +1 -0
- package/dist/commands/addon/sign.d.ts +9 -0
- package/dist/commands/addon/sign.d.ts.map +1 -0
- package/dist/commands/addon/sign.js +83 -0
- package/dist/commands/addon/sign.js.map +1 -0
- package/dist/commands/addon/toggle.d.ts +6 -0
- package/dist/commands/addon/toggle.d.ts.map +1 -0
- package/dist/commands/addon/toggle.js +46 -0
- package/dist/commands/addon/toggle.js.map +1 -0
- package/dist/commands/agent/index.d.ts +34 -0
- package/dist/commands/agent/index.d.ts.map +1 -0
- package/dist/commands/agent/index.js +564 -0
- package/dist/commands/agent/index.js.map +1 -0
- package/dist/commands/brand/index.d.ts +54 -0
- package/dist/commands/brand/index.d.ts.map +1 -0
- package/dist/commands/brand/index.js +367 -0
- package/dist/commands/brand/index.js.map +1 -0
- package/dist/commands/build/index.d.ts +53 -0
- package/dist/commands/build/index.d.ts.map +1 -0
- package/dist/commands/build/index.js +726 -0
- package/dist/commands/build/index.js.map +1 -0
- package/dist/commands/cache/flush-local.d.ts +31 -0
- package/dist/commands/cache/flush-local.d.ts.map +1 -0
- package/dist/commands/cache/flush-local.js +161 -0
- package/dist/commands/cache/flush-local.js.map +1 -0
- package/dist/commands/cache/index.d.ts +14 -0
- package/dist/commands/cache/index.d.ts.map +1 -0
- package/dist/commands/cache/index.js +453 -0
- package/dist/commands/cache/index.js.map +1 -0
- package/dist/commands/check/index.d.ts +8 -0
- package/dist/commands/check/index.d.ts.map +1 -0
- package/dist/commands/check/index.js +1316 -0
- package/dist/commands/check/index.js.map +1 -0
- package/dist/commands/cloudflare/index.d.ts +8 -0
- package/dist/commands/cloudflare/index.d.ts.map +1 -0
- package/dist/commands/cloudflare/index.js +453 -0
- package/dist/commands/cloudflare/index.js.map +1 -0
- package/dist/commands/core/create.d.ts +12 -0
- package/dist/commands/core/create.d.ts.map +1 -0
- package/dist/commands/core/create.js +206 -0
- package/dist/commands/core/create.js.map +1 -0
- package/dist/commands/core/delete.d.ts +11 -0
- package/dist/commands/core/delete.d.ts.map +1 -0
- package/dist/commands/core/delete.js +64 -0
- package/dist/commands/core/delete.js.map +1 -0
- package/dist/commands/core/env.d.ts +12 -0
- package/dist/commands/core/env.d.ts.map +1 -0
- package/dist/commands/core/env.js +95 -0
- package/dist/commands/core/env.js.map +1 -0
- package/dist/commands/core/health.d.ts +6 -0
- package/dist/commands/core/health.d.ts.map +1 -0
- package/dist/commands/core/health.js +215 -0
- package/dist/commands/core/health.js.map +1 -0
- package/dist/commands/core/index.d.ts +15 -0
- package/dist/commands/core/index.d.ts.map +1 -0
- package/dist/commands/core/index.js +86 -0
- package/dist/commands/core/index.js.map +1 -0
- package/dist/commands/core/list.d.ts +11 -0
- package/dist/commands/core/list.d.ts.map +1 -0
- package/dist/commands/core/list.js +58 -0
- package/dist/commands/core/list.js.map +1 -0
- package/dist/commands/core/rebuild.d.ts +13 -0
- package/dist/commands/core/rebuild.d.ts.map +1 -0
- package/dist/commands/core/rebuild.js +119 -0
- package/dist/commands/core/rebuild.js.map +1 -0
- package/dist/commands/db/index.d.ts +23 -0
- package/dist/commands/db/index.d.ts.map +1 -0
- package/dist/commands/db/index.js +355 -0
- package/dist/commands/db/index.js.map +1 -0
- package/dist/commands/db/promote-silo.d.ts +320 -0
- package/dist/commands/db/promote-silo.d.ts.map +1 -0
- package/dist/commands/db/promote-silo.js +930 -0
- package/dist/commands/db/promote-silo.js.map +1 -0
- package/dist/commands/db/relocate.d.ts +41 -0
- package/dist/commands/db/relocate.d.ts.map +1 -0
- package/dist/commands/db/relocate.js +482 -0
- package/dist/commands/db/relocate.js.map +1 -0
- package/dist/commands/db/rollback-silo.d.ts +44 -0
- package/dist/commands/db/rollback-silo.d.ts.map +1 -0
- package/dist/commands/db/rollback-silo.js +402 -0
- package/dist/commands/db/rollback-silo.js.map +1 -0
- package/dist/commands/deploy/index.d.ts +26 -0
- package/dist/commands/deploy/index.d.ts.map +1 -0
- package/dist/commands/deploy/index.js +107 -0
- package/dist/commands/deploy/index.js.map +1 -0
- package/dist/commands/devops/index.d.ts +6 -0
- package/dist/commands/devops/index.d.ts.map +1 -0
- package/dist/commands/devops/index.js +220 -0
- package/dist/commands/devops/index.js.map +1 -0
- package/dist/commands/domain/index.d.ts +8 -0
- package/dist/commands/domain/index.d.ts.map +1 -0
- package/dist/commands/domain/index.js +386 -0
- package/dist/commands/domain/index.js.map +1 -0
- package/dist/commands/image/index.d.ts +8 -0
- package/dist/commands/image/index.d.ts.map +1 -0
- package/dist/commands/image/index.js +308 -0
- package/dist/commands/image/index.js.map +1 -0
- package/dist/commands/install/factory-reset.d.ts +21 -0
- package/dist/commands/install/factory-reset.d.ts.map +1 -0
- package/dist/commands/install/factory-reset.js +83 -0
- package/dist/commands/install/factory-reset.js.map +1 -0
- package/dist/commands/install/index.d.ts +17 -0
- package/dist/commands/install/index.d.ts.map +1 -0
- package/dist/commands/install/index.js +44 -0
- package/dist/commands/install/index.js.map +1 -0
- package/dist/commands/install/install.d.ts +35 -0
- package/dist/commands/install/install.d.ts.map +1 -0
- package/dist/commands/install/install.js +171 -0
- package/dist/commands/install/install.js.map +1 -0
- package/dist/commands/login/index.d.ts +15 -0
- package/dist/commands/login/index.d.ts.map +1 -0
- package/dist/commands/login/index.js +58 -0
- package/dist/commands/login/index.js.map +1 -0
- package/dist/commands/nginx/index.d.ts +11 -0
- package/dist/commands/nginx/index.d.ts.map +1 -0
- package/dist/commands/nginx/index.js +580 -0
- package/dist/commands/nginx/index.js.map +1 -0
- package/dist/commands/server/bootstrap.d.ts +25 -0
- package/dist/commands/server/bootstrap.d.ts.map +1 -0
- package/dist/commands/server/bootstrap.js +260 -0
- package/dist/commands/server/bootstrap.js.map +1 -0
- package/dist/commands/server/index.d.ts +8 -0
- package/dist/commands/server/index.d.ts.map +1 -0
- package/dist/commands/server/index.js +2524 -0
- package/dist/commands/server/index.js.map +1 -0
- package/dist/commands/setup/index.d.ts +34 -0
- package/dist/commands/setup/index.d.ts.map +1 -0
- package/dist/commands/setup/index.js +423 -0
- package/dist/commands/setup/index.js.map +1 -0
- package/dist/commands/ssl/index.d.ts +8 -0
- package/dist/commands/ssl/index.d.ts.map +1 -0
- package/dist/commands/ssl/index.js +275 -0
- package/dist/commands/ssl/index.js.map +1 -0
- package/dist/commands/superadmin/index.d.ts +16 -0
- package/dist/commands/superadmin/index.d.ts.map +1 -0
- package/dist/commands/superadmin/index.js +81 -0
- package/dist/commands/superadmin/index.js.map +1 -0
- package/dist/commands/tenant/index.d.ts +6 -0
- package/dist/commands/tenant/index.d.ts.map +1 -0
- package/dist/commands/tenant/index.js +192 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/addon-sign.d.ts +23 -0
- package/dist/lib/addon-sign.d.ts.map +1 -0
- package/dist/lib/addon-sign.js +39 -0
- package/dist/lib/addon-sign.js.map +1 -0
- package/dist/lib/addon-sign.test.d.ts +2 -0
- package/dist/lib/addon-sign.test.d.ts.map +1 -0
- package/dist/lib/addon-sign.test.js +27 -0
- package/dist/lib/addon-sign.test.js.map +1 -0
- package/dist/lib/cdn.d.ts +25 -0
- package/dist/lib/cdn.d.ts.map +1 -0
- package/dist/lib/cdn.js +131 -0
- package/dist/lib/cdn.js.map +1 -0
- package/dist/lib/cloudflare.d.ts +133 -0
- package/dist/lib/cloudflare.d.ts.map +1 -0
- package/dist/lib/cloudflare.js +435 -0
- package/dist/lib/cloudflare.js.map +1 -0
- package/dist/lib/config.d.ts +96 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +132 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/env.d.ts +8 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +64 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/hosts.d.ts +194 -0
- package/dist/lib/hosts.d.ts.map +1 -0
- package/dist/lib/hosts.js +183 -0
- package/dist/lib/hosts.js.map +1 -0
- package/dist/lib/logger.d.ts +68 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +130 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/nginx-config.d.ts +78 -0
- package/dist/lib/nginx-config.d.ts.map +1 -0
- package/dist/lib/nginx-config.js +736 -0
- package/dist/lib/nginx-config.js.map +1 -0
- package/dist/lib/ops/addon-dev.d.ts +93 -0
- package/dist/lib/ops/addon-dev.d.ts.map +1 -0
- package/dist/lib/ops/addon-dev.js +237 -0
- package/dist/lib/ops/addon-dev.js.map +1 -0
- package/dist/lib/ops/addon-quality.d.ts +38 -0
- package/dist/lib/ops/addon-quality.d.ts.map +1 -0
- package/dist/lib/ops/addon-quality.js +338 -0
- package/dist/lib/ops/addon-quality.js.map +1 -0
- package/dist/lib/ops/addon-routes.d.ts +49 -0
- package/dist/lib/ops/addon-routes.d.ts.map +1 -0
- package/dist/lib/ops/addon-routes.js +189 -0
- package/dist/lib/ops/addon-routes.js.map +1 -0
- package/dist/lib/ops/addon.d.ts +120 -0
- package/dist/lib/ops/addon.d.ts.map +1 -0
- package/dist/lib/ops/addon.js +260 -0
- package/dist/lib/ops/addon.js.map +1 -0
- package/dist/lib/ops/cdn.d.ts +87 -0
- package/dist/lib/ops/cdn.d.ts.map +1 -0
- package/dist/lib/ops/cdn.js +170 -0
- package/dist/lib/ops/cdn.js.map +1 -0
- package/dist/lib/ops/cf.d.ts +36 -0
- package/dist/lib/ops/cf.d.ts.map +1 -0
- package/dist/lib/ops/cf.js +114 -0
- package/dist/lib/ops/cf.js.map +1 -0
- package/dist/lib/ops/compose.d.ts +95 -0
- package/dist/lib/ops/compose.d.ts.map +1 -0
- package/dist/lib/ops/compose.js +165 -0
- package/dist/lib/ops/compose.js.map +1 -0
- package/dist/lib/ops/core.d.ts +117 -0
- package/dist/lib/ops/core.d.ts.map +1 -0
- package/dist/lib/ops/core.js +322 -0
- package/dist/lib/ops/core.js.map +1 -0
- package/dist/lib/ops/db.d.ts +116 -0
- package/dist/lib/ops/db.d.ts.map +1 -0
- package/dist/lib/ops/db.js +351 -0
- package/dist/lib/ops/db.js.map +1 -0
- package/dist/lib/ops/dns.d.ts +111 -0
- package/dist/lib/ops/dns.d.ts.map +1 -0
- package/dist/lib/ops/dns.js +306 -0
- package/dist/lib/ops/dns.js.map +1 -0
- package/dist/lib/ops/image.d.ts +94 -0
- package/dist/lib/ops/image.d.ts.map +1 -0
- package/dist/lib/ops/image.js +159 -0
- package/dist/lib/ops/image.js.map +1 -0
- package/dist/lib/ops/nginx.d.ts +114 -0
- package/dist/lib/ops/nginx.d.ts.map +1 -0
- package/dist/lib/ops/nginx.js +388 -0
- package/dist/lib/ops/nginx.js.map +1 -0
- package/dist/lib/ops/redis.d.ts +7 -0
- package/dist/lib/ops/redis.d.ts.map +1 -0
- package/dist/lib/ops/redis.js +35 -0
- package/dist/lib/ops/redis.js.map +1 -0
- package/dist/lib/ops/ssh.d.ts +127 -0
- package/dist/lib/ops/ssh.d.ts.map +1 -0
- package/dist/lib/ops/ssh.js +269 -0
- package/dist/lib/ops/ssh.js.map +1 -0
- package/dist/lib/prompts.d.ts +46 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +113 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/sast.d.ts +43 -0
- package/dist/lib/sast.d.ts.map +1 -0
- package/dist/lib/sast.js +79 -0
- package/dist/lib/sast.js.map +1 -0
- package/dist/lib/sast.test.d.ts +2 -0
- package/dist/lib/sast.test.d.ts.map +1 -0
- package/dist/lib/sast.test.js +33 -0
- package/dist/lib/sast.test.js.map +1 -0
- package/dist/lib/shell.d.ts +61 -0
- package/dist/lib/shell.d.ts.map +1 -0
- package/dist/lib/shell.js +183 -0
- package/dist/lib/shell.js.map +1 -0
- package/dist/lib/ssh-config.d.ts +37 -0
- package/dist/lib/ssh-config.d.ts.map +1 -0
- package/dist/lib/ssh-config.js +122 -0
- package/dist/lib/ssh-config.js.map +1 -0
- package/dist/lib/tenant-scope.d.ts +38 -0
- package/dist/lib/tenant-scope.d.ts.map +1 -0
- package/dist/lib/tenant-scope.js +129 -0
- package/dist/lib/tenant-scope.js.map +1 -0
- package/dist/lib/tenant-scope.test.d.ts +2 -0
- package/dist/lib/tenant-scope.test.d.ts.map +1 -0
- package/dist/lib/tenant-scope.test.js +223 -0
- package/dist/lib/tenant-scope.test.js.map +1 -0
- package/package.json +58 -0
- package/templates/bootstrap/.env.template +54 -0
- package/templates/bootstrap/docker-compose.yml +145 -0
- package/templates/vhost.conf.tmpl +446 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — PostgreSQL primitives
|
|
3
|
+
*
|
|
4
|
+
* Host-aware database operations that run psql inside the postgres
|
|
5
|
+
* container on the remote host (`docker exec -i <dbContainerName> psql`).
|
|
6
|
+
* The container exposes psql to localhost only; routing through `docker
|
|
7
|
+
* exec` keeps us from needing inbound 5432 on the server.
|
|
8
|
+
*
|
|
9
|
+
* Authentication uses `cfg.dbAdminUser` + `cfg.dbAdminPassword`. The
|
|
10
|
+
* password is passed via PGPASSWORD env to avoid `-W` interactive prompts
|
|
11
|
+
* and to keep it out of `ps` output (PGPASSWORD is read by libpq and
|
|
12
|
+
* doesn't appear in argv).
|
|
13
|
+
*
|
|
14
|
+
* Idempotency:
|
|
15
|
+
* - `dbCreateIfMissing`: checks pg_database before CREATE
|
|
16
|
+
* - `dbRunMigrations`: uses a `schema_migrations` ledger table inside
|
|
17
|
+
* each database, only applying versions not already recorded
|
|
18
|
+
*
|
|
19
|
+
* Migration files: `*.sql` in a local directory, sorted by filename.
|
|
20
|
+
* Convention `NNN_description.sql` (e.g. `001_create_greetings.sql`).
|
|
21
|
+
* The version key = filename without `.sql`.
|
|
22
|
+
*/
|
|
23
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
24
|
+
import { join as joinPath } from 'node:path';
|
|
25
|
+
import { log } from '../logger.js';
|
|
26
|
+
import { runRemoteScript } from './ssh.js';
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Identifier validation — defense in depth against SQL injection through
|
|
29
|
+
// caller-provided database/user names. CREATE DATABASE doesn't accept
|
|
30
|
+
// parameterized identifiers; only literal interpolation works, so the
|
|
31
|
+
// name MUST be validated before substitution.
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
33
|
+
/** Postgres identifier rules: alpha/underscore, then alpha/digit/underscore, ≤63 chars. */
|
|
34
|
+
const IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/;
|
|
35
|
+
function assertIdent(value, kind) {
|
|
36
|
+
if (!IDENT_RE.test(value)) {
|
|
37
|
+
throw new Error(`Invalid ${kind} identifier '${value}'. ` +
|
|
38
|
+
`Must match /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/ (Postgres unquoted identifier rules).`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
42
|
+
// Existence checks
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Returns true if a database with the given name exists.
|
|
46
|
+
* Uses `pg_database` catalog query — works regardless of search_path.
|
|
47
|
+
*/
|
|
48
|
+
export async function dbExists(cfg, dbName) {
|
|
49
|
+
assertIdent(dbName, 'database');
|
|
50
|
+
const result = await execSql(cfg, 'postgres', `SELECT 1 FROM pg_database WHERE datname='${dbName}';`);
|
|
51
|
+
if (!result.success)
|
|
52
|
+
return false;
|
|
53
|
+
return result.stdout.includes('(1 row)');
|
|
54
|
+
}
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
56
|
+
// Role & Database lifecycle
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Check if a postgres ROLE (login user) already exists.
|
|
60
|
+
* Used by `dbCreateRoleAndDb` for idempotency.
|
|
61
|
+
*/
|
|
62
|
+
export async function roleExists(cfg, role) {
|
|
63
|
+
assertIdent(role, 'role');
|
|
64
|
+
const result = await execSql(cfg, 'postgres', `SELECT 1 FROM pg_roles WHERE rolname='${role}';`);
|
|
65
|
+
if (!result.success)
|
|
66
|
+
return false;
|
|
67
|
+
return result.stdout.includes('(1 row)');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create the database if missing (idempotent). On creation, grants the
|
|
71
|
+
* admin user full privileges — postgres makes the creating role the owner
|
|
72
|
+
* automatically, but the GRANT is belt-and-suspenders against permission
|
|
73
|
+
* drift from later role changes.
|
|
74
|
+
*/
|
|
75
|
+
export async function dbCreateIfMissing(cfg, dbName) {
|
|
76
|
+
assertIdent(dbName, 'database');
|
|
77
|
+
if (await dbExists(cfg, dbName)) {
|
|
78
|
+
log.info(`[db] '${dbName}' already exists on ${cfg.brandName} — skip create`);
|
|
79
|
+
return Object.freeze({ stdout: '', stderr: '', exitCode: 0, success: true });
|
|
80
|
+
}
|
|
81
|
+
log.info(`[db] CREATE DATABASE ${dbName} on ${cfg.brandName}`);
|
|
82
|
+
// CREATE DATABASE can't run inside a transaction block; psql autocommits
|
|
83
|
+
// single statements when no BEGIN is present, so this works as-is.
|
|
84
|
+
const sql = `CREATE DATABASE ${dbName} OWNER ${cfg.dbAdminUser};\n` +
|
|
85
|
+
`GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${cfg.dbAdminUser};`;
|
|
86
|
+
return execSql(cfg, 'postgres', sql);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Per-core role + database provisioning (replaces the shared admin
|
|
90
|
+
* pattern). Each core gets its own postgres LOGIN role with a random
|
|
91
|
+
* password, owning its own database — defense in depth: if one core's
|
|
92
|
+
* .env leaks, the blast radius is one database, not the whole instance.
|
|
93
|
+
*
|
|
94
|
+
* Idempotency:
|
|
95
|
+
* - If the role already exists, its password is updated (ALTER ROLE)
|
|
96
|
+
* so a fresh .env re-write stays in sync with the server.
|
|
97
|
+
* - If the database already exists, ownership is reassigned to the
|
|
98
|
+
* role (REASSIGN OWNED is overkill for first-run; a simpler
|
|
99
|
+
* ALTER DATABASE ... OWNER TO covers it).
|
|
100
|
+
*
|
|
101
|
+
* Both statements run under the admin connection (`cfg.dbAdminUser`)
|
|
102
|
+
* because CREATE ROLE / CREATE DATABASE require it. The single quotes
|
|
103
|
+
* in the password are escaped before substitution; identifiers go
|
|
104
|
+
* through `assertIdent` to forbid SQL-meta characters entirely.
|
|
105
|
+
*
|
|
106
|
+
* Caller responsibility: pass a strong password (≥24 random base64url
|
|
107
|
+
* chars) and a globally-unique role name. The setup orchestrator
|
|
108
|
+
* generates both via Node `crypto.randomBytes`.
|
|
109
|
+
*/
|
|
110
|
+
export async function dbCreateRoleAndDb(cfg, role, password, dbName) {
|
|
111
|
+
assertIdent(role, 'role');
|
|
112
|
+
assertIdent(dbName, 'database');
|
|
113
|
+
if (!password || password.length < 16) {
|
|
114
|
+
throw new Error(`Password too short (got ${password?.length ?? 0} chars, need ≥16)`);
|
|
115
|
+
}
|
|
116
|
+
// Postgres string literal escape: double up single quotes. The role
|
|
117
|
+
// name itself is already validated as a plain identifier, so it's
|
|
118
|
+
// interpolated raw.
|
|
119
|
+
const pwEscaped = password.replace(/'/g, "''");
|
|
120
|
+
log.info(`[db] ensure role+db '${role}'/'${dbName}' on ${cfg.brandName}`);
|
|
121
|
+
// Step 1: ROLE — create with password, or update password if exists.
|
|
122
|
+
// CREATE ROLE inside a DO block lets us branch on existence atomically.
|
|
123
|
+
// CREATEDB: the per-core role must be able to create its own database —
|
|
124
|
+
// the app's installer (POST /setup/install → SetupService::createDatabase)
|
|
125
|
+
// connects AS this role and issues CREATE DATABASE. Without it the install
|
|
126
|
+
// fails "permission denied to create database". Scoped to DB creation only
|
|
127
|
+
// (not superuser), so per-core isolation holds. (Bug surfaced 2026-05-24.)
|
|
128
|
+
const roleSql = `DO $$\n` +
|
|
129
|
+
`BEGIN\n` +
|
|
130
|
+
` IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${role}') THEN\n` +
|
|
131
|
+
` ALTER ROLE ${role} WITH LOGIN CREATEDB PASSWORD '${pwEscaped}';\n` +
|
|
132
|
+
` ELSE\n` +
|
|
133
|
+
` CREATE ROLE ${role} WITH LOGIN CREATEDB PASSWORD '${pwEscaped}';\n` +
|
|
134
|
+
` END IF;\n` +
|
|
135
|
+
`END $$;`;
|
|
136
|
+
const roleResult = await execSql(cfg, 'postgres', roleSql);
|
|
137
|
+
if (!roleResult.success) {
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
stdout: roleResult.stdout,
|
|
140
|
+
stderr: `role create/update failed: ${roleResult.stderr}`,
|
|
141
|
+
exitCode: roleResult.exitCode,
|
|
142
|
+
success: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// Step 2: DATABASE — create owned by role, or reassign ownership if
|
|
146
|
+
// it already exists. CREATE DATABASE can't run in a DO block (not
|
|
147
|
+
// allowed in transactions), so check + branch in two statements.
|
|
148
|
+
if (await dbExists(cfg, dbName)) {
|
|
149
|
+
log.info(`[db] '${dbName}' exists — reassigning owner to '${role}'`);
|
|
150
|
+
const alterSql = `ALTER DATABASE ${dbName} OWNER TO ${role};\n` +
|
|
151
|
+
`GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${role};`;
|
|
152
|
+
return execSql(cfg, 'postgres', alterSql);
|
|
153
|
+
}
|
|
154
|
+
log.info(`[db] CREATE DATABASE ${dbName} OWNER ${role}`);
|
|
155
|
+
const createSql = `CREATE DATABASE ${dbName} OWNER ${role};\n` +
|
|
156
|
+
`GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${role};`;
|
|
157
|
+
return execSql(cfg, 'postgres', createSql);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Apply every `*.sql` file under `migrationsDir` that hasn't already run
|
|
161
|
+
* against `dbName`. Files are applied in filename-sorted order so that the
|
|
162
|
+
* `NNN_description.sql` convention controls ordering.
|
|
163
|
+
*
|
|
164
|
+
* Each migration runs inside an explicit transaction:
|
|
165
|
+
*
|
|
166
|
+
* BEGIN;
|
|
167
|
+
* <file contents>
|
|
168
|
+
* INSERT INTO schema_migrations(version) VALUES ('<version>');
|
|
169
|
+
* COMMIT;
|
|
170
|
+
*
|
|
171
|
+
* On failure the transaction rolls back atomically — partial migrations
|
|
172
|
+
* never leave the schema in a half-applied state. Subsequent migrations
|
|
173
|
+
* are skipped, and {@link MigrationRunResult.success} is false.
|
|
174
|
+
*
|
|
175
|
+
* The ledger table is created on first call (idempotent). Existing rows
|
|
176
|
+
* dictate which versions are skipped on subsequent runs.
|
|
177
|
+
*/
|
|
178
|
+
export async function dbRunMigrations(cfg, dbName, migrationsDir) {
|
|
179
|
+
assertIdent(dbName, 'database');
|
|
180
|
+
const files = await listMigrationFiles(migrationsDir);
|
|
181
|
+
if (files.length === 0) {
|
|
182
|
+
log.debug(`[db] no migration files in ${migrationsDir}`);
|
|
183
|
+
return Object.freeze({ applied: [], skipped: [], success: true });
|
|
184
|
+
}
|
|
185
|
+
const ledgerSetup = await ensureMigrationsLedger(cfg, dbName);
|
|
186
|
+
if (!ledgerSetup.success) {
|
|
187
|
+
return Object.freeze({
|
|
188
|
+
applied: [],
|
|
189
|
+
skipped: [],
|
|
190
|
+
success: false,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const alreadyApplied = await listAppliedMigrations(cfg, dbName);
|
|
194
|
+
const appliedSet = new Set(alreadyApplied);
|
|
195
|
+
const applied = [];
|
|
196
|
+
const skipped = [];
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
const version = file.version;
|
|
199
|
+
if (appliedSet.has(version)) {
|
|
200
|
+
skipped.push(version);
|
|
201
|
+
log.debug(`[db] migration ${version} already applied — skip`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
log.info(`[db] apply migration ${version} → ${dbName}`);
|
|
205
|
+
const body = await readFile(file.path, 'utf8');
|
|
206
|
+
// Compose a single transactional script: file body + ledger insert.
|
|
207
|
+
// We use dollar-quoted string for the version to avoid escaping issues
|
|
208
|
+
// when the version itself contains single quotes (it won't, but defense).
|
|
209
|
+
const tx = `BEGIN;\n${body}\nINSERT INTO schema_migrations(version) VALUES ($mv$${version}$mv$);\nCOMMIT;\n`;
|
|
210
|
+
const result = await execSql(cfg, dbName, tx);
|
|
211
|
+
applied.push(Object.freeze({
|
|
212
|
+
version,
|
|
213
|
+
success: result.success,
|
|
214
|
+
stderr: result.stderr,
|
|
215
|
+
}));
|
|
216
|
+
if (!result.success) {
|
|
217
|
+
// Stop the chain — earlier migrations may depend on this one.
|
|
218
|
+
return Object.freeze({
|
|
219
|
+
applied: Object.freeze(applied),
|
|
220
|
+
skipped: Object.freeze(skipped),
|
|
221
|
+
success: false,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return Object.freeze({
|
|
226
|
+
applied: Object.freeze(applied),
|
|
227
|
+
skipped: Object.freeze(skipped),
|
|
228
|
+
success: true,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** List `<dir>/*.sql` sorted by filename, paired with the version key. */
|
|
232
|
+
async function listMigrationFiles(dir) {
|
|
233
|
+
const entries = await readdir(dir).catch(() => []);
|
|
234
|
+
const sqlFiles = entries
|
|
235
|
+
.filter(name => name.endsWith('.sql') && !name.startsWith('.'))
|
|
236
|
+
.sort();
|
|
237
|
+
return Object.freeze(sqlFiles.map(name => Object.freeze({
|
|
238
|
+
version: name.slice(0, -'.sql'.length),
|
|
239
|
+
path: joinPath(dir, name),
|
|
240
|
+
})));
|
|
241
|
+
}
|
|
242
|
+
/** Ensure `schema_migrations` ledger table exists (CREATE IF NOT EXISTS). */
|
|
243
|
+
async function ensureMigrationsLedger(cfg, dbName) {
|
|
244
|
+
const sql = `CREATE TABLE IF NOT EXISTS schema_migrations (\n` +
|
|
245
|
+
` version TEXT PRIMARY KEY,\n` +
|
|
246
|
+
` applied_at TIMESTAMPTZ NOT NULL DEFAULT now()\n` +
|
|
247
|
+
`);`;
|
|
248
|
+
return execSql(cfg, dbName, sql);
|
|
249
|
+
}
|
|
250
|
+
/** Return the list of already-applied migration versions, sorted. */
|
|
251
|
+
async function listAppliedMigrations(cfg, dbName) {
|
|
252
|
+
// -A unaligned, -t tuples-only — strips headers and separators for clean parsing.
|
|
253
|
+
const result = await execSql(cfg, dbName, 'SELECT version FROM schema_migrations ORDER BY version;', {
|
|
254
|
+
psqlFlags: ['-A', '-t'],
|
|
255
|
+
});
|
|
256
|
+
if (!result.success)
|
|
257
|
+
return [];
|
|
258
|
+
return result.stdout
|
|
259
|
+
.split('\n')
|
|
260
|
+
.map(line => line.trim())
|
|
261
|
+
.filter(line => line.length > 0);
|
|
262
|
+
}
|
|
263
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
264
|
+
// Read-only query (control-plane reconcile, status reads)
|
|
265
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
266
|
+
/**
|
|
267
|
+
* Run a read-only SELECT against `dbName` and return rows as parsed objects.
|
|
268
|
+
*
|
|
269
|
+
* The caller's inner query is wrapped in `json_agg` so every row round-trips
|
|
270
|
+
* as one JSON document — jsonb columns (themes), embedded commas, quotes and
|
|
271
|
+
* escapes all survive losslessly, with none of the fragile CSV/line parsing
|
|
272
|
+
* the migration ledger reader can get away with for a single text column.
|
|
273
|
+
*
|
|
274
|
+
* Returns `[]` on an empty result set or a failed/unparseable query (the
|
|
275
|
+
* failure is logged). `innerSelect` must be a bare SELECT with no trailing
|
|
276
|
+
* semicolon (it's interpolated as a subquery).
|
|
277
|
+
*/
|
|
278
|
+
export async function dbSelectJson(cfg, dbName, innerSelect) {
|
|
279
|
+
const sql = `SELECT COALESCE(json_agg(t), '[]'::json) FROM (${innerSelect}) t;`;
|
|
280
|
+
// Read-only: dry-run planının gerçek veriyi okuyabilmesi için global dry-run'da
|
|
281
|
+
// bile koştur (Part A). Aksi halde validate/inventory boş döner → "not found".
|
|
282
|
+
const result = await execSql(cfg, dbName, sql, { psqlFlags: ['-A', '-t'], silent: true, allowInDryRun: true });
|
|
283
|
+
if (!result.success) {
|
|
284
|
+
log.error(`[db] query on ${dbName} failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
const text = result.stdout.trim();
|
|
288
|
+
if (text === '')
|
|
289
|
+
return [];
|
|
290
|
+
try {
|
|
291
|
+
return JSON.parse(text);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
log.error(`[db] could not parse query result: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Run a write statement (UPDATE/INSERT) against `dbName`. Thin wrapper over
|
|
300
|
+
* the internal executor for callers outside this module (e.g. reconcile
|
|
301
|
+
* writing achieved DNS state back to the control-plane). The caller is
|
|
302
|
+
* responsible for validating any interpolated values — there is no parameter
|
|
303
|
+
* binding here; pass only validated identifiers / enum literals.
|
|
304
|
+
*/
|
|
305
|
+
export async function dbExec(cfg, dbName, sql) {
|
|
306
|
+
return execSql(cfg, dbName, sql, { silent: true });
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Execute a SQL script inside the postgres container via `docker exec -i`.
|
|
310
|
+
*
|
|
311
|
+
* The script is piped through stdin (not -c) so multi-statement migrations
|
|
312
|
+
* including BEGIN/COMMIT work as expected. PGPASSWORD is exported in the
|
|
313
|
+
* remote shell before invoking docker exec so libpq picks it up — never
|
|
314
|
+
* appears in argv.
|
|
315
|
+
*/
|
|
316
|
+
async function execSql(cfg, dbName, sql, opts = {}) {
|
|
317
|
+
assertIdent(dbName, 'database');
|
|
318
|
+
// Build psql argv. ON_ERROR_STOP=1 makes psql exit non-zero on the first
|
|
319
|
+
// SQL error inside a script — without it, psql continues and reports
|
|
320
|
+
// success even though individual statements failed.
|
|
321
|
+
const psqlArgs = [
|
|
322
|
+
'-v',
|
|
323
|
+
'ON_ERROR_STOP=1',
|
|
324
|
+
'-U',
|
|
325
|
+
cfg.dbAdminUser,
|
|
326
|
+
'-d',
|
|
327
|
+
dbName,
|
|
328
|
+
...(opts.psqlFlags ?? []),
|
|
329
|
+
];
|
|
330
|
+
const psqlArgline = psqlArgs.map(quoteShellArg).join(' ');
|
|
331
|
+
// The remote shell sees: docker exec -i -e PGPASSWORD=… <container> psql <args>
|
|
332
|
+
// Heredoc carries the SQL. -i keeps stdin attached. -e injects the password
|
|
333
|
+
// into the container env (not just the outer shell), so libpq can read it.
|
|
334
|
+
const script = `docker exec -i -e PGPASSWORD="$PGPASSWORD" ${quoteShellArg(cfg.dbContainerName)} ` +
|
|
335
|
+
`psql ${psqlArgline} <<'__VU_SQL_EOF__'\n` +
|
|
336
|
+
sql +
|
|
337
|
+
`\n__VU_SQL_EOF__\n`;
|
|
338
|
+
return runRemoteScript(cfg, script, {
|
|
339
|
+
env: { PGPASSWORD: cfg.dbAdminPassword },
|
|
340
|
+
silent: opts.silent,
|
|
341
|
+
allowInDryRun: opts.allowInDryRun,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
345
|
+
// Helpers
|
|
346
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
347
|
+
/** Single-quote wrap for safe shell interpolation. */
|
|
348
|
+
function quoteShellArg(s) {
|
|
349
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/lib/ops/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,eAAe,EAAmB,MAAM,UAAU,CAAA;AAE3D,4EAA4E;AAC5E,yEAAyE;AACzE,sEAAsE;AACtE,sEAAsE;AACtE,8CAA8C;AAC9C,4EAA4E;AAE5E,2FAA2F;AAC3F,MAAM,QAAQ,GAAG,+BAA+B,CAAA;AAEhD,SAAS,WAAW,CAAC,KAAa,EAAE,IAAY;IAC9C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,WAAW,IAAI,gBAAgB,KAAK,KAAK;YACvC,kFAAkF,CACrF,CAAA;IACH,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,mBAAmB;AACnB,4EAA4E;AAE5E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAe,EAAE,MAAc;IAC5D,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC/B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,4CAA4C,MAAM,IAAI,CAAC,CAAA;IACrG,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IACjC,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;AAC1C,CAAC;AAED,4EAA4E;AAC5E,4BAA4B;AAC5B,4EAA4E;AAE5E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAe,EAAE,IAAY;IAC5D,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,yCAAyC,IAAI,IAAI,CAAC,CAAA;IAChG,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IACjC,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAe,EAAE,MAAc;IACrE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAE/B,IAAI,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,SAAS,MAAM,uBAAuB,GAAG,CAAC,SAAS,gBAAgB,CAAC,CAAA;QAC7E,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9E,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,wBAAwB,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,CAAC,CAAA;IAC9D,yEAAyE;IACzE,mEAAmE;IACnE,MAAM,GAAG,GACP,mBAAmB,MAAM,UAAU,GAAG,CAAC,WAAW,KAAK;QACvD,oCAAoC,MAAM,OAAO,GAAG,CAAC,WAAW,GAAG,CAAA;IACrE,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAe,EACf,IAAY,EACZ,QAAgB,EAChB,MAAc;IAEd,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACzB,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC/B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,MAAM,IAAI,CAAC,mBAAmB,CAAC,CAAA;IACtF,CAAC;IAED,oEAAoE;IACpE,kEAAkE;IAClE,oBAAoB;IACpB,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAE9C,GAAG,CAAC,IAAI,CAAC,wBAAwB,IAAI,MAAM,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,CAAC,CAAA;IAEzE,qEAAqE;IACrE,wEAAwE;IACxE,wEAAwE;IACxE,2EAA2E;IAC3E,2EAA2E;IAC3E,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,OAAO,GACX,SAAS;QACT,SAAS;QACT,wDAAwD,IAAI,WAAW;QACvE,kBAAkB,IAAI,kCAAkC,SAAS,MAAM;QACvE,UAAU;QACV,mBAAmB,IAAI,kCAAkC,SAAS,MAAM;QACxE,aAAa;QACb,SAAS,CAAA;IACX,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IAC1D,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,MAAM,EAAE,8BAA8B,UAAU,CAAC,MAAM,EAAE;YACzD,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;IACJ,CAAC;IAED,oEAAoE;IACpE,kEAAkE;IAClE,iEAAiE;IACjE,IAAI,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,SAAS,MAAM,oCAAoC,IAAI,GAAG,CAAC,CAAA;QACpE,MAAM,QAAQ,GACZ,kBAAkB,MAAM,aAAa,IAAI,KAAK;YAC9C,oCAAoC,MAAM,OAAO,IAAI,GAAG,CAAA;QAC1D,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAA;IAC3C,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,wBAAwB,MAAM,UAAU,IAAI,EAAE,CAAC,CAAA;IACxD,MAAM,SAAS,GACb,mBAAmB,MAAM,UAAU,IAAI,KAAK;QAC5C,oCAAoC,MAAM,OAAO,IAAI,GAAG,CAAA;IAC1D,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,CAAC,CAAA;AAC5C,CAAC;AAkBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAe,EACf,MAAc,EACd,aAAqB;IAErB,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC/B,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,aAAa,CAAC,CAAA;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,GAAG,CAAC,KAAK,CAAC,8BAA8B,aAAa,EAAE,CAAC,CAAA;QACxD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC7D,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC/D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,CAAA;IAE1C,MAAM,OAAO,GAAuB,EAAE,CAAA;IACtC,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC5B,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACrB,GAAG,CAAC,KAAK,CAAC,kBAAkB,OAAO,yBAAyB,CAAC,CAAA;YAC7D,SAAQ;QACV,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,wBAAwB,OAAO,MAAM,MAAM,EAAE,CAAC,CAAA;QACvD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC9C,oEAAoE;QACpE,uEAAuE;QACvE,0EAA0E;QAC1E,MAAM,EAAE,GACN,WAAW,IAAI,wDAAwD,OAAO,mBAAmB,CAAA;QAEnG,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;QAC7C,OAAO,CAAC,IAAI,CACV,MAAM,CAAC,MAAM,CAAC;YACZ,OAAO;YACP,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CACH,CAAA;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,8DAA8D;YAC9D,OAAO,MAAM,CAAC,MAAM,CAAC;gBACnB,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;gBAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;gBAC/B,OAAO,EAAE,KAAK;aACf,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAC/B,OAAO,EAAE,IAAI;KACd,CAAC,CAAA;AACJ,CAAC;AAED,0EAA0E;AAC1E,KAAK,UAAU,kBAAkB,CAC/B,GAAW;IAEX,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAc,CAAC,CAAA;IAC9D,MAAM,QAAQ,GAAG,OAAO;SACrB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;SAC9D,IAAI,EAAE,CAAA;IACT,OAAO,MAAM,CAAC,MAAM,CAClB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAClB,MAAM,CAAC,MAAM,CAAC;QACZ,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;QACtC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;KAC1B,CAAC,CACH,CACF,CAAA;AACH,CAAC;AAED,6EAA6E;AAC7E,KAAK,UAAU,sBAAsB,CAAC,GAAe,EAAE,MAAc;IACnE,MAAM,GAAG,GACP,kDAAkD;QAClD,+BAA+B;QAC/B,mDAAmD;QACnD,IAAI,CAAA;IACN,OAAO,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;AAClC,CAAC;AAED,qEAAqE;AACrE,KAAK,UAAU,qBAAqB,CAAC,GAAe,EAAE,MAAc;IAClE,kFAAkF;IAClF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,yDAAyD,EAAE;QACnG,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;KACxB,CAAC,CAAA;IACF,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IAC9B,OAAO,MAAM,CAAC,MAAM;SACjB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SACxB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACpC,CAAC;AAED,4EAA4E;AAC5E,0DAA0D;AAC1D,4EAA4E;AAE5E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAe,EACf,MAAc,EACd,WAAmB;IAEnB,MAAM,GAAG,GAAG,kDAAkD,WAAW,MAAM,CAAA;IAC/E,gFAAgF;IAChF,+EAA+E;IAC/E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9G,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,GAAG,CAAC,KAAK,CAAC,iBAAiB,MAAM,YAAY,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAC5F,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IACjC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,CAAA;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAQ,CAAA;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,sCAAsC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACnG,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAe,EAAE,MAAc,EAAE,GAAW;IACvE,OAAO,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;AACpD,CAAC;AAaD;;;;;;;GAOG;AACH,KAAK,UAAU,OAAO,CACpB,GAAe,EACf,MAAc,EACd,GAAW,EACX,OAAuB,EAAE;IAEzB,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAE/B,yEAAyE;IACzE,qEAAqE;IACrE,oDAAoD;IACpD,MAAM,QAAQ,GAAG;QACf,IAAI;QACJ,iBAAiB;QACjB,IAAI;QACJ,GAAG,CAAC,WAAW;QACf,IAAI;QACJ,MAAM;QACN,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;KAC1B,CAAA;IACD,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAEzD,gFAAgF;IAChF,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,MAAM,GACV,8CAA8C,aAAa,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG;QACnF,QAAQ,WAAW,uBAAuB;QAC1C,GAAG;QACH,oBAAoB,CAAA;IAEtB,OAAO,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE;QAClC,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,eAAe,EAAE;QACxC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,aAAa,EAAE,IAAI,CAAC,aAAa;KAClC,CAAC,CAAA;AACJ,CAAC;AAED,4EAA4E;AAC5E,UAAU;AACV,4EAA4E;AAE5E,sDAAsD;AACtD,SAAS,aAAa,CAAC,CAAS;IAC9B,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAA;AACxC,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — DNS / TLS provider abstraction
|
|
3
|
+
*
|
|
4
|
+
* Vendor-neutral {@link DnsProvider} interface + a Cloudflare implementation.
|
|
5
|
+
* The interface exists so a second vendor (Route53, …) can slot in later
|
|
6
|
+
* without touching callers (see brand-layer plan §K2 / §8). For now the only
|
|
7
|
+
* implementation is {@link CloudflareDnsProvider}.
|
|
8
|
+
*
|
|
9
|
+
* Two routing modes — both first-class, both modelled by the control plane
|
|
10
|
+
* (`cp_domains.dns_mode`):
|
|
11
|
+
*
|
|
12
|
+
* - `owned_zone` — the hostname lives in a zone *we* manage (e.g.
|
|
13
|
+
* `lab.multinodex.com`). We upsert a proxied CNAME → our origin target;
|
|
14
|
+
* TLS is covered by the zone's Universal / Advanced certificate. The
|
|
15
|
+
* zone id is resolved from the hostname at call time (a single host can
|
|
16
|
+
* serve several owned zones — mkt fronts both multinodex.com and
|
|
17
|
+
* randevu360.net — so we can't rely on `cfg.cfZoneId` alone).
|
|
18
|
+
*
|
|
19
|
+
* - `custom_hostname` (PRIMARY) — a domain the *customer* owns (BYO domain,
|
|
20
|
+
* Shopify/Webflow model). We register it as a Cloudflare for SaaS custom
|
|
21
|
+
* hostname in our SaaS zone (`cfg.cfZoneId`); DV TLS is issued + renewed
|
|
22
|
+
* automatically, and the customer points a CNAME at our fallback origin.
|
|
23
|
+
* This retires the per-host tunnel-ingress + `api-lab` SSL-depth hack:
|
|
24
|
+
* deep subdomains (`api.lab.x`) that Universal SSL's single-level wildcard
|
|
25
|
+
* can't cover get their own per-hostname cert for free.
|
|
26
|
+
*
|
|
27
|
+
* All calls use the host's scoped token `cfg.cfApiToken` (Bearer) — NOT the
|
|
28
|
+
* stored-config token that `lib/cloudflare.ts` reads. Keep new DNS work here,
|
|
29
|
+
* on the host-aware path. Every mutation honours `--dry-run`.
|
|
30
|
+
*/
|
|
31
|
+
import type { HostConfig } from '../hosts.js';
|
|
32
|
+
/** How a hostname is routed + secured. Mirrors `cp_domains.dns_mode`. */
|
|
33
|
+
export type DnsMode = 'owned_zone' | 'custom_hostname';
|
|
34
|
+
/** Routing status, aligned with `cp_domains.routing_status` / `ssl_status`. */
|
|
35
|
+
export type DnsStatus = 'active' | 'pending' | 'error';
|
|
36
|
+
export interface DnsHostRequest {
|
|
37
|
+
/** Fully-qualified hostname to provision (e.g. `lab.multinodex.com`). */
|
|
38
|
+
readonly hostname: string;
|
|
39
|
+
/** Routing mode. Defaults to auto-detection via {@link DnsProvider.detectMode}. */
|
|
40
|
+
readonly mode: DnsMode;
|
|
41
|
+
/**
|
|
42
|
+
* Where traffic should land:
|
|
43
|
+
* - owned_zone: CNAME content (e.g. the host's proxied A-record name or
|
|
44
|
+
* a tunnel CNAME target) the proxied record points at.
|
|
45
|
+
* - custom_hostname: the fallback-origin hostname customers CNAME to;
|
|
46
|
+
* used to surface the CNAME target back to the caller. The custom
|
|
47
|
+
* hostname itself is registered against `cfg.cfZoneId`.
|
|
48
|
+
*/
|
|
49
|
+
readonly originTarget: string;
|
|
50
|
+
}
|
|
51
|
+
/** Resolved state of a provisioned hostname — enough to persist in `cp_domains`. */
|
|
52
|
+
export interface DnsHostState {
|
|
53
|
+
readonly success: boolean;
|
|
54
|
+
readonly hostname: string;
|
|
55
|
+
readonly mode: DnsMode;
|
|
56
|
+
/** Routing reachability. */
|
|
57
|
+
readonly routingStatus: DnsStatus;
|
|
58
|
+
/** TLS issuance/validation status. */
|
|
59
|
+
readonly sslStatus: DnsStatus;
|
|
60
|
+
/** custom_hostname only: CF object id → persist as `cf_custom_hostname_id`. */
|
|
61
|
+
readonly customHostnameId?: string;
|
|
62
|
+
/** Zone the record/hostname lives in → persist as `cf_zone_id`. */
|
|
63
|
+
readonly cfZoneId?: string;
|
|
64
|
+
/** owned_zone: CNAME content. custom_hostname: target the customer must CNAME to. */
|
|
65
|
+
readonly cnameTarget?: string;
|
|
66
|
+
/**
|
|
67
|
+
* custom_hostname only: ownership + DV validation records the customer (or
|
|
68
|
+
* we, for owned validation) must publish → persist as `verification_record`.
|
|
69
|
+
*/
|
|
70
|
+
readonly verification?: unknown;
|
|
71
|
+
readonly errorMessage: string;
|
|
72
|
+
}
|
|
73
|
+
/** Minimal mutation result for teardown / bootstrap ops. */
|
|
74
|
+
export interface DnsResult {
|
|
75
|
+
readonly success: boolean;
|
|
76
|
+
readonly errorMessage: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Vendor-neutral DNS/TLS provisioning surface. A `CloudflareDnsProvider`
|
|
80
|
+
* implements it today; the brand provisioning flow + `ci setup` depend only
|
|
81
|
+
* on this interface.
|
|
82
|
+
*/
|
|
83
|
+
export interface DnsProvider {
|
|
84
|
+
/**
|
|
85
|
+
* Decide whether `hostname` falls under a zone we manage (`owned_zone`) or
|
|
86
|
+
* belongs to a customer (`custom_hostname`). Lets callers omit an explicit
|
|
87
|
+
* mode and "do the right thing" for both our subdomains and BYO domains.
|
|
88
|
+
*/
|
|
89
|
+
detectMode(hostname: string): Promise<DnsMode>;
|
|
90
|
+
/**
|
|
91
|
+
* Make `hostname` reachable + TLS-valid, routing to our origin. Idempotent.
|
|
92
|
+
* For `custom_hostname`, TLS is issued asynchronously by Cloudflare — the
|
|
93
|
+
* returned `sslStatus` may be `pending`; poll {@link getHostState} until it
|
|
94
|
+
* flips to `active` before marking the domain live.
|
|
95
|
+
*/
|
|
96
|
+
ensureHost(req: DnsHostRequest): Promise<DnsHostState>;
|
|
97
|
+
/** Current routing + TLS state. Use to poll a `pending` custom hostname. */
|
|
98
|
+
getHostState(hostname: string, mode: DnsMode): Promise<DnsHostState>;
|
|
99
|
+
/** Remove routing + cert for `hostname` (rollback / brand teardown). Idempotent. */
|
|
100
|
+
removeHost(hostname: string, mode: DnsMode): Promise<DnsResult>;
|
|
101
|
+
/**
|
|
102
|
+
* One-time per-zone bootstrap (custom_hostname mode): designate
|
|
103
|
+
* `originHostname` — a *proxied* record that already exists in the SaaS zone
|
|
104
|
+
* — as the Cloudflare for SaaS fallback origin. Required before any custom
|
|
105
|
+
* hostname can be served. Idempotent.
|
|
106
|
+
*/
|
|
107
|
+
ensureFallbackOrigin(originHostname: string): Promise<DnsResult>;
|
|
108
|
+
}
|
|
109
|
+
/** Construct the configured provider for a host. Cloudflare-only today. */
|
|
110
|
+
export declare function createDnsProvider(cfg: HostConfig): DnsProvider;
|
|
111
|
+
//# sourceMappingURL=dns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/dns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAQ7C,yEAAyE;AACzE,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,iBAAiB,CAAA;AAEtD,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAA;AAEtD,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,4BAA4B;IAC5B,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAA;IACjC,sCAAsC;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAID;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE9C;;;;;OAKG;IACH,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAEtD,4EAA4E;IAC5E,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAEpE,oFAAoF;IACpF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAE/D;;;;;OAKG;IACH,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CACjE;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAE9D"}
|