@underpostnet/cyberia 3.2.90 → 3.3.73
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/.dockerignore +1 -0
- package/.env.example +16 -0
- package/.github/workflows/coverall.cyberia.ci.yml +107 -0
- package/.github/workflows/cyberia-client.cd.yml +6 -18
- package/.github/workflows/cyberia-server.cd.yml +6 -18
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +6 -78
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +42 -21
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +2 -9
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +581 -2417
- package/CLI-HELP.md +416 -127
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +70 -43
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +372 -135
- package/bin/deploy.js +18 -104
- package/bin/index.js +372 -135
- package/compose.env +16 -0
- package/conf.js +93 -4
- package/deploy/cyberia-client/deploy.sh +47 -0
- package/deploy/cyberia-client/package.sh +18 -0
- package/deploy/cyberia-client/state.sh +14 -0
- package/deploy/cyberia-server/deploy.sh +47 -0
- package/deploy/cyberia-server/package.sh +18 -0
- package/deploy/cyberia-server/state.sh +14 -0
- package/deploy/dd-cyberia/deploy.sh +22 -0
- package/deploy/dd-cyberia/init.sh +55 -0
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +116 -0
- package/deploy/lib/github-actions-logging.sh +594 -0
- package/deploy/lib/host.sh +185 -0
- package/deploy/lib/state.sh +62 -0
- package/deploy/pwa-microservices-template/deploy.sh +45 -0
- package/deploy/release/deploy.sh +32 -0
- package/deployment.yaml +7 -2
- package/docker-compose.yml +31 -37
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +104 -43
- package/hardhat/package.json +3 -3
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +83 -10
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/event-monitor.sh +56 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +30 -14
- package/scripts/kubeadm-node-setup.sh +40 -31
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/maas-setup.sh +8 -8
- package/scripts/nat-iptables.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +1 -1
- package/src/api/cyberia-action/cyberia-action.service.js +2 -2
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +2 -2
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +16 -29
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.model.js +204 -3
- package/src/api/object-layer/object-layer.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +4 -17
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +391 -0
- package/src/cli/baremetal.js +30 -36
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +242 -135
- package/src/cli/db.js +126 -67
- package/src/cli/deploy.js +158 -60
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs.js +3 -3
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -7
- package/src/cli/index.js +426 -134
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +73 -34
- package/src/cli/repository.js +564 -367
- package/src/cli/run.js +362 -654
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +651 -331
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +199 -125
- package/src/cli/vultr.js +623 -0
- package/src/cli/wireguard.js +2930 -0
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +258 -211
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +17 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +61 -46
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +58 -44
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongooseDB.js +11 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +1 -1
- package/src/index.js +103 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +54 -6
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +15 -24
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +22 -3
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +1 -1
- package/src/projects/cyberia/object-layer.js +12 -22
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +13 -2
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +9 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
- package/src/runtime/engine-cyberia/compose.env +16 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +117 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +465 -0
- package/src/server/build/testing.js +528 -0
- package/src/server/{dns.js → network/dns.js} +241 -14
- package/src/server/network/forward-proxy.js +541 -0
- package/src/server/{middlewares.js → network/middlewares.js} +59 -4
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/network/underpost-compression.js +186 -0
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/ops/cron.js +949 -0
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +108 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/ops/systemd.js +259 -0
- package/src/server/{conf.js → runtime/conf.js} +521 -520
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -13
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/security/selinux.js +185 -0
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +1 -1
- package/src/server/{downloader.js → storage/downloader.js} +2 -2
- package/src/server/storage/repository.js +68 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +2 -1
- package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/integration/infra/1-security/selinux.test.js +69 -0
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
- package/test/integration/infra/2-network/wireguard-edge.test.js +1731 -0
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +47 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +334 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +979 -0
- package/test/unit/coverage-artifact.test.js +130 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +447 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/logger-redaction.test.js +112 -0
- package/test/unit/package.test.js +403 -0
- package/test/unit/prepare-host.test.js +299 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +193 -0
- package/vitest.config.js +58 -0
- package/.nycrc +0 -9
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/cron.js +0 -476
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module coverage-artifact.test
|
|
5
|
+
* @description Covers the coverage report as a build artifact: where a run leaves it, where an
|
|
6
|
+
* assembled deploy artifact carries it, and the guard that keeps a workload container from
|
|
7
|
+
* writing the runtime status contract while a test runner is what is executing.
|
|
8
|
+
*
|
|
9
|
+
* Uses 'chai' for assertions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { expect } from 'chai';
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import {
|
|
15
|
+
COVERAGE_BUNDLE_DIRECTORY,
|
|
16
|
+
bundleCoverageReport,
|
|
17
|
+
coverageReportCandidates,
|
|
18
|
+
coverageUnavailablePage,
|
|
19
|
+
resolveCoverageReportPath,
|
|
20
|
+
} from '../../src/server/build/coverage.js';
|
|
21
|
+
import { EXECUTION_PROFILE_ENV_KEY } from '../../src/server/build/execution.js';
|
|
22
|
+
import { isTestRuntime, runtimeStatusWritable } from '../../src/server/runtime/runtime-status.js';
|
|
23
|
+
|
|
24
|
+
describe('coverage report resolution', () => {
|
|
25
|
+
let fixturePath;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
fixturePath = fs.mkdtempSync('/tmp/engine-coverage-artifact-');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
fs.removeSync(fixturePath);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('looks for a run output first and the bundled artifact last', () => {
|
|
36
|
+
expect(coverageReportCandidates('/src/')).to.deep.equal([
|
|
37
|
+
'/src/coverage/html',
|
|
38
|
+
'/src/coverage/lcov-report',
|
|
39
|
+
'/src/coverage',
|
|
40
|
+
`/src/${COVERAGE_BUNDLE_DIRECTORY}`,
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('has nowhere to look without a source tree', () => {
|
|
45
|
+
expect(coverageReportCandidates(undefined)).to.deep.equal([]);
|
|
46
|
+
expect(resolveCoverageReportPath(undefined)).to.equal(undefined);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('ignores a coverage directory that carries no HTML index', () => {
|
|
50
|
+
fs.outputFileSync(`${fixturePath}/coverage/lcov.info`, 'TN:\n');
|
|
51
|
+
expect(resolveCoverageReportPath(fixturePath)).to.equal(undefined);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('resolves the bundled artifact when no run wrote a report', () => {
|
|
55
|
+
fs.outputFileSync(`${fixturePath}/${COVERAGE_BUNDLE_DIRECTORY}/index.html`, '<!doctype html>');
|
|
56
|
+
expect(resolveCoverageReportPath(fixturePath)).to.equal(`${fixturePath}/${COVERAGE_BUNDLE_DIRECTORY}`);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('coverage bundling into a deploy artifact', () => {
|
|
61
|
+
let fixturePath;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
fixturePath = fs.mkdtempSync('/tmp/engine-coverage-artifact-');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
fs.removeSync(fixturePath);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('carries the report into the artifact at the published path', () => {
|
|
72
|
+
fs.outputFileSync(`${fixturePath}/engine/coverage/lcov-report/index.html`, '<!doctype html><title>report</title>');
|
|
73
|
+
fs.outputFileSync(`${fixturePath}/engine/coverage/lcov-report/base.css`, 'body {}');
|
|
74
|
+
|
|
75
|
+
const result = bundleCoverageReport(`${fixturePath}/engine`, `${fixturePath}/template`);
|
|
76
|
+
|
|
77
|
+
expect(result.bundled).to.equal(true);
|
|
78
|
+
expect(result.to).to.equal(`${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}`);
|
|
79
|
+
expect(fs.readFileSync(`${result.to}/index.html`, 'utf8')).to.include('report');
|
|
80
|
+
expect(fs.existsSync(`${result.to}/base.css`)).to.equal(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('replaces a previously bundled report rather than merging into it', () => {
|
|
84
|
+
fs.outputFileSync(`${fixturePath}/template/${COVERAGE_BUNDLE_DIRECTORY}/stale.html`, 'stale');
|
|
85
|
+
fs.outputFileSync(`${fixturePath}/engine/coverage/html/index.html`, '<!doctype html><title>fresh</title>');
|
|
86
|
+
|
|
87
|
+
const { to } = bundleCoverageReport(`${fixturePath}/engine`, `${fixturePath}/template`);
|
|
88
|
+
|
|
89
|
+
expect(fs.existsSync(`${to}/stale.html`)).to.equal(false);
|
|
90
|
+
expect(fs.readFileSync(`${to}/index.html`, 'utf8')).to.include('fresh');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('reports that nothing was bundled without creating the directory', () => {
|
|
94
|
+
const result = bundleCoverageReport(`${fixturePath}/engine`, `${fixturePath}/template`);
|
|
95
|
+
|
|
96
|
+
expect(result.bundled).to.equal(false);
|
|
97
|
+
expect(fs.existsSync(result.to)).to.equal(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('renders a static unavailable page that never points at a runtime test run', () => {
|
|
101
|
+
const page = coverageUnavailablePage();
|
|
102
|
+
expect(page).to.include('Coverage report unavailable');
|
|
103
|
+
expect(page).to.include('npm run test:coverage');
|
|
104
|
+
expect(page).to.include(COVERAGE_BUNDLE_DIRECTORY);
|
|
105
|
+
expect(page).not.to.include('<script');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Regression: a suite run inside a pod latched `container-status=error` on every expected
|
|
110
|
+
// non-zero exit, and the CD monitor failed the rollout on a deployment that was healthy.
|
|
111
|
+
describe('runtime status contract writability', () => {
|
|
112
|
+
it('recognizes a test runner as the executing context', () => {
|
|
113
|
+
expect(isTestRuntime()).to.equal(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('refuses to write the contract from under a test runner', () => {
|
|
117
|
+
expect(runtimeStatusWritable()).to.equal(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('refuses to write the contract under a non-live execution profile', () => {
|
|
121
|
+
const previous = process.env[EXECUTION_PROFILE_ENV_KEY];
|
|
122
|
+
try {
|
|
123
|
+
process.env[EXECUTION_PROFILE_ENV_KEY] = 'HERMETIC_BUILD';
|
|
124
|
+
expect(runtimeStatusWritable()).to.equal(false);
|
|
125
|
+
} finally {
|
|
126
|
+
if (previous === undefined) delete process.env[EXECUTION_PROFILE_ENV_KEY];
|
|
127
|
+
else process.env[EXECUTION_PROFILE_ENV_KEY] = previous;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { expect } from 'chai';
|
|
10
10
|
|
|
11
11
|
// Import the cryptographic classes from the Canvas's refactored module
|
|
12
|
-
import { SymmetricCrypto, AsymmetricCrypto } from '
|
|
12
|
+
import { SymmetricCrypto, AsymmetricCrypto } from '../../src/server/security/crypto.js';
|
|
13
13
|
|
|
14
14
|
// Define a common plaintext message for testing
|
|
15
15
|
const plaintext = 'This is a secret message for testing cryptographic operations.';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import { deployEnvFactory } from '../../src/server/runtime/environment.js';
|
|
5
|
+
|
|
6
|
+
describe('deploy environment resolution', () => {
|
|
7
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
|
|
11
|
+
else process.env.NODE_ENV = originalNodeEnv;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('prefers an explicit --env over every other signal', () => {
|
|
15
|
+
process.env.NODE_ENV = 'development';
|
|
16
|
+
expect(deployEnvFactory({ env: 'production', dev: true }, 'test')).to.equal('production');
|
|
17
|
+
expect(deployEnvFactory({ env: ' staging ' })).to.equal('staging');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('treats --dev as the development shorthand', () => {
|
|
21
|
+
expect(deployEnvFactory({ dev: true })).to.equal('development');
|
|
22
|
+
expect(deployEnvFactory({ dev: true }, 'test')).to.equal('development');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('falls back to the caller-supplied default, never to ambient NODE_ENV', () => {
|
|
26
|
+
process.env.NODE_ENV = 'development';
|
|
27
|
+
expect(deployEnvFactory({})).to.equal('production');
|
|
28
|
+
expect(deployEnvFactory()).to.equal('production');
|
|
29
|
+
expect(deployEnvFactory({ env: '' }, 'test')).to.equal('test');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('lets a caller opt into the ambient environment by passing it as the fallback', () => {
|
|
33
|
+
process.env.NODE_ENV = 'production';
|
|
34
|
+
expect(deployEnvFactory({}, process.env.NODE_ENV || 'development')).to.equal('production');
|
|
35
|
+
delete process.env.NODE_ENV;
|
|
36
|
+
expect(deployEnvFactory({}, process.env.NODE_ENV || 'development')).to.equal('development');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module deploy-log-table.test
|
|
5
|
+
* @description Covers the run_quiet filter in `deploy/lib/github-actions-logging.sh`: how a
|
|
6
|
+
* deployment monitor's stream is parsed into the live pod table (one row per
|
|
7
|
+
* pod, titled with the iteration and its clock, monitor JSON emits folded into
|
|
8
|
+
* the cells) and how it degrades when stdout is not a terminal. Drives the real shell helper — no cluster, no root.
|
|
9
|
+
*
|
|
10
|
+
* Uses 'chai' for assertions.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { expect } from 'chai';
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import fs from 'fs-extra';
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
21
|
+
const loggingLib = path.join(repoRoot, 'deploy/lib/github-actions-logging.sh');
|
|
22
|
+
|
|
23
|
+
const ESC = '\u001b';
|
|
24
|
+
const POD_A = 'dd-test-production-green-644cbdf488-j7drh';
|
|
25
|
+
const POD_B = 'dd-test-production-green-644cbdf488-abcde';
|
|
26
|
+
|
|
27
|
+
const podLine = (pod, k8s, runtime) =>
|
|
28
|
+
`Target pod: ${ESC}[42;30;1m${pod}${ESC}[0m | Pod status: ${ESC}[33;1m${k8s}${ESC}[0m` +
|
|
29
|
+
` | Runtime status: ${ESC}[36;1m${runtime}${ESC}[0m`;
|
|
30
|
+
|
|
31
|
+
const emit = (clock, phase, state, status) =>
|
|
32
|
+
[
|
|
33
|
+
`[monitor.js] 2026-08-25 ${clock}:911 info deploy-monitor: {`,
|
|
34
|
+
' "deployId": "dd-test-production-green",',
|
|
35
|
+
` "phase": "${phase}",`,
|
|
36
|
+
` "state": "${state}",`,
|
|
37
|
+
` "status": ${status ? `"${status}"` : 'null'},`,
|
|
38
|
+
' "timestamp": "2026-08-25T12:09:11.891Z"',
|
|
39
|
+
'}',
|
|
40
|
+
].join('\n');
|
|
41
|
+
|
|
42
|
+
// The table is padded into columns and painted; both are presentation, so the
|
|
43
|
+
// content assertions compare cells rather than the drawn line.
|
|
44
|
+
const stripAnsi = (text) => text.replace(/\u001b\[[0-9;]*[A-Za-z]/g, '');
|
|
45
|
+
const cells = (line) =>
|
|
46
|
+
stripAnsi(line)
|
|
47
|
+
.split('|')
|
|
48
|
+
.slice(1, -1)
|
|
49
|
+
.map((cell) => cell.trim())
|
|
50
|
+
.join(' | ');
|
|
51
|
+
|
|
52
|
+
// The iteration and its clock title the table rather than occupying two columns
|
|
53
|
+
// of every row, so a row is any line whose first cell is a pod name.
|
|
54
|
+
const isPodRow = (line) => /^\| dd-test/.test(stripAnsi(line));
|
|
55
|
+
const podRows = (out) => out.split('\n').filter(isPodRow).map(cells);
|
|
56
|
+
const titles = (out) =>
|
|
57
|
+
out
|
|
58
|
+
.split('\n')
|
|
59
|
+
.map((line) => stripAnsi(line).trim())
|
|
60
|
+
.filter((line) => line.startsWith('Sync dd-test cluster (refresh #'));
|
|
61
|
+
|
|
62
|
+
const PROGRESS_LINE = '[monitor.js] info [dd-test-production-green] | rollout still in progress';
|
|
63
|
+
const READY_LINE = '[monitor.js] info [dd-test-production-green] | Deployment ready (K8S Ready + runtime)';
|
|
64
|
+
const CHATTER_LINE = '[process.js] info cmd: "kubectl get service dd-test-production-traffic-service -n default"';
|
|
65
|
+
|
|
66
|
+
// POD_B joins one iteration late; every iteration carries its own monitor clock.
|
|
67
|
+
const stream = [
|
|
68
|
+
emit('08:09:11', 'kubernetes', 'pod_scheduled'),
|
|
69
|
+
emit('08:09:11', 'runtime', 'runtime_booting', 'build-deployment'),
|
|
70
|
+
podLine(POD_A, 'ContainerCreating', 'waiting for status (pending)'),
|
|
71
|
+
PROGRESS_LINE,
|
|
72
|
+
emit('08:10:22', 'kubernetes', 'pod_scheduled'),
|
|
73
|
+
emit('08:10:22', 'runtime', 'runtime_booting', 'build-deployment'),
|
|
74
|
+
podLine(POD_A, 'Running', 'build-deployment (pending)'),
|
|
75
|
+
podLine(POD_B, 'ContainerCreating', 'waiting for status (pending)'),
|
|
76
|
+
emit('08:11:33', 'kubernetes', 'pod_ready'),
|
|
77
|
+
podLine(POD_A, 'Running', 'running-deployment'),
|
|
78
|
+
podLine(POD_B, 'Running', 'running-deployment'),
|
|
79
|
+
// The monitor confirms both phases only after the last report of the iteration.
|
|
80
|
+
emit('08:11:33', 'runtime', 'runtime_ready', 'running-deployment'),
|
|
81
|
+
READY_LINE,
|
|
82
|
+
CHATTER_LINE,
|
|
83
|
+
].join('\n');
|
|
84
|
+
|
|
85
|
+
let workDir;
|
|
86
|
+
let streamPath;
|
|
87
|
+
|
|
88
|
+
const runQuiet = ({ tty = false, plain = false, ci = false, exitCode = 0 } = {}) => {
|
|
89
|
+
const command = `exit ${exitCode}`;
|
|
90
|
+
const script = [
|
|
91
|
+
'status=0',
|
|
92
|
+
`source ${JSON.stringify(loggingLib)}`,
|
|
93
|
+
`run_quiet "Sync dd-test cluster" "Target pod:" 14 bash -c 'cat ${JSON.stringify(streamPath)}; ${command}' || status=$?`,
|
|
94
|
+
'exit $status',
|
|
95
|
+
].join('\n');
|
|
96
|
+
const scriptPath = path.join(workDir, 'run.sh');
|
|
97
|
+
fs.writeFileSync(scriptPath, script);
|
|
98
|
+
|
|
99
|
+
const env = { ...process.env, TERM: 'xterm', COLUMNS: '140', LINES: '40' };
|
|
100
|
+
delete env.NO_COLOR;
|
|
101
|
+
if (plain) env.RUN_QUIET_PLAIN = '1';
|
|
102
|
+
else delete env.RUN_QUIET_PLAIN;
|
|
103
|
+
// The helper picks its rendering from the environment, so a run on a GitHub
|
|
104
|
+
// runner would otherwise answer these cases in CI mode and a run on a laptop
|
|
105
|
+
// would not. Every case names the mode it means to exercise.
|
|
106
|
+
delete env.GITHUB_ACTIONS;
|
|
107
|
+
delete env.RUN_QUIET_CI;
|
|
108
|
+
if (ci) env.RUN_QUIET_CI = 'github';
|
|
109
|
+
|
|
110
|
+
const [file, args] = tty ? ['script', ['-qec', `bash ${scriptPath}`, '/dev/null']] : ['bash', [scriptPath]];
|
|
111
|
+
return execFileSync(file, args, { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// `script` (util-linux) is the only way to hand the helper a real pty here.
|
|
115
|
+
const hasScript = (() => {
|
|
116
|
+
try {
|
|
117
|
+
execFileSync('script', ['--version'], { stdio: 'ignore' });
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
|
|
124
|
+
describe('deploy failure propagation (run_quiet exit status)', () => {
|
|
125
|
+
let failDir;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Runs the helper against an arbitrary stream and reports the exit status rather than
|
|
129
|
+
* throwing, so a failing step is an assertion rather than a caught exception.
|
|
130
|
+
*/
|
|
131
|
+
const runWith = (lines, exitCode = 0) => {
|
|
132
|
+
const logPath = path.join(failDir, 'stream.log');
|
|
133
|
+
fs.writeFileSync(logPath, `${lines.join('\n')}\n`);
|
|
134
|
+
const scriptPath = path.join(failDir, 'run.sh');
|
|
135
|
+
fs.writeFileSync(
|
|
136
|
+
scriptPath,
|
|
137
|
+
[
|
|
138
|
+
'set -euo pipefail',
|
|
139
|
+
`source ${JSON.stringify(loggingLib)}`,
|
|
140
|
+
`run_quiet "Deploy dd-test" "Target pod:" 14 bash -c 'cat ${JSON.stringify(logPath)}; exit ${exitCode}'`,
|
|
141
|
+
'echo REACHED_NEXT_STEP',
|
|
142
|
+
].join('\n'),
|
|
143
|
+
);
|
|
144
|
+
const env = { ...process.env, RUN_QUIET_PLAIN: '1' };
|
|
145
|
+
delete env.GITHUB_ACTIONS;
|
|
146
|
+
delete env.RUN_QUIET_CI;
|
|
147
|
+
try {
|
|
148
|
+
const stdout = execFileSync('bash', [scriptPath], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
149
|
+
return { status: 0, stdout, stderr: '' };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return { status: error.status, stdout: `${error.stdout ?? ''}`, stderr: `${error.stderr ?? ''}` };
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
failDir = fs.mkdtempSync(path.join(os.tmpdir(), 'run-quiet-fail-'));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
afterEach(() => fs.removeSync(failDir));
|
|
160
|
+
|
|
161
|
+
it('propagates a non-zero command status so `set -e` stops the deployment', () => {
|
|
162
|
+
const result = runWith([podLine(POD_A, 'Running', 'running-deployment')], 7);
|
|
163
|
+
expect(result.status).to.equal(7);
|
|
164
|
+
expect(result.stdout).to.not.include('REACHED_NEXT_STEP');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('keeps the exit code and both log paths when the log carries no error marker', () => {
|
|
168
|
+
// Regression: `run_quiet_error_trace` greps the log for a marker, and grep exits 1 when it
|
|
169
|
+
// matches nothing. Under the `set -euo pipefail` every deploy script runs, that aborted the
|
|
170
|
+
// helper before it could report anything — the step failed as a bare 1 with no exit code,
|
|
171
|
+
// no error trace path, and no debug log path, which is the whole diagnostic.
|
|
172
|
+
const result = runWith([podLine(POD_A, 'Running', 'running-deployment')], 7);
|
|
173
|
+
expect(result.status).to.equal(7);
|
|
174
|
+
expect(result.stderr).to.include('failed (exit 7)');
|
|
175
|
+
expect(result.stderr).to.include('error trace:');
|
|
176
|
+
expect(result.stderr).to.include('debug log:');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('fails a step that latched a fatal runtime state but exited 0', () => {
|
|
180
|
+
// The in-pod lifecycle sets `container-status=error` and deliberately does not crash the
|
|
181
|
+
// container, so the exit status alone would report a failed deployment as a green step —
|
|
182
|
+
// and a green step is a green GitHub Actions job.
|
|
183
|
+
const result = runWith([podLine(POD_A, 'Running', 'error')], 0);
|
|
184
|
+
expect(result.status).to.equal(1);
|
|
185
|
+
expect(result.stdout).to.not.include('REACHED_NEXT_STEP');
|
|
186
|
+
expect(result.stderr).to.include('fatal runtime state but exited 0');
|
|
187
|
+
expect(result.stderr).to.include(POD_A);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('fails on a fatal runtime status carried by a monitor event rather than a pod row', () => {
|
|
191
|
+
const result = runWith(
|
|
192
|
+
[
|
|
193
|
+
podLine(POD_A, 'Running', 'waiting for status (pending)'),
|
|
194
|
+
emit('08:11:33', 'runtime', 'runtime_error', 'error'),
|
|
195
|
+
],
|
|
196
|
+
0,
|
|
197
|
+
);
|
|
198
|
+
expect(result.status).to.equal(1);
|
|
199
|
+
expect(result.stderr).to.include('runtime event: status=error');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('never hands off to the traffic switch on a pod reporting the fatal state', () => {
|
|
203
|
+
// `error` used to satisfy all_pods_running(), so the filter announced the hand-off and the
|
|
204
|
+
// deployment switched traffic onto a runtime that had already failed.
|
|
205
|
+
const result = runWith([podLine(POD_A, 'Running', 'error')], 0);
|
|
206
|
+
expect(result.stdout).to.not.include('Switch traffic');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('leaves a healthy deployment green and still hands off', () => {
|
|
210
|
+
const result = runWith(
|
|
211
|
+
[
|
|
212
|
+
podLine(POD_A, 'Running', 'running-deployment'),
|
|
213
|
+
emit('08:11:33', 'kubernetes', 'pod_ready'),
|
|
214
|
+
emit('08:11:33', 'runtime', 'runtime_ready', 'running-deployment'),
|
|
215
|
+
CHATTER_LINE,
|
|
216
|
+
],
|
|
217
|
+
0,
|
|
218
|
+
);
|
|
219
|
+
expect(result.status).to.equal(0);
|
|
220
|
+
expect(result.stdout).to.include('REACHED_NEXT_STEP');
|
|
221
|
+
expect(result.stdout).to.include('Switch traffic');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('does not read a namespaced phase that merely contains the fatal word as fatal', () => {
|
|
225
|
+
const result = runWith([podLine(POD_A, 'Running', 'dd-test-production-error-handler-deployment')], 0);
|
|
226
|
+
expect(result.status).to.equal(0);
|
|
227
|
+
expect(result.stdout).to.include('REACHED_NEXT_STEP');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('deploy log table (run_quiet filter)', () => {
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'run-quiet-'));
|
|
234
|
+
streamPath = path.join(workDir, 'stream.log');
|
|
235
|
+
fs.writeFileSync(streamPath, `${stream}\n`);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
afterEach(() => fs.removeSync(workDir));
|
|
239
|
+
|
|
240
|
+
it('never prints the monitor JSON emits', () => {
|
|
241
|
+
const out = runQuiet();
|
|
242
|
+
expect(out).to.not.include('deploy-monitor');
|
|
243
|
+
expect(out).to.not.include('"phase"');
|
|
244
|
+
expect(out).to.not.include('"timestamp"');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('appends one row per pod state change, carrying only the three pod cells', () => {
|
|
248
|
+
expect(podRows(runQuiet())).to.deep.equal([
|
|
249
|
+
`${POD_A} | ContainerCreating | waiting for status (pending)`,
|
|
250
|
+
`${POD_A} | Running | build-deployment (pending)`,
|
|
251
|
+
`${POD_B} | ContainerCreating | waiting for status (pending)`,
|
|
252
|
+
// Readiness arrives as an event, before the iteration that reports it,
|
|
253
|
+
// and only annotates a pod whose own phase already agrees.
|
|
254
|
+
`${POD_A} | Running (ready) | build-deployment (pending)`,
|
|
255
|
+
`${POD_A} | Running (ready) | running-deployment`,
|
|
256
|
+
`${POD_B} | Running (ready) | running-deployment`,
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('titles each iteration from its own monitor clock, never the UTC emit field', () => {
|
|
261
|
+
const out = runQuiet();
|
|
262
|
+
|
|
263
|
+
expect(out).to.not.include('12:09:11');
|
|
264
|
+
expect(titles(out)).to.deep.equal([
|
|
265
|
+
'Sync dd-test cluster (refresh #1, 08:09:11)',
|
|
266
|
+
'Sync dd-test cluster (refresh #2, 08:10:22)',
|
|
267
|
+
'Sync dd-test cluster (refresh #3, 08:11:33)',
|
|
268
|
+
]);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Colour is SGR only, which a log viewer that is not a terminal still renders.
|
|
272
|
+
it('paints the appended rows unless colour is turned off', () => {
|
|
273
|
+
expect(runQuiet()).to.include('\u001b[');
|
|
274
|
+
expect(runQuiet({ plain: true })).to.not.include('\u001b[');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('heads the appended rows once and keeps non-monitor lines', () => {
|
|
278
|
+
const out = runQuiet();
|
|
279
|
+
const headers = out.split('\n').filter((line) => line.includes('POD NAME'));
|
|
280
|
+
|
|
281
|
+
expect(headers).to.have.lengthOf(1);
|
|
282
|
+
expect(cells(headers[0])).to.equal('POD NAME | K8S STATUS | RUNTIME STATUS');
|
|
283
|
+
expect(out).to.include(PROGRESS_LINE);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// GitHub renders no cursor motion, so the live region becomes a collapsed
|
|
287
|
+
// section and the settled table is reprinted outside it. This is the mode CI
|
|
288
|
+
// actually runs in, and it is reached only through the environment.
|
|
289
|
+
it('folds the frames into a CI group and reprints the settled table after it', () => {
|
|
290
|
+
const out = runQuiet({ ci: true });
|
|
291
|
+
const lines = out.split('\n');
|
|
292
|
+
|
|
293
|
+
expect(out).to.not.include('\u001b[A');
|
|
294
|
+
expect(lines.filter((line) => line === '::group::Sync dd-test cluster monitor frames')).to.have.lengthOf(2);
|
|
295
|
+
expect(lines.filter((line) => line === '::endgroup::')).to.have.lengthOf(2);
|
|
296
|
+
|
|
297
|
+
const settled = lines.slice(lines.lastIndexOf('::endgroup::') + 1);
|
|
298
|
+
|
|
299
|
+
expect(settled.filter(isPodRow).map(cells)).to.deep.equal([
|
|
300
|
+
`${POD_A} | Running (ready) | running-deployment`,
|
|
301
|
+
`${POD_B} | Running (ready) | running-deployment`,
|
|
302
|
+
]);
|
|
303
|
+
expect(settled.map(stripAnsi)).to.include('Sync dd-test cluster (refresh #3, 08:11:33)');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('opens no CI group when nothing is there to collapse one', () => {
|
|
307
|
+
expect(runQuiet()).to.not.include('::group::');
|
|
308
|
+
expect(runQuiet({ ci: true, plain: true })).to.not.include('::group::');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('labels the traffic switch once every pod runs, and drops the chatter after it', () => {
|
|
312
|
+
const out = runQuiet();
|
|
313
|
+
const lines = out.trimEnd().split('\n');
|
|
314
|
+
|
|
315
|
+
expect(lines.filter((line) => line.includes('▶ Switch traffic'))).to.have.lengthOf(1);
|
|
316
|
+
expect(lines[lines.length - 1]).to.include('▶ Switch traffic');
|
|
317
|
+
expect(out).to.not.include(READY_LINE);
|
|
318
|
+
expect(out).to.not.include(CHATTER_LINE);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('falls back to plain rows on a terminal when RUN_QUIET_PLAIN is set', function () {
|
|
322
|
+
if (!hasScript) return this.skip();
|
|
323
|
+
const out = runQuiet({ tty: true, plain: true });
|
|
324
|
+
|
|
325
|
+
expect(out).to.not.include('\u001b[');
|
|
326
|
+
expect(podRows(out)).to.include(`${POD_A} | Running (ready) | running-deployment`);
|
|
327
|
+
expect(titles(out)).to.include('Sync dd-test cluster (refresh #3, 08:11:33)');
|
|
328
|
+
expect(out).to.not.include('deploy-monitor');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('redraws a single aligned table in place on a terminal', function () {
|
|
332
|
+
if (!hasScript) return this.skip();
|
|
333
|
+
const out = runQuiet({ tty: true });
|
|
334
|
+
const rendered = render(out, 140, 40);
|
|
335
|
+
const rows = rendered.filter(isPodRow);
|
|
336
|
+
|
|
337
|
+
// Every repaint but the first is preceded by a cursor-up over the title, the
|
|
338
|
+
// two header lines and the rows it replaces.
|
|
339
|
+
expect(out).to.include('\u001b[5A');
|
|
340
|
+
expect(rows).to.have.lengthOf(2);
|
|
341
|
+
expect(rows[0]).to.include(POD_A).and.to.include('Running (ready)');
|
|
342
|
+
expect(rows[1]).to.include(POD_B);
|
|
343
|
+
expect(rendered.map(stripAnsi)).to.include('Sync dd-test cluster (refresh #3, 08:11:33)');
|
|
344
|
+
expect(rendered.filter((line) => line.includes('POD NAME'))).to.have.lengthOf(1);
|
|
345
|
+
expect(rendered.some((line) => line.includes(PROGRESS_LINE))).to.equal(true);
|
|
346
|
+
// The handoff label closes the run below the last frame of the table.
|
|
347
|
+
expect(rendered[rendered.length - 1]).to.include('▶ Switch traffic');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('opens a fresh table for a later monitor in the same command', () => {
|
|
351
|
+
const second = [
|
|
352
|
+
emit('08:20:00', 'kubernetes', 'pod_scheduled'),
|
|
353
|
+
podLine('dd-test-production-blue-77d9f4c6b8-kkq2z', 'Running', 'build-deployment (pending)'),
|
|
354
|
+
PROGRESS_LINE,
|
|
355
|
+
emit('08:21:00', 'kubernetes', 'pod_ready'),
|
|
356
|
+
podLine('dd-test-production-blue-77d9f4c6b8-kkq2z', 'Running', 'running-deployment'),
|
|
357
|
+
emit('08:21:00', 'runtime', 'runtime_ready', 'running-deployment'),
|
|
358
|
+
CHATTER_LINE,
|
|
359
|
+
].join('\n');
|
|
360
|
+
fs.writeFileSync(streamPath, `${stream}\n${second}\n`);
|
|
361
|
+
|
|
362
|
+
const out = runQuiet();
|
|
363
|
+
const rows = podRows(out);
|
|
364
|
+
|
|
365
|
+
expect(out.match(/▶ Switch traffic/g)).to.have.lengthOf(2);
|
|
366
|
+
expect(rows[rows.length - 1]).to.equal(
|
|
367
|
+
'dd-test-production-blue-77d9f4c6b8-kkq2z | Running (ready) | running-deployment',
|
|
368
|
+
);
|
|
369
|
+
// The second table counts its own iterations, from its own clock.
|
|
370
|
+
expect(titles(out)).to.include('Sync dd-test cluster (refresh #2, 08:21:00)');
|
|
371
|
+
expect(out).to.not.include(CHATTER_LINE);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('propagates the command status and keeps the debug paths on failure', () => {
|
|
375
|
+
let thrown;
|
|
376
|
+
try {
|
|
377
|
+
runQuiet({ exitCode: 7 });
|
|
378
|
+
} catch (error) {
|
|
379
|
+
thrown = error;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
expect(thrown, 'run_quiet must fail the deployment').to.not.equal(undefined);
|
|
383
|
+
expect(thrown.status).to.equal(7);
|
|
384
|
+
|
|
385
|
+
const report = thrown.stderr.replace(/\u001b\[[0-9;]*[A-Za-z]/g, '');
|
|
386
|
+
expect(report).to.include('failed (exit 7)');
|
|
387
|
+
expect(report).to.include('error trace:');
|
|
388
|
+
expect(report).to.include('debug log:');
|
|
389
|
+
|
|
390
|
+
const debugLog = report.match(/debug log:\s+(\S+)/)[1];
|
|
391
|
+
// The unfiltered stream survives for debugging even though it never printed.
|
|
392
|
+
expect(fs.readFileSync(debugLog, 'utf8')).to.include('deploy-monitor');
|
|
393
|
+
fs.removeSync(debugLog);
|
|
394
|
+
fs.removeSync(report.match(/error trace:\s+(\S+)/)[1]);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @function render
|
|
400
|
+
* @description Replays the captured pty stream onto a virtual screen: cursor
|
|
401
|
+
* ups, erase-to-end-of-line and erase-to-end-of-screen only, which is all the
|
|
402
|
+
* filter emits.
|
|
403
|
+
* @param {string} output - Raw stream captured from the pty.
|
|
404
|
+
* @param {number} columns - Screen width.
|
|
405
|
+
* @param {number} lines - Screen height.
|
|
406
|
+
* @returns {string[]} Non-empty screen rows, top to bottom.
|
|
407
|
+
*/
|
|
408
|
+
function render(output, columns, lines) {
|
|
409
|
+
const screen = Array.from({ length: lines }, () => '');
|
|
410
|
+
let row = 0;
|
|
411
|
+
let column = 0;
|
|
412
|
+
const write = (text) => {
|
|
413
|
+
const line = screen[row] ?? '';
|
|
414
|
+
screen[row] = (line.padEnd(column, ' ') + text).slice(0, columns);
|
|
415
|
+
column = screen[row].length;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
for (let i = 0; i < output.length; i++) {
|
|
419
|
+
const character = output[i];
|
|
420
|
+
if (character === '\u001b' && output[i + 1] === '[') {
|
|
421
|
+
const sequence = /^\u001b\[([0-9;]*)([A-Za-z])/.exec(output.slice(i));
|
|
422
|
+
if (sequence) {
|
|
423
|
+
const [match, parameters, command] = sequence;
|
|
424
|
+
if (command === 'A') row = Math.max(0, row - (Number(parameters) || 1));
|
|
425
|
+
if (command === 'K') screen[row] = (screen[row] ?? '').slice(0, column);
|
|
426
|
+
if (command === 'J') {
|
|
427
|
+
screen[row] = (screen[row] ?? '').slice(0, column);
|
|
428
|
+
for (let below = row + 1; below < lines; below++) screen[below] = '';
|
|
429
|
+
}
|
|
430
|
+
i += match.length - 1;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (character === '\n') {
|
|
435
|
+
row = Math.min(lines - 1, row + 1);
|
|
436
|
+
column = 0;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (character === '\r') {
|
|
440
|
+
column = 0;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
write(character);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return screen.filter((line) => line.trim().length > 0);
|
|
447
|
+
}
|