@underpostnet/cyberia 3.3.0 → 3.3.76
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 +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +39 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- 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 +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +653 -2513
- package/CLI-HELP.md +323 -157
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +5 -5
- package/README.md +48 -28
- package/bin/build.js +86 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +1494 -1403
- package/bin/deploy.js +18 -104
- package/bin/index.js +1494 -1403
- package/compose.env +17 -5
- package/conf.js +98 -7
- package/deploy/cyberia-client/deploy.sh +46 -0
- package/deploy/cyberia-client/package.sh +17 -0
- package/deploy/cyberia-client/state.sh +15 -0
- package/deploy/cyberia-server/deploy.sh +46 -0
- package/deploy/cyberia-server/package.sh +17 -0
- package/deploy/cyberia-server/state.sh +15 -0
- package/deploy/dd-cyberia/deploy.sh +9 -29
- package/deploy/dd-cyberia/init.sh +38 -45
- package/deploy/dd-cyberia/package.sh +16 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +133 -147
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +610 -0
- package/deploy/lib/host.sh +194 -0
- package/deploy/lib/state.sh +65 -0
- package/deploy/pwa-microservices-template/deploy.sh +17 -42
- package/deploy/release/deploy.sh +9 -41
- package/deployment.yaml +7 -2
- package/docker-compose.yml +23 -17
- 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 +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- 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 +87 -12
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +5 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
- 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 +3 -3
- package/src/api/cyberia-action/cyberia-action.service.js +5 -6
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- 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.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
- package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
- package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
- 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 +8 -4
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.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 +403 -214
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- 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.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- 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 +47 -12
- package/src/api/object-layer/object-layer.router.js +12 -5
- package/src/api/object-layer/object-layer.service.js +116 -123
- 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 +402 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +191 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +157 -75
- 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-selection.js +115 -0
- package/src/cli/fs.js +159 -343
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +343 -151
- 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 +16 -12
- package/src/cli/repository.js +586 -363
- package/src/cli/run.js +398 -843
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +203 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1112 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +327 -303
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/Modal.js +56 -1
- 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/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- 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 +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- 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/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- 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-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- 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 +92 -77
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +105 -74
- package/src/client-builder/client-bundle.js +347 -0
- 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/MongoExpress.js +187 -0
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +6 -8
- package/src/index.js +79 -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 +144 -147
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +8 -17
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +54 -10
- package/src/projects/cyberia/gemini-client.js +4 -4
- package/src/projects/cyberia/generate-saga.js +5 -12
- package/src/projects/cyberia/hot-reload-trigger.js +18 -29
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +178 -258
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +33 -69
- package/src/projects/cyberia/object-layer.js +153 -320
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +3 -19
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +17 -8
- package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
- package/src/runtime/cyberia-server/Dockerfile +18 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- 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 +188 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +508 -0
- package/src/server/build/testing.js +578 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +11 -5
- 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/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +143 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +516 -423
- 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 -12
- 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/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +17 -1
- package/src/server/storage/downloader.js +121 -0
- package/src/server/storage/repository.js +68 -0
- package/src/server/storage/zip.js +153 -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/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -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/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- 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 +1187 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
- 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/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -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} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -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 +361 -0
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +996 -0
- package/test/unit/coverage-artifact.test.js +221 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +518 -0
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/logger-redaction.test.js +141 -0
- package/test/unit/package.test.js +457 -0
- package/test/unit/prepare-host.test.js +382 -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 +220 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +66 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- 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/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
- 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/downloader.js +0 -73
- package/src/server/environment.js +0 -98
|
@@ -0,0 +1,518 @@
|
|
|
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, debug = 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
|
+
delete env.RUN_QUIET_DEBUG;
|
|
110
|
+
if (debug) env.RUN_QUIET_DEBUG = '1';
|
|
111
|
+
|
|
112
|
+
const [file, args] = tty ? ['script', ['-qec', `bash ${scriptPath}`, '/dev/null']] : ['bash', [scriptPath]];
|
|
113
|
+
return execFileSync(file, args, { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// `script` (util-linux) is the only way to hand the helper a real pty here.
|
|
117
|
+
const hasScript = (() => {
|
|
118
|
+
try {
|
|
119
|
+
execFileSync('script', ['--version'], { stdio: 'ignore' });
|
|
120
|
+
return true;
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
})();
|
|
125
|
+
|
|
126
|
+
describe('deploy failure propagation (run_quiet exit status)', () => {
|
|
127
|
+
let failDir;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Runs the helper against an arbitrary stream and reports the exit status rather than
|
|
131
|
+
* throwing, so a failing step is an assertion rather than a caught exception.
|
|
132
|
+
*/
|
|
133
|
+
const runWith = (lines, exitCode = 0, mode = { RUN_QUIET_PLAIN: '1' }) => {
|
|
134
|
+
const logPath = path.join(failDir, 'stream.log');
|
|
135
|
+
fs.writeFileSync(logPath, `${lines.join('\n')}\n`);
|
|
136
|
+
const scriptPath = path.join(failDir, 'run.sh');
|
|
137
|
+
fs.writeFileSync(
|
|
138
|
+
scriptPath,
|
|
139
|
+
[
|
|
140
|
+
'set -euo pipefail',
|
|
141
|
+
`source ${JSON.stringify(loggingLib)}`,
|
|
142
|
+
`run_quiet "Deploy dd-test" "Target pod:" 14 bash -c 'cat ${JSON.stringify(logPath)}; exit ${exitCode}'`,
|
|
143
|
+
'echo REACHED_NEXT_STEP',
|
|
144
|
+
].join('\n'),
|
|
145
|
+
);
|
|
146
|
+
const env = { ...process.env };
|
|
147
|
+
delete env.GITHUB_ACTIONS;
|
|
148
|
+
delete env.RUN_QUIET_CI;
|
|
149
|
+
delete env.RUN_QUIET_PLAIN;
|
|
150
|
+
delete env.RUN_QUIET_DEBUG;
|
|
151
|
+
Object.assign(env, mode);
|
|
152
|
+
try {
|
|
153
|
+
const stdout = execFileSync('bash', [scriptPath], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
154
|
+
return { status: 0, stdout, stderr: '' };
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return { status: error.status, stdout: `${error.stdout ?? ''}`, stderr: `${error.stderr ?? ''}` };
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
failDir = fs.mkdtempSync(path.join(os.tmpdir(), 'run-quiet-fail-'));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
afterEach(() => fs.removeSync(failDir));
|
|
165
|
+
|
|
166
|
+
// The failure report names two files and prints nothing out of them, so what the step captured
|
|
167
|
+
// is read back from the file rather than from the transcript.
|
|
168
|
+
const errorLog = (result) => {
|
|
169
|
+
const named = /error trace:\s+(\S+)/.exec(result.stderr);
|
|
170
|
+
expect(named, 'the failure report names an error trace').to.not.equal(null);
|
|
171
|
+
return fs.readFileSync(named[1], 'utf8');
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
it('latches a fatal runtime state in every rendering, including the debug stream', () => {
|
|
175
|
+
// The latch is parsed, not rendered. Turning the display off must not turn it off with it, or
|
|
176
|
+
// an audit run reports success on a deployment that never came up.
|
|
177
|
+
const podFatal = 'Target pod: dd-test-blue | Pod status: Running | Runtime status: error';
|
|
178
|
+
|
|
179
|
+
for (const [name, mode] of [
|
|
180
|
+
['plain', { RUN_QUIET_PLAIN: '1' }],
|
|
181
|
+
['ci', { RUN_QUIET_CI: 'github' }],
|
|
182
|
+
['debug', { RUN_QUIET_DEBUG: '1' }],
|
|
183
|
+
]) {
|
|
184
|
+
const result = runWith([podFatal], 0, mode);
|
|
185
|
+
expect(result.status, name).to.equal(1);
|
|
186
|
+
expect(result.stderr, name).to.include('reported a fatal runtime state but exited 0');
|
|
187
|
+
expect(result.stdout, name).to.not.include('REACHED_NEXT_STEP');
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('latches a fatal runtime state reported only by a monitor event, in the debug stream too', () => {
|
|
192
|
+
// The event arrives as a multi-line JSON block, so the state machine that reads it has to run
|
|
193
|
+
// in a mode that renders none of it.
|
|
194
|
+
const emitted = emit('08:11:33', 'runtime', 'runtime_error', 'error').split('\n');
|
|
195
|
+
const result = runWith(emitted, 0, { RUN_QUIET_DEBUG: '1' });
|
|
196
|
+
|
|
197
|
+
expect(result.status).to.equal(1);
|
|
198
|
+
expect(result.stdout, 'debug shows the event it latched on').to.include('"status": "error"');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('propagates a non-zero command status so `set -e` stops the deployment', () => {
|
|
202
|
+
const result = runWith([podLine(POD_A, 'Running', 'running-deployment')], 7);
|
|
203
|
+
expect(result.status).to.equal(7);
|
|
204
|
+
expect(result.stdout).to.not.include('REACHED_NEXT_STEP');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('keeps the exit code and both log paths when the log carries no error marker', () => {
|
|
208
|
+
// Regression: `run_quiet_error_trace` greps the log for a marker, and grep exits 1 when it
|
|
209
|
+
// matches nothing. Under the `set -euo pipefail` every deploy script runs, that aborted the
|
|
210
|
+
// helper before it could report anything — the step failed as a bare 1 with no exit code,
|
|
211
|
+
// no error trace path, and no debug log path, which is the whole diagnostic.
|
|
212
|
+
const result = runWith([podLine(POD_A, 'Running', 'running-deployment')], 7);
|
|
213
|
+
expect(result.status).to.equal(7);
|
|
214
|
+
expect(result.stderr).to.include('failed (exit 7)');
|
|
215
|
+
expect(result.stderr).to.include('error trace:');
|
|
216
|
+
expect(result.stderr).to.include('debug log:');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('fails a step that latched a fatal runtime state but exited 0', () => {
|
|
220
|
+
// The in-pod lifecycle sets `container-status=error` and deliberately does not crash the
|
|
221
|
+
// container, so the exit status alone would report a failed deployment as a green step —
|
|
222
|
+
// and a green step is a green GitHub Actions job.
|
|
223
|
+
const result = runWith([podLine(POD_A, 'Running', 'error')], 0);
|
|
224
|
+
expect(result.status).to.equal(1);
|
|
225
|
+
expect(result.stdout).to.not.include('REACHED_NEXT_STEP');
|
|
226
|
+
expect(result.stderr).to.include('fatal runtime state but exited 0');
|
|
227
|
+
// The pod name is captured output: a CI transcript is readable by anyone with the job log,
|
|
228
|
+
// so the latch detail belongs in the error log and nowhere else.
|
|
229
|
+
expect(result.stderr).to.not.include(POD_A);
|
|
230
|
+
expect(errorLog(result)).to.include(`pod ${POD_A}: runtime status=error`);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('fails on a fatal runtime status carried by a monitor event rather than a pod row', () => {
|
|
234
|
+
const result = runWith(
|
|
235
|
+
[
|
|
236
|
+
podLine(POD_A, 'Running', 'waiting for status (pending)'),
|
|
237
|
+
emit('08:11:33', 'runtime', 'runtime_error', 'error'),
|
|
238
|
+
],
|
|
239
|
+
0,
|
|
240
|
+
);
|
|
241
|
+
expect(result.status).to.equal(1);
|
|
242
|
+
expect(result.stderr).to.not.include('status=error');
|
|
243
|
+
expect(errorLog(result)).to.include('runtime event: status=error');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('never hands off to the traffic switch on a pod reporting the fatal state', () => {
|
|
247
|
+
// `error` used to satisfy all_pods_running(), so the filter announced the hand-off and the
|
|
248
|
+
// deployment switched traffic onto a runtime that had already failed.
|
|
249
|
+
const result = runWith([podLine(POD_A, 'Running', 'error')], 0);
|
|
250
|
+
expect(result.stdout).to.not.include('Switch traffic');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('leaves a healthy deployment green and still hands off', () => {
|
|
254
|
+
const result = runWith(
|
|
255
|
+
[
|
|
256
|
+
podLine(POD_A, 'Running', 'running-deployment'),
|
|
257
|
+
emit('08:11:33', 'kubernetes', 'pod_ready'),
|
|
258
|
+
emit('08:11:33', 'runtime', 'runtime_ready', 'running-deployment'),
|
|
259
|
+
CHATTER_LINE,
|
|
260
|
+
],
|
|
261
|
+
0,
|
|
262
|
+
);
|
|
263
|
+
expect(result.status).to.equal(0);
|
|
264
|
+
expect(result.stdout).to.include('REACHED_NEXT_STEP');
|
|
265
|
+
expect(result.stdout).to.include('Switch traffic');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('does not read a namespaced phase that merely contains the fatal word as fatal', () => {
|
|
269
|
+
const result = runWith([podLine(POD_A, 'Running', 'dd-test-production-error-handler-deployment')], 0);
|
|
270
|
+
expect(result.status).to.equal(0);
|
|
271
|
+
expect(result.stdout).to.include('REACHED_NEXT_STEP');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('deploy log table (run_quiet filter)', () => {
|
|
276
|
+
beforeEach(() => {
|
|
277
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'run-quiet-'));
|
|
278
|
+
streamPath = path.join(workDir, 'stream.log');
|
|
279
|
+
fs.writeFileSync(streamPath, `${stream}\n`);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
afterEach(() => fs.removeSync(workDir));
|
|
283
|
+
|
|
284
|
+
it('never prints the monitor JSON emits', () => {
|
|
285
|
+
const out = runQuiet();
|
|
286
|
+
expect(out).to.not.include('deploy-monitor');
|
|
287
|
+
expect(out).to.not.include('"phase"');
|
|
288
|
+
expect(out).to.not.include('"timestamp"');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('appends one row per pod state change, carrying only the three pod cells', () => {
|
|
292
|
+
expect(podRows(runQuiet())).to.deep.equal([
|
|
293
|
+
`${POD_A} | ContainerCreating | waiting for status (pending)`,
|
|
294
|
+
`${POD_A} | Running | build-deployment (pending)`,
|
|
295
|
+
`${POD_B} | ContainerCreating | waiting for status (pending)`,
|
|
296
|
+
// Readiness arrives as an event, before the iteration that reports it,
|
|
297
|
+
// and only annotates a pod whose own phase already agrees.
|
|
298
|
+
`${POD_A} | Running (ready) | build-deployment (pending)`,
|
|
299
|
+
`${POD_A} | Running (ready) | running-deployment`,
|
|
300
|
+
`${POD_B} | Running (ready) | running-deployment`,
|
|
301
|
+
]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('titles each iteration from its own monitor clock, never the UTC emit field', () => {
|
|
305
|
+
const out = runQuiet();
|
|
306
|
+
|
|
307
|
+
expect(out).to.not.include('12:09:11');
|
|
308
|
+
expect(titles(out)).to.deep.equal([
|
|
309
|
+
'Sync dd-test cluster (refresh #1, 08:09:11)',
|
|
310
|
+
'Sync dd-test cluster (refresh #2, 08:10:22)',
|
|
311
|
+
'Sync dd-test cluster (refresh #3, 08:11:33)',
|
|
312
|
+
]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Colour is SGR only, which a log viewer that is not a terminal still renders.
|
|
316
|
+
it('paints the appended rows unless colour is turned off', () => {
|
|
317
|
+
expect(runQuiet()).to.include('\u001b[');
|
|
318
|
+
expect(runQuiet({ plain: true })).to.not.include('\u001b[');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('heads the appended rows once and keeps non-monitor lines', () => {
|
|
322
|
+
const out = runQuiet();
|
|
323
|
+
const headers = out.split('\n').filter((line) => line.includes('POD NAME'));
|
|
324
|
+
|
|
325
|
+
expect(headers).to.have.lengthOf(1);
|
|
326
|
+
expect(cells(headers[0])).to.equal('POD NAME | K8S STATUS | RUNTIME STATUS');
|
|
327
|
+
expect(out).to.include(PROGRESS_LINE);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// GitHub renders no cursor motion, so the live region becomes a collapsed
|
|
331
|
+
// section and the settled table is reprinted outside it. This is the mode CI
|
|
332
|
+
// actually runs in, and it is reached only through the environment.
|
|
333
|
+
it('folds the frames into a CI group and reprints the settled table after it', () => {
|
|
334
|
+
const out = runQuiet({ ci: true });
|
|
335
|
+
const lines = out.split('\n');
|
|
336
|
+
|
|
337
|
+
expect(out).to.not.include('\u001b[A');
|
|
338
|
+
expect(lines.filter((line) => line === '::group::Sync dd-test cluster monitor frames')).to.have.lengthOf(2);
|
|
339
|
+
expect(lines.filter((line) => line === '::endgroup::')).to.have.lengthOf(2);
|
|
340
|
+
|
|
341
|
+
const settled = lines.slice(lines.lastIndexOf('::endgroup::') + 1);
|
|
342
|
+
|
|
343
|
+
expect(settled.filter(isPodRow).map(cells)).to.deep.equal([
|
|
344
|
+
`${POD_A} | Running (ready) | running-deployment`,
|
|
345
|
+
`${POD_B} | Running (ready) | running-deployment`,
|
|
346
|
+
]);
|
|
347
|
+
expect(settled.map(stripAnsi)).to.include('Sync dd-test cluster (refresh #3, 08:11:33)');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('opens no CI group when nothing is there to collapse one', () => {
|
|
351
|
+
expect(runQuiet()).to.not.include('::group::');
|
|
352
|
+
expect(runQuiet({ ci: true, plain: true })).to.not.include('::group::');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('labels the traffic switch once every pod runs, and drops the chatter after it', () => {
|
|
356
|
+
const out = runQuiet();
|
|
357
|
+
const lines = out.trimEnd().split('\n');
|
|
358
|
+
|
|
359
|
+
expect(lines.filter((line) => line.includes('▶ Switch traffic'))).to.have.lengthOf(1);
|
|
360
|
+
expect(lines[lines.length - 1]).to.include('▶ Switch traffic');
|
|
361
|
+
expect(out).to.not.include(READY_LINE);
|
|
362
|
+
expect(out).to.not.include(CHATTER_LINE);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('streams every line verbatim in debug mode, rendering nothing of its own', () => {
|
|
366
|
+
const out = runQuiet({ debug: true });
|
|
367
|
+
|
|
368
|
+
// What the other modes consume is exactly what an operator opened this session to read: the
|
|
369
|
+
// monitor's own JSON, and the report lines the table replaces.
|
|
370
|
+
expect(out).to.include('deploy-monitor');
|
|
371
|
+
expect(out).to.include('"phase": "kubernetes"');
|
|
372
|
+
expect(out).to.include('Target pod: ');
|
|
373
|
+
expect(out).to.include(READY_LINE);
|
|
374
|
+
expect(out).to.include(CHATTER_LINE);
|
|
375
|
+
|
|
376
|
+
// Nothing is rendered on top of the stream: no table, no CI section, no hand-off.
|
|
377
|
+
expect(out).to.not.include('POD NAME');
|
|
378
|
+
expect(out).to.not.include('refresh #');
|
|
379
|
+
expect(out).to.not.include('::group::');
|
|
380
|
+
expect(out).to.not.include('▶ Switch traffic');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('keeps debug mode a passthrough even where a CI log would fold frames', () => {
|
|
384
|
+
// Debug is the operator asking for the stream, so it wins over the rendering the destination
|
|
385
|
+
// would otherwise get: a collapsed section hides exactly what the run is being watched for.
|
|
386
|
+
const out = runQuiet({ debug: true, ci: true });
|
|
387
|
+
|
|
388
|
+
expect(out).to.not.include('::group::');
|
|
389
|
+
expect(out).to.include('deploy-monitor');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('falls back to plain rows on a terminal when RUN_QUIET_PLAIN is set', function () {
|
|
393
|
+
if (!hasScript) return this.skip();
|
|
394
|
+
const out = runQuiet({ tty: true, plain: true });
|
|
395
|
+
|
|
396
|
+
expect(out).to.not.include('\u001b[');
|
|
397
|
+
expect(podRows(out)).to.include(`${POD_A} | Running (ready) | running-deployment`);
|
|
398
|
+
expect(titles(out)).to.include('Sync dd-test cluster (refresh #3, 08:11:33)');
|
|
399
|
+
expect(out).to.not.include('deploy-monitor');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('redraws a single aligned table in place on a terminal', function () {
|
|
403
|
+
if (!hasScript) return this.skip();
|
|
404
|
+
const out = runQuiet({ tty: true });
|
|
405
|
+
const rendered = render(out, 140, 40);
|
|
406
|
+
const rows = rendered.filter(isPodRow);
|
|
407
|
+
|
|
408
|
+
// Every repaint but the first is preceded by a cursor-up over the title, the
|
|
409
|
+
// two header lines and the rows it replaces.
|
|
410
|
+
expect(out).to.include('\u001b[5A');
|
|
411
|
+
expect(rows).to.have.lengthOf(2);
|
|
412
|
+
expect(rows[0]).to.include(POD_A).and.to.include('Running (ready)');
|
|
413
|
+
expect(rows[1]).to.include(POD_B);
|
|
414
|
+
expect(rendered.map(stripAnsi)).to.include('Sync dd-test cluster (refresh #3, 08:11:33)');
|
|
415
|
+
expect(rendered.filter((line) => line.includes('POD NAME'))).to.have.lengthOf(1);
|
|
416
|
+
expect(rendered.some((line) => line.includes(PROGRESS_LINE))).to.equal(true);
|
|
417
|
+
// The handoff label closes the run below the last frame of the table.
|
|
418
|
+
expect(rendered[rendered.length - 1]).to.include('▶ Switch traffic');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('opens a fresh table for a later monitor in the same command', () => {
|
|
422
|
+
const second = [
|
|
423
|
+
emit('08:20:00', 'kubernetes', 'pod_scheduled'),
|
|
424
|
+
podLine('dd-test-production-blue-77d9f4c6b8-kkq2z', 'Running', 'build-deployment (pending)'),
|
|
425
|
+
PROGRESS_LINE,
|
|
426
|
+
emit('08:21:00', 'kubernetes', 'pod_ready'),
|
|
427
|
+
podLine('dd-test-production-blue-77d9f4c6b8-kkq2z', 'Running', 'running-deployment'),
|
|
428
|
+
emit('08:21:00', 'runtime', 'runtime_ready', 'running-deployment'),
|
|
429
|
+
CHATTER_LINE,
|
|
430
|
+
].join('\n');
|
|
431
|
+
fs.writeFileSync(streamPath, `${stream}\n${second}\n`);
|
|
432
|
+
|
|
433
|
+
const out = runQuiet();
|
|
434
|
+
const rows = podRows(out);
|
|
435
|
+
|
|
436
|
+
expect(out.match(/▶ Switch traffic/g)).to.have.lengthOf(2);
|
|
437
|
+
expect(rows[rows.length - 1]).to.equal(
|
|
438
|
+
'dd-test-production-blue-77d9f4c6b8-kkq2z | Running (ready) | running-deployment',
|
|
439
|
+
);
|
|
440
|
+
// The second table counts its own iterations, from its own clock.
|
|
441
|
+
expect(titles(out)).to.include('Sync dd-test cluster (refresh #2, 08:21:00)');
|
|
442
|
+
expect(out).to.not.include(CHATTER_LINE);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('propagates the command status and keeps the debug paths on failure', () => {
|
|
446
|
+
let thrown;
|
|
447
|
+
try {
|
|
448
|
+
runQuiet({ exitCode: 7 });
|
|
449
|
+
} catch (error) {
|
|
450
|
+
thrown = error;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
expect(thrown, 'run_quiet must fail the deployment').to.not.equal(undefined);
|
|
454
|
+
expect(thrown.status).to.equal(7);
|
|
455
|
+
|
|
456
|
+
const report = thrown.stderr.replace(/\u001b\[[0-9;]*[A-Za-z]/g, '');
|
|
457
|
+
expect(report).to.include('failed (exit 7)');
|
|
458
|
+
expect(report).to.include('error trace:');
|
|
459
|
+
expect(report).to.include('debug log:');
|
|
460
|
+
|
|
461
|
+
const debugLog = report.match(/debug log:\s+(\S+)/)[1];
|
|
462
|
+
// The unfiltered stream survives for debugging even though it never printed.
|
|
463
|
+
expect(fs.readFileSync(debugLog, 'utf8')).to.include('deploy-monitor');
|
|
464
|
+
fs.removeSync(debugLog);
|
|
465
|
+
fs.removeSync(report.match(/error trace:\s+(\S+)/)[1]);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* @function render
|
|
471
|
+
* @description Replays the captured pty stream onto a virtual screen: cursor
|
|
472
|
+
* ups, erase-to-end-of-line and erase-to-end-of-screen only, which is all the
|
|
473
|
+
* filter emits.
|
|
474
|
+
* @param {string} output - Raw stream captured from the pty.
|
|
475
|
+
* @param {number} columns - Screen width.
|
|
476
|
+
* @param {number} lines - Screen height.
|
|
477
|
+
* @returns {string[]} Non-empty screen rows, top to bottom.
|
|
478
|
+
*/
|
|
479
|
+
function render(output, columns, lines) {
|
|
480
|
+
const screen = Array.from({ length: lines }, () => '');
|
|
481
|
+
let row = 0;
|
|
482
|
+
let column = 0;
|
|
483
|
+
const write = (text) => {
|
|
484
|
+
const line = screen[row] ?? '';
|
|
485
|
+
screen[row] = (line.padEnd(column, ' ') + text).slice(0, columns);
|
|
486
|
+
column = screen[row].length;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
for (let i = 0; i < output.length; i++) {
|
|
490
|
+
const character = output[i];
|
|
491
|
+
if (character === '\u001b' && output[i + 1] === '[') {
|
|
492
|
+
const sequence = /^\u001b\[([0-9;]*)([A-Za-z])/.exec(output.slice(i));
|
|
493
|
+
if (sequence) {
|
|
494
|
+
const [match, parameters, command] = sequence;
|
|
495
|
+
if (command === 'A') row = Math.max(0, row - (Number(parameters) || 1));
|
|
496
|
+
if (command === 'K') screen[row] = (screen[row] ?? '').slice(0, column);
|
|
497
|
+
if (command === 'J') {
|
|
498
|
+
screen[row] = (screen[row] ?? '').slice(0, column);
|
|
499
|
+
for (let below = row + 1; below < lines; below++) screen[below] = '';
|
|
500
|
+
}
|
|
501
|
+
i += match.length - 1;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (character === '\n') {
|
|
506
|
+
row = Math.min(lines - 1, row + 1);
|
|
507
|
+
column = 0;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (character === '\r') {
|
|
511
|
+
column = 0;
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
write(character);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return screen.filter((line) => line.trim().length > 0);
|
|
518
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module downloader-integrity.test
|
|
5
|
+
* @description Covers the contract a split-artifact transfer depends on: a download either
|
|
6
|
+
* produces the whole file or produces none. A response that ends early still ends, so the write
|
|
7
|
+
* stream finishes and, without a length check, a short file is reported as a completed download
|
|
8
|
+
* and only surfaces later as a corrupt archive assembled from it.
|
|
9
|
+
*
|
|
10
|
+
* Uses 'chai' for assertions.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { expect } from 'chai';
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import http from 'http';
|
|
16
|
+
import Downloader from '../../src/server/storage/downloader.js';
|
|
17
|
+
|
|
18
|
+
const BODY = Buffer.from('a'.repeat(64 * 1024));
|
|
19
|
+
|
|
20
|
+
/** Serves one response shape and resolves the base URL it listens on. */
|
|
21
|
+
const serverFactory = (handler) =>
|
|
22
|
+
new Promise((resolve) => {
|
|
23
|
+
const server = http.createServer(handler);
|
|
24
|
+
server.listen(0, '127.0.0.1', () => resolve({ server, url: `http://127.0.0.1:${server.address().port}/part` }));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('download integrity', () => {
|
|
28
|
+
let fixturePath;
|
|
29
|
+
let running;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
fixturePath = fs.mkdtempSync('/tmp/engine-downloader-');
|
|
33
|
+
running = [];
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
for (const server of running) await new Promise((resolve) => server.close(resolve));
|
|
38
|
+
fs.removeSync(fixturePath);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const serve = async (handler) => {
|
|
42
|
+
const { server, url } = await serverFactory(handler);
|
|
43
|
+
running.push(server);
|
|
44
|
+
return url;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
it('saves a complete response and reports the path', async () => {
|
|
48
|
+
const url = await serve((request, response) => {
|
|
49
|
+
response.writeHead(200, { 'content-length': String(BODY.length) });
|
|
50
|
+
response.end(BODY);
|
|
51
|
+
});
|
|
52
|
+
const target = `${fixturePath}/whole.bin`;
|
|
53
|
+
|
|
54
|
+
expect(await Downloader.downloadFile(url, target)).to.equal(target);
|
|
55
|
+
expect(fs.statSync(target).size).to.equal(BODY.length);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('rejects a response that stops short of its declared length, and leaves no partial file', async () => {
|
|
59
|
+
const url = await serve((request, response) => {
|
|
60
|
+
response.writeHead(200, { 'content-length': String(BODY.length) });
|
|
61
|
+
response.write(BODY.subarray(0, 1024));
|
|
62
|
+
// Ending early is what a cut-short transfer looks like to the client: the stream still ends.
|
|
63
|
+
response.destroy();
|
|
64
|
+
});
|
|
65
|
+
const target = `${fixturePath}/short.bin`;
|
|
66
|
+
|
|
67
|
+
let thrown;
|
|
68
|
+
await Downloader.downloadFile(url, target).catch((error) => (thrown = error));
|
|
69
|
+
|
|
70
|
+
expect(thrown, 'a truncated transfer must not resolve').to.not.equal(undefined);
|
|
71
|
+
expect(fs.existsSync(target), 'a partial file is indistinguishable from a whole one').to.equal(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('accepts a response that declares no length, since nothing can be checked against', async () => {
|
|
75
|
+
const url = await serve((request, response) => {
|
|
76
|
+
response.writeHead(200, { 'transfer-encoding': 'chunked' });
|
|
77
|
+
response.end(BODY);
|
|
78
|
+
});
|
|
79
|
+
const target = `${fixturePath}/chunked.bin`;
|
|
80
|
+
|
|
81
|
+
expect(await Downloader.downloadFile(url, target)).to.equal(target);
|
|
82
|
+
expect(fs.statSync(target).size).to.equal(BODY.length);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('rejects a request the server refuses, and leaves no file', async () => {
|
|
86
|
+
const url = await serve((request, response) => {
|
|
87
|
+
response.writeHead(404);
|
|
88
|
+
response.end();
|
|
89
|
+
});
|
|
90
|
+
const target = `${fixturePath}/missing.bin`;
|
|
91
|
+
|
|
92
|
+
let thrown;
|
|
93
|
+
await Downloader.downloadFile(url, target).catch((error) => (thrown = error));
|
|
94
|
+
|
|
95
|
+
expect(thrown).to.not.equal(undefined);
|
|
96
|
+
expect(fs.existsSync(target)).to.equal(false);
|
|
97
|
+
});
|
|
98
|
+
});
|