@mesh-tech/mesh-cli 0.1.1 → 0.12.1
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/assets/intent/agents-fence.md +10 -0
- package/assets/intent/intent-claude-gate.mjs +255 -0
- package/assets/log-shipper.mjs +94 -0
- package/bin/mesh.mjs +80 -0
- package/dist/bin/mesh.d.ts +7 -9
- package/dist/bin/mesh.d.ts.map +1 -1
- package/dist/bin/mesh.js +20878 -37
- package/dist/bin/mesh.js.map +7 -1
- package/dist/build-info.json +4 -0
- package/dist/src/commands/agent-api-client.d.ts +79 -0
- package/dist/src/commands/agent-api-client.d.ts.map +1 -0
- package/dist/src/commands/agent-api-client.js +170 -0
- package/dist/src/commands/agent-api-client.js.map +1 -0
- package/dist/src/commands/artifacts.d.ts +13 -0
- package/dist/src/commands/artifacts.d.ts.map +1 -0
- package/dist/src/commands/artifacts.js +140 -0
- package/dist/src/commands/artifacts.js.map +1 -0
- package/dist/src/commands/cluster.d.ts +45 -0
- package/dist/src/commands/cluster.d.ts.map +1 -0
- package/dist/src/commands/cluster.js +259 -0
- package/dist/src/commands/cluster.js.map +1 -0
- package/dist/src/commands/conversations.d.ts +61 -0
- package/dist/src/commands/conversations.d.ts.map +1 -0
- package/dist/src/commands/conversations.js +239 -0
- package/dist/src/commands/conversations.js.map +1 -0
- package/dist/src/commands/create-app.d.ts +26 -3
- package/dist/src/commands/create-app.d.ts.map +1 -1
- package/dist/src/commands/create-app.js +465 -107
- package/dist/src/commands/create-app.js.map +1 -1
- package/dist/src/commands/deploy.d.ts +19 -0
- package/dist/src/commands/deploy.d.ts.map +1 -1
- package/dist/src/commands/deploy.js +83 -142
- package/dist/src/commands/deploy.js.map +1 -1
- package/dist/src/commands/dev-doctor.d.ts +137 -0
- package/dist/src/commands/dev-doctor.d.ts.map +1 -0
- package/dist/src/commands/dev-doctor.js +671 -0
- package/dist/src/commands/dev-doctor.js.map +1 -0
- package/dist/src/commands/dev-launch.d.ts +59 -0
- package/dist/src/commands/dev-launch.d.ts.map +1 -0
- package/dist/src/commands/dev-launch.js +118 -0
- package/dist/src/commands/dev-launch.js.map +1 -0
- package/dist/src/commands/dev-token-server.d.ts +32 -0
- package/dist/src/commands/dev-token-server.d.ts.map +1 -0
- package/dist/src/commands/dev-token-server.js +68 -0
- package/dist/src/commands/dev-token-server.js.map +1 -0
- package/dist/src/commands/dev.d.ts +289 -2
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +2501 -574
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/docs.d.ts +3 -0
- package/dist/src/commands/docs.d.ts.map +1 -0
- package/dist/src/commands/docs.js +262 -0
- package/dist/src/commands/docs.js.map +1 -0
- package/dist/src/commands/hub/index.d.ts +99 -0
- package/dist/src/commands/hub/index.d.ts.map +1 -0
- package/dist/src/commands/hub/index.js +452 -0
- package/dist/src/commands/hub/index.js.map +1 -0
- package/dist/src/commands/init.d.ts +65 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +370 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/install-shim.d.ts +28 -0
- package/dist/src/commands/install-shim.d.ts.map +1 -0
- package/dist/src/commands/install-shim.js +191 -0
- package/dist/src/commands/install-shim.js.map +1 -0
- package/dist/src/commands/local/auth-provision.d.ts +71 -0
- package/dist/src/commands/local/auth-provision.d.ts.map +1 -0
- package/dist/src/commands/local/auth-provision.js +286 -0
- package/dist/src/commands/local/auth-provision.js.map +1 -0
- package/dist/src/commands/local/dev-local.d.ts +97 -0
- package/dist/src/commands/local/dev-local.d.ts.map +1 -0
- package/dist/src/commands/local/dev-local.js +306 -0
- package/dist/src/commands/local/dev-local.js.map +1 -0
- package/dist/src/commands/local/docker-runner.d.ts +37 -0
- package/dist/src/commands/local/docker-runner.d.ts.map +1 -0
- package/dist/src/commands/local/docker-runner.js +104 -0
- package/dist/src/commands/local/docker-runner.js.map +1 -0
- package/dist/src/commands/local/helpers.d.ts +22 -0
- package/dist/src/commands/local/helpers.d.ts.map +1 -0
- package/dist/src/commands/local/helpers.js +46 -0
- package/dist/src/commands/local/helpers.js.map +1 -0
- package/dist/src/commands/local/hub-local.d.ts +51 -0
- package/dist/src/commands/local/hub-local.d.ts.map +1 -0
- package/dist/src/commands/local/hub-local.js +197 -0
- package/dist/src/commands/local/hub-local.js.map +1 -0
- package/dist/src/commands/local/index.d.ts +23 -0
- package/dist/src/commands/local/index.d.ts.map +1 -0
- package/dist/src/commands/local/index.js +386 -0
- package/dist/src/commands/local/index.js.map +1 -0
- package/dist/src/commands/local/mocks.d.ts +361 -0
- package/dist/src/commands/local/mocks.d.ts.map +1 -0
- package/dist/src/commands/local/mocks.js +756 -0
- package/dist/src/commands/local/mocks.js.map +1 -0
- package/dist/src/commands/local/seed-zitadel.d.ts +151 -0
- package/dist/src/commands/local/seed-zitadel.d.ts.map +1 -0
- package/dist/src/commands/local/seed-zitadel.js +408 -0
- package/dist/src/commands/local/seed-zitadel.js.map +1 -0
- package/dist/src/commands/local/seed.d.ts +106 -0
- package/dist/src/commands/local/seed.d.ts.map +1 -0
- package/dist/src/commands/local/seed.js +252 -0
- package/dist/src/commands/local/seed.js.map +1 -0
- package/dist/src/commands/local/stack.d.ts +173 -0
- package/dist/src/commands/local/stack.d.ts.map +1 -0
- package/dist/src/commands/local/stack.js +499 -0
- package/dist/src/commands/local/stack.js.map +1 -0
- package/dist/src/commands/login.d.ts +256 -12
- package/dist/src/commands/login.d.ts.map +1 -1
- package/dist/src/commands/login.js +978 -50
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/commands/peer-addressing.d.ts +51 -0
- package/dist/src/commands/peer-addressing.d.ts.map +1 -0
- package/dist/src/commands/peer-addressing.js +64 -0
- package/dist/src/commands/peer-addressing.js.map +1 -0
- package/dist/src/commands/port-ownership.d.ts +69 -0
- package/dist/src/commands/port-ownership.d.ts.map +1 -0
- package/dist/src/commands/port-ownership.js +161 -0
- package/dist/src/commands/port-ownership.js.map +1 -0
- package/dist/src/commands/registry-publish.d.ts +3 -0
- package/dist/src/commands/registry-publish.d.ts.map +1 -0
- package/dist/src/commands/registry-publish.js +161 -0
- package/dist/src/commands/registry-publish.js.map +1 -0
- package/dist/src/commands/registry.d.ts +146 -2
- package/dist/src/commands/registry.d.ts.map +1 -1
- package/dist/src/commands/registry.js +541 -85
- package/dist/src/commands/registry.js.map +1 -1
- package/dist/src/commands/secrets/index.d.ts.map +1 -1
- package/dist/src/commands/secrets/index.js +15 -1
- package/dist/src/commands/secrets/index.js.map +1 -1
- package/dist/src/commands/secrets/migrate-config.d.ts +24 -0
- package/dist/src/commands/secrets/migrate-config.d.ts.map +1 -0
- package/dist/src/commands/secrets/migrate-config.js +277 -0
- package/dist/src/commands/secrets/migrate-config.js.map +1 -0
- package/dist/src/commands/secrets/set.d.ts +1 -0
- package/dist/src/commands/secrets/set.d.ts.map +1 -1
- package/dist/src/commands/secrets/set.js +96 -2
- package/dist/src/commands/secrets/set.js.map +1 -1
- package/dist/src/commands/skills.d.ts +34 -0
- package/dist/src/commands/skills.d.ts.map +1 -0
- package/dist/src/commands/skills.js +207 -0
- package/dist/src/commands/skills.js.map +1 -0
- package/dist/src/commands/stack.d.ts.map +1 -1
- package/dist/src/commands/stack.js +361 -23
- package/dist/src/commands/stack.js.map +1 -1
- package/dist/src/commands/temporal.d.ts +15 -0
- package/dist/src/commands/temporal.d.ts.map +1 -0
- package/dist/src/commands/temporal.js +781 -0
- package/dist/src/commands/temporal.js.map +1 -0
- package/dist/src/commands/tenant.d.ts +56 -0
- package/dist/src/commands/tenant.d.ts.map +1 -0
- package/dist/src/commands/tenant.js +233 -0
- package/dist/src/commands/tenant.js.map +1 -0
- package/dist/src/commands/tunnel/index.d.ts +49 -3
- package/dist/src/commands/tunnel/index.d.ts.map +1 -1
- package/dist/src/commands/tunnel/index.js +331 -8
- package/dist/src/commands/tunnel/index.js.map +1 -1
- package/dist/src/commands/vcs/clone.d.ts +3 -0
- package/dist/src/commands/vcs/clone.d.ts.map +1 -0
- package/dist/src/commands/vcs/clone.js +20 -0
- package/dist/src/commands/vcs/clone.js.map +1 -0
- package/dist/src/commands/vcs/common.d.ts +21 -0
- package/dist/src/commands/vcs/common.d.ts.map +1 -0
- package/dist/src/commands/vcs/common.js +62 -0
- package/dist/src/commands/vcs/common.js.map +1 -0
- package/dist/src/commands/vcs/drafts.d.ts +13 -0
- package/dist/src/commands/vcs/drafts.d.ts.map +1 -0
- package/dist/src/commands/vcs/drafts.js +45 -0
- package/dist/src/commands/vcs/drafts.js.map +1 -0
- package/dist/src/commands/vcs/get.d.ts +8 -0
- package/dist/src/commands/vcs/get.d.ts.map +1 -0
- package/dist/src/commands/vcs/get.js +61 -0
- package/dist/src/commands/vcs/get.js.map +1 -0
- package/dist/src/commands/vcs/index.d.ts +3 -0
- package/dist/src/commands/vcs/index.d.ts.map +1 -0
- package/dist/src/commands/vcs/index.js +58 -0
- package/dist/src/commands/vcs/index.js.map +1 -0
- package/dist/src/commands/vcs/propose.d.ts +23 -0
- package/dist/src/commands/vcs/propose.d.ts.map +1 -0
- package/dist/src/commands/vcs/propose.js +94 -0
- package/dist/src/commands/vcs/propose.js.map +1 -0
- package/dist/src/commands/vcs/review.d.ts +15 -0
- package/dist/src/commands/vcs/review.d.ts.map +1 -0
- package/dist/src/commands/vcs/review.js +35 -0
- package/dist/src/commands/vcs/review.js.map +1 -0
- package/dist/src/commands/vcs-enrich.d.ts +15 -0
- package/dist/src/commands/vcs-enrich.d.ts.map +1 -0
- package/dist/src/commands/vcs-enrich.js +28 -0
- package/dist/src/commands/vcs-enrich.js.map +1 -0
- package/dist/src/commands/vpn/index.d.ts +24 -0
- package/dist/src/commands/vpn/index.d.ts.map +1 -0
- package/dist/src/commands/vpn/index.js +543 -0
- package/dist/src/commands/vpn/index.js.map +1 -0
- package/dist/src/commands/vpn/tunnel.d.ts +11 -0
- package/dist/src/commands/vpn/tunnel.d.ts.map +1 -0
- package/dist/src/commands/vpn/tunnel.js +188 -0
- package/dist/src/commands/vpn/tunnel.js.map +1 -0
- package/dist/src/commands/workflow.d.ts +24 -0
- package/dist/src/commands/workflow.d.ts.map +1 -0
- package/dist/src/commands/workflow.js +577 -0
- package/dist/src/commands/workflow.js.map +1 -0
- package/dist/src/docs/assemble.d.ts +217 -0
- package/dist/src/docs/assemble.d.ts.map +1 -0
- package/dist/src/docs/assemble.js +739 -0
- package/dist/src/docs/assemble.js.map +1 -0
- package/dist/src/docs/cli-reference.d.ts +93 -0
- package/dist/src/docs/cli-reference.d.ts.map +1 -0
- package/dist/src/docs/cli-reference.js +256 -0
- package/dist/src/docs/cli-reference.js.map +1 -0
- package/dist/src/docs/discover.d.ts +60 -0
- package/dist/src/docs/discover.d.ts.map +1 -0
- package/dist/src/docs/discover.js +233 -0
- package/dist/src/docs/discover.js.map +1 -0
- package/dist/src/docs/portal.d.ts +79 -0
- package/dist/src/docs/portal.d.ts.map +1 -0
- package/dist/src/docs/portal.js +242 -0
- package/dist/src/docs/portal.js.map +1 -0
- package/dist/src/docs/registry-docs.d.ts +43 -0
- package/dist/src/docs/registry-docs.d.ts.map +1 -0
- package/dist/src/docs/registry-docs.js +138 -0
- package/dist/src/docs/registry-docs.js.map +1 -0
- package/dist/src/docs/schema.d.ts +48 -0
- package/dist/src/docs/schema.d.ts.map +1 -0
- package/dist/src/docs/schema.js +96 -0
- package/dist/src/docs/schema.js.map +1 -0
- package/dist/src/docs/serve.d.ts +27 -0
- package/dist/src/docs/serve.d.ts.map +1 -0
- package/dist/src/docs/serve.js +102 -0
- package/dist/src/docs/serve.js.map +1 -0
- package/dist/src/docs/start.d.ts +80 -0
- package/dist/src/docs/start.d.ts.map +1 -0
- package/dist/src/docs/start.js +313 -0
- package/dist/src/docs/start.js.map +1 -0
- package/dist/src/eval/agent-eval.d.mts +14 -0
- package/dist/src/eval/agent-eval.d.mts.map +1 -0
- package/dist/src/eval/agent-eval.mjs +273 -0
- package/dist/src/eval/agent-eval.mjs.map +1 -0
- package/dist/src/index.js +624 -7
- package/dist/src/index.js.map +7 -1
- package/dist/src/program.d.ts +26 -0
- package/dist/src/program.d.ts.map +1 -0
- package/dist/src/program.js +76 -0
- package/dist/src/program.js.map +1 -0
- package/dist/src/utils/auth-preflight.d.ts +159 -0
- package/dist/src/utils/auth-preflight.d.ts.map +1 -0
- package/dist/src/utils/auth-preflight.js +298 -0
- package/dist/src/utils/auth-preflight.js.map +1 -0
- package/dist/src/utils/aws-auth.d.ts +181 -0
- package/dist/src/utils/aws-auth.d.ts.map +1 -0
- package/dist/src/utils/aws-auth.js +527 -0
- package/dist/src/utils/aws-auth.js.map +1 -0
- package/dist/src/utils/bastion.d.ts +1 -1
- package/dist/src/utils/bastion.d.ts.map +1 -1
- package/dist/src/utils/bastion.js +29 -8
- package/dist/src/utils/bastion.js.map +1 -1
- package/dist/src/utils/build-info.d.ts +33 -0
- package/dist/src/utils/build-info.d.ts.map +1 -0
- package/dist/src/utils/build-info.js +102 -0
- package/dist/src/utils/build-info.js.map +1 -0
- package/dist/src/utils/capture-history.d.ts +62 -0
- package/dist/src/utils/capture-history.d.ts.map +1 -0
- package/dist/src/utils/capture-history.js +94 -0
- package/dist/src/utils/capture-history.js.map +1 -0
- package/dist/src/utils/deploy-preflight.d.ts +52 -0
- package/dist/src/utils/deploy-preflight.d.ts.map +1 -0
- package/dist/src/utils/deploy-preflight.js +183 -0
- package/dist/src/utils/deploy-preflight.js.map +1 -0
- package/dist/src/utils/errors.d.ts +75 -0
- package/dist/src/utils/errors.d.ts.map +1 -0
- package/dist/src/utils/errors.js +106 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/first-party-contexts.d.ts +40 -0
- package/dist/src/utils/first-party-contexts.d.ts.map +1 -0
- package/dist/src/utils/first-party-contexts.js +53 -0
- package/dist/src/utils/first-party-contexts.js.map +1 -0
- package/dist/src/utils/index.d.ts +2 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +2 -0
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/kubeconfig.d.ts +62 -0
- package/dist/src/utils/kubeconfig.d.ts.map +1 -0
- package/dist/src/utils/kubeconfig.js +118 -0
- package/dist/src/utils/kubeconfig.js.map +1 -0
- package/dist/src/utils/log.d.ts +27 -1
- package/dist/src/utils/log.d.ts.map +1 -1
- package/dist/src/utils/log.js +59 -5
- package/dist/src/utils/log.js.map +1 -1
- package/dist/src/utils/pid.d.ts +47 -0
- package/dist/src/utils/pid.d.ts.map +1 -0
- package/dist/src/utils/pid.js +83 -0
- package/dist/src/utils/pid.js.map +1 -0
- package/dist/src/utils/pulumi-run.d.ts +23 -0
- package/dist/src/utils/pulumi-run.d.ts.map +1 -0
- package/dist/src/utils/pulumi-run.js +112 -0
- package/dist/src/utils/pulumi-run.js.map +1 -0
- package/dist/src/utils/pulumi.d.ts +15 -0
- package/dist/src/utils/pulumi.d.ts.map +1 -1
- package/dist/src/utils/pulumi.js +35 -0
- package/dist/src/utils/pulumi.js.map +1 -1
- package/dist/src/utils/reachability.d.ts +29 -0
- package/dist/src/utils/reachability.d.ts.map +1 -0
- package/dist/src/utils/reachability.js +81 -0
- package/dist/src/utils/reachability.js.map +1 -0
- package/dist/src/utils/recover-conversation.d.ts +154 -0
- package/dist/src/utils/recover-conversation.d.ts.map +1 -0
- package/dist/src/utils/recover-conversation.js +314 -0
- package/dist/src/utils/recover-conversation.js.map +1 -0
- package/dist/src/utils/registry-broker.d.ts +148 -0
- package/dist/src/utils/registry-broker.d.ts.map +1 -0
- package/dist/src/utils/registry-broker.js +180 -0
- package/dist/src/utils/registry-broker.js.map +1 -0
- package/dist/src/utils/socks-forward.d.ts +40 -0
- package/dist/src/utils/socks-forward.d.ts.map +1 -0
- package/dist/src/utils/socks-forward.js +97 -0
- package/dist/src/utils/socks-forward.js.map +1 -0
- package/dist/src/utils/stack-flag.d.ts +23 -0
- package/dist/src/utils/stack-flag.d.ts.map +1 -0
- package/dist/src/utils/stack-flag.js +41 -0
- package/dist/src/utils/stack-flag.js.map +1 -0
- package/dist/src/utils/tailscale-targets.d.ts +52 -0
- package/dist/src/utils/tailscale-targets.d.ts.map +1 -0
- package/dist/src/utils/tailscale-targets.js +93 -0
- package/dist/src/utils/tailscale-targets.js.map +1 -0
- package/dist/src/utils/tailscale.d.ts +277 -0
- package/dist/src/utils/tailscale.d.ts.map +1 -0
- package/dist/src/utils/tailscale.js +1007 -0
- package/dist/src/utils/tailscale.js.map +1 -0
- package/dist/src/utils/temporal-auth.d.ts +43 -0
- package/dist/src/utils/temporal-auth.d.ts.map +1 -0
- package/dist/src/utils/temporal-auth.js +176 -0
- package/dist/src/utils/temporal-auth.js.map +1 -0
- package/dist/src/utils/temporal-codec.d.ts +101 -0
- package/dist/src/utils/temporal-codec.d.ts.map +1 -0
- package/dist/src/utils/temporal-codec.js +319 -0
- package/dist/src/utils/temporal-codec.js.map +1 -0
- package/dist/src/utils/tunnel-ownership.d.ts +130 -0
- package/dist/src/utils/tunnel-ownership.d.ts.map +1 -0
- package/dist/src/utils/tunnel-ownership.js +274 -0
- package/dist/src/utils/tunnel-ownership.js.map +1 -0
- package/dist/src/utils/vpn-join.d.ts +32 -0
- package/dist/src/utils/vpn-join.d.ts.map +1 -0
- package/dist/src/utils/vpn-join.js +62 -0
- package/dist/src/utils/vpn-join.js.map +1 -0
- package/dist/src/utils/vpn.d.ts +62 -0
- package/dist/src/utils/vpn.d.ts.map +1 -0
- package/dist/src/utils/vpn.js +183 -0
- package/dist/src/utils/vpn.js.map +1 -0
- package/dist/src/utils/workflow-fingerprint.d.ts +49 -0
- package/dist/src/utils/workflow-fingerprint.d.ts.map +1 -0
- package/dist/src/utils/workflow-fingerprint.js +135 -0
- package/dist/src/utils/workflow-fingerprint.js.map +1 -0
- package/dist/src/utils/worktree-identity.d.ts +86 -0
- package/dist/src/utils/worktree-identity.d.ts.map +1 -0
- package/dist/src/utils/worktree-identity.js +164 -0
- package/dist/src/utils/worktree-identity.js.map +1 -0
- package/fragments/agents/AGENTS.md.hbs +92 -0
- package/fragments/agents/CLAUDE.md.hbs +8 -0
- package/fragments/base/Pulumi.dev.yaml.hbs +10 -0
- package/fragments/base/Pulumi.yaml.hbs +9 -0
- package/fragments/base/index.ts.hbs +107 -0
- package/fragments/base/package.json.hbs +22 -0
- package/{templates/api-web-db/api → fragments/base}/tsconfig.json +3 -4
- package/fragments/bucket/api/src/storage.ts.hbs +81 -0
- package/fragments/database/api/src/db.ts.hbs +23 -0
- package/fragments/database/prisma/schema.prisma.hbs +18 -0
- package/{templates/api-web-db → fragments/service}/api/.dockerignore +2 -0
- package/fragments/service/api/package.json.hbs +40 -0
- package/fragments/service/api/src/index.ts.hbs +58 -0
- package/{templates/api-web-db → fragments/service/api}/tsconfig.json +5 -3
- package/fragments/temporal/worker/.dockerignore +4 -0
- package/fragments/temporal/worker/Dockerfile +15 -0
- package/{templates/temporal-api-worker → fragments/temporal}/worker/package.json.hbs +3 -2
- package/fragments/temporal/worker/src/activities.ts.hbs +14 -0
- package/fragments/temporal/worker/src/worker.ts.hbs +27 -0
- package/fragments/temporal/worker/src/workflows.ts.hbs +24 -0
- package/fragments/temporal/worker/tsconfig.json +13 -0
- package/package.json +36 -22
- package/skills/core/SKILL.md +365 -0
- package/skills/pull-remote-agent-context/SKILL.md +194 -0
- package/stack/docker-compose.hub.yml +134 -0
- package/stack/docker-compose.yml +447 -0
- package/stack/hub/Dockerfile.api +18 -0
- package/stack/hub/Dockerfile.auth +10 -0
- package/stack/hub/Dockerfile.ui +10 -0
- package/stack/init/01-databases.sql +12 -0
- package/stack/observability/blackbox.yml +20 -0
- package/stack/observability/otel-collector.yaml +73 -0
- package/stack/observability/prometheus.yml +36 -0
- package/stack/observability/tempo.yaml +25 -0
- package/stack/temporal/local.yaml +44 -0
- package/templates/api-auth/AGENTS.md.hbs +16 -0
- package/templates/api-auth/Pulumi.dev.yaml.hbs +8 -0
- package/templates/api-auth/Pulumi.yaml.hbs +8 -0
- package/templates/api-auth/README.md.hbs +67 -0
- package/templates/api-auth/api/package.json.hbs +28 -0
- package/templates/api-auth/api/src/index.ts.hbs +96 -0
- package/templates/api-auth/api/src/schema.ts.hbs +35 -0
- package/templates/api-auth/api/src/seed-authz.ts.hbs +48 -0
- package/templates/api-auth/api/src/spicedb-http-provider.ts.hbs +89 -0
- package/templates/api-auth/index.ts.hbs +65 -0
- package/templates/api-auth/package.json.hbs +20 -0
- package/templates/api-auth/skills/develop/SKILL.md.hbs +32 -0
- package/templates/api-role-gating/AGENTS.md.hbs +20 -0
- package/templates/api-role-gating/Pulumi.dev.yaml.hbs +8 -0
- package/templates/api-role-gating/Pulumi.yaml.hbs +8 -0
- package/templates/api-role-gating/README.md.hbs +64 -0
- package/templates/api-role-gating/api/Dockerfile +17 -0
- package/templates/api-role-gating/api/package.json.hbs +28 -0
- package/templates/api-role-gating/api/src/authn.ts.hbs +29 -0
- package/templates/api-role-gating/api/src/authz.ts.hbs +12 -0
- package/templates/api-role-gating/api/src/main.ts.hbs +58 -0
- package/templates/api-role-gating/api/src/schema.ts.hbs +22 -0
- package/templates/api-role-gating/api/tsconfig.json +13 -0
- package/templates/api-role-gating/index.ts.hbs +107 -0
- package/templates/api-role-gating/package.json.hbs +21 -0
- package/templates/api-role-gating/skills/develop/SKILL.md.hbs +60 -0
- package/templates/api-role-gating/tsconfig.json +10 -0
- package/templates/apps-repo/README.md.hbs +24 -0
- package/templates/apps-repo/gitignore +41 -0
- package/templates/apps-repo/npmrc.hbs +7 -0
- package/templates/apps-repo/package.json.hbs +15 -0
- package/templates/apps-repo/pnpm-workspace.yaml +3 -0
- package/templates/apps-repo/tsconfig.json +18 -0
- package/templates/external-service/AGENTS.md.hbs +20 -0
- package/templates/external-service/Pulumi.dev.yaml.hbs +8 -0
- package/templates/external-service/Pulumi.yaml.hbs +8 -0
- package/templates/external-service/README.md.hbs +56 -0
- package/templates/external-service/api/Dockerfile +15 -0
- package/templates/external-service/api/package.json.hbs +25 -0
- package/templates/external-service/api/src/index.ts.hbs +81 -0
- package/templates/external-service/index.ts.hbs +82 -0
- package/templates/external-service/mocks/vendorpay/package.json.hbs +9 -0
- package/templates/external-service/mocks/vendorpay/server.mjs.hbs +61 -0
- package/templates/external-service/package.json.hbs +34 -0
- package/templates/external-service/skills/develop/SKILL.md.hbs +55 -0
- package/templates/external-service/tsconfig.json +10 -0
- package/templates/platform-repo/AGENTS.md.hbs +18 -0
- package/templates/platform-repo/README.md.hbs +38 -0
- package/templates/platform-repo/core/Pulumi.yaml.hbs +12 -0
- package/templates/platform-repo/core/Pulumi.{{tenant}}-{{env}}.yaml.hbs +57 -0
- package/templates/platform-repo/core/index.ts +62 -0
- package/templates/platform-repo/core/package.json.hbs +22 -0
- package/templates/platform-repo/core/tsconfig.json +8 -0
- package/templates/platform-repo/package.json.hbs +15 -0
- package/templates/platform-repo/platform/Pulumi.yaml.hbs +12 -0
- package/templates/platform-repo/platform/Pulumi.{{tenant}}-{{env}}.yaml.hbs +59 -0
- package/templates/platform-repo/platform/index.ts +95 -0
- package/templates/platform-repo/platform/package.json.hbs +26 -0
- package/templates/platform-repo/platform/tsconfig.json +8 -0
- package/templates/platform-repo/pnpm-workspace.yaml +3 -0
- package/templates/workflow/AGENTS.md.hbs +15 -0
- package/templates/workflow/Pulumi.dev.yaml.hbs +8 -0
- package/templates/workflow/Pulumi.yaml.hbs +8 -0
- package/templates/workflow/README.md.hbs +45 -0
- package/templates/workflow/api/.dockerignore +4 -0
- package/templates/workflow/api/Dockerfile +15 -0
- package/templates/{temporal-api-worker → workflow}/api/package.json.hbs +8 -6
- package/templates/workflow/api/src/index.ts.hbs +130 -0
- package/templates/workflow/api/tsconfig.json +14 -0
- package/templates/workflow/index.ts.hbs +67 -0
- package/templates/workflow/package.json.hbs +20 -0
- package/templates/workflow/skills/develop/SKILL.md.hbs +32 -0
- package/templates/workflow/tsconfig.json +10 -0
- package/templates/workflow/worker/.dockerignore +4 -0
- package/templates/workflow/worker/Dockerfile +13 -0
- package/templates/workflow/worker/package.json.hbs +25 -0
- package/templates/workflow/worker/src/worker.ts.hbs +24 -0
- package/templates/workflow/worker/tsconfig.json +14 -0
- package/dist/src/commands/devbox.d.ts +0 -22
- package/dist/src/commands/devbox.d.ts.map +0 -1
- package/dist/src/commands/devbox.js +0 -463
- package/dist/src/commands/devbox.js.map +0 -1
- package/templates/api-web-db/api/Dockerfile +0 -16
- package/templates/api-web-db/api/package.json.hbs +0 -21
- package/templates/api-web-db/api/src/index.ts.hbs +0 -123
- package/templates/api-web-db/package.json.hbs +0 -27
- package/templates/api-web-db/sst.config.ts.hbs +0 -151
- package/templates/api-web-db/web/.dockerignore +0 -3
- package/templates/api-web-db/web/Dockerfile +0 -17
- package/templates/api-web-db/web/app/lib/db.server.ts +0 -36
- package/templates/api-web-db/web/app/root.tsx +0 -29
- package/templates/api-web-db/web/app/routes/home.tsx.hbs +0 -114
- package/templates/api-web-db/web/app/routes.ts +0 -3
- package/templates/api-web-db/web/package.json.hbs +0 -30
- package/templates/api-web-db/web/prisma/schema.prisma +0 -25
- package/templates/api-web-db/web/prisma.config.ts +0 -18
- package/templates/api-web-db/web/react-router.config.ts +0 -5
- package/templates/api-web-db/web/tsconfig.json +0 -19
- package/templates/api-web-db/web/vite.config.ts +0 -9
- package/templates/temporal-api-worker/README.md.hbs +0 -133
- package/templates/temporal-api-worker/api/src/index.ts.hbs +0 -145
- package/templates/temporal-api-worker/package.json.hbs +0 -29
- package/templates/temporal-api-worker/sst.config.ts.hbs +0 -161
- package/templates/temporal-api-worker/worker/Dockerfile +0 -11
- package/templates/temporal-api-worker/worker/src/bundle-workflows.ts +0 -38
- package/templates/temporal-api-worker/worker/src/worker.ts.hbs +0 -93
- /package/templates/{temporal-api-worker → api-auth}/api/.dockerignore +0 -0
- /package/templates/{temporal-api-worker → api-auth}/api/Dockerfile +0 -0
- /package/templates/{temporal-api-worker → api-auth}/api/tsconfig.json +0 -0
- /package/templates/{temporal-api-worker → api-auth}/tsconfig.json +0 -0
- /package/templates/{temporal-api-worker/worker → api-role-gating/api}/.dockerignore +0 -0
- /package/templates/{temporal-api-worker/worker → external-service/api}/tsconfig.json +0 -0
- /package/templates/{temporal-api-worker → workflow}/api/src/encryption-codec.ts +0 -0
- /package/templates/{temporal-api-worker → workflow}/worker/src/activities.ts.hbs +0 -0
- /package/templates/{temporal-api-worker → workflow}/worker/src/encryption-codec.ts +0 -0
- /package/templates/{temporal-api-worker → workflow}/worker/src/workflows.ts.hbs +0 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mesh dev --externals` (alias `--mock`) — external-service realizations
|
|
3
|
+
* for a local dev session (MESH-1885, design C4).
|
|
4
|
+
*
|
|
5
|
+
* Apps DECLARE how each external dependency is realized locally in the root
|
|
6
|
+
* package.json under `mesh.externals` (legacy key `mesh.mocks` still read).
|
|
7
|
+
* Every declaration runs in exactly one of three MODES:
|
|
8
|
+
*
|
|
9
|
+
* mock — the service is MIMICKED: no real instance exists locally or
|
|
10
|
+
* remotely. Realized from an OpenAPI spec (`openapi` → Prism
|
|
11
|
+
* mock server) or a hand-written mock process (`src`).
|
|
12
|
+
* local — a LOCAL VERSION of the real service runs on this machine,
|
|
13
|
+
* e.g. a vendor-shaped database replica. Realized from a docker
|
|
14
|
+
* compose file (`compose` + published `port`).
|
|
15
|
+
* remote — the session connects to the ACTUAL external service: either
|
|
16
|
+
* the vendor's real endpoint with inline `credentials` (e.g. a
|
|
17
|
+
* sandbox), or the external service as configured in the app's
|
|
18
|
+
* tenant environment (`remote.env`, e.g. dev — credentials are
|
|
19
|
+
* pulled from that environment's secret).
|
|
20
|
+
*
|
|
21
|
+
* "mesh": {
|
|
22
|
+
* "externals": {
|
|
23
|
+
* "plaid-db": { // ── local (default) ──
|
|
24
|
+
* "mode": "local",
|
|
25
|
+
* "compose": "libs/plaid-db/docker/docker-compose.yml",
|
|
26
|
+
* "port": 1433, // REQUIRED: the published port
|
|
27
|
+
* "probe": "tcp", // non-HTTP uptime probe
|
|
28
|
+
* "external": "plaid-db",
|
|
29
|
+
* "credentials": { // {{host}}/{{port}} → localhost:1433
|
|
30
|
+
* "host": "{{host}}", "port": "{{port}}",
|
|
31
|
+
* "name": "Plaid_Clean", "username": "sa",
|
|
32
|
+
* "password": "{{env:PLAID_DB_PASSWORD}}"
|
|
33
|
+
* },
|
|
34
|
+
* "remote": { "env": "dev" } // `--externals plaid-db=remote`
|
|
35
|
+
* }, // pulls the dev-configured secret
|
|
36
|
+
* "plaid": { // ── remote ──
|
|
37
|
+
* "mode": "remote",
|
|
38
|
+
* "external": "plaid", // the vendor's REAL sandbox
|
|
39
|
+
* "displayName": "Plaid", // the Hub shows the real name…
|
|
40
|
+
* "type": "api", // …and the integration type
|
|
41
|
+
* "docs": "https://plaid.com/docs/api/", // vendor reference docs
|
|
42
|
+
* "credentials": {
|
|
43
|
+
* "endpoint": "https://sandbox.plaid.com",
|
|
44
|
+
* "client_id": "{{env:PLAID_CLIENT_ID}}",
|
|
45
|
+
* "secret": "{{env:PLAID_SECRET}}"
|
|
46
|
+
* }
|
|
47
|
+
* },
|
|
48
|
+
* "vendorpay": { // ── mock ──
|
|
49
|
+
* "mode": "mock",
|
|
50
|
+
* "openapi": "api/docs/spec/openapi.json", // spec → Prism emulation
|
|
51
|
+
* "external": "vendorpay",
|
|
52
|
+
* "credentials": { "baseUrl": "{{url}}", "apiKey": "mock" }
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* `mode` picks the DEFAULT; a declaration may carry more than one
|
|
58
|
+
* realization (e.g. compose + remote.env) and the CLI overrides per
|
|
59
|
+
* external at run time: `mesh dev --externals plaid-db=remote,plaid`.
|
|
60
|
+
* Declarations without `mode` infer it from their fields (compose → local,
|
|
61
|
+
* openapi/src → mock, sandbox/credentials-only → remote), so pre-mode
|
|
62
|
+
* declarations keep working (`sandbox: true` is a deprecated alias for
|
|
63
|
+
* mode remote).
|
|
64
|
+
*
|
|
65
|
+
* All credential values support `{{env:VAR}}` interpolation from the
|
|
66
|
+
* developer's environment, so real secrets (sandbox keys, DB passwords)
|
|
67
|
+
* never land in package.json.
|
|
68
|
+
*
|
|
69
|
+
* Realization: mock externals join the SAME orchestration as services named
|
|
70
|
+
* mock-{name} (ports, env files, tmux windows, Loki log pipeline); local
|
|
71
|
+
* externals come up via `docker compose up -d --wait` before the app's
|
|
72
|
+
* services and are torn down on `mesh dev --kill`; remote externals spawn
|
|
73
|
+
* nothing. In every mode the ExternalService credential secret is seeded at
|
|
74
|
+
* mesh/{tenant}/{env}/external/{name} so the app's resolveCredentials()
|
|
75
|
+
* path runs UNCHANGED, the vendor is registered in the local Hub's
|
|
76
|
+
* external-services view, and a blackbox uptime probe watches the
|
|
77
|
+
* realization — remote endpoints included.
|
|
78
|
+
*/
|
|
79
|
+
import { execFileSync } from "child_process";
|
|
80
|
+
import * as fs from "fs";
|
|
81
|
+
import * as path from "path";
|
|
82
|
+
import { logInfo, logSuccess, logWarn } from "../../utils/log.js";
|
|
83
|
+
import { MeshCliError } from "../../utils/errors.js";
|
|
84
|
+
import { LOCAL_ENV, LOCAL_AWS_CONFIG } from "./seed.js";
|
|
85
|
+
import { probeTcp, upsertLocalSecret } from "./helpers.js";
|
|
86
|
+
import { localProbesDir } from "./stack.js";
|
|
87
|
+
const EXTERNAL_MODES = ["mock", "local", "remote"];
|
|
88
|
+
const DECL_DOCS = { docs: 'package.json → "mesh": { "externals": { … } }' };
|
|
89
|
+
/**
|
|
90
|
+
* Resolve + validate an external's mode: explicit `mode` (or the one
|
|
91
|
+
* inferred from the realization fields), checked against the fields that
|
|
92
|
+
* mode needs. Pre-mode declarations infer: compose → local, openapi/src →
|
|
93
|
+
* mock, sandbox/credentials-only → remote.
|
|
94
|
+
*/
|
|
95
|
+
export function externalMode(name, decl) {
|
|
96
|
+
const mode = decl.mode ??
|
|
97
|
+
(decl.compose
|
|
98
|
+
? "local"
|
|
99
|
+
: decl.openapi || decl.src
|
|
100
|
+
? "mock"
|
|
101
|
+
: decl.sandbox || decl.credentials || decl.remote
|
|
102
|
+
? "remote"
|
|
103
|
+
: undefined);
|
|
104
|
+
if (!mode || !EXTERNAL_MODES.includes(mode)) {
|
|
105
|
+
throw new MeshCliError(`External '${name}': cannot determine mode — set "mode" to mock | local | remote (or declare openapi/src, compose, or credentials).`, { remediation: DECL_DOCS });
|
|
106
|
+
}
|
|
107
|
+
if (mode === "local") {
|
|
108
|
+
if (!decl.compose) {
|
|
109
|
+
throw new MeshCliError(`External '${name}': local mode requires "compose" — the docker compose file running the local version of the service.`, { remediation: DECL_DOCS });
|
|
110
|
+
}
|
|
111
|
+
if (typeof decl.port !== "number") {
|
|
112
|
+
throw new MeshCliError(`External '${name}': local (compose) mode requires "port" — the port the compose file publishes on localhost.`, { remediation: DECL_DOCS });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (mode === "mock" && !decl.openapi && !decl.src) {
|
|
116
|
+
throw new MeshCliError(`External '${name}': mock mode emulates the service — declare "openapi" (spec → Prism mock) or "src" (mock process).`, { remediation: DECL_DOCS });
|
|
117
|
+
}
|
|
118
|
+
if (mode === "remote" && !remoteInlineCredentials(decl) && !decl.external) {
|
|
119
|
+
throw new MeshCliError(`External '${name}': remote mode needs a credential source — inline "credentials", "remote": { "env": … }, or "external" (pulls that environment's configured secret).`, { remediation: DECL_DOCS });
|
|
120
|
+
}
|
|
121
|
+
return mode;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse a `--externals` value — a comma-separated list of `name` or
|
|
125
|
+
* `name=mode` entries. An explicit mode overrides that declaration's
|
|
126
|
+
* default FOR THIS RUN; a bare name keeps the declared default.
|
|
127
|
+
*
|
|
128
|
+
* Names are returned in the order typed, de-duplicated (the last mode
|
|
129
|
+
* given for a name wins, so `--externals plaid,plaid=remote` is remote).
|
|
130
|
+
*/
|
|
131
|
+
export function parseExternalsSelection(input) {
|
|
132
|
+
const names = [];
|
|
133
|
+
const overrides = new Map();
|
|
134
|
+
for (const entry of input.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
135
|
+
const [rawName, mode, ...rest] = entry.split("=").map((s) => s.trim());
|
|
136
|
+
const name = rawName ?? "";
|
|
137
|
+
if (!name) {
|
|
138
|
+
throw new MeshCliError(`Bad --externals entry '${entry}' — expected name or name=mode with mode ∈ ${EXTERNAL_MODES.join(" | ")}.`, { remediation: { command: "mesh dev --externals plaid-db=remote,plaid" } });
|
|
139
|
+
}
|
|
140
|
+
if (mode !== undefined) {
|
|
141
|
+
if (rest.length > 0 || !EXTERNAL_MODES.includes(mode)) {
|
|
142
|
+
throw new MeshCliError(`Bad --externals entry '${entry}' — expected name or name=mode with mode ∈ ${EXTERNAL_MODES.join(" | ")}.`, { remediation: { command: "mesh dev --externals plaid-db=remote,plaid" } });
|
|
143
|
+
}
|
|
144
|
+
overrides.set(name, mode);
|
|
145
|
+
}
|
|
146
|
+
if (!names.includes(name))
|
|
147
|
+
names.push(name);
|
|
148
|
+
}
|
|
149
|
+
return { names, overrides };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The inline credential template a remote-mode run should use, if any:
|
|
153
|
+
* `remote.credentials` always wins; top-level `credentials` count only when
|
|
154
|
+
* the declaration has no local/mock realization (otherwise they are that
|
|
155
|
+
* realization's template, full of {{host}}/{{port}} tokens that only mean
|
|
156
|
+
* something against a local endpoint).
|
|
157
|
+
*/
|
|
158
|
+
export function remoteInlineCredentials(decl) {
|
|
159
|
+
if (decl.remote?.credentials)
|
|
160
|
+
return decl.remote.credentials;
|
|
161
|
+
if (decl.credentials && !decl.compose && !decl.openapi && !decl.src)
|
|
162
|
+
return decl.credentials;
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
/** Whether this declaration runs as a mock-{name} service in the dev
|
|
166
|
+
* orchestration (mock mode) vs outside it (local compose / remote). */
|
|
167
|
+
export function isServiceMode(name, decl) {
|
|
168
|
+
return externalMode(name, decl) === "mock";
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Interpolate one credential value: {{url}}/{{host}}/{{port}} from the
|
|
172
|
+
* stand-in's endpoint, {{env:VAR}} from the developer's environment
|
|
173
|
+
* (missing env vars are a hard error — a half-seeded secret is worse).
|
|
174
|
+
*/
|
|
175
|
+
export function interpolateCredentialValue(raw, endpoint, env = process.env) {
|
|
176
|
+
let value = raw.replace(/\{\{env:([A-Za-z_][A-Za-z0-9_]*)\}\}/g, (_, name) => {
|
|
177
|
+
const resolved = env[name];
|
|
178
|
+
if (resolved === undefined) {
|
|
179
|
+
throw new MeshCliError(`Credential value references {{env:${name}}} but ${name} is not set in your environment.`, { remediation: { command: `export ${name}=… # then re-run mesh dev` } });
|
|
180
|
+
}
|
|
181
|
+
return resolved;
|
|
182
|
+
});
|
|
183
|
+
if (endpoint) {
|
|
184
|
+
value = value
|
|
185
|
+
.replaceAll("{{url}}", endpoint.url)
|
|
186
|
+
.replaceAll("{{host}}", endpoint.host)
|
|
187
|
+
.replaceAll("{{port}}", String(endpoint.port));
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
/** The Prism mock command for an openapi-mode declaration. `$PORT` expands
|
|
192
|
+
* in the service's launch shell (tmux and docker runners both source the
|
|
193
|
+
* env file), so port reallocation keeps working. */
|
|
194
|
+
export function openapiMockCommand(decl) {
|
|
195
|
+
return (decl.command ?? [
|
|
196
|
+
"npx",
|
|
197
|
+
"-y",
|
|
198
|
+
"@stoplight/prism-cli@5",
|
|
199
|
+
"mock",
|
|
200
|
+
"-p",
|
|
201
|
+
"$PORT",
|
|
202
|
+
"-h",
|
|
203
|
+
"0.0.0.0",
|
|
204
|
+
decl.openapi,
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
/** Compose-mode project name — pinned so `--kill` can tear down exactly
|
|
208
|
+
* what `up` created, regardless of the compose file's own `name:`. */
|
|
209
|
+
export function composeProjectName(sessionName, mockName) {
|
|
210
|
+
return `mesh-ext-${sessionName}-${mockName}`.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
211
|
+
}
|
|
212
|
+
/** Wait until the external's published port accepts TCP (bounded). */
|
|
213
|
+
async function waitForPort(port, timeoutMs) {
|
|
214
|
+
const deadline = Date.now() + timeoutMs;
|
|
215
|
+
while (Date.now() < deadline) {
|
|
216
|
+
if (await probeTcp(port, { timeoutMs: 2000 }))
|
|
217
|
+
return true;
|
|
218
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Decide how to realize a compose external, given what already exists.
|
|
224
|
+
*
|
|
225
|
+
* OWNERSHIP FIRST: a port probe can't tell WHO serves the port, and the
|
|
226
|
+
* ordinary flow re-runs `mesh dev` against a session whose compose externals
|
|
227
|
+
* are deliberately left running. Adopting there would drop the ref from
|
|
228
|
+
* session state and permanently orphan the checkout's own container, so our
|
|
229
|
+
* own realization takes the normal `up` path (idempotent, ref re-recorded)
|
|
230
|
+
* and only a foreign one is adopted.
|
|
231
|
+
*
|
|
232
|
+
* IDENTITY SECOND: a foreign publisher is only adopted when it is a service
|
|
233
|
+
* THIS compose file defines. Well-known ports (1433/5432/22) are exactly
|
|
234
|
+
* where a native install or an unrelated container turns up, and adopting one
|
|
235
|
+
* silently seeds the app's credentials against the wrong database.
|
|
236
|
+
*/
|
|
237
|
+
export async function planComposeExternal(composeFile, project, port, probes) {
|
|
238
|
+
if (probes.ownsRunning(composeFile, project))
|
|
239
|
+
return { action: "up" };
|
|
240
|
+
if (await probes.portServed(port)) {
|
|
241
|
+
const publisher = probes.portPublisher(composeFile, port);
|
|
242
|
+
if (!publisher || !publisher.definedByFile) {
|
|
243
|
+
return { action: "conflict", ...(publisher ? { container: publisher.container } : {}) };
|
|
244
|
+
}
|
|
245
|
+
return { action: "adopt-served", container: publisher.container };
|
|
246
|
+
}
|
|
247
|
+
// Pinned container_name held by a STOPPED container from another checkout/
|
|
248
|
+
// project would make `up` fail with a name conflict — start that container
|
|
249
|
+
// instead. (A RUNNING one is caught by the port probe above.)
|
|
250
|
+
const foreign = probes.foreignPinned(composeFile, project);
|
|
251
|
+
if (foreign)
|
|
252
|
+
return { action: "adopt-stopped", container: foreign };
|
|
253
|
+
return { action: "up" };
|
|
254
|
+
}
|
|
255
|
+
/** Real docker/network probes — replaced in tests. */
|
|
256
|
+
const dockerProbes = {
|
|
257
|
+
ownsRunning: ownsRunningRealization,
|
|
258
|
+
portServed: (port) => probeTcp(port, { timeoutMs: 2000 }),
|
|
259
|
+
foreignPinned: foreignPinnedContainer,
|
|
260
|
+
portPublisher: composeFilePortPublisher,
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Bring a compose-mode external up (idempotent; waits for healthchecks —
|
|
264
|
+
* vendor DBs restoring seed data can take a while, hence the timeout).
|
|
265
|
+
*
|
|
266
|
+
* ADOPTION: the same external is often realized by more than one checkout —
|
|
267
|
+
* e.g. a vendor-DB compose ported from a legacy repo pins `container_name`
|
|
268
|
+
* and a host port, so whichever checkout created it first blocks every other
|
|
269
|
+
* `docker compose up` (name + port conflicts). An external service is by
|
|
270
|
+
* definition outside the app, so if ANOTHER checkout already serves its
|
|
271
|
+
* published port — or left a stopped container holding the pinned name —
|
|
272
|
+
* this ADOPTS that realization instead of failing: skip/`docker start`, wait
|
|
273
|
+
* for the port, and return undefined so `--kill` never tears down what
|
|
274
|
+
* another checkout owns. Our OWN realization is never adopted (see
|
|
275
|
+
* `planComposeExternal`) — it stays recorded so `--kill` still cleans it up,
|
|
276
|
+
* and a publisher this compose file doesn't define is never adopted either
|
|
277
|
+
* (it would seed the app's credentials against the wrong service).
|
|
278
|
+
*/
|
|
279
|
+
export async function composeExternalUp(appRoot, sessionName, name, decl, probes = dockerProbes) {
|
|
280
|
+
const composeFile = path.resolve(appRoot, decl.compose);
|
|
281
|
+
if (!fs.existsSync(composeFile)) {
|
|
282
|
+
throw new MeshCliError(`External '${name}': compose file not found at ${composeFile}.`, {
|
|
283
|
+
remediation: { docs: 'package.json → "mesh": { "externals": { … } }' },
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const project = composeProjectName(sessionName, name);
|
|
287
|
+
const plan = await planComposeExternal(composeFile, project, decl.port, probes);
|
|
288
|
+
if (plan.action === "conflict") {
|
|
289
|
+
throw new MeshCliError(`External '${name}': localhost:${decl.port} is already served by ${plan.container ? `container '${plan.container}'` : "a process outside docker"}, which ${decl.compose} does not define — refusing to seed '${name}' credentials against it.`, {
|
|
290
|
+
remediation: {
|
|
291
|
+
command: `docker ps --filter publish=${decl.port} # stop it, or change the declared port`,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (plan.action === "adopt-served") {
|
|
296
|
+
logInfo(`External '${name}' already served on localhost:${decl.port} by container '${plan.container}' — adopting it (started outside this checkout; \`mesh dev --kill\` won't touch it).`);
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
if (plan.action === "adopt-stopped") {
|
|
300
|
+
const { container } = plan;
|
|
301
|
+
logInfo(`External '${name}': container '${container}' exists from another checkout — starting and adopting it.`);
|
|
302
|
+
execFileSync("docker", ["start", container], { stdio: ["ignore", "ignore", "inherit"] });
|
|
303
|
+
if (!(await waitForPort(decl.port, 300_000))) {
|
|
304
|
+
throw new MeshCliError(`External '${name}': adopted container '${container}' never served localhost:${decl.port}.`, { remediation: { command: `docker logs ${container} # then: docker rm -f ${container} and re-run` } });
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
execFileSync("docker", ["compose", "-p", project, "-f", composeFile, "up", "-d", "--wait", "--wait-timeout", "300"], { stdio: ["ignore", "inherit", "inherit"] });
|
|
309
|
+
return { name, composeFile, project };
|
|
310
|
+
}
|
|
311
|
+
/** Whether OUR compose project already has a container running for this file.
|
|
312
|
+
* `docker compose ps` (running only) covers services WITHOUT a pinned
|
|
313
|
+
* `container_name` too, which the label-inspect path below structurally
|
|
314
|
+
* cannot — an unpinned service of ours is just `<project>-<svc>-1`. */
|
|
315
|
+
function ownsRunningRealization(composeFile, project) {
|
|
316
|
+
try {
|
|
317
|
+
const out = execFileSync("docker", ["compose", "-p", project, "-f", composeFile, "ps", "--format", "json"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
318
|
+
// Compose emits either a JSON array or NDJSON depending on version;
|
|
319
|
+
// "has any row" is all we need from it.
|
|
320
|
+
return out.length > 0 && out !== "[]";
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// ps hiccup — treat as "not ours" and let the normal paths decide.
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/** The compose file's rendered service map, or undefined when it can't be
|
|
328
|
+
* rendered (docker missing / bad file — callers decide what that means). */
|
|
329
|
+
function composeServices(composeFile) {
|
|
330
|
+
try {
|
|
331
|
+
const config = JSON.parse(execFileSync("docker", ["compose", "-f", composeFile, "config", "--format", "json"], {
|
|
332
|
+
encoding: "utf-8",
|
|
333
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
334
|
+
}));
|
|
335
|
+
const services = config?.services;
|
|
336
|
+
return services && typeof services === "object" ? services : undefined;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function portPublisher(port) {
|
|
343
|
+
try {
|
|
344
|
+
const out = execFileSync("docker", [
|
|
345
|
+
"ps",
|
|
346
|
+
"--filter",
|
|
347
|
+
`publish=${port}`,
|
|
348
|
+
"--format",
|
|
349
|
+
'{{.Names}}\t{{.Image}}\t{{.Label "com.docker.compose.project"}}\t{{.Label "com.docker.compose.service"}}',
|
|
350
|
+
], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
351
|
+
const line = out
|
|
352
|
+
.split("\n")
|
|
353
|
+
.map((l) => l.trim())
|
|
354
|
+
.filter(Boolean)[0];
|
|
355
|
+
if (!line)
|
|
356
|
+
return undefined;
|
|
357
|
+
const [container, image, project, service] = line.split("\t");
|
|
358
|
+
if (!container)
|
|
359
|
+
return undefined;
|
|
360
|
+
return {
|
|
361
|
+
container,
|
|
362
|
+
image: image || undefined,
|
|
363
|
+
project: project || undefined,
|
|
364
|
+
service: service || undefined,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/** Whether the container publishing the declared port is a service THIS
|
|
372
|
+
* compose file defines — the evidence adoption requires. Matches on the
|
|
373
|
+
* pinned `container_name`, or on the compose service name (cross-checked
|
|
374
|
+
* against the image when both sides declare one, so an unrelated project's
|
|
375
|
+
* service that happens to be called `db` isn't mistaken for ours). */
|
|
376
|
+
function composeFileOwns(composeFile, publisher) {
|
|
377
|
+
if (!publisher)
|
|
378
|
+
return false;
|
|
379
|
+
const services = composeServices(composeFile);
|
|
380
|
+
if (!services)
|
|
381
|
+
return false; // no identity evidence — don't adopt
|
|
382
|
+
for (const [key, svc] of Object.entries(services)) {
|
|
383
|
+
if (svc?.container_name && svc.container_name === publisher.container)
|
|
384
|
+
return true;
|
|
385
|
+
if (publisher.service && publisher.service === key) {
|
|
386
|
+
if (!svc?.image || !publisher.image || svc.image === publisher.image)
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
/** `ComposeExternalProbes.portPublisher` — who serves the declared port, and
|
|
393
|
+
* whether this compose file defines it. Undefined when docker publishes
|
|
394
|
+
* nothing there (a native process on the port, or no docker at all), which
|
|
395
|
+
* `planComposeExternal` treats as a conflict rather than an adoption. */
|
|
396
|
+
function composeFilePortPublisher(composeFile, port) {
|
|
397
|
+
const publisher = portPublisher(port);
|
|
398
|
+
if (!publisher)
|
|
399
|
+
return undefined;
|
|
400
|
+
return { container: publisher.container, definedByFile: composeFileOwns(composeFile, publisher) };
|
|
401
|
+
}
|
|
402
|
+
/** Find a container holding a `container_name` this compose file pins, when
|
|
403
|
+
* it belongs to a different compose project (another checkout's realization
|
|
404
|
+
* of the same external). Returns the container name, or undefined. */
|
|
405
|
+
function foreignPinnedContainer(composeFile, project) {
|
|
406
|
+
// config render hiccup — fall through to the normal `up`, which reports
|
|
407
|
+
// real errors itself.
|
|
408
|
+
for (const svc of Object.values(composeServices(composeFile) ?? {})) {
|
|
409
|
+
const pinned = svc?.container_name;
|
|
410
|
+
if (!pinned)
|
|
411
|
+
continue;
|
|
412
|
+
try {
|
|
413
|
+
const owner = execFileSync("docker", ["inspect", pinned, "--format", '{{ index .Config.Labels "com.docker.compose.project" }}'], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
414
|
+
if (owner !== project)
|
|
415
|
+
return pinned;
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// no container by that name — nothing to adopt for this service
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
/** Tear down compose-mode externals recorded in session state. Volumes are
|
|
424
|
+
* kept (vendor DB data survives sessions, like the cloud external it
|
|
425
|
+
* stands in for); best-effort per project. */
|
|
426
|
+
export function composeExternalsDown(refs) {
|
|
427
|
+
for (const ref of refs ?? []) {
|
|
428
|
+
try {
|
|
429
|
+
execFileSync("docker", ["compose", "-p", ref.project, "-f", ref.composeFile, "down", "--remove-orphans"], {
|
|
430
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
/* best effort — the file may be gone or docker down */
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/** Same heuristic the schema seed uses: which credential keys are secret. */
|
|
439
|
+
const SECRET_KEY_RE = /key|secret|password|token/i;
|
|
440
|
+
/** Read the app's declared externals (root package.json → mesh.externals;
|
|
441
|
+
* legacy key mesh.mocks still read, externals wins on name collision). */
|
|
442
|
+
export function readLocalMocks(appRoot) {
|
|
443
|
+
try {
|
|
444
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(appRoot, "package.json"), "utf-8"));
|
|
445
|
+
const mocks = pkg?.mesh?.mocks;
|
|
446
|
+
const externals = pkg?.mesh?.externals;
|
|
447
|
+
return {
|
|
448
|
+
...(mocks && typeof mocks === "object" ? mocks : {}),
|
|
449
|
+
...(externals && typeof externals === "object" ? externals : {}),
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return {};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
export function externalSecretPath(tenant, external) {
|
|
457
|
+
return `mesh/${tenant}/${LOCAL_ENV}/external/${external}`;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Pull an external service's credentials as configured in the app's tenant
|
|
461
|
+
* environment (the CLOUD platform, not the local fabric) — secret
|
|
462
|
+
* mesh/{tenant}/{env}/external/{name}, read with the developer's ambient
|
|
463
|
+
* AWS credentials (AWS_PROFILE / SSO session for that tenant's account).
|
|
464
|
+
* When the shell carries no AWS identity at all, `profile` (from the
|
|
465
|
+
* declaration; MESH_AWS_PROFILE overrides) names the profile to use — the
|
|
466
|
+
* same convention repo pull scripts follow.
|
|
467
|
+
*/
|
|
468
|
+
export async function fetchRemoteExternalCredentials(tenant, env, external, profile) {
|
|
469
|
+
const secretId = `mesh/${tenant}/${env}/external/${external}`;
|
|
470
|
+
const region = process.env.MESH_PLATFORM_REGION ?? process.env.AWS_REGION ?? "us-east-2";
|
|
471
|
+
const fallbackProfile = !process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_PROFILE
|
|
472
|
+
? (process.env.MESH_AWS_PROFILE ?? profile)
|
|
473
|
+
: undefined;
|
|
474
|
+
try {
|
|
475
|
+
const { SecretsManagerClient, GetSecretValueCommand } = await import("@aws-sdk/client-secrets-manager");
|
|
476
|
+
// The SDK's default chain reads AWS_PROFILE at first resolution — scope
|
|
477
|
+
// the fallback to this client's call (no ambient identity to clobber).
|
|
478
|
+
if (fallbackProfile)
|
|
479
|
+
process.env.AWS_PROFILE = fallbackProfile;
|
|
480
|
+
const sm = new SecretsManagerClient({ region });
|
|
481
|
+
let res;
|
|
482
|
+
try {
|
|
483
|
+
res = await sm.send(new GetSecretValueCommand({ SecretId: secretId }));
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
if (fallbackProfile)
|
|
487
|
+
delete process.env.AWS_PROFILE;
|
|
488
|
+
}
|
|
489
|
+
const parsed = JSON.parse(res.SecretString ?? "{}");
|
|
490
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
491
|
+
throw new Error("secret value is not a JSON object");
|
|
492
|
+
}
|
|
493
|
+
return Object.fromEntries(Object.entries(parsed).map(([key, v]) => [key, String(v)]));
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
throw new MeshCliError(`External '${external}': could not pull remote credentials from the ${env} environment (${secretId}, region ${region}): ${err instanceof Error ? err.message : err}`, {
|
|
497
|
+
remediation: {
|
|
498
|
+
command: `AWS_PROFILE=<${tenant}-${env} profile> mesh dev … # or set "remote": { "profile": … } on the declaration / MESH_AWS_PROFILE`,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/** The prober runs inside the stack: anything addressed at the developer
|
|
504
|
+
* host must go through host.docker.internal. */
|
|
505
|
+
function hostForProber(target) {
|
|
506
|
+
return target.replace(/\b(?:localhost|127\.0\.0\.1)\b/, "host.docker.internal");
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* A credential's endpoint value as an HTTP probe target, or undefined when
|
|
510
|
+
* it isn't one. Credentials pulled from a tenant secret carry whatever the
|
|
511
|
+
* vendor configured — a JDBC/ODBC connection string, an `sftp://` URL, or
|
|
512
|
+
* an http URL with embedded userinfo. Anything non-http(s) falls through to
|
|
513
|
+
* the tcp branch, and userinfo is stripped: the target is written to a
|
|
514
|
+
* plaintext probe file and relabeled into Prometheus' `instance` label, so
|
|
515
|
+
* a password in it would leak into every `probe_*` series.
|
|
516
|
+
*/
|
|
517
|
+
function httpProbeUrl(raw) {
|
|
518
|
+
if (!raw)
|
|
519
|
+
return undefined;
|
|
520
|
+
let parsed;
|
|
521
|
+
try {
|
|
522
|
+
parsed = new URL(raw);
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
528
|
+
return undefined;
|
|
529
|
+
parsed.username = "";
|
|
530
|
+
parsed.password = "";
|
|
531
|
+
return hostForProber(parsed.toString());
|
|
532
|
+
}
|
|
533
|
+
/** Reachability target for a non-HTTP integration, derived from its
|
|
534
|
+
* credentials: a host/port pair (databases, SFTP) or an S3 bucket endpoint
|
|
535
|
+
* (probed via TCP — no SDK or credentials needed, the technique CSP's
|
|
536
|
+
* network lib used). */
|
|
537
|
+
function derivedTcpTarget(creds) {
|
|
538
|
+
if (creds.host && creds.port)
|
|
539
|
+
return `${hostForProber(creds.host)}:${creds.port}`;
|
|
540
|
+
if (creds.bucket)
|
|
541
|
+
return `${creds.bucket}.s3.${creds.region || "us-east-1"}.amazonaws.com:443`;
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
/** The blackbox probe target for a realization, by mode: local/mock
|
|
545
|
+
* stand-ins are probed on the host (the prober runs inside the stack →
|
|
546
|
+
* host.docker.internal); remote externals are probed at their REAL
|
|
547
|
+
* endpoint, so the Hub shows actual vendor reachability. Every integration
|
|
548
|
+
* gets AT LEAST a reachability check when its credentials expose a target:
|
|
549
|
+
* an HTTP endpoint, a host(:port), or an S3 bucket (probed via TCP to the
|
|
550
|
+
* bucket endpoint — no SDK/credentials needed, the same technique CSP's
|
|
551
|
+
* network lib used). Undefined = nothing probeable in the credentials.
|
|
552
|
+
*
|
|
553
|
+
* Target and module come from ONE decision here so they can't drift: a
|
|
554
|
+
* misrouted probe just reports probe_success 0, indistinguishable from a
|
|
555
|
+
* genuinely down service. `tcp` yields host:port with no scheme/path and
|
|
556
|
+
* routes the scrape to blackbox's tcp_connect (the scrape config's default
|
|
557
|
+
* stays http_2xx); `http` GETs the target and expects 2xx. */
|
|
558
|
+
export function resolveProbeTarget(mode, decl, endpoint, creds) {
|
|
559
|
+
if (mode === "remote") {
|
|
560
|
+
// The endpoint comes from a TENANT SECRET, so it may be anything a
|
|
561
|
+
// vendor credential carries — a JDBC/ODBC connection string, or a URL
|
|
562
|
+
// with userinfo. Only a real http(s) URL is probeable, and its
|
|
563
|
+
// credentials must never reach the probe file (world-readable) or the
|
|
564
|
+
// Prometheus `instance` label.
|
|
565
|
+
const url = httpProbeUrl(creds.endpoint ?? creds.url ?? creds.baseUrl);
|
|
566
|
+
// Reachability fallbacks for non-HTTP integrations: host:port
|
|
567
|
+
// (databases, SFTP) or the S3 bucket endpoint (object storage).
|
|
568
|
+
const tcpTarget = derivedTcpTarget(creds);
|
|
569
|
+
if (decl.probe === "tcp" || (!url && tcpTarget)) {
|
|
570
|
+
if (!tcpTarget && decl.probe === "tcp") {
|
|
571
|
+
logWarn(`External '${decl.external ?? ""}': probe: "tcp" declared but the remote credentials carry no host/port (or bucket) to probe — no uptime probe registered.`);
|
|
572
|
+
}
|
|
573
|
+
return tcpTarget ? { target: tcpTarget, module: "tcp_connect" } : undefined;
|
|
574
|
+
}
|
|
575
|
+
// Remote endpoints rarely expose /health — default to the endpoint
|
|
576
|
+
// root (Plaid's sandbox, for one, answers 200 there).
|
|
577
|
+
return url ? { target: url.replace(/\/+$/, "") + (decl.healthPath ?? "") } : undefined;
|
|
578
|
+
}
|
|
579
|
+
if (!endpoint)
|
|
580
|
+
return undefined;
|
|
581
|
+
if (decl.probe === "tcp") {
|
|
582
|
+
return { target: `host.docker.internal:${endpoint.port}`, module: "tcp_connect" };
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
585
|
+
target: hostForProber(endpoint.url).replace(/\/+$/, "") + (decl.healthPath ?? "/health"),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
/** Hub display name: explicit `displayName`, else the external name
|
|
589
|
+
* title-cased on word boundaries ("plaid-db" → "Plaid Db"). */
|
|
590
|
+
export function externalDisplayName(name, decl) {
|
|
591
|
+
return (decl.displayName ??
|
|
592
|
+
name
|
|
593
|
+
.split(/[-_\s]+/)
|
|
594
|
+
.filter(Boolean)
|
|
595
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
596
|
+
.join(" "));
|
|
597
|
+
}
|
|
598
|
+
/** Normalize the decl's `docs` (string URL or {url, provider}) for the meta. */
|
|
599
|
+
export function externalDocs(decl) {
|
|
600
|
+
if (!decl.docs)
|
|
601
|
+
return undefined;
|
|
602
|
+
return typeof decl.docs === "string" ? { url: decl.docs } : decl.docs;
|
|
603
|
+
}
|
|
604
|
+
/** Prometheus file_sd target file for one external's uptime probe. One
|
|
605
|
+
* file per (tenant, app, external) so a session's probes can be removed on
|
|
606
|
+
* `--kill` without touching another app's. */
|
|
607
|
+
export function localProbeFile(tenant, app, external) {
|
|
608
|
+
return path.join(localProbesDir(), `${tenant}-${app}-${external}.json`);
|
|
609
|
+
}
|
|
610
|
+
/** Drop probe target files (best effort). Called on `mesh dev --kill` so
|
|
611
|
+
* Prometheus stops scraping stand-ins that no longer exist — otherwise the
|
|
612
|
+
* Hub shows an integration down for a session that isn't running. */
|
|
613
|
+
export function localProbesRemove(files) {
|
|
614
|
+
for (const file of files ?? []) {
|
|
615
|
+
try {
|
|
616
|
+
fs.rmSync(file, { force: true });
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
/* best effort — the cache dir may be gone */
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Wire a realization into the platform: seed the external-service
|
|
625
|
+
* credential secret and register the vendor in the local Hub's
|
|
626
|
+
* external-services view — the same shapes ExternalService exports in the
|
|
627
|
+
* cloud. `endpoint` is the local stand-in's address (absent in remote
|
|
628
|
+
* mode — the credentials carry the real endpoint).
|
|
629
|
+
*
|
|
630
|
+
* Returns the probe target file this realization registered, if any, so the
|
|
631
|
+
* caller can record it in session state and remove it on `--kill`.
|
|
632
|
+
*/
|
|
633
|
+
export async function seedLocalMock(args) {
|
|
634
|
+
const { tenant, app, name, decl, endpoint } = args;
|
|
635
|
+
if (!decl.external)
|
|
636
|
+
return undefined;
|
|
637
|
+
const mode = externalMode(name, decl);
|
|
638
|
+
// Credential values by mode: local/mock interpolate the declared template
|
|
639
|
+
// against the stand-in's endpoint; remote uses inline credentials or
|
|
640
|
+
// pulls the tenant environment's configured secret.
|
|
641
|
+
let value;
|
|
642
|
+
if (mode === "remote") {
|
|
643
|
+
const inline = remoteInlineCredentials(decl);
|
|
644
|
+
value = inline
|
|
645
|
+
? Object.fromEntries(Object.entries(inline).map(([key, raw]) => [
|
|
646
|
+
key,
|
|
647
|
+
interpolateCredentialValue(raw, undefined),
|
|
648
|
+
]))
|
|
649
|
+
: await fetchRemoteExternalCredentials(tenant, decl.remote?.env ?? "dev", decl.external, decl.remote?.profile);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
if (!decl.credentials)
|
|
653
|
+
return undefined;
|
|
654
|
+
value = Object.fromEntries(Object.entries(decl.credentials).map(([key, raw]) => [
|
|
655
|
+
key,
|
|
656
|
+
interpolateCredentialValue(raw, endpoint),
|
|
657
|
+
]));
|
|
658
|
+
}
|
|
659
|
+
const secretName = externalSecretPath(tenant, decl.external);
|
|
660
|
+
await upsertLocalSecret(secretName, value);
|
|
661
|
+
// The .config mirror — non-secret fields only. This is what the Hub's
|
|
662
|
+
// credential views READ (the main secret is runtime-only by design), so
|
|
663
|
+
// baseUrl etc. display in the Integrations detail page.
|
|
664
|
+
await upsertLocalSecret(`${secretName}/.config`, Object.fromEntries(Object.entries(value).filter(([key]) => !SECRET_KEY_RE.test(key))));
|
|
665
|
+
// Hub discovery — the same per-stack shapes ExternalService exports in the
|
|
666
|
+
// cloud (/apps/{app}/stacks/{stack}/external-services/{name}/…).
|
|
667
|
+
const { SSMClient, PutParameterCommand } = await import("@aws-sdk/client-ssm");
|
|
668
|
+
const ssm = new SSMClient(LOCAL_AWS_CONFIG);
|
|
669
|
+
const base = `/mesh-platform/${tenant}/${LOCAL_ENV}/apps/${app}/stacks/local/external-services/${decl.external}`;
|
|
670
|
+
await ssm.send(new PutParameterCommand({
|
|
671
|
+
Name: `${base}/meta`,
|
|
672
|
+
Type: "String",
|
|
673
|
+
Overwrite: true,
|
|
674
|
+
Value: JSON.stringify({
|
|
675
|
+
name: decl.external,
|
|
676
|
+
// The integration keeps its REAL name in the Hub ("Plaid", not a
|
|
677
|
+
// generated phrase); the description leads with it too, so display
|
|
678
|
+
// heuristics that promote description heads still land on the name.
|
|
679
|
+
displayName: externalDisplayName(name, decl),
|
|
680
|
+
type: decl.type ?? "other",
|
|
681
|
+
description: mode === "local"
|
|
682
|
+
? `${externalDisplayName(name, decl)} — local replica via mesh dev (docker compose)`
|
|
683
|
+
: mode === "remote"
|
|
684
|
+
? remoteInlineCredentials(decl)
|
|
685
|
+
? `${externalDisplayName(name, decl)} — remote service (vendor credentials)`
|
|
686
|
+
: `${externalDisplayName(name, decl)} — remote service (${decl.remote?.env ?? "dev"} environment credentials)`
|
|
687
|
+
: `${externalDisplayName(name, decl)} — emulated by mesh dev (mock)`,
|
|
688
|
+
secretPrefix: secretName,
|
|
689
|
+
...(externalDocs(decl) ? { docs: externalDocs(decl) } : {}),
|
|
690
|
+
}),
|
|
691
|
+
Description: `External service registration (local mock ${name})`,
|
|
692
|
+
}));
|
|
693
|
+
await ssm.send(new PutParameterCommand({
|
|
694
|
+
Name: `${base}/credentials`,
|
|
695
|
+
Type: "String",
|
|
696
|
+
Overwrite: true,
|
|
697
|
+
Value: JSON.stringify({
|
|
698
|
+
fields: Object.fromEntries(Object.keys(value).map((key) => [
|
|
699
|
+
key,
|
|
700
|
+
{ type: "field", name: key, description: "", secret: SECRET_KEY_RE.test(key), optional: false },
|
|
701
|
+
])),
|
|
702
|
+
keyedBy: null,
|
|
703
|
+
}),
|
|
704
|
+
Description: `External service credential schema (local mock ${name})`,
|
|
705
|
+
}));
|
|
706
|
+
// Blackbox probe target (Prometheus file_sd) — the local analog of the
|
|
707
|
+
// Probe CR ExternalService creates in the cloud, with the SAME label set,
|
|
708
|
+
// so probe_success drives the Hub's health/uptime views unchanged.
|
|
709
|
+
// Local/mock stand-ins are probed on the host; remote externals at their
|
|
710
|
+
// real endpoint (host:port via the tcp_connect module for non-HTTP
|
|
711
|
+
// externals — the __probe_module label routes the module through the
|
|
712
|
+
// scrape config's relabeling, and "__"-prefixed labels drop after
|
|
713
|
+
// relabeling so it never pollutes the stored series).
|
|
714
|
+
const probe = resolveProbeTarget(mode, decl, endpoint, value);
|
|
715
|
+
await ssm.send(new PutParameterCommand({
|
|
716
|
+
Name: `${base}/healthCheck`,
|
|
717
|
+
Type: "String",
|
|
718
|
+
Overwrite: true,
|
|
719
|
+
Value: JSON.stringify({ intervalSeconds: 30, timeoutSeconds: 10, hasCustomCheck: false, hasProbe: !!probe }),
|
|
720
|
+
Description: `External service health check config (local mock ${name})`,
|
|
721
|
+
}));
|
|
722
|
+
// The file and `hasProbe` must never disagree: a realization that no
|
|
723
|
+
// longer resolves a probe target drops the file it wrote on a previous
|
|
724
|
+
// run, or Prometheus keeps scraping the old target forever.
|
|
725
|
+
const probeFile = localProbeFile(tenant, app, decl.external);
|
|
726
|
+
if (probe) {
|
|
727
|
+
fs.writeFileSync(probeFile, JSON.stringify([
|
|
728
|
+
{
|
|
729
|
+
targets: [probe.target],
|
|
730
|
+
labels: {
|
|
731
|
+
type: "external-service",
|
|
732
|
+
tenant,
|
|
733
|
+
env: LOCAL_ENV,
|
|
734
|
+
app,
|
|
735
|
+
external_service: decl.external,
|
|
736
|
+
target: decl.external,
|
|
737
|
+
// "__"-prefixed labels drop after relabeling, so the module
|
|
738
|
+
// selector never pollutes the stored series.
|
|
739
|
+
...(probe.module ? { __probe_module: probe.module } : {}),
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
], null, 2));
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
localProbesRemove([probeFile]);
|
|
746
|
+
}
|
|
747
|
+
const endpointShown = endpoint?.url ??
|
|
748
|
+
value.endpoint ??
|
|
749
|
+
value.url ??
|
|
750
|
+
value.baseUrl ??
|
|
751
|
+
(value.host ? `${value.host}${value.port ? `:${value.port}` : ""}` : "credentials");
|
|
752
|
+
logSuccess(`External '${name}' wired (${mode}): ${secretName} → ${endpointShown}` +
|
|
753
|
+
` (Hub registration${probe ? " + uptime probe" : ""})`);
|
|
754
|
+
return probe ? probeFile : undefined;
|
|
755
|
+
}
|
|
756
|
+
//# sourceMappingURL=mocks.js.map
|