@mesh-tech/mesh-cli 0.18.2 → 0.19.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/README.md +111 -56
- package/dist/bin/mesh.js +4843 -4077
- package/dist/bin/mesh.js.map +4 -4
- package/dist/build-info.json +2 -2
- package/dist/src/commands/create-app.d.ts +28 -0
- package/dist/src/commands/create-app.d.ts.map +1 -1
- package/dist/src/commands/create-app.js +45 -1
- package/dist/src/commands/create-app.js.map +1 -1
- package/dist/src/commands/dev-doctor.js +2 -2
- package/dist/src/commands/dev-doctor.js.map +1 -1
- package/dist/src/commands/init/wizard.d.ts +216 -0
- package/dist/src/commands/init/wizard.d.ts.map +1 -0
- package/dist/src/commands/init/wizard.js +471 -0
- package/dist/src/commands/init/wizard.js.map +1 -0
- package/dist/src/commands/init.d.ts +14 -6
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +41 -15
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/local/dev-local.d.ts.map +1 -1
- package/dist/src/commands/local/dev-local.js +6 -0
- package/dist/src/commands/local/dev-local.js.map +1 -1
- package/dist/src/commands/login.d.ts +76 -5
- package/dist/src/commands/login.d.ts.map +1 -1
- package/dist/src/commands/login.js +161 -60
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/commands/registry.d.ts +133 -10
- package/dist/src/commands/registry.d.ts.map +1 -1
- package/dist/src/commands/registry.js +238 -86
- package/dist/src/commands/registry.js.map +1 -1
- package/dist/src/commands/temporal.js +37 -23
- package/dist/src/commands/temporal.js.map +3 -3
- package/dist/src/docs/registry-docs.d.ts.map +1 -1
- package/dist/src/docs/registry-docs.js +8 -3
- package/dist/src/docs/registry-docs.js.map +1 -1
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +5 -1
- package/dist/src/program.js.map +1 -1
- package/dist/src/utils/auth-preflight.d.ts +51 -20
- package/dist/src/utils/auth-preflight.d.ts.map +1 -1
- package/dist/src/utils/auth-preflight.js +48 -21
- package/dist/src/utils/auth-preflight.js.map +1 -1
- package/dist/src/utils/mesh-json.d.ts +58 -0
- package/dist/src/utils/mesh-json.d.ts.map +1 -0
- package/dist/src/utils/mesh-json.js +85 -0
- package/dist/src/utils/mesh-json.js.map +1 -0
- package/dist/src/utils/registry-broker.d.ts +12 -29
- package/dist/src/utils/registry-broker.d.ts.map +1 -1
- package/dist/src/utils/registry-broker.js +12 -29
- package/dist/src/utils/registry-broker.js.map +1 -1
- package/dist/src/utils/registry-identity.d.ts +170 -0
- package/dist/src/utils/registry-identity.d.ts.map +1 -0
- package/dist/src/utils/registry-identity.js +273 -0
- package/dist/src/utils/registry-identity.js.map +1 -0
- package/package.json +3 -2
- package/skills/core/SKILL.md +6 -5
|
@@ -3,35 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the path that lets a brand-new Trabian developer install
|
|
5
5
|
* `@mesh-tech/*` with **no AWS account, no AWS CLI and no ~/.aws/config**: the
|
|
6
|
-
* broker verifies the Zitadel id token `mesh login`
|
|
7
|
-
* caller's role, and returns a CodeArtifact
|
|
8
|
-
* identity. No AWS credential of any kind
|
|
6
|
+
* broker verifies the Zitadel id token `mesh registry login` cached for the
|
|
7
|
+
* registry's own issuer, checks the caller's role, and returns a CodeArtifact
|
|
8
|
+
* token minted with the BROKER's AWS identity. No AWS credential of any kind
|
|
9
|
+
* reaches the laptop. Which broker to call comes from the registry's global
|
|
10
|
+
* identity (`registry-identity.ts`), never from a platform context.
|
|
9
11
|
*
|
|
10
12
|
* Everything here is pure or dependency-injected so the fallback behaviour —
|
|
11
13
|
* which is what decides whether a broker hiccup breaks an install — is
|
|
12
14
|
* unit-testable without a network.
|
|
13
15
|
*/
|
|
14
|
-
import { getContextConfig, getValidToken
|
|
15
|
-
/**
|
|
16
|
-
* Pick a context for a `mesh registry login` invoked with none.
|
|
17
|
-
*
|
|
18
|
-
* A context-less call used to skip the broker outright, which quietly sent
|
|
19
|
-
* anyone following a remediation that omits the context straight back to the
|
|
20
|
-
* AWS chain — the exact routing this feature removes. So: if exactly one cached
|
|
21
|
-
* context publishes a `registryBroker`, use it. Zero or several is genuinely
|
|
22
|
-
* ambiguous and the caller must say which.
|
|
23
|
-
*
|
|
24
|
-
* Deliberately narrow. Guessing among several contexts would post the user's id
|
|
25
|
-
* token to a broker they did not name.
|
|
26
|
-
*/
|
|
27
|
-
export function resolveDefaultBrokerContext(configs = readAllContextConfigs(), credentials = readAllCredentials()) {
|
|
28
|
-
const candidates = Object.entries(configs)
|
|
29
|
-
.filter(([name, cfg]) => Boolean(cfg?.registryBroker) && name in credentials)
|
|
30
|
-
.map(([name]) => name);
|
|
31
|
-
if (candidates.length === 1)
|
|
32
|
-
return { context: candidates[0] };
|
|
33
|
-
return { reason: candidates.length === 0 ? "none" : "ambiguous", candidates };
|
|
34
|
-
}
|
|
16
|
+
import { getContextConfig, getValidToken } from "../commands/login.js";
|
|
35
17
|
/**
|
|
36
18
|
* Resolve the broker URL, preferring the platform-published value from
|
|
37
19
|
* discovery (`ContextConfig.registryBroker`) and falling back to DNS-convention
|
|
@@ -141,12 +123,13 @@ export async function classifyBrokerResponse(res) {
|
|
|
141
123
|
};
|
|
142
124
|
}
|
|
143
125
|
/**
|
|
144
|
-
* Exchange the cached Zitadel id token
|
|
126
|
+
* Exchange the cached Zitadel id token under `context` (the reserved registry
|
|
127
|
+
* key, or any cached context whose issuer matches the registry's) for a
|
|
128
|
+
* CodeArtifact read grant.
|
|
145
129
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* device-code poll.
|
|
130
|
+
* The token lookup never prompts — an expired session with no refresh token
|
|
131
|
+
* comes back as `no-session` for the caller to decide about, rather than
|
|
132
|
+
* stalling a headless run in a device-code poll.
|
|
150
133
|
*/
|
|
151
134
|
export async function fetchRegistryGrant(context, brokerUrl, deps) {
|
|
152
135
|
const token = await deps.getValidToken(context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-broker.js","sourceRoot":"","sources":["../../../src/utils/registry-broker.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"registry-broker.js","sourceRoot":"","sources":["../../../src/utils/registry-broker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE identity behind `@mesh-tech` package access.
|
|
3
|
+
*
|
|
4
|
+
* Every reader of the registry — `pnpm install`, `mesh create-app`,
|
|
5
|
+
* `mesh docs start`, the Hub image build under `mesh start` — talks to the same
|
|
6
|
+
* CodeArtifact domain, and that domain is owned by exactly one Mesh platform:
|
|
7
|
+
* `dev.platform.meshtech.io`. So the registry has a single, global identity
|
|
8
|
+
* (issuer + CLI client id + broker URL) that never depends on which tenant
|
|
9
|
+
* platform, if any, the developer deploys to.
|
|
10
|
+
*
|
|
11
|
+
* This module keeps that identity separate from platform contexts:
|
|
12
|
+
*
|
|
13
|
+
* - The domain comes from the compiled-in first-party alias table and nowhere
|
|
14
|
+
* else — not an argument, not an environment variable, not an unpinned
|
|
15
|
+
* config value. That is the same trust anchor `mesh login` relies on, so the
|
|
16
|
+
* ≥2-dot anti-phishing guard stays intact.
|
|
17
|
+
* - The Zitadel session for the registry is cached under a **reserved**
|
|
18
|
+
* credential key (`registry`) that no `<tenant>.<env>` context can collide
|
|
19
|
+
* with, and `mesh login` / `mesh logout` refuse it.
|
|
20
|
+
* - A developer who already signed in to the registry's own platform
|
|
21
|
+
* (`mesh login mesh.dev`) is reused rather than sent through the browser a
|
|
22
|
+
* second time: any cached session whose issuer matches the registry's counts.
|
|
23
|
+
*
|
|
24
|
+
* Nothing here touches `~/.npmrc` or the broker — that is `mesh registry
|
|
25
|
+
* login`'s job. This module only answers "who is the registry?" and "do I hold
|
|
26
|
+
* a session for it?".
|
|
27
|
+
*/
|
|
28
|
+
import { MeshCliError } from "./errors.js";
|
|
29
|
+
/**
|
|
30
|
+
* The first-party alias of the platform that owns the `@mesh-tech` registry.
|
|
31
|
+
* The alias is the key in `FIRST_PARTY_CONTEXTS`; the domain is looked up
|
|
32
|
+
* from that reviewed table, never typed here twice.
|
|
33
|
+
*/
|
|
34
|
+
export declare const REGISTRY_ALIAS = "mesh.dev";
|
|
35
|
+
/** The pinned domain anonymous discovery runs against, e.g. `dev.platform.meshtech.io`. */
|
|
36
|
+
export declare const REGISTRY_DOMAIN: string;
|
|
37
|
+
/**
|
|
38
|
+
* The reserved context name the registry session and identity are cached under
|
|
39
|
+
* in `credentials.json` / `config.json`. Deliberately has no dot, so it can
|
|
40
|
+
* never parse as a `<tenant>.<env>` platform context.
|
|
41
|
+
*/
|
|
42
|
+
export declare const REGISTRY_CREDENTIAL_KEY: string;
|
|
43
|
+
/** True when a context name is the reserved registry key. */
|
|
44
|
+
export declare function isReservedRegistryContext(context: string): boolean;
|
|
45
|
+
/** What the registry's platform publishes about itself, plus when we fetched it. */
|
|
46
|
+
export interface RegistryIdentity {
|
|
47
|
+
/** Zitadel issuer URL for the registry's platform. */
|
|
48
|
+
issuer: string;
|
|
49
|
+
/** The CLI's OIDC client id on that issuer. */
|
|
50
|
+
clientId: string;
|
|
51
|
+
/** The `registry-auth` broker that exchanges an id token for a CodeArtifact grant. */
|
|
52
|
+
registryBroker: string;
|
|
53
|
+
/** ISO timestamp of the discovery that produced this record. */
|
|
54
|
+
discoveredAt: string;
|
|
55
|
+
}
|
|
56
|
+
/** Raised when the registry's identity cannot be resolved (offline, endpoint down). */
|
|
57
|
+
export declare class RegistryIdentityError extends MeshCliError {
|
|
58
|
+
constructor(detail: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the registry's identity: the cached record under the reserved key,
|
|
62
|
+
* or a fresh anonymous HTTPS discovery on the pinned domain which is then
|
|
63
|
+
* cached there. `refresh: true` skips the cache.
|
|
64
|
+
*
|
|
65
|
+
* Never consults SSM and never takes the domain from anything but the alias
|
|
66
|
+
* table. Throws `RegistryIdentityError` when discovery is impossible and
|
|
67
|
+
* nothing is cached.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const { registryBroker } = await resolveRegistryIdentity();
|
|
71
|
+
* // → "https://registry-auth.dev.platform.meshtech.io"
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveRegistryIdentity(opts?: {
|
|
74
|
+
refresh?: boolean;
|
|
75
|
+
}): Promise<RegistryIdentity>;
|
|
76
|
+
/**
|
|
77
|
+
* The cached contexts whose session can stand in for the registry's: the
|
|
78
|
+
* reserved key first, then the registry's own alias and domain — but only
|
|
79
|
+
* when the cached config for that name points at the SAME issuer, so a
|
|
80
|
+
* pre-repoint `mesh.dev` entry (a different platform) is never mistaken for
|
|
81
|
+
* the registry. With no known issuer (nothing discovered yet) only the
|
|
82
|
+
* reserved key counts: a guess about which platform session "is" the
|
|
83
|
+
* registry is the mistake this function exists to prevent.
|
|
84
|
+
*
|
|
85
|
+
* Pure over the two files' contents; exported for tests.
|
|
86
|
+
*/
|
|
87
|
+
export declare function registrySessionCandidates(issuer: string | null, configs?: Record<string, {
|
|
88
|
+
issuer?: string;
|
|
89
|
+
} | undefined>, credentials?: Record<string, unknown>): string[];
|
|
90
|
+
/** A live registry session and the context key it is cached under. */
|
|
91
|
+
export interface RegistrySession {
|
|
92
|
+
/** Which `credentials.json` entry holds the session (reserved key, alias, or domain). */
|
|
93
|
+
context: string;
|
|
94
|
+
email: string;
|
|
95
|
+
expiresAt: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The registry session as cached right now — no refresh, no network. Null when
|
|
99
|
+
* no candidate context holds an unexpired token.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const s = readRegistrySession();
|
|
103
|
+
* if (s) console.log(`signed in as ${s.email}`);
|
|
104
|
+
*/
|
|
105
|
+
export declare function readRegistrySession(): RegistrySession | null;
|
|
106
|
+
/** Why `ensureRegistrySession` could not produce a session. */
|
|
107
|
+
export type RegistrySessionFailure = {
|
|
108
|
+
ok: false;
|
|
109
|
+
reason: "no-identity";
|
|
110
|
+
detail: string;
|
|
111
|
+
} | {
|
|
112
|
+
ok: false;
|
|
113
|
+
reason: "no-tty";
|
|
114
|
+
detail: string;
|
|
115
|
+
} | {
|
|
116
|
+
ok: false;
|
|
117
|
+
reason: "login-failed";
|
|
118
|
+
detail: string;
|
|
119
|
+
};
|
|
120
|
+
export type RegistrySessionResult = ({
|
|
121
|
+
ok: true;
|
|
122
|
+
} & RegistrySession) | RegistrySessionFailure;
|
|
123
|
+
/**
|
|
124
|
+
* Get a valid Zitadel session for the registry's issuer, signing in when
|
|
125
|
+
* there is none.
|
|
126
|
+
*
|
|
127
|
+
* Reuses ANY cached session whose issuer matches — a developer already signed
|
|
128
|
+
* in to `mesh.dev` gets no second browser trip — refreshing an expired one
|
|
129
|
+
* through its refresh token first. Only when no candidate can be refreshed
|
|
130
|
+
* does it run the sign-in (browser PKCE, or device code with `device: true`
|
|
131
|
+
* or in a remote/non-TTY environment) and store the result under the
|
|
132
|
+
* reserved key.
|
|
133
|
+
*
|
|
134
|
+
* `interactive: false` means "cached or nothing": callers that cannot open a
|
|
135
|
+
* browser or print a device code get `no-tty` back instead of a hung prompt.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const session = await ensureRegistrySession({ interactive: true });
|
|
139
|
+
* if (session.ok) console.log(session.email);
|
|
140
|
+
*/
|
|
141
|
+
export declare function ensureRegistrySession(opts: {
|
|
142
|
+
device?: boolean;
|
|
143
|
+
interactive: boolean;
|
|
144
|
+
}): Promise<RegistrySessionResult>;
|
|
145
|
+
/**
|
|
146
|
+
* Forget the registry: the session AND the pinned identity record, so the
|
|
147
|
+
* next `mesh registry login` re-discovers the registry from the pinned
|
|
148
|
+
* domain. Clears the reserved key only — a platform session under `mesh.dev`
|
|
149
|
+
* that happened to double as the registry's is a platform login and stays.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* clearRegistrySession(); // `mesh registry logout`
|
|
153
|
+
*/
|
|
154
|
+
export declare function clearRegistrySession(): void;
|
|
155
|
+
/**
|
|
156
|
+
* True when two identity records disagree on anything that matters for a
|
|
157
|
+
* broker call — the signal that a re-discovery is worth a retry.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* identityChanged(a, { ...a, registryBroker: "https://moved" }); // → true
|
|
161
|
+
*/
|
|
162
|
+
export declare function identityChanged(before: RegistryIdentity, after: RegistryIdentity): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Human-readable time left on a session, e.g. `11h 42m`; `expired` when the
|
|
165
|
+
* expiry has passed. Pure; exported for the status line's test.
|
|
166
|
+
*/
|
|
167
|
+
export declare function describeSessionExpiry(expiresAt: string, now?: number): string;
|
|
168
|
+
/** Log the one-line identity banner `mesh registry login` opens with. */
|
|
169
|
+
export declare function logRegistryIdentity(identity: RegistryIdentity): void;
|
|
170
|
+
//# sourceMappingURL=registry-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-identity.d.ts","sourceRoot":"","sources":["../../../src/utils/registry-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAmBH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;;GAIG;AACH,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC,2FAA2F;AAC3F,eAAO,MAAM,eAAe,EAAE,MAM1B,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAkC,CAAC;AAEzE,6DAA6D;AAC7D,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;CACtB;AASD,uFAAuF;AACvF,qBAAa,qBAAsB,SAAQ,YAAY;gBACzC,MAAM,EAAE,MAAM;CAQ3B;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqBzG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAA2B,EAClF,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAwB,GAC1D,MAAM,EAAE,CAQV;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CAS5D;AAED,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,qBAAqB,GAAG,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,eAAe,CAAC,GAAG,sBAAsB,CAAC;AAE9F;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAuDjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAM1F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,CAOjF;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAQpE"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE identity behind `@mesh-tech` package access.
|
|
3
|
+
*
|
|
4
|
+
* Every reader of the registry — `pnpm install`, `mesh create-app`,
|
|
5
|
+
* `mesh docs start`, the Hub image build under `mesh start` — talks to the same
|
|
6
|
+
* CodeArtifact domain, and that domain is owned by exactly one Mesh platform:
|
|
7
|
+
* `dev.platform.meshtech.io`. So the registry has a single, global identity
|
|
8
|
+
* (issuer + CLI client id + broker URL) that never depends on which tenant
|
|
9
|
+
* platform, if any, the developer deploys to.
|
|
10
|
+
*
|
|
11
|
+
* This module keeps that identity separate from platform contexts:
|
|
12
|
+
*
|
|
13
|
+
* - The domain comes from the compiled-in first-party alias table and nowhere
|
|
14
|
+
* else — not an argument, not an environment variable, not an unpinned
|
|
15
|
+
* config value. That is the same trust anchor `mesh login` relies on, so the
|
|
16
|
+
* ≥2-dot anti-phishing guard stays intact.
|
|
17
|
+
* - The Zitadel session for the registry is cached under a **reserved**
|
|
18
|
+
* credential key (`registry`) that no `<tenant>.<env>` context can collide
|
|
19
|
+
* with, and `mesh login` / `mesh logout` refuse it.
|
|
20
|
+
* - A developer who already signed in to the registry's own platform
|
|
21
|
+
* (`mesh login mesh.dev`) is reused rather than sent through the browser a
|
|
22
|
+
* second time: any cached session whose issuer matches the registry's counts.
|
|
23
|
+
*
|
|
24
|
+
* Nothing here touches `~/.npmrc` or the broker — that is `mesh registry
|
|
25
|
+
* login`'s job. This module only answers "who is the registry?" and "do I hold
|
|
26
|
+
* a session for it?".
|
|
27
|
+
*/
|
|
28
|
+
import { clearContextConfig, clearCredentials, discoverConfigFromWellKnown, getContextConfig, getValidToken, readAllContextConfigs, readAllCredentials, readCredentials, runLoginFlow, tokenStillValid, writeContextConfig, RESERVED_REGISTRY_CONTEXT, } from "../commands/login.js";
|
|
29
|
+
import { firstPartyDomainFor } from "./first-party-contexts.js";
|
|
30
|
+
import { deriveRegistryBroker } from "./registry-broker.js";
|
|
31
|
+
import { MeshCliError } from "./errors.js";
|
|
32
|
+
import { logInfo } from "./log.js";
|
|
33
|
+
/**
|
|
34
|
+
* The first-party alias of the platform that owns the `@mesh-tech` registry.
|
|
35
|
+
* The alias is the key in `FIRST_PARTY_CONTEXTS`; the domain is looked up
|
|
36
|
+
* from that reviewed table, never typed here twice.
|
|
37
|
+
*/
|
|
38
|
+
export const REGISTRY_ALIAS = "mesh.dev";
|
|
39
|
+
/** The pinned domain anonymous discovery runs against, e.g. `dev.platform.meshtech.io`. */
|
|
40
|
+
export const REGISTRY_DOMAIN = (() => {
|
|
41
|
+
const domain = firstPartyDomainFor(REGISTRY_ALIAS);
|
|
42
|
+
if (!domain) {
|
|
43
|
+
throw new Error(`FIRST_PARTY_CONTEXTS has no entry for the registry alias "${REGISTRY_ALIAS}"`);
|
|
44
|
+
}
|
|
45
|
+
return domain;
|
|
46
|
+
})();
|
|
47
|
+
/**
|
|
48
|
+
* The reserved context name the registry session and identity are cached under
|
|
49
|
+
* in `credentials.json` / `config.json`. Deliberately has no dot, so it can
|
|
50
|
+
* never parse as a `<tenant>.<env>` platform context.
|
|
51
|
+
*/
|
|
52
|
+
export const REGISTRY_CREDENTIAL_KEY = RESERVED_REGISTRY_CONTEXT;
|
|
53
|
+
/** True when a context name is the reserved registry key. */
|
|
54
|
+
export function isReservedRegistryContext(context) {
|
|
55
|
+
return context === REGISTRY_CREDENTIAL_KEY;
|
|
56
|
+
}
|
|
57
|
+
/** Raised when the registry's identity cannot be resolved (offline, endpoint down). */
|
|
58
|
+
export class RegistryIdentityError extends MeshCliError {
|
|
59
|
+
constructor(detail) {
|
|
60
|
+
super(`Could not resolve the Mesh package registry (${detail}).\n` +
|
|
61
|
+
` Discovery endpoint: https://cli.${REGISTRY_DOMAIN}/.well-known/mesh.json`, { remediation: { command: "mesh registry login # retry once you are online" } });
|
|
62
|
+
this.name = "RegistryIdentityError";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function toIdentity(cfg) {
|
|
66
|
+
if (!cfg?.issuer || !cfg.clientId)
|
|
67
|
+
return null;
|
|
68
|
+
const registryBroker = cfg.registryBroker ?? deriveRegistryBroker(REGISTRY_CREDENTIAL_KEY, () => cfg);
|
|
69
|
+
if (!registryBroker)
|
|
70
|
+
return null;
|
|
71
|
+
return {
|
|
72
|
+
issuer: cfg.issuer,
|
|
73
|
+
clientId: cfg.clientId,
|
|
74
|
+
registryBroker,
|
|
75
|
+
discoveredAt: cfg.discoveredAt ?? new Date(0).toISOString(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the registry's identity: the cached record under the reserved key,
|
|
80
|
+
* or a fresh anonymous HTTPS discovery on the pinned domain which is then
|
|
81
|
+
* cached there. `refresh: true` skips the cache.
|
|
82
|
+
*
|
|
83
|
+
* Never consults SSM and never takes the domain from anything but the alias
|
|
84
|
+
* table. Throws `RegistryIdentityError` when discovery is impossible and
|
|
85
|
+
* nothing is cached.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* const { registryBroker } = await resolveRegistryIdentity();
|
|
89
|
+
* // → "https://registry-auth.dev.platform.meshtech.io"
|
|
90
|
+
*/
|
|
91
|
+
export async function resolveRegistryIdentity(opts = {}) {
|
|
92
|
+
if (!opts.refresh) {
|
|
93
|
+
const cached = toIdentity(getContextConfig(REGISTRY_CREDENTIAL_KEY));
|
|
94
|
+
if (cached)
|
|
95
|
+
return cached;
|
|
96
|
+
}
|
|
97
|
+
const discovered = await discoverConfigFromWellKnown(REGISTRY_DOMAIN, REGISTRY_CREDENTIAL_KEY, {
|
|
98
|
+
quiet: true,
|
|
99
|
+
});
|
|
100
|
+
if (!discovered) {
|
|
101
|
+
throw new RegistryIdentityError(`discovery on ${REGISTRY_DOMAIN} failed — are you online?`);
|
|
102
|
+
}
|
|
103
|
+
const stored = { ...discovered, discoveredAt: new Date().toISOString() };
|
|
104
|
+
const identity = toIdentity(stored);
|
|
105
|
+
if (!identity) {
|
|
106
|
+
throw new RegistryIdentityError(`the discovery document names no registry broker`);
|
|
107
|
+
}
|
|
108
|
+
writeContextConfig(REGISTRY_CREDENTIAL_KEY, stored);
|
|
109
|
+
return identity;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The cached contexts whose session can stand in for the registry's: the
|
|
113
|
+
* reserved key first, then the registry's own alias and domain — but only
|
|
114
|
+
* when the cached config for that name points at the SAME issuer, so a
|
|
115
|
+
* pre-repoint `mesh.dev` entry (a different platform) is never mistaken for
|
|
116
|
+
* the registry. With no known issuer (nothing discovered yet) only the
|
|
117
|
+
* reserved key counts: a guess about which platform session "is" the
|
|
118
|
+
* registry is the mistake this function exists to prevent.
|
|
119
|
+
*
|
|
120
|
+
* Pure over the two files' contents; exported for tests.
|
|
121
|
+
*/
|
|
122
|
+
export function registrySessionCandidates(issuer, configs = readAllContextConfigs(), credentials = readAllCredentials()) {
|
|
123
|
+
const names = issuer ? [REGISTRY_CREDENTIAL_KEY, REGISTRY_ALIAS, REGISTRY_DOMAIN] : [REGISTRY_CREDENTIAL_KEY];
|
|
124
|
+
return names.filter((name) => {
|
|
125
|
+
if (!(name in credentials))
|
|
126
|
+
return false;
|
|
127
|
+
const cfg = configs[name];
|
|
128
|
+
if (!cfg?.issuer)
|
|
129
|
+
return false;
|
|
130
|
+
return issuer ? cfg.issuer === issuer : true;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The registry session as cached right now — no refresh, no network. Null when
|
|
135
|
+
* no candidate context holds an unexpired token.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const s = readRegistrySession();
|
|
139
|
+
* if (s) console.log(`signed in as ${s.email}`);
|
|
140
|
+
*/
|
|
141
|
+
export function readRegistrySession() {
|
|
142
|
+
const identity = toIdentity(getContextConfig(REGISTRY_CREDENTIAL_KEY));
|
|
143
|
+
for (const context of registrySessionCandidates(identity?.issuer ?? null)) {
|
|
144
|
+
const creds = readCredentials(context);
|
|
145
|
+
if (creds && tokenStillValid(creds.expiresAt)) {
|
|
146
|
+
return { context, email: creds.email ?? "unknown", expiresAt: creds.expiresAt };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get a valid Zitadel session for the registry's issuer, signing in when
|
|
153
|
+
* there is none.
|
|
154
|
+
*
|
|
155
|
+
* Reuses ANY cached session whose issuer matches — a developer already signed
|
|
156
|
+
* in to `mesh.dev` gets no second browser trip — refreshing an expired one
|
|
157
|
+
* through its refresh token first. Only when no candidate can be refreshed
|
|
158
|
+
* does it run the sign-in (browser PKCE, or device code with `device: true`
|
|
159
|
+
* or in a remote/non-TTY environment) and store the result under the
|
|
160
|
+
* reserved key.
|
|
161
|
+
*
|
|
162
|
+
* `interactive: false` means "cached or nothing": callers that cannot open a
|
|
163
|
+
* browser or print a device code get `no-tty` back instead of a hung prompt.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* const session = await ensureRegistrySession({ interactive: true });
|
|
167
|
+
* if (session.ok) console.log(session.email);
|
|
168
|
+
*/
|
|
169
|
+
export async function ensureRegistrySession(opts) {
|
|
170
|
+
let identity;
|
|
171
|
+
try {
|
|
172
|
+
identity = await resolveRegistryIdentity();
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
return { ok: false, reason: "no-identity", detail: err.message };
|
|
176
|
+
}
|
|
177
|
+
for (const context of registrySessionCandidates(identity.issuer)) {
|
|
178
|
+
const token = await getValidToken(context);
|
|
179
|
+
if (!token)
|
|
180
|
+
continue;
|
|
181
|
+
const creds = readCredentials(context);
|
|
182
|
+
if (creds)
|
|
183
|
+
return { ok: true, context, email: creds.email ?? "unknown", expiresAt: creds.expiresAt };
|
|
184
|
+
}
|
|
185
|
+
if (!opts.interactive) {
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
reason: "no-tty",
|
|
189
|
+
detail: "no registry session is cached and this run cannot open a sign-in",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// The reserved key gets the registry's identity as its config so the
|
|
193
|
+
// ordinary token machinery (refresh included) works for it unchanged.
|
|
194
|
+
const stored = getContextConfig(REGISTRY_CREDENTIAL_KEY);
|
|
195
|
+
if (!stored) {
|
|
196
|
+
writeContextConfig(REGISTRY_CREDENTIAL_KEY, {
|
|
197
|
+
issuer: identity.issuer,
|
|
198
|
+
clientId: identity.clientId,
|
|
199
|
+
registryBroker: identity.registryBroker,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// Said here, not by the caller: only now is it known that no cached session
|
|
203
|
+
// could be refreshed, so this line never precedes a silent refresh.
|
|
204
|
+
logInfo(opts.device
|
|
205
|
+
? "No registry session — sign in with the device code below…"
|
|
206
|
+
: "No registry session — opening your browser to sign in… (headless? use --device)");
|
|
207
|
+
try {
|
|
208
|
+
await runLoginFlow(REGISTRY_CREDENTIAL_KEY, identity, { device: opts.device });
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
return { ok: false, reason: "login-failed", detail: err.message };
|
|
212
|
+
}
|
|
213
|
+
const creds = readCredentials(REGISTRY_CREDENTIAL_KEY);
|
|
214
|
+
if (!creds) {
|
|
215
|
+
return { ok: false, reason: "login-failed", detail: "sign-in completed but no session was cached" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
context: REGISTRY_CREDENTIAL_KEY,
|
|
220
|
+
email: creds.email ?? "unknown",
|
|
221
|
+
expiresAt: creds.expiresAt,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Forget the registry: the session AND the pinned identity record, so the
|
|
226
|
+
* next `mesh registry login` re-discovers the registry from the pinned
|
|
227
|
+
* domain. Clears the reserved key only — a platform session under `mesh.dev`
|
|
228
|
+
* that happened to double as the registry's is a platform login and stays.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* clearRegistrySession(); // `mesh registry logout`
|
|
232
|
+
*/
|
|
233
|
+
export function clearRegistrySession() {
|
|
234
|
+
clearCredentials(REGISTRY_CREDENTIAL_KEY);
|
|
235
|
+
clearContextConfig(REGISTRY_CREDENTIAL_KEY);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* True when two identity records disagree on anything that matters for a
|
|
239
|
+
* broker call — the signal that a re-discovery is worth a retry.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* identityChanged(a, { ...a, registryBroker: "https://moved" }); // → true
|
|
243
|
+
*/
|
|
244
|
+
export function identityChanged(before, after) {
|
|
245
|
+
return (before.issuer !== after.issuer ||
|
|
246
|
+
before.clientId !== after.clientId ||
|
|
247
|
+
before.registryBroker !== after.registryBroker);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Human-readable time left on a session, e.g. `11h 42m`; `expired` when the
|
|
251
|
+
* expiry has passed. Pure; exported for the status line's test.
|
|
252
|
+
*/
|
|
253
|
+
export function describeSessionExpiry(expiresAt, now = Date.now()) {
|
|
254
|
+
const ms = new Date(expiresAt).getTime() - now;
|
|
255
|
+
if (!(ms > 0))
|
|
256
|
+
return "expired";
|
|
257
|
+
const totalMinutes = Math.floor(ms / 60_000);
|
|
258
|
+
const h = Math.floor(totalMinutes / 60);
|
|
259
|
+
const m = totalMinutes % 60;
|
|
260
|
+
return h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
261
|
+
}
|
|
262
|
+
/** Log the one-line identity banner `mesh registry login` opens with. */
|
|
263
|
+
export function logRegistryIdentity(identity) {
|
|
264
|
+
let host = identity.registryBroker;
|
|
265
|
+
try {
|
|
266
|
+
host = new URL(identity.registryBroker).host;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// keep the raw value when it is not a URL
|
|
270
|
+
}
|
|
271
|
+
logInfo(`Mesh package registry: mesh-platform (broker ${host})`);
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=registry-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-identity.js","sourceRoot":"","sources":["../../../src/utils/registry-identity.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mesh-tech/mesh-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "CLI for Mesh platform development utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@mesh-tech/agent-contracts": "0.2.0",
|
|
45
|
-
"@mesh-tech/tsconfig": "2.
|
|
45
|
+
"@mesh-tech/tsconfig": "2.2.0",
|
|
46
46
|
"@mesh-tech/workflow-viz": "0.3.0",
|
|
47
47
|
"@tanstack/intent": "^0.3.6",
|
|
48
48
|
"@types/node": "^20.0.0",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@aws-sdk/client-ssm": "^3.700.0",
|
|
55
55
|
"@aws-sdk/client-sts": "^3.700.0",
|
|
56
56
|
"@inquirer/checkbox": "^4.2.0",
|
|
57
|
+
"@inquirer/confirm": "^5.1.14",
|
|
57
58
|
"@inquirer/input": "^4.2.1",
|
|
58
59
|
"@inquirer/password": "^4.0.17",
|
|
59
60
|
"@inquirer/select": "^4.3.1",
|
package/skills/core/SKILL.md
CHANGED
|
@@ -90,7 +90,7 @@ Top-level commands (each `register*` is wired in `src/program.ts`, which `bin/me
|
|
|
90
90
|
| Command | Subcommands | Does | Key flags / args |
|
|
91
91
|
|---|---|---|---|
|
|
92
92
|
| `mesh login <context>` | (also `mesh logout <context>`) | Zitadel OIDC PKCE or device-code auth; caches creds in `~/.config/mesh/`. Context like `mesh.dev`. Auto-discovers config via SSM then HTTPS `.well-known/mesh.json`. | `--status` (reports the cached Zitadel session, or — when there isn't one — the AWS identity the credential chain resolves, so an SSO profile or a key/secret pair passes too; non-zero only when neither works), `--device`, `--export` (print AWS `export`s for `eval`), `--role <arn>`, `--region <r>` |
|
|
93
|
-
| `mesh registry` | `login [context]`, `status`, `publish` | CodeArtifact auth → refreshes `~/.npmrc` token (12h) + adds `@mesh-tech` scope to project `.npmrc` + **strips an unscoped CodeArtifact `registry=` line from `~/.npmrc`** (after writing `~/.npmrc.bak-<ts>`) — that line is what a raw CodeArtifact sign-in *without* `--namespace @mesh-tech` leaves behind, and it makes CodeArtifact your DEFAULT registry so every *public* install 401s at the 12h expiry. Skipped under `--ci`, where the unscoped login is deliberate. `status` probes the registry rather than grepping the file: expired/missing token or a hijack exits 1, an unreachable registry warns and exits 0. **Step 0 is the Zitadel-gated broker:**
|
|
93
|
+
| `mesh registry` | `login [context]`, `logout`, `status`, `publish` | CodeArtifact auth → refreshes `~/.npmrc` token (12h) + adds `@mesh-tech` scope to project `.npmrc` + **strips an unscoped CodeArtifact `registry=` line from `~/.npmrc`** (after writing `~/.npmrc.bak-<ts>`) — that line is what a raw CodeArtifact sign-in *without* `--namespace @mesh-tech` leaves behind, and it makes CodeArtifact your DEFAULT registry so every *public* install 401s at the 12h expiry. Skipped under `--ci`, where the unscoped login is deliberate. `status` probes the registry rather than grepping the file: expired/missing token or a hijack exits 1, an unreachable registry warns and exits 0. **Step 0 is the Zitadel-gated broker:** the registry is ONE global identity (`utils/registry-identity.ts`, seeded from the compiled-in first-party alias table, session cached under the reserved key `registry`), so no context is needed — `mesh registry login` signs in to the registry's issuer if nothing is cached (browser; `--device` when headless; reuses a `mesh.dev` session on the same issuer) and POSTs the id token to the platform's `registry-auth` service, which returns a CodeArtifact read token minted with its OWN AWS identity — so this path needs no AWS account, CLI or profile at all. It writes the same two `~/.npmrc` lines the AWS path does. Falls back to the AWS chain when the platform publishes no broker, when the broker is unreachable, or with `--no-broker`; a 403 (a valid Mesh account without the `registry:read` role) is fatal and does **not** fall through. `--publish` and `--profile` deliberately skip the broker — both are AWS-identity paths. `--profile mesh-dev` auto-runs the AWS SSO login when the session is stale. `mesh login` never touches the registry (it prints a one-line pointer to `mesh registry login` when the machine has no registry access); `logout` clears the registry session only. A `[context]` on the read path is ignored with a notice; it still selects the SSM export for `--publish`. `login --publish` assumes the registry's publisher role (from `--role` or the registry SSM export); `publish` pushes package snapshots. | `login`: `--device`, `--no-broker`, `--profile <sso>`, `--role <arn>`, `--ci`, `--skip-npmrc`, `--publish`; `publish`: `--snapshot` |
|
|
94
94
|
| `mesh db` | `connect`, `credentials`(alias `creds`), `env`, `psql`, `exec <command...>` | Access tenant DBs through an SSM bastion tunnel. `env` prints `export`s for `eval`; `exec` runs a command with `DATABASE_URL` injected. | `-t/--tenant`, `-e/--env`, `-a/--app`, `--app-tenant`, `--app-stage`; `exec` also: `--secret <arn>`, `--ssl <require\|no-verify\|disable>`, `--port <n>` |
|
|
95
95
|
| `mesh deploy ...` | passthrough | Runs `pulumi` with the stack's `mesh:deployerRole` assumed. Unknown opts/args pass through; defaults to `up` if no subcommand. | `-s/--stack <name>` (stripped before pulumi `up`); everything else → pulumi (`preview`, `up --yes`, `destroy`, `stack output …`) |
|
|
96
96
|
| `mesh dev` | `logs <service>`, `restart <service>`, `list`, `test-user [name]` | Two modes. **Tethered** (deployed stack): reads the Pulumi stack dev output, opens tunnels, injects Secrets Manager secrets; needs VPN/AWS creds. **Local** (`--local`, auto when the app has no `Pulumi.yaml`): synthesizes services from the app layout (subdirs with a `dev` script) and wires them to the `mesh start` platform (Temporal `local-dev`, app DB, Zitadel, ministack via `AWS_ENDPOINT_URL`) — zero AWS/VPN/Pulumi. `--externals` (local mode; `--mock` is an alias) also realizes the app's declared external services (package.json → `mesh.externals`; legacy `mesh.mocks` still read). Each declaration runs in one of three MODES — `mock` (the service is emulated: `openapi` → a Prism mock server from a spec, or `src` → a mock process), `local` (a local version of the real service: a `compose` file + published `port`, e.g. a vendor-shaped DB — up `--wait` before the app's services; a realization another checkout already started is **adopted** instead of failing — declared port already served **by a container this compose file defines** → adopt as-is (a publisher the file doesn't define — a native install or unrelated container — is a hard **conflict**, never adopted, since credentials would seed against the wrong service); a stopped foreign container holding the compose `container_name` → `docker start` + adopt — so startup order across checkouts doesn't matter; adopted externals stay out of session state and `--kill` tears down only externals this session created, volumes kept), `remote` (connect to the ACTUAL service: inline vendor `credentials`, or the credentials the app's tenant environment is configured with via `remote.env`). A declaration may carry SEVERAL realizations; `mode` picks the default and `--externals name=mode` overrides it per run (`--externals plaid-db=remote,plaid`). Remote externals get a blackbox uptime probe against their real endpoint. Failing to realize a remote external is fatal only when you named it; a bare `--externals` warns and keeps going, so the local loop still works with no AWS identity. Every realization seeds its ExternalService credential secret (`{{url}}`/`{{host}}`/`{{port}}` from the stand-in's endpoint, `{{env:VAR}}` from the shell) so `resolveCredentials()` runs the deployed code path against the stand-in. Runners: tmux (default) or `--runner docker` (CI/headless — services run as a docker compose project with host networking; parity `--status`/`--kill`/`logs`/`restart`). | root: `--local`, `--externals [name[=mode],…]` (alias `--mock`), `--runner <tmux\|docker>`, `--app <path>`, `--stage`, `--headless`, `--kill`, `--status`, `--json`, `--session <name>`, `--profile <sso>`; `logs`: `--tail <n>`; `test-user`: `--tenant/--env/--region` |
|
|
@@ -98,7 +98,7 @@ Top-level commands (each `register*` is wired in `src/program.ts`, which `bin/me
|
|
|
98
98
|
| `mesh hub` | `dev` | One-command local Hub over a running dev-local stack. Launches the CURRENT-CODE Hub (apps/hub api+ui from a mesh-platform checkout) pointed at an existing `mesh dev` session — auto-assembles `HUB_TENANT`/`HUB_SCOPE_ENV`/`HUB_SCOPE_TENANTS` (session platform context), `TEMPORAL_ADDRESS` (session tunnel), `AWS_*` (session scratch profile), `DEV_USER_*`+`DEV_USER_TOKEN_URL` (session dev identity; starts its own token-server if the session predates it, so tokens stay fresh past ~1h). Refuses to launch with an empty tenant scope (which the Hub renders as silently blank). Runs in tmux session `mesh-hub-dev`. | `dev`: `--session <name>`, `--tenants <csv>`, `--port` (or `MESH_HUB_DEV_PORT`, default 9000; the containerized `mesh start` Hub uses the separate `MESH_HUB_PORT`), `--api-port <3002>`, `--platform-dir <dir>` (or `MESH_PLATFORM_DIR`), `--print-env`, `--kill` |
|
|
99
99
|
| `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
100
|
| `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`); a tenant-local auth lib is advisory (`LOCAL_AUTH_LIB`). 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
|
-
| `mesh init` | `app-tenant` |
|
|
101
|
+
| `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`), local-only vs deployed platform, 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 shape check, skills sync; writes `mesh.json` (`{tenant, platform}`) so `create-app` / `mesh dev` default `--tenant`; flags `--tenant --local\|--platform <env> --device --profile --skip-repo --yes --json`; exit 1 if any step failed, 130 on Ctrl-C. `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
102
|
| `mesh skills` | `sync` | Agent-skill distribution (D1/D2): installs the base building-with-Mesh skills into the repo's `.claude/skills/mesh-*/` (managed-marker files) 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
103
|
| `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
104
|
| `mesh stack` | `init`, `rm <name>` | `init` creates a personal dev 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). `rm <name>` removes a personal stack (backend state + local config), credentialed. Both assume the deployer role — no raw `pulumi` / manual creds needed. | `init`: `--from <stack>`, `--name <stack>`, `--worktree`; `rm`: `--yes` |
|
|
@@ -119,10 +119,11 @@ Top-level commands (each `register*` is wired in `src/program.ts`, which `bin/me
|
|
|
119
119
|
### 1. Onboard: auth + install dependencies
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
mesh login
|
|
123
|
-
mesh login
|
|
124
|
-
mesh
|
|
122
|
+
mesh registry login # ONE command: signs in (browser) + registry token via the Zitadel broker + project .npmrc scope — no AWS, no platform context
|
|
123
|
+
mesh registry login --device # same, device-code sign-in (browser callback times out over SSH/tailscale)
|
|
124
|
+
mesh init # guided: tenant, local-only vs deployed, registry access, repo bootstrap/doctor, skills → mesh.json
|
|
125
125
|
pnpm install # now resolves @mesh-tech/* from CodeArtifact
|
|
126
|
+
# mesh login <tenant>.<env> is for a DEPLOYED platform only (mesh deploy / Hub / VPN) — never needed for packages
|
|
126
127
|
```
|
|
127
128
|
|
|
128
129
|
### 1b. Run the platform fully locally (no cloud)
|