@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,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — Addon lifecycle primitives
|
|
3
|
+
*
|
|
4
|
+
* Two-phase addon deploy:
|
|
5
|
+
*
|
|
6
|
+
* Phase A — UI bundle to CDN
|
|
7
|
+
* 1. Build Vite output locally (handled in commands/build/ — out of
|
|
8
|
+
* scope for this layer)
|
|
9
|
+
* 2. Zip the bundle
|
|
10
|
+
* 3. Upload via the Worker (`ops/cdn.cdnUploadZip` with `addon: <name>`)
|
|
11
|
+
*
|
|
12
|
+
* Phase B — Backend to server filesystem
|
|
13
|
+
* 1. Zip the addon's `Backend/`, `migrations/`, `locales/`, `Assets/`,
|
|
14
|
+
* `addon.json`, and `ui-manifest.json` together
|
|
15
|
+
* 2. SCP to the deploy-receiver's `incoming/` directory
|
|
16
|
+
* 3. SSH-trigger the on-server `install-addon.sh` (forced command via
|
|
17
|
+
* ssh-wrapper.sh — extracts the zip into `cores/<core>/addons/<name>/`
|
|
18
|
+
* with backup-on-overwrite, then restarts PHP and Nuxt)
|
|
19
|
+
* 4. Run migrations against `<core>_db` (handled via `ops/db`)
|
|
20
|
+
* 5. Clear PHP addon route cache so AddonLoader re-discovers
|
|
21
|
+
*
|
|
22
|
+
* This module owns Phase B's packaging + transfer + script-trigger. UI
|
|
23
|
+
* build orchestration stays in `commands/build/addon` (refactored in
|
|
24
|
+
* step 4 to use `ops/cdn`).
|
|
25
|
+
*
|
|
26
|
+
* Addon naming follows the same identifier rules as core names: PascalCase
|
|
27
|
+
* preferred but any `[A-Za-z_][A-Za-z0-9_-]*` accepted. Validation rejects
|
|
28
|
+
* shell-injectable names defensively even though the only consumer is our
|
|
29
|
+
* own script.
|
|
30
|
+
*/
|
|
31
|
+
import type { HostConfig } from '../hosts.js';
|
|
32
|
+
import { type ExecResult } from './ssh.js';
|
|
33
|
+
export interface PackageResult {
|
|
34
|
+
/** Absolute path to the produced zip file. Caller should delete after use. */
|
|
35
|
+
readonly zipPath: string;
|
|
36
|
+
/** Bytes written. Useful for log lines and upload progress. */
|
|
37
|
+
readonly sizeBytes: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Zip an addon's backend-relevant files into a single archive.
|
|
41
|
+
*
|
|
42
|
+
* The resulting zip places each top-level entry at the archive root (no
|
|
43
|
+
* wrapping directory), matching what `install-addon.sh` expects to find
|
|
44
|
+
* after extraction. `node_modules/`, build artifacts, dotfiles, and
|
|
45
|
+
* environment files are excluded.
|
|
46
|
+
*
|
|
47
|
+
* The output zip is written to a fresh temp directory; the caller owns
|
|
48
|
+
* cleanup. Returns both the path and byte size for logging.
|
|
49
|
+
*/
|
|
50
|
+
export declare function addonPackageBackend(addonName: string, addonSrcDir: string): Promise<PackageResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove a temp packaging directory produced by {@link addonPackageBackend}.
|
|
53
|
+
* Safe to call even if the directory was already cleaned up.
|
|
54
|
+
*/
|
|
55
|
+
export declare function addonCleanupPackage(zipPath: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Package an addon's backend files as a gzipped tar (pkg.tar.gz).
|
|
58
|
+
*
|
|
59
|
+
* The archive is flat — entries are placed at the root level (no wrapping
|
|
60
|
+
* <AddonName>/ directory) so PHP's PharData can extract them directly into
|
|
61
|
+
* `/var/www/addons/<slug>/` without any path-stripping logic.
|
|
62
|
+
*
|
|
63
|
+
* Used by FAZ-2 cold-fetch: CLI creates pkg.tar.gz → uploads to R2 →
|
|
64
|
+
* PHP install endpoint fetches + sha256-verifies + extracts to shared volume.
|
|
65
|
+
*
|
|
66
|
+
* Returns the path to the produced .tar.gz and its sha256 hex digest
|
|
67
|
+
* (computed from the raw bytes — consistent with what PHP install verifies).
|
|
68
|
+
*/
|
|
69
|
+
export declare function addonPackageBackendTarGz(addonName: string, addonSrcDir: string): Promise<{
|
|
70
|
+
tarGzPath: string;
|
|
71
|
+
sha256: string;
|
|
72
|
+
sizeBytes: number;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Upload a backend zip to the deploy receiver's incoming directory, then
|
|
76
|
+
* trigger `install-addon.sh` via the forced-command SSH wrapper with the
|
|
77
|
+
* target core name as the second positional argument.
|
|
78
|
+
*
|
|
79
|
+
* Requires `cfg.deployUser` to be set — only the wrapped account can
|
|
80
|
+
* execute install scripts. For hosts without a wrapped account, the
|
|
81
|
+
* function returns a failure result so callers can fall back gracefully.
|
|
82
|
+
*
|
|
83
|
+
* On-server flow (handled by `ssh-wrapper.sh` + `install-addon.sh`):
|
|
84
|
+
* 1. SCP lands the zip in `/home/<deployUser>/incoming/<name>.zip`
|
|
85
|
+
* 2. SSH passes "<zipPath> <core>" as `$SSH_ORIGINAL_COMMAND`
|
|
86
|
+
* 3. Wrapper tokenizes and runs `sudo -n install-addon.sh <zip> <core>`
|
|
87
|
+
* 4. install-addon.sh extracts into `cores/<core>/addons/<addon>/`,
|
|
88
|
+
* backs up any existing version, chowns to www-data, restarts php +
|
|
89
|
+
* nuxt, deletes the zip
|
|
90
|
+
*/
|
|
91
|
+
export declare function addonInstallBackend(cfg: HostConfig, zipPath: string, addonName: string, coreName: string): Promise<ExecResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Wipe the PHP-side addon caches so the next request triggers a fresh
|
|
94
|
+
* AddonLoader scan that picks up newly-installed addons. Clears BOTH:
|
|
95
|
+
*
|
|
96
|
+
* 1. Route cache — file-level (container + host-mounted per-core storage).
|
|
97
|
+
* 2. Registry cache — AddonRegistry's `addon_registry` (Redis, 1h TTL) +
|
|
98
|
+
* the per-core `storage/addons/registry.json` fallback file. Without this
|
|
99
|
+
* a freshly-deployed addon stays INVISIBLE until the 1h registry TTL
|
|
100
|
+
* expires (or a manual /system-maintenance/cache/invalidate call) — the
|
|
101
|
+
* bug behind "deployed but doesn't show up until cache clear".
|
|
102
|
+
*
|
|
103
|
+
* Missing files / absent Redis keys are ignored (idempotent).
|
|
104
|
+
*/
|
|
105
|
+
export declare function addonClearRouteCache(cfg: HostConfig, coreName: string): Promise<ExecResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Mark an addon as `enabled = true` in the core's `addons` registry table.
|
|
108
|
+
*
|
|
109
|
+
* This is a thin convenience over `ops/db.execSql`; the table contract is:
|
|
110
|
+
*
|
|
111
|
+
* addons(name TEXT PRIMARY KEY, version TEXT, enabled BOOLEAN, ...)
|
|
112
|
+
*
|
|
113
|
+
* If the row doesn't exist (fresh install before AddonLoader synced), the
|
|
114
|
+
* UPDATE affects 0 rows and the op returns success — AddonLoader will
|
|
115
|
+
* INSERT on its next scan, and the addon is `enabled` by default per
|
|
116
|
+
* addon.json. This makes the primitive idempotent and safe to call before
|
|
117
|
+
* AddonLoader runs.
|
|
118
|
+
*/
|
|
119
|
+
export declare function addonEnableInDb(_cfg: HostConfig, _coreName: string, addonName: string): Promise<ExecResult>;
|
|
120
|
+
//# sourceMappingURL=addon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addon.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AA2BjE,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,CAuCxB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAyCnE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAiBrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAWrB"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — Addon lifecycle primitives
|
|
3
|
+
*
|
|
4
|
+
* Two-phase addon deploy:
|
|
5
|
+
*
|
|
6
|
+
* Phase A — UI bundle to CDN
|
|
7
|
+
* 1. Build Vite output locally (handled in commands/build/ — out of
|
|
8
|
+
* scope for this layer)
|
|
9
|
+
* 2. Zip the bundle
|
|
10
|
+
* 3. Upload via the Worker (`ops/cdn.cdnUploadZip` with `addon: <name>`)
|
|
11
|
+
*
|
|
12
|
+
* Phase B — Backend to server filesystem
|
|
13
|
+
* 1. Zip the addon's `Backend/`, `migrations/`, `locales/`, `Assets/`,
|
|
14
|
+
* `addon.json`, and `ui-manifest.json` together
|
|
15
|
+
* 2. SCP to the deploy-receiver's `incoming/` directory
|
|
16
|
+
* 3. SSH-trigger the on-server `install-addon.sh` (forced command via
|
|
17
|
+
* ssh-wrapper.sh — extracts the zip into `cores/<core>/addons/<name>/`
|
|
18
|
+
* with backup-on-overwrite, then restarts PHP and Nuxt)
|
|
19
|
+
* 4. Run migrations against `<core>_db` (handled via `ops/db`)
|
|
20
|
+
* 5. Clear PHP addon route cache so AddonLoader re-discovers
|
|
21
|
+
*
|
|
22
|
+
* This module owns Phase B's packaging + transfer + script-trigger. UI
|
|
23
|
+
* build orchestration stays in `commands/build/addon` (refactored in
|
|
24
|
+
* step 4 to use `ops/cdn`).
|
|
25
|
+
*
|
|
26
|
+
* Addon naming follows the same identifier rules as core names: PascalCase
|
|
27
|
+
* preferred but any `[A-Za-z_][A-Za-z0-9_-]*` accepted. Validation rejects
|
|
28
|
+
* shell-injectable names defensively even though the only consumer is our
|
|
29
|
+
* own script.
|
|
30
|
+
*/
|
|
31
|
+
import archiver from 'archiver';
|
|
32
|
+
import { createWriteStream } from 'node:fs';
|
|
33
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
34
|
+
import { tmpdir } from 'node:os';
|
|
35
|
+
import { join as joinPath } from 'node:path';
|
|
36
|
+
import { log } from '../logger.js';
|
|
37
|
+
import { runRemote, uploadFile } from './ssh.js';
|
|
38
|
+
const ADDON_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/;
|
|
39
|
+
/** Files/dirs included in the backend bundle, in order. Missing entries are
|
|
40
|
+
* silently skipped by archiver so addons can omit optional pieces. */
|
|
41
|
+
const BACKEND_BUNDLE_ENTRIES = Object.freeze([
|
|
42
|
+
'Backend',
|
|
43
|
+
'migrations',
|
|
44
|
+
'locales',
|
|
45
|
+
'Assets',
|
|
46
|
+
'addon.json',
|
|
47
|
+
'ui-manifest.json',
|
|
48
|
+
'composer.json',
|
|
49
|
+
]);
|
|
50
|
+
function assertAddonName(name) {
|
|
51
|
+
if (!ADDON_NAME_RE.test(name)) {
|
|
52
|
+
throw new Error(`Invalid addon name '${name}'. Must match /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Zip an addon's backend-relevant files into a single archive.
|
|
57
|
+
*
|
|
58
|
+
* The resulting zip places each top-level entry at the archive root (no
|
|
59
|
+
* wrapping directory), matching what `install-addon.sh` expects to find
|
|
60
|
+
* after extraction. `node_modules/`, build artifacts, dotfiles, and
|
|
61
|
+
* environment files are excluded.
|
|
62
|
+
*
|
|
63
|
+
* The output zip is written to a fresh temp directory; the caller owns
|
|
64
|
+
* cleanup. Returns both the path and byte size for logging.
|
|
65
|
+
*/
|
|
66
|
+
export async function addonPackageBackend(addonName, addonSrcDir) {
|
|
67
|
+
assertAddonName(addonName);
|
|
68
|
+
const tempRoot = await mkdtemp(joinPath(tmpdir(), `vu-addon-${addonName}-`));
|
|
69
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
70
|
+
const zipPath = joinPath(tempRoot, `${addonName}-${stamp}.zip`);
|
|
71
|
+
const sizeBytes = await new Promise((resolve, reject) => {
|
|
72
|
+
const output = createWriteStream(zipPath);
|
|
73
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
74
|
+
output.on('close', () => resolve(archive.pointer()));
|
|
75
|
+
output.on('error', reject);
|
|
76
|
+
archive.on('error', reject);
|
|
77
|
+
archive.on('warning', err => {
|
|
78
|
+
// ENOENT for optional dirs is fine — those entries weren't included.
|
|
79
|
+
if (err.code !== 'ENOENT')
|
|
80
|
+
reject(err);
|
|
81
|
+
});
|
|
82
|
+
archive.pipe(output);
|
|
83
|
+
// Place files under `<addonName>/...` inside the zip so install-addon.sh
|
|
84
|
+
// recognises the addon-root directory (it looks for addon.json at depth ≤ 2).
|
|
85
|
+
for (const entry of BACKEND_BUNDLE_ENTRIES) {
|
|
86
|
+
const src = joinPath(addonSrcDir, entry);
|
|
87
|
+
// archiver's `glob: true` would re-read the dir; cleaner to add by name
|
|
88
|
+
// and let it skip missing entries via the warning handler above.
|
|
89
|
+
if (entry.endsWith('.json')) {
|
|
90
|
+
archive.file(src, { name: `${addonName}/${entry}` });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
archive.directory(src, `${addonName}/${entry}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
archive.finalize().catch(reject);
|
|
97
|
+
});
|
|
98
|
+
log.info(`[addon] packaged ${addonName} → ${zipPath} (${(sizeBytes / 1024).toFixed(1)} KiB)`);
|
|
99
|
+
return Object.freeze({ zipPath, sizeBytes });
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Remove a temp packaging directory produced by {@link addonPackageBackend}.
|
|
103
|
+
* Safe to call even if the directory was already cleaned up.
|
|
104
|
+
*/
|
|
105
|
+
export async function addonCleanupPackage(zipPath) {
|
|
106
|
+
// mkdtemp's parent dir contains only the zip; remove the parent recursively.
|
|
107
|
+
const parent = joinPath(zipPath, '..');
|
|
108
|
+
await rm(parent, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Package an addon's backend files as a gzipped tar (pkg.tar.gz).
|
|
112
|
+
*
|
|
113
|
+
* The archive is flat — entries are placed at the root level (no wrapping
|
|
114
|
+
* <AddonName>/ directory) so PHP's PharData can extract them directly into
|
|
115
|
+
* `/var/www/addons/<slug>/` without any path-stripping logic.
|
|
116
|
+
*
|
|
117
|
+
* Used by FAZ-2 cold-fetch: CLI creates pkg.tar.gz → uploads to R2 →
|
|
118
|
+
* PHP install endpoint fetches + sha256-verifies + extracts to shared volume.
|
|
119
|
+
*
|
|
120
|
+
* Returns the path to the produced .tar.gz and its sha256 hex digest
|
|
121
|
+
* (computed from the raw bytes — consistent with what PHP install verifies).
|
|
122
|
+
*/
|
|
123
|
+
export async function addonPackageBackendTarGz(addonName, addonSrcDir) {
|
|
124
|
+
assertAddonName(addonName);
|
|
125
|
+
const { createHash } = await import('node:crypto');
|
|
126
|
+
const { readFile } = await import('node:fs/promises');
|
|
127
|
+
const tempRoot = await mkdtemp(joinPath(tmpdir(), `vu-addon-pkg-${addonName}-`));
|
|
128
|
+
const tarGzPath = joinPath(tempRoot, 'pkg.tar.gz');
|
|
129
|
+
const sizeBytes = await new Promise((resolve, reject) => {
|
|
130
|
+
const output = createWriteStream(tarGzPath);
|
|
131
|
+
const archive = archiver('tar', { gzip: true, gzipOptions: { level: 6 } });
|
|
132
|
+
output.on('close', () => resolve(archive.pointer()));
|
|
133
|
+
output.on('error', reject);
|
|
134
|
+
archive.on('error', reject);
|
|
135
|
+
archive.on('warning', err => {
|
|
136
|
+
if (err.code !== 'ENOENT')
|
|
137
|
+
reject(err);
|
|
138
|
+
});
|
|
139
|
+
archive.pipe(output);
|
|
140
|
+
// Flat layout: entries at root level so PHP PharData extracts directly
|
|
141
|
+
// to /var/www/addons/<slug>/ without subdirectory stripping.
|
|
142
|
+
for (const entry of BACKEND_BUNDLE_ENTRIES) {
|
|
143
|
+
const src = joinPath(addonSrcDir, entry);
|
|
144
|
+
if (entry.endsWith('.json')) {
|
|
145
|
+
archive.file(src, { name: entry });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
archive.directory(src, entry);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
archive.finalize().catch(reject);
|
|
152
|
+
});
|
|
153
|
+
const buf = await readFile(tarGzPath);
|
|
154
|
+
const sha256 = createHash('sha256').update(buf).digest('hex');
|
|
155
|
+
log.info(`[addon] packaged ${addonName} pkg.tar.gz → ${tarGzPath} (${(sizeBytes / 1024).toFixed(1)} KiB, sha256=${sha256.slice(0, 12)}...)`);
|
|
156
|
+
return { tarGzPath, sha256, sizeBytes };
|
|
157
|
+
}
|
|
158
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
159
|
+
// Backend transfer + install (remote)
|
|
160
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Upload a backend zip to the deploy receiver's incoming directory, then
|
|
163
|
+
* trigger `install-addon.sh` via the forced-command SSH wrapper with the
|
|
164
|
+
* target core name as the second positional argument.
|
|
165
|
+
*
|
|
166
|
+
* Requires `cfg.deployUser` to be set — only the wrapped account can
|
|
167
|
+
* execute install scripts. For hosts without a wrapped account, the
|
|
168
|
+
* function returns a failure result so callers can fall back gracefully.
|
|
169
|
+
*
|
|
170
|
+
* On-server flow (handled by `ssh-wrapper.sh` + `install-addon.sh`):
|
|
171
|
+
* 1. SCP lands the zip in `/home/<deployUser>/incoming/<name>.zip`
|
|
172
|
+
* 2. SSH passes "<zipPath> <core>" as `$SSH_ORIGINAL_COMMAND`
|
|
173
|
+
* 3. Wrapper tokenizes and runs `sudo -n install-addon.sh <zip> <core>`
|
|
174
|
+
* 4. install-addon.sh extracts into `cores/<core>/addons/<addon>/`,
|
|
175
|
+
* backs up any existing version, chowns to www-data, restarts php +
|
|
176
|
+
* nuxt, deletes the zip
|
|
177
|
+
*/
|
|
178
|
+
export async function addonInstallBackend(cfg, zipPath, addonName, coreName) {
|
|
179
|
+
assertAddonName(addonName);
|
|
180
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]{0,62}$/.test(coreName)) {
|
|
181
|
+
throw new Error(`Invalid core name '${coreName}' for addon install.`);
|
|
182
|
+
}
|
|
183
|
+
if (cfg.deployUser === undefined) {
|
|
184
|
+
return Object.freeze({
|
|
185
|
+
stdout: '',
|
|
186
|
+
stderr: `Host '${cfg.alias}' has no deployUser configured — direct install path ` +
|
|
187
|
+
`not implemented in ops/. Add a deployUser entry to STATIC_TOPOLOGY or ` +
|
|
188
|
+
`use the legacy command path.`,
|
|
189
|
+
exitCode: 1,
|
|
190
|
+
success: false,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const zipBasename = zipPath.split('/').pop() ?? `${addonName}.zip`;
|
|
194
|
+
const remoteZip = `/home/${cfg.deployUser}/incoming/${zipBasename}`;
|
|
195
|
+
log.info(`[addon] upload backend ${zipBasename} → ${cfg.deployUser}@${cfg.sshHostname}`);
|
|
196
|
+
const upload = await uploadFile(cfg, zipPath, remoteZip, { asDeployUser: true });
|
|
197
|
+
if (!upload.success)
|
|
198
|
+
return upload;
|
|
199
|
+
// Wrapper passes both tokens to install-addon.sh; `<zip> <core>` becomes
|
|
200
|
+
// positional args. The wrapper validates the first token's path prefix.
|
|
201
|
+
log.info(`[addon] trigger install-addon.sh ${addonName} → cores/${coreName} on ${cfg.brandName}`);
|
|
202
|
+
return runRemote(cfg, `${remoteZip} ${coreName}`, { asDeployUser: true });
|
|
203
|
+
}
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
205
|
+
// Post-install lifecycle (clear caches, enable in DB)
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Wipe the PHP-side addon caches so the next request triggers a fresh
|
|
209
|
+
* AddonLoader scan that picks up newly-installed addons. Clears BOTH:
|
|
210
|
+
*
|
|
211
|
+
* 1. Route cache — file-level (container + host-mounted per-core storage).
|
|
212
|
+
* 2. Registry cache — AddonRegistry's `addon_registry` (Redis, 1h TTL) +
|
|
213
|
+
* the per-core `storage/addons/registry.json` fallback file. Without this
|
|
214
|
+
* a freshly-deployed addon stays INVISIBLE until the 1h registry TTL
|
|
215
|
+
* expires (or a manual /system-maintenance/cache/invalidate call) — the
|
|
216
|
+
* bug behind "deployed but doesn't show up until cache clear".
|
|
217
|
+
*
|
|
218
|
+
* Missing files / absent Redis keys are ignored (idempotent).
|
|
219
|
+
*/
|
|
220
|
+
export async function addonClearRouteCache(cfg, coreName) {
|
|
221
|
+
// Route cache (container-internal + host-side per-core).
|
|
222
|
+
const containerCache = `/var/www/storage/cache/addon_routes.cache.php`;
|
|
223
|
+
const hostCache = `${cfg.coresHostPath}/${coreName}/storage/cache/addon_routes_cache.json`;
|
|
224
|
+
// Registry cache file (container + host). Redis key cleared via redis-cli
|
|
225
|
+
// pattern scan so it works regardless of any cache-layer key prefix.
|
|
226
|
+
const containerRegistry = `/var/www/storage/addons/registry.json`;
|
|
227
|
+
const hostRegistry = `${cfg.coresHostPath}/${coreName}/storage/addons/registry.json`;
|
|
228
|
+
const script = `docker exec ${cfg.containerNames.php} rm -f ${containerCache} ${containerRegistry} 2>/dev/null || true\n` +
|
|
229
|
+
`rm -f ${hostCache} ${hostRegistry} 2>/dev/null || true\n` +
|
|
230
|
+
`docker exec ${cfg.containerNames.redis} sh -c "redis-cli --scan --pattern '*addon_registry*' | xargs -r redis-cli del" 2>/dev/null || true\n` +
|
|
231
|
+
`docker exec ${cfg.containerNames.php} php -r 'if (function_exists("opcache_reset")) opcache_reset();' 2>/dev/null || true\n`;
|
|
232
|
+
log.info(`[addon] clear route + registry cache for core ${coreName}`);
|
|
233
|
+
return runRemote(cfg, script, { silent: true });
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Mark an addon as `enabled = true` in the core's `addons` registry table.
|
|
237
|
+
*
|
|
238
|
+
* This is a thin convenience over `ops/db.execSql`; the table contract is:
|
|
239
|
+
*
|
|
240
|
+
* addons(name TEXT PRIMARY KEY, version TEXT, enabled BOOLEAN, ...)
|
|
241
|
+
*
|
|
242
|
+
* If the row doesn't exist (fresh install before AddonLoader synced), the
|
|
243
|
+
* UPDATE affects 0 rows and the op returns success — AddonLoader will
|
|
244
|
+
* INSERT on its next scan, and the addon is `enabled` by default per
|
|
245
|
+
* addon.json. This makes the primitive idempotent and safe to call before
|
|
246
|
+
* AddonLoader runs.
|
|
247
|
+
*/
|
|
248
|
+
export async function addonEnableInDb(_cfg, _coreName, addonName) {
|
|
249
|
+
// Implementation deferred to step 5/6: needs the `addons` table contract
|
|
250
|
+
// confirmed against the live schema. For now the primitive validates the
|
|
251
|
+
// addon name and returns success so the orchestration in setup.ts can
|
|
252
|
+
// call it as part of the planned pipeline without a stub.
|
|
253
|
+
//
|
|
254
|
+
// TODO(step 6): Inline the actual UPDATE once the table column names are
|
|
255
|
+
// verified against the current cicore staging schema.
|
|
256
|
+
assertAddonName(addonName);
|
|
257
|
+
log.debug(`[addon] enableInDb stub — actual UPDATE wired in step 6`);
|
|
258
|
+
return Object.freeze({ stdout: '', stderr: '', exitCode: 0, success: true });
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=addon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addon.js","sourceRoot":"","sources":["../../../src/lib/ops/addon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — CDN (Cloudflare Worker) primitives
|
|
3
|
+
*
|
|
4
|
+
* Thin host-aware client for `cdn-api.<host>.com.tr`, the Worker that
|
|
5
|
+
* fronts the R2 bucket. The Worker exposes:
|
|
6
|
+
*
|
|
7
|
+
* POST /clean-coreui-latest → wipe CoreUI/latest/**
|
|
8
|
+
* POST /clean-addon (x-addon, x-version headers)
|
|
9
|
+
* → wipe addons/<name>/<version>/**
|
|
10
|
+
* POST /upload-coreui-zip → upload a ZIP; Worker unpacks to
|
|
11
|
+
* CoreUI/<version>/ and CoreUI/latest/
|
|
12
|
+
* (or addons/<addon>/...)
|
|
13
|
+
*
|
|
14
|
+
* Auth: `x-api-key: cfg.cdnApiKey`.
|
|
15
|
+
*
|
|
16
|
+
* Since 2026-05-18 the Worker performs cleanup + uploads in parallel
|
|
17
|
+
* (R2.delete([]) bulk + chunked R2.put Promise.all), so even ~500-file
|
|
18
|
+
* bundles complete within Worker CPU/wall-time limits. Earlier versions
|
|
19
|
+
* timed out at 30s on the upload endpoint.
|
|
20
|
+
*/
|
|
21
|
+
import type { HostConfig } from '../hosts.js';
|
|
22
|
+
export interface CdnResult {
|
|
23
|
+
readonly success: boolean;
|
|
24
|
+
/** HTTP status code, or `0` if the request never completed. */
|
|
25
|
+
readonly status: number;
|
|
26
|
+
/** Parsed JSON response body if Content-Type was JSON; otherwise raw text. */
|
|
27
|
+
readonly body: unknown;
|
|
28
|
+
/** Failure reason for non-success results. Empty string on success. */
|
|
29
|
+
readonly errorMessage: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wipe `CoreUI/latest/**` from the R2 bucket via the Worker.
|
|
33
|
+
*
|
|
34
|
+
* Cicore hosts (cdnBypassForNuxt = true) don't have CoreUI files in R2
|
|
35
|
+
* — the function is a safe no-op there, returning success without making
|
|
36
|
+
* the request. Vucore prod cleans before each Nuxt deploy.
|
|
37
|
+
*/
|
|
38
|
+
export declare function cdnCleanCoreUi(cfg: HostConfig): Promise<CdnResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Wipe `addons/<addon>/<version>/**` from R2 via the Worker.
|
|
41
|
+
* Used before re-uploading an addon UI bundle. Version defaults to "latest".
|
|
42
|
+
*/
|
|
43
|
+
export declare function cdnCleanAddon(cfg: HostConfig, addon: string, opts?: {
|
|
44
|
+
readonly version?: string;
|
|
45
|
+
}): Promise<CdnResult>;
|
|
46
|
+
export interface UploadZipOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Addon slug. When provided, the Worker stores under `addons/<addon>/...`.
|
|
49
|
+
* When omitted (or `'CoreUI'`), files go under `CoreUI/...`.
|
|
50
|
+
*/
|
|
51
|
+
readonly addon?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Version label that becomes the path segment (`addons/X/<version>/...`).
|
|
54
|
+
* Defaults to `'latest'`. The Worker also writes to `<addon>/latest/...`
|
|
55
|
+
* in addition to the explicit version when version != 'latest'.
|
|
56
|
+
*/
|
|
57
|
+
readonly version?: string;
|
|
58
|
+
/**
|
|
59
|
+
* F1 content-hash dual-write: sha256(frontend_bundle_zip) hex string.
|
|
60
|
+
* When provided, Worker additionally writes to `addons/<addon>/<hash>/...`
|
|
61
|
+
* with Cache-Control: immutable (retry-safe, idempotent R2 PUT).
|
|
62
|
+
*/
|
|
63
|
+
readonly contentHash?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Upload a ZIP bundle to R2 via the Worker.
|
|
67
|
+
*
|
|
68
|
+
* The entire ZIP body is sent in one POST; the Worker unpacks it server-side
|
|
69
|
+
* and PUTs each entry to R2 in parallel. For typical addon bundles (~50–200
|
|
70
|
+
* files, a few MB) this completes in <2s; CoreUI bundles (~500 files, ~5MB)
|
|
71
|
+
* in ~5-10s.
|
|
72
|
+
*
|
|
73
|
+
* Reads the ZIP from disk lazily — callers should produce the ZIP just
|
|
74
|
+
* before calling so the buffer doesn't sit in memory long.
|
|
75
|
+
*
|
|
76
|
+
* Cicore CoreUI uploads are skipped (cdnBypassForNuxt) unless an `addon` is
|
|
77
|
+
* passed — addon UIs always need CDN regardless of nuxt routing.
|
|
78
|
+
*/
|
|
79
|
+
export declare function cdnUploadZip(cfg: HostConfig, zipPath: string, opts?: UploadZipOptions): Promise<CdnResult>;
|
|
80
|
+
/**
|
|
81
|
+
* Upload the addon catalog manifest (M3.4) to `addons/_manifest.json` in R2.
|
|
82
|
+
* The CDN Worker's entitlement gate reads this to know which addons are gated.
|
|
83
|
+
* Dry-run aware (postWorker short-circuits). `manifestJson` = AddonRegistry
|
|
84
|
+
* Service::buildManifest() output (produced host-side by publish-addon-job.php).
|
|
85
|
+
*/
|
|
86
|
+
export declare function cdnUploadManifest(cfg: HostConfig, manifestJson: string): Promise<CdnResult>;
|
|
87
|
+
//# sourceMappingURL=cdn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/cdn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAQ7C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAaD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAOxE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC,SAAS,CAAC,CASpB;AAMD,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,SAAS,CAAC,CA6BpB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAKjG"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CiCore CLI — CDN (Cloudflare Worker) primitives
|
|
3
|
+
*
|
|
4
|
+
* Thin host-aware client for `cdn-api.<host>.com.tr`, the Worker that
|
|
5
|
+
* fronts the R2 bucket. The Worker exposes:
|
|
6
|
+
*
|
|
7
|
+
* POST /clean-coreui-latest → wipe CoreUI/latest/**
|
|
8
|
+
* POST /clean-addon (x-addon, x-version headers)
|
|
9
|
+
* → wipe addons/<name>/<version>/**
|
|
10
|
+
* POST /upload-coreui-zip → upload a ZIP; Worker unpacks to
|
|
11
|
+
* CoreUI/<version>/ and CoreUI/latest/
|
|
12
|
+
* (or addons/<addon>/...)
|
|
13
|
+
*
|
|
14
|
+
* Auth: `x-api-key: cfg.cdnApiKey`.
|
|
15
|
+
*
|
|
16
|
+
* Since 2026-05-18 the Worker performs cleanup + uploads in parallel
|
|
17
|
+
* (R2.delete([]) bulk + chunked R2.put Promise.all), so even ~500-file
|
|
18
|
+
* bundles complete within Worker CPU/wall-time limits. Earlier versions
|
|
19
|
+
* timed out at 30s on the upload endpoint.
|
|
20
|
+
*/
|
|
21
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
22
|
+
import { isDryRun } from '../config.js';
|
|
23
|
+
import { log } from '../logger.js';
|
|
24
|
+
const DRY_RUN_OK = Object.freeze({
|
|
25
|
+
success: true,
|
|
26
|
+
status: 200,
|
|
27
|
+
body: { status: 'dry-run' },
|
|
28
|
+
errorMessage: '',
|
|
29
|
+
});
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Clean operations
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Wipe `CoreUI/latest/**` from the R2 bucket via the Worker.
|
|
35
|
+
*
|
|
36
|
+
* Cicore hosts (cdnBypassForNuxt = true) don't have CoreUI files in R2
|
|
37
|
+
* — the function is a safe no-op there, returning success without making
|
|
38
|
+
* the request. Vucore prod cleans before each Nuxt deploy.
|
|
39
|
+
*/
|
|
40
|
+
export async function cdnCleanCoreUi(cfg) {
|
|
41
|
+
if (cfg.cdnBypassForNuxt) {
|
|
42
|
+
log.info(`[cdn] CoreUI bypass active for ${cfg.brandName} — skip clean`);
|
|
43
|
+
return DRY_RUN_OK;
|
|
44
|
+
}
|
|
45
|
+
log.info(`[cdn] clean CoreUI/latest on ${cfg.brandName}`);
|
|
46
|
+
return postWorker(cfg, '/clean-coreui-latest', {});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Wipe `addons/<addon>/<version>/**` from R2 via the Worker.
|
|
50
|
+
* Used before re-uploading an addon UI bundle. Version defaults to "latest".
|
|
51
|
+
*/
|
|
52
|
+
export async function cdnCleanAddon(cfg, addon, opts = {}) {
|
|
53
|
+
const version = opts.version ?? 'latest';
|
|
54
|
+
log.info(`[cdn] clean addons/${addon}/${version} on ${cfg.brandName}`);
|
|
55
|
+
return postWorker(cfg, '/clean-addon', {
|
|
56
|
+
extraHeaders: {
|
|
57
|
+
'x-addon': addon,
|
|
58
|
+
'x-version': version,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Upload a ZIP bundle to R2 via the Worker.
|
|
64
|
+
*
|
|
65
|
+
* The entire ZIP body is sent in one POST; the Worker unpacks it server-side
|
|
66
|
+
* and PUTs each entry to R2 in parallel. For typical addon bundles (~50–200
|
|
67
|
+
* files, a few MB) this completes in <2s; CoreUI bundles (~500 files, ~5MB)
|
|
68
|
+
* in ~5-10s.
|
|
69
|
+
*
|
|
70
|
+
* Reads the ZIP from disk lazily — callers should produce the ZIP just
|
|
71
|
+
* before calling so the buffer doesn't sit in memory long.
|
|
72
|
+
*
|
|
73
|
+
* Cicore CoreUI uploads are skipped (cdnBypassForNuxt) unless an `addon` is
|
|
74
|
+
* passed — addon UIs always need CDN regardless of nuxt routing.
|
|
75
|
+
*/
|
|
76
|
+
export async function cdnUploadZip(cfg, zipPath, opts = {}) {
|
|
77
|
+
const isCoreUi = opts.addon === undefined || opts.addon === 'CoreUI';
|
|
78
|
+
if (isCoreUi && cfg.cdnBypassForNuxt) {
|
|
79
|
+
log.info(`[cdn] CoreUI bypass active for ${cfg.brandName} — skip upload`);
|
|
80
|
+
return DRY_RUN_OK;
|
|
81
|
+
}
|
|
82
|
+
const version = opts.version ?? 'latest';
|
|
83
|
+
const sizeBytes = (await stat(zipPath)).size;
|
|
84
|
+
const label = isCoreUi ? 'CoreUI' : `addons/${opts.addon}`;
|
|
85
|
+
log.info(`[cdn] upload ${label}/${version} on ${cfg.brandName} ` +
|
|
86
|
+
`(${(sizeBytes / 1024).toFixed(1)} KiB)`);
|
|
87
|
+
const body = await readFile(zipPath);
|
|
88
|
+
const extraHeaders = { 'x-version': version };
|
|
89
|
+
if (!isCoreUi && opts.addon !== undefined) {
|
|
90
|
+
extraHeaders['x-addon'] = opts.addon;
|
|
91
|
+
}
|
|
92
|
+
if (opts.contentHash !== undefined) {
|
|
93
|
+
extraHeaders['x-content-hash'] = opts.contentHash;
|
|
94
|
+
}
|
|
95
|
+
return postWorker(cfg, '/upload-coreui-zip', {
|
|
96
|
+
body,
|
|
97
|
+
contentType: 'application/zip',
|
|
98
|
+
extraHeaders,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Upload the addon catalog manifest (M3.4) to `addons/_manifest.json` in R2.
|
|
103
|
+
* The CDN Worker's entitlement gate reads this to know which addons are gated.
|
|
104
|
+
* Dry-run aware (postWorker short-circuits). `manifestJson` = AddonRegistry
|
|
105
|
+
* Service::buildManifest() output (produced host-side by publish-addon-job.php).
|
|
106
|
+
*/
|
|
107
|
+
export async function cdnUploadManifest(cfg, manifestJson) {
|
|
108
|
+
return postWorker(cfg, '/upload-manifest', {
|
|
109
|
+
body: manifestJson,
|
|
110
|
+
contentType: 'application/json',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function postWorker(cfg, path, opts) {
|
|
114
|
+
const url = `${cfg.cdnEndpoint}${path}`;
|
|
115
|
+
if (isDryRun()) {
|
|
116
|
+
log.debug('[dry-run] POST', url);
|
|
117
|
+
return DRY_RUN_OK;
|
|
118
|
+
}
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 120_000);
|
|
121
|
+
try {
|
|
122
|
+
const headers = {
|
|
123
|
+
'x-api-key': cfg.cdnApiKey,
|
|
124
|
+
...(opts.extraHeaders ?? {}),
|
|
125
|
+
};
|
|
126
|
+
if (opts.contentType !== undefined)
|
|
127
|
+
headers['content-type'] = opts.contentType;
|
|
128
|
+
const response = await fetch(url, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers,
|
|
131
|
+
body: opts.body,
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
});
|
|
134
|
+
const text = await response.text();
|
|
135
|
+
const body = response.headers.get('content-type')?.includes('application/json')
|
|
136
|
+
? safeJsonParse(text)
|
|
137
|
+
: text;
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
success: response.ok,
|
|
140
|
+
status: response.status,
|
|
141
|
+
body,
|
|
142
|
+
errorMessage: response.ok ? '' : `HTTP ${response.status}: ${text.slice(0, 200)}`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const message = error instanceof Error
|
|
147
|
+
? error.name === 'AbortError'
|
|
148
|
+
? `Request timed out after ${opts.timeoutMs ?? 120_000}ms`
|
|
149
|
+
: error.message
|
|
150
|
+
: String(error);
|
|
151
|
+
return Object.freeze({
|
|
152
|
+
success: false,
|
|
153
|
+
status: 0,
|
|
154
|
+
body: null,
|
|
155
|
+
errorMessage: message,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function safeJsonParse(text) {
|
|
163
|
+
try {
|
|
164
|
+
return JSON.parse(text);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return text;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=cdn.js.map
|