@mesh-tech/mesh-cli 0.34.3 → 0.35.0
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/app-skill/SKILL.md +1 -1
- package/dist/bin/mesh.js +567 -195
- package/dist/bin/mesh.js.map +3 -3
- package/dist/build-info.json +2 -2
- package/dist/src/commands/cluster.js +2 -2
- package/dist/src/commands/dev.js +4 -2
- package/dist/src/commands/login.d.ts +1 -0
- package/dist/src/commands/login.js +23 -20
- package/dist/src/commands/skills.d.ts +7 -2
- package/dist/src/commands/skills.js +84 -15
- package/dist/src/commands/stack-status.d.ts +65 -0
- package/dist/src/commands/stack-status.js +285 -0
- package/dist/src/commands/stack.js +15 -1
- package/dist/src/commands/temporal.js +45 -17
- package/dist/src/commands/temporal.js.map +1 -1
- package/dist/src/program.d.ts +1 -1
- package/dist/src/program.js +1 -1
- package/dist/src/utils/aws-auth.d.ts +1 -0
- package/dist/src/utils/aws-auth.js +10 -1
- package/dist/src/utils/first-party-contexts.d.ts +3 -0
- package/dist/src/utils/first-party-contexts.js +12 -0
- package/dist/src/utils/pulumi-run.d.ts +1 -1
- package/dist/src/utils/pulumi-run.js +16 -6
- package/fragments/base/index.ts.hbs +3 -2
- package/package.json +2 -2
- package/skills/{core → mesh}/SKILL.md +106 -100
- package/skills/mesh/references/programmatic-api.md +35 -0
- package/skills/mesh/references/worktrees.md +53 -0
- package/skills/pull-remote-agent-context/SKILL.md +1 -1
- package/templates/api-auth/README.md.hbs +3 -2
|
@@ -1,29 +1,111 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: mesh
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
portal.
|
|
4
|
+
Mesh, the platform, and the `mesh` developer CLI (@mesh-tech/mesh-cli).
|
|
5
|
+
Explains how an org's Mesh is shaped — the core layer (VPC/EKS/RDS/IAM),
|
|
6
|
+
the platform layer (Zitadel, Temporal, the Hub, tenant environments) and a
|
|
7
|
+
personal stack (an isolated platform for one developer on the org's core) —
|
|
8
|
+
and drives the CLI that sets it up, runs and deploys apps on it, and checks
|
|
9
|
+
it is current. Activate on ANY Mesh work, even when no command is named:
|
|
10
|
+
"set me up", "where do I deploy", "deploy my app / studio", a personal
|
|
11
|
+
stack or platform, mesh login / deploy errors (web identity token could not
|
|
12
|
+
be validated), local dev (mesh start / mesh dev), scaffolding an app,
|
|
13
|
+
upgrading Mesh or checking whether an instance is behind, tunnels, DB
|
|
14
|
+
access, secrets, VPN, Temporal inspection, and the docs portal.
|
|
15
15
|
metadata:
|
|
16
16
|
version: '0.1'
|
|
17
17
|
category: 'tooling'
|
|
18
18
|
type: core
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# Mesh and the `mesh` CLI
|
|
22
22
|
|
|
23
23
|
Developer CLI for Mesh Platform. Binary name is **`mesh`**. Install it explicitly —
|
|
24
24
|
`@mesh-tech/app-kit` ships a `mesh` bin shim onto it but declares it an **optional
|
|
25
25
|
peer**, so it is never installed transitively.
|
|
26
26
|
|
|
27
|
+
## Start here: how an org's Mesh is shaped
|
|
28
|
+
|
|
29
|
+
Read this before choosing where anything runs. A developer who is asked "where
|
|
30
|
+
do you want to deploy?" without it has no way to answer.
|
|
31
|
+
|
|
32
|
+
| Layer | What it is | Pulumi program | Registers (SSM) |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| **Core** | The base cloud setup: VPC, EKS cluster, RDS, IAM, DNS zone. One per AWS account/environment the org chooses. | `core/` of the org's platform repo | `/mesh-platform/<tenant>/<env>/core` |
|
|
35
|
+
| **Platform** | Instantiates the services on a core: Zitadel (sign-in), Temporal, the Hub, and the **tenant environments** apps deploy into. Several platforms can share one core. | `platform/` of the same repo | `/mesh-platform/<tenant>/<env>/platform` |
|
|
36
|
+
| **Personal stack** | A platform instance for ONE developer — its own Zitadel, Hub, Temporal and tenant envs, isolated from everyone else's, running on the org's existing core. Nothing you do there can break a shared environment. | a stack of `platform/` (`mesh stack init --platform`) | its own `…/platform` |
|
|
37
|
+
| **App** | Your services, deployed into a tenant environment of a platform. | the app's own Pulumi program | `…/apps/<app>/…` |
|
|
38
|
+
|
|
39
|
+
`mesh start` is the fifth option: the whole platform locally in Docker, no cloud at all
|
|
40
|
+
(§1b). Use it first when the developer only needs to run an app.
|
|
41
|
+
|
|
42
|
+
### Two ways orgs lay out environments
|
|
43
|
+
|
|
44
|
+
Every org decides this for itself, so find out which one yours uses — never assume.
|
|
45
|
+
|
|
46
|
+
| Shape | The platform repo holds | A personal platform goes |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| **One core, several platforms** — environments (and developers) are platform stacks sharing a core | `core/Pulumi.acme-dev.yaml` (one core); `platform/Pulumi.acme-dev.yaml`, `platform/Pulumi.acme-qa.yaml`, … — each with `mesh:coreEnv` (and `mesh:coreTenant` when it differs) naming that core | onto that core: another `platform/` stack |
|
|
49
|
+
| **A full core + platform per environment** — each env has its own VPC/EKS, often its own AWS account | `core/Pulumi.acme-dev.yaml` + `core/Pulumi.acme-prod.yaml`, and the matching `platform/` stacks | onto the **non-production** core (`acme-dev`) — **never prod** |
|
|
50
|
+
|
|
51
|
+
### "Set me up to deploy" — the flow
|
|
52
|
+
|
|
53
|
+
1. **Ask which repo holds the org's Mesh platform** (the one with `core/` and
|
|
54
|
+
`platform/` Pulumi programs; often `<org>-mesh-platform`). Don't guess from
|
|
55
|
+
memory — the answer differs per org.
|
|
56
|
+
2. **Read its stack files** — `core/Pulumi.*.yaml` and `platform/Pulumi.*.yaml` —
|
|
57
|
+
and classify the shape above. List the candidate cores: each `core/` stack, with
|
|
58
|
+
the platforms that already name it through `mesh:coreEnv`.
|
|
59
|
+
3. **Recommend the non-production core and confirm it with the developer** before
|
|
60
|
+
creating anything. Say what will be created and on which AWS account.
|
|
61
|
+
4. **Create and deploy the personal platform** from that repo's `platform/`:
|
|
62
|
+
```bash
|
|
63
|
+
cd <org-platform-repo>/platform
|
|
64
|
+
mesh stack init --platform --from acme-dev --parent-zone dev.acme.io
|
|
65
|
+
# → stack acme-<github-user>-dev: mesh:coreEnv = the base's env, mesh:clusterOwner false,
|
|
66
|
+
# org-wide singletons (package registry, VPN) off; hosts become <sub>.<user>-dev.dev.acme.io
|
|
67
|
+
mesh deploy preview --stack acme-<github-user>-dev
|
|
68
|
+
mesh deploy up --stack acme-<github-user>-dev
|
|
69
|
+
```
|
|
70
|
+
A platform stack carries no `mesh:deployerRole`: `mesh deploy` runs it with the
|
|
71
|
+
developer's own AWS credentials **in the core's account** (`AWS_PROFILE`, or the
|
|
72
|
+
stack's `aws:profile`). A developer with no access to that account needs the
|
|
73
|
+
platform admin to grant it first — say so rather than retrying.
|
|
74
|
+
5. **Register a tenant environment and point the app at it.** `mesh tenant add <tenant>
|
|
75
|
+
--stack acme-<github-user>-dev`, deploy again, then in the app's stack file:
|
|
76
|
+
`mesh:platform` `{name: acme, env: <github-user>-dev}` and `mesh:deployerRole`
|
|
77
|
+
`arn:aws:iam::<core account>:role/<tenant>-<github-user>-dev-apps-deployer`.
|
|
78
|
+
Sign in to the personal platform's own Zitadel — `mesh login <user>-dev.dev.acme.io`
|
|
79
|
+
(its domain) — and put that same context in the app stack as
|
|
80
|
+
`mesh:loginContext`, so `mesh deploy` uses that session.
|
|
81
|
+
|
|
82
|
+
### Keeping an instance current (run at session start)
|
|
83
|
+
|
|
84
|
+
The developer owns keeping their instance up to date; your part is to notice.
|
|
85
|
+
When the session starts in a platform repo or an app directory with a stack, run:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
mesh stack status # pinned vs deployed vs latest, per layer; plus this CLI vs latest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It is read-only. When a layer is `behind`, or a pin was never deployed, **tell the
|
|
92
|
+
developer and offer the upgrade — do it only when they say yes.** An upgrade bumps
|
|
93
|
+
`@mesh-tech/infra-components` (and `@mesh-tech/hub` in `platform/`) in **both**
|
|
94
|
+
`core/` and `platform/`, then deploys **both** — core first, previewed, since it is
|
|
95
|
+
the VPC/EKS/RDS. Deploying `platform/` alone leaves core reporting the old version.
|
|
96
|
+
`mesh stack status` prints the exact steps. A deployed version only changes on a
|
|
97
|
+
**successful** deploy, so a failed `up` still reads as the old version.
|
|
98
|
+
|
|
99
|
+
### Signing in: one session per platform
|
|
100
|
+
|
|
101
|
+
Each platform has its own Zitadel, and a deployer role trusts only its own
|
|
102
|
+
platform's tokens. `mesh deploy`, `mesh dev` and `mesh kubectl` pick the session
|
|
103
|
+
from the **stack**: `mesh:loginContext` when set, else the stack's `mesh:platform`
|
|
104
|
+
(`{name}.{env}`). The error `AssumeRoleWithWebIdentity … web identity token could
|
|
105
|
+
not be validated` means you are signed in to a *different* platform than the stack
|
|
106
|
+
deploys to — run the `mesh login <context>` the preflight names, never AWS keys.
|
|
107
|
+
`mesh registry login` (packages) is separate and needs no platform at all.
|
|
108
|
+
|
|
27
109
|
## Running it
|
|
28
110
|
|
|
29
111
|
**In this monorepo the CLI runs from TypeScript source — there is no build step and
|
|
@@ -99,9 +181,9 @@ Top-level commands (each `register*` is wired in `src/program.ts`, which `bin/me
|
|
|
99
181
|
| `mesh create-app` | — | Scaffold a tenant app. Composable mode (primitives) or legacy template mode. Interactive when TTY + missing flags. Run at the root of an **empty** `{tenant}-mesh-apps` git clone (a `.git` with no `package.json`/`apps/`/`tenants/` and no enclosing pnpm workspace), it first generates the repo-level shape — `pnpm-workspace.yaml` over `apps/*`, private root `package.json`, scoped-registry `.npmrc`, base `tsconfig.json`, `.gitignore`, `README.md`, `apps/` — then scaffolds the app. Never overwrites an existing file. | `--tenant`, `--name`, `--primitives <csv>` (`service,database,temporal,bucket`), `--template <workflow\|api-auth\|api-role-gating\|external-service>`, `--test` (writes to `tests/tenants/`) |
|
|
100
182
|
| `mesh app` | `check [apps...]` | Checks tenant apps against the **Mesh app contract** (`@mesh-tech/app-kit#apps` → `references/app-contract.md`): a UI is its own Service (`UI_IN_API`, `NO_UI_SERVICE`), the app registers with the Hub (`NO_REGISTER`), sign-in goes through the platform proxy (`CUSTOM_SESSION_AUTH`), logs go through `@mesh-tech/logger` (`NON_PLATFORM_LOGGER`), and people/roles/keys are surfaced through the Hub (`NO_AUTHZ_POINTER`, `METADATA_UNPUBLISHED`, `IAC_GRANTS`, `PASSWORD_STORE`, `EMAIL_ALLOWLIST`, `USERS_TABLE`), and a docs site is published through the Hub docs door rather than on a hostname of its own, with its readers stated and grantable (`DOCS_SITE_PUBLIC` — advisory: the public hostname is the deprecated compatibility path and keeps deploying, so the finding names the door instead — `DOCS_AUDIENCE_IMPLICIT`, `DOCS_AUDIENCE_UNGRANTABLE`, `DOCS_VENDOR_AUDIENCE_PARTNER` — a vendor surface's reference is never partner-readable); a tenant-local auth lib is advisory (`LOCAL_AUTH_LIB`), as is a pointer that names its own Zitadel project instead of binding the env with `zitadelAppProjectId` (`POINTER_PROJECT_OVERRIDE`). Gate 0.6 additionally resolves the Pulumi program's imports against the installed tree (`UNRESOLVABLE_IMPORT`) — an optional peer nothing declares installs silently and `skipLibCheck` hides it, so the break otherwise waits for `mesh deploy`; with no `node_modules` the gate reports itself not checked rather than met. One check per code on the `mesh dev doctor` engine; exit 1 on any BLOCK. The SAME gate `mesh create-app` runs after scaffolding, tenant CI runs beside `mesh skills sync --check`, and the platform reviewer runs on a PR. Default: every `apps/*` with a Pulumi program. | `[apps...]` (repo-relative dirs), `--root <dir>`, `--json`, `--verbose` |
|
|
101
183
|
| `mesh init` | (bare = wizard), `app-tenant`, `platform <tenant>` | **Bare `mesh init` is the guided setup wizard**: tenant (pre-filled from `mesh.json` / Pulumi `mesh:tenant`), **where the tenant runs — local-only, a deployed platform, or NOTHING YET (scaffold one; §1c)**, registry access (in-process `mesh registry login`; a 403 marks the step ✘ and the wizard continues), platform sign-in on the deployed branch only, repo bootstrap (empty clone, or an empty non-git folder the wizard offers to `git init`) or shape check, skills sync; writes `mesh.json` (`{tenant, platform}`) so `create-app` / `mesh dev` default `--tenant`; flags `--tenant --local\|--platform <env>\|--new-platform [env] --device --profile --skip-repo --yes --json`, plus the scaffold's own under `--new-platform` (`--region --domain --state-bucket --create-state-bucket/--no-create-state-bucket --dir`); exit 1 if any step failed, 130 on Ctrl-C. `platform <tenant>` **prompts** on a TTY for env/region/profile/state bucket (defaults on each) and for `--domain`, which has **no** default because a placeholder scaffolds an unvalidatable zone; it offers to create the Pulumi state bucket (versioned) and prints the deploy sequence including the manual NS delegation. `app-tenant` is the repo doctor (A1–A3): read-only check pipeline — CLI auth, registry access, public npm not hijacked, platform reachable (Hub API), app-tenant registration, deployer role, repo shape, agent skills — each failure names the exact `mesh` fix. **Registry access** is a live probe of the CodeArtifact token, not a grep of `~/.npmrc`: an expired token `fail`s (it used to `pass`), an unreachable registry `warn`s (a network fault is not an auth fault). **Public npm not hijacked** fails when `~/.npmrc` carries an unscoped `registry=<codeartifact>` line, which makes CodeArtifact your default registry — one `mesh registry login` fixes both. Exit 0 iff all pass; re-run any time. `--fix` applies developer-scope fixes (registry token, skills sync); operator actions stay remediations — and `--profile <p>` is the SSO profile `--fix` logs into CodeArtifact with, echoed back in the `fix:` command each check prints so the advertised command is the one that runs. | `app-tenant`: `--tenant <name>`, `--context <ctx>` (default `local`), `--hub-url <url>`, `--fix`, `--profile <sso>`, `--json` |
|
|
102
|
-
| `mesh skills` | `sync` | Agent-skill distribution (D1/D2): installs the base building-with-Mesh skills into the repo's `.claude/skills
|
|
184
|
+
| `mesh skills` | `sync` | Agent-skill distribution (D1/D2): installs the base building-with-Mesh skills into the repo's `.claude/skills/` — this one as `mesh/` (`/mesh`) — and the `@mesh-tech/*` package skills as `mesh-<pkg>[-<domain>]/` (`-core` dropped: `mesh-authn`, `mesh-app-kit-apps`); every copy's `name:` is its directory, and managed copies left under older names (`mesh-core/`, `mesh-<pkg>-core/`) are removed and wires TanStack-Intent discovery for the pattern skills shipping inside `@mesh-tech/*` packages (`.intent/hooks/` gate + `.claude/settings.json` SessionStart hook + AGENTS.md fence — the PR #2356 mechanism, vendored). Idempotent; runs automatically after `mesh create-app`. | `sync`: `--check` (CI/doctor: exit 1 when missing/stale), `--root <path>` |
|
|
103
185
|
| `mesh docs` | `build`, `portal`, `cli-reference` | `build` builds/validates an **app's** OpenAPI specs from `docs/docs.config.json` (file copy or SymXchange generation). `start`/`stop`/`list` are the simple front door: `start` serves the docs on loopback — DETACHED in a tmux session named `mesh-docs` by default (prints the bare URL once live; `stop` kills the session; `--foreground` or any non-TTY runs in the foreground) — serving the working tree in a mesh-platform checkout, or the published `@mesh-tech/docs` artifact fetched from the role-gated CodeArtifact registry anywhere else (docs version == the @mesh-tech/* baseline it describes; `-v` pins, default latest; cached under `~/.cache/mesh/docs/`). `list` shows the published docs versions. `portal` assembles the **Mesh developer portal** (docs.meshtech.io) from every `docs.json`-opted-in doc root in the repo — a directory publishes its markdown iff it holds a `docs.json`; route/title/order/nav derive from the tree and each file, so adding a doc is adding one file — then hands the assembled tree to Zudoku (`apps/docs/`): default runs `zudoku build`, `--serve` runs `zudoku dev`, `--assemble-only`/`-o` stop after assembly. Links between published files are rewritten to routes; links to unpublished repo files become blob links. Reserved directory names (`plans/`, `designs/`, `incidents/`, … the frozen list in `src/docs/schema.ts`) are excluded at any depth. `cli-reference` regenerates `docs/portal/generated/cli-reference.md` from THIS command tree, so it can't document a flag the binary lacks. `portal --check` is the CI gate (`pnpm check:docs-portal`): docs.json schema validation, reserved-segment fail-close, broken links, and CLI-reference staleness; `--diff-base <ref>` prints the publish-set diff for PR job summaries. Hidden commands and `--version` are excluded from the reference. | `build`: `-c/--config <path>` (default `docs/docs.config.json`); `portal`: `-o/--out <dir>`, `--assemble-only`, `--serve`, `-p/--port <n>` (default `3000`), `--check`, `--print-manifest`, `--diff-base <ref>`, `--manifest-out <path>`; `cli-reference`: `-o/--out <path>`, `--check`; `start`: `-v/--version <v>` (default: latest), `-p/--port <n>` (default `4400`; `0` is foreground-only), `--dev` (foreground HMR), `--foreground`; `stop`: no flags; `list`: no flags |
|
|
104
|
-
| `mesh stack` | `init`, `rm <name>` | `init` creates a personal
|
|
186
|
+
| `mesh stack` | `init`, `status`, `rm <name>` | `init` in an **app** creates a personal app stack `dev-{github-user}`, copying base-stack config, setting `mesh:deploy: false`, inheriting KMS secrets provider; `--worktree` appends the git-worktree token (`dev-{user}-{token}`) so concurrent worktrees deploy to **distinct** stacks (SSM paths, Temporal namespace, Nexus endpoint all derive from the stack name → one discriminator isolates all three; no-op on the primary checkout). `init --platform` in a platform repo's **`platform/`** creates a **personal platform** `{tenant}-{github-user}-dev` — its own Zitadel, Hub, Temporal and tenant envs on the base stack's core (`mesh:coreEnv`, `mesh:clusterOwner false`, registry/VPN singletons off, deploy on); `--parent-zone` gives it its own DNS zone. `status` is the read-only drift check: for each platform this directory deploys (or an app deploys to), pinned vs deployed (the `core` and `platform` SSM registrations) vs latest `@mesh-tech/infra-components` / `@mesh-tech/hub`, plus this CLI vs the latest `@mesh-tech/mesh-cli`, with the upgrade steps when anything is behind. `rm <name>` removes a personal stack (backend state + local config), credentialed. `init`/`rm` assume the deployer role — no raw `pulumi` / manual creds needed. | `init`: `--from <stack>`, `--name <stack>`, `--worktree`, `--adopt`, `--platform`, `--parent-zone <zone>`; `status`: `--stack`, `--profile`, `--region`, `--json`, `--check` (exit 1 when behind); `rm`: `--yes` |
|
|
105
187
|
| `mesh tenant` | `add <name>`, `list` | Register/inspect app tenants on a platform stack. Run from the **platform layer** of a tenant platform repo (e.g. `mesh-sandbox/platform`). `add` inserts the tenant under `mesh:tenants` in `Pulumi.<stack>.yaml` via a comment-preserving YAML round-trip (untouched lines don't reformat), refuses a tenant that is already declared (an existing entry may carry fields a re-add would drop), derives the `{name}-{env}` subdomain from the stack name (echoed in the success line), and guards against running on the core layer or an app dir; it then prints the deploy + verify sequence — `mesh deploy up` is what makes the registration live. `list` prints what the stack config declares, so "is it registered?" is answerable without opening YAML (warns when the project doesn't look like a platform layer, so an empty result isn't mistaken for "no tenants"). | `add`: `--display-name <name>`, `--subdomain <sub>`, `--zitadel-org-id <id>`, `--stack <stack>`, `--json`; `list`: `--stack <stack>`, `--json` |
|
|
106
188
|
| `mesh tunnel [group]` | `external <name>` | SSM port-forward to platform services. Groups: `dev` (temporal+db), `temporal`. **`external <name>`** tunnels to a *registered* `ExternalService`: host/port are read from its credential secret (`mesh/{app-tenant}/{app-stage}/external/{name}`) — the same values deployed apps resolve at runtime — so anything the bastion can reach is tunnelable with no per-service CLI entry and no bastion services-map change. `-t/-e` are **optional inside an app directory** — they fall back to the same Pulumi/SST context detection `mesh dev` uses, which fails closed elsewhere, so pass `-t` outside an app dir (`-t` alone is enough; `-e` alone is not). `-e` names a platform env, not a stack, so it no longer doubles as the stage hint: the credential axis `external` keys on stays the app's real stack name. And `AWS_REGION`/`AWS_PROFILE` default from the nearest Pulumi config + the `{tenant}-{env}` profile convention (region falls back to the effective profile's own `region` — the `[default]` profile when none was resolved — before the platform default us-east-2). | root: `-t/--tenant`, `-e/--env`, `-l/--list`, `-s/--services <csv\|group>`, `--db-port`, `--temporal-ui-port`, `--temporal-frontend-port`; `external`: `-p/--port` (default: the remote port), `-k/--key <key>` (multi-instance `credentials.keyedBy` externals), `--app-tenant`/`--app-stage` (credential axis; default to `-t`/`-e`) |
|
|
107
189
|
| `mesh secrets` | `exec <command...>`, `set [service]`, `reindex [service]`, `migrate-config` | Manage external-service creds in Secrets Manager. `exec` injects secrets as env; `set` reads field schema from SSM and prompts (or `--json`) — with `--key` it also records the instance in the service's instance index (`{prefix}/.index`, what `listInstances()` reads). `reindex` rebuilds that index from the per-key secrets actually in Secrets Manager (operator-credential `ListSecrets`) — the repair for an index that drifted (out-of-band secret create/delete, concurrent writers, malformed value) and the populate step when rolling the index out to an estate: **deploy the declaring stack first** (creates the managed, empty index), then `reindex`. All three resolve tenant + platform env from the cwd stack file and the app's `config.ts`; `-t/--tenant` overrides the tenant for one invocation, and with several `Pulumi.<stack>.yaml` files present pass `--stack` rather than relying on the base-stack pick. | `set`: `--key <id>` (multi-instance), `--all`, `--json <str>`, `-t/--tenant`, `--stack`, `--region`; `reindex`: `--dry-run`, `-t/--tenant`, `--stack`, `--region`; `migrate-config`: `--force`, `--dry-run`, `-t/--tenant`, `--stack`, `--region` |
|
|
@@ -265,59 +347,12 @@ it over the containerized Hub when you are iterating on Hub code and want the UI
|
|
|
265
347
|
reload on save. Both default to :9000, so run them together only with `--port` /
|
|
266
348
|
`MESH_HUB_DEV_PORT` set on this one.
|
|
267
349
|
|
|
268
|
-
### 3. Concurrent `mesh dev` across git worktrees and sibling apps
|
|
350
|
+
### 3. Concurrent `mesh dev` across git worktrees and sibling apps
|
|
269
351
|
|
|
270
|
-
Run the **same plain `mesh dev`**
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
session name carries the worktree's sanitized basename, not the hash. The reserved **service-port
|
|
275
|
-
block is derived from (worktree, app)**, not the worktree alone — a repo can hold several apps whose
|
|
276
|
-
sessions run concurrently (e.g. an apps repo's `worker-a` + `worker-b`), and one
|
|
277
|
-
block per worktree handed both the same ports.
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
# worktree A
|
|
281
|
-
cd .worktrees/feature-a/tests/tenants/acme/apps/demo-agent # monorepo test tenant
|
|
282
|
-
mesh dev --dry-run # preview: session name, port block, state/env paths, task queue
|
|
283
|
-
mesh dev # launch (attaches tmux; --headless to skip)
|
|
284
|
-
|
|
285
|
-
# worktree B — SAME command, different shell; auto-isolated
|
|
286
|
-
cd .worktrees/feature-b/tests/tenants/acme/apps/demo-agent
|
|
287
|
-
mesh dev
|
|
288
|
-
|
|
289
|
-
# sibling app in worktree A — same worktree, still its own port block
|
|
290
|
-
cd .worktrees/feature-a/tests/tenants/acme/apps/silverlake-demo
|
|
291
|
-
mesh dev
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
- The **primary checkout** keeps its historical identity (`${project}-dev` session, the app's
|
|
295
|
-
preferred ports). Only **linked worktrees** get an offset session name + a port block in the 40000+
|
|
296
|
-
range. `mesh dev --dry-run` labels which you are (`primary checkout` vs `linked worktree (block N)`).
|
|
297
|
-
- **Port blocks are disjoint by default, not by construction.** The block is a hash bucketed into 63
|
|
298
|
-
slots, so two `(worktree, app)` pairs *can* land on the same block; the in-block free-port probe and
|
|
299
|
-
the ephemeral-port fallback are the backstop when they do — but that backstop is the same
|
|
300
|
-
check-then-bind probe that raced two still-booting sessions onto one port before #2889, so treat a
|
|
301
|
-
shared block as a live hazard, not a handled case. Two consequences worth knowing before you
|
|
302
|
-
debug a collision as a regression:
|
|
303
|
-
- Sibling apps in the **primary checkout** are deliberately excluded — they keep preferred-port
|
|
304
|
-
semantics (block 0) and rely on that probe, so run them from linked worktrees if you need them
|
|
305
|
-
isolated. This is the layout most tenant app repos use day to day.
|
|
306
|
-
- App-scoping raises the number of hashed entities from *#worktrees* to *#worktrees × #apps*, so
|
|
307
|
-
bucket collisions get likelier as you add apps.
|
|
308
|
-
|
|
309
|
-
A deterministic, collision-free allocator that also folds primary checkouts back in is planned.
|
|
310
|
-
- **Do NOT force a shared `--session`** across worktrees — that defeats the auto-isolation and
|
|
311
|
-
reintroduces the collision. Omit `--session` and let it derive per worktree.
|
|
312
|
-
- `mesh dev --kill` / `--status` act only on the current worktree's session.
|
|
313
|
-
- If the stack was last deployed from a *different* worktree, `mesh dev` **refuses** to launch a service
|
|
314
|
-
whose source resolves outside the current worktree (guards against silently running stale code) — re-run
|
|
315
|
-
`mesh deploy up` in this worktree.
|
|
316
|
-
- **Deploying isolated stacks per worktree?** The `mesh dev` isolation above is local-only. To
|
|
317
|
-
`mesh deploy up` a `deploy:false` personal stack from several worktrees at once, give each worktree
|
|
318
|
-
its own stack with `mesh stack init --worktree` — otherwise they collide on SSM export paths
|
|
319
|
-
(`ParameterAlreadyExists`) and the Temporal namespace (both derive from the stack name).
|
|
320
|
-
`mesh deploy up`/`destroy` **warns** when it sees a shared (non-worktree) stack name in a linked worktree.
|
|
352
|
+
Run the **same plain `mesh dev`** in each worktree — it auto-isolates the tmux session, state
|
|
353
|
+
dir, port block and Temporal task queue. Don't force a shared `--session`. To *deploy* from
|
|
354
|
+
several worktrees at once, give each its own stack: `mesh stack init --worktree`. Details,
|
|
355
|
+
port-block caveats and the stale-source guard: [`references/worktrees.md`](references/worktrees.md).
|
|
321
356
|
|
|
322
357
|
### 4. Deploy (pulumi passthrough)
|
|
323
358
|
|
|
@@ -431,6 +466,7 @@ re-reads stack/SSM output; if the *topology* changed, run `mesh deploy up` first
|
|
|
431
466
|
| `No mesh:deployerRole found` | stack config missing the role | `mesh deploy config set mesh:deployerRole <arn>` |
|
|
432
467
|
| `Could not determine stack` (deploy) | no selected stack & no `--stack` | `mesh deploy stack select <name>` or pass `--stack` |
|
|
433
468
|
| `artifacts get` → ECONNREFUSED / 404 | agent API down or convo expired | start API (`mesh dev`) or pass `--api-url`; restart API after code changes |
|
|
469
|
+
| `AssumeRoleWithWebIdentity … web identity token could not be validated` (`mesh deploy`, `mesh dev --stack`) | the cached session is from a different platform's Zitadel than the one the stack's deployer role trusts — e.g. a `mesh.dev` session (the package-registry front door) against a stack on the `mesh`/`dev` platform, which signs in as `mesh-platform.dev` | run the `mesh login <context> --device` the preflight prints (it is derived from the stack: `mesh:loginContext`, else `mesh:platform`); for a personal platform, set `mesh:loginContext` to its domain. Never fall back to exported AWS keys |
|
|
434
470
|
| `login` config not found | context not discoverable | a known first-party short context (`mesh.dev`) resolves anonymously — no AWS needed. Otherwise use the platform's full domain (`mesh login dev.<tenant>.meshtech.io`). Only if neither works: ensure AWS SSO is active + the platform is deployed (SSM `cliClientId`), or add config to `~/.config/mesh/config.json` |
|
|
435
471
|
| `vpn`/`temporal --show-payloads` fail | kubeconfig not set / expired creds | refresh the tenant cluster kubeconfig (`mesh vpn connect <ctx>` wires it; kubeconfig absorption into `mesh deploy` is planned) then retry | <!-- skill-lint-disable-next-line — next row's backticked `kubectl …` is a quoted ERROR SYMPTOM, not an operator command; the row's fix column already routes through mesh dev -->|
|
|
436
472
|
| `kubectl could not read a kubeconfig … macOS blocks it` (from `mesh kubectl`/`logs`/`exec`/`temporal` in a bare shell) | macOS Full-Disk-Access blocks reading `~/.kube` from a shell outside the `mesh dev` session | run the command **inside** the `mesh dev` tmux window, or `export KUBECONFIG=$TMPDIR/mesh-dev-sessions/<session>.kubeconfig` first |
|
|
@@ -447,39 +483,9 @@ re-reads stack/SSM output; if the *topology* changed, run `mesh deploy up` first
|
|
|
447
483
|
|
|
448
484
|
## Programmatic API
|
|
449
485
|
|
|
450
|
-
`@mesh-tech/mesh-cli` re-exports its utils (`
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
import { detectContext, getPlatformBastionInfo } from "@mesh-tech/mesh-cli";
|
|
454
|
-
const ctx = detectContext(); // { tenant, stage, ... }
|
|
455
|
-
const bastion = await getPlatformBastionInfo(ctx.tenant, ctx.platformEnv);
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
Exports include `detectContext`, `getPlatformBastionInfo` (bastion), credentials,
|
|
459
|
-
pulumi, vpn, and log helpers.
|
|
460
|
-
|
|
461
|
-
`detectContext(stageArg?, { tenant?, stack? })` fails closed: when no source
|
|
462
|
-
declares a tenant it throws a `MeshCliError` naming what it looked at, instead
|
|
463
|
-
of silently defaulting to the hub tenant `"mesh"`. The shared `config.ts` must
|
|
464
|
-
export the documented shape — `export const config = { tenant: … }` or
|
|
465
|
-
`export default { … }`; a renamed or re-exported const is not scanned, and a
|
|
466
|
-
`tenant:` in a comment, in a nested ref, or in a second export cannot supply
|
|
467
|
-
one. `mesh tunnel` and the `secrets` commands forward their `--tenant` through
|
|
468
|
-
`options.tenant`; `db` subcommands take `--tenant` and `--env` together,
|
|
469
|
-
bypassing detection. Embedders should pass `options.tenant` or catch the error.
|
|
470
|
-
|
|
471
|
-
The two options are not interchangeable. `options.stack` is a real stack
|
|
472
|
-
selection — it names the one `Pulumi.<stack>.yaml` allowed to supply
|
|
473
|
-
`mesh:tenant`, and no match means none is read. `stageArg` is only a hint:
|
|
474
|
-
`mesh tunnel -e <env>` passes a platform env, which names no stack file, so
|
|
475
|
-
tunnel passes no hint at all and lets the stack file supply the stage.
|
|
476
|
-
|
|
477
|
-
The returned `platformEnv` is `envMap[stage] ?? defaultEnv` from the config's
|
|
478
|
-
`platform` block — the same expression the config's own `getPlatform()`
|
|
479
|
-
evaluates, so the CLI addresses the environment the stack was deployed
|
|
480
|
-
against. `detectedTenant` carries what detection produced before any override,
|
|
481
|
-
and a `--tenant`/`MESH_TENANT` that disagrees with it warns: paths follow the
|
|
482
|
-
override, implicit AWS credentials do not.
|
|
486
|
+
`@mesh-tech/mesh-cli` re-exports its utils (`detectContext`, `getPlatformBastionInfo`, credentials,
|
|
487
|
+
pulumi, vpn, log helpers). `detectContext` fails closed when no source declares a tenant. Full
|
|
488
|
+
contract: [`references/programmatic-api.md`](references/programmatic-api.md).
|
|
483
489
|
|
|
484
490
|
## See also
|
|
485
491
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Programmatic API
|
|
2
|
+
|
|
3
|
+
`@mesh-tech/mesh-cli` re-exports its utils (`src/utils/index.ts`):
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { detectContext, getPlatformBastionInfo } from "@mesh-tech/mesh-cli";
|
|
7
|
+
const ctx = detectContext(); // { tenant, stage, ... }
|
|
8
|
+
const bastion = await getPlatformBastionInfo(ctx.tenant, ctx.platformEnv);
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Exports include `detectContext`, `getPlatformBastionInfo` (bastion), credentials,
|
|
12
|
+
pulumi, vpn, and log helpers.
|
|
13
|
+
|
|
14
|
+
`detectContext(stageArg?, { tenant?, stack? })` fails closed: when no source
|
|
15
|
+
declares a tenant it throws a `MeshCliError` naming what it looked at, instead
|
|
16
|
+
of silently defaulting to the hub tenant `"mesh"`. The shared `config.ts` must
|
|
17
|
+
export the documented shape — `export const config = { tenant: … }` or
|
|
18
|
+
`export default { … }`; a renamed or re-exported const is not scanned, and a
|
|
19
|
+
`tenant:` in a comment, in a nested ref, or in a second export cannot supply
|
|
20
|
+
one. `mesh tunnel` and the `secrets` commands forward their `--tenant` through
|
|
21
|
+
`options.tenant`; `db` subcommands take `--tenant` and `--env` together,
|
|
22
|
+
bypassing detection. Embedders should pass `options.tenant` or catch the error.
|
|
23
|
+
|
|
24
|
+
The two options are not interchangeable. `options.stack` is a real stack
|
|
25
|
+
selection — it names the one `Pulumi.<stack>.yaml` allowed to supply
|
|
26
|
+
`mesh:tenant`, and no match means none is read. `stageArg` is only a hint:
|
|
27
|
+
`mesh tunnel -e <env>` passes a platform env, which names no stack file, so
|
|
28
|
+
tunnel passes no hint at all and lets the stack file supply the stage.
|
|
29
|
+
|
|
30
|
+
The returned `platformEnv` is `envMap[stage] ?? defaultEnv` from the config's
|
|
31
|
+
`platform` block — the same expression the config's own `getPlatform()`
|
|
32
|
+
evaluates, so the CLI addresses the environment the stack was deployed
|
|
33
|
+
against. `detectedTenant` carries what detection produced before any override,
|
|
34
|
+
and a `--tenant`/`MESH_TENANT` that disagrees with it warns: paths follow the
|
|
35
|
+
override, implicit AWS credentials do not.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Concurrent `mesh dev` across git worktrees and sibling apps
|
|
2
|
+
|
|
3
|
+
Run the **same plain `mesh dev`** from each worktree's app dir — there is nothing special to
|
|
4
|
+
configure. `mesh dev` derives a deterministic token from the worktree path and threads it through
|
|
5
|
+
the tmux session name, the tmpdir session-state + env-file dir and the Temporal task-queue suffix, so
|
|
6
|
+
two worktrees don't kill each other's session — as long as their directory basenames differ, since the
|
|
7
|
+
session name carries the worktree's sanitized basename, not the hash. The reserved **service-port
|
|
8
|
+
block is derived from (worktree, app)**, not the worktree alone — a repo can hold several apps whose
|
|
9
|
+
sessions run concurrently (e.g. an apps repo's `worker-a` + `worker-b`), and one
|
|
10
|
+
block per worktree handed both the same ports.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# worktree A
|
|
14
|
+
cd .worktrees/feature-a/tests/tenants/acme/apps/demo-agent # monorepo test tenant
|
|
15
|
+
mesh dev --dry-run # preview: session name, port block, state/env paths, task queue
|
|
16
|
+
mesh dev # launch (attaches tmux; --headless to skip)
|
|
17
|
+
|
|
18
|
+
# worktree B — SAME command, different shell; auto-isolated
|
|
19
|
+
cd .worktrees/feature-b/tests/tenants/acme/apps/demo-agent
|
|
20
|
+
mesh dev
|
|
21
|
+
|
|
22
|
+
# sibling app in worktree A — same worktree, still its own port block
|
|
23
|
+
cd .worktrees/feature-a/tests/tenants/acme/apps/silverlake-demo
|
|
24
|
+
mesh dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- The **primary checkout** keeps its historical identity (`${project}-dev` session, the app's
|
|
28
|
+
preferred ports). Only **linked worktrees** get an offset session name + a port block in the 40000+
|
|
29
|
+
range. `mesh dev --dry-run` labels which you are (`primary checkout` vs `linked worktree (block N)`).
|
|
30
|
+
- **Port blocks are disjoint by default, not by construction.** The block is a hash bucketed into 63
|
|
31
|
+
slots, so two `(worktree, app)` pairs *can* land on the same block; the in-block free-port probe and
|
|
32
|
+
the ephemeral-port fallback are the backstop when they do — but that backstop is the same
|
|
33
|
+
check-then-bind probe that raced two still-booting sessions onto one port before #2889, so treat a
|
|
34
|
+
shared block as a live hazard, not a handled case. Two consequences worth knowing before you
|
|
35
|
+
debug a collision as a regression:
|
|
36
|
+
- Sibling apps in the **primary checkout** are deliberately excluded — they keep preferred-port
|
|
37
|
+
semantics (block 0) and rely on that probe, so run them from linked worktrees if you need them
|
|
38
|
+
isolated. This is the layout most tenant app repos use day to day.
|
|
39
|
+
- App-scoping raises the number of hashed entities from *#worktrees* to *#worktrees × #apps*, so
|
|
40
|
+
bucket collisions get likelier as you add apps.
|
|
41
|
+
|
|
42
|
+
A deterministic, collision-free allocator that also folds primary checkouts back in is planned.
|
|
43
|
+
- **Do NOT force a shared `--session`** across worktrees — that defeats the auto-isolation and
|
|
44
|
+
reintroduces the collision. Omit `--session` and let it derive per worktree.
|
|
45
|
+
- `mesh dev --kill` / `--status` act only on the current worktree's session.
|
|
46
|
+
- If the stack was last deployed from a *different* worktree, `mesh dev` **refuses** to launch a service
|
|
47
|
+
whose source resolves outside the current worktree (guards against silently running stale code) — re-run
|
|
48
|
+
`mesh deploy up` in this worktree.
|
|
49
|
+
- **Deploying isolated stacks per worktree?** The `mesh dev` isolation above is local-only. To
|
|
50
|
+
`mesh deploy up` a `deploy:false` personal stack from several worktrees at once, give each worktree
|
|
51
|
+
its own stack with `mesh stack init --worktree` — otherwise they collide on SSM export paths
|
|
52
|
+
(`ParameterAlreadyExists`) and the Temporal namespace (both derive from the stack name).
|
|
53
|
+
`mesh deploy up`/`destroy` **warns** when it sees a shared (non-worktree) stack name in a linked worktree.
|
|
@@ -187,7 +187,7 @@ fub | jq ...`) rather than pass `--json` — output auto-detects non-TTY.
|
|
|
187
187
|
|
|
188
188
|
## See also
|
|
189
189
|
|
|
190
|
-
- `packages/mesh-cli/skills/
|
|
190
|
+
- `packages/mesh-cli/skills/mesh/SKILL.md` — full `mesh` command reference,
|
|
191
191
|
including the `conversations`/`artifacts`/`temporal` rows this skill
|
|
192
192
|
builds on.
|
|
193
193
|
- `packages/agent-remote/agent-targets.example.json` — a worked registry
|
|
@@ -58,8 +58,9 @@ that Service's own ingress hostname; a Service that answers for more than one
|
|
|
58
58
|
hostname (`ingress.extraHostnames`) sets `auth.deriveRedirectUrl: true` so each
|
|
59
59
|
host gets its own callback, and registers a redirect URI per host at the IdP.
|
|
60
60
|
The proxy keeps each session in its cookie, which grows with the user's roles;
|
|
61
|
-
once a user holds enough of them
|
|
62
|
-
|
|
61
|
+
once a user holds enough of them every request fails — a `431` from the app,
|
|
62
|
+
which receives the cookie too, or a `400` from the load balancer.
|
|
63
|
+
`auth.sessionStore: "redis"` keeps sessions in a small Redis the Service
|
|
63
64
|
creates for itself, so the cookie stays under 1KB.
|
|
64
65
|
|
|
65
66
|
`index.ts` owns the whole access surface, so there is nothing to create by
|