@underpostnet/cyberia 3.3.73 → 3.3.77
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/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +10 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +1 -1
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/AGENTS.md +61 -12
- package/CHANGELOG.md +159 -161
- package/CLI-HELP.md +20 -13
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +2 -2
- package/README.md +28 -28
- package/bin/build.js +96 -21
- package/bin/cyberia.js +1247 -1373
- package/bin/index.js +1247 -1373
- package/compose.env +17 -5
- package/conf.js +7 -4
- package/deploy/cyberia-client/deploy.sh +10 -11
- package/deploy/cyberia-client/package.sh +0 -1
- package/deploy/cyberia-client/state.sh +3 -2
- package/deploy/cyberia-server/deploy.sh +10 -11
- package/deploy/cyberia-server/package.sh +0 -1
- package/deploy/cyberia-server/state.sh +3 -2
- package/deploy/dd-cyberia/deploy.sh +5 -5
- package/deploy/dd-cyberia/init.sh +15 -14
- package/deploy/dd-cyberia/package.sh +0 -1
- package/deploy/dd-cyberia/state.sh +2 -2
- package/deploy/dd-cyberia/sync-deploy.sh +139 -75
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +92 -76
- package/deploy/lib/host.sh +28 -19
- package/deploy/lib/state.sh +4 -1
- package/deploy/pwa-microservices-template/deploy.sh +10 -8
- package/deploy/release/deploy.sh +0 -2
- package/deployment.yaml +1 -1
- package/docker-compose.yml +23 -17
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
- package/docs/coverage/cyberia/base.css +362 -0
- package/docs/coverage/cyberia/block-navigation.js +82 -0
- package/docs/coverage/cyberia/favicon.png +0 -0
- package/docs/coverage/cyberia/index.html +161 -0
- package/docs/coverage/cyberia/prettify.css +101 -0
- package/docs/coverage/cyberia/prettify.js +937 -0
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
- package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
- package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
- package/docs/coverage/cyberia/sorter.js +205 -0
- package/hardhat/package-lock.json +2 -2
- package/hardhat/package.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- 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/package.json +19 -17
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
- package/src/api/cyberia-action/cyberia-action.router.js +2 -2
- package/src/api/cyberia-action/cyberia-action.service.js +3 -4
- 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 +1 -3
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
- 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 +16 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
- package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -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 +34 -141
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
- package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
- package/src/api/cyberia-map/cyberia-map.service.js +6 -2
- 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.router.js +3 -3
- package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +10 -3
- package/src/api/object-layer/object-layer.service.js +113 -120
- package/src/cli/app.js +12 -1
- package/src/cli/client.js +2 -2
- package/src/cli/cluster.js +80 -18
- package/src/cli/deploy.js +215 -34
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +158 -342
- package/src/cli/index.js +22 -10
- package/src/cli/release.js +3 -1
- package/src/cli/repository.js +35 -9
- package/src/cli/run.js +80 -199
- package/src/cli/test.js +4 -0
- package/src/cli/wireguard.js +32 -31
- package/src/client/components/core/Badge.js +0 -1
- package/src/client/components/core/CssCore.js +8 -1
- package/src/client/components/core/Docs.js +77 -100
- package/src/client/components/core/Modal.js +77 -23
- package/src/client/components/core/ToolTip.js +15 -0
- package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -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 +0 -34
- 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/RouterCyberiaPortal.js +0 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -4
- package/src/client/components/itemledger/AppShellItemledger.js +0 -2
- package/src/client/components/underpost/AppShellUnderpost.js +1 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
- 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 +28 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- 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/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/views/Cyberia404.js +3 -3
- package/src/client-builder/client-build-docs.js +52 -52
- package/src/client-builder/client-build.js +67 -44
- package/src/client-builder/client-bundle.js +347 -0
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/grpc/cyberia/grpc-server.js +5 -7
- package/src/index.js +1 -1
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +5 -14
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +3 -2
- package/src/projects/cyberia/gemini-client.js +3 -3
- package/src/projects/cyberia/generate-saga.js +4 -11
- package/src/projects/cyberia/hot-reload-trigger.js +17 -28
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +177 -257
- package/src/projects/cyberia/map-preview-generator.js +32 -68
- package/src/projects/cyberia/object-layer.js +152 -319
- 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 +2 -18
- 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/runtime/cyberia-client/Dockerfile +8 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -2
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/server/build/coverage.js +113 -42
- package/src/server/build/package.js +55 -12
- package/src/server/build/testing.js +59 -9
- package/src/server/network/middlewares.js +8 -2
- package/src/server/network/underpost-gateway.js +113 -14
- package/src/server/ops/logger.js +70 -35
- package/src/server/runtime/conf.js +40 -9
- package/src/server/runtime/start.js +11 -2
- package/src/server/storage/data-query.js +16 -0
- package/src/server/storage/downloader.js +62 -14
- package/src/server/storage/zip.js +153 -0
- 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 +34 -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/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
- 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 +87 -2
- 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/integration/infra/2-network/wireguard-cli.test.js +33 -9
- package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
- 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/underpost-ingress.test.js +1 -1
- package/test/unit/catalog.test.js +13 -2
- package/test/unit/client-build-docs.test.js +97 -70
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-resolution.test.js +58 -7
- package/test/unit/coverage-artifact.test.js +120 -29
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-data.test.js +314 -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-legacy-gateway-sweep.test.js +63 -0
- package/test/unit/deploy-log-table.test.js +76 -5
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/gateway-pod-fetch.test.js +95 -0
- package/test/unit/letsencrypt-issuer.test.js +101 -0
- package/test/unit/logger-redaction.test.js +29 -0
- package/test/unit/package.test.js +70 -16
- package/test/unit/prepare-host.test.js +84 -1
- package/test/unit/release-bump.test.js +3 -1
- package/test/unit/start-options.test.js +12 -0
- package/test/unit/test-tiers.test.js +27 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +11 -3
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
package/src/cli/deploy.js
CHANGED
|
@@ -50,6 +50,8 @@ import nodePath from 'node:path';
|
|
|
50
50
|
import dotenv from 'dotenv';
|
|
51
51
|
import os from 'node:os';
|
|
52
52
|
import crypto from 'node:crypto';
|
|
53
|
+
import { fileURLToPath } from 'node:url';
|
|
54
|
+
import { dump as yamlDump, load as yamlLoad } from 'js-yaml';
|
|
53
55
|
import { appSecretName } from './app.js';
|
|
54
56
|
import { domainContextFactory } from './domains.js';
|
|
55
57
|
import Underpost from '../index.js';
|
|
@@ -93,6 +95,23 @@ const UPSTREAM_FAILURE_STATUSES = [502, 503, 504];
|
|
|
93
95
|
|
|
94
96
|
const CONTAINER_ENGINE_ROOT = '/home/dd/engine';
|
|
95
97
|
|
|
98
|
+
/**
|
|
99
|
+
* @constant LETSENCRYPT_ISSUER_NAME
|
|
100
|
+
* @description The ClusterIssuer every generated Certificate references.
|
|
101
|
+
* @memberof UnderpostDeploy
|
|
102
|
+
*/
|
|
103
|
+
const LETSENCRYPT_ISSUER_NAME = 'letsencrypt-prod';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @constant LETSENCRYPT_ISSUER_MANIFEST
|
|
107
|
+
* @description Base ClusterIssuer manifest: account, ACME server and key Secret. Resolved
|
|
108
|
+
* from this module so an installed CLI and a checkout read the same file.
|
|
109
|
+
* @memberof UnderpostDeploy
|
|
110
|
+
*/
|
|
111
|
+
const LETSENCRYPT_ISSUER_MANIFEST = fileURLToPath(
|
|
112
|
+
new URL(`../../manifests/${LETSENCRYPT_ISSUER_NAME}.yaml`, import.meta.url),
|
|
113
|
+
);
|
|
114
|
+
|
|
96
115
|
// The manifest set `deploy --build-manifest` produces for one environment.
|
|
97
116
|
const DEFAULT_MANIFEST_FILES = [
|
|
98
117
|
'deployment.yaml',
|
|
@@ -147,10 +166,10 @@ const GATEWAY_DURATION_UNITS = [
|
|
|
147
166
|
];
|
|
148
167
|
|
|
149
168
|
/**
|
|
150
|
-
* Converts an HTTPProxy duration (`
|
|
169
|
+
* Converts an HTTPProxy duration (`10000ms`, `10s`, `infinity`) into a Gateway
|
|
151
170
|
* API Duration. The Gateway API grammar allows at most 5 digits per component,
|
|
152
171
|
* so a value that overflows in one unit is re-expressed in a coarser one
|
|
153
|
-
* (`
|
|
172
|
+
* (`10000ms` → `5m`); `infinity` maps to `0s`, which disables the timeout.
|
|
154
173
|
* @param {string|number} value - Source duration.
|
|
155
174
|
* @returns {string|null} Gateway API Duration, or null when unset/unparsable.
|
|
156
175
|
*/
|
|
@@ -255,12 +274,68 @@ ${buildKindPorts(fromPort, toPort)}`;
|
|
|
255
274
|
: Underpost.deploy.trafficServiceYamlFactory({ deployId, env, traffic, namespace, fromPort, toPort });
|
|
256
275
|
const escaped = `${deployId}-${env}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
257
276
|
manifest = manifest.replace(new RegExp(`app: ${escaped}-(?:blue|green)`), `app: ${deployId}-${env}-${traffic}`);
|
|
258
|
-
|
|
277
|
+
logger.info('Applying the traffic Service', { deployId, env, traffic, namespace });
|
|
278
|
+
shellExec(
|
|
279
|
+
`kubectl apply -f - -n ${namespace} <<'EOF'
|
|
259
280
|
${manifest}
|
|
260
281
|
EOF
|
|
261
|
-
|
|
282
|
+
`,
|
|
283
|
+
{ disableLog: true },
|
|
284
|
+
);
|
|
262
285
|
return Underpost.deploy.trafficServiceNameFactory({ deployId, env });
|
|
263
286
|
},
|
|
287
|
+
/**
|
|
288
|
+
* Removes the Gateway API objects an earlier model of this deploy left behind, so
|
|
289
|
+
* they stop competing with the consolidated `<deployId>-<env>` Gateway.
|
|
290
|
+
*
|
|
291
|
+
* The per-host model emitted one Gateway and one `<host>-http3` ClientTrafficPolicy
|
|
292
|
+
* per hostname. Left in place they duplicate that hostname's listeners in the same
|
|
293
|
+
* merged set, and the oldest resource retains traffic. `undefined` / `undefined-http3`
|
|
294
|
+
* is the same problem under a different name: the consolidated objects were briefly
|
|
295
|
+
* emitted with an unresolved host in their metadata, and merged listeners are
|
|
296
|
+
* configured by the oldest policy that targets them.
|
|
297
|
+
*
|
|
298
|
+
* The namespace is listed once and only objects that exist are deleted: a sweep that
|
|
299
|
+
* issued a blind delete per host per kind spent two dozen `kubectl` round-trips per
|
|
300
|
+
* sync on names that were gone after the first migration, and buried the one line
|
|
301
|
+
* that mattered when something was actually removed.
|
|
302
|
+
* @param {object} params
|
|
303
|
+
* @param {string[]} params.hosts - Hostnames the deploy's `conf.server.json` declares.
|
|
304
|
+
* @param {string} params.namespace - Namespace the deploy's Gateway objects live in.
|
|
305
|
+
* @returns {{gateways: string[], policies: string[]}} Names removed, per kind.
|
|
306
|
+
* @memberof UnderpostDeploy
|
|
307
|
+
*/
|
|
308
|
+
sweepLegacyGatewayObjects({ hosts = [], namespace = 'default' }) {
|
|
309
|
+
const existing = (kind) =>
|
|
310
|
+
new Set(
|
|
311
|
+
`${
|
|
312
|
+
shellExec(`kubectl get ${kind} -n ${namespace} -o name --ignore-not-found`, {
|
|
313
|
+
stdout: true,
|
|
314
|
+
silent: true,
|
|
315
|
+
silentOnError: true,
|
|
316
|
+
disableLog: true,
|
|
317
|
+
}) ?? ''
|
|
318
|
+
}`
|
|
319
|
+
.split('\n')
|
|
320
|
+
.map((line) => line.trim().split('/').pop())
|
|
321
|
+
.filter(Boolean),
|
|
322
|
+
);
|
|
323
|
+
const legacy = [...new Set(hosts.filter(Boolean)), 'undefined'];
|
|
324
|
+
const gateways = existing('Gateway');
|
|
325
|
+
const policies = existing('ClientTrafficPolicy');
|
|
326
|
+
const removed = {
|
|
327
|
+
gateways: legacy.filter((name) => gateways.has(name)),
|
|
328
|
+
policies: legacy.map((name) => `${name}-http3`).filter((name) => policies.has(name)),
|
|
329
|
+
};
|
|
330
|
+
for (const name of removed.gateways)
|
|
331
|
+
shellExec(`sudo kubectl delete Gateway ${name} -n ${namespace} --ignore-not-found`);
|
|
332
|
+
for (const name of removed.policies)
|
|
333
|
+
shellExec(`sudo kubectl delete ClientTrafficPolicy ${name} -n ${namespace} --ignore-not-found`);
|
|
334
|
+
if (removed.gateways.length > 0 || removed.policies.length > 0)
|
|
335
|
+
logger.warn('Legacy per-host Gateway API objects removed', { namespace, ...removed });
|
|
336
|
+
return removed;
|
|
337
|
+
},
|
|
338
|
+
|
|
264
339
|
/**
|
|
265
340
|
* Removes the route kind owned by the inactive ingress stack. The active
|
|
266
341
|
* route is already published before this runs, so migration never creates a
|
|
@@ -617,7 +692,7 @@ spec:
|
|
|
617
692
|
* @param {string} options.namespace - Kubernetes namespace for the deployment (defaults to "default").
|
|
618
693
|
* @param {string} [options.versions] - Comma-separated list of versions to deploy.
|
|
619
694
|
* @param {string} [options.cmd] - Custom initialization command for deploymentYamlPartsFactory (comma-separated commands).
|
|
620
|
-
* @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "
|
|
695
|
+
* @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "10000ms", "infinity").
|
|
621
696
|
* @param {string} [options.timeoutIdle] - HTTPProxy per-route idle timeout (e.g. "10s", "infinity").
|
|
622
697
|
* @param {string} [options.retryCount] - HTTPProxy per-route retry count (e.g. 3).
|
|
623
698
|
* @param {string} [options.retryPerTryTimeout] - HTTPProxy per-route per-try timeout (e.g. "150ms").
|
|
@@ -1112,6 +1187,125 @@ spec:
|
|
|
1112
1187
|
kind: ClusterIssuer
|
|
1113
1188
|
secretName: ${host}`;
|
|
1114
1189
|
},
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* The Gateways an ACME HTTP-01 challenge route must attach to: every Gateway the
|
|
1193
|
+
* namespace holds now, plus the one each routed deploy publishes under, whether or
|
|
1194
|
+
* not it exists yet. A challenge HTTPRoute lists them all as parents; the data plane
|
|
1195
|
+
* attaches it to whichever one carries a listener for the challenged hostname, and
|
|
1196
|
+
* reports the rest as unmatched, which is harmless.
|
|
1197
|
+
*
|
|
1198
|
+
* The union rather than either side alone: the live list covers Gateways a deploy
|
|
1199
|
+
* outside `dd.routes` created, the route table covers a deploy that has not been
|
|
1200
|
+
* applied yet — an issuer that only knew the live list would have to be re-applied
|
|
1201
|
+
* after every first deploy before that deploy's certificate could issue.
|
|
1202
|
+
* @param {object} params
|
|
1203
|
+
* @param {string} [params.namespace] - Namespace the Gateways live in.
|
|
1204
|
+
* @param {string} [params.env] - Environment the routed deploys publish under.
|
|
1205
|
+
* @returns {Array<{name: string, namespace: string}>} Parent references, sorted by name.
|
|
1206
|
+
* @memberof UnderpostDeploy
|
|
1207
|
+
*/
|
|
1208
|
+
acmeGatewayParentRefsFactory({ namespace = 'default', env = 'production' } = {}) {
|
|
1209
|
+
const live = `${
|
|
1210
|
+
shellExec(`kubectl get gateway -n ${namespace} -o name --ignore-not-found`, {
|
|
1211
|
+
stdout: true,
|
|
1212
|
+
silent: true,
|
|
1213
|
+
silentOnError: true,
|
|
1214
|
+
disableLog: true,
|
|
1215
|
+
}) ?? ''
|
|
1216
|
+
}`
|
|
1217
|
+
.split('\n')
|
|
1218
|
+
.map((line) => line.trim().split('/').pop())
|
|
1219
|
+
.filter(Boolean);
|
|
1220
|
+
const routed = readDeployRoutes().map((deployId) => Underpost.deploy.gatewayNameFactory({ deployId, env }));
|
|
1221
|
+
return [...new Set([...live, ...routed])].sort().map((name) => ({ name, namespace }));
|
|
1222
|
+
},
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Renders the Let's Encrypt ClusterIssuer for the ingress stack that actually
|
|
1226
|
+
* carries the HTTP-01 challenge.
|
|
1227
|
+
*
|
|
1228
|
+
* The base manifest fixes the account (email, ACME server, key Secret); only the
|
|
1229
|
+
* solver is decided here. With the Gateway API stack, the challenge must be answered
|
|
1230
|
+
* through an HTTPRoute on the deploy's Gateway: an `ingress`-class solver creates an
|
|
1231
|
+
* Ingress nothing on the request path reads, so `/.well-known/acme-challenge/*` is
|
|
1232
|
+
* routed by the application's own HTTPRoute to the workload — a 404 — or, with no
|
|
1233
|
+
* workload behind it, to the maintenance fallback. Every renewal fails that way while
|
|
1234
|
+
* the solver pods sit there ready, and the failure only surfaces when the certificate
|
|
1235
|
+
* expires.
|
|
1236
|
+
* @param {object} params
|
|
1237
|
+
* @param {'gateway'|'ingress'} params.solver - Which stack answers the challenge.
|
|
1238
|
+
* @param {Array<{name: string, namespace: string}>} [params.parentRefs] - Gateways for the `gateway` solver.
|
|
1239
|
+
* @param {string} [params.basePath] - Base ClusterIssuer manifest.
|
|
1240
|
+
* @returns {string} ClusterIssuer YAML.
|
|
1241
|
+
* @throws {Error} When the Gateway solver is requested with no Gateway to attach to.
|
|
1242
|
+
* @memberof UnderpostDeploy
|
|
1243
|
+
*/
|
|
1244
|
+
letsEncryptIssuerYamlFactory({ solver, parentRefs = [], basePath = LETSENCRYPT_ISSUER_MANIFEST }) {
|
|
1245
|
+
const issuer = yamlLoad(fs.readFileSync(basePath, 'utf8'));
|
|
1246
|
+
if (solver === 'gateway') {
|
|
1247
|
+
if (parentRefs.length === 0)
|
|
1248
|
+
throw new Error('[cert-manager] the Gateway API HTTP-01 solver needs at least one Gateway parentRef');
|
|
1249
|
+
issuer.spec.acme.solvers = [
|
|
1250
|
+
{
|
|
1251
|
+
http01: {
|
|
1252
|
+
gatewayHTTPRoute: {
|
|
1253
|
+
parentRefs: parentRefs.map(({ name, namespace }) => ({ name, namespace, kind: 'Gateway' })),
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
];
|
|
1258
|
+
} else issuer.spec.acme.solvers = [{ http01: { ingress: { class: 'contour' } } }];
|
|
1259
|
+
return yamlDump(issuer, { lineWidth: -1, noRefs: true });
|
|
1260
|
+
},
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Whether the Let's Encrypt ClusterIssuer is installed. False both when it was never
|
|
1264
|
+
* applied and when cert-manager's CRDs are absent, which is the same answer here.
|
|
1265
|
+
* @returns {boolean}
|
|
1266
|
+
* @memberof UnderpostDeploy
|
|
1267
|
+
*/
|
|
1268
|
+
letsEncryptIssuerExists() {
|
|
1269
|
+
return (
|
|
1270
|
+
`${
|
|
1271
|
+
shellExec(`kubectl get clusterissuer ${LETSENCRYPT_ISSUER_NAME} -o name --ignore-not-found`, {
|
|
1272
|
+
stdout: true,
|
|
1273
|
+
silent: true,
|
|
1274
|
+
silentOnError: true,
|
|
1275
|
+
disableLog: true,
|
|
1276
|
+
}) ?? ''
|
|
1277
|
+
}`.trim().length > 0
|
|
1278
|
+
);
|
|
1279
|
+
},
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Applies the Let's Encrypt ClusterIssuer for the stack in use, so certificates keep
|
|
1283
|
+
* renewing through whichever data plane the deploy's hostnames are served by.
|
|
1284
|
+
* @param {object} params
|
|
1285
|
+
* @param {'gateway'|'ingress'} params.solver - Which stack answers the challenge.
|
|
1286
|
+
* @param {string} [params.namespace] - Namespace the Gateways live in.
|
|
1287
|
+
* @param {string} [params.env] - Environment the routed deploys publish under.
|
|
1288
|
+
* @param {string} [params.basePath] - Base ClusterIssuer manifest.
|
|
1289
|
+
* @returns {{solver: string, parentRefs: Array<{name: string, namespace: string}>}} What was applied.
|
|
1290
|
+
* @memberof UnderpostDeploy
|
|
1291
|
+
*/
|
|
1292
|
+
applyLetsEncryptIssuer({ solver, namespace = 'default', env = 'production', basePath } = {}) {
|
|
1293
|
+
const parentRefs = solver === 'gateway' ? Underpost.deploy.acmeGatewayParentRefsFactory({ namespace, env }) : [];
|
|
1294
|
+
const manifest = Underpost.deploy.letsEncryptIssuerYamlFactory({ solver, parentRefs, basePath });
|
|
1295
|
+
const stagePath = `/dev/shm/underpost-${LETSENCRYPT_ISSUER_NAME}.yaml`;
|
|
1296
|
+
fs.writeFileSync(stagePath, manifest, 'utf8');
|
|
1297
|
+
try {
|
|
1298
|
+
shellExec(`sudo kubectl apply -f ${stagePath}`);
|
|
1299
|
+
} finally {
|
|
1300
|
+
fs.removeSync(stagePath);
|
|
1301
|
+
}
|
|
1302
|
+
logger.info("Let's Encrypt ClusterIssuer applied", {
|
|
1303
|
+
issuer: LETSENCRYPT_ISSUER_NAME,
|
|
1304
|
+
solver,
|
|
1305
|
+
gateways: parentRefs.map(({ name }) => name),
|
|
1306
|
+
});
|
|
1307
|
+
return { solver, parentRefs };
|
|
1308
|
+
},
|
|
1115
1309
|
/**
|
|
1116
1310
|
* Mirrors each deploy's built development manifests into the project tree, and refreshes the
|
|
1117
1311
|
* public default configuration they belong to.
|
|
@@ -2185,7 +2379,7 @@ ${rules}`;
|
|
|
2185
2379
|
* @param {boolean} options.disableUpdateVolume - Whether to disable volume updates.
|
|
2186
2380
|
* @param {boolean} options.disableUpdateUnderpostConfig - Whether to disable Underpost config updates.
|
|
2187
2381
|
* @param {string} [options.namespace] - Kubernetes namespace for the deployment (defaults to "default").
|
|
2188
|
-
* @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "
|
|
2382
|
+
* @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "10000ms", "infinity").
|
|
2189
2383
|
* @param {string} [options.timeoutIdle] - HTTPProxy per-route idle timeout (e.g. "10s", "infinity").
|
|
2190
2384
|
* @param {string} [options.retryCount] - HTTPProxy per-route retry count (e.g. 3).
|
|
2191
2385
|
* @param {string} [options.retryPerTryTimeout] - HTTPProxy per-route per-try timeout (e.g. "150ms").
|
|
@@ -2341,34 +2535,15 @@ EOF`);
|
|
|
2341
2535
|
});
|
|
2342
2536
|
}
|
|
2343
2537
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
// one, and leaving them behind duplicates that hostname's listeners
|
|
2354
|
-
// in the same merged set. The oldest resource would retain traffic.
|
|
2355
|
-
//
|
|
2356
|
-
// `undefined-http3` is the same problem under a different name: the
|
|
2357
|
-
// consolidated policy was briefly emitted with an unresolved host in
|
|
2358
|
-
// its metadata. Merged listeners are configured by the oldest policy
|
|
2359
|
-
// that targets them, so that object outranks the correctly named one
|
|
2360
|
-
// for as long as it exists.
|
|
2361
|
-
if (options.gatewayApi)
|
|
2362
|
-
for (const name of [host, 'undefined']) {
|
|
2363
|
-
shellExec(`sudo kubectl delete Gateway ${name} -n ${namespace} --ignore-not-found`, { silent: true });
|
|
2364
|
-
shellExec(`sudo kubectl delete ClientTrafficPolicy ${name}-http3 -n ${namespace} --ignore-not-found`, {
|
|
2365
|
-
silent: true,
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2368
|
-
if (Underpost.deploy.isCertManagerContext({ host, env, options }))
|
|
2369
|
-
shellExec(`sudo kubectl delete Certificate ${host} -n ${namespace} --ignore-not-found`);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2538
|
+
// The host's route object is left in place and replaced by the `apply`
|
|
2539
|
+
// below. Deleting it first unpublished the hostname for the whole
|
|
2540
|
+
// reconciliation window, so every promote dropped live requests before
|
|
2541
|
+
// the new colour was ever the question.
|
|
2542
|
+
if (!options.disableUpdateProxy && options.gatewayApi)
|
|
2543
|
+
Underpost.deploy.sweepLegacyGatewayObjects({ hosts: Object.keys(confServer), namespace });
|
|
2544
|
+
for (const host of Object.keys(confServer))
|
|
2545
|
+
if (!options.disableUpdateProxy && Underpost.deploy.isCertManagerContext({ host, env, options }))
|
|
2546
|
+
shellExec(`sudo kubectl delete Certificate ${host} -n ${namespace} --ignore-not-found`);
|
|
2372
2547
|
|
|
2373
2548
|
const manifestsPath =
|
|
2374
2549
|
env === 'production'
|
|
@@ -2480,6 +2655,11 @@ EOF`);
|
|
|
2480
2655
|
}
|
|
2481
2656
|
|
|
2482
2657
|
if (Underpost.deploy.isCertManagerContext({ host: Object.keys(confServer)[0], env, options })) {
|
|
2658
|
+
// The issuer's challenge route must list this deploy's Gateway as a parent, or
|
|
2659
|
+
// the certificates applied below can never issue. Re-rendered from the live
|
|
2660
|
+
// Gateway list, so a deploy created after `cluster --cert-manager` is covered.
|
|
2661
|
+
if (options.gatewayApi && Underpost.deploy.letsEncryptIssuerExists())
|
|
2662
|
+
Underpost.deploy.applyLetsEncryptIssuer({ solver: 'gateway', namespace, env });
|
|
2483
2663
|
const secretPath = `./${manifestsPath}/secret.yaml`;
|
|
2484
2664
|
if (fs.existsSync(secretPath) && fs.readFileSync(secretPath, 'utf8').trim()) {
|
|
2485
2665
|
shellExec(`sudo kubectl apply -f ${secretPath} -n ${namespace}`);
|
|
@@ -3143,3 +3323,4 @@ spec:
|
|
|
3143
3323
|
}
|
|
3144
3324
|
|
|
3145
3325
|
export default UnderpostDeploy;
|
|
3326
|
+
export { LETSENCRYPT_ISSUER_MANIFEST, LETSENCRYPT_ISSUER_NAME };
|
|
@@ -192,7 +192,7 @@ services:
|
|
|
192
192
|
MONGO_IMAGE=mongo:latest
|
|
193
193
|
VALKEY_IMAGE=valkey/valkey:latest
|
|
194
194
|
APP_IMAGE=underpost/underpost-engine
|
|
195
|
-
APP_TAG=v3.3.
|
|
195
|
+
APP_TAG=v3.3.77
|
|
196
196
|
PROXY_IMAGE=nginx:stable-alpine
|
|
197
197
|
PROMETHEUS_IMAGE=prom/prometheus:latest
|
|
198
198
|
GRAFANA_IMAGE=grafana/grafana:latest
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import UnderpostRepository from './repository.js';
|
|
4
|
+
|
|
5
|
+
const manifestFilterNames = ['key', 'fromKey', 'toKey', 'keyRegex'];
|
|
6
|
+
|
|
7
|
+
const normalizeStoragePath = (value) => {
|
|
8
|
+
if (value === undefined || value === '') return '';
|
|
9
|
+
const absolute = path.resolve(value);
|
|
10
|
+
const relative = path.relative(process.cwd(), absolute);
|
|
11
|
+
return (
|
|
12
|
+
path.isAbsolute(value) && (relative === '..' || relative.startsWith(`..${path.sep}`)) ? absolute : relative || '.'
|
|
13
|
+
)
|
|
14
|
+
.split(path.sep)
|
|
15
|
+
.join('/');
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const isWithinScope = (key, scope) => {
|
|
19
|
+
if (scope === undefined || scope === '') return true;
|
|
20
|
+
const relative = path.relative(path.resolve(scope), path.resolve(key));
|
|
21
|
+
return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const validateSelectionOptions = (scope, options) => {
|
|
25
|
+
if (options.git && options.tracked) throw new Error('Use either --git or --tracked.');
|
|
26
|
+
if (!options.tracked && !scope) throw new Error('Provide a filesystem path, or use --tracked.');
|
|
27
|
+
if (options.rm && options.pull) throw new Error('Use either --rm or --pull.');
|
|
28
|
+
if (options.omitUnzip && !options.pull) throw new Error('--omit-unzip requires --pull.');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const filterManifestEntries = (entries, options = {}, scope) => {
|
|
32
|
+
const keys = entries.map(([key]) => key);
|
|
33
|
+
const keyIndex = (name) => {
|
|
34
|
+
const index = keys.indexOf(options[name]);
|
|
35
|
+
if (index < 0) throw new Error(`Manifest key not found for ${name}: ${options[name]}`);
|
|
36
|
+
return index;
|
|
37
|
+
};
|
|
38
|
+
const start = options.fromKey === undefined ? 0 : keyIndex('fromKey');
|
|
39
|
+
const end = options.toKey === undefined ? keys.length - 1 : keyIndex('toKey');
|
|
40
|
+
if (start > end && (options.fromKey !== undefined || options.toKey !== undefined))
|
|
41
|
+
throw new Error('--from-key must precede or equal --to-key.');
|
|
42
|
+
|
|
43
|
+
let regex;
|
|
44
|
+
if (options.keyRegex !== undefined) {
|
|
45
|
+
try {
|
|
46
|
+
regex = new RegExp(options.keyRegex);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(`Invalid --key-regex: ${options.keyRegex}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const matches = (key) => isWithinScope(key, scope) && (!regex || regex.test(key));
|
|
52
|
+
if (options.key !== undefined) {
|
|
53
|
+
const index = keyIndex('key');
|
|
54
|
+
if (index < start || index > end || !matches(options.key))
|
|
55
|
+
throw new Error('--key conflicts with the manifest range, path scope, or regex.');
|
|
56
|
+
}
|
|
57
|
+
return entries
|
|
58
|
+
.slice(start, end + 1)
|
|
59
|
+
.filter(([key]) => matches(key) && (options.key === undefined || key === options.key));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveFilesystemSelection = (scope) => {
|
|
63
|
+
const files = [];
|
|
64
|
+
const visit = (target) => {
|
|
65
|
+
const stats = fs.statSync(target);
|
|
66
|
+
if (stats.isFile()) files.push(normalizeStoragePath(target));
|
|
67
|
+
else if (stats.isDirectory()) {
|
|
68
|
+
for (const entry of fs
|
|
69
|
+
.readdirSync(target, { withFileTypes: true })
|
|
70
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
71
|
+
const child = path.join(target, entry.name);
|
|
72
|
+
// Do not follow directory links during traversal.
|
|
73
|
+
if (entry.isSymbolicLink()) {
|
|
74
|
+
if (fs.statSync(child, { throwIfNoEntry: false })?.isFile()) files.push(normalizeStoragePath(child));
|
|
75
|
+
} else visit(child);
|
|
76
|
+
}
|
|
77
|
+
} else throw new Error(`Storage requires a regular file or directory: ${target}`);
|
|
78
|
+
};
|
|
79
|
+
visit(scope);
|
|
80
|
+
return files;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const resolveGitSelection = (files, scope) => {
|
|
84
|
+
const context = fs.statSync(scope).isDirectory() ? scope : path.dirname(scope);
|
|
85
|
+
const tracked = new Set(UnderpostRepository.API.getTrackedFiles(context));
|
|
86
|
+
return files.filter((file) => tracked.has(path.resolve(file)));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const resolveTrackedSelection = (entries) => entries.map(([key]) => key);
|
|
90
|
+
|
|
91
|
+
const resolveSelection = (scope, manifest, options = {}) => {
|
|
92
|
+
validateSelectionOptions(scope, options);
|
|
93
|
+
const entries = Object.entries(manifest);
|
|
94
|
+
const filtered = filterManifestEntries(entries, options, scope);
|
|
95
|
+
if (options.tracked) return resolveTrackedSelection(filtered);
|
|
96
|
+
|
|
97
|
+
let files = resolveFilesystemSelection(scope);
|
|
98
|
+
if (options.git) files = resolveGitSelection(files, scope);
|
|
99
|
+
const manifestKeys = new Map(entries.map(([key]) => [normalizeStoragePath(key), key]));
|
|
100
|
+
files = files.map((file) => manifestKeys.get(file) ?? file);
|
|
101
|
+
if (manifestFilterNames.some((name) => options[name] !== undefined)) {
|
|
102
|
+
const selected = new Set(resolveTrackedSelection(filtered));
|
|
103
|
+
files = files.filter((file) => selected.has(file));
|
|
104
|
+
}
|
|
105
|
+
return files;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
normalizeStoragePath,
|
|
110
|
+
filterManifestEntries,
|
|
111
|
+
resolveFilesystemSelection,
|
|
112
|
+
resolveGitSelection,
|
|
113
|
+
resolveTrackedSelection,
|
|
114
|
+
resolveSelection,
|
|
115
|
+
};
|