@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.
- package/README.md +74 -246
- package/dist/bin/mesh.js +1451 -847
- package/dist/bin/mesh.js.map +3 -3
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.d.ts +2 -1
- package/dist/src/commands/app-check.js +27 -8
- package/dist/src/commands/db/axes.d.ts +19 -0
- package/dist/src/commands/db/axes.js +35 -0
- package/dist/src/commands/db/connect.d.ts +2 -4
- package/dist/src/commands/db/connect.js +4 -19
- package/dist/src/commands/db/credentials.d.ts +2 -5
- package/dist/src/commands/db/credentials.js +7 -23
- package/dist/src/commands/db/env.d.ts +2 -5
- package/dist/src/commands/db/env.js +7 -23
- package/dist/src/commands/db/exec.js +7 -24
- package/dist/src/commands/db/psql.d.ts +2 -5
- package/dist/src/commands/db/psql.js +8 -23
- package/dist/src/commands/init/platform-setup.d.ts +68 -0
- package/dist/src/commands/init/platform-setup.js +203 -0
- package/dist/src/commands/init/wizard.d.ts +20 -0
- package/dist/src/commands/init/wizard.js +89 -7
- package/dist/src/commands/init.js +42 -36
- package/dist/src/commands/login.d.ts +4 -0
- package/dist/src/commands/login.js +16 -2
- package/dist/src/commands/registry.d.ts +2 -2
- package/dist/src/commands/registry.js +11 -6
- package/dist/src/commands/secrets/context.d.ts +5 -0
- package/dist/src/commands/secrets/context.js +5 -0
- package/dist/src/commands/secrets/index.js +3 -0
- package/dist/src/commands/secrets/migrate-config.d.ts +1 -0
- package/dist/src/commands/secrets/migrate-config.js +2 -3
- package/dist/src/commands/secrets/reindex.d.ts +1 -0
- package/dist/src/commands/secrets/reindex.js +2 -2
- package/dist/src/commands/secrets/set.d.ts +1 -0
- package/dist/src/commands/secrets/set.js +2 -2
- package/dist/src/commands/temporal.js +34 -10
- package/dist/src/commands/temporal.js.map +2 -2
- package/dist/src/commands/tunnel/index.d.ts +8 -0
- package/dist/src/commands/tunnel/index.js +3 -3
- package/dist/src/index.js +324 -62
- package/dist/src/index.js.map +3 -3
- package/dist/src/utils/context.d.ts +6 -1
- package/dist/src/utils/context.js +302 -39
- package/dist/src/utils/registry-identity.d.ts +5 -0
- package/dist/src/utils/registry-identity.js +16 -0
- package/fragments/agents/AGENTS.md.hbs +14 -2
- package/fragments/base/index.ts.hbs +15 -5
- package/package.json +2 -2
- package/skills/core/SKILL.md +74 -7
- package/templates/api-auth/index.ts.hbs +5 -4
- package/templates/api-role-gating/index.ts.hbs +5 -4
- package/templates/external-service/index.ts.hbs +5 -4
- package/templates/platform-repo/README.md.hbs +17 -3
- package/templates/platform-repo/core/Pulumi.{{tenant}}-{{env}}.yaml.hbs +7 -0
- package/templates/workflow/index.ts.hbs +5 -4
package/dist/build-info.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
-
|
|
4
|
-
|
|
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=${
|
|
9
|
+
logInfo(`App credentials: tenant=${app.tenant}, stage=${app.stage}, app=${appName}`);
|
|
26
10
|
}
|
|
27
|
-
const databaseUrl = await getDatabaseUrl(
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
-
|
|
4
|
-
|
|
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=${
|
|
9
|
+
console.error(`ℹ App credentials: tenant=${app.tenant}, stage=${app.stage}, app=${appName}`);
|
|
26
10
|
}
|
|
27
|
-
const rawUrl = await getDatabaseUrl(
|
|
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 {
|
|
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
|
-
|
|
174
|
-
|
|
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=${
|
|
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(
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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=${
|
|
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(
|
|
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>;
|