@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/fs.js
CHANGED
|
@@ -1,378 +1,194 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File storage module for managing file operations using Cloudinary.
|
|
3
|
-
* @module src/cli/fs.js
|
|
4
|
-
* @namespace UnderpostFileStorage
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { v2 as cloudinary } from 'cloudinary';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import * as dir from 'path';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import * as dir from 'node:path';
|
|
11
4
|
import fs from 'fs-extra';
|
|
5
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
6
|
+
import { readZipEntry } from '../server/storage/zip.js';
|
|
12
7
|
import Downloader from '../server/storage/downloader.js';
|
|
13
|
-
import
|
|
14
|
-
import Underpost from '../index.js';
|
|
8
|
+
import * as selection from './fs-selection.js';
|
|
15
9
|
|
|
16
10
|
const logger = loggerFactory(import.meta);
|
|
11
|
+
const CLOUDINARY_DELIVERY_TYPE = 'private';
|
|
12
|
+
const CLOUDINARY_ACCESS_CONTROL = [{ access_type: 'token' }];
|
|
13
|
+
const DELIVERY_TYPES = ['private', 'upload', 'authenticated'];
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* This class provides a set of static methods to upload, pull, and delete files
|
|
22
|
-
* from Cloudinary, as well as manage a local storage configuration file.
|
|
16
|
+
* What a pull reports when the local file is already there and no overwrite was asked for.
|
|
17
|
+
* Distinct from a delivery type, which is what a pull that actually transferred returns.
|
|
23
18
|
*/
|
|
19
|
+
const PULL_SKIPPED = 'skipped';
|
|
20
|
+
const manifestIdPattern = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
21
|
+
|
|
24
22
|
class UnderpostFileStorage {
|
|
25
23
|
static API = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @description Configures the Cloudinary client with environment variables.
|
|
29
|
-
* @memberof UnderpostFileStorage
|
|
30
|
-
*/
|
|
24
|
+
...selection,
|
|
25
|
+
|
|
31
26
|
cloudinaryConfig() {
|
|
32
|
-
|
|
33
|
-
cloudinary.config({
|
|
27
|
+
const config = {
|
|
34
28
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
35
29
|
api_key: process.env.CLOUDINARY_API_KEY,
|
|
36
30
|
api_secret: process.env.CLOUDINARY_API_SECRET,
|
|
37
|
-
}
|
|
31
|
+
};
|
|
32
|
+
if (Object.values(config).some((value) => !value))
|
|
33
|
+
throw new Error('Set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET in the environment.');
|
|
34
|
+
cloudinary.config(config);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
resolveManifest(options = {}) {
|
|
38
|
+
if (options.storageFilePath !== undefined)
|
|
39
|
+
throw new Error('Use --storage-id with a sub-id, not a manifest path.');
|
|
40
|
+
if (typeof options.deployId !== 'string' || !manifestIdPattern.test(options.deployId))
|
|
41
|
+
throw new Error('Provide a valid --deploy-id using letters, digits, hyphens, or underscores.');
|
|
42
|
+
if (
|
|
43
|
+
options.storageId !== undefined &&
|
|
44
|
+
(typeof options.storageId !== 'string' || !manifestIdPattern.test(options.storageId))
|
|
45
|
+
)
|
|
46
|
+
throw new Error('Use a --storage-id sub-id with letters, digits, hyphens, or underscores.');
|
|
47
|
+
const name = options.storageId === undefined ? 'storage.json' : `storage.${options.storageId}.json`;
|
|
48
|
+
return `engine-private/conf/${options.deployId}/${name}`;
|
|
38
49
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!
|
|
53
|
-
|
|
50
|
+
|
|
51
|
+
readManifest(options = {}) {
|
|
52
|
+
const storageConf = UnderpostFileStorage.API.resolveManifest(options);
|
|
53
|
+
const storage = fs.existsSync(storageConf) ? JSON.parse(fs.readFileSync(storageConf, 'utf8')) : {};
|
|
54
|
+
if (!storage || typeof storage !== 'object' || Array.isArray(storage))
|
|
55
|
+
throw new Error(`Manifest must contain an object: ${storageConf}`);
|
|
56
|
+
const paths = new Set();
|
|
57
|
+
for (const [key, entry] of Object.entries(storage)) {
|
|
58
|
+
const normalized = selection.normalizeStoragePath(key);
|
|
59
|
+
if (!key || !entry || typeof entry !== 'object' || Array.isArray(entry) || paths.has(normalized))
|
|
60
|
+
throw new Error(`Invalid or duplicate manifest entry: ${key}`);
|
|
61
|
+
if (entry.type !== undefined && !DELIVERY_TYPES.includes(entry.type))
|
|
62
|
+
throw new Error(`Invalid manifest delivery type: ${key}`);
|
|
63
|
+
if (entry.bytes !== undefined && (!Number.isSafeInteger(entry.bytes) || entry.bytes < 0))
|
|
64
|
+
throw new Error(`Invalid manifest byte count: ${key}`);
|
|
65
|
+
paths.add(normalized);
|
|
54
66
|
}
|
|
55
67
|
return { storage, storageConf };
|
|
56
68
|
},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* @param {string} storageConf - The path to the storage configuration file.
|
|
62
|
-
* @memberof UnderpostFileStorage
|
|
63
|
-
*/
|
|
64
|
-
writeStorageConf(storage, storageConf) {
|
|
65
|
-
if (storage) fs.writeFileSync(storageConf, JSON.stringify(storage, null, 4), 'utf8');
|
|
66
|
-
},
|
|
67
|
-
/**
|
|
68
|
-
* @method gitTrack
|
|
69
|
-
* @description Optional, non-fatal Git tracking layer. Any Git error is logged and swallowed
|
|
70
|
-
* so it can never interrupt or roll back the canonical `storage.*.json` workflow.
|
|
71
|
-
* @param {string} gitPath - The working directory to stage/commit.
|
|
72
|
-
* @param {object} [options] - Tracking options.
|
|
73
|
-
* @param {boolean} [options.init=false] - If true, initialize a local repo before staging.
|
|
74
|
-
* @param {string} [options.message=''] - Explicit commit message; when omitted, `underpost cmt` is used.
|
|
75
|
-
* @memberof UnderpostFileStorage
|
|
76
|
-
*/
|
|
77
|
-
gitTrack(gitPath, options = { init: false, message: '' }) {
|
|
69
|
+
|
|
70
|
+
writeManifest(storage, storageConf) {
|
|
71
|
+
fs.ensureDirSync(dir.dirname(storageConf));
|
|
72
|
+
const temporary = `${storageConf}.${randomUUID()}.tmp`;
|
|
78
73
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
silentOnError: true,
|
|
84
|
-
silent: true,
|
|
85
|
-
disableLog: true,
|
|
86
|
-
});
|
|
87
|
-
else shellExec(`underpost cmt ${gitPath} feat`, { silentOnError: true, silent: true, disableLog: true });
|
|
88
|
-
} catch (error) {
|
|
89
|
-
logger.warn('git tracking skipped (non-fatal)', { gitPath, error: error?.message });
|
|
74
|
+
fs.writeFileSync(temporary, JSON.stringify(storage, null, 4), { encoding: 'utf8', flag: 'wx' });
|
|
75
|
+
fs.renameSync(temporary, storageConf);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.removeSync(temporary);
|
|
90
78
|
}
|
|
91
79
|
},
|
|
92
|
-
/**
|
|
93
|
-
* @method recursiveCallback
|
|
94
|
-
* @description Recursively processes files and directories based on the provided options.
|
|
95
|
-
* @param {string} path - The path to the directory to process.
|
|
96
|
-
* @param {object} [options] - An object containing options for the recursive callback.
|
|
97
|
-
* @param {boolean} [options.rm=false] - Flag to remove files and directories.
|
|
98
|
-
* @param {boolean} [options.recursive=false] - Flag to process directories recursively.
|
|
99
|
-
* @param {string} [options.deployId=''] - The identifier for the deployment.
|
|
100
|
-
* @param {boolean} [options.force=false] - Flag to force file operations.
|
|
101
|
-
* @param {boolean} [options.pull=false] - Flag to pull files from storage.
|
|
102
|
-
* @param {boolean} [options.git=false] - Flag to use Git for file operations.
|
|
103
|
-
* @param {boolean} [options.omitUnzip=false] - If true, do not extract zip and keep downloaded zip file.
|
|
104
|
-
* @param {string} [options.storageFilePath=''] - The path to the storage configuration file.
|
|
105
|
-
* @returns {Promise<void>} A promise that resolves when the recursive callback is complete.
|
|
106
|
-
* @memberof UnderpostFileStorage
|
|
107
|
-
*/
|
|
108
|
-
async recursiveCallback(
|
|
109
|
-
path,
|
|
110
|
-
options = {
|
|
111
|
-
rm: false,
|
|
112
|
-
recursive: false,
|
|
113
|
-
deployId: '',
|
|
114
|
-
force: false,
|
|
115
|
-
pull: false,
|
|
116
|
-
git: false,
|
|
117
|
-
omitUnzip: false,
|
|
118
|
-
storageFilePath: '',
|
|
119
|
-
},
|
|
120
|
-
) {
|
|
121
|
-
const { storage, storageConf } = Underpost.fs.getStorageConf(options);
|
|
122
|
-
|
|
123
|
-
// ── Single-file handling: when path is a file (not a directory), use parent dir
|
|
124
|
-
// as the git working directory and process just that one file. ──────────────
|
|
125
|
-
let isSingleFile = false;
|
|
126
|
-
let parentDir = path;
|
|
127
|
-
let singleFileName = '';
|
|
128
|
-
if (fs.existsSync(path) && !fs.statSync(path).isDirectory()) {
|
|
129
|
-
isSingleFile = true;
|
|
130
|
-
parentDir = dir.dirname(path);
|
|
131
|
-
singleFileName = path.split('/').pop();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// In recursive remove mode, delete every tracked storage key under the requested path,
|
|
135
|
-
// even when local files/directories are already missing.
|
|
136
|
-
if (options.rm === true) {
|
|
137
|
-
const normalizedPath = typeof path === 'string' ? path.trim() : '';
|
|
138
|
-
const basePath = normalizedPath.replace(/\/+$/, '');
|
|
139
|
-
const hasPathFilter = basePath.length > 0;
|
|
140
|
-
|
|
141
|
-
const associatedPaths = Object.keys(storage || {}).filter((storedPath) => {
|
|
142
|
-
if (!hasPathFilter) return true;
|
|
143
|
-
return storedPath === basePath || storedPath.startsWith(`${basePath}/`);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
for (const associatedPath of associatedPaths) {
|
|
147
|
-
await Underpost.fs.delete(associatedPath);
|
|
148
|
-
if (storage) delete storage[associatedPath];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (hasPathFilter && options.force === true && fs.existsSync(basePath)) fs.removeSync(basePath);
|
|
152
|
-
|
|
153
|
-
// Storage is canonical: persist the removal before any (optional) git tracking runs.
|
|
154
|
-
Underpost.fs.writeStorageConf(storage, storageConf);
|
|
155
|
-
|
|
156
|
-
if (associatedPaths.length === 0)
|
|
157
|
-
logger.warn('No associated tracked storage paths found', { path: hasPathFilter ? basePath : '*' });
|
|
158
|
-
else
|
|
159
|
-
logger.info('Removed associated tracked storage paths', {
|
|
160
|
-
path: hasPathFilter ? basePath : '*',
|
|
161
|
-
removed: associatedPaths.length,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
if (options.git === true) {
|
|
165
|
-
const gitPath = !hasPathFilter ? '.' : isSingleFile ? parentDir : basePath;
|
|
166
|
-
Underpost.fs.gitTrack(gitPath);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// For single files, run getDeleteFiles against the parent directory to avoid
|
|
173
|
-
// trying to `cd` into a file.
|
|
174
|
-
const gitContextPath = isSingleFile ? parentDir : path;
|
|
175
|
-
// Detecting locally-deleted files is a best-effort enhancement backed by git; if the path is
|
|
176
|
-
// not a repo (or git is unavailable) it must not block the canonical storage workflow.
|
|
177
|
-
let deleteFiles = [];
|
|
178
|
-
if (options.pull !== true) {
|
|
179
|
-
try {
|
|
180
|
-
deleteFiles = Underpost.repo.getDeleteFiles(gitContextPath);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
logger.warn('delete detection skipped (git unavailable)', { path: gitContextPath, error: error?.message });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
80
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (options.pull === true) {
|
|
195
|
-
let pullSkipCount = 0;
|
|
196
|
-
for (const _path of Object.keys(storage)) {
|
|
197
|
-
if (!fs.existsSync(_path) || options.force === true) {
|
|
198
|
-
if (options.force === true && fs.existsSync(_path)) fs.removeSync(_path);
|
|
199
|
-
await Underpost.fs.pull(_path, options);
|
|
200
|
-
} else pullSkipCount++;
|
|
201
|
-
}
|
|
202
|
-
if (pullSkipCount > 0) logger.warn(`Pull skipped ${pullSkipCount} files that already exist`);
|
|
203
|
-
// Only run git init/commit when the caller explicitly requests git tracking (--git flag).
|
|
204
|
-
// For bundle pulls into ./build the git step is unwanted and would error on a non-repo path.
|
|
205
|
-
if (options.git === true) Underpost.fs.gitTrack(gitContextPath, { init: true, message: 'Base pull state' });
|
|
206
|
-
} else {
|
|
207
|
-
let files;
|
|
208
|
-
if (isSingleFile) {
|
|
209
|
-
// Single file: treat the file itself as the sole item to process
|
|
210
|
-
files = [singleFileName];
|
|
211
|
-
} else {
|
|
212
|
-
files =
|
|
213
|
-
options.git === true
|
|
214
|
-
? Underpost.repo.getChangedFiles(gitContextPath)
|
|
215
|
-
: await fs.readdir(path, { recursive: true });
|
|
216
|
-
}
|
|
217
|
-
for (const relativePath of files) {
|
|
218
|
-
const _path = isSingleFile ? path : path + '/' + relativePath;
|
|
219
|
-
if (fs.existsSync(_path) && fs.statSync(_path).isDirectory()) {
|
|
220
|
-
if (options.pull === true && !fs.existsSync(_path)) fs.mkdirSync(_path, { recursive: true });
|
|
221
|
-
continue;
|
|
222
|
-
} else if (!(_path in storage) || options.force === true) {
|
|
223
|
-
await Underpost.fs.upload(_path, options);
|
|
224
|
-
if (storage) storage[_path] = {};
|
|
225
|
-
} else logger.warn('File already exists', _path);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Storage is canonical and always persisted; git is an optional layer on top.
|
|
229
|
-
Underpost.fs.writeStorageConf(storage, storageConf);
|
|
230
|
-
if (options.git === true) Underpost.fs.gitTrack(gitContextPath);
|
|
81
|
+
updateManifest(manifest, path, entry) {
|
|
82
|
+
const next = { ...manifest.storage };
|
|
83
|
+
if (entry === undefined) delete next[path];
|
|
84
|
+
else Object.defineProperty(next, path, { value: entry, enumerable: true, configurable: true, writable: true });
|
|
85
|
+
UnderpostFileStorage.API.writeManifest(next, manifest.storageConf);
|
|
86
|
+
manifest.storage = next;
|
|
231
87
|
},
|
|
232
|
-
/**
|
|
233
|
-
* @method callback
|
|
234
|
-
* @description Orchestrates file storage operations based on the provided options.
|
|
235
|
-
* This method handles file uploads, deletions, and recursive processing of directories.
|
|
236
|
-
* @param {string} path - The path to the file or directory to process.
|
|
237
|
-
* @param {object} [options] - An object containing options for the callback.
|
|
238
|
-
* @param {boolean} [options.rm=false] - Flag to remove files and directories.
|
|
239
|
-
* @param {boolean} [options.recursive=false] - Flag to process directories recursively.
|
|
240
|
-
* @param {string} [options.deployId=''] - The identifier for the deployment.
|
|
241
|
-
* @param {boolean} [options.force=false] - Flag to force file operations.
|
|
242
|
-
* @param {boolean} [options.pull=false] - Flag to pull files from storage.
|
|
243
|
-
* @param {boolean} [options.git=false] - Flag to use Git for file operations.
|
|
244
|
-
* @param {boolean} [options.omitUnzip=false] - If true, do not extract zip and keep downloaded zip file.
|
|
245
|
-
* @returns {Promise<void>} A promise that resolves when the callback is complete.
|
|
246
|
-
* @memberof UnderpostFileStorage
|
|
247
|
-
*/
|
|
248
|
-
async callback(
|
|
249
|
-
path,
|
|
250
|
-
options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false, omitUnzip: false },
|
|
251
|
-
) {
|
|
252
|
-
// rm always routes through recursiveCallback so storage.*.json is updated regardless of
|
|
253
|
-
// --recursive/--git. The bare `delete` primitive only removes the remote asset and would
|
|
254
|
-
// otherwise leave the tracked storage key orphaned.
|
|
255
|
-
if (options.recursive === true || options.git === true || options.rm === true)
|
|
256
|
-
return await Underpost.fs.recursiveCallback(path, options);
|
|
257
|
-
if (options.pull === true) return await Underpost.fs.pull(path, options);
|
|
258
|
-
return await Underpost.fs.upload(path, options);
|
|
259
|
-
},
|
|
260
|
-
/**
|
|
261
|
-
* @method upload
|
|
262
|
-
* @description Uploads a file to Cloudinary.
|
|
263
|
-
* @param {string} path - The path to the file to upload.
|
|
264
|
-
* @param {object} [options] - An object containing options for the upload.
|
|
265
|
-
* @param {string} [options.deployId=''] - The identifier for the deployment (used to locate the storage config file).
|
|
266
|
-
* @param {boolean} [options.force=false] - Flag to force file operations (overwrites existing remote asset).
|
|
267
|
-
* @param {string} [options.storageFilePath=''] - The path to the storage configuration file.
|
|
268
|
-
* @returns {Promise<object>} A promise that resolves to the upload result.
|
|
269
|
-
* @memberof UnderpostFileStorage
|
|
270
|
-
*/
|
|
271
88
|
|
|
272
|
-
|
|
273
|
-
path
|
|
274
|
-
|
|
275
|
-
) {
|
|
276
|
-
Underpost.fs.cloudinaryConfig();
|
|
277
|
-
const { storage, storageConf } = Underpost.fs.getStorageConf(options);
|
|
278
|
-
// path = Underpost.fs.file2Zip(path);
|
|
279
|
-
const uploadResult = await cloudinary.uploader
|
|
280
|
-
.upload(path, {
|
|
281
|
-
public_id: path,
|
|
282
|
-
resource_type: 'raw',
|
|
283
|
-
overwrite: options.force === true ? true : false,
|
|
284
|
-
})
|
|
285
|
-
.catch((error) => {
|
|
286
|
-
logger.error(error, { path, stack: error.stack });
|
|
287
|
-
});
|
|
288
|
-
logger.info('upload result', uploadResult);
|
|
289
|
-
if (storage) storage[path] = {};
|
|
290
|
-
Underpost.fs.writeStorageConf(storage, storageConf);
|
|
291
|
-
return uploadResult;
|
|
89
|
+
deliveryTypeCandidates(storage, path) {
|
|
90
|
+
const recorded = storage[path]?.type ?? 'upload';
|
|
91
|
+
return [...new Set([recorded, CLOUDINARY_DELIVERY_TYPE, 'upload'])];
|
|
292
92
|
},
|
|
293
|
-
/**
|
|
294
|
-
* @method pull
|
|
295
|
-
* @description Pulls a file from Cloudinary.
|
|
296
|
-
* @param {string} path - The path to the file to pull.
|
|
297
|
-
* @param {object} [options] - Pull options.
|
|
298
|
-
* @param {boolean} [options.omitUnzip=false] - If true, do not extract zip and keep downloaded zip file.
|
|
299
|
-
* @param {boolean} [options.force=false] - If true, re-download even if the local zip already exists.
|
|
300
|
-
* @returns {Promise<void>} A promise that resolves when the file is pulled.
|
|
301
|
-
* @memberof UnderpostFileStorage
|
|
302
|
-
*/
|
|
303
|
-
async pull(path, options = { omitUnzip: false, force: false }) {
|
|
304
|
-
Underpost.fs.cloudinaryConfig();
|
|
305
|
-
const folder = dir.dirname(path);
|
|
306
|
-
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
307
|
-
const zipPath = `${path}.zip`;
|
|
308
93
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
94
|
+
async callback(path, options = {}) {
|
|
95
|
+
const api = UnderpostFileStorage.API;
|
|
96
|
+
const manifest = api.readManifest(options);
|
|
97
|
+
const paths = api.resolveSelection(path, manifest.storage, options);
|
|
98
|
+
const operation = options.rm ? 'delete' : options.pull ? 'pull' : 'upload';
|
|
99
|
+
if (paths.length === 0) logger.warn('No storage paths selected.');
|
|
100
|
+
// A skip is the absence of work, and one line per file buries the transfers that did happen
|
|
101
|
+
// under hundreds that did not. The count carries the same information: which files were
|
|
102
|
+
// skipped is exactly which files were already there.
|
|
103
|
+
let skipped = 0;
|
|
104
|
+
for (const selected of paths) {
|
|
105
|
+
if ((await api[operation](selected, options, manifest)) === PULL_SKIPPED) skipped++;
|
|
312
106
|
}
|
|
107
|
+
if (skipped > 0) logger.info('Pull skipped files that already exist', { skipped, selected: paths.length });
|
|
108
|
+
},
|
|
313
109
|
|
|
314
|
-
|
|
315
|
-
|
|
110
|
+
async upload(path, options = {}, manifest = UnderpostFileStorage.API.readManifest(options)) {
|
|
111
|
+
const api = UnderpostFileStorage.API;
|
|
112
|
+
if (!fs.statSync(path).isFile()) throw new Error(`Upload requires a regular file: ${path}`);
|
|
113
|
+
api.cloudinaryConfig();
|
|
114
|
+
const result = await cloudinary.uploader.upload(path, {
|
|
115
|
+
public_id: path,
|
|
316
116
|
resource_type: 'raw',
|
|
117
|
+
type: CLOUDINARY_DELIVERY_TYPE,
|
|
118
|
+
access_control: CLOUDINARY_ACCESS_CONTROL,
|
|
119
|
+
overwrite: options.force === true,
|
|
317
120
|
});
|
|
318
|
-
|
|
121
|
+
const remote = result?.existing
|
|
122
|
+
? await cloudinary.api.resource(path, { resource_type: 'raw', type: CLOUDINARY_DELIVERY_TYPE })
|
|
123
|
+
: result;
|
|
124
|
+
if (
|
|
125
|
+
remote?.error ||
|
|
126
|
+
remote?.public_id !== path ||
|
|
127
|
+
remote?.type !== CLOUDINARY_DELIVERY_TYPE ||
|
|
128
|
+
!Number.isSafeInteger(remote?.bytes) ||
|
|
129
|
+
remote.bytes < 0
|
|
130
|
+
)
|
|
131
|
+
throw new Error(`Invalid Cloudinary upload response: ${path}`);
|
|
132
|
+
api.updateManifest(manifest, path, { ...manifest.storage[path], type: remote.type, bytes: remote.bytes });
|
|
133
|
+
logger.info(result.existing ? 'Remote asset already exists' : 'Uploaded asset', { path });
|
|
134
|
+
return result;
|
|
135
|
+
},
|
|
319
136
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
137
|
+
async pull(path, options = {}, manifest = UnderpostFileStorage.API.readManifest(options)) {
|
|
138
|
+
const api = UnderpostFileStorage.API;
|
|
139
|
+
if (!Object.hasOwn(manifest.storage, path)) throw new Error(`Asset is not in the selected manifest: ${path}`);
|
|
140
|
+
const target = options.omitUnzip ? `${path}.zip` : path;
|
|
141
|
+
if (fs.existsSync(target) && !fs.statSync(target).isFile())
|
|
142
|
+
throw new Error(`Pull target must be a regular file: ${target}`);
|
|
143
|
+
if (fs.existsSync(target) && !options.force) return PULL_SKIPPED;
|
|
144
|
+
api.cloudinaryConfig();
|
|
145
|
+
fs.ensureDirSync(dir.dirname(target));
|
|
146
|
+
const temporary = fs.mkdtempSync(dir.join(dir.dirname(target), '.underpost-fs-'));
|
|
147
|
+
const archive = dir.join(temporary, 'download.zip');
|
|
148
|
+
try {
|
|
149
|
+
let resolvedType;
|
|
150
|
+
let downloadError;
|
|
151
|
+
for (const type of api.deliveryTypeCandidates(manifest.storage, path)) {
|
|
152
|
+
try {
|
|
153
|
+
const url = cloudinary.utils.download_archive_url({ public_ids: [path], resource_type: 'raw', type });
|
|
154
|
+
await Downloader.downloadFile(url, archive);
|
|
155
|
+
resolvedType = type;
|
|
156
|
+
break;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
downloadError = error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (resolvedType === undefined) throw downloadError;
|
|
162
|
+
const content = await readZipEntry(archive, dir.basename(path));
|
|
163
|
+
if (!content) throw new Error(`Downloaded archive has no asset: ${path}`);
|
|
164
|
+
let downloaded = archive;
|
|
165
|
+
if (!options.omitUnzip) {
|
|
166
|
+
downloaded = dir.join(temporary, 'asset');
|
|
167
|
+
fs.writeFileSync(downloaded, content);
|
|
168
|
+
}
|
|
169
|
+
fs.renameSync(downloaded, target);
|
|
170
|
+
api.updateManifest(manifest, path, { ...manifest.storage[path], type: resolvedType, bytes: content.length });
|
|
171
|
+
logger.info('Pulled asset', { path: target });
|
|
172
|
+
return resolvedType;
|
|
173
|
+
} finally {
|
|
174
|
+
fs.removeSync(temporary);
|
|
323
175
|
}
|
|
324
|
-
|
|
325
|
-
path = Underpost.fs.zip2File(zipPath);
|
|
326
|
-
fs.removeSync(`${path}.zip`);
|
|
327
|
-
},
|
|
328
|
-
/**
|
|
329
|
-
* @method delete
|
|
330
|
-
* @description Deletes a file from Cloudinary by its public ID.
|
|
331
|
-
* @param {string} path - The path (public ID) of the file to delete.
|
|
332
|
-
* @returns {Promise<object>} A promise that resolves to the Cloudinary delete result.
|
|
333
|
-
* @memberof UnderpostFileStorage
|
|
334
|
-
*/
|
|
335
|
-
async delete(path) {
|
|
336
|
-
Underpost.fs.cloudinaryConfig();
|
|
337
|
-
const deleteResult = await cloudinary.api
|
|
338
|
-
.delete_resources([path], { type: 'upload', resource_type: 'raw' })
|
|
339
|
-
.catch((error) => {
|
|
340
|
-
logger.error(error, { path, stack: error.stack });
|
|
341
|
-
});
|
|
342
|
-
logger.info('delete result', deleteResult);
|
|
343
|
-
return deleteResult;
|
|
344
|
-
},
|
|
345
|
-
/**
|
|
346
|
-
* @method file2Zip
|
|
347
|
-
* @description Converts a file to a zip file.
|
|
348
|
-
* @param {string} path - The path to the file to convert.
|
|
349
|
-
* @returns {string} The path to the zip file.
|
|
350
|
-
* @memberof UnderpostFileStorage
|
|
351
|
-
*/
|
|
352
|
-
file2Zip(path) {
|
|
353
|
-
const zip = new AdmZip();
|
|
354
|
-
zip.addLocalFile(path, '/');
|
|
355
|
-
path = path + '.zip';
|
|
356
|
-
zip.writeZip(path);
|
|
357
|
-
return path;
|
|
358
176
|
},
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
);
|
|
375
|
-
return path;
|
|
177
|
+
|
|
178
|
+
async delete(path, options = {}, manifest = UnderpostFileStorage.API.readManifest(options)) {
|
|
179
|
+
const api = UnderpostFileStorage.API;
|
|
180
|
+
api.cloudinaryConfig();
|
|
181
|
+
const results = {};
|
|
182
|
+
for (const type of api.deliveryTypeCandidates(manifest.storage, path)) {
|
|
183
|
+
const result = await cloudinary.api.delete_resources([path], { type, resource_type: 'raw' });
|
|
184
|
+
const status = result?.deleted?.[path];
|
|
185
|
+
if (result?.error || !['deleted', 'not_found'].includes(status))
|
|
186
|
+
throw new Error(`Cloudinary delete failed for ${path} (${type}): ${status ?? 'missing status'}`);
|
|
187
|
+
results[type] = result;
|
|
188
|
+
}
|
|
189
|
+
api.updateManifest(manifest, path, undefined);
|
|
190
|
+
logger.info('Deleted remote asset', { path });
|
|
191
|
+
return results;
|
|
376
192
|
},
|
|
377
193
|
};
|
|
378
194
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -289,6 +289,12 @@ program
|
|
|
289
289
|
.option('--service-host <host>', 'Set custom host/IP for exposed MongoDB and Valkey clients.')
|
|
290
290
|
.option('--postgresql', 'Initializes the cluster with a PostgreSQL statefulset.')
|
|
291
291
|
.option('--mongodb4', 'Initializes the cluster with a MongoDB 4.4 service.')
|
|
292
|
+
.option(
|
|
293
|
+
'--mongo-express',
|
|
294
|
+
'Deploys the mongo-express web client for inspecting the MongoDB statefulset databases and collections. ' +
|
|
295
|
+
'Reuses the mongodb-secret credentials, and drops them when the deployed mongod runs without --auth. ' +
|
|
296
|
+
'Combine with --node-port to expose it on the node network.',
|
|
297
|
+
)
|
|
292
298
|
.option('--valkey', 'Initializes the cluster with a Valkey service.')
|
|
293
299
|
.option('--ipfs', 'Initializes the cluster with an ipfs-cluster statefulset.')
|
|
294
300
|
.option('--contour', 'Initializes the cluster with Project Contour base HTTPProxy and Envoy.')
|
|
@@ -622,16 +628,21 @@ program
|
|
|
622
628
|
|
|
623
629
|
program
|
|
624
630
|
.command('fs')
|
|
625
|
-
.argument('[path]', '
|
|
626
|
-
.option('--rm', '
|
|
627
|
-
.option('--git', '
|
|
628
|
-
.option('--recursive', '
|
|
629
|
-
.option('--
|
|
630
|
-
.
|
|
631
|
+
.argument('[path]', 'Selects one file or all files below a directory. Required unless --tracked is set.')
|
|
632
|
+
.option('--rm', 'Deletes selected remote assets and their manifest entries.')
|
|
633
|
+
.option('--git', 'Restricts filesystem selection to Git-tracked files. Does not change Git state.')
|
|
634
|
+
.option('--recursive', 'Compatibility option. Directories always select files recursively.')
|
|
635
|
+
.option('--tracked', 'Selects only manifest entries. An optional path limits their scope.')
|
|
636
|
+
.requiredOption('--deploy-id <deploy-id>', 'Selects the deployment configuration.')
|
|
637
|
+
.option('--pull', 'Downloads selected manifest assets. Use --tracked to restore missing local files.')
|
|
631
638
|
.option('--omit-unzip', 'With --pull, keeps the downloaded .zip file and skips extraction.')
|
|
632
|
-
.option('--force', '
|
|
633
|
-
.option('--storage-
|
|
634
|
-
.
|
|
639
|
+
.option('--force', 'Overwrites remote uploads or local downloads. Does not change selection.')
|
|
640
|
+
.option('--storage-id <sub-id>', 'Selects storage.<sub-id>.json. The default is storage.json.')
|
|
641
|
+
.option('--key <path>', 'Selects one exact manifest key within the path scope and filters.')
|
|
642
|
+
.option('--from-key <path>', 'Starts an inclusive range in manifest key order.')
|
|
643
|
+
.option('--to-key <path>', 'Ends an inclusive range. The default start is the first manifest key.')
|
|
644
|
+
.option('--key-regex <pattern>', 'Filters manifest keys by a JavaScript regular expression.')
|
|
645
|
+
.description('Uploads, pulls, or deletes Cloudinary assets and synchronizes the selected storage manifest.')
|
|
635
646
|
.action(Underpost.fs.callback);
|
|
636
647
|
|
|
637
648
|
program
|
|
@@ -734,7 +745,7 @@ program
|
|
|
734
745
|
.option('--service-status', 'Reports whether the underpost-event unit is active and enabled.')
|
|
735
746
|
.option('--list', 'Lists the registered events with their resolved probe targets.')
|
|
736
747
|
.option('--port <port>', 'Listening port for --serve and the generated unit (default: 39099).')
|
|
737
|
-
.option('--cooldown-ms <ms>', 'Minimum interval between two dispatches of one event in --serve (default:
|
|
748
|
+
.option('--cooldown-ms <ms>', 'Minimum interval between two dispatches of one event in --serve (default: 10000).')
|
|
738
749
|
.option(
|
|
739
750
|
'--spoke <spoke-id>',
|
|
740
751
|
'Spoke to remediate when dispatching wireguard-spoke-down by hand; a webhook takes it from the alert labels.',
|
|
@@ -1026,6 +1037,7 @@ program
|
|
|
1026
1037
|
.option('--deploy-id <deploy-id>', 'Sets deploy id context for the runner execution.')
|
|
1027
1038
|
.option('--user <user>', 'Sets user context for the runner execution.')
|
|
1028
1039
|
.option('--hosts <hosts>', 'Comma-separated list of hosts for the runner execution.')
|
|
1040
|
+
.option('--split <mb>', 'Zip part size in MB for push-bundle, or "none" to upload a single zip. Defaults to 8.')
|
|
1029
1041
|
.option('--instance-id <instance-id>', 'Sets instance id context for the runner execution.')
|
|
1030
1042
|
.option('--pid <process-id>', 'Sets process id context for the runner execution.')
|
|
1031
1043
|
.option(
|
package/src/cli/release.js
CHANGED
|
@@ -547,7 +547,9 @@ class UnderpostRelease {
|
|
|
547
547
|
logger.info(`CI push commit message: ${commitMsg}`);
|
|
548
548
|
shellExec(`node engine/bin clone --bare ${githubOrg}/${repoName}`);
|
|
549
549
|
shellCd('/home/dd/engine');
|
|
550
|
-
|
|
550
|
+
// `--coverage` runs the suites the deploy's `docs.coverage` names so the published
|
|
551
|
+
// source carries the reports; without it the pod serves the unavailable page.
|
|
552
|
+
shellExec(`node bin/build ${buildTarget} --coverage`);
|
|
551
553
|
shellCd('/home/dd/pwa-microservices-template');
|
|
552
554
|
shellExec(`rm -rf ./.git`);
|
|
553
555
|
shellExec(`mv ../${repoName}.git ./.git`);
|