@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,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — Core skeleton + environment primitives
|
|
3
|
+
*
|
|
4
|
+
* A "core" is one logical SaaS instance inside the multi-core VuCore
|
|
5
|
+
* stack. Each core lives in its own subdirectory under `cfg.coresHostPath`
|
|
6
|
+
* with its own `.env`, addons/, storage/, etc. The PHP and Nuxt containers
|
|
7
|
+
* are shared across cores — runtime routing (nginx $target_core) picks
|
|
8
|
+
* which core handles a given request.
|
|
9
|
+
*
|
|
10
|
+
* This module provides the primitives `vu setup` needs to onboard a new
|
|
11
|
+
* core:
|
|
12
|
+
*
|
|
13
|
+
* - {@link coreEnsureSkeleton}: server-side mkdir of the standard layout
|
|
14
|
+
* - {@link coreEnvGenerate}: build a config object from host + per-core
|
|
15
|
+
* inputs (domain, name, secrets)
|
|
16
|
+
* - {@link coreWriteEnv}: serialize the config and write it atomically
|
|
17
|
+
* to `<coresHostPath>/<core>/.env`
|
|
18
|
+
*
|
|
19
|
+
* Secrets (DB password, JWT) are generated with `crypto.randomBytes` — 32
|
|
20
|
+
* bytes hex, suitable for both. The caller can override via options to
|
|
21
|
+
* support importing an existing core's secrets.
|
|
22
|
+
*/
|
|
23
|
+
import { randomBytes } from 'node:crypto';
|
|
24
|
+
import { log } from '../logger.js';
|
|
25
|
+
import { runRemote, runRemoteScript } from './ssh.js';
|
|
26
|
+
/** Core name validation — same rule as Postgres ident + DB name. */
|
|
27
|
+
const CORE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/;
|
|
28
|
+
function assertCoreName(name) {
|
|
29
|
+
if (!CORE_NAME_RE.test(name)) {
|
|
30
|
+
throw new Error(`Invalid core name '${name}'. Must match /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
34
|
+
// Skeleton (directory layout on the server)
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Standard subdirectory layout under `cores/<name>/`. Each entry gets
|
|
38
|
+
* created with `mkdir -p` so re-running the op is idempotent.
|
|
39
|
+
*
|
|
40
|
+
* `storage/` and its children are writable by the PHP container's
|
|
41
|
+
* www-data (uid 82) — see {@link coreEnsureSkeleton} for the chmod
|
|
42
|
+
* that establishes that.
|
|
43
|
+
*/
|
|
44
|
+
const SKELETON_DIRS = Object.freeze([
|
|
45
|
+
'addons',
|
|
46
|
+
'shared',
|
|
47
|
+
'vendor',
|
|
48
|
+
'storage',
|
|
49
|
+
'storage/cache',
|
|
50
|
+
'storage/sessions',
|
|
51
|
+
'storage/logs',
|
|
52
|
+
'storage/framework/cache',
|
|
53
|
+
]);
|
|
54
|
+
/**
|
|
55
|
+
* Create `cores/<name>/` and its subdirectories on the server if missing.
|
|
56
|
+
* Permissions are set so PHP container's www-data (uid 82) can write into
|
|
57
|
+
* the storage/ tree. Files outside storage/ keep root-owned defaults.
|
|
58
|
+
*
|
|
59
|
+
* Idempotent — safe to call repeatedly. Returns success when every dir
|
|
60
|
+
* exists (whether we created it or it was already there).
|
|
61
|
+
*/
|
|
62
|
+
export async function coreEnsureSkeleton(cfg, coreName) {
|
|
63
|
+
assertCoreName(coreName);
|
|
64
|
+
const root = `${cfg.coresHostPath}/${coreName}`;
|
|
65
|
+
log.info(`[core] ensure skeleton ${root} on ${cfg.brandName}`);
|
|
66
|
+
// Compose a single script: mkdir -p each dir, then chmod the storage tree,
|
|
67
|
+
// then chown the whole core tree to the PHP runtime uid so the container can
|
|
68
|
+
// read/write it regardless of which SSH user the CLI logged in as.
|
|
69
|
+
// `set -e` aborts on first error; we'd rather know immediately than
|
|
70
|
+
// leave a half-built skeleton.
|
|
71
|
+
const mkdirs = SKELETON_DIRS.map(dir => `mkdir -p ${shellQuote(root + '/' + dir)}`).join('\n');
|
|
72
|
+
// Default brand.json so the core renders its own identity out of the box
|
|
73
|
+
// (core-loader → useBrand → VBrandLogo + titleTemplate). Written only if
|
|
74
|
+
// absent — `ci brand reconcile` / the panel overwrite it with richer data
|
|
75
|
+
// (logo, theme) from the control-plane. Makes a fresh `ci setup` reproducible
|
|
76
|
+
// without the hand-written brand.json this used to need.
|
|
77
|
+
const brandJsonPath = root + '/brand.json';
|
|
78
|
+
const brandJson = JSON.stringify({ brand: coreName, title: humanizeBrand(coreName), logoUrl: null, faviconUrl: null, theme: {} }, null, 2);
|
|
79
|
+
const script = `set -e\n` +
|
|
80
|
+
`mkdir -p ${shellQuote(root)}\n` +
|
|
81
|
+
mkdirs +
|
|
82
|
+
`\nchmod -R u+rwX,g+rwX,o+rwX ${shellQuote(root + '/storage')}\n` +
|
|
83
|
+
`if [ ! -f ${shellQuote(brandJsonPath)} ]; then cat > ${shellQuote(brandJsonPath)} <<'__VU_BRAND_EOF__'\n` +
|
|
84
|
+
brandJson + `\n__VU_BRAND_EOF__\nfi\n` +
|
|
85
|
+
chownToRuntimeUid(cfg, root);
|
|
86
|
+
return runRemoteScript(cfg, script, { silent: true });
|
|
87
|
+
}
|
|
88
|
+
/** Humanize a core slug for the default brand.json title. */
|
|
89
|
+
function humanizeBrand(slug) {
|
|
90
|
+
if (slug === 'cicore')
|
|
91
|
+
return 'CiCore';
|
|
92
|
+
if (slug === 'cicore')
|
|
93
|
+
return 'ciCore';
|
|
94
|
+
return slug.charAt(0).toUpperCase() + slug.slice(1);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Shell snippet that chowns `target` (recursively) to the host's PHP runtime
|
|
98
|
+
* uid:gid. Tries direct chown first (works when the SSH user is root or is
|
|
99
|
+
* already that uid — a no-op), falling back to `sudo -n` for non-root SSH
|
|
100
|
+
* users whose uid differs. Decouples file ownership from the SSH login so the
|
|
101
|
+
* PHP/Nuxt container can always read per-core files (notably the mode-600
|
|
102
|
+
* `.env`). Returns a trailing-newline-terminated snippet for concatenation.
|
|
103
|
+
*/
|
|
104
|
+
function chownToRuntimeUid(cfg, target) {
|
|
105
|
+
const owner = `${cfg.phpRuntimeUid}:${cfg.phpRuntimeUid}`;
|
|
106
|
+
const q = shellQuote(target);
|
|
107
|
+
return `chown -R ${owner} ${q} 2>/dev/null || sudo -n chown -R ${owner} ${q}\n`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build the full `.env` config object from the host topology and per-core
|
|
111
|
+
* inputs. Values are simple strings — caller serializes via
|
|
112
|
+
* {@link coreWriteEnv} when ready.
|
|
113
|
+
*
|
|
114
|
+
* Secrets default to fresh 32-byte hex values; pass overrides via
|
|
115
|
+
* `inputs.dbPassword` / `inputs.jwtSecret` when reusing an existing core's
|
|
116
|
+
* credentials (e.g. clone, restore).
|
|
117
|
+
*/
|
|
118
|
+
export function coreEnvGenerate(cfg, coreName, inputs) {
|
|
119
|
+
assertCoreName(coreName);
|
|
120
|
+
const brand = inputs.brand ?? coreName;
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
// Identity
|
|
123
|
+
CORE_NAME: coreName,
|
|
124
|
+
DOMAIN: inputs.domain,
|
|
125
|
+
ADDITIONAL_DOMAINS: inputs.additionalDomains ?? '',
|
|
126
|
+
// Public Nuxt config (visible to browser)
|
|
127
|
+
NUXT_PUBLIC_API_BASE: `https://api.${inputs.domain}`,
|
|
128
|
+
NUXT_PUBLIC_WS_URL: `wss://${inputs.domain}/ws`,
|
|
129
|
+
NUXT_PUBLIC_HOST: inputs.domain,
|
|
130
|
+
NUXT_PUBLIC_CORE_CDN_URL: `${cfg.cdnBaseUrl}/CoreUI/latest`,
|
|
131
|
+
NUXT_PUBLIC_BRAND: brand,
|
|
132
|
+
// Image source — runtime pull reads these for self-update workflows
|
|
133
|
+
DOCKER_REGISTRY: cfg.composeImageName.split('/')[0] ?? 'baynis',
|
|
134
|
+
IMAGE_TAG: 'latest',
|
|
135
|
+
// Database — connection points to the shared postgres container.
|
|
136
|
+
// Each core gets its own LOGIN role + database (dbCreateRoleAndDb)
|
|
137
|
+
// so a leaked .env's blast radius is exactly one core's DB. The
|
|
138
|
+
// role name defaults to `db_<core>_<8hex>` and the password to a
|
|
139
|
+
// 24-byte base64url string; setup overrides both when re-running
|
|
140
|
+
// (idempotency — existing .env wins).
|
|
141
|
+
DB_HOST: cfg.dbContainerName,
|
|
142
|
+
DB_PORT: '5432',
|
|
143
|
+
DB_NAME: `${coreName}_db`,
|
|
144
|
+
DB_USER: inputs.dbUser ?? `db_${coreName}_${randomHex(4)}`,
|
|
145
|
+
DB_PASS: inputs.dbPassword ?? randomHex(32),
|
|
146
|
+
DB_GATEWAY: 'local',
|
|
147
|
+
// Auth secrets — fresh per core (no cross-core token validity).
|
|
148
|
+
JWT_SECRET: inputs.jwtSecret ?? randomHex(32),
|
|
149
|
+
// Misc
|
|
150
|
+
ADDON_DEBUG: 'true',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
154
|
+
// .env serialization + atomic write
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
156
|
+
/**
|
|
157
|
+
* Serialize an env config to dotenv format and write atomically to the
|
|
158
|
+
* server at `cores/<coreName>/.env`.
|
|
159
|
+
*
|
|
160
|
+
* Format: `KEY=value` per line, no quoting (values must be free of
|
|
161
|
+
* newlines and unescaped `"`/`'`). Generated secrets meet this; caller
|
|
162
|
+
* overrides should too.
|
|
163
|
+
*
|
|
164
|
+
* Atomic via temp file + mv inside the same script. If a previous .env
|
|
165
|
+
* exists, it's backed up to `.env.bak-<timestamp>` so a botched setup is
|
|
166
|
+
* recoverable. Idempotent: writing the same content twice is a no-op
|
|
167
|
+
* (transform short-circuits when content matches).
|
|
168
|
+
*/
|
|
169
|
+
export async function coreWriteEnv(cfg, coreName, env) {
|
|
170
|
+
assertCoreName(coreName);
|
|
171
|
+
validateEnvValues(env);
|
|
172
|
+
const path = `${cfg.coresHostPath}/${coreName}/.env`;
|
|
173
|
+
const content = serializeEnv(env);
|
|
174
|
+
log.info(`[core] write .env (${Object.keys(env).length} keys) → ${path}`);
|
|
175
|
+
// Backup-then-replace pattern: timestamp suffix + cmp before overwriting.
|
|
176
|
+
// The `cmp --silent` short-circuits identical writes (skip backup + mv).
|
|
177
|
+
const script = `set -e\n` +
|
|
178
|
+
`tmp="${path}.tmp.$$"\n` +
|
|
179
|
+
`cat > "$tmp" <<'__VU_ENV_EOF__'\n` +
|
|
180
|
+
content +
|
|
181
|
+
`__VU_ENV_EOF__\n` +
|
|
182
|
+
`chmod 600 "$tmp"\n` +
|
|
183
|
+
`if [ -f ${shellQuote(path)} ] && cmp --silent ${shellQuote(path)} "$tmp"; then\n` +
|
|
184
|
+
` rm -f "$tmp"\n` +
|
|
185
|
+
` echo "unchanged"\n` +
|
|
186
|
+
` exit 0\n` +
|
|
187
|
+
`fi\n` +
|
|
188
|
+
`if [ -f ${shellQuote(path)} ]; then\n` +
|
|
189
|
+
` cp -a ${shellQuote(path)} "${path}.bak-$(date -u +%Y%m%d-%H%M%S)"\n` +
|
|
190
|
+
`fi\n` +
|
|
191
|
+
`mv "$tmp" ${shellQuote(path)}\n` +
|
|
192
|
+
// Newly mv'd file is owned by the SSH user; hand it to the PHP runtime uid
|
|
193
|
+
// so the container can read it (mode 600). Keep 600 — owner is now php-fpm.
|
|
194
|
+
chownToRuntimeUid(cfg, path) +
|
|
195
|
+
`echo "written"\n`;
|
|
196
|
+
return runRemoteScript(cfg, script, { silent: true });
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parse the existing `.env` on the server into a plain object. Useful for
|
|
200
|
+
* re-using an existing core's secrets when adding domains or for
|
|
201
|
+
* inspection. Returns an empty object if the file doesn't exist.
|
|
202
|
+
*
|
|
203
|
+
* Best-effort parsing: supports `KEY=value` and `KEY="value"`, skips blank
|
|
204
|
+
* lines and `#` comments. Doesn't expand `$VAR` references — values are
|
|
205
|
+
* returned verbatim.
|
|
206
|
+
*/
|
|
207
|
+
export async function coreReadEnv(cfg, coreName) {
|
|
208
|
+
assertCoreName(coreName);
|
|
209
|
+
const path = `${cfg.coresHostPath}/${coreName}/.env`;
|
|
210
|
+
const result = await runRemote(cfg, `cat ${shellQuote(path)} 2>/dev/null || true`, {
|
|
211
|
+
silent: true,
|
|
212
|
+
});
|
|
213
|
+
if (!result.success || result.stdout.trim() === '')
|
|
214
|
+
return {};
|
|
215
|
+
const out = {};
|
|
216
|
+
for (const rawLine of result.stdout.split('\n')) {
|
|
217
|
+
const line = rawLine.trim();
|
|
218
|
+
if (line === '' || line.startsWith('#'))
|
|
219
|
+
continue;
|
|
220
|
+
const eq = line.indexOf('=');
|
|
221
|
+
if (eq === -1)
|
|
222
|
+
continue;
|
|
223
|
+
const key = line.slice(0, eq).trim();
|
|
224
|
+
let value = line.slice(eq + 1);
|
|
225
|
+
// Strip surrounding quotes if present (matched pair only).
|
|
226
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
227
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
228
|
+
value = value.slice(1, -1);
|
|
229
|
+
}
|
|
230
|
+
out[key] = value;
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Project a brand into `cores/<slug>/brand.json` on the host, idempotently.
|
|
236
|
+
*
|
|
237
|
+
* Reads the existing file and compares *semantically* (parsed + key-sorted),
|
|
238
|
+
* so formatting differences between the PHP and CLI writers never register as
|
|
239
|
+
* drift — only a real identity change rewrites the file. Returns whether the
|
|
240
|
+
* file changed so `ci brand reconcile` can report an honest unchanged/written
|
|
241
|
+
* summary. The freshly-written file is chowned to the PHP runtime uid so the
|
|
242
|
+
* container can read it.
|
|
243
|
+
*/
|
|
244
|
+
export async function coreReconcileBrandJson(cfg, slug, projection) {
|
|
245
|
+
assertCoreName(slug);
|
|
246
|
+
const path = `${cfg.coresHostPath}/${slug}/brand.json`;
|
|
247
|
+
// Compare against what's already there (semantic, not byte-for-byte).
|
|
248
|
+
const existing = await runRemote(cfg, `cat ${shellQuote(path)} 2>/dev/null || true`, { silent: true });
|
|
249
|
+
if (existing.success && existing.stdout.trim() !== '') {
|
|
250
|
+
try {
|
|
251
|
+
const current = JSON.parse(existing.stdout);
|
|
252
|
+
if (stableStringify(current) === stableStringify(projection)) {
|
|
253
|
+
return { success: true, changed: false, stderr: '' };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Unparseable existing file → treat as drift and rewrite.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const content = JSON.stringify(projection, null, 2) + '\n';
|
|
261
|
+
log.info(`[core] reconcile brand.json → ${path}`);
|
|
262
|
+
const script = `set -e\n` +
|
|
263
|
+
`mkdir -p ${shellQuote(`${cfg.coresHostPath}/${slug}`)}\n` +
|
|
264
|
+
`tmp="${path}.tmp.$$"\n` +
|
|
265
|
+
`cat > "$tmp" <<'__VU_BRAND_EOF__'\n` +
|
|
266
|
+
content +
|
|
267
|
+
`__VU_BRAND_EOF__\n` +
|
|
268
|
+
`mv "$tmp" ${shellQuote(path)}\n` +
|
|
269
|
+
chownToRuntimeUid(cfg, path);
|
|
270
|
+
const result = await runRemoteScript(cfg, script, { silent: true });
|
|
271
|
+
return { success: result.success, changed: result.success, stderr: result.stderr };
|
|
272
|
+
}
|
|
273
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
274
|
+
// Internal helpers
|
|
275
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
276
|
+
/**
|
|
277
|
+
* Deterministic JSON string with recursively sorted object keys, so two
|
|
278
|
+
* structurally-equal documents compare equal regardless of key order or
|
|
279
|
+
* whitespace. Arrays keep their order (meaningful); scalars pass through.
|
|
280
|
+
*/
|
|
281
|
+
function stableStringify(value) {
|
|
282
|
+
if (value === null || typeof value !== 'object')
|
|
283
|
+
return JSON.stringify(value);
|
|
284
|
+
if (Array.isArray(value))
|
|
285
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
286
|
+
const obj = value;
|
|
287
|
+
const body = Object.keys(obj)
|
|
288
|
+
.sort()
|
|
289
|
+
.map(k => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)
|
|
290
|
+
.join(',');
|
|
291
|
+
return `{${body}}`;
|
|
292
|
+
}
|
|
293
|
+
function serializeEnv(env) {
|
|
294
|
+
// Stable order makes diffs meaningful between runs.
|
|
295
|
+
const lines = Object.keys(env)
|
|
296
|
+
.sort()
|
|
297
|
+
.map(key => `${key}=${env[key]}`);
|
|
298
|
+
return lines.join('\n') + '\n';
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Reject env values that would break the serializer: embedded newlines
|
|
302
|
+
* (would prematurely terminate the line), trailing whitespace (parsed
|
|
303
|
+
* inconsistently across loaders), or NUL bytes. Caller-provided overrides
|
|
304
|
+
* sometimes carry these — better to fail loudly than write a broken file.
|
|
305
|
+
*/
|
|
306
|
+
function validateEnvValues(env) {
|
|
307
|
+
for (const [key, value] of Object.entries(env)) {
|
|
308
|
+
if (/[\r\n\0]/.test(value)) {
|
|
309
|
+
throw new Error(`Env value for '${key}' contains a newline or NUL byte; cannot serialize safely.`);
|
|
310
|
+
}
|
|
311
|
+
if (value !== value.replace(/[\s]+$/, '')) {
|
|
312
|
+
throw new Error(`Env value for '${key}' has trailing whitespace; trim before passing.`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function randomHex(bytes) {
|
|
317
|
+
return randomBytes(bytes).toString('hex');
|
|
318
|
+
}
|
|
319
|
+
function shellQuote(s) {
|
|
320
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../src/lib/ops/core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
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 type { HostConfig } from '../hosts.js';
|
|
24
|
+
import { type ExecResult } from './ssh.js';
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if a database with the given name exists.
|
|
27
|
+
* Uses `pg_database` catalog query — works regardless of search_path.
|
|
28
|
+
*/
|
|
29
|
+
export declare function dbExists(cfg: HostConfig, dbName: string): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a postgres ROLE (login user) already exists.
|
|
32
|
+
* Used by `dbCreateRoleAndDb` for idempotency.
|
|
33
|
+
*/
|
|
34
|
+
export declare function roleExists(cfg: HostConfig, role: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Create the database if missing (idempotent). On creation, grants the
|
|
37
|
+
* admin user full privileges — postgres makes the creating role the owner
|
|
38
|
+
* automatically, but the GRANT is belt-and-suspenders against permission
|
|
39
|
+
* drift from later role changes.
|
|
40
|
+
*/
|
|
41
|
+
export declare function dbCreateIfMissing(cfg: HostConfig, dbName: string): Promise<ExecResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Per-core role + database provisioning (replaces the shared admin
|
|
44
|
+
* pattern). Each core gets its own postgres LOGIN role with a random
|
|
45
|
+
* password, owning its own database — defense in depth: if one core's
|
|
46
|
+
* .env leaks, the blast radius is one database, not the whole instance.
|
|
47
|
+
*
|
|
48
|
+
* Idempotency:
|
|
49
|
+
* - If the role already exists, its password is updated (ALTER ROLE)
|
|
50
|
+
* so a fresh .env re-write stays in sync with the server.
|
|
51
|
+
* - If the database already exists, ownership is reassigned to the
|
|
52
|
+
* role (REASSIGN OWNED is overkill for first-run; a simpler
|
|
53
|
+
* ALTER DATABASE ... OWNER TO covers it).
|
|
54
|
+
*
|
|
55
|
+
* Both statements run under the admin connection (`cfg.dbAdminUser`)
|
|
56
|
+
* because CREATE ROLE / CREATE DATABASE require it. The single quotes
|
|
57
|
+
* in the password are escaped before substitution; identifiers go
|
|
58
|
+
* through `assertIdent` to forbid SQL-meta characters entirely.
|
|
59
|
+
*
|
|
60
|
+
* Caller responsibility: pass a strong password (≥24 random base64url
|
|
61
|
+
* chars) and a globally-unique role name. The setup orchestrator
|
|
62
|
+
* generates both via Node `crypto.randomBytes`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function dbCreateRoleAndDb(cfg: HostConfig, role: string, password: string, dbName: string): Promise<ExecResult>;
|
|
65
|
+
export interface MigrationApplied {
|
|
66
|
+
readonly version: string;
|
|
67
|
+
readonly success: boolean;
|
|
68
|
+
readonly stderr: string;
|
|
69
|
+
}
|
|
70
|
+
export interface MigrationRunResult {
|
|
71
|
+
readonly applied: readonly MigrationApplied[];
|
|
72
|
+
readonly skipped: readonly string[];
|
|
73
|
+
readonly success: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Apply every `*.sql` file under `migrationsDir` that hasn't already run
|
|
77
|
+
* against `dbName`. Files are applied in filename-sorted order so that the
|
|
78
|
+
* `NNN_description.sql` convention controls ordering.
|
|
79
|
+
*
|
|
80
|
+
* Each migration runs inside an explicit transaction:
|
|
81
|
+
*
|
|
82
|
+
* BEGIN;
|
|
83
|
+
* <file contents>
|
|
84
|
+
* INSERT INTO schema_migrations(version) VALUES ('<version>');
|
|
85
|
+
* COMMIT;
|
|
86
|
+
*
|
|
87
|
+
* On failure the transaction rolls back atomically — partial migrations
|
|
88
|
+
* never leave the schema in a half-applied state. Subsequent migrations
|
|
89
|
+
* are skipped, and {@link MigrationRunResult.success} is false.
|
|
90
|
+
*
|
|
91
|
+
* The ledger table is created on first call (idempotent). Existing rows
|
|
92
|
+
* dictate which versions are skipped on subsequent runs.
|
|
93
|
+
*/
|
|
94
|
+
export declare function dbRunMigrations(cfg: HostConfig, dbName: string, migrationsDir: string): Promise<MigrationRunResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Run a read-only SELECT against `dbName` and return rows as parsed objects.
|
|
97
|
+
*
|
|
98
|
+
* The caller's inner query is wrapped in `json_agg` so every row round-trips
|
|
99
|
+
* as one JSON document — jsonb columns (themes), embedded commas, quotes and
|
|
100
|
+
* escapes all survive losslessly, with none of the fragile CSV/line parsing
|
|
101
|
+
* the migration ledger reader can get away with for a single text column.
|
|
102
|
+
*
|
|
103
|
+
* Returns `[]` on an empty result set or a failed/unparseable query (the
|
|
104
|
+
* failure is logged). `innerSelect` must be a bare SELECT with no trailing
|
|
105
|
+
* semicolon (it's interpolated as a subquery).
|
|
106
|
+
*/
|
|
107
|
+
export declare function dbSelectJson<T = Record<string, unknown>>(cfg: HostConfig, dbName: string, innerSelect: string): Promise<T[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Run a write statement (UPDATE/INSERT) against `dbName`. Thin wrapper over
|
|
110
|
+
* the internal executor for callers outside this module (e.g. reconcile
|
|
111
|
+
* writing achieved DNS state back to the control-plane). The caller is
|
|
112
|
+
* responsible for validating any interpolated values — there is no parameter
|
|
113
|
+
* binding here; pass only validated identifiers / enum literals.
|
|
114
|
+
*/
|
|
115
|
+
export declare function dbExec(cfg: HostConfig, dbName: string, sql: string): Promise<ExecResult>;
|
|
116
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAyB3D;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKhF;AAMD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKhF;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAe5F;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC,CAwDrB;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,kBAAkB,CAAC,CA6D7B;AA+CD;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,CAAC,EAAE,CAAC,CAiBd;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAE9F"}
|