@underpostnet/cyberia 3.3.73 → 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/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +5 -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/publish.cyberia.ci.yml +44 -0
- package/AGENTS.md +61 -12
- package/CHANGELOG.md +126 -41
- package/CLI-HELP.md +19 -12
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +2 -2
- package/README.md +28 -28
- package/bin/build.js +37 -17
- package/bin/cyberia.js +1247 -1373
- package/bin/index.js +1247 -1373
- package/compose.env +17 -5
- package/conf.js +7 -3
- 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 +27 -18
- 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/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 +7 -0
- package/src/cli/deploy.js +4 -4
- 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/repository.js +28 -0
- package/src/cli/run.js +71 -197
- package/src/cli/test.js +4 -0
- package/src/cli/wireguard.js +32 -31
- package/src/client/components/core/Docs.js +77 -100
- package/src/client/components/core/Modal.js +56 -1
- 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/TranslateCyberiaPortal.js +0 -4
- 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-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/ops/logger.js +70 -35
- package/src/server/runtime/conf.js +4 -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/unit/client-build-docs.test.js +97 -70
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-resolution.test.js +17 -0
- package/test/unit/coverage-artifact.test.js +120 -29
- 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-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/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/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
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mongo Express Deployment Module
|
|
3
|
+
* @module src/db/mongo/MongoExpress
|
|
4
|
+
* @namespace MongoExpress
|
|
5
|
+
* @description Deploys the mongo-express web client used to inspect the cluster's MongoDB
|
|
6
|
+
* StatefulSet collections. The workload is a plain Deployment plus a ClusterIP Service, so the
|
|
7
|
+
* same manifests converge identically under Kind, Kubeadm and K3s; the only runtime-dependent
|
|
8
|
+
* decision is whether the deployed mongod enforces authentication, which selects the overlay.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { loggerFactory } from '../../server/ops/logger.js';
|
|
12
|
+
import { shellExec } from '../../server/runtime/process.js';
|
|
13
|
+
// Cyclic by construction (index -> cluster -> MongoExpress -> index), same as MongoBootstrap.js.
|
|
14
|
+
// Safe because the binding is only dereferenced inside method bodies, never at module scope.
|
|
15
|
+
import Underpost from '../../index.js';
|
|
16
|
+
import { MONGODB_DEFAULT_PORT, MONGODB_SERVICE_NAME, MONGODB_STATEFULSET_NAME } from './MongooseDB.js';
|
|
17
|
+
|
|
18
|
+
const logger = loggerFactory(import.meta);
|
|
19
|
+
|
|
20
|
+
const MONGO_EXPRESS_NAME = 'mongo-express';
|
|
21
|
+
const MONGO_EXPRESS_SERVICE_NAME = 'mongo-express-service';
|
|
22
|
+
const MONGO_EXPRESS_IMAGE = 'mongo-express:1.0.2';
|
|
23
|
+
const MONGO_EXPRESS_PORT = 8081;
|
|
24
|
+
const MONGO_EXPRESS_NODE_PORT = 32081;
|
|
25
|
+
const MONGO_EXPRESS_BASE_URL = '/mongo/';
|
|
26
|
+
// The UI reads the root credentials and gates its own session with the same pair, so it consumes
|
|
27
|
+
// the StatefulSet's secret rather than owning a second credential of its own.
|
|
28
|
+
const MONGO_EXPRESS_SECRET_NAME = 'mongodb-secret';
|
|
29
|
+
const MONGO_EXPRESS_MANIFEST_DIR = 'manifests/deployment/mongo-express';
|
|
30
|
+
const MONGO_EXPRESS_NO_AUTH_MANIFEST_DIR = 'manifests/deployment/mongo-express-no-auth';
|
|
31
|
+
|
|
32
|
+
// Literal and unescaped: kubectl's jsonpath quotes carry escape sequences of their own, and no
|
|
33
|
+
// mongod argument contains a pipe.
|
|
34
|
+
const MONGOD_ARGS_FIELD_SEPARATOR = '|';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parses the mongod argument vector reported by `kubectl -o jsonpath` for the StatefulSet.
|
|
38
|
+
* @param {string} raw - Raw jsonpath stdout: the StatefulSet name, then one field per argument.
|
|
39
|
+
* @returns {Array<string>|null} Argument vector, or null when no StatefulSet was reported.
|
|
40
|
+
* @memberof MongoExpress
|
|
41
|
+
*/
|
|
42
|
+
const parseMongodArgs = (raw) => {
|
|
43
|
+
const value = `${raw || ''}`;
|
|
44
|
+
if (!value.trim()) return null;
|
|
45
|
+
const [, ...args] = value.split(MONGOD_ARGS_FIELD_SEPARATOR);
|
|
46
|
+
return args.map((arg) => arg.trim()).filter(Boolean);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Decides whether mongo-express must authenticate against the deployed mongod.
|
|
51
|
+
*
|
|
52
|
+
* The deployed StatefulSet is the authority: `manifests/mongodb` starts mongod with `--auth` and
|
|
53
|
+
* `manifests/mongodb-4.4` does not, and either can be the one running regardless of which flag
|
|
54
|
+
* this invocation carries. The flags only answer for a cluster with no StatefulSet deployed yet,
|
|
55
|
+
* where `--mongodb` (the auth-enabled manifest set) is the default the platform installs.
|
|
56
|
+
* @param {object} [params={}] - Resolution inputs.
|
|
57
|
+
* @param {Array<string>|null} [params.args=null] - mongod argument vector, or null when undeployed.
|
|
58
|
+
* @param {object} [params.options={}] - Cluster options (`mongodb`, `mongodb4`).
|
|
59
|
+
* @returns {boolean} True when the admin credential pair must be projected.
|
|
60
|
+
* @memberof MongoExpress
|
|
61
|
+
*/
|
|
62
|
+
const mongoAuthEnabledFactory = ({ args = null, options = {} } = {}) => {
|
|
63
|
+
if (Array.isArray(args)) return args.includes('--auth');
|
|
64
|
+
return options.mongodb4 !== true;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves the kustomization root the auth mode selects.
|
|
69
|
+
* @param {object} [params={}] - Path inputs.
|
|
70
|
+
* @param {string} [params.underpostRoot='.'] - Repository root holding `manifests/`.
|
|
71
|
+
* @param {boolean} [params.authEnabled=true] - Whether mongod enforces authentication.
|
|
72
|
+
* @returns {string} Kustomization directory path.
|
|
73
|
+
* @memberof MongoExpress
|
|
74
|
+
*/
|
|
75
|
+
const mongoExpressManifestPathFactory = ({ underpostRoot = '.', authEnabled = true } = {}) =>
|
|
76
|
+
`${underpostRoot}/${authEnabled ? MONGO_EXPRESS_MANIFEST_DIR : MONGO_EXPRESS_NO_AUTH_MANIFEST_DIR}`;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolves the opt-in NodePort Service manifest path. It lives in the base directory and is left
|
|
80
|
+
* out of both kustomizations: exposing an admin UI on the node network is a caller's decision.
|
|
81
|
+
* @param {object} [params={}] - Path inputs.
|
|
82
|
+
* @param {string} [params.underpostRoot='.'] - Repository root holding `manifests/`.
|
|
83
|
+
* @returns {string} NodePort manifest path.
|
|
84
|
+
* @memberof MongoExpress
|
|
85
|
+
*/
|
|
86
|
+
const mongoExpressNodePortManifestFactory = ({ underpostRoot = '.' } = {}) =>
|
|
87
|
+
`${underpostRoot}/${MONGO_EXPRESS_MANIFEST_DIR}/mongo-express-nodeport.yaml`;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @class MongoExpress
|
|
91
|
+
* @description Static deployment surface for the mongo-express admin client.
|
|
92
|
+
* @memberof MongoExpress
|
|
93
|
+
*/
|
|
94
|
+
class MongoExpress {
|
|
95
|
+
/**
|
|
96
|
+
* Reads the mongod argument vector from the deployed MongoDB StatefulSet.
|
|
97
|
+
*
|
|
98
|
+
* The projection leads with the StatefulSet name so a deployed server that declares no arguments
|
|
99
|
+
* stays distinguishable from no server at all — one is mongod on its defaults, the other is a
|
|
100
|
+
* cluster with no data tier yet, and both would otherwise reduce to empty output.
|
|
101
|
+
* @param {string} [namespace='default'] - Namespace holding the StatefulSet.
|
|
102
|
+
* @returns {Array<string>|null} Argument vector, or null when no StatefulSet is deployed.
|
|
103
|
+
* @memberof MongoExpress
|
|
104
|
+
*/
|
|
105
|
+
static readMongodArgs(namespace = 'default') {
|
|
106
|
+
const raw = shellExec(
|
|
107
|
+
`kubectl get statefulset ${MONGODB_STATEFULSET_NAME} -n ${namespace} --ignore-not-found -o jsonpath=` +
|
|
108
|
+
`'{.metadata.name}{"${MONGOD_ARGS_FIELD_SEPARATOR}"}` +
|
|
109
|
+
`{range .spec.template.spec.containers[?(@.name=="${MONGODB_STATEFULSET_NAME}")].args[*]}` +
|
|
110
|
+
`{@}{"${MONGOD_ARGS_FIELD_SEPARATOR}"}{end}'`,
|
|
111
|
+
{ stdout: true, silent: true, silentOnError: true },
|
|
112
|
+
);
|
|
113
|
+
return parseMongodArgs(raw);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Deploys mongo-express and, once it is Ready, applies the exposure and placement the caller
|
|
118
|
+
* asked for. Cluster-type agnostic by construction: only the image pull differs between Kind
|
|
119
|
+
* (loaded into the node's image store) and Kubeadm/K3s (pulled through the CRI).
|
|
120
|
+
* @param {object} [params={}] - Deployment parameters.
|
|
121
|
+
* @param {string} [params.namespace='default'] - Target namespace.
|
|
122
|
+
* @param {string} [params.underpostRoot='.'] - Repository root holding `manifests/`.
|
|
123
|
+
* @param {object} [params.options={}] - Cluster options (`pullImage`, `nodePort`, `nodeName`, `mongodb4`).
|
|
124
|
+
* @returns {Promise<boolean>} Whether the deployment reported Ready.
|
|
125
|
+
* @memberof MongoExpress
|
|
126
|
+
*/
|
|
127
|
+
static async deploy({ namespace = 'default', underpostRoot = '.', options = {} } = {}) {
|
|
128
|
+
// Secret before workload: the Deployment's secretKeyRef must resolve at pod admission, and
|
|
129
|
+
// the encrypted store stays the source of truth wherever a manifest exists.
|
|
130
|
+
if (!Underpost.secret.applyIfPresent(MONGO_EXPRESS_SECRET_NAME, namespace))
|
|
131
|
+
Underpost.secret.applyFromOriginSeed(MONGO_EXPRESS_SECRET_NAME, namespace);
|
|
132
|
+
|
|
133
|
+
if (options.pullImage) Underpost.cluster.pullImage(MONGO_EXPRESS_IMAGE, options);
|
|
134
|
+
|
|
135
|
+
const args = MongoExpress.readMongodArgs(namespace);
|
|
136
|
+
const authEnabled = mongoAuthEnabledFactory({ args, options });
|
|
137
|
+
const manifestPath = mongoExpressManifestPathFactory({ underpostRoot, authEnabled });
|
|
138
|
+
logger.info('Deploying mongo-express', {
|
|
139
|
+
namespace,
|
|
140
|
+
authEnabled,
|
|
141
|
+
manifestPath,
|
|
142
|
+
mongodb: args ? 'deployed' : 'not deployed',
|
|
143
|
+
});
|
|
144
|
+
shellExec(`kubectl apply -k ${manifestPath} -n ${namespace}`);
|
|
145
|
+
|
|
146
|
+
const ready = await Underpost.test.statusMonitor(MONGO_EXPRESS_NAME, 'Running', 'pods', 1000, 60 * 10);
|
|
147
|
+
if (!ready) {
|
|
148
|
+
logger.error('mongo-express did not report Running', { namespace });
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options.nodePort)
|
|
153
|
+
shellExec(`kubectl apply -f ${mongoExpressNodePortManifestFactory({ underpostRoot })} -n ${namespace}`);
|
|
154
|
+
if (options.nodeName)
|
|
155
|
+
Underpost.cluster.pinToNode({
|
|
156
|
+
kind: 'deployment',
|
|
157
|
+
name: MONGO_EXPRESS_NAME,
|
|
158
|
+
namespace,
|
|
159
|
+
node: options.nodeName,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
logger.info('mongo-express ready', {
|
|
163
|
+
server: `${MONGODB_STATEFULSET_NAME}-0.${MONGODB_SERVICE_NAME}:${MONGODB_DEFAULT_PORT}`,
|
|
164
|
+
basicAuth: `secret/${MONGO_EXPRESS_SECRET_NAME}`,
|
|
165
|
+
clusterIp: `${MONGO_EXPRESS_SERVICE_NAME}:${MONGO_EXPRESS_PORT}${MONGO_EXPRESS_BASE_URL}`,
|
|
166
|
+
nodePort: options.nodePort ? `<node>:${MONGO_EXPRESS_NODE_PORT}${MONGO_EXPRESS_BASE_URL}` : null,
|
|
167
|
+
portForward: `kubectl port-forward -n ${namespace} svc/${MONGO_EXPRESS_SERVICE_NAME} ${MONGO_EXPRESS_PORT}:${MONGO_EXPRESS_PORT}`,
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
MONGO_EXPRESS_BASE_URL,
|
|
175
|
+
MONGO_EXPRESS_IMAGE,
|
|
176
|
+
MONGO_EXPRESS_NAME,
|
|
177
|
+
MONGO_EXPRESS_NODE_PORT,
|
|
178
|
+
MONGO_EXPRESS_PORT,
|
|
179
|
+
MONGO_EXPRESS_SECRET_NAME,
|
|
180
|
+
MONGO_EXPRESS_SERVICE_NAME,
|
|
181
|
+
MongoExpress,
|
|
182
|
+
mongoAuthEnabledFactory,
|
|
183
|
+
mongoExpressManifestPathFactory,
|
|
184
|
+
mongoExpressNodePortManifestFactory,
|
|
185
|
+
parseMongodArgs,
|
|
186
|
+
};
|
|
187
|
+
export default MongoExpress;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gRPC transport for the Cyberia Engine data pipeline.
|
|
3
3
|
*
|
|
4
|
-
* Runs
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* /api/cyberia-instance/boot/* — so both transports stay equivalent.
|
|
4
|
+
* Runs beside Express on its own port (default 50051). Thin adapter over
|
|
5
|
+
* src/projects/cyberia/instance-data.js, the same assembly the REST fallback
|
|
6
|
+
* at /api/cyberia-instance/boot/* serves, so both transports stay equivalent.
|
|
8
7
|
*
|
|
9
8
|
* @module src/grpc/cyberia/grpc-server.js
|
|
10
9
|
*/
|
|
@@ -16,7 +15,6 @@ import { fileURLToPath } from 'url';
|
|
|
16
15
|
import { loggerFactory } from '../../server/ops/logger.js';
|
|
17
16
|
import {
|
|
18
17
|
buildFallbackConfig,
|
|
19
|
-
buildFallbackInstanceConfig,
|
|
20
18
|
fetchFullInstance,
|
|
21
19
|
fetchMapData,
|
|
22
20
|
fetchObjectLayer,
|
|
@@ -131,7 +129,7 @@ class GrpcServer {
|
|
|
131
129
|
|
|
132
130
|
server.addService(proto.CyberiaDataService.service, buildHandlers(dbKey));
|
|
133
131
|
|
|
134
|
-
// gRPC
|
|
132
|
+
// gRPC stays on the cluster-internal network, so credentials are insecure.
|
|
135
133
|
const creds = grpc.ServerCredentials.createInsecure();
|
|
136
134
|
|
|
137
135
|
return new Promise((resolve, reject) => {
|
|
@@ -159,4 +157,4 @@ class GrpcServer {
|
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
export { GrpcServer, buildFallbackConfig
|
|
160
|
+
export { GrpcServer, buildFallbackConfig };
|
package/src/index.js
CHANGED
|
@@ -5,14 +5,28 @@
|
|
|
5
5
|
* @namespace CyberiaAtlasSpriteSheetGenerator
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import sharp from 'sharp';
|
|
11
8
|
import { Jimp, rgbaToInt } from 'jimp';
|
|
12
9
|
import { loggerFactory } from '../../server/ops/logger.js';
|
|
10
|
+
import { getKeyframeDirectionsByCode } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
|
|
13
11
|
|
|
14
12
|
const logger = loggerFactory(import.meta);
|
|
15
13
|
|
|
14
|
+
/** Direction code of the still an item is previewed by: down idle. */
|
|
15
|
+
export const IDLE_PREVIEW_DIRECTION_CODE = '08';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pixels per cell of the human-resolution atlas, when no factor is given.
|
|
19
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_ATLAS_UPSCALE_FACTOR = 20;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pixels per cell of the minified atlas. The client runtime downloads this one,
|
|
25
|
+
* so it stays at one pixel per cell.
|
|
26
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
27
|
+
*/
|
|
28
|
+
const MINIFY_CELL_PIXEL_DIM = 1;
|
|
29
|
+
|
|
16
30
|
/**
|
|
17
31
|
* @typedef {Object} AtlasFrame
|
|
18
32
|
* @property {number} x - X position in atlas
|
|
@@ -38,7 +52,7 @@ export class AtlasSpriteSheetGenerator {
|
|
|
38
52
|
* @returns {Promise<Jimp>} The generated image
|
|
39
53
|
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
40
54
|
*/
|
|
41
|
-
static async frameMatrixToImage(frameMatrix, colors, cellPixelDim =
|
|
55
|
+
static async frameMatrixToImage(frameMatrix, colors, cellPixelDim = 1) {
|
|
42
56
|
if (!frameMatrix || frameMatrix.length === 0 || frameMatrix[0].length === 0) {
|
|
43
57
|
throw new Error('Invalid frame matrix');
|
|
44
58
|
}
|
|
@@ -79,59 +93,47 @@ export class AtlasSpriteSheetGenerator {
|
|
|
79
93
|
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
80
94
|
*/
|
|
81
95
|
static calculateOptimalDimension(frameImages) {
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const heights = frameImages.map((f) => f.height).filter((h) => !isNaN(h) && h > 0);
|
|
90
|
-
|
|
91
|
-
if (widths.length === 0 || heights.length === 0) {
|
|
92
|
-
logger.warn('Invalid frame dimensions found, using default 2048');
|
|
93
|
-
return 2048;
|
|
96
|
+
if (
|
|
97
|
+
!frameImages?.length ||
|
|
98
|
+
frameImages.some(
|
|
99
|
+
({ width, height }) => !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0,
|
|
100
|
+
)
|
|
101
|
+
) {
|
|
102
|
+
throw new Error('Invalid frame dimensions');
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const cols = Math.ceil(Math.sqrt(frameCount));
|
|
104
|
-
const rows = Math.ceil(frameCount / cols);
|
|
105
|
-
|
|
106
|
-
// Calculate required dimensions
|
|
107
|
-
const requiredWidth = cols * maxFrameWidth;
|
|
108
|
-
const requiredHeight = rows * maxFrameHeight;
|
|
109
|
-
const requiredDim = Math.max(requiredWidth, requiredHeight);
|
|
110
|
-
|
|
111
|
-
logger.info(`Grid layout: ${cols}x${rows}, required: ${requiredWidth}x${requiredHeight}`);
|
|
112
|
-
|
|
113
|
-
// Round up to next power of 2
|
|
114
|
-
const optimalDim = AtlasSpriteSheetGenerator.nextPowerOf2(requiredDim);
|
|
115
|
-
|
|
116
|
-
// Ensure minimum 1024, maximum 8192
|
|
117
|
-
const finalDim = Math.max(1024, Math.min(8192, optimalDim));
|
|
118
|
-
|
|
119
|
-
logger.info(`Calculated optimal dimension: ${finalDim}x${finalDim}`);
|
|
120
|
-
|
|
121
|
-
return finalDim;
|
|
104
|
+
const cols = Math.ceil(Math.sqrt(frameImages.length));
|
|
105
|
+
const rows = Math.ceil(frameImages.length / cols);
|
|
106
|
+
return AtlasSpriteSheetGenerator.nextPowerOf2(
|
|
107
|
+
Math.max(
|
|
108
|
+
cols * Math.max(...frameImages.map((frame) => frame.width)),
|
|
109
|
+
rows * Math.max(...frameImages.map((frame) => frame.height)),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
122
112
|
}
|
|
123
113
|
|
|
124
114
|
/**
|
|
125
|
-
* Consolidates all frames
|
|
115
|
+
* Consolidates all frames of an ObjectLayerRenderFrames into one atlas sprite sheet.
|
|
116
|
+
*
|
|
117
|
+
* One packing produces two renders of the same layout: `minifyBuffer` at one
|
|
118
|
+
* pixel per cell, which the client runtime downloads, and `buffer` at
|
|
119
|
+
* `upscaleFactor` pixels per cell for human viewing. `metadata` describes the
|
|
120
|
+
* minified render, so atlas metadata and the served blob always agree.
|
|
121
|
+
*
|
|
126
122
|
* @static
|
|
127
123
|
* @param {Object} objectLayerRenderFrames - The ObjectLayerRenderFrames document
|
|
128
124
|
* @param {string} itemKey - The item key for the atlas
|
|
129
|
-
* @param {number} [
|
|
125
|
+
* @param {number} [upscaleFactor=DEFAULT_ATLAS_UPSCALE_FACTOR] - Pixels per cell of the human-resolution render
|
|
130
126
|
* @param {number} [maxAtlasDim=null] - Maximum atlas dimension (auto-calculated if null)
|
|
131
|
-
* @returns {Promise<{buffer: Buffer, metadata: Object}>}
|
|
127
|
+
* @returns {Promise<{buffer: Buffer, minifyBuffer: Buffer, metadata: Object}>} Both renders and the shared metadata
|
|
132
128
|
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
133
129
|
*/
|
|
134
|
-
static async generateAtlas(
|
|
130
|
+
static async generateAtlas(
|
|
131
|
+
objectLayerRenderFrames,
|
|
132
|
+
itemKey,
|
|
133
|
+
upscaleFactor = DEFAULT_ATLAS_UPSCALE_FACTOR,
|
|
134
|
+
maxAtlasDim = null,
|
|
135
|
+
) {
|
|
136
|
+
if (!Number.isInteger(upscaleFactor) || upscaleFactor < 1) throw new Error('Invalid pixel scale');
|
|
135
137
|
const { frames, colors } = objectLayerRenderFrames;
|
|
136
138
|
const frameDuration = Number(objectLayerRenderFrames?.frame_duration);
|
|
137
139
|
|
|
@@ -157,7 +159,7 @@ export class AtlasSpriteSheetGenerator {
|
|
|
157
159
|
'none_idle',
|
|
158
160
|
];
|
|
159
161
|
|
|
160
|
-
//
|
|
162
|
+
// The packing works on the minified render; the upscaled render reuses its layout.
|
|
161
163
|
const frameImages = [];
|
|
162
164
|
|
|
163
165
|
for (const direction of directionOrder) {
|
|
@@ -167,10 +169,15 @@ export class AtlasSpriteSheetGenerator {
|
|
|
167
169
|
for (let frameIndex = 0; frameIndex < directionFrames.length; frameIndex++) {
|
|
168
170
|
const frameMatrix = directionFrames[frameIndex];
|
|
169
171
|
try {
|
|
170
|
-
const frameImage = await AtlasSpriteSheetGenerator.frameMatrixToImage(
|
|
172
|
+
const frameImage = await AtlasSpriteSheetGenerator.frameMatrixToImage(
|
|
173
|
+
frameMatrix,
|
|
174
|
+
colors,
|
|
175
|
+
MINIFY_CELL_PIXEL_DIM,
|
|
176
|
+
);
|
|
171
177
|
|
|
172
178
|
frameImages.push({
|
|
173
179
|
image: frameImage,
|
|
180
|
+
frameMatrix,
|
|
174
181
|
direction,
|
|
175
182
|
frameIndex,
|
|
176
183
|
width: frameImage.bitmap.width,
|
|
@@ -194,10 +201,8 @@ export class AtlasSpriteSheetGenerator {
|
|
|
194
201
|
);
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
logger.error(`Invalid maxAtlasDim: ${maxAtlasDim}, using default 2048`);
|
|
200
|
-
maxAtlasDim = 2048;
|
|
204
|
+
if (!Number.isInteger(maxAtlasDim) || maxAtlasDim <= 0 || maxAtlasDim > 4096) {
|
|
205
|
+
throw new Error('Atlas dimension must be between 1 and 4096');
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
// Simple grid packing algorithm
|
|
@@ -215,43 +220,29 @@ export class AtlasSpriteSheetGenerator {
|
|
|
215
220
|
);
|
|
216
221
|
}
|
|
217
222
|
|
|
218
|
-
|
|
219
|
-
const
|
|
223
|
+
const minifyImage = new Jimp({ width: atlasWidth, height: atlasHeight, color: 0x00000000 });
|
|
224
|
+
const upscaledImage = new Jimp({
|
|
225
|
+
width: atlasWidth * upscaleFactor,
|
|
226
|
+
height: atlasHeight * upscaleFactor,
|
|
227
|
+
color: 0x00000000,
|
|
228
|
+
});
|
|
220
229
|
|
|
221
|
-
// Build metadata structure first
|
|
222
230
|
const frameMetadata = {};
|
|
223
231
|
|
|
224
|
-
// Composite frames onto atlas
|
|
225
|
-
let loggedCount = 0;
|
|
226
232
|
for (const packedFrame of packedFrames) {
|
|
227
|
-
const { image, x, y, direction, frameIndex } = packedFrame;
|
|
228
|
-
|
|
229
|
-
// Log first few frames for debugging
|
|
230
|
-
if (loggedCount < 3) {
|
|
231
|
-
logger.info(
|
|
232
|
-
`Frame ${loggedCount + 1}: ${direction}[${frameIndex}] → (${x},${y}) ${image.bitmap.width}x${image.bitmap.height}`,
|
|
233
|
-
);
|
|
234
|
-
loggedCount++;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Manually copy pixels to ensure correct positioning
|
|
238
|
-
for (let srcY = 0; srcY < image.bitmap.height; srcY++) {
|
|
239
|
-
for (let srcX = 0; srcX < image.bitmap.width; srcX++) {
|
|
240
|
-
const destX = x + srcX;
|
|
241
|
-
const destY = y + srcY;
|
|
233
|
+
const { image, frameMatrix, x, y, direction, frameIndex } = packedFrame;
|
|
242
234
|
|
|
243
|
-
|
|
244
|
-
if (destX >= atlasWidth || destY >= atlasHeight) continue;
|
|
235
|
+
AtlasSpriteSheetGenerator.blitFrame(minifyImage, image, x, y);
|
|
245
236
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
// The same cell grid at a larger scale, so the upscaled atlas is an exact
|
|
238
|
+
// multiple of the layout the metadata describes.
|
|
239
|
+
if (upscaleFactor > MINIFY_CELL_PIXEL_DIM) {
|
|
240
|
+
const upscaledFrame = await AtlasSpriteSheetGenerator.frameMatrixToImage(frameMatrix, colors, upscaleFactor);
|
|
241
|
+
AtlasSpriteSheetGenerator.blitFrame(upscaledImage, upscaledFrame, x * upscaleFactor, y * upscaleFactor);
|
|
242
|
+
} else {
|
|
243
|
+
AtlasSpriteSheetGenerator.blitFrame(upscaledImage, image, x, y);
|
|
252
244
|
}
|
|
253
245
|
|
|
254
|
-
// Store metadata in correct order
|
|
255
246
|
if (!frameMetadata[direction]) {
|
|
256
247
|
frameMetadata[direction] = [];
|
|
257
248
|
}
|
|
@@ -265,19 +256,80 @@ export class AtlasSpriteSheetGenerator {
|
|
|
265
256
|
};
|
|
266
257
|
}
|
|
267
258
|
|
|
268
|
-
// Convert to PNG buffer
|
|
269
|
-
const buffer = await atlasImage.getBuffer('image/png');
|
|
270
|
-
|
|
271
259
|
const metadata = {
|
|
272
260
|
itemKey,
|
|
273
261
|
atlasWidth,
|
|
274
262
|
atlasHeight,
|
|
275
|
-
cellPixelDim,
|
|
263
|
+
cellPixelDim: MINIFY_CELL_PIXEL_DIM,
|
|
264
|
+
upscaleFactor,
|
|
276
265
|
frame_duration: Number.isFinite(frameDuration) ? frameDuration : 100,
|
|
277
266
|
frames: frameMetadata,
|
|
278
267
|
};
|
|
279
268
|
|
|
280
|
-
return {
|
|
269
|
+
return {
|
|
270
|
+
buffer: await upscaledImage.getBuffer('image/png'),
|
|
271
|
+
minifyBuffer: await minifyImage.getBuffer('image/png'),
|
|
272
|
+
metadata,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The frame an item is previewed by: the first frame of the first down-idle keyframe the
|
|
278
|
+
* atlas carries, or the first frame of any direction when it has none.
|
|
279
|
+
*
|
|
280
|
+
* @static
|
|
281
|
+
* @param {Object} metadata - Atlas metadata as {@link generateAtlas} returns it.
|
|
282
|
+
* @returns {{x:number,y:number,width:number,height:number}|null} Frame box in minified cells.
|
|
283
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
284
|
+
*/
|
|
285
|
+
static idlePreviewFrame(metadata) {
|
|
286
|
+
const frames = metadata?.frames ?? {};
|
|
287
|
+
const preferred = getKeyframeDirectionsByCode(IDLE_PREVIEW_DIRECTION_CODE);
|
|
288
|
+
for (const direction of [...preferred, ...Object.keys(frames)]) {
|
|
289
|
+
const frame = frames[direction]?.[0];
|
|
290
|
+
if (frame) return frame;
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Cuts the idle-preview still out of the human-resolution render, using the same layout
|
|
297
|
+
* `metadata.frames` describes for the minified one scaled by `metadata.upscaleFactor`.
|
|
298
|
+
*
|
|
299
|
+
* @static
|
|
300
|
+
* @param {Buffer} buffer - PNG bytes of the human-resolution atlas render.
|
|
301
|
+
* @param {Object} metadata - Atlas metadata as {@link generateAtlas} returns it.
|
|
302
|
+
* @returns {Promise<Buffer|null>} PNG bytes of the still, or null when the atlas has no frame.
|
|
303
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
304
|
+
*/
|
|
305
|
+
static async idlePreviewFromAtlas(buffer, metadata) {
|
|
306
|
+
const frame = AtlasSpriteSheetGenerator.idlePreviewFrame(metadata);
|
|
307
|
+
if (!frame) return null;
|
|
308
|
+
const scale = Number(metadata.upscaleFactor) || 1;
|
|
309
|
+
const image = await Jimp.read(buffer);
|
|
310
|
+
image.crop({ x: frame.x * scale, y: frame.y * scale, w: frame.width * scale, h: frame.height * scale });
|
|
311
|
+
return await image.getBuffer('image/png');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Copies a frame image onto an atlas canvas at the given origin.
|
|
316
|
+
* @static
|
|
317
|
+
* @param {Jimp} atlasImage - Destination atlas canvas.
|
|
318
|
+
* @param {Jimp} frameImage - Source frame image.
|
|
319
|
+
* @param {number} originX - Destination X origin.
|
|
320
|
+
* @param {number} originY - Destination Y origin.
|
|
321
|
+
* @returns {void}
|
|
322
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
323
|
+
*/
|
|
324
|
+
static blitFrame(atlasImage, frameImage, originX, originY) {
|
|
325
|
+
for (let srcY = 0; srcY < frameImage.bitmap.height; srcY++) {
|
|
326
|
+
for (let srcX = 0; srcX < frameImage.bitmap.width; srcX++) {
|
|
327
|
+
const destX = originX + srcX;
|
|
328
|
+
const destY = originY + srcY;
|
|
329
|
+
if (destX >= atlasImage.bitmap.width || destY >= atlasImage.bitmap.height) continue;
|
|
330
|
+
atlasImage.setPixelColor(frameImage.getPixelColor(srcX, srcY), destX, destY);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
281
333
|
}
|
|
282
334
|
|
|
283
335
|
/**
|
|
@@ -302,7 +354,6 @@ export class AtlasSpriteSheetGenerator {
|
|
|
302
354
|
let rowHeight = 0;
|
|
303
355
|
let maxWidth = 0;
|
|
304
356
|
let totalHeight = 0;
|
|
305
|
-
let exceedsCount = 0;
|
|
306
357
|
|
|
307
358
|
for (const frame of sortedFrames) {
|
|
308
359
|
// Check if frame fits in current row
|
|
@@ -313,9 +364,8 @@ export class AtlasSpriteSheetGenerator {
|
|
|
313
364
|
rowHeight = 0;
|
|
314
365
|
}
|
|
315
366
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
exceedsCount++;
|
|
367
|
+
if (frame.width > maxDim || currentY + frame.height > maxDim) {
|
|
368
|
+
throw new Error(`Frames exceed atlas dimension ${maxDim}`);
|
|
319
369
|
}
|
|
320
370
|
|
|
321
371
|
packedFrames.push({
|
|
@@ -330,20 +380,8 @@ export class AtlasSpriteSheetGenerator {
|
|
|
330
380
|
totalHeight = Math.max(totalHeight, currentY + frame.height);
|
|
331
381
|
}
|
|
332
382
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const recommendedSize = AtlasSpriteSheetGenerator.nextPowerOf2(Math.max(maxWidth, totalHeight));
|
|
336
|
-
logger.warn(
|
|
337
|
-
`${exceedsCount} frames exceed atlas dimension (${maxDim}x${maxDim}). ` +
|
|
338
|
-
`Actual required: ${maxWidth}x${totalHeight}. ` +
|
|
339
|
-
`Recommended: --to-atlas-sprite-sheet ${recommendedSize}`,
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Use maxDim as the atlas dimensions (already power-of-2 and optimal from auto-calculation)
|
|
344
|
-
// If all frames fit within a smaller power-of-2, use that instead
|
|
345
|
-
const atlasWidth = maxDim;
|
|
346
|
-
const atlasHeight = maxDim;
|
|
383
|
+
const atlasWidth = maxWidth;
|
|
384
|
+
const atlasHeight = totalHeight;
|
|
347
385
|
|
|
348
386
|
return {
|
|
349
387
|
packedFrames,
|
|
@@ -369,45 +407,4 @@ export class AtlasSpriteSheetGenerator {
|
|
|
369
407
|
n |= n >> 16;
|
|
370
408
|
return n + 1;
|
|
371
409
|
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Generates atlas and saves to file system
|
|
375
|
-
* @static
|
|
376
|
-
* @param {Object} objectLayerRenderFrames - The ObjectLayerRenderFrames document
|
|
377
|
-
* @param {string} itemKey - The item key for the atlas
|
|
378
|
-
* @param {string} outputPath - Output file path
|
|
379
|
-
* @param {number} [cellPixelDim=20] - Pixel dimension per cell
|
|
380
|
-
* @param {number} [maxAtlasDim=null] - Maximum atlas dimension (auto-calculated if null)
|
|
381
|
-
* @returns {Promise<{buffer: Buffer, metadata: Object, outputPath: string}>}
|
|
382
|
-
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
383
|
-
*/
|
|
384
|
-
static async generateAtlasToFile(
|
|
385
|
-
objectLayerRenderFrames,
|
|
386
|
-
itemKey,
|
|
387
|
-
outputPath,
|
|
388
|
-
cellPixelDim = 20,
|
|
389
|
-
maxAtlasDim = null,
|
|
390
|
-
) {
|
|
391
|
-
const { buffer, metadata } = await AtlasSpriteSheetGenerator.generateAtlas(
|
|
392
|
-
objectLayerRenderFrames,
|
|
393
|
-
itemKey,
|
|
394
|
-
cellPixelDim,
|
|
395
|
-
maxAtlasDim,
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
await fs.ensureDir(path.dirname(outputPath));
|
|
399
|
-
await fs.writeFile(outputPath, buffer);
|
|
400
|
-
|
|
401
|
-
logger.info(`Atlas sprite sheet generated: ${outputPath}`);
|
|
402
|
-
|
|
403
|
-
return { buffer, metadata, outputPath };
|
|
404
|
-
}
|
|
405
410
|
}
|
|
406
|
-
|
|
407
|
-
// Export convenience functions
|
|
408
|
-
export const generateAtlas = AtlasSpriteSheetGenerator.generateAtlas;
|
|
409
|
-
export const generateAtlasToFile = AtlasSpriteSheetGenerator.generateAtlasToFile;
|
|
410
|
-
export const frameMatrixToImage = AtlasSpriteSheetGenerator.frameMatrixToImage;
|
|
411
|
-
export const packFramesGrid = AtlasSpriteSheetGenerator.packFramesGrid;
|
|
412
|
-
export const nextPowerOf2 = AtlasSpriteSheetGenerator.nextPowerOf2;
|
|
413
|
-
export const calculateOptimalDimension = AtlasSpriteSheetGenerator.calculateOptimalDimension;
|