@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,996 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import net from 'node:net';
|
|
7
|
+
import shell from 'shelljs';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_DEPLOY_ID,
|
|
10
|
+
awaitDeployMonitor,
|
|
11
|
+
buildCliDoc,
|
|
12
|
+
buildReplicaId,
|
|
13
|
+
clusterContextFactory,
|
|
14
|
+
clusterTypeFactory,
|
|
15
|
+
devProxyHostFactory,
|
|
16
|
+
ensureTemplateCheckout,
|
|
17
|
+
exposePartialMatchesFactory,
|
|
18
|
+
exposePathPartsFactory,
|
|
19
|
+
exposePortListFactory,
|
|
20
|
+
exposePortPlanFactory,
|
|
21
|
+
exposeTcpPortsFactory,
|
|
22
|
+
gatewayApiEnabledFactory,
|
|
23
|
+
generateSecurePassword,
|
|
24
|
+
getConfFilePath,
|
|
25
|
+
getConfFolder,
|
|
26
|
+
getInstanceContext,
|
|
27
|
+
getPathsSSR,
|
|
28
|
+
getTlsHosts,
|
|
29
|
+
gitOriginRepositoryName,
|
|
30
|
+
isDevProxyContext,
|
|
31
|
+
isTlsDevProxy,
|
|
32
|
+
pruneTemplateWorkTree,
|
|
33
|
+
readConfJson,
|
|
34
|
+
resolveConfSecrets,
|
|
35
|
+
resolveHostKeyContext,
|
|
36
|
+
resolveReplicaCount,
|
|
37
|
+
syncDeployIdSources,
|
|
38
|
+
syncPrivateConf,
|
|
39
|
+
updatePrivateEngineTestRepo,
|
|
40
|
+
updatePrivateTemplateRepo,
|
|
41
|
+
waitForPort,
|
|
42
|
+
} from '../../src/server/runtime/conf.js';
|
|
43
|
+
import UnderpostState from '../../src/cli/state.js';
|
|
44
|
+
|
|
45
|
+
const withArgv = (argv, run) => {
|
|
46
|
+
const previous = process.argv;
|
|
47
|
+
process.argv = [...previous.slice(0, 2), ...argv];
|
|
48
|
+
try {
|
|
49
|
+
return run();
|
|
50
|
+
} finally {
|
|
51
|
+
process.argv = previous;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const withEnv = (values, run) => {
|
|
56
|
+
const previous = {};
|
|
57
|
+
for (const [key, value] of Object.entries(values)) {
|
|
58
|
+
previous[key] = process.env[key];
|
|
59
|
+
if (value === undefined) delete process.env[key];
|
|
60
|
+
else process.env[key] = value;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return run();
|
|
64
|
+
} finally {
|
|
65
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
66
|
+
if (value === undefined) delete process.env[key];
|
|
67
|
+
else process.env[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
describe('host key context', () => {
|
|
73
|
+
it('joins a host and path into the key the conf is indexed by', () => {
|
|
74
|
+
expect(resolveHostKeyContext({ host: 'app.test', path: '/admin' })).to.equal('app.test/admin');
|
|
75
|
+
expect(resolveHostKeyContext({ host: 'app.test' })).to.equal('app.test');
|
|
76
|
+
expect(resolveHostKeyContext()).to.equal('');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('passes a prebuilt key through unchanged', () => {
|
|
80
|
+
expect(resolveHostKeyContext('app.test/admin')).to.equal('app.test/admin');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('conf secret references', () => {
|
|
85
|
+
it('leaves everything that is not an env reference alone', () => {
|
|
86
|
+
const conf = { port: 3000, enabled: true, name: 'core', nothing: null, missing: undefined };
|
|
87
|
+
expect(resolveConfSecrets(conf)).to.deep.equal(conf);
|
|
88
|
+
expect(resolveConfSecrets(null)).to.equal(null);
|
|
89
|
+
expect(resolveConfSecrets(undefined)).to.equal(undefined);
|
|
90
|
+
expect(resolveConfSecrets(7)).to.equal(7);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('resolves a bare reference from the environment', () => {
|
|
94
|
+
withEnv({ CONF_FIXTURE_SECRET: 'supersecret' }, () => {
|
|
95
|
+
expect(resolveConfSecrets({ db: { password: 'env:CONF_FIXTURE_SECRET' } })).to.deep.equal({
|
|
96
|
+
db: { password: 'supersecret' },
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// An unresolved credential must not reach a connection string as the literal
|
|
102
|
+
// `env:` reference; empty is the value a caller can detect.
|
|
103
|
+
it('resolves an unset reference with no default to an empty string', () => {
|
|
104
|
+
withEnv({ CONF_FIXTURE_SECRET: undefined }, () => {
|
|
105
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_SECRET')).to.equal('');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('falls back to a declared default', () => {
|
|
110
|
+
withEnv({ CONF_FIXTURE_PROVIDER: undefined }, () => {
|
|
111
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_PROVIDER:mongoose')).to.equal('mongoose');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('coerces an int reference from either the environment or its default', () => {
|
|
116
|
+
withEnv({ CONF_FIXTURE_PORT: '587' }, () => {
|
|
117
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_PORT:int:465')).to.equal(587);
|
|
118
|
+
});
|
|
119
|
+
withEnv({ CONF_FIXTURE_PORT: undefined }, () => {
|
|
120
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_PORT:int:465')).to.equal(465);
|
|
121
|
+
});
|
|
122
|
+
withEnv({ CONF_FIXTURE_PORT: 'not-a-number' }, () => {
|
|
123
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_PORT:int:465')).to.equal(465);
|
|
124
|
+
});
|
|
125
|
+
withEnv({ CONF_FIXTURE_PORT: 'nope' }, () => {
|
|
126
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_PORT:int:zero')).to.equal(0);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('coerces a bool reference, treating only the literal false as false', () => {
|
|
131
|
+
withEnv({ CONF_FIXTURE_SECURE: 'false' }, () => {
|
|
132
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_SECURE:bool:true')).to.equal(false);
|
|
133
|
+
});
|
|
134
|
+
withEnv({ CONF_FIXTURE_SECURE: '0' }, () => {
|
|
135
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_SECURE:bool:true')).to.equal(true);
|
|
136
|
+
});
|
|
137
|
+
withEnv({ CONF_FIXTURE_SECURE: undefined }, () => {
|
|
138
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_SECURE:bool:false')).to.equal(false);
|
|
139
|
+
expect(resolveConfSecrets('env:CONF_FIXTURE_SECURE:bool:true')).to.equal(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('walks arrays and nested objects', () => {
|
|
144
|
+
withEnv({ CONF_FIXTURE_ORIGIN: 'https://app.test' }, () => {
|
|
145
|
+
expect(
|
|
146
|
+
resolveConfSecrets({
|
|
147
|
+
origins: ['env:CONF_FIXTURE_ORIGIN', 'literal'],
|
|
148
|
+
nested: [{ a: 'env:CONF_FIXTURE_ORIGIN' }],
|
|
149
|
+
}),
|
|
150
|
+
).to.deep.equal({ origins: ['https://app.test', 'literal'], nested: [{ a: 'https://app.test' }] });
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns a new object rather than resolving secrets into the conf it was given', () => {
|
|
155
|
+
const conf = { db: { password: 'env:CONF_FIXTURE_SECRET' } };
|
|
156
|
+
withEnv({ CONF_FIXTURE_SECRET: 'x' }, () => resolveConfSecrets(conf));
|
|
157
|
+
expect(conf.db.password).to.equal('env:CONF_FIXTURE_SECRET');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('conf file resolution', () => {
|
|
162
|
+
afterEach(() => vi.restoreAllMocks());
|
|
163
|
+
|
|
164
|
+
it('names the default deploy', () => {
|
|
165
|
+
expect(DEFAULT_DEPLOY_ID).to.equal('dd-default');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('prefers a replica folder over the deploy conf folder', () => {
|
|
169
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) =>
|
|
170
|
+
`${filePath}`.startsWith('./engine-private/replica/dd-core'),
|
|
171
|
+
);
|
|
172
|
+
expect(getConfFolder('dd-core')).to.equal('./engine-private/replica/dd-core');
|
|
173
|
+
expect(getConfFolder('dd-other')).to.equal('./engine-private/conf/dd-other');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('resolves the canonical conf path', () => {
|
|
177
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
178
|
+
expect(getConfFilePath('dd-core', 'cron')).to.equal('./engine-private/conf/dd-core/conf.cron.json');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('prefers a development sub-conf variant of the server conf when one exists', () => {
|
|
182
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => `${filePath}`.endsWith('conf.server.dev.local.json'));
|
|
183
|
+
withEnv({ NODE_ENV: 'development' }, () => {
|
|
184
|
+
expect(getConfFilePath('dd-core', 'server', 'local')).to.equal(
|
|
185
|
+
'./engine-private/conf/dd-core/conf.server.dev.local.json',
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('honors an explicitly named sub-conf whatever the ambient environment says', () => {
|
|
191
|
+
// Regression: `underpost` loads the host configuration store with `override: true`, and on a
|
|
192
|
+
// provisioned node that store carries NODE_ENV=production. Gating the explicit sub-conf on
|
|
193
|
+
// development therefore discarded it on every CLI call — `node bin client dd-core nexodev`
|
|
194
|
+
// built conf.server.json, every host in it, rather than conf.server.dev.nexodev.json.
|
|
195
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => `${filePath}`.endsWith('conf.server.dev.local.json'));
|
|
196
|
+
for (const NODE_ENV of ['production', 'test', undefined])
|
|
197
|
+
withEnv({ NODE_ENV }, () => {
|
|
198
|
+
expect(getConfFilePath('dd-core', 'server', 'local'), `NODE_ENV=${NODE_ENV}`).to.equal(
|
|
199
|
+
'./engine-private/conf/dd-core/conf.server.dev.local.json',
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('falls back to the base conf when the named sub-conf has no variant file', () => {
|
|
205
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
206
|
+
withEnv({ NODE_ENV: 'development' }, () => {
|
|
207
|
+
expect(getConfFilePath('dd-core', 'server', 'absent')).to.equal('./engine-private/conf/dd-core/conf.server.json');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('reads the sub-conf from the environment when none is passed', () => {
|
|
212
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => `${filePath}`.endsWith('conf.server.dev.envsub.json'));
|
|
213
|
+
withEnv({ NODE_ENV: 'development', DEPLOY_SUB_CONF: 'envsub' }, () => {
|
|
214
|
+
expect(getConfFilePath('dd-core', 'server')).to.equal(
|
|
215
|
+
'./engine-private/conf/dd-core/conf.server.dev.envsub.json',
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('never takes the development variant for a sub-conf only inherited from the environment', () => {
|
|
221
|
+
// The env-var fallback keeps its gate: a production deploy that merely carries
|
|
222
|
+
// DEPLOY_SUB_CONF must not start reading a dev conf because of it. Only a caller that
|
|
223
|
+
// names the sub-conf overrides the environment.
|
|
224
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
225
|
+
withEnv({ NODE_ENV: 'production', DEPLOY_SUB_CONF: 'envsub' }, () => {
|
|
226
|
+
expect(getConfFilePath('dd-core', 'server')).to.equal('./engine-private/replica/dd-core/conf.server.json');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('never takes a development variant for a conf type that has none', () => {
|
|
231
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
232
|
+
withEnv({ NODE_ENV: 'development' }, () => {
|
|
233
|
+
for (const confType of ['client', 'ssr', 'cron'])
|
|
234
|
+
expect(getConfFilePath('dd-core', confType, 'local'), confType).to.equal(
|
|
235
|
+
`./engine-private/replica/dd-core/conf.${confType}.json`,
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('carries the sub-conf into the build rather than re-deriving it from the environment', () => {
|
|
241
|
+
// `client` accepts `[sub-conf]` but the two reads that actually drive the build used to take
|
|
242
|
+
// it from DEPLOY_SUB_CONF, so a build could read a different conf.server than the loadConf
|
|
243
|
+
// its caller had just run — and did, whenever the ambient environment was not development.
|
|
244
|
+
const clientSource = fs.readFileSync(new URL('../../src/cli/client.js', import.meta.url), 'utf8');
|
|
245
|
+
const buildSource = fs.readFileSync(new URL('../../src/client-builder/client-build.js', import.meta.url), 'utf8');
|
|
246
|
+
expect(clientSource).to.include(
|
|
247
|
+
"readConfJson(resolvedDeployId, 'server', { subConf: subConf ?? '', loadReplicas: true })",
|
|
248
|
+
);
|
|
249
|
+
expect(clientSource).to.include("subConf: subConf ?? '',");
|
|
250
|
+
for (const call of [
|
|
251
|
+
"readConfJson(deployId, 'client', { subConf })",
|
|
252
|
+
"readConfJson(deployId, 'server', { subConf, loadReplicas: true })",
|
|
253
|
+
"readConfJson(deployId, 'ssr', { subConf })",
|
|
254
|
+
])
|
|
255
|
+
expect(buildSource, call).to.include(call);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('names the missing file rather than failing on a parse', () => {
|
|
259
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
260
|
+
expect(() => readConfJson('dd-core', 'cron')).to.throw('configuration file not found');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('resolves env references only when asked to', () => {
|
|
264
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
265
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ token: 'env:CONF_FIXTURE_TOKEN' }));
|
|
266
|
+
withEnv({ CONF_FIXTURE_TOKEN: 'abc', NODE_ENV: 'production' }, () => {
|
|
267
|
+
expect(readConfJson('dd-core', 'cron')).to.deep.equal({ token: 'env:CONF_FIXTURE_TOKEN' });
|
|
268
|
+
expect(readConfJson('dd-core', 'cron', { resolve: true })).to.deep.equal({ token: 'abc' });
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('deploy value helpers', () => {
|
|
274
|
+
it('derives the replica deploy id from the replica path', () => {
|
|
275
|
+
expect(buildReplicaId({ deployId: 'dd-core', replica: '/blue' })).to.equal('dd-core-blue');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// A floor would silently override an explicit lower request, which is what
|
|
279
|
+
// made `--replicas 2` deploy three MongoDB members.
|
|
280
|
+
it('takes a positive integer replica count and falls back otherwise', () => {
|
|
281
|
+
expect(resolveReplicaCount('3')).to.equal(3);
|
|
282
|
+
expect(resolveReplicaCount(2, 5)).to.equal(2);
|
|
283
|
+
expect(resolveReplicaCount('0', 5)).to.equal(5);
|
|
284
|
+
expect(resolveReplicaCount('-1', 5)).to.equal(5);
|
|
285
|
+
expect(resolveReplicaCount(undefined)).to.equal(1);
|
|
286
|
+
expect(resolveReplicaCount('abc', 4)).to.equal(4);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('generates a password satisfying every validation class', () => {
|
|
290
|
+
for (const length of [8, 16, 32]) {
|
|
291
|
+
const password = generateSecurePassword(length);
|
|
292
|
+
expect(password.length).to.equal(length);
|
|
293
|
+
expect(/[a-z]/.test(password), password).to.equal(true);
|
|
294
|
+
expect(/[A-Z]/.test(password), password).to.equal(true);
|
|
295
|
+
expect(/[0-9]/.test(password), password).to.equal(true);
|
|
296
|
+
expect(/[@#$%^&*()_+]/.test(password), password).to.equal(true);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('raises a password shorter than the minimum rather than emitting one', () => {
|
|
301
|
+
expect(generateSecurePassword(4).length).to.equal(8);
|
|
302
|
+
expect(generateSecurePassword().length).to.equal(16);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('never repeats a generated password', () => {
|
|
306
|
+
const generated = new Set(Array.from({ length: 25 }, () => generateSecurePassword(16)));
|
|
307
|
+
expect(generated.size).to.equal(25);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('lists every SSR source a conf declares', () => {
|
|
311
|
+
expect(
|
|
312
|
+
getPathsSSR({
|
|
313
|
+
head: ['Meta'],
|
|
314
|
+
body: ['Menu'],
|
|
315
|
+
mailer: { confirm: 'ConfirmEmail' },
|
|
316
|
+
views: [{ client: 'Home' }],
|
|
317
|
+
}),
|
|
318
|
+
).to.deep.equal([
|
|
319
|
+
'src/client/ssr/RootDocument.js',
|
|
320
|
+
'src/client/ssr/head/Meta.js',
|
|
321
|
+
'src/client/ssr/body/Menu.js',
|
|
322
|
+
'src/client/ssr/mailer/ConfirmEmail.js',
|
|
323
|
+
'src/client/ssr/views/Home.js',
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('tolerates a conf that declares no views', () => {
|
|
328
|
+
expect(getPathsSSR({ head: [], body: [], mailer: {} })).to.deep.equal(['src/client/ssr/RootDocument.js']);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('deploy monitor wait', () => {
|
|
333
|
+
afterEach(() => vi.restoreAllMocks());
|
|
334
|
+
|
|
335
|
+
// Both keys this reads — `container-status` and the `await-deploy` boot latch — are
|
|
336
|
+
// container-scoped, so the whole wait resolves against the state store and never touches the
|
|
337
|
+
// host configuration store a host-domain action is free to clear underneath it.
|
|
338
|
+
it('returns false as soon as a container reports an error', async () => {
|
|
339
|
+
vi.spyOn(UnderpostState.API, 'get').mockImplementation((key) => (key === 'container-status' ? 'error' : ''));
|
|
340
|
+
vi.spyOn(UnderpostState.API, 'set').mockImplementation(() => undefined);
|
|
341
|
+
expect(await awaitDeployMonitor(true, 1)).to.equal(false);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('returns true once nothing is awaiting a deploy', async () => {
|
|
345
|
+
vi.spyOn(UnderpostState.API, 'set').mockImplementation(() => undefined);
|
|
346
|
+
vi.spyOn(UnderpostState.API, 'get').mockReturnValue('');
|
|
347
|
+
expect(await awaitDeployMonitor(false, 1)).to.equal(true);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('keeps polling while a deploy is still marked awaiting', async () => {
|
|
351
|
+
vi.spyOn(UnderpostState.API, 'set').mockImplementation(() => undefined);
|
|
352
|
+
let remaining = 2;
|
|
353
|
+
vi.spyOn(UnderpostState.API, 'get').mockImplementation((key) => {
|
|
354
|
+
if (key === 'container-status') return '';
|
|
355
|
+
return remaining-- > 0 ? '2026-01-01T00:00:00.000Z' : '';
|
|
356
|
+
});
|
|
357
|
+
expect(await awaitDeployMonitor(false, 1)).to.equal(true);
|
|
358
|
+
expect(remaining).to.be.below(0);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('never reaches for the host configuration store', () => {
|
|
362
|
+
// `host load` cleans that file before repopulating it and `host clean` removes it outright,
|
|
363
|
+
// both while a deployment may be mid-boot. A latch held there is one a host operation can
|
|
364
|
+
// silently drop.
|
|
365
|
+
const source = readFileSync(new URL('../../src/server/runtime/conf.js', import.meta.url), 'utf8');
|
|
366
|
+
const body = source.slice(source.indexOf('const awaitDeployMonitor ='), source.indexOf('const mergeFile ='));
|
|
367
|
+
expect(body).to.not.include('Underpost.host');
|
|
368
|
+
expect(body).to.include('latchAwaitDeploy()');
|
|
369
|
+
expect(body).to.include('isAwaitingDeploy()');
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe('single replica port offsets', () => {
|
|
374
|
+
afterEach(() => vi.restoreAllMocks());
|
|
375
|
+
|
|
376
|
+
it('reserves no ports when the path declares no replicas', async () => {
|
|
377
|
+
expect(await getInstanceContext({ deployId: 'dd-core', singleReplica: false, replicas: [] })).to.deep.equal({
|
|
378
|
+
redirectTarget: undefined,
|
|
379
|
+
singleReplicaOffsetPortSum: 0,
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('estimates one port per replica, plus one for peer, before the replicas are built', async () => {
|
|
384
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
385
|
+
const { singleReplicaOffsetPortSum } = await getInstanceContext({
|
|
386
|
+
deployId: 'dd-core',
|
|
387
|
+
singleReplica: true,
|
|
388
|
+
replicas: ['/blue', '/green'],
|
|
389
|
+
peer: true,
|
|
390
|
+
});
|
|
391
|
+
expect(singleReplicaOffsetPortSum).to.equal(4);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('counts the ports the built replica conf actually declares', async () => {
|
|
395
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
396
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ 'a.test': { '/': { peer: true }, '/admin': {} } }));
|
|
397
|
+
const { singleReplicaOffsetPortSum } = await getInstanceContext({
|
|
398
|
+
deployId: 'dd-core',
|
|
399
|
+
singleReplica: true,
|
|
400
|
+
replicas: ['/blue'],
|
|
401
|
+
});
|
|
402
|
+
expect(singleReplicaOffsetPortSum).to.equal(3);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('strips the trailing slash off a redirect target', async () => {
|
|
406
|
+
const { redirectTarget } = await getInstanceContext({ deployId: 'dd-core', redirect: 'https://www.app.test/' });
|
|
407
|
+
expect(redirectTarget).to.equal('https://www.app.test');
|
|
408
|
+
expect(
|
|
409
|
+
(await getInstanceContext({ deployId: 'dd-core', redirect: 'https://www.app.test' })).redirectTarget,
|
|
410
|
+
).to.equal('https://www.app.test');
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('dev proxy context', () => {
|
|
415
|
+
it('detects the proxy invocation off the command line', () => {
|
|
416
|
+
expect(withArgv(['proxy', 'dd-core'], isDevProxyContext)).to.equal(true);
|
|
417
|
+
expect(withArgv(['dd-core'], isDevProxyContext)).to.equal(false);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('enables TLS only outside production and only when asked', () => {
|
|
421
|
+
withEnv({ NODE_ENV: 'development' }, () => {
|
|
422
|
+
expect(withArgv(['proxy', 'tls'], isTlsDevProxy)).to.equal(true);
|
|
423
|
+
expect(withArgv(['proxy'], isTlsDevProxy)).to.equal(false);
|
|
424
|
+
});
|
|
425
|
+
withEnv({ NODE_ENV: 'production' }, () => {
|
|
426
|
+
expect(withArgv(['proxy', 'tls'], isTlsDevProxy)).to.equal(false);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('drops the port from the host when the offset lands on the default one', () => {
|
|
431
|
+
withEnv({ DEV_PROXY_PORT_OFFSET: '0' }, () => {
|
|
432
|
+
expect(devProxyHostFactory({ host: 'app.test', port: 80 })).to.equal('app.test');
|
|
433
|
+
expect(devProxyHostFactory({ host: 'app.test', port: 443, tls: true })).to.equal('app.test');
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('carries the offset port and the scheme when asked', () => {
|
|
438
|
+
withEnv({ DEV_PROXY_PORT_OFFSET: '8000' }, () => {
|
|
439
|
+
expect(devProxyHostFactory({ host: 'app.test', port: 80, includeHttp: true })).to.equal('http://app.test:8080');
|
|
440
|
+
expect(devProxyHostFactory({ host: 'app.test', port: 443, tls: true, includeHttp: true })).to.equal(
|
|
441
|
+
'https://app.test:8443',
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('falls back to localhost and the scheme default port', () => {
|
|
447
|
+
withEnv({ DEV_PROXY_PORT_OFFSET: '0' }, () => {
|
|
448
|
+
expect(devProxyHostFactory({ host: '', tls: true })).to.equal('localhost');
|
|
449
|
+
expect(devProxyHostFactory({})).to.equal('localhost');
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('reduces the conf hosts to their unique hostnames', () => {
|
|
454
|
+
expect(getTlsHosts({ 'app.test': {}, 'app.test:443': {}, 'api.test': {} })).to.deep.equal(['app.test', 'api.test']);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
describe('cluster context', () => {
|
|
459
|
+
it('names the selected cluster runtime, defaulting to kind', () => {
|
|
460
|
+
expect(clusterTypeFactory({ k3s: true })).to.equal('k3s');
|
|
461
|
+
expect(clusterTypeFactory({ kubeadm: true })).to.equal('kubeadm');
|
|
462
|
+
expect(clusterTypeFactory({})).to.equal('kind');
|
|
463
|
+
expect(clusterTypeFactory({}, 'kubeadm')).to.equal('kubeadm');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('turns a cluster type back into mutually exclusive runner flags', () => {
|
|
467
|
+
expect(clusterContextFactory('kubeadm')).to.deep.equal({ kind: false, kubeadm: true, k3s: false });
|
|
468
|
+
expect(clusterContextFactory('kind')).to.deep.equal({ kind: true, kubeadm: false, k3s: false });
|
|
469
|
+
expect(clusterContextFactory('k3s')).to.deep.equal({ kind: false, kubeadm: false, k3s: true });
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// The Gateway API is the platform routing stack; the Contour set is what a
|
|
473
|
+
// caller opts into, never what a missing flag falls back to.
|
|
474
|
+
it('routes through the Gateway API unless it is explicitly disabled', () => {
|
|
475
|
+
expect(gatewayApiEnabledFactory({})).to.equal(true);
|
|
476
|
+
expect(gatewayApiEnabledFactory({ gatewayApi: true })).to.equal(true);
|
|
477
|
+
expect(gatewayApiEnabledFactory({ disableGatewayApi: true })).to.equal(false);
|
|
478
|
+
expect(gatewayApiEnabledFactory({ gatewayApi: true, disableGatewayApi: true })).to.equal(true);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe('expose port planning', () => {
|
|
483
|
+
const RESOURCE = { NAME: 'app-svc', 'PORT(S)': '8080:32080/TCP,53/UDP,9090/TCP' };
|
|
484
|
+
|
|
485
|
+
it('reads only TCP service ports off a kubectl row', () => {
|
|
486
|
+
expect(exposeTcpPortsFactory(RESOURCE)).to.deep.equal([8080, 9090]);
|
|
487
|
+
expect(exposeTcpPortsFactory({})).to.deep.equal([]);
|
|
488
|
+
expect(exposeTcpPortsFactory()).to.deep.equal([]);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('parses a comma separated resource path into literal name fragments', () => {
|
|
492
|
+
expect(exposePathPartsFactory('app, worker ')).to.deep.equal(['app', 'worker']);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('refuses an empty path and any fragment that is not a literal resource name', () => {
|
|
496
|
+
expect(() => exposePathPartsFactory('')).to.throw('requires a Service or Pod name');
|
|
497
|
+
expect(() => exposePathPartsFactory(' , ')).to.throw('requires a Service or Pod name');
|
|
498
|
+
expect(() => exposePathPartsFactory('app;rm -rf /')).to.throw('Invalid Kubernetes resource name match');
|
|
499
|
+
expect(() => exposePathPartsFactory('app*')).to.throw('Invalid Kubernetes resource name match');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('orders matches by requested fragment, exact name first, then lexically', () => {
|
|
503
|
+
const resources = [
|
|
504
|
+
{ NAME: 'worker-2' },
|
|
505
|
+
{ NAME: 'app-blue' },
|
|
506
|
+
{ NAME: 'app' },
|
|
507
|
+
{ NAME: 'worker-1' },
|
|
508
|
+
{ NAME: 'unrelated' },
|
|
509
|
+
];
|
|
510
|
+
expect(exposePartialMatchesFactory(resources, ['app', 'worker']).map(({ NAME }) => NAME)).to.deep.equal([
|
|
511
|
+
'app',
|
|
512
|
+
'app-blue',
|
|
513
|
+
'worker-1',
|
|
514
|
+
'worker-2',
|
|
515
|
+
]);
|
|
516
|
+
expect(resources[0].NAME).to.equal('worker-2');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('parses a CLI port list and rejects anything outside a TCP port', () => {
|
|
520
|
+
expect(exposePortListFactory('8080, 9090')).to.deep.equal([8080, 9090]);
|
|
521
|
+
expect(exposePortListFactory('')).to.deep.equal([]);
|
|
522
|
+
expect(exposePortListFactory(undefined)).to.deep.equal([]);
|
|
523
|
+
expect(exposePortListFactory(null)).to.deep.equal([]);
|
|
524
|
+
expect(exposePortListFactory(8080)).to.deep.equal([8080]);
|
|
525
|
+
expect(() => exposePortListFactory('8080,')).to.throw('Invalid ports');
|
|
526
|
+
expect(() => exposePortListFactory('0')).to.throw('Invalid ports');
|
|
527
|
+
expect(() => exposePortListFactory('65536')).to.throw('Invalid ports');
|
|
528
|
+
expect(() => exposePortListFactory('abc', 'host-ports')).to.throw('Invalid host-ports');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('forwards every declared port of a single resource onto the same local port', () => {
|
|
532
|
+
expect(exposePortPlanFactory({ resources: [RESOURCE], kindType: 'svc' })).to.deep.equal([
|
|
533
|
+
{ kindType: 'svc', name: 'app-svc', localPort: 8080, remotePort: 8080 },
|
|
534
|
+
{ kindType: 'svc', name: 'app-svc', localPort: 9090, remotePort: 9090 },
|
|
535
|
+
]);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('maps explicit container and host ports pairwise for one resource', () => {
|
|
539
|
+
expect(
|
|
540
|
+
exposePortPlanFactory({
|
|
541
|
+
resources: [RESOURCE],
|
|
542
|
+
kindType: 'svc',
|
|
543
|
+
containerPorts: [8080, 9090],
|
|
544
|
+
hostPorts: [18080, 19090],
|
|
545
|
+
}),
|
|
546
|
+
).to.deep.equal([
|
|
547
|
+
{ kindType: 'svc', name: 'app-svc', localPort: 18080, remotePort: 8080 },
|
|
548
|
+
{ kindType: 'svc', name: 'app-svc', localPort: 19090, remotePort: 9090 },
|
|
549
|
+
]);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('maps host ports onto the declared ports they name', () => {
|
|
553
|
+
expect(exposePortPlanFactory({ resources: [RESOURCE], kindType: 'svc', hostPorts: [9090] })).to.deep.equal([
|
|
554
|
+
{ kindType: 'svc', name: 'app-svc', localPort: 9090, remotePort: 9090 },
|
|
555
|
+
]);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('falls back to the declared ports when a host port names none of them', () => {
|
|
559
|
+
expect(exposePortPlanFactory({ resources: [RESOURCE], kindType: 'svc', hostPorts: [19090] })).to.deep.equal([
|
|
560
|
+
{ kindType: 'svc', name: 'app-svc', localPort: 19090, remotePort: 8080 },
|
|
561
|
+
]);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('maps ports by resource index across several resources', () => {
|
|
565
|
+
const resources = [{ NAME: 'a' }, { NAME: 'b' }];
|
|
566
|
+
expect(
|
|
567
|
+
exposePortPlanFactory({ resources, kindType: 'pod', containerPorts: [80, 81], hostPorts: [8080, 8081] }),
|
|
568
|
+
).to.deep.equal([
|
|
569
|
+
{ kindType: 'pod', name: 'a', localPort: 8080, remotePort: 80 },
|
|
570
|
+
{ kindType: 'pod', name: 'b', localPort: 8081, remotePort: 81 },
|
|
571
|
+
]);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('walks an automatic host port past one already taken', () => {
|
|
575
|
+
const resources = [
|
|
576
|
+
{ NAME: 'a', 'PORT(S)': '8080/TCP' },
|
|
577
|
+
{ NAME: 'b', 'PORT(S)': '8080/TCP' },
|
|
578
|
+
];
|
|
579
|
+
expect(exposePortPlanFactory({ resources, kindType: 'svc' })).to.deep.equal([
|
|
580
|
+
{ kindType: 'svc', name: 'a', localPort: 8080, remotePort: 8080 },
|
|
581
|
+
{ kindType: 'svc', name: 'b', localPort: 8081, remotePort: 8080 },
|
|
582
|
+
]);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('refuses a port list that cannot map onto the matched resources', () => {
|
|
586
|
+
const resources = [{ NAME: 'a' }, { NAME: 'b' }];
|
|
587
|
+
expect(() => exposePortPlanFactory({ resources, kindType: 'svc', containerPorts: [80] })).to.throw(
|
|
588
|
+
'requires 2 ports for 2 resources',
|
|
589
|
+
);
|
|
590
|
+
expect(() => exposePortPlanFactory({ resources, kindType: 'svc', hostPorts: [80] })).to.throw(
|
|
591
|
+
'requires 2 ports for 2 resources',
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('refuses a resource with no declared port and no explicit one', () => {
|
|
596
|
+
expect(() => exposePortPlanFactory({ resources: [{ NAME: 'a' }], kindType: 'svc' })).to.throw(
|
|
597
|
+
'No declared TCP port for svc/a',
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('refuses mismatched host and container port counts on one resource', () => {
|
|
602
|
+
expect(() =>
|
|
603
|
+
exposePortPlanFactory({
|
|
604
|
+
resources: [RESOURCE],
|
|
605
|
+
kindType: 'svc',
|
|
606
|
+
containerPorts: [8080, 9090],
|
|
607
|
+
hostPorts: [18080],
|
|
608
|
+
}),
|
|
609
|
+
).to.throw('Host/container port counts differ');
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('refuses to bind one explicit host port twice', () => {
|
|
613
|
+
const resources = [{ NAME: 'a' }, { NAME: 'b' }];
|
|
614
|
+
expect(() =>
|
|
615
|
+
exposePortPlanFactory({ resources, kindType: 'svc', containerPorts: [80, 81], hostPorts: [8080, 8080] }),
|
|
616
|
+
).to.throw('Duplicate --expose-host-ports value: 8080');
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('refuses when no automatic host port remains below the top of the range', () => {
|
|
620
|
+
const resources = [{ NAME: 'a' }, { NAME: 'b' }];
|
|
621
|
+
expect(() => exposePortPlanFactory({ resources, kindType: 'svc', portsOf: () => [65535] })).to.throw(
|
|
622
|
+
'No valid host port remains',
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('takes a declared-port resolver of its own', () => {
|
|
627
|
+
expect(
|
|
628
|
+
exposePortPlanFactory({ resources: [{ NAME: 'a' }], kindType: 'pod', portsOf: () => [3000, 3000] }),
|
|
629
|
+
).to.deep.equal([{ kindType: 'pod', name: 'a', localPort: 3000, remotePort: 3000 }]);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
describe('port readiness wait', () => {
|
|
634
|
+
it('reports a listening port as soon as it accepts', async () => {
|
|
635
|
+
const server = net.createServer();
|
|
636
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
637
|
+
try {
|
|
638
|
+
expect(await waitForPort({ port: server.address().port, timeoutMs: 2000, intervalMs: 10 })).to.equal(true);
|
|
639
|
+
} finally {
|
|
640
|
+
server.close();
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('reports a closed port when waiting for one to go down', async () => {
|
|
645
|
+
expect(await waitForPort({ port: 1, open: false, timeoutMs: 2000, intervalMs: 10 })).to.equal(true);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it('gives up once the window closes', async () => {
|
|
649
|
+
expect(await waitForPort({ port: 1, open: true, timeoutMs: 30, intervalMs: 10, connectTimeoutMs: 10 })).to.equal(
|
|
650
|
+
false,
|
|
651
|
+
);
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
describe('private conf sync', () => {
|
|
656
|
+
const CRON_ID_PATH = './engine-private/deploy/dd.cron';
|
|
657
|
+
let commands;
|
|
658
|
+
|
|
659
|
+
// `existsSync` is answered per path, never blanket-true: the sync reads the cron deploy id
|
|
660
|
+
// straight after its own existence check, so a blanket answer fabricates
|
|
661
|
+
// `engine-private/deploy/dd.cron` and sends the read at whatever the runner happens to have
|
|
662
|
+
// cloned — present on a full checkout, absent in CI. `cronId` states which case is under test.
|
|
663
|
+
const checkout = ({ present, cronId = null }) => {
|
|
664
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) =>
|
|
665
|
+
`${filePath}` === CRON_ID_PATH ? cronId !== null : present,
|
|
666
|
+
);
|
|
667
|
+
vi.spyOn(fs, 'readFileSync').mockImplementation((filePath, ...rest) => {
|
|
668
|
+
if (`${filePath}` === CRON_ID_PATH) return `${cronId}\n`;
|
|
669
|
+
return readFileSync(filePath, ...rest);
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
beforeEach(() => {
|
|
674
|
+
commands = [];
|
|
675
|
+
vi.spyOn(shell, 'exec').mockImplementation((command) => {
|
|
676
|
+
commands.push(command);
|
|
677
|
+
return { code: 0, stdout: '', stderr: '', toString: () => '' };
|
|
678
|
+
});
|
|
679
|
+
vi.spyOn(fs, 'removeSync').mockImplementation(() => undefined);
|
|
680
|
+
vi.spyOn(fs, 'mkdirSync').mockImplementation(() => undefined);
|
|
681
|
+
vi.spyOn(fs, 'copySync').mockImplementation(() => undefined);
|
|
682
|
+
vi.spyOn(fs, 'moveSync').mockImplementation(() => undefined);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
afterEach(() => vi.restoreAllMocks());
|
|
686
|
+
|
|
687
|
+
it('clones the private repository when the checkout is missing', () => {
|
|
688
|
+
checkout({ present: false });
|
|
689
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core'));
|
|
690
|
+
expect(commands[0]).to.equal('cd .. && underpost clone fixture-org/engine-core-private');
|
|
691
|
+
expect(commands.some((command) => command.includes('underpost push . fixture-org/engine-core-private'))).to.equal(
|
|
692
|
+
true,
|
|
693
|
+
);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('resets an existing checkout rather than cloning over it', () => {
|
|
697
|
+
checkout({ present: true, cronId: 'dd-cron' });
|
|
698
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue([]);
|
|
699
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core'));
|
|
700
|
+
expect(commands.some((command) => command.includes('git checkout . && git clean -f -d'))).to.equal(true);
|
|
701
|
+
expect(commands.some((command) => command.includes('underpost clone'))).to.equal(false);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('mirrors only the payload entries belonging to the deploy', () => {
|
|
705
|
+
const copied = [];
|
|
706
|
+
checkout({ present: true, cronId: 'dd-cron' });
|
|
707
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue(['dd-core-blue', 'dd-other-green']);
|
|
708
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => copied.push(`${src} -> ${dest}`));
|
|
709
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core', ['catalog/items.json']));
|
|
710
|
+
expect(copied).to.include('./engine-private/replica/dd-core-blue -> ../engine-core-private/replica/dd-core-blue');
|
|
711
|
+
expect(copied.some((entry) => entry.includes('dd-other-green'))).to.equal(false);
|
|
712
|
+
expect(copied).to.include('./engine-private/catalog/items.json -> ../engine-core-private/catalog/items.json');
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Regression: an instance backup that dropped an item left the item's stale atlas in the
|
|
716
|
+
// mirror, and the pod restored it over the current one from another instance.
|
|
717
|
+
it('replaces each payload entry in the checkout instead of merging into it', () => {
|
|
718
|
+
const calls = [];
|
|
719
|
+
checkout({ present: true });
|
|
720
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue([]);
|
|
721
|
+
vi.spyOn(fs, 'removeSync').mockImplementation((target) => calls.push(`remove ${target}`));
|
|
722
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => calls.push(`copy ${src} -> ${dest}`));
|
|
723
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core', ['cyberia-instances/TEST']));
|
|
724
|
+
const remove = calls.indexOf('remove ../engine-core-private/cyberia-instances/TEST');
|
|
725
|
+
const copy = calls.indexOf(
|
|
726
|
+
'copy ./engine-private/cyberia-instances/TEST -> ../engine-core-private/cyberia-instances/TEST',
|
|
727
|
+
);
|
|
728
|
+
expect(remove).to.be.greaterThan(-1);
|
|
729
|
+
expect(copy).to.be.greaterThan(remove);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('mirrors the cron conf the checkout declares', () => {
|
|
733
|
+
const copied = [];
|
|
734
|
+
checkout({ present: true, cronId: 'dd-cron' });
|
|
735
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue([]);
|
|
736
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => copied.push(`${src} -> ${dest}`));
|
|
737
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core'));
|
|
738
|
+
expect(copied).to.include('./engine-private/conf/dd-cron -> ../engine-core-private/conf/dd-cron');
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// A checkout with no `deploy/dd.cron` is the CI one. Mirroring `conf/null` fails the whole
|
|
742
|
+
// sync over an optional the deploy never declared.
|
|
743
|
+
it('mirrors no cron conf when the checkout declares none', () => {
|
|
744
|
+
const copied = [];
|
|
745
|
+
checkout({ present: true });
|
|
746
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue([]);
|
|
747
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest) => copied.push(`${src} -> ${dest}`));
|
|
748
|
+
withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => syncPrivateConf('dd-core'));
|
|
749
|
+
expect(copied.some((entry) => entry.includes('null'))).to.equal(false);
|
|
750
|
+
expect(commands.some((command) => command.includes('underpost push . fixture-org/engine-core-private'))).to.equal(
|
|
751
|
+
true,
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it('reports that a deploy declares no public sources to move', () => {
|
|
756
|
+
expect(syncDeployIdSources()).to.equal(false);
|
|
757
|
+
expect(syncDeployIdSources([])).to.equal(false);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('moves every declared source that is present, skipping the rest', () => {
|
|
761
|
+
const moved = [];
|
|
762
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => `${filePath}`.includes('present'));
|
|
763
|
+
vi.spyOn(fs, 'moveSync').mockImplementation((src, dest) => moved.push(`${src} -> ${dest}`));
|
|
764
|
+
expect(
|
|
765
|
+
syncDeployIdSources([
|
|
766
|
+
['src/present.js', 'src/api/present.js'],
|
|
767
|
+
['src/absent.js', 'src/api/absent.js'],
|
|
768
|
+
]),
|
|
769
|
+
).to.equal(true);
|
|
770
|
+
expect(moved).to.deep.equal(['src/present.js -> src/api/present.js']);
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
describe('template checkout identity', () => {
|
|
775
|
+
let commands;
|
|
776
|
+
|
|
777
|
+
beforeEach(() => {
|
|
778
|
+
commands = [];
|
|
779
|
+
vi.spyOn(shell, 'exec').mockImplementation((command) => {
|
|
780
|
+
commands.push(command);
|
|
781
|
+
const stdout = command.includes('remote.origin.url')
|
|
782
|
+
? 'https://github.com/underpostnet/pwa-microservices-template.git\n'
|
|
783
|
+
: '';
|
|
784
|
+
return { code: 0, stdout, stderr: '', toString: () => stdout };
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
afterEach(() => vi.restoreAllMocks());
|
|
789
|
+
|
|
790
|
+
it('reads the repository name off the checkout origin', () => {
|
|
791
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
792
|
+
expect(gitOriginRepositoryName('../pwa-microservices-template')).to.equal('pwa-microservices-template');
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('reports no repository for a path that is not a work tree', () => {
|
|
796
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
797
|
+
expect(gitOriginRepositoryName('../nothing')).to.equal(null);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it('reports no repository when the checkout declares no origin', () => {
|
|
801
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
802
|
+
vi.spyOn(shell, 'exec').mockReturnValue({ code: 1, stdout: '', stderr: '', toString: () => '' });
|
|
803
|
+
expect(gitOriginRepositoryName('../detached')).to.equal(null);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it('leaves a checkout that already is the template alone', () => {
|
|
807
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
808
|
+
expect(ensureTemplateCheckout({ toPath: '../pwa-microservices-template' })).to.equal(false);
|
|
809
|
+
expect(commands.some((command) => command.includes('clone'))).to.equal(false);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Publishing steps swap the checkout's `.git` for a product repository's, so
|
|
813
|
+
// a build that trusted whatever it found would read another product's identity
|
|
814
|
+
// back into the base template package.json.
|
|
815
|
+
it('refuses to reuse a foreign checkout under --no-clone', () => {
|
|
816
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
817
|
+
vi.spyOn(shell, 'exec').mockReturnValue({
|
|
818
|
+
code: 0,
|
|
819
|
+
stdout: 'git@github.com:o/engine-core.git\n',
|
|
820
|
+
toString: () => '',
|
|
821
|
+
});
|
|
822
|
+
expect(() => ensureTemplateCheckout({ toPath: '../x', noClone: true })).to.throw('is not a');
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
it('stages a clone and swaps it in only once it is complete', () => {
|
|
826
|
+
const origins = new Map();
|
|
827
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
828
|
+
vi.spyOn(fs, 'mkdtempSync').mockImplementation((prefix) => `${prefix}stage`);
|
|
829
|
+
vi.spyOn(fs, 'removeSync').mockImplementation(() => undefined);
|
|
830
|
+
const moved = [];
|
|
831
|
+
vi.spyOn(fs, 'moveSync').mockImplementation((src, dest) => moved.push(`${src} -> ${dest}`));
|
|
832
|
+
vi.spyOn(shell, 'exec').mockImplementation((command) => {
|
|
833
|
+
commands.push(command);
|
|
834
|
+
const stdout = command.includes('remote.origin.url')
|
|
835
|
+
? origins.get(command) || (command.includes('stage') ? 'o/pwa-microservices-template.git' : 'o/engine-core.git')
|
|
836
|
+
: '';
|
|
837
|
+
return { code: 0, stdout, stderr: '', toString: () => stdout };
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
expect(ensureTemplateCheckout({ toPath: '../pwa-microservices-template', githubUsername: 'fixture-org' })).to.equal(
|
|
841
|
+
true,
|
|
842
|
+
);
|
|
843
|
+
expect(commands.some((command) => command.includes('clone fixture-org/pwa-microservices-template'))).to.equal(true);
|
|
844
|
+
expect(moved.length).to.equal(1);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it('fails when the staged clone is not the template either', () => {
|
|
848
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
849
|
+
vi.spyOn(fs, 'mkdtempSync').mockImplementation((prefix) => `${prefix}stage`);
|
|
850
|
+
vi.spyOn(fs, 'removeSync').mockImplementation(() => undefined);
|
|
851
|
+
vi.spyOn(shell, 'exec').mockReturnValue({ code: 0, stdout: 'o/engine-core.git', toString: () => '' });
|
|
852
|
+
expect(() => ensureTemplateCheckout({ toPath: '../x' })).to.throw('could not clone');
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
it('empties the work tree of everything a build reconstructs', () => {
|
|
856
|
+
const removed = [];
|
|
857
|
+
vi.spyOn(fs, 'readdirSync').mockReturnValue(['.git', 'node_modules', 'src', 'package.json']);
|
|
858
|
+
vi.spyOn(fs, 'removeSync').mockImplementation((filePath) => removed.push(`${filePath}`));
|
|
859
|
+
pruneTemplateWorkTree('../template', ['.git', 'node_modules']);
|
|
860
|
+
expect(removed).to.deep.equal(['../template/src', '../template/package.json']);
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
describe('private template publishing', () => {
|
|
865
|
+
let commands;
|
|
866
|
+
|
|
867
|
+
const stubShell = (hasChanges) => {
|
|
868
|
+
commands = [];
|
|
869
|
+
vi.spyOn(shell, 'exec').mockImplementation((command) => {
|
|
870
|
+
commands.push(command);
|
|
871
|
+
const stdout = command.includes('--has-changes') ? hasChanges : '';
|
|
872
|
+
return { code: 0, stdout, stderr: '', toString: () => stdout };
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
afterEach(() => vi.restoreAllMocks());
|
|
877
|
+
|
|
878
|
+
it('commits and pushes the private template only when it changed', async () => {
|
|
879
|
+
stubShell('1');
|
|
880
|
+
await updatePrivateTemplateRepo();
|
|
881
|
+
expect(commands.some((command) => command.includes("git commit -m 'Update template'"))).to.equal(true);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it('leaves the private template alone when nothing changed', async () => {
|
|
885
|
+
stubShell('0');
|
|
886
|
+
await updatePrivateTemplateRepo();
|
|
887
|
+
expect(commands.some((command) => command.includes('git commit'))).to.equal(false);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
it('refuses to publish a test repo before the template is assembled', async () => {
|
|
891
|
+
stubShell('1');
|
|
892
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
893
|
+
await expect(updatePrivateEngineTestRepo('dd-core')).rejects.toThrow('assemble the template first');
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('publishes the assembled template from a work tree of its own', async () => {
|
|
897
|
+
stubShell('1');
|
|
898
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
899
|
+
const copied = [];
|
|
900
|
+
vi.spyOn(fs, 'copySync').mockImplementation((src, dest, options) => copied.push({ src, dest, options }));
|
|
901
|
+
await withEnv({ GITHUB_USERNAME: 'fixture-org' }, () => updatePrivateEngineTestRepo('dd-core'));
|
|
902
|
+
|
|
903
|
+
expect(
|
|
904
|
+
commands.some((command) => command.includes('underpost clone --bare fixture-org/engine-test-core')),
|
|
905
|
+
).to.equal(true);
|
|
906
|
+
expect(copied[0].dest).to.equal('/home/dd/engine-test-core');
|
|
907
|
+
// The template's own git history and installed modules belong to the
|
|
908
|
+
// checkout, not to the published source.
|
|
909
|
+
expect(copied[0].options.filter('/home/dd/pwa-microservices-template/.git')).to.equal(false);
|
|
910
|
+
expect(copied[0].options.filter('/home/dd/pwa-microservices-template/node_modules/x')).to.equal(false);
|
|
911
|
+
expect(copied[0].options.filter('/home/dd/pwa-microservices-template/src/index.js')).to.equal(true);
|
|
912
|
+
expect(commands.some((command) => command.includes("git commit -m 'Update engine-test-core'"))).to.equal(true);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it('reports nothing to publish when the test repo is up to date', async () => {
|
|
916
|
+
stubShell('0');
|
|
917
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
918
|
+
vi.spyOn(fs, 'copySync').mockImplementation(() => undefined);
|
|
919
|
+
await updatePrivateEngineTestRepo('dd-core');
|
|
920
|
+
expect(commands.some((command) => command.includes('git commit'))).to.equal(false);
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
describe('CLI documentation', () => {
|
|
925
|
+
const HELP_ROOT = [
|
|
926
|
+
'Usage: underpost [options] [command]',
|
|
927
|
+
'Underpost CI/CD CLI v3.2.9',
|
|
928
|
+
'',
|
|
929
|
+
'Options:',
|
|
930
|
+
' -V, --version output the version number',
|
|
931
|
+
' -h, --help display help',
|
|
932
|
+
'',
|
|
933
|
+
'Commands:',
|
|
934
|
+
' cron [options] <deploy-list> Cron jobs management,',
|
|
935
|
+
' scheduled on the cluster',
|
|
936
|
+
' help [command] display help for command',
|
|
937
|
+
'',
|
|
938
|
+
].join('\n');
|
|
939
|
+
|
|
940
|
+
const HELP_CRON = [
|
|
941
|
+
'Usage: underpost cron [options] <deploy-list> <job-list>',
|
|
942
|
+
'Cron jobs management',
|
|
943
|
+
'',
|
|
944
|
+
'Arguments:',
|
|
945
|
+
' deploy-list Comma separated deploy ids',
|
|
946
|
+
'',
|
|
947
|
+
'Options:',
|
|
948
|
+
' --apply Apply the manifests',
|
|
949
|
+
'',
|
|
950
|
+
].join('\n');
|
|
951
|
+
|
|
952
|
+
afterEach(() => vi.restoreAllMocks());
|
|
953
|
+
|
|
954
|
+
it('renders the command index and per command reference from commander help', () => {
|
|
955
|
+
const written = new Map();
|
|
956
|
+
vi.spyOn(shell, 'exec').mockImplementation((command) => {
|
|
957
|
+
const stdout = command.includes('help cron') ? HELP_CRON : HELP_ROOT;
|
|
958
|
+
return { code: 0, stdout, stderr: '', toString: () => stdout };
|
|
959
|
+
});
|
|
960
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue(
|
|
961
|
+
['# Engine v3.2.9', '<!-- cli-index-start -->', 'stale', '<!-- cli-index-end -->', 'tail'].join('\n'),
|
|
962
|
+
);
|
|
963
|
+
vi.spyOn(fs, 'writeFileSync').mockImplementation((filePath, value) => written.set(`${filePath}`, `${value}`));
|
|
964
|
+
|
|
965
|
+
buildCliDoc({ commands: [{ _name: 'cron' }, { _name: 'help' }] }, '3.2.9', '3.3.0');
|
|
966
|
+
|
|
967
|
+
const cliHelp = written.get('./CLI-HELP.md');
|
|
968
|
+
expect(cliHelp).to.include('## Underpost CLI');
|
|
969
|
+
expect(cliHelp).to.include('> Underpost CI/CD CLI v3.3.0');
|
|
970
|
+
expect(cliHelp).to.include('| [`cron`](#underpost-cron) | Cron jobs management, scheduled on the cluster |');
|
|
971
|
+
expect(cliHelp).to.include('### underpost cron');
|
|
972
|
+
expect(cliHelp).to.include('#### Arguments');
|
|
973
|
+
expect(cliHelp).to.include('#### Options');
|
|
974
|
+
// The index only lists commands; `help` is commander's own and never a
|
|
975
|
+
// documented one.
|
|
976
|
+
expect(cliHelp).not.to.include('### underpost help');
|
|
977
|
+
expect(written.get('./src/client/public/nexodev/docs/references/Command Line Interface.md')).to.equal(cliHelp);
|
|
978
|
+
|
|
979
|
+
const readme = written.get('./README.md');
|
|
980
|
+
expect(readme).to.include('# Engine v3.3.0');
|
|
981
|
+
expect(readme).to.include('(CLI-HELP.md#underpost-cron)');
|
|
982
|
+
expect(readme).not.to.include('stale');
|
|
983
|
+
expect(readme.endsWith('tail')).to.equal(true);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
it('leaves a README carrying no CLI index tags alone apart from the version', () => {
|
|
987
|
+
const written = new Map();
|
|
988
|
+
vi.spyOn(shell, 'exec').mockReturnValue({ code: 0, stdout: HELP_ROOT, toString: () => HELP_ROOT });
|
|
989
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue('# Engine v3.2.9\n');
|
|
990
|
+
vi.spyOn(fs, 'writeFileSync').mockImplementation((filePath, value) => written.set(`${filePath}`, `${value}`));
|
|
991
|
+
|
|
992
|
+
buildCliDoc({ commands: [] }, '3.2.9', '3.3.0');
|
|
993
|
+
|
|
994
|
+
expect(written.get('./README.md')).to.equal('# Engine v3.3.0\n');
|
|
995
|
+
});
|
|
996
|
+
});
|