@mesh-tech/mesh-cli 0.24.1 → 0.26.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.
Files changed (55) hide show
  1. package/README.md +74 -246
  2. package/dist/bin/mesh.js +1451 -847
  3. package/dist/bin/mesh.js.map +3 -3
  4. package/dist/build-info.json +2 -2
  5. package/dist/src/commands/app-check.d.ts +2 -1
  6. package/dist/src/commands/app-check.js +27 -8
  7. package/dist/src/commands/db/axes.d.ts +19 -0
  8. package/dist/src/commands/db/axes.js +35 -0
  9. package/dist/src/commands/db/connect.d.ts +2 -4
  10. package/dist/src/commands/db/connect.js +4 -19
  11. package/dist/src/commands/db/credentials.d.ts +2 -5
  12. package/dist/src/commands/db/credentials.js +7 -23
  13. package/dist/src/commands/db/env.d.ts +2 -5
  14. package/dist/src/commands/db/env.js +7 -23
  15. package/dist/src/commands/db/exec.js +7 -24
  16. package/dist/src/commands/db/psql.d.ts +2 -5
  17. package/dist/src/commands/db/psql.js +8 -23
  18. package/dist/src/commands/init/platform-setup.d.ts +68 -0
  19. package/dist/src/commands/init/platform-setup.js +203 -0
  20. package/dist/src/commands/init/wizard.d.ts +20 -0
  21. package/dist/src/commands/init/wizard.js +89 -7
  22. package/dist/src/commands/init.js +42 -36
  23. package/dist/src/commands/login.d.ts +4 -0
  24. package/dist/src/commands/login.js +16 -2
  25. package/dist/src/commands/registry.d.ts +2 -2
  26. package/dist/src/commands/registry.js +11 -6
  27. package/dist/src/commands/secrets/context.d.ts +5 -0
  28. package/dist/src/commands/secrets/context.js +5 -0
  29. package/dist/src/commands/secrets/index.js +3 -0
  30. package/dist/src/commands/secrets/migrate-config.d.ts +1 -0
  31. package/dist/src/commands/secrets/migrate-config.js +2 -3
  32. package/dist/src/commands/secrets/reindex.d.ts +1 -0
  33. package/dist/src/commands/secrets/reindex.js +2 -2
  34. package/dist/src/commands/secrets/set.d.ts +1 -0
  35. package/dist/src/commands/secrets/set.js +2 -2
  36. package/dist/src/commands/temporal.js +34 -10
  37. package/dist/src/commands/temporal.js.map +2 -2
  38. package/dist/src/commands/tunnel/index.d.ts +8 -0
  39. package/dist/src/commands/tunnel/index.js +3 -3
  40. package/dist/src/index.js +324 -62
  41. package/dist/src/index.js.map +3 -3
  42. package/dist/src/utils/context.d.ts +6 -1
  43. package/dist/src/utils/context.js +302 -39
  44. package/dist/src/utils/registry-identity.d.ts +5 -0
  45. package/dist/src/utils/registry-identity.js +16 -0
  46. package/fragments/agents/AGENTS.md.hbs +14 -2
  47. package/fragments/base/index.ts.hbs +15 -5
  48. package/package.json +2 -2
  49. package/skills/core/SKILL.md +74 -7
  50. package/templates/api-auth/index.ts.hbs +5 -4
  51. package/templates/api-role-gating/index.ts.hbs +5 -4
  52. package/templates/external-service/index.ts.hbs +5 -4
  53. package/templates/platform-repo/README.md.hbs +17 -3
  54. package/templates/platform-repo/core/Pulumi.{{tenant}}-{{env}}.yaml.hbs +7 -0
  55. package/templates/workflow/index.ts.hbs +5 -4
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "8aff693d3",
3
- "builtAt": "2026-09-15T19:25:45.158Z"
2
+ "commit": "8c1e961c4",
3
+ "builtAt": "2026-09-17T07:12:18.568Z"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { jsonReport, type Check, type CheckResult, type CheckStatus } from "./dev-doctor.js";
3
- export type AppCheckCode = "UI_IN_API" | "NO_UI_SERVICE" | "NO_REGISTER" | "CUSTOM_SESSION_AUTH" | "NON_PLATFORM_LOGGER" | "LOCAL_AUTH_LIB" | "NO_AUTHZ_POINTER" | "METADATA_UNPUBLISHED" | "IAC_GRANTS" | "POINTER_PROJECT_OVERRIDE" | "PASSWORD_STORE" | "EMAIL_ALLOWLIST" | "USERS_TABLE" | "DOCS_SITE_PUBLIC" | "DOCS_AUDIENCE_IMPLICIT" | "DOCS_AUDIENCE_UNGRANTABLE";
3
+ export type AppCheckCode = "UI_IN_API" | "NO_UI_SERVICE" | "NO_REGISTER" | "CUSTOM_SESSION_AUTH" | "NON_PLATFORM_LOGGER" | "LOCAL_AUTH_LIB" | "NO_AUTHZ_POINTER" | "METADATA_UNPUBLISHED" | "IAC_GRANTS" | "POINTER_PROJECT_OVERRIDE" | "PASSWORD_STORE" | "EMAIL_ALLOWLIST" | "USERS_TABLE" | "DOCS_SITE_PUBLIC" | "DOCS_AUDIENCE_IMPLICIT" | "DOCS_AUDIENCE_UNGRANTABLE" | "DOCS_VENDOR_AUDIENCE_PARTNER";
4
4
  export interface AppCheckRule {
5
5
  gate: string;
6
6
  title: string;
@@ -33,6 +33,7 @@ export declare function docsSiteFindings(text: string): Array<{
33
33
  line: number;
34
34
  why: string;
35
35
  }>;
36
+ export declare function statedKind(decl: Pick<SurfaceDeclaration, "ctor">, args: Map<string, string>): string | undefined;
36
37
  export declare function listApps(root: string): string[];
37
38
  export declare function scanApp(root: string, appRel: string): Finding[];
38
39
  export declare function scanRepo(root: string): Finding[];
@@ -117,6 +117,13 @@ export const APP_CHECK_RULES = {
117
117
  remediation: 'declare every audience role on the ZitadelAppIdentity (roles: ["docs"]) so the Hub can grant it from the app\'s Access tab — the audience is published on the surface\'s registry entry as the reader set for the Hub to admit by, and a role nobody can be granted admits nobody',
118
118
  scope: "app",
119
119
  },
120
+ DOCS_VENDOR_AUDIENCE_PARTNER: {
121
+ gate: "H",
122
+ title: "Vendor reference never partner-readable",
123
+ severity: "block",
124
+ remediation: "drop docs.audience from the vendor surface — a vendor surface's site is the raw core API reference, read inside the Hub by holders of the vendor-docs add-on only; audience: { roles: [] } would declare it readable by every docs-role user of the tenant, which the Hub never honours and the deploy refuses",
125
+ scope: "app",
126
+ },
120
127
  };
121
128
  export const APP_CHECK_CODES = Object.keys(APP_CHECK_RULES);
122
129
  const SRC_EXT = new Set([".ts", ".tsx", ".js", ".mjs", ".jsx"]);
@@ -503,6 +510,17 @@ export function docsSiteFindings(text) {
503
510
  why: `mesh.apps.${decl.ctor} declares a docs site but neither its kind nor its audience — who the Hub admits through the door would fall out of the name heuristic, and a vendor surface must never default to partner-readable that way`,
504
511
  });
505
512
  }
513
+ if (statedKind(decl, args) === "vendor" && docs.has("audience")) {
514
+ const audience = objectLiteralOf(docs.get("audience"), text);
515
+ const roles = audience ? stringList(topLevelProperties(audience).get("roles")) : undefined;
516
+ if (roles !== undefined && roles.length === 0) {
517
+ findings.push({
518
+ code: "DOCS_VENDOR_AUDIENCE_PARTNER",
519
+ line: decl.line,
520
+ why: `mesh.apps.${decl.ctor} declares a vendor surface's docs site with audience: { roles: [] } — partner-readable by every docs-role user of the tenant; a vendor's spec is the raw core API, its site is read by holders of the Hub's vendor-docs add-on only, and the deploy refuses this declaration`,
521
+ });
522
+ }
523
+ }
506
524
  const httpText = objectLiteralOf(topLevelProperties(objectLiteralOf(args.get("surfaces"), text) ?? "{}").get("http"), text);
507
525
  for (const f of docsAudienceUngrantable(text, decl, args, docs, httpText))
508
526
  findings.push(f);
@@ -522,6 +540,13 @@ export function docsSiteFindings(text) {
522
540
  }
523
541
  return findings;
524
542
  }
543
+ export function statedKind(decl, args) {
544
+ if (decl.ctor === "AppApiSurface")
545
+ return "app";
546
+ if (decl.ctor === "VendorApiSurface")
547
+ return "vendor";
548
+ return /^["']([a-z]+)["']$/.exec(args.get("kind") ?? "")?.[1];
549
+ }
525
550
  function docsAudienceUngrantable(text, decl, args, docs, httpText) {
526
551
  const findings = [];
527
552
  let roles;
@@ -529,14 +554,8 @@ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
529
554
  const audience = objectLiteralOf(docs.get("audience"), text);
530
555
  roles = audience ? stringList(topLevelProperties(audience).get("roles")) : undefined;
531
556
  }
532
- else {
533
- const kind = decl.ctor === "AppApiSurface"
534
- ? "app"
535
- : decl.ctor === "VendorApiSurface"
536
- ? "vendor"
537
- : /^["']([a-z]+)["']$/.exec(args.get("kind") ?? "")?.[1];
538
- if (kind === "app")
539
- roles = DEFAULT_PARTNER_DOCS_ROLES;
557
+ else if (statedKind(decl, args) === "app") {
558
+ roles = DEFAULT_PARTNER_DOCS_ROLES;
540
559
  }
541
560
  if (!roles || roles.length === 0)
542
561
  return findings;
@@ -0,0 +1,19 @@
1
+ export interface DbAxisOptions {
2
+ tenant?: string;
3
+ env?: string;
4
+ appTenant?: string;
5
+ appStage?: string;
6
+ }
7
+ export interface PlatformAxis {
8
+ tenant: string;
9
+ env: string;
10
+ }
11
+ export interface AppAxis {
12
+ tenant: string;
13
+ stage: string;
14
+ }
15
+ export declare function platformAxis(options: DbAxisOptions, usage: string, detect?: () => {
16
+ tenant: string;
17
+ platformEnv: string;
18
+ }): () => PlatformAxis;
19
+ export declare function appAxis(options: DbAxisOptions, platform: () => PlatformAxis): AppAxis;
@@ -0,0 +1,35 @@
1
+ import { detectContext } from "../../utils/context.js";
2
+ import { MeshCliError } from "../../utils/errors.js";
3
+ import { logInfo } from "../../utils/log.js";
4
+ export function platformAxis(options, usage, detect = detectContext) {
5
+ if ((options.tenant && !options.env) || (!options.tenant && options.env)) {
6
+ throw new MeshCliError("Both --tenant and --env are required", {
7
+ remediation: { command: usage },
8
+ });
9
+ }
10
+ let resolved;
11
+ return () => {
12
+ if (resolved)
13
+ return resolved;
14
+ if (options.tenant && options.env) {
15
+ resolved = { tenant: options.tenant, env: options.env };
16
+ logInfo(`Platform: Tenant=${resolved.tenant}, Env=${resolved.env}`);
17
+ }
18
+ else {
19
+ const ctx = detect();
20
+ resolved = { tenant: ctx.tenant, env: ctx.platformEnv };
21
+ logInfo(`Auto-detected: Tenant=${resolved.tenant}, Env=${resolved.env}`);
22
+ }
23
+ return resolved;
24
+ };
25
+ }
26
+ export function appAxis(options, platform) {
27
+ if (options.appTenant && options.appStage) {
28
+ return { tenant: options.appTenant, stage: options.appStage };
29
+ }
30
+ const resolved = platform();
31
+ return {
32
+ tenant: options.appTenant ?? resolved.tenant,
33
+ stage: options.appStage ?? resolved.env,
34
+ };
35
+ }
@@ -1,6 +1,4 @@
1
- interface ConnectOptions {
2
- tenant?: string;
3
- env?: string;
4
- }
1
+ import { type DbAxisOptions } from "./axes.js";
2
+ type ConnectOptions = Pick<DbAxisOptions, "tenant" | "env">;
5
3
  export declare function connectCommand(options: ConnectOptions): Promise<void>;
6
4
  export {};
@@ -1,24 +1,9 @@
1
1
  import { spawn } from "child_process";
2
- import { detectContext, getPlatformBastionInfo, logInfo, logError, } from "../../utils/index.js";
2
+ import { getPlatformBastionInfo, logInfo, logError } from "../../utils/index.js";
3
+ import { platformAxis } from "./axes.js";
4
+ const USAGE = "mesh db connect --tenant mesh --env dev-temporal";
3
5
  export async function connectCommand(options) {
4
- let tenant;
5
- let platformEnv;
6
- if (options.tenant && options.env) {
7
- tenant = options.tenant;
8
- platformEnv = options.env;
9
- logInfo(`Explicit mode: Tenant=${tenant}, Env=${platformEnv}`);
10
- }
11
- else if (options.tenant || options.env) {
12
- logError("Both --tenant and --env are required");
13
- logInfo("Usage: mesh db connect --tenant mesh --env dev-temporal");
14
- process.exit(1);
15
- }
16
- else {
17
- const ctx = detectContext();
18
- tenant = ctx.tenant;
19
- platformEnv = ctx.platformEnv;
20
- logInfo(`Auto-detected: Tenant=${tenant}, Env=${platformEnv}`);
21
- }
6
+ const { tenant, env: platformEnv } = platformAxis(options, USAGE)();
22
7
  const bastion = await getPlatformBastionInfo(tenant, platformEnv);
23
8
  const rdsService = bastion.services.rds;
24
9
  if (!rdsService) {
@@ -1,9 +1,6 @@
1
- interface CredentialsOptions {
2
- tenant?: string;
3
- env?: string;
1
+ import { type DbAxisOptions } from "./axes.js";
2
+ interface CredentialsOptions extends DbAxisOptions {
4
3
  app?: string;
5
- appTenant?: string;
6
- appStage?: string;
7
4
  }
8
5
  export declare function credentialsCommand(options: CredentialsOptions): Promise<void>;
9
6
  export {};
@@ -1,30 +1,14 @@
1
- import { detectContext, getDatabaseUrl, logInfo, logError, } from "../../utils/index.js";
1
+ import { getDatabaseUrl, logInfo } from "../../utils/index.js";
2
+ import { appAxis, platformAxis } from "./axes.js";
3
+ const USAGE = "mesh db credentials --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev";
2
4
  export async function credentialsCommand(options) {
3
- let platformTenant;
4
- let platformEnv;
5
- if (options.tenant && options.env) {
6
- platformTenant = options.tenant;
7
- platformEnv = options.env;
8
- logInfo(`Platform: Tenant=${platformTenant}, Env=${platformEnv}`);
9
- }
10
- else if (options.tenant || options.env) {
11
- logError("Both --tenant and --env are required");
12
- logInfo("Usage: mesh db credentials --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev");
13
- process.exit(1);
14
- }
15
- else {
16
- const ctx = detectContext();
17
- platformTenant = ctx.tenant;
18
- platformEnv = ctx.platformEnv;
19
- logInfo(`Auto-detected: Tenant=${platformTenant}, Env=${platformEnv}`);
20
- }
21
- const appTenant = options.appTenant ?? platformTenant;
22
- const appStage = options.appStage ?? platformEnv;
5
+ const platform = platformAxis(options, USAGE);
6
+ const app = appAxis(options, platform);
23
7
  const appName = options.app;
24
8
  if (appName) {
25
- logInfo(`App credentials: tenant=${appTenant}, stage=${appStage}, app=${appName}`);
9
+ logInfo(`App credentials: tenant=${app.tenant}, stage=${app.stage}, app=${appName}`);
26
10
  }
27
- const databaseUrl = await getDatabaseUrl(appTenant, appStage, { app: appName });
11
+ const databaseUrl = await getDatabaseUrl(app.tenant, app.stage, { app: appName });
28
12
  const parsed = new URL(databaseUrl);
29
13
  console.log("");
30
14
  console.log("Database Credentials:");
@@ -1,9 +1,6 @@
1
- interface EnvOptions {
2
- tenant?: string;
3
- env?: string;
1
+ import { type DbAxisOptions } from "./axes.js";
2
+ interface EnvOptions extends DbAxisOptions {
4
3
  app?: string;
5
- appTenant?: string;
6
- appStage?: string;
7
4
  }
8
5
  export declare function envCommand(options: EnvOptions): Promise<void>;
9
6
  export {};
@@ -1,30 +1,14 @@
1
- import { detectContext, getDatabaseUrl, rewriteDatabaseUrl, logInfo, logError, } from "../../utils/index.js";
1
+ import { getDatabaseUrl, rewriteDatabaseUrl } from "../../utils/index.js";
2
+ import { appAxis, platformAxis } from "./axes.js";
3
+ const USAGE = "eval $(mesh db env --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev)";
2
4
  export async function envCommand(options) {
3
- let platformTenant;
4
- let platformEnv;
5
- if (options.tenant && options.env) {
6
- platformTenant = options.tenant;
7
- platformEnv = options.env;
8
- console.error(`ℹ Platform: Tenant=${platformTenant}, Env=${platformEnv}`);
9
- }
10
- else if (options.tenant || options.env) {
11
- logError("Both --tenant and --env are required");
12
- logInfo("Usage: eval $(mesh db env --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev)");
13
- process.exit(1);
14
- }
15
- else {
16
- const ctx = detectContext();
17
- platformTenant = ctx.tenant;
18
- platformEnv = ctx.platformEnv;
19
- console.error(`ℹ Auto-detected: Tenant=${platformTenant}, Env=${platformEnv}`);
20
- }
21
- const appTenant = options.appTenant ?? platformTenant;
22
- const appStage = options.appStage ?? platformEnv;
5
+ const platform = platformAxis(options, USAGE);
6
+ const app = appAxis(options, platform);
23
7
  const appName = options.app;
24
8
  if (appName) {
25
- console.error(`ℹ App credentials: tenant=${appTenant}, stage=${appStage}, app=${appName}`);
9
+ console.error(`ℹ App credentials: tenant=${app.tenant}, stage=${app.stage}, app=${appName}`);
26
10
  }
27
- const rawUrl = await getDatabaseUrl(appTenant, appStage, { app: appName });
11
+ const rawUrl = await getDatabaseUrl(app.tenant, app.stage, { app: appName });
28
12
  const tunnelUrl = rewriteDatabaseUrl(rawUrl, { endpoint: "localhost:5432" });
29
13
  const parsed = new URL(tunnelUrl);
30
14
  console.log(`export DATABASE_URL="${tunnelUrl}"`);
@@ -3,7 +3,8 @@ import * as net from "net";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import { SecretsManagerClient, GetSecretValueCommand, } from "@aws-sdk/client-secrets-manager";
6
- import { detectContext, getPlatformBastionInfo, getDatabaseUrl, rewriteDatabaseUrl, logInfo, logSuccess, logError, logWarn, } from "../../utils/index.js";
6
+ import { getPlatformBastionInfo, getDatabaseUrl, rewriteDatabaseUrl, logInfo, logSuccess, logError, logWarn, } from "../../utils/index.js";
7
+ import { appAxis, platformAxis } from "./axes.js";
7
8
  const DEFAULT_LOCAL_PORT = 5432;
8
9
  const MAX_RETRIES = 12;
9
10
  const RETRY_DELAY_MS = 5000;
@@ -170,30 +171,11 @@ export async function execCommand(commandArgs, options) {
170
171
  }
171
172
  }
172
173
  else {
173
- let platformTenant;
174
- let platformEnv;
175
- if (options.tenant && options.env) {
176
- platformTenant = options.tenant;
177
- platformEnv = options.env;
178
- logInfo(`Explicit mode: Platform Tenant=${platformTenant}, Platform Env=${platformEnv}`);
179
- }
180
- else if (options.tenant || options.env) {
181
- logError("Both --tenant and --env are required when using explicit mode");
182
- logInfo("Usage: mesh db exec --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev -- <command>");
183
- logInfo("Or omit both to use auto-detection");
184
- process.exit(1);
185
- }
186
- else {
187
- const ctx = detectContext();
188
- platformTenant = ctx.tenant;
189
- platformEnv = ctx.platformEnv;
190
- logInfo(`Discovery mode: Platform=${platformEnv}, Tenant=${platformTenant}`);
191
- }
192
- const appTenant = options.appTenant ?? platformTenant;
193
- const appStage = options.appStage ?? platformEnv;
174
+ const platform = platformAxis(options, "mesh db exec --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev -- <command>");
175
+ const app = appAxis(options, platform);
194
176
  const appName = options.app;
195
177
  if (appName) {
196
- logInfo(`App credentials: tenant=${appTenant}, stage=${appStage}, app=${appName}`);
178
+ logInfo(`App credentials: tenant=${app.tenant}, stage=${app.stage}, app=${appName}`);
197
179
  }
198
180
  const tunnelExists = await checkPort(localPort);
199
181
  const sstOutputs = findSstOutputs();
@@ -215,6 +197,7 @@ export async function execCommand(commandArgs, options) {
215
197
  }
216
198
  if (!databaseUrl) {
217
199
  if (!tunnelExists) {
200
+ const { tenant: platformTenant, env: platformEnv } = platform();
218
201
  const bastion = await getPlatformBastionInfo(platformTenant, platformEnv);
219
202
  const rdsService = bastion.services.rds;
220
203
  if (!rdsService) {
@@ -225,7 +208,7 @@ export async function execCommand(commandArgs, options) {
225
208
  else {
226
209
  logSuccess(`Using existing tunnel on port ${localPort}`);
227
210
  }
228
- const rawUrl = await getDatabaseUrl(appTenant, appStage, { app: appName });
211
+ const rawUrl = await getDatabaseUrl(app.tenant, app.stage, { app: appName });
229
212
  databaseUrl = rewriteDatabaseUrl(rawUrl, {
230
213
  endpoint: `localhost:${localPort}`,
231
214
  sslMode: options.ssl,
@@ -1,9 +1,6 @@
1
- interface PsqlOptions {
2
- tenant?: string;
3
- env?: string;
1
+ import { type DbAxisOptions } from "./axes.js";
2
+ interface PsqlOptions extends DbAxisOptions {
4
3
  app?: string;
5
- appTenant?: string;
6
- appStage?: string;
7
4
  }
8
5
  export declare function psqlCommand(options: PsqlOptions): Promise<void>;
9
6
  export {};
@@ -1,6 +1,8 @@
1
1
  import { spawn } from "child_process";
2
2
  import * as net from "net";
3
- import { detectContext, getPlatformBastionInfo, getDatabaseUrl, logInfo, logSuccess, logError, } from "../../utils/index.js";
3
+ import { getPlatformBastionInfo, getDatabaseUrl, logInfo, logSuccess, logError, } from "../../utils/index.js";
4
+ import { appAxis, platformAxis } from "./axes.js";
5
+ const USAGE = "mesh db psql --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev";
4
6
  async function startTunnelBackground(instanceId, rdsHost, rdsPort, localPort) {
5
7
  logInfo("Starting tunnel in background...");
6
8
  const tunnel = spawn("aws", [
@@ -46,29 +48,12 @@ function sleep(ms) {
46
48
  return new Promise((resolve) => setTimeout(resolve, ms));
47
49
  }
48
50
  export async function psqlCommand(options) {
49
- let platformTenant;
50
- let platformEnv;
51
- if (options.tenant && options.env) {
52
- platformTenant = options.tenant;
53
- platformEnv = options.env;
54
- logInfo(`Platform: Tenant=${platformTenant}, Env=${platformEnv}`);
55
- }
56
- else if (options.tenant || options.env) {
57
- logError("Both --tenant and --env are required");
58
- logInfo("Usage: mesh db psql --tenant mesh --env dev-temporal --app rdc --app-tenant encore --app-stage dev");
59
- process.exit(1);
60
- }
61
- else {
62
- const ctx = detectContext();
63
- platformTenant = ctx.tenant;
64
- platformEnv = ctx.platformEnv;
65
- logInfo(`Auto-detected: Tenant=${platformTenant}, Env=${platformEnv}`);
66
- }
67
- const appTenant = options.appTenant ?? platformTenant;
68
- const appStage = options.appStage ?? platformEnv;
51
+ const platform = platformAxis(options, USAGE);
52
+ const { tenant: platformTenant, env: platformEnv } = platform();
53
+ const app = appAxis(options, platform);
69
54
  const appName = options.app;
70
55
  if (appName) {
71
- logInfo(`App credentials: tenant=${appTenant}, stage=${appStage}, app=${appName}`);
56
+ logInfo(`App credentials: tenant=${app.tenant}, stage=${app.stage}, app=${appName}`);
72
57
  }
73
58
  const psqlCheck = spawn("which", ["psql"]);
74
59
  await new Promise((resolve, reject) => {
@@ -96,7 +81,7 @@ export async function psqlCommand(options) {
96
81
  process.on("exit", cleanup);
97
82
  process.on("SIGINT", cleanup);
98
83
  process.on("SIGTERM", cleanup);
99
- const databaseUrl = await getDatabaseUrl(appTenant, appStage, { app: appName });
84
+ const databaseUrl = await getDatabaseUrl(app.tenant, app.stage, { app: appName });
100
85
  const parsed = new URL(databaseUrl);
101
86
  const username = parsed.username;
102
87
  const password = decodeURIComponent(parsed.password);
@@ -0,0 +1,68 @@
1
+ export interface SetupPrompts {
2
+ input(args: {
3
+ message: string;
4
+ default?: string;
5
+ validate?: (v: string) => true | string;
6
+ }): Promise<string>;
7
+ confirm(args: {
8
+ message: string;
9
+ default?: boolean;
10
+ }): Promise<boolean>;
11
+ }
12
+ export interface PlatformSetupFlags {
13
+ env?: string;
14
+ region?: string;
15
+ domain?: string;
16
+ profile?: string;
17
+ stateBucket?: string;
18
+ dir?: string;
19
+ createStateBucket?: boolean;
20
+ yes?: boolean;
21
+ }
22
+ export interface PlatformSetup {
23
+ tenant: string;
24
+ env: string;
25
+ region: string;
26
+ domain: string;
27
+ profile: string;
28
+ stateBucket: string;
29
+ dir: string;
30
+ createStateBucket: boolean;
31
+ }
32
+ export declare const PLATFORM_SETUP_DEFAULTS: {
33
+ readonly env: "dev";
34
+ readonly region: "us-east-2";
35
+ readonly profile: "default";
36
+ };
37
+ export declare const DOMAIN_REQUIRED_MESSAGE: string;
38
+ export declare function defaultStateBucket(tenant: string): string;
39
+ export declare function defaultPlatformDir(tenant: string): string;
40
+ export declare function validateEnv(value: string): true | string;
41
+ export declare function validateRegion(value: string): true | string;
42
+ export declare function validateDomain(value: string): true | string;
43
+ export declare function validateStateBucket(value: string): true | string;
44
+ export declare function resolveTargetDir(tenant: string, flags: PlatformSetupFlags, cwd: string): string;
45
+ export declare function resolvePlatformSetup(tenant: string, flags: PlatformSetupFlags, deps: {
46
+ prompts?: SetupPrompts;
47
+ interactive: boolean;
48
+ cwd?: string;
49
+ }): Promise<PlatformSetup>;
50
+ export declare function stateBucketCreateCommand(setup: Pick<PlatformSetup, "stateBucket" | "region" | "profile">): string;
51
+ export interface StateBucketOutcome {
52
+ created: boolean;
53
+ present: boolean;
54
+ detail: string;
55
+ }
56
+ export interface StateBucketDeps {
57
+ headBucket(setup: PlatformSetup): boolean;
58
+ createBucket(setup: PlatformSetup): void;
59
+ enableVersioning(setup: PlatformSetup): void;
60
+ }
61
+ export declare const realStateBucketDeps: StateBucketDeps;
62
+ export declare function ensureStateBucket(setup: PlatformSetup, deps?: StateBucketDeps): Promise<StateBucketOutcome>;
63
+ export declare function exportCredentialsCommand(profile: string): string;
64
+ export declare function renderPlatformNextSteps(setup: PlatformSetup, opts?: {
65
+ bucket?: StateBucketOutcome;
66
+ relativeDir?: string;
67
+ }): string[];
68
+ export declare function scaffoldPlatformRepo(setup: PlatformSetup): Promise<void>;