synapse-mcp 1.0.0 → 1.0.2
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/README.md +1820 -147
- package/dist/constants.d.ts +10 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +18 -8
- package/dist/constants.js.map +1 -1
- package/dist/events/emitter.d.ts +63 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +112 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +3 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/types.d.ts +51 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +3 -0
- package/dist/events/types.js.map +1 -0
- package/dist/formatters/compose.d.ts +185 -0
- package/dist/formatters/compose.d.ts.map +1 -0
- package/dist/formatters/compose.js +397 -0
- package/dist/formatters/compose.js.map +1 -0
- package/dist/formatters/container.d.ts +84 -0
- package/dist/formatters/container.d.ts.map +1 -0
- package/dist/formatters/container.js +323 -0
- package/dist/formatters/container.js.map +1 -0
- package/dist/formatters/diagnostics.d.ts +20 -0
- package/dist/formatters/diagnostics.d.ts.map +1 -0
- package/dist/formatters/diagnostics.js +73 -0
- package/dist/formatters/diagnostics.js.map +1 -0
- package/dist/formatters/docker.d.ts +139 -0
- package/dist/formatters/docker.d.ts.map +1 -0
- package/dist/formatters/docker.js +216 -0
- package/dist/formatters/docker.js.map +1 -0
- package/dist/formatters/host.d.ts +137 -0
- package/dist/formatters/host.d.ts.map +1 -0
- package/dist/formatters/host.js +198 -0
- package/dist/formatters/host.js.map +1 -0
- package/dist/formatters/index.d.ts +17 -270
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +21 -456
- package/dist/formatters/index.js.map +1 -1
- package/dist/formatters/scout.d.ts +424 -0
- package/dist/formatters/scout.d.ts.map +1 -0
- package/dist/formatters/scout.js +687 -0
- package/dist/formatters/scout.js.map +1 -0
- package/dist/formatters/strategy.d.ts +105 -0
- package/dist/formatters/strategy.d.ts.map +1 -0
- package/dist/formatters/strategy.js +120 -0
- package/dist/formatters/strategy.js.map +1 -0
- package/dist/formatters/utils.d.ts +84 -0
- package/dist/formatters/utils.d.ts.map +1 -0
- package/dist/formatters/utils.js +129 -0
- package/dist/formatters/utils.js.map +1 -0
- package/dist/health-rate-limiter.d.ts +59 -0
- package/dist/health-rate-limiter.d.ts.map +1 -0
- package/dist/health-rate-limiter.js +159 -0
- package/dist/health-rate-limiter.js.map +1 -0
- package/dist/index.js +61 -100
- package/dist/index.js.map +1 -1
- package/dist/middleware/async-handler.d.ts +62 -0
- package/dist/middleware/async-handler.d.ts.map +1 -0
- package/dist/middleware/async-handler.js +58 -0
- package/dist/middleware/async-handler.js.map +1 -0
- package/dist/middleware/auth.d.ts +32 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +63 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/csrf-protection.d.ts +58 -0
- package/dist/middleware/csrf-protection.d.ts.map +1 -0
- package/dist/middleware/csrf-protection.js +123 -0
- package/dist/middleware/csrf-protection.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +49 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +90 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/error-mapper.d.ts +44 -0
- package/dist/middleware/error-mapper.d.ts.map +1 -0
- package/dist/middleware/error-mapper.js +127 -0
- package/dist/middleware/error-mapper.js.map +1 -0
- package/dist/middleware/index.d.ts +13 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +13 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/request-id.d.ts +22 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +31 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/types.d.ts +33 -0
- package/dist/middleware/types.d.ts.map +1 -0
- package/dist/middleware/types.js +2 -0
- package/dist/middleware/types.js.map +1 -0
- package/dist/schemas/common.d.ts +205 -8
- package/dist/schemas/common.d.ts.map +1 -1
- package/dist/schemas/common.js +290 -17
- package/dist/schemas/common.js.map +1 -1
- package/dist/schemas/flux/compose.d.ts +307 -44
- package/dist/schemas/flux/compose.d.ts.map +1 -1
- package/dist/schemas/flux/compose.js +74 -48
- package/dist/schemas/flux/compose.js.map +1 -1
- package/dist/schemas/flux/container.d.ts +423 -56
- package/dist/schemas/flux/container.d.ts.map +1 -1
- package/dist/schemas/flux/container.js +83 -61
- package/dist/schemas/flux/container.js.map +1 -1
- package/dist/schemas/flux/docker.d.ts +254 -37
- package/dist/schemas/flux/docker.d.ts.map +1 -1
- package/dist/schemas/flux/docker.js +69 -39
- package/dist/schemas/flux/docker.js.map +1 -1
- package/dist/schemas/flux/host.d.ts +312 -29
- package/dist/schemas/flux/host.d.ts.map +1 -1
- package/dist/schemas/flux/host.js +74 -31
- package/dist/schemas/flux/host.js.map +1 -1
- package/dist/schemas/flux/index.d.ts +503 -11
- package/dist/schemas/flux/index.d.ts.map +1 -1
- package/dist/schemas/flux/index.js +34 -70
- package/dist/schemas/flux/index.js.map +1 -1
- package/dist/schemas/host-config.d.ts +76 -0
- package/dist/schemas/host-config.d.ts.map +1 -0
- package/dist/schemas/host-config.js +105 -0
- package/dist/schemas/host-config.js.map +1 -0
- package/dist/schemas/scout/index.d.ts +80 -23
- package/dist/schemas/scout/index.d.ts.map +1 -1
- package/dist/schemas/scout/index.js +26 -11
- package/dist/schemas/scout/index.js.map +1 -1
- package/dist/schemas/scout/logs.d.ts +17 -5
- package/dist/schemas/scout/logs.d.ts.map +1 -1
- package/dist/schemas/scout/logs.js +41 -31
- package/dist/schemas/scout/logs.js.map +1 -1
- package/dist/schemas/scout/simple.d.ts +126 -11
- package/dist/schemas/scout/simple.d.ts.map +1 -1
- package/dist/schemas/scout/simple.js +112 -57
- package/dist/schemas/scout/simple.js.map +1 -1
- package/dist/schemas/scout/zfs.d.ts +17 -5
- package/dist/schemas/scout/zfs.d.ts.map +1 -1
- package/dist/schemas/scout/zfs.js +34 -25
- package/dist/schemas/scout/zfs.js.map +1 -1
- package/dist/services/cache-layer.d.ts +160 -0
- package/dist/services/cache-layer.d.ts.map +1 -0
- package/dist/services/cache-layer.js +138 -0
- package/dist/services/cache-layer.js.map +1 -0
- package/dist/services/compose-cache.d.ts +75 -0
- package/dist/services/compose-cache.d.ts.map +1 -0
- package/dist/services/compose-cache.js +178 -0
- package/dist/services/compose-cache.js.map +1 -0
- package/dist/services/compose-discovery.d.ts +46 -0
- package/dist/services/compose-discovery.d.ts.map +1 -0
- package/dist/services/compose-discovery.js +219 -0
- package/dist/services/compose-discovery.js.map +1 -0
- package/dist/services/compose-project-lister.d.ts +27 -0
- package/dist/services/compose-project-lister.d.ts.map +1 -0
- package/dist/services/compose-project-lister.js +71 -0
- package/dist/services/compose-project-lister.js.map +1 -0
- package/dist/services/compose-scanner.d.ts +63 -0
- package/dist/services/compose-scanner.d.ts.map +1 -0
- package/dist/services/compose-scanner.js +253 -0
- package/dist/services/compose-scanner.js.map +1 -0
- package/dist/services/compose.d.ts +64 -28
- package/dist/services/compose.d.ts.map +1 -1
- package/dist/services/compose.js +220 -98
- package/dist/services/compose.js.map +1 -1
- package/dist/services/config-loader.d.ts +23 -0
- package/dist/services/config-loader.d.ts.map +1 -0
- package/dist/services/config-loader.js +124 -0
- package/dist/services/config-loader.js.map +1 -0
- package/dist/services/config-service.d.ts +38 -0
- package/dist/services/config-service.d.ts.map +1 -0
- package/dist/services/config-service.js +225 -0
- package/dist/services/config-service.js.map +1 -0
- package/dist/services/container-host-map-cache.d.ts +121 -0
- package/dist/services/container-host-map-cache.d.ts.map +1 -0
- package/dist/services/container-host-map-cache.js +188 -0
- package/dist/services/container-host-map-cache.js.map +1 -0
- package/dist/services/container.d.ts +194 -6
- package/dist/services/container.d.ts.map +1 -1
- package/dist/services/container.js +386 -11
- package/dist/services/container.js.map +1 -1
- package/dist/services/diagnostics.d.ts +57 -0
- package/dist/services/diagnostics.d.ts.map +1 -0
- package/dist/services/diagnostics.js +271 -0
- package/dist/services/diagnostics.js.map +1 -0
- package/dist/services/docker/container-service.d.ts +123 -0
- package/dist/services/docker/container-service.d.ts.map +1 -0
- package/dist/services/docker/container-service.js +347 -0
- package/dist/services/docker/container-service.js.map +1 -0
- package/dist/services/docker/image-service.d.ts +82 -0
- package/dist/services/docker/image-service.d.ts.map +1 -0
- package/dist/services/docker/image-service.js +193 -0
- package/dist/services/docker/image-service.js.map +1 -0
- package/dist/services/docker/index.d.ts +80 -0
- package/dist/services/docker/index.d.ts.map +1 -0
- package/dist/services/docker/index.js +103 -0
- package/dist/services/docker/index.js.map +1 -0
- package/dist/services/docker/network-service.d.ts +22 -0
- package/dist/services/docker/network-service.d.ts.map +1 -0
- package/dist/services/docker/network-service.js +43 -0
- package/dist/services/docker/network-service.js.map +1 -0
- package/dist/services/docker/system-service.d.ts +49 -0
- package/dist/services/docker/system-service.d.ts.map +1 -0
- package/dist/services/docker/system-service.js +215 -0
- package/dist/services/docker/system-service.js.map +1 -0
- package/dist/services/docker/utils/client-factory.d.ts +56 -0
- package/dist/services/docker/utils/client-factory.d.ts.map +1 -0
- package/dist/services/docker/utils/client-factory.js +139 -0
- package/dist/services/docker/utils/client-factory.js.map +1 -0
- package/dist/services/docker/utils/client-manager.d.ts +88 -0
- package/dist/services/docker/utils/client-manager.d.ts.map +1 -0
- package/dist/services/docker/utils/client-manager.js +124 -0
- package/dist/services/docker/utils/client-manager.js.map +1 -0
- package/dist/services/docker/utils/exec-handler.d.ts +94 -0
- package/dist/services/docker/utils/exec-handler.d.ts.map +1 -0
- package/dist/services/docker/utils/exec-handler.js +197 -0
- package/dist/services/docker/utils/exec-handler.js.map +1 -0
- package/dist/services/docker/utils/formatters.d.ts +13 -0
- package/dist/services/docker/utils/formatters.d.ts.map +1 -0
- package/dist/services/docker/utils/formatters.js +33 -0
- package/dist/services/docker/utils/formatters.js.map +1 -0
- package/dist/services/docker/utils/log-parser.d.ts +10 -0
- package/dist/services/docker/utils/log-parser.d.ts.map +1 -0
- package/dist/services/docker/utils/log-parser.js +48 -0
- package/dist/services/docker/utils/log-parser.js.map +1 -0
- package/dist/services/docker/utils/stats-calculator.d.ts +68 -0
- package/dist/services/docker/utils/stats-calculator.d.ts.map +1 -0
- package/dist/services/docker/utils/stats-calculator.js +61 -0
- package/dist/services/docker/utils/stats-calculator.js.map +1 -0
- package/dist/services/docker/volume-service.d.ts +22 -0
- package/dist/services/docker/volume-service.d.ts.map +1 -0
- package/dist/services/docker/volume-service.js +48 -0
- package/dist/services/docker/volume-service.js.map +1 -0
- package/dist/services/docker-interfaces.d.ts +283 -0
- package/dist/services/docker-interfaces.d.ts.map +1 -0
- package/dist/services/docker-interfaces.js +13 -0
- package/dist/services/docker-interfaces.js.map +1 -0
- package/dist/services/docker.d.ts +42 -5
- package/dist/services/docker.d.ts.map +1 -1
- package/dist/services/docker.js +335 -127
- package/dist/services/docker.js.map +1 -1
- package/dist/services/file-service.d.ts +6 -2
- package/dist/services/file-service.d.ts.map +1 -1
- package/dist/services/file-service.js +156 -52
- package/dist/services/file-service.js.map +1 -1
- package/dist/services/host-config-repository.d.ts +133 -0
- package/dist/services/host-config-repository.d.ts.map +1 -0
- package/dist/services/host-config-repository.js +323 -0
- package/dist/services/host-config-repository.js.map +1 -0
- package/dist/services/host-resolver.d.ts +49 -0
- package/dist/services/host-resolver.d.ts.map +1 -0
- package/dist/services/host-resolver.js +176 -0
- package/dist/services/host-resolver.js.map +1 -0
- package/dist/services/interfaces.d.ts +61 -194
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/local-executor.d.ts +31 -0
- package/dist/services/local-executor.d.ts.map +1 -0
- package/dist/services/local-executor.js +71 -0
- package/dist/services/local-executor.js.map +1 -0
- package/dist/services/ssh-config-loader.d.ts +35 -0
- package/dist/services/ssh-config-loader.d.ts.map +1 -0
- package/dist/services/ssh-config-loader.js +218 -0
- package/dist/services/ssh-config-loader.js.map +1 -0
- package/dist/services/ssh-pool.d.ts +26 -1
- package/dist/services/ssh-pool.d.ts.map +1 -1
- package/dist/services/ssh-pool.js +166 -25
- package/dist/services/ssh-pool.js.map +1 -1
- package/dist/services/ssh-service.d.ts +3 -0
- package/dist/services/ssh-service.d.ts.map +1 -1
- package/dist/services/ssh-service.js +53 -31
- package/dist/services/ssh-service.js.map +1 -1
- package/dist/services/ssh.d.ts +2 -6
- package/dist/services/ssh.d.ts.map +1 -1
- package/dist/services/ssh.js +9 -40
- package/dist/services/ssh.js.map +1 -1
- package/dist/tools/definitions/flux.d.ts +13 -0
- package/dist/tools/definitions/flux.d.ts.map +1 -0
- package/dist/tools/definitions/flux.js +101 -0
- package/dist/tools/definitions/flux.js.map +1 -0
- package/dist/tools/definitions/index.d.ts +8 -0
- package/dist/tools/definitions/index.d.ts.map +1 -0
- package/dist/tools/definitions/index.js +8 -0
- package/dist/tools/definitions/index.js.map +1 -0
- package/dist/tools/definitions/scout.d.ts +13 -0
- package/dist/tools/definitions/scout.d.ts.map +1 -0
- package/dist/tools/definitions/scout.js +78 -0
- package/dist/tools/definitions/scout.js.map +1 -0
- package/dist/tools/flux.d.ts +16 -8
- package/dist/tools/flux.d.ts.map +1 -1
- package/dist/tools/flux.js +27 -66
- package/dist/tools/flux.js.map +1 -1
- package/dist/tools/handlers/base-handler.d.ts +172 -0
- package/dist/tools/handlers/base-handler.d.ts.map +1 -0
- package/dist/tools/handlers/base-handler.js +234 -0
- package/dist/tools/handlers/base-handler.js.map +1 -0
- package/dist/tools/handlers/compose-handlers.d.ts +108 -0
- package/dist/tools/handlers/compose-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/compose-handlers.js +293 -0
- package/dist/tools/handlers/compose-handlers.js.map +1 -0
- package/dist/tools/handlers/compose-utils.d.ts +35 -0
- package/dist/tools/handlers/compose-utils.d.ts.map +1 -0
- package/dist/tools/handlers/compose-utils.js +76 -0
- package/dist/tools/handlers/compose-utils.js.map +1 -0
- package/dist/tools/handlers/compose.d.ts +23 -0
- package/dist/tools/handlers/compose.d.ts.map +1 -0
- package/dist/tools/handlers/compose.js +125 -0
- package/dist/tools/handlers/compose.js.map +1 -0
- package/dist/tools/handlers/container.d.ts +23 -0
- package/dist/tools/handlers/container.d.ts.map +1 -0
- package/dist/tools/handlers/container.js +333 -0
- package/dist/tools/handlers/container.js.map +1 -0
- package/dist/tools/handlers/docker.d.ts +24 -0
- package/dist/tools/handlers/docker.d.ts.map +1 -0
- package/dist/tools/handlers/docker.js +155 -0
- package/dist/tools/handlers/docker.js.map +1 -0
- package/dist/tools/handlers/host.d.ts +23 -0
- package/dist/tools/handlers/host.d.ts.map +1 -0
- package/dist/tools/handlers/host.js +196 -0
- package/dist/tools/handlers/host.js.map +1 -0
- package/dist/tools/handlers/scout-logs.d.ts +24 -0
- package/dist/tools/handlers/scout-logs.d.ts.map +1 -0
- package/dist/tools/handlers/scout-logs.js +119 -0
- package/dist/tools/handlers/scout-logs.js.map +1 -0
- package/dist/tools/handlers/scout-simple.d.ts +23 -0
- package/dist/tools/handlers/scout-simple.d.ts.map +1 -0
- package/dist/tools/handlers/scout-simple.js +286 -0
- package/dist/tools/handlers/scout-simple.js.map +1 -0
- package/dist/tools/handlers/scout-zfs.d.ts +23 -0
- package/dist/tools/handlers/scout-zfs.d.ts.map +1 -0
- package/dist/tools/handlers/scout-zfs.js +82 -0
- package/dist/tools/handlers/scout-zfs.js.map +1 -0
- package/dist/tools/index.d.ts +32 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +41 -35
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry.d.ts +135 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +151 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/scout.d.ts +16 -8
- package/dist/tools/scout.d.ts.map +1 -1
- package/dist/tools/scout.js +36 -78
- package/dist/tools/scout.js.map +1 -1
- package/dist/types.d.ts +629 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/command-security.d.ts +82 -0
- package/dist/utils/command-security.d.ts.map +1 -0
- package/dist/utils/command-security.js +122 -0
- package/dist/utils/command-security.js.map +1 -0
- package/dist/utils/error-sanitization.d.ts +77 -0
- package/dist/utils/error-sanitization.d.ts.map +1 -0
- package/dist/utils/error-sanitization.js +107 -0
- package/dist/utils/error-sanitization.js.map +1 -0
- package/dist/utils/errors.d.ts +30 -6
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +91 -12
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/help-handler.d.ts +23 -0
- package/dist/utils/help-handler.d.ts.map +1 -0
- package/dist/utils/help-handler.js +21 -0
- package/dist/utils/help-handler.js.map +1 -0
- package/dist/utils/help.d.ts +1 -1
- package/dist/utils/help.d.ts.map +1 -1
- package/dist/utils/help.js +57 -16
- package/dist/utils/help.js.map +1 -1
- package/dist/utils/host-utils.d.ts +31 -0
- package/dist/utils/host-utils.d.ts.map +1 -0
- package/dist/utils/host-utils.js +80 -0
- package/dist/utils/host-utils.js.map +1 -0
- package/dist/utils/index.d.ts +8 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +8 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/init-detection.d.ts +36 -0
- package/dist/utils/init-detection.d.ts.map +1 -0
- package/dist/utils/init-detection.js +79 -0
- package/dist/utils/init-detection.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pagination.d.ts +20 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +29 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/path-security.d.ts +132 -18
- package/dist/utils/path-security.d.ts.map +1 -1
- package/dist/utils/path-security.js +164 -35
- package/dist/utils/path-security.js.map +1 -1
- package/dist/utils/sorting.d.ts +33 -0
- package/dist/utils/sorting.d.ts.map +1 -0
- package/dist/utils/sorting.js +57 -0
- package/dist/utils/sorting.js.map +1 -0
- package/dist/utils/text-filters.d.ts +13 -0
- package/dist/utils/text-filters.d.ts.map +1 -0
- package/dist/utils/text-filters.js +18 -0
- package/dist/utils/text-filters.js.map +1 -0
- package/dist/utils/time.d.ts +11 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +13 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/utils/validation.d.ts +25 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +56 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +45 -19
- package/dist/schemas/discriminator.d.ts +0 -20
- package/dist/schemas/discriminator.d.ts.map +0 -1
- package/dist/schemas/discriminator.js +0 -25
- package/dist/schemas/discriminator.js.map +0 -1
- package/dist/schemas/unified.d.ts +0 -674
- package/dist/schemas/unified.d.ts.map +0 -1
- package/dist/schemas/unified.js +0 -453
- package/dist/schemas/unified.js.map +0 -1
- package/dist/tools/unified.d.ts +0 -7
- package/dist/tools/unified.d.ts.map +0 -1
- package/dist/tools/unified.js +0 -827
- package/dist/tools/unified.js.map +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { DEFAULT_COMPOSE_SCAN_CONCURRENCY } from "../constants.js";
|
|
2
|
+
import { logError } from "../utils/errors.js";
|
|
3
|
+
import { getCurrentTimestamp } from "../utils/time.js";
|
|
4
|
+
const DEFAULT_SEARCH_PATHS = ["/compose", "/mnt/cache/compose", "/mnt/cache/code"];
|
|
5
|
+
export class ComposeDiscovery {
|
|
6
|
+
projectLister;
|
|
7
|
+
cache;
|
|
8
|
+
scanner;
|
|
9
|
+
/**
|
|
10
|
+
* In-flight filesystem scan promises keyed by host name.
|
|
11
|
+
* Coalesces concurrent scan requests for the same host into a single scan,
|
|
12
|
+
* preventing redundant filesystem operations (200-1000ms each).
|
|
13
|
+
*/
|
|
14
|
+
inflightScans = new Map();
|
|
15
|
+
constructor(projectLister, cache, // Public for cache invalidation in handlers
|
|
16
|
+
scanner) {
|
|
17
|
+
this.projectLister = projectLister;
|
|
18
|
+
this.cache = cache;
|
|
19
|
+
this.scanner = scanner;
|
|
20
|
+
}
|
|
21
|
+
getSearchPaths(host, cachedPaths) {
|
|
22
|
+
const paths = new Set();
|
|
23
|
+
// Add default paths
|
|
24
|
+
for (const p of DEFAULT_SEARCH_PATHS) {
|
|
25
|
+
paths.add(p);
|
|
26
|
+
}
|
|
27
|
+
// Add cached paths
|
|
28
|
+
for (const p of cachedPaths) {
|
|
29
|
+
paths.add(p);
|
|
30
|
+
}
|
|
31
|
+
// Add user-configured paths
|
|
32
|
+
if (host.composeSearchPaths) {
|
|
33
|
+
for (const p of host.composeSearchPaths) {
|
|
34
|
+
paths.add(p);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Array.from(paths);
|
|
38
|
+
}
|
|
39
|
+
async discoverFromDockerLs(host, projectName) {
|
|
40
|
+
try {
|
|
41
|
+
const projects = await this.projectLister.listComposeProjects(host);
|
|
42
|
+
const found = projects.find((p) => p.name === projectName);
|
|
43
|
+
if (found && found.configFiles.length > 0) {
|
|
44
|
+
return {
|
|
45
|
+
path: found.configFiles[0],
|
|
46
|
+
name: projectName,
|
|
47
|
+
discoveredFrom: "docker-ls",
|
|
48
|
+
lastSeen: getCurrentTimestamp(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logError(error, {
|
|
54
|
+
operation: "discoverFromDockerLs",
|
|
55
|
+
metadata: { host: host.name, project: projectName },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Scan a host's filesystem for compose projects, coalescing concurrent requests.
|
|
62
|
+
* If a scan is already in flight for this host, returns the existing promise.
|
|
63
|
+
*
|
|
64
|
+
* @param host - Host to scan
|
|
65
|
+
* @returns Array of discovered projects with name and path
|
|
66
|
+
*/
|
|
67
|
+
async scanHostFilesystem(host) {
|
|
68
|
+
const existing = this.inflightScans.get(host.name);
|
|
69
|
+
if (existing) {
|
|
70
|
+
return existing;
|
|
71
|
+
}
|
|
72
|
+
const scanPromise = (async () => {
|
|
73
|
+
try {
|
|
74
|
+
const cacheData = await this.cache.load(host.name);
|
|
75
|
+
const searchPaths = this.getSearchPaths(host, cacheData.searchPaths);
|
|
76
|
+
const hostWithSearchPaths = {
|
|
77
|
+
...host,
|
|
78
|
+
composeSearchPaths: searchPaths,
|
|
79
|
+
};
|
|
80
|
+
const files = await this.scanner.findComposeFiles(hostWithSearchPaths);
|
|
81
|
+
const nameMap = await this.scanner.batchParseComposeNames(host, files, DEFAULT_COMPOSE_SCAN_CONCURRENCY);
|
|
82
|
+
return files.map((file) => {
|
|
83
|
+
const dirName = this.scanner.extractProjectName(file);
|
|
84
|
+
const explicitName = nameMap.get(file);
|
|
85
|
+
const name = explicitName ?? dirName;
|
|
86
|
+
return { name, path: file };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
this.inflightScans.delete(host.name);
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
this.inflightScans.set(host.name, scanPromise);
|
|
94
|
+
return scanPromise;
|
|
95
|
+
}
|
|
96
|
+
async discoverFromFilesystem(host, projectName) {
|
|
97
|
+
try {
|
|
98
|
+
const projects = await this.scanHostFilesystem(host);
|
|
99
|
+
// Cache all discovered projects to avoid future scans
|
|
100
|
+
for (const project of projects) {
|
|
101
|
+
await this.cache.updateProject(host.name, project.name, {
|
|
102
|
+
path: project.path,
|
|
103
|
+
name: project.name,
|
|
104
|
+
discoveredFrom: "scan",
|
|
105
|
+
lastSeen: getCurrentTimestamp(),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const found = projects.find((p) => p.name === projectName);
|
|
109
|
+
if (found) {
|
|
110
|
+
return {
|
|
111
|
+
path: found.path,
|
|
112
|
+
name: found.name,
|
|
113
|
+
discoveredFrom: "scan",
|
|
114
|
+
lastSeen: getCurrentTimestamp(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logError(error, {
|
|
120
|
+
operation: "discoverFromFilesystem",
|
|
121
|
+
metadata: { host: host.name, project: projectName },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve compose file path for a project
|
|
128
|
+
* Strategy:
|
|
129
|
+
* 1. Check cache (trust it - lazy invalidation at handler level)
|
|
130
|
+
* 2. Check docker compose ls (running projects) - FAST
|
|
131
|
+
* 3. Scan filesystem if not found - SLOWER
|
|
132
|
+
* 4. Error if not found
|
|
133
|
+
*/
|
|
134
|
+
async resolveProjectPath(host, projectName) {
|
|
135
|
+
// Step 1: Check cache (no validation - lazy invalidation)
|
|
136
|
+
const cached = await this.cache.getProject(host.name, projectName);
|
|
137
|
+
if (cached) {
|
|
138
|
+
return cached.path;
|
|
139
|
+
}
|
|
140
|
+
// Step 2: Try docker ls first (fast, authoritative for running projects)
|
|
141
|
+
const dockerLsResult = await this.discoverFromDockerLs(host, projectName);
|
|
142
|
+
if (dockerLsResult) {
|
|
143
|
+
await this.cache.updateProject(host.name, projectName, dockerLsResult);
|
|
144
|
+
return dockerLsResult.path;
|
|
145
|
+
}
|
|
146
|
+
// Step 3: Fallback to filesystem scan (slower, but finds stopped projects)
|
|
147
|
+
const scanResult = await this.discoverFromFilesystem(host, projectName);
|
|
148
|
+
if (scanResult) {
|
|
149
|
+
await this.cache.updateProject(host.name, projectName, scanResult);
|
|
150
|
+
return scanResult.path;
|
|
151
|
+
}
|
|
152
|
+
// Step 4: Not found
|
|
153
|
+
const cacheData = await this.cache.load(host.name);
|
|
154
|
+
const searchPaths = cacheData.searchPaths.length > 0 ? cacheData.searchPaths : DEFAULT_SEARCH_PATHS;
|
|
155
|
+
throw new Error(`Project '${projectName}' not found on host '${host.name}'\nSearched locations: ${searchPaths.join(", ")}\nTip: Provide compose_file parameter if project is in a different location`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Warm cache by discovering all projects on a host in the background.
|
|
159
|
+
* This mitigates cold-start penalty (200-1000ms) on first compose operation.
|
|
160
|
+
* Call this on server startup or after cache invalidation.
|
|
161
|
+
*
|
|
162
|
+
* @param host - Host to warm cache for
|
|
163
|
+
*/
|
|
164
|
+
async warmCache(host) {
|
|
165
|
+
// Phase 1: Discover from docker ls (fast, finds running projects)
|
|
166
|
+
try {
|
|
167
|
+
const runningProjects = await this.projectLister.listComposeProjects(host);
|
|
168
|
+
// Cache all running projects
|
|
169
|
+
for (const project of runningProjects) {
|
|
170
|
+
if (project.configFiles.length > 0) {
|
|
171
|
+
await this.cache.updateProject(host.name, project.name, {
|
|
172
|
+
path: project.configFiles[0],
|
|
173
|
+
name: project.name,
|
|
174
|
+
discoveredFrom: "docker-ls",
|
|
175
|
+
lastSeen: getCurrentTimestamp(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
// Non-fatal: docker-ls failure should not prevent filesystem scan
|
|
182
|
+
logError(error, {
|
|
183
|
+
operation: "warmCache:docker-ls",
|
|
184
|
+
metadata: { host: host.name },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Phase 2: Discover from filesystem (slower, finds stopped projects)
|
|
188
|
+
try {
|
|
189
|
+
const cacheData = await this.cache.load(host.name);
|
|
190
|
+
const searchPaths = this.getSearchPaths(host, cacheData.searchPaths);
|
|
191
|
+
const hostWithSearchPaths = { ...host, composeSearchPaths: searchPaths };
|
|
192
|
+
const files = await this.scanner.findComposeFiles(hostWithSearchPaths);
|
|
193
|
+
const nameMap = await this.scanner.batchParseComposeNames(host, files, DEFAULT_COMPOSE_SCAN_CONCURRENCY);
|
|
194
|
+
const projects = files.map((file) => {
|
|
195
|
+
const dirName = this.scanner.extractProjectName(file);
|
|
196
|
+
const explicitName = nameMap.get(file);
|
|
197
|
+
const name = explicitName ?? dirName;
|
|
198
|
+
return { name, path: file };
|
|
199
|
+
});
|
|
200
|
+
// Cache all discovered projects
|
|
201
|
+
for (const project of projects) {
|
|
202
|
+
await this.cache.updateProject(host.name, project.name, {
|
|
203
|
+
path: project.path,
|
|
204
|
+
name: project.name,
|
|
205
|
+
discoveredFrom: "scan",
|
|
206
|
+
lastSeen: getCurrentTimestamp(),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Non-fatal: filesystem scan failure is best-effort
|
|
212
|
+
logError(error, {
|
|
213
|
+
operation: "warmCache:filesystem-scan",
|
|
214
|
+
metadata: { host: host.name },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=compose-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-discovery.js","sourceRoot":"","sources":["../../src/services/compose-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ComposeProject, HostConfig } from "../types.js";
|
|
2
|
+
import type { IComposeProjectLister, ILocalExecutorService, ISSHService } from "./interfaces.js";
|
|
3
|
+
/**
|
|
4
|
+
* Standalone service for listing Docker Compose projects on hosts.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from ComposeService to break the circular dependency:
|
|
7
|
+
* ComposeService <-> ComposeDiscovery
|
|
8
|
+
*
|
|
9
|
+
* Before: ComposeService implemented both IComposeService and IComposeProjectLister,
|
|
10
|
+
* requiring setter injection to wire ComposeDiscovery as its path resolver.
|
|
11
|
+
*
|
|
12
|
+
* After: ComposeProjectLister implements IComposeProjectLister independently,
|
|
13
|
+
* allowing a clean dependency chain:
|
|
14
|
+
* ComposeProjectLister -> ComposeDiscovery -> ComposeService
|
|
15
|
+
* (no cycle, no setter injection)
|
|
16
|
+
*/
|
|
17
|
+
export declare class ComposeProjectLister implements IComposeProjectLister {
|
|
18
|
+
private sshService;
|
|
19
|
+
private localExecutor;
|
|
20
|
+
constructor(sshService: ISSHService, localExecutor: ILocalExecutorService);
|
|
21
|
+
/**
|
|
22
|
+
* List all compose projects on a host (local or remote)
|
|
23
|
+
* via `docker compose ls --format json`.
|
|
24
|
+
*/
|
|
25
|
+
listComposeProjects(host: HostConfig): Promise<ComposeProject[]>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=compose-project-lister.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-project-lister.d.ts","sourceRoot":"","sources":["../../src/services/compose-project-lister.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGjG;;;;;;;;;;;;;GAaG;AACH,qBAAa,oBAAqB,YAAW,qBAAqB;IAE9D,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,aAAa;gBADb,UAAU,EAAE,WAAW,EACvB,aAAa,EAAE,qBAAqB;IAG9C;;;OAGG;IACG,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CA0DvE"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ComposeOperationError } from "../utils/errors.js";
|
|
2
|
+
import { isLocalHost } from "../utils/host-utils.js";
|
|
3
|
+
import { parseComposeStatus } from "./compose.js";
|
|
4
|
+
import { validateHostForSsh } from "./ssh.js";
|
|
5
|
+
/**
|
|
6
|
+
* Standalone service for listing Docker Compose projects on hosts.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from ComposeService to break the circular dependency:
|
|
9
|
+
* ComposeService <-> ComposeDiscovery
|
|
10
|
+
*
|
|
11
|
+
* Before: ComposeService implemented both IComposeService and IComposeProjectLister,
|
|
12
|
+
* requiring setter injection to wire ComposeDiscovery as its path resolver.
|
|
13
|
+
*
|
|
14
|
+
* After: ComposeProjectLister implements IComposeProjectLister independently,
|
|
15
|
+
* allowing a clean dependency chain:
|
|
16
|
+
* ComposeProjectLister -> ComposeDiscovery -> ComposeService
|
|
17
|
+
* (no cycle, no setter injection)
|
|
18
|
+
*/
|
|
19
|
+
export class ComposeProjectLister {
|
|
20
|
+
sshService;
|
|
21
|
+
localExecutor;
|
|
22
|
+
constructor(sshService, localExecutor) {
|
|
23
|
+
this.sshService = sshService;
|
|
24
|
+
this.localExecutor = localExecutor;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* List all compose projects on a host (local or remote)
|
|
28
|
+
* via `docker compose ls --format json`.
|
|
29
|
+
*/
|
|
30
|
+
async listComposeProjects(host) {
|
|
31
|
+
const args = ["--format", "json"];
|
|
32
|
+
try {
|
|
33
|
+
let stdout;
|
|
34
|
+
if (isLocalHost(host)) {
|
|
35
|
+
stdout = await this.localExecutor.executeLocalCommand("docker", ["compose", "ls", ...args], { timeoutMs: 15000 });
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
validateHostForSsh(host);
|
|
39
|
+
const command = `docker compose ls ${args.join(" ")}`;
|
|
40
|
+
stdout = await this.sshService.executeSSHCommand(host, command, [], {
|
|
41
|
+
timeoutMs: 15000,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (!stdout.trim()) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const projects = JSON.parse(stdout);
|
|
48
|
+
return projects.map((p) => {
|
|
49
|
+
if (!p.ConfigFiles) {
|
|
50
|
+
return {
|
|
51
|
+
name: p.Name,
|
|
52
|
+
status: parseComposeStatus(p.Status),
|
|
53
|
+
configFiles: [],
|
|
54
|
+
services: [],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
name: p.Name,
|
|
59
|
+
status: parseComposeStatus(p.Status),
|
|
60
|
+
configFiles: p.ConfigFiles.split(",").map((f) => f.trim()),
|
|
61
|
+
services: [],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const detail = error instanceof Error ? error.message : "Unknown error";
|
|
67
|
+
throw new ComposeOperationError(`Failed to list compose projects: ${detail}`, host.name, "*", "ls", error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=compose-project-lister.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-project-lister.js","sourceRoot":"","sources":["../../src/services/compose-project-lister.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,oBAAoB;IAErB;IACA;IAFV,YACU,UAAuB,EACvB,aAAoC;QADpC,eAAU,GAAV,UAAU,CAAa;QACvB,kBAAa,GAAb,aAAa,CAAuB;IAC3C,CAAC;IAEJ;;;OAGG;IACH,KAAK,CAAC,mBAAmB,CAAC,IAAgB;QACxC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAElC,IAAI,CAAC;YACH,IAAI,MAAc,CAAC;YAEnB,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtB,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,mBAAmB,CACnD,QAAQ,EACR,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,EAC1B,EAAE,SAAS,EAAE,KAAK,EAAE,CACrB,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBACzB,MAAM,OAAO,GAAG,qBAAqB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtD,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;oBAClE,SAAS,EAAE,KAAK;iBACjB,CAAC,CAAC;YACL,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAIhC,CAAC;YAEH,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACxB,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;oBACnB,OAAO;wBACL,IAAI,EAAE,CAAC,CAAC,IAAI;wBACZ,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC;wBACpC,WAAW,EAAE,EAAE;wBACf,QAAQ,EAAE,EAAE;qBACb,CAAC;gBACJ,CAAC;gBAED,OAAO;oBACL,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC;oBACpC,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC1D,QAAQ,EAAE,EAAE;iBACb,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACxE,MAAM,IAAI,qBAAqB,CAC7B,oCAAoC,MAAM,EAAE,EAC5C,IAAI,CAAC,IAAI,EACT,GAAG,EACH,IAAI,EACJ,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { HostConfig } from "../types.js";
|
|
2
|
+
import type { ILocalExecutorService, ISSHService } from "./interfaces.js";
|
|
3
|
+
/**
|
|
4
|
+
* Service for scanning filesystems to find Docker Compose files.
|
|
5
|
+
* Supports both remote (SSH) and local execution.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ComposeScanner {
|
|
8
|
+
private readonly sshService;
|
|
9
|
+
private readonly localExecutor;
|
|
10
|
+
constructor(sshService: ISSHService, localExecutor: ILocalExecutorService);
|
|
11
|
+
/**
|
|
12
|
+
* Find all compose files in configured search paths.
|
|
13
|
+
* Uses SSH for remote hosts, local execution for localhost.
|
|
14
|
+
*
|
|
15
|
+
* @param host - Host configuration with optional composeSearchPaths
|
|
16
|
+
* @returns Array of absolute paths to compose files
|
|
17
|
+
*/
|
|
18
|
+
findComposeFiles(host: HostConfig): Promise<string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Extract project name from compose file path (parent directory name).
|
|
21
|
+
*
|
|
22
|
+
* @param filePath - Absolute path to compose file
|
|
23
|
+
* @returns Parent directory name, or empty string if at root
|
|
24
|
+
*/
|
|
25
|
+
extractProjectName(filePath: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Parse compose file to extract explicit 'name:' field.
|
|
28
|
+
* Returns null if parsing fails or no name field exists.
|
|
29
|
+
*
|
|
30
|
+
* SECURITY: Uses args array to prevent shell injection when reading files.
|
|
31
|
+
*
|
|
32
|
+
* @param host - Host configuration
|
|
33
|
+
* @param filePath - Absolute path to compose file
|
|
34
|
+
* @returns Explicit project name from 'name:' field, or null
|
|
35
|
+
*/
|
|
36
|
+
parseComposeName(host: HostConfig, filePath: string): Promise<string | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Parse multiple compose files in chunks and read each chunk in parallel.
|
|
39
|
+
* Avoids shell string concatenation by issuing one cat command per file.
|
|
40
|
+
*
|
|
41
|
+
* @param host - Host configuration where files are located
|
|
42
|
+
* @param filePaths - Array of absolute paths to compose files
|
|
43
|
+
* @param concurrency - Maximum concurrent SSH connections (default from constants)
|
|
44
|
+
* @returns Map of file path to project name (null if parse failed)
|
|
45
|
+
*/
|
|
46
|
+
batchParseComposeNames(host: HostConfig, filePaths: string[], concurrency?: number): Promise<Map<string, string | null>>;
|
|
47
|
+
/**
|
|
48
|
+
* Read and parse a batch of compose files using parallel cat commands.
|
|
49
|
+
*
|
|
50
|
+
* @param host - Host configuration
|
|
51
|
+
* @param filePaths - Batch of file paths to read
|
|
52
|
+
* @returns Map of file path to parsed project name (null if parse failed)
|
|
53
|
+
*/
|
|
54
|
+
private batchReadAndParse;
|
|
55
|
+
/**
|
|
56
|
+
* Check if host is localhost (avoids SSH overhead for local operations).
|
|
57
|
+
*
|
|
58
|
+
* @param host - Host configuration
|
|
59
|
+
* @returns true if host is localhost/127.0.0.1
|
|
60
|
+
*/
|
|
61
|
+
private isLocalHost;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=compose-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-scanner.d.ts","sourceRoot":"","sources":["../../src/services/compose-scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAsC1E;;;GAGG;AACH,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa;gBADb,UAAU,EAAE,WAAW,EACvB,aAAa,EAAE,qBAAqB;IAGvD;;;;;;OAMG;IACG,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAwC3D;;;;;OAKG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAS5C;;;;;;;;;OASG;IACG,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA8BlF;;;;;;;;OAQG;IACG,sBAAsB,CAC1B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EAAE,EACnB,WAAW,GAAE,MAAyC,GACrD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAyCtC;;;;;;OAMG;YACW,iBAAiB;IAwE/B;;;;;OAKG;IACH,OAAO,CAAC,WAAW;CAIpB"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import pLimit from "p-limit";
|
|
2
|
+
import YAML from "yaml";
|
|
3
|
+
import { DEFAULT_COMPOSE_SCAN_CONCURRENCY } from "../constants.js";
|
|
4
|
+
/**
|
|
5
|
+
* Default search paths for compose files if not specified in host config
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_SEARCH_PATHS = ["/compose", "/mnt/cache/compose", "/mnt/cache/code"];
|
|
8
|
+
/**
|
|
9
|
+
* Maximum depth to search for compose files
|
|
10
|
+
*/
|
|
11
|
+
const MAX_SCAN_DEPTH = 3;
|
|
12
|
+
/**
|
|
13
|
+
* Timeout for remote find operations (ms)
|
|
14
|
+
*/
|
|
15
|
+
const FIND_TIMEOUT_MS = 60_000;
|
|
16
|
+
/**
|
|
17
|
+
* Timeout for reading individual compose files (ms)
|
|
18
|
+
*/
|
|
19
|
+
const CAT_TIMEOUT_MS = 10_000;
|
|
20
|
+
/**
|
|
21
|
+
* Maximum number of files to read in a single batched cat command.
|
|
22
|
+
* Keeps argument lists and output size manageable.
|
|
23
|
+
*/
|
|
24
|
+
const BATCH_CAT_CHUNK_SIZE = 20;
|
|
25
|
+
/**
|
|
26
|
+
* Compose file patterns to search for
|
|
27
|
+
*/
|
|
28
|
+
const COMPOSE_FILE_PATTERNS = [
|
|
29
|
+
"docker-compose.yml",
|
|
30
|
+
"docker-compose.yaml",
|
|
31
|
+
"compose.yml",
|
|
32
|
+
"compose.yaml",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Service for scanning filesystems to find Docker Compose files.
|
|
36
|
+
* Supports both remote (SSH) and local execution.
|
|
37
|
+
*/
|
|
38
|
+
export class ComposeScanner {
|
|
39
|
+
sshService;
|
|
40
|
+
localExecutor;
|
|
41
|
+
constructor(sshService, localExecutor) {
|
|
42
|
+
this.sshService = sshService;
|
|
43
|
+
this.localExecutor = localExecutor;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Find all compose files in configured search paths.
|
|
47
|
+
* Uses SSH for remote hosts, local execution for localhost.
|
|
48
|
+
*
|
|
49
|
+
* @param host - Host configuration with optional composeSearchPaths
|
|
50
|
+
* @returns Array of absolute paths to compose files
|
|
51
|
+
*/
|
|
52
|
+
async findComposeFiles(host) {
|
|
53
|
+
const searchPaths = host.composeSearchPaths ?? DEFAULT_SEARCH_PATHS;
|
|
54
|
+
const isLocal = this.isLocalHost(host);
|
|
55
|
+
// Build find command args to avoid shell injection
|
|
56
|
+
// find /path1 /path2 -maxdepth 3 -type f \( -name "docker-compose.yml" -o -name "compose.yaml" ... \) -print
|
|
57
|
+
const args = [...searchPaths, "-maxdepth", MAX_SCAN_DEPTH.toString(), "-type", "f", "("];
|
|
58
|
+
// Add each pattern with -o (OR) between them
|
|
59
|
+
for (let i = 0; i < COMPOSE_FILE_PATTERNS.length; i++) {
|
|
60
|
+
if (i > 0) {
|
|
61
|
+
args.push("-o");
|
|
62
|
+
}
|
|
63
|
+
const pattern = COMPOSE_FILE_PATTERNS[i];
|
|
64
|
+
if (pattern) {
|
|
65
|
+
args.push("-name", pattern);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
args.push(")", "-print");
|
|
69
|
+
try {
|
|
70
|
+
const output = isLocal
|
|
71
|
+
? await this.localExecutor.executeLocalCommand("find", args, { timeoutMs: FIND_TIMEOUT_MS })
|
|
72
|
+
: await this.sshService.executeSSHCommand(host, "find", args, {
|
|
73
|
+
timeoutMs: FIND_TIMEOUT_MS,
|
|
74
|
+
});
|
|
75
|
+
// Split output by newlines, filter empty lines, and deduplicate
|
|
76
|
+
const allFiles = output
|
|
77
|
+
.split("\n")
|
|
78
|
+
.map((line) => line.trim())
|
|
79
|
+
.filter((line) => line.length > 0);
|
|
80
|
+
return Array.from(new Set(allFiles));
|
|
81
|
+
}
|
|
82
|
+
catch (_error) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract project name from compose file path (parent directory name).
|
|
88
|
+
*
|
|
89
|
+
* @param filePath - Absolute path to compose file
|
|
90
|
+
* @returns Parent directory name, or empty string if at root
|
|
91
|
+
*/
|
|
92
|
+
extractProjectName(filePath) {
|
|
93
|
+
const parts = filePath.split("/").filter((p) => p.length > 0);
|
|
94
|
+
// Return parent directory name (last directory before filename)
|
|
95
|
+
if (parts.length < 2) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
return parts[parts.length - 2] ?? "";
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Parse compose file to extract explicit 'name:' field.
|
|
102
|
+
* Returns null if parsing fails or no name field exists.
|
|
103
|
+
*
|
|
104
|
+
* SECURITY: Uses args array to prevent shell injection when reading files.
|
|
105
|
+
*
|
|
106
|
+
* @param host - Host configuration
|
|
107
|
+
* @param filePath - Absolute path to compose file
|
|
108
|
+
* @returns Explicit project name from 'name:' field, or null
|
|
109
|
+
*/
|
|
110
|
+
async parseComposeName(host, filePath) {
|
|
111
|
+
const isLocal = this.isLocalHost(host);
|
|
112
|
+
try {
|
|
113
|
+
// Read file contents using cat command with args array
|
|
114
|
+
const content = isLocal
|
|
115
|
+
? await this.localExecutor.executeLocalCommand("cat", [filePath], {
|
|
116
|
+
timeoutMs: CAT_TIMEOUT_MS,
|
|
117
|
+
})
|
|
118
|
+
: await this.sshService.executeSSHCommand(host, "cat", [filePath], {
|
|
119
|
+
timeoutMs: CAT_TIMEOUT_MS,
|
|
120
|
+
});
|
|
121
|
+
// Parse YAML and extract name field
|
|
122
|
+
const parsed = YAML.parse(content);
|
|
123
|
+
if (parsed && typeof parsed === "object" && "name" in parsed) {
|
|
124
|
+
const name = parsed.name;
|
|
125
|
+
if (typeof name === "string" && name.length > 0) {
|
|
126
|
+
return name;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
catch (_error) {
|
|
132
|
+
// Alternative: Log errors for debugging while still returning null.
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parse multiple compose files in chunks and read each chunk in parallel.
|
|
138
|
+
* Avoids shell string concatenation by issuing one cat command per file.
|
|
139
|
+
*
|
|
140
|
+
* @param host - Host configuration where files are located
|
|
141
|
+
* @param filePaths - Array of absolute paths to compose files
|
|
142
|
+
* @param concurrency - Maximum concurrent SSH connections (default from constants)
|
|
143
|
+
* @returns Map of file path to project name (null if parse failed)
|
|
144
|
+
*/
|
|
145
|
+
async batchParseComposeNames(host, filePaths, concurrency = DEFAULT_COMPOSE_SCAN_CONCURRENCY) {
|
|
146
|
+
const results = new Map();
|
|
147
|
+
if (filePaths.length === 0) {
|
|
148
|
+
return results;
|
|
149
|
+
}
|
|
150
|
+
// Split file paths into chunks for batched reading
|
|
151
|
+
const chunks = [];
|
|
152
|
+
for (let i = 0; i < filePaths.length; i += BATCH_CAT_CHUNK_SIZE) {
|
|
153
|
+
chunks.push(filePaths.slice(i, i + BATCH_CAT_CHUNK_SIZE));
|
|
154
|
+
}
|
|
155
|
+
// Process chunks in parallel with concurrency control
|
|
156
|
+
const limit = pLimit(concurrency);
|
|
157
|
+
const chunkPromises = chunks.map((chunk) => limit(async () => this.batchReadAndParse(host, chunk)));
|
|
158
|
+
const chunkResults = await Promise.allSettled(chunkPromises);
|
|
159
|
+
// Merge results from all chunks
|
|
160
|
+
for (const settled of chunkResults) {
|
|
161
|
+
if (settled.status === "fulfilled") {
|
|
162
|
+
for (const [path, name] of settled.value) {
|
|
163
|
+
results.set(path, name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Ensure every requested path has an entry
|
|
168
|
+
for (const filePath of filePaths) {
|
|
169
|
+
if (!results.has(filePath)) {
|
|
170
|
+
results.set(filePath, null);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Read and parse a batch of compose files using parallel cat commands.
|
|
177
|
+
*
|
|
178
|
+
* @param host - Host configuration
|
|
179
|
+
* @param filePaths - Batch of file paths to read
|
|
180
|
+
* @returns Map of file path to parsed project name (null if parse failed)
|
|
181
|
+
*/
|
|
182
|
+
async batchReadAndParse(host, filePaths) {
|
|
183
|
+
const results = new Map();
|
|
184
|
+
const isLocal = this.isLocalHost(host);
|
|
185
|
+
if (filePaths.length === 1) {
|
|
186
|
+
// Single file — use simple cat (no delimiter overhead)
|
|
187
|
+
const name = await this.parseComposeName(host, filePaths[0]);
|
|
188
|
+
results.set(filePaths[0], name);
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const contents = await Promise.all(filePaths.map((filePath) => isLocal
|
|
193
|
+
? this.localExecutor.executeLocalCommand("cat", [filePath], {
|
|
194
|
+
timeoutMs: CAT_TIMEOUT_MS,
|
|
195
|
+
})
|
|
196
|
+
: this.sshService.executeSSHCommand(host, "cat", [filePath], {
|
|
197
|
+
timeoutMs: CAT_TIMEOUT_MS,
|
|
198
|
+
})));
|
|
199
|
+
for (let i = 0; i < filePaths.length; i++) {
|
|
200
|
+
const content = contents[i]?.trim();
|
|
201
|
+
const filePath = filePaths[i];
|
|
202
|
+
if (!content) {
|
|
203
|
+
results.set(filePath, null);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const parsed = YAML.parse(content);
|
|
208
|
+
if (parsed && typeof parsed === "object" && "name" in parsed) {
|
|
209
|
+
const name = parsed.name;
|
|
210
|
+
if (typeof name === "string" && name.length > 0) {
|
|
211
|
+
results.set(filePath, name);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
results.set(filePath, null);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
results.set(filePath, null);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Batch read failed — fall back to individual reads
|
|
224
|
+
const individualPromises = filePaths.map(async (filePath) => {
|
|
225
|
+
try {
|
|
226
|
+
const name = await this.parseComposeName(host, filePath);
|
|
227
|
+
return { path: filePath, name };
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return { path: filePath, name: null };
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
const settled = await Promise.allSettled(individualPromises);
|
|
234
|
+
for (const result of settled) {
|
|
235
|
+
if (result.status === "fulfilled") {
|
|
236
|
+
results.set(result.value.path, result.value.name);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if host is localhost (avoids SSH overhead for local operations).
|
|
244
|
+
*
|
|
245
|
+
* @param host - Host configuration
|
|
246
|
+
* @returns true if host is localhost/127.0.0.1
|
|
247
|
+
*/
|
|
248
|
+
isLocalHost(host) {
|
|
249
|
+
const hostname = host.host.toLowerCase();
|
|
250
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=compose-scanner.js.map
|