@mesh-tech/mesh-cli 0.25.0 → 0.27.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 +2179 -1328
- package/dist/bin/mesh.js.map +3 -3
- package/dist/build-info.json +2 -2
- 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/local/index.d.ts +5 -3
- package/dist/src/commands/local/index.js +61 -23
- package/dist/src/commands/local/mock-idps.d.ts +48 -0
- package/dist/src/commands/local/mock-idps.js +119 -0
- package/dist/src/commands/local/seed-zitadel.d.ts +6 -0
- package/dist/src/commands/local/seed-zitadel.js +76 -0
- package/dist/src/commands/local/stack.d.ts +11 -3
- package/dist/src/commands/local/stack.js +29 -15
- 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 +35 -8
- 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 +10 -2
- package/fragments/base/index.ts.hbs +15 -5
- package/package.json +2 -2
- package/skills/core/SKILL.md +74 -7
- package/stack/docker-compose.idps.yml +104 -0
- package/stack/idps/dex.yaml +53 -0
- 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
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { probeRegistryToken } from "../../utils/auth-preflight.js";
|
|
2
2
|
import { type RegistryLoginOptions } from "../registry.js";
|
|
3
3
|
import { type MeshJson } from "../../utils/mesh-json.js";
|
|
4
|
+
import { scaffoldPlatformRepo, type PlatformSetupFlags, type StateBucketDeps } from "./platform-setup.js";
|
|
4
5
|
export interface WizardOptions {
|
|
5
6
|
tenant?: string;
|
|
6
7
|
local?: boolean;
|
|
7
8
|
platform?: string;
|
|
9
|
+
newPlatform?: string | boolean;
|
|
10
|
+
region?: string;
|
|
11
|
+
domain?: string;
|
|
12
|
+
stateBucket?: string;
|
|
13
|
+
createStateBucket?: boolean;
|
|
14
|
+
dir?: string;
|
|
8
15
|
device?: boolean;
|
|
9
16
|
profile?: string;
|
|
10
17
|
skipRepo?: boolean;
|
|
@@ -18,9 +25,15 @@ export interface StepResult {
|
|
|
18
25
|
detail: string;
|
|
19
26
|
fix?: string;
|
|
20
27
|
blocking?: boolean;
|
|
28
|
+
next?: string[];
|
|
21
29
|
}
|
|
22
30
|
export type WizardMode = "local" | {
|
|
23
31
|
platform: string;
|
|
32
|
+
} | {
|
|
33
|
+
newPlatform: PlatformSetupFlags;
|
|
34
|
+
};
|
|
35
|
+
export declare function isNewPlatform(mode: WizardMode): mode is {
|
|
36
|
+
newPlatform: PlatformSetupFlags;
|
|
24
37
|
};
|
|
25
38
|
export interface WizardCtx {
|
|
26
39
|
cwd: string;
|
|
@@ -40,6 +53,8 @@ export interface WizardSeams {
|
|
|
40
53
|
probe?: typeof probeRegistryToken;
|
|
41
54
|
runRegistryLogin?: (opts: RegistryLoginOptions) => Promise<void>;
|
|
42
55
|
gitInit?: (dir: string) => void;
|
|
56
|
+
scaffoldPlatform?: typeof scaffoldPlatformRepo;
|
|
57
|
+
stateBucket?: StateBucketDeps;
|
|
43
58
|
}
|
|
44
59
|
export interface WizardStep {
|
|
45
60
|
name: "registry" | "platform" | "repo" | "skills";
|
|
@@ -72,7 +87,9 @@ export interface Prompts {
|
|
|
72
87
|
}
|
|
73
88
|
export declare const NO_SESSION_NO_TTY_MESSAGE: string;
|
|
74
89
|
export declare const TENANT_REQUIRED_MESSAGE = "--tenant is required (no TTY for interactive mode)";
|
|
90
|
+
export declare const REGISTRY_WHY: string;
|
|
75
91
|
export declare const INTERRUPTED_EXIT_CODE = 130;
|
|
92
|
+
export declare const PLATFORM_SETUP_LABEL = "Platform setup";
|
|
76
93
|
export declare function wizardExitCode(steps: ReadonlyArray<Pick<StepResult, "status">>): number;
|
|
77
94
|
export declare function wizardNextCommands(mode: WizardMode, steps?: ReadonlyArray<StepResult>): string[];
|
|
78
95
|
export declare function stepBlocks(result: StepResult): boolean;
|
|
@@ -85,7 +102,9 @@ export declare function resolveAnswers(opts: WizardOptions, recorded: MeshJson |
|
|
|
85
102
|
mode?: WizardMode;
|
|
86
103
|
open: Array<"tenant" | "mode">;
|
|
87
104
|
};
|
|
105
|
+
export declare function platformSetupFlags(opts: WizardOptions): PlatformSetupFlags;
|
|
88
106
|
export declare const registryStep: WizardStep;
|
|
107
|
+
export declare const newPlatformStep: WizardStep;
|
|
89
108
|
export type RepoKind = "platform-monorepo" | "empty-apps-repo" | "apps-repo" | "other-repo" | "empty-dir" | "no-repo";
|
|
90
109
|
export declare function classifyRepo(cwd: string): {
|
|
91
110
|
kind: RepoKind;
|
|
@@ -93,6 +112,7 @@ export declare function classifyRepo(cwd: string): {
|
|
|
93
112
|
};
|
|
94
113
|
export declare const INIT_REPO_FIX = "git init && mesh init";
|
|
95
114
|
export declare const repoStep: WizardStep;
|
|
115
|
+
export declare const skillsStep: WizardStep;
|
|
96
116
|
export declare const WIZARD_STEPS: readonly WizardStep[];
|
|
97
117
|
export declare function runWizardSteps(ctx: WizardCtx, steps: readonly WizardStep[]): Promise<StepResult[]>;
|
|
98
118
|
export declare function runInitWizard(opts: WizardOptions, deps?: {
|
|
@@ -12,10 +12,17 @@ import { discoverConfigForContext, isRemoteEnvironment, readCredentials, renderN
|
|
|
12
12
|
import { bootstrapAppsRepo, isInsidePlatformMonorepo, shouldBootstrapAppsRepo } from "../create-app.js";
|
|
13
13
|
import { resolveTargetRoot, syncSkills } from "../skills.js";
|
|
14
14
|
import { detectLocalTenant } from "../local/dev-local.js";
|
|
15
|
+
import { PLATFORM_SETUP_DEFAULTS, ensureStateBucket, renderPlatformNextSteps, resolvePlatformSetup, scaffoldPlatformRepo, } from "./platform-setup.js";
|
|
16
|
+
export function isNewPlatform(mode) {
|
|
17
|
+
return typeof mode === "object" && "newPlatform" in mode;
|
|
18
|
+
}
|
|
15
19
|
export const NO_SESSION_NO_TTY_MESSAGE = "No registry session and no terminal to sign in with.\n" +
|
|
16
20
|
" On a headless box: mesh registry login --device (one-time, prints a code)\n" +
|
|
17
21
|
" In CI with AWS creds: mesh registry login --ci";
|
|
18
22
|
export const TENANT_REQUIRED_MESSAGE = "--tenant is required (no TTY for interactive mode)";
|
|
23
|
+
export const REGISTRY_WHY = "Package access — a sign-in to the Mesh package REGISTRY, which is what makes `@mesh-tech/*` installable.\n" +
|
|
24
|
+
" It is one global identity: no AWS account, no platform, no <tenant>.<env> context.\n" +
|
|
25
|
+
" (`mesh login <tenant>.<env>` is the other sign-in — that one is only for deploying to, or operating, a platform.)";
|
|
19
26
|
export const INTERRUPTED_EXIT_CODE = 130;
|
|
20
27
|
const STEP_LABELS = {
|
|
21
28
|
registry: "Registry access",
|
|
@@ -23,10 +30,14 @@ const STEP_LABELS = {
|
|
|
23
30
|
repo: "Repo",
|
|
24
31
|
skills: "Agent skills",
|
|
25
32
|
};
|
|
33
|
+
export const PLATFORM_SETUP_LABEL = "Platform setup";
|
|
26
34
|
export function wizardExitCode(steps) {
|
|
27
35
|
return steps.some((s) => s.status === "fail") ? 1 : 0;
|
|
28
36
|
}
|
|
29
37
|
export function wizardNextCommands(mode, steps = []) {
|
|
38
|
+
const owned = steps.find((s) => s.next?.length)?.next;
|
|
39
|
+
if (owned)
|
|
40
|
+
return owned;
|
|
30
41
|
const repo = steps.find((s) => s.name === STEP_LABELS.repo);
|
|
31
42
|
const first = repo && repo.status !== "pass" && repo.fix ? repo.fix : "mesh create-app";
|
|
32
43
|
return mode === "local" ? [first] : [first, "mesh deploy up # from the app directory, once it exists"];
|
|
@@ -35,7 +46,11 @@ export function stepBlocks(result) {
|
|
|
35
46
|
return result.status === "fail" && result.blocking !== false;
|
|
36
47
|
}
|
|
37
48
|
export function describeMode(mode) {
|
|
38
|
-
|
|
49
|
+
if (mode === "local")
|
|
50
|
+
return "local only";
|
|
51
|
+
if (isNewPlatform(mode))
|
|
52
|
+
return `a new platform (${mode.newPlatform.env ?? PLATFORM_SETUP_DEFAULTS.env})`;
|
|
53
|
+
return `deployed platform (${mode.platform})`;
|
|
39
54
|
}
|
|
40
55
|
export function renderWizardSummary(outcome, opts = {}) {
|
|
41
56
|
const paint = opts.color === false ? { green: (s) => s, red: (s) => s, yellow: (s) => s, dim: (s) => s, cyan: (s) => s } : chalk;
|
|
@@ -65,10 +80,13 @@ export function resolveAnswers(opts, recorded, interactive) {
|
|
|
65
80
|
open.push("tenant");
|
|
66
81
|
}
|
|
67
82
|
let mode;
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
const chosen = [opts.local && "--local", opts.platform && "--platform", opts.newPlatform && "--new-platform"].filter(Boolean);
|
|
84
|
+
if (chosen.length > 1) {
|
|
85
|
+
throw new MeshCliError(`${chosen.join(", ")} are mutually exclusive — a tenant runs in exactly one of those three places.`);
|
|
70
86
|
}
|
|
71
|
-
if (opts.
|
|
87
|
+
if (opts.newPlatform)
|
|
88
|
+
mode = { newPlatform: platformSetupFlags(opts) };
|
|
89
|
+
else if (opts.platform)
|
|
72
90
|
mode = { platform: opts.platform };
|
|
73
91
|
else if (opts.local)
|
|
74
92
|
mode = "local";
|
|
@@ -80,6 +98,18 @@ export function resolveAnswers(opts, recorded, interactive) {
|
|
|
80
98
|
}
|
|
81
99
|
return { tenant, mode, open };
|
|
82
100
|
}
|
|
101
|
+
export function platformSetupFlags(opts) {
|
|
102
|
+
return {
|
|
103
|
+
env: typeof opts.newPlatform === "string" ? opts.newPlatform : undefined,
|
|
104
|
+
region: opts.region,
|
|
105
|
+
domain: opts.domain,
|
|
106
|
+
profile: opts.profile,
|
|
107
|
+
stateBucket: opts.stateBucket,
|
|
108
|
+
createStateBucket: opts.createStateBucket,
|
|
109
|
+
dir: opts.dir,
|
|
110
|
+
yes: opts.yes,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
83
113
|
export const registryStep = {
|
|
84
114
|
name: "registry",
|
|
85
115
|
async run(ctx) {
|
|
@@ -102,6 +132,7 @@ export const registryStep = {
|
|
|
102
132
|
return { name, status: "fail", detail: NO_SESSION_NO_TTY_MESSAGE.split("\n")[0], fix: "mesh registry login --device", blocking: true };
|
|
103
133
|
}
|
|
104
134
|
try {
|
|
135
|
+
logInfo(REGISTRY_WHY);
|
|
105
136
|
const login = ctx.seams?.runRegistryLogin ??
|
|
106
137
|
(async (opts) => {
|
|
107
138
|
const { runRegistryLogin } = await import("../registry.js");
|
|
@@ -132,10 +163,51 @@ export const registryStep = {
|
|
|
132
163
|
return { name, status: "pass", detail: `Registry access OK (${session ? `signed in as ${session.email}` : "token written"})` };
|
|
133
164
|
},
|
|
134
165
|
};
|
|
166
|
+
export const newPlatformStep = {
|
|
167
|
+
name: "platform",
|
|
168
|
+
async run(ctx) {
|
|
169
|
+
const name = PLATFORM_SETUP_LABEL;
|
|
170
|
+
if (!isNewPlatform(ctx.mode))
|
|
171
|
+
return { name, status: "skip", detail: "no new platform requested" };
|
|
172
|
+
let setup;
|
|
173
|
+
try {
|
|
174
|
+
setup = await resolvePlatformSetup(ctx.tenant, { ...ctx.mode.newPlatform, yes: ctx.yes }, {
|
|
175
|
+
prompts: ctx.prompts,
|
|
176
|
+
interactive: ctx.interactive,
|
|
177
|
+
cwd: ctx.cwd,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
status: "fail",
|
|
184
|
+
detail: err.message.split("\n")[0],
|
|
185
|
+
fix: `mesh init --tenant ${ctx.tenant} --new-platform --domain <your-domain>`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
await (ctx.seams?.scaffoldPlatform ?? scaffoldPlatformRepo)(setup);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return { name, status: "fail", detail: `platform repo not scaffolded: ${err.message.split("\n")[0]}`, fix: `mesh init platform ${ctx.tenant} --dir <empty-dir>` };
|
|
193
|
+
}
|
|
194
|
+
const bucket = await ensureStateBucket(setup, ctx.seams?.stateBucket);
|
|
195
|
+
const next = renderPlatformNextSteps(setup, { bucket, relativeDir: path.relative(ctx.cwd, setup.dir) || "." });
|
|
196
|
+
return {
|
|
197
|
+
name,
|
|
198
|
+
status: setup.createStateBucket && !bucket.present ? "warn" : "pass",
|
|
199
|
+
detail: `platform repo scaffolded at ${setup.dir} (${setup.tenant}.${setup.env}, ${setup.region}, zone ${setup.env}.${setup.domain})` +
|
|
200
|
+
(setup.createStateBucket ? ` — state bucket: ${bucket.detail}` : ` — state bucket ${setup.stateBucket} not created (see Next)`),
|
|
201
|
+
next,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
};
|
|
135
205
|
const platformStep = {
|
|
136
206
|
name: "platform",
|
|
137
207
|
async run(ctx) {
|
|
138
208
|
const name = STEP_LABELS.platform;
|
|
209
|
+
if (isNewPlatform(ctx.mode))
|
|
210
|
+
return newPlatformStep.run(ctx);
|
|
139
211
|
if (!ctx.context)
|
|
140
212
|
return { name, status: "skip", detail: "local only — no platform to sign in to" };
|
|
141
213
|
const context = ctx.context;
|
|
@@ -192,6 +264,9 @@ export const repoStep = {
|
|
|
192
264
|
const name = STEP_LABELS.repo;
|
|
193
265
|
if (ctx.skipRepo)
|
|
194
266
|
return { name, status: "skip", detail: "--skip-repo" };
|
|
267
|
+
if (isNewPlatform(ctx.mode)) {
|
|
268
|
+
return { name, status: "skip", detail: "setting up a platform — the apps repo comes after the first deploy (see Next)" };
|
|
269
|
+
}
|
|
195
270
|
const { kind, root } = classifyRepo(ctx.cwd);
|
|
196
271
|
switch (kind) {
|
|
197
272
|
case "platform-monorepo":
|
|
@@ -259,12 +334,15 @@ export const repoStep = {
|
|
|
259
334
|
}
|
|
260
335
|
},
|
|
261
336
|
};
|
|
262
|
-
const skillsStep = {
|
|
337
|
+
export const skillsStep = {
|
|
263
338
|
name: "skills",
|
|
264
339
|
async run(ctx) {
|
|
265
340
|
const name = STEP_LABELS.skills;
|
|
266
341
|
if (ctx.skipRepo)
|
|
267
342
|
return { name, status: "skip", detail: "--skip-repo" };
|
|
343
|
+
if (isNewPlatform(ctx.mode)) {
|
|
344
|
+
return { name, status: "skip", detail: "synced into the new platform repo by the scaffold" };
|
|
345
|
+
}
|
|
268
346
|
const { kind, root } = classifyRepo(ctx.cwd);
|
|
269
347
|
if (kind !== "apps-repo" && kind !== "empty-apps-repo") {
|
|
270
348
|
return { name, status: "skip", detail: "no apps repo to install skills into" };
|
|
@@ -341,11 +419,15 @@ export async function runInitWizard(opts, deps = {}) {
|
|
|
341
419
|
choices: [
|
|
342
420
|
{ name: "Local only (mesh start / mesh dev — no deployed platform yet)", value: "local" },
|
|
343
421
|
{ name: `A deployed Mesh platform (I have a ${tenant}.<env> context)`, value: "deployed" },
|
|
422
|
+
{ name: "Nothing yet — set up a new Mesh platform in my AWS account", value: "new" },
|
|
344
423
|
],
|
|
345
424
|
default: recorded && recorded.platform !== "local" ? "deployed" : "local",
|
|
346
425
|
});
|
|
347
426
|
if (choice === "local")
|
|
348
427
|
mode = "local";
|
|
428
|
+
else if (choice === "new") {
|
|
429
|
+
mode = { newPlatform: platformSetupFlags({ ...opts, newPlatform: true }) };
|
|
430
|
+
}
|
|
349
431
|
else {
|
|
350
432
|
const env = await prompts.input({
|
|
351
433
|
message: "Environment",
|
|
@@ -359,7 +441,7 @@ export async function runInitWizard(opts, deps = {}) {
|
|
|
359
441
|
cwd,
|
|
360
442
|
tenant,
|
|
361
443
|
mode,
|
|
362
|
-
context: mode === "local" ? null : `${tenant}.${mode.platform}`,
|
|
444
|
+
context: mode === "local" || isNewPlatform(mode) ? null : `${tenant}.${mode.platform}`,
|
|
363
445
|
device: opts.device,
|
|
364
446
|
profile: opts.profile,
|
|
365
447
|
interactive,
|
|
@@ -372,7 +454,7 @@ export async function runInitWizard(opts, deps = {}) {
|
|
|
372
454
|
logInfo(`Setting up tenant '${tenant}' (${describeMode(mode)})…`);
|
|
373
455
|
const steps = await runWizardSteps(ctx, deps.steps ?? WIZARD_STEPS);
|
|
374
456
|
const { kind, root } = classifyRepo(cwd);
|
|
375
|
-
if (!opts.skipRepo && (kind === "apps-repo" || kind === "empty-apps-repo")) {
|
|
457
|
+
if (!opts.skipRepo && !isNewPlatform(mode) && (kind === "apps-repo" || kind === "empty-apps-repo")) {
|
|
376
458
|
const written = writeMeshJson(root, { tenant, platform: mode === "local" ? "local" : mode.platform });
|
|
377
459
|
logInfo(`Recorded tenant + mode in ${written}`);
|
|
378
460
|
}
|
|
@@ -8,6 +8,14 @@ import { readCredentials } from "./login.js";
|
|
|
8
8
|
import { resolveTargetRoot, syncSkills } from "./skills.js";
|
|
9
9
|
import { detectLocalTenant } from "./local/dev-local.js";
|
|
10
10
|
import { LOGIN_CONTEXT } from "./local/seed-zitadel.js";
|
|
11
|
+
import { PLATFORM_SETUP_DEFAULTS } from "./init/platform-setup.js";
|
|
12
|
+
async function setupPrompts() {
|
|
13
|
+
const [{ default: input }, { default: confirm }] = await Promise.all([
|
|
14
|
+
import("@inquirer/input"),
|
|
15
|
+
import("@inquirer/confirm"),
|
|
16
|
+
]);
|
|
17
|
+
return { input: (args) => input(args), confirm: (args) => confirm(args) };
|
|
18
|
+
}
|
|
11
19
|
const LOCAL_HUB_API = "http://localhost:4568";
|
|
12
20
|
function isLocalContext(context) {
|
|
13
21
|
return context === LOGIN_CONTEXT;
|
|
@@ -211,12 +219,19 @@ export function registerInitCommands(program) {
|
|
|
211
219
|
const init = program
|
|
212
220
|
.command("init")
|
|
213
221
|
.enablePositionalOptions()
|
|
214
|
-
.description("Set up this machine and repo for a tenant — a guided wizard with no subcommand
|
|
222
|
+
.description("Set up this machine and repo for a tenant — a guided wizard with no subcommand. It starts from nothing: it asks where the tenant runs, and 'nowhere yet' is one of the answers (it scaffolds you a platform). Then package-registry sign-in, repo, skills. `init platform` is the same scaffold on its own; `init app-tenant` is the apps-repo doctor")
|
|
215
223
|
.option("--tenant <name>", "app tenant this repo belongs to (prompted when omitted on a TTY)")
|
|
216
224
|
.option("--local", "local only — mesh start / mesh dev, no deployed platform (the default)")
|
|
217
|
-
.option("--platform <env>", "a deployed platform's environment; signs in to
|
|
225
|
+
.option("--platform <env>", "a deployed platform's environment; signs in to `<tenant>.<env>`")
|
|
226
|
+
.option("--new-platform [env]", `nothing deployed yet — scaffold a tenant platform repo for \`<tenant>.<env>\` (default env: ${PLATFORM_SETUP_DEFAULTS.env}) and print its deploy sequence`)
|
|
227
|
+
.option("--region <region>", `--new-platform: AWS region (default: ${PLATFORM_SETUP_DEFAULTS.region})`)
|
|
228
|
+
.option("--domain <domain>", "--new-platform: base public domain you control; the core stack creates the zone {env}.{domain}. No default — a placeholder scaffolds a zone that can never validate")
|
|
229
|
+
.option("--state-bucket <bucket>", "--new-platform: Pulumi state S3 bucket (default: {tenant}-mesh-platform-pulumi-state)")
|
|
230
|
+
.option("--create-state-bucket", "--new-platform: create the state bucket (versioned) if it does not exist")
|
|
231
|
+
.option("--no-create-state-bucket", "--new-platform: never create the state bucket; print the aws command instead")
|
|
232
|
+
.option("--dir <path>", "--new-platform: where to scaffold the platform repo (default: ./{tenant}-mesh-platform)")
|
|
218
233
|
.option("--device", "device-code sign-in for the registry and platform steps (headless / SSH)")
|
|
219
|
-
.option("--profile <aws-profile>", "
|
|
234
|
+
.option("--profile <aws-profile>", "AWS profile this machine uses: read the registry without the broker (deployers only), and the profile a --new-platform stack config references")
|
|
220
235
|
.option("--skip-repo", "do not bootstrap, check or record anything in the current repo", false)
|
|
221
236
|
.option("--yes", "accept every default and never prompt", false)
|
|
222
237
|
.option("--json", "one JSON document on stdout ({tenant, mode, steps, next}); human output on stderr", false)
|
|
@@ -228,47 +243,38 @@ export function registerInitCommands(program) {
|
|
|
228
243
|
});
|
|
229
244
|
init
|
|
230
245
|
.command("platform <tenant>")
|
|
231
|
-
.description("Scaffold a tenant PLATFORM repo (core + platform Pulumi layers on @mesh-tech/infra-components) that deploys with `mesh deploy` unmodified — the proven mesh-sandbox shape, annotated.")
|
|
232
|
-
.option("--env <env>",
|
|
233
|
-
.option("--region <region>",
|
|
234
|
-
.option("--domain <domain>", "base public domain
|
|
235
|
-
.option("--profile <profile>",
|
|
236
|
-
.option("--state-bucket <bucket>", "Pulumi state S3 bucket (
|
|
246
|
+
.description("Scaffold a tenant PLATFORM repo (core + platform Pulumi layers on @mesh-tech/infra-components) that deploys with `mesh deploy` unmodified — the proven mesh-sandbox shape, annotated. On a terminal it ASKS for the values that differ per install (env, region, domain, state bucket, AWS profile), each pre-filled with its default; every answer is also a flag.")
|
|
247
|
+
.option("--env <env>", `environment the first stack targets (default: ${PLATFORM_SETUP_DEFAULTS.env})`)
|
|
248
|
+
.option("--region <region>", `AWS region (default: ${PLATFORM_SETUP_DEFAULTS.region})`)
|
|
249
|
+
.option("--domain <domain>", "base public domain you control — the core stack creates the Route 53 zone {env}.{domain} and waits on its NS delegation. Required: it has no default, because a placeholder scaffolds a zone that can never validate")
|
|
250
|
+
.option("--profile <profile>", `AWS profile the stack config references (default: ${PLATFORM_SETUP_DEFAULTS.profile})`)
|
|
251
|
+
.option("--state-bucket <bucket>", "Pulumi state S3 bucket (default: {tenant}-mesh-platform-pulumi-state)")
|
|
252
|
+
.option("--create-state-bucket", "create the state bucket (versioned) if it does not exist — prompted on a terminal")
|
|
253
|
+
.option("--no-create-state-bucket", "never create the state bucket; print the aws command instead")
|
|
237
254
|
.option("--dir <path>", "target directory (default: ./{tenant}-mesh-platform)")
|
|
255
|
+
.option("--yes", "accept every default and never prompt", false)
|
|
238
256
|
.action(async (tenant, opts) => {
|
|
239
257
|
if (!/^[a-z][a-z0-9-]*$/.test(tenant)) {
|
|
240
258
|
throw new MeshCliError(`Invalid tenant name '${tenant}' (lowercase alphanumeric + dashes).`);
|
|
241
259
|
}
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
env: opts.env,
|
|
254
|
-
region: opts.region,
|
|
255
|
-
domain: opts.domain,
|
|
256
|
-
profile: opts.profile,
|
|
257
|
-
stateBucket: opts.stateBucket ?? `${tenant}-mesh-platform-pulumi-state`,
|
|
258
|
-
};
|
|
259
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
260
|
-
copyTemplate(templateDir, targetDir, context);
|
|
261
|
-
syncSkills(targetDir);
|
|
262
|
-
logInfo(`Platform repo scaffolded at ${targetDir}`);
|
|
260
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
261
|
+
const { resolvePlatformSetup, scaffoldPlatformRepo, ensureStateBucket, renderPlatformNextSteps } = await import("./init/platform-setup.js");
|
|
262
|
+
const setup = await resolvePlatformSetup(tenant, opts, {
|
|
263
|
+
prompts: interactive && !opts.yes ? await setupPrompts() : undefined,
|
|
264
|
+
interactive,
|
|
265
|
+
});
|
|
266
|
+
await scaffoldPlatformRepo(setup);
|
|
267
|
+
logInfo(`Platform repo scaffolded at ${setup.dir}`);
|
|
268
|
+
const bucket = await ensureStateBucket(setup);
|
|
269
|
+
if (bucket.detail !== "not requested")
|
|
270
|
+
logInfo(`Pulumi state bucket: ${bucket.detail}`);
|
|
263
271
|
console.log("");
|
|
264
272
|
console.log("Next steps:");
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
console.log(" cd core && mesh deploy up");
|
|
269
|
-
console.log(" cd ../platform && mesh deploy up");
|
|
273
|
+
for (const line of renderPlatformNextSteps(setup, { bucket, relativeDir: path.relative(process.cwd(), setup.dir) || "." })) {
|
|
274
|
+
console.log(line ? ` ${line}` : "");
|
|
275
|
+
}
|
|
270
276
|
console.log("");
|
|
271
|
-
logInfo(`Review the annotated stack configs (Pulumi.${tenant}-${
|
|
277
|
+
logInfo(`Review the annotated stack configs (Pulumi.${tenant}-${setup.env}.yaml) before the first deploy.`);
|
|
272
278
|
});
|
|
273
279
|
init
|
|
274
280
|
.command("app-tenant")
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { type ServiceStatus, type StackEndpoint } from "./stack.js";
|
|
2
|
+
import { type OverlayState, type ServiceStatus, type StackEndpoint } from "./stack.js";
|
|
3
3
|
export interface WaitForStackIo {
|
|
4
4
|
probe: (endpoint: StackEndpoint) => Promise<boolean>;
|
|
5
5
|
services: () => ServiceStatus[];
|
|
6
|
-
|
|
6
|
+
overlays: () => readonly OverlayState[];
|
|
7
7
|
}
|
|
8
|
-
export declare function waitForStack(
|
|
8
|
+
export declare function waitForStack(opts: {
|
|
9
|
+
idpsStarted: boolean;
|
|
10
|
+
}, io?: WaitForStackIo): Promise<void>;
|
|
9
11
|
export declare function startHereLines(hubRunning: boolean, up?: ReadonlyMap<string, boolean>): string[];
|
|
10
12
|
export declare function registerLocalCommands(program: Command): void;
|
|
@@ -3,12 +3,13 @@ import * as path from "path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { logError, logInfo, logSuccess, logWarn } from "../../utils/log.js";
|
|
5
5
|
import { MeshCliError } from "../../utils/errors.js";
|
|
6
|
-
import { compose, COMPOSE_PROJECT, composeStreamed, crashedServices, DEFAULT_HUB_PORT, ensureDockerAvailable, findPackageRoot, hubApiRunning, hubEndpoints,
|
|
6
|
+
import { compose, COMPOSE_PROJECT, composeStreamed, crashedServices, DEFAULT_HUB_PORT, ensureDockerAvailable, findPackageRoot, hubApiRunning, hubEndpoints, hubOverlayServices, hubPort, idpsOverlayServices, ONE_SHOT_SERVICES, overlayRunning, probeEndpoint, STACK_ENDPOINTS, stackOwnedElsewhere, stackServices, } from "./stack.js";
|
|
7
7
|
import { probeRegistryToken, REGISTRY_LOGIN_FIX } from "../../utils/auth-preflight.js";
|
|
8
8
|
import { ensureHubAuthImage, ensureHubImages, buildHubImagesFromSource, readHubCompiledAuthz, hasRegistryAuth, localHubVersion, planWithHubRefresh, } from "./hub-local.js";
|
|
9
9
|
import { reconcileRegistryFromZitadel } from "./auth-provision.js";
|
|
10
10
|
import { LOCAL_AWS_CONFIG, localAwsEnv, seedLocalPlatform, verifyFabric } from "./seed.js";
|
|
11
|
-
import { LOGIN_CONTEXT, readHubAuth, seedZitadel, TEST_USERS } from "./seed-zitadel.js";
|
|
11
|
+
import { LOGIN_CONTEXT, readHubAuth, seedMockIdps, seedZitadel, TEST_USERS } from "./seed-zitadel.js";
|
|
12
|
+
import { idpEndpoints, mockIdpComposeEnv, MOCK_OIDC_PORT, MOCK_SAML_PORT } from "./mock-idps.js";
|
|
12
13
|
import { publishHubAuthzCatalog } from "./seed-hub-catalog.js";
|
|
13
14
|
import { describePortConflicts, findPortConflicts, hostPortsOf } from "./helpers.js";
|
|
14
15
|
const WAIT_TIMEOUT_MS = 180_000;
|
|
@@ -20,10 +21,13 @@ function crashError(crashed) {
|
|
|
20
21
|
},
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
|
-
export async function waitForStack(io = {
|
|
24
|
+
export async function waitForStack(opts, io = {
|
|
24
25
|
probe: probeEndpoint,
|
|
25
26
|
services: stackServices,
|
|
26
|
-
|
|
27
|
+
overlays: () => [
|
|
28
|
+
{ services: hubOverlayServices(), started: false },
|
|
29
|
+
{ services: idpsOverlayServices(), started: opts.idpsStarted },
|
|
30
|
+
],
|
|
27
31
|
}) {
|
|
28
32
|
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
29
33
|
let pending = STACK_ENDPOINTS.filter((e) => e.probe.kind !== "none");
|
|
@@ -39,10 +43,7 @@ export async function waitForStack(io = {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
pending = stillDown;
|
|
42
|
-
const crashed = crashedServices(io.services(), {
|
|
43
|
-
overlayServices: io.overlayServices(),
|
|
44
|
-
overlayStarted: false,
|
|
45
|
-
});
|
|
46
|
+
const crashed = crashedServices(io.services(), { overlays: io.overlays() });
|
|
46
47
|
if (crashed.length > 0)
|
|
47
48
|
throw crashError(crashed);
|
|
48
49
|
if (pending.length === 0)
|
|
@@ -112,6 +113,11 @@ function portMoveHints() {
|
|
|
112
113
|
Number(hubPort()),
|
|
113
114
|
"MESH_HUB_PORT=<free port> mesh start (or mesh start --no-hub, which starts everything else)",
|
|
114
115
|
],
|
|
116
|
+
[MOCK_SAML_PORT, "mesh start (without --with-idps — the mock identity providers are opt-in)"],
|
|
117
|
+
[
|
|
118
|
+
MOCK_OIDC_PORT,
|
|
119
|
+
"mesh start (without --with-idps; 5556 is Dex's own default port, so a Dex of your own is the usual holder)",
|
|
120
|
+
],
|
|
115
121
|
]);
|
|
116
122
|
}
|
|
117
123
|
function repoRootForHubSource() {
|
|
@@ -133,6 +139,7 @@ export function registerLocalCommands(program) {
|
|
|
133
139
|
.option("--no-hub", "start without the Hub (API + UI)")
|
|
134
140
|
.option("--with-hub", "force-refresh the Hub to the latest published @mesh-tech/hub (the Hub is included by default when its images exist or registry auth is available)")
|
|
135
141
|
.option("--hub-from-source", "build the Hub images from THIS checkout's apps/hub instead of the published tarball (the only way to exercise a Hub change locally)")
|
|
142
|
+
.option("--with-idps", "also run mock SAML and OIDC identity providers, and federate the local Zitadel with both (for testing enterprise SSO without a cloud tenant or an IdP admin)", false)
|
|
136
143
|
.option("--takeover", "re-own a stack that another checkout started (may recreate shared containers with THIS checkout's config)", false)
|
|
137
144
|
.option("--skip-port-check", "start even though a host port the stack wants is already held (the service that loses the bind will be broken — use when the holder is something you cannot stop)", false)
|
|
138
145
|
.action(async (opts) => {
|
|
@@ -151,7 +158,11 @@ export function registerLocalCommands(program) {
|
|
|
151
158
|
logWarn(`Taking over the running stack (previously driven from ${foreign}).`);
|
|
152
159
|
}
|
|
153
160
|
if (!opts.skipPortCheck && !stackServices().some((s) => s.state === "running")) {
|
|
154
|
-
const ports = hostPortsOf([
|
|
161
|
+
const ports = hostPortsOf([
|
|
162
|
+
...STACK_ENDPOINTS,
|
|
163
|
+
...(opts.hub ? hubEndpoints() : []),
|
|
164
|
+
...(opts.withIdps ? idpEndpoints() : []),
|
|
165
|
+
]);
|
|
155
166
|
const conflicts = await findPortConflicts(ports, `${COMPOSE_PROJECT}-`);
|
|
156
167
|
if (conflicts.length > 0) {
|
|
157
168
|
throw new MeshCliError(describePortConflicts(conflicts, portMoveHints()), {
|
|
@@ -197,9 +208,10 @@ export function registerLocalCommands(program) {
|
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
210
|
logInfo("Starting the local Mesh platform (docker compose project: mesh-local)…");
|
|
200
|
-
|
|
211
|
+
const idpEnv = opts.withIdps ? mockIdpComposeEnv() : undefined;
|
|
212
|
+
await composeStreamed(["up", "-d"], { idps: opts.withIdps, env: idpEnv });
|
|
201
213
|
compose(["restart", "prometheus", "blackbox-exporter"], { inherit: true });
|
|
202
|
-
await waitForStack();
|
|
214
|
+
await waitForStack({ idpsStarted: opts.withIdps });
|
|
203
215
|
let hubAuth = readHubAuth();
|
|
204
216
|
if (opts.seed) {
|
|
205
217
|
logInfo("Seeding local platform (tenant registry, artifacts bucket)…");
|
|
@@ -207,6 +219,15 @@ export function registerLocalCommands(program) {
|
|
|
207
219
|
logInfo("Seeding Zitadel (Platform project, Mesh CLI app, Hub auth, test users)…");
|
|
208
220
|
const zitadel = await seedZitadel(LOCAL_AWS_CONFIG);
|
|
209
221
|
hubAuth = zitadel.hubAuth;
|
|
222
|
+
if (opts.withIdps) {
|
|
223
|
+
logInfo("Federating local Zitadel with the mock SAML and OIDC identity providers…");
|
|
224
|
+
await seedMockIdps();
|
|
225
|
+
logInfo(`Mock SAML IdP: http://localhost:${MOCK_SAML_PORT} · ` +
|
|
226
|
+
`mock OIDC IdP: http://localhost:${MOCK_OIDC_PORT}/dex · ` +
|
|
227
|
+
"sign in at http://localhost:8080 and both federation buttons are offered");
|
|
228
|
+
logInfo("Both are listed in the Zitadel console under Settings → Identity Providers (owner: Instance): " +
|
|
229
|
+
"http://localhost:8080/ui/console/org-settings?id=idp");
|
|
230
|
+
}
|
|
210
231
|
if (hubVersion) {
|
|
211
232
|
const compiled = readHubCompiledAuthz(hubVersion);
|
|
212
233
|
if (compiled) {
|
|
@@ -246,19 +267,23 @@ export function registerLocalCommands(program) {
|
|
|
246
267
|
hubEnv.MESH_HUB_OAUTH2_CLIENT_SECRET = hubAuth.clientSecret;
|
|
247
268
|
hubEnv.MESH_HUB_OAUTH2_COOKIE_SECRET = hubAuth.cookieSecret;
|
|
248
269
|
}
|
|
249
|
-
await composeStreamed(["up", "-d"], {
|
|
270
|
+
await composeStreamed(["up", "-d"], {
|
|
271
|
+
hub: true,
|
|
272
|
+
idps: opts.withIdps,
|
|
273
|
+
env: { ...idpEnv, ...hubEnv },
|
|
274
|
+
});
|
|
250
275
|
if (opts.seed) {
|
|
251
276
|
compose(["restart", "hub-api"], { hub: true, env: hubEnv });
|
|
252
277
|
}
|
|
253
|
-
const
|
|
278
|
+
const overlays = [
|
|
279
|
+
{ services: hubOverlayServices(), started: true },
|
|
280
|
+
{ services: idpsOverlayServices(), started: opts.withIdps },
|
|
281
|
+
];
|
|
254
282
|
for (const endpoint of hubEndpoints()) {
|
|
255
283
|
logInfo(`Waiting for ${endpoint.label}…`);
|
|
256
284
|
let up = await probeEndpoint(endpoint);
|
|
257
285
|
for (let i = 0; i < 60 && !up; i++) {
|
|
258
|
-
const crashed = crashedServices(stackServices(), {
|
|
259
|
-
overlayServices,
|
|
260
|
-
overlayStarted: true,
|
|
261
|
-
});
|
|
286
|
+
const crashed = crashedServices(stackServices(), { overlays });
|
|
262
287
|
if (crashed.length > 0)
|
|
263
288
|
throw crashError(crashed);
|
|
264
289
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -310,11 +335,20 @@ export function registerLocalCommands(program) {
|
|
|
310
335
|
remediation: { command: "mesh start" },
|
|
311
336
|
});
|
|
312
337
|
}
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
const
|
|
338
|
+
const hubOverlay = hubOverlayServices();
|
|
339
|
+
const idpsOverlay = idpsOverlayServices();
|
|
340
|
+
const idpsStarted = overlayRunning(services, idpsOverlay);
|
|
341
|
+
const overlays = [
|
|
342
|
+
{ services: hubOverlay, started: overlayRunning(services, hubOverlay) },
|
|
343
|
+
{ services: idpsOverlay, started: idpsStarted },
|
|
344
|
+
];
|
|
345
|
+
const notStarted = (s) => s.state === "exited" && overlays.some((o) => !o.started && o.services.has(s.name));
|
|
316
346
|
const hasHub = hubApiRunning(services);
|
|
317
|
-
const endpoints =
|
|
347
|
+
const endpoints = [
|
|
348
|
+
...STACK_ENDPOINTS,
|
|
349
|
+
...(hasHub ? hubEndpoints() : []),
|
|
350
|
+
...(idpsStarted ? idpEndpoints() : []),
|
|
351
|
+
];
|
|
318
352
|
const probes = await Promise.all(endpoints.map(async (endpoint) => ({
|
|
319
353
|
service: endpoint.service,
|
|
320
354
|
label: endpoint.label,
|
|
@@ -337,6 +371,7 @@ export function registerLocalCommands(program) {
|
|
|
337
371
|
services,
|
|
338
372
|
endpoints: probes,
|
|
339
373
|
fabric,
|
|
374
|
+
mockIdps: idpsStarted,
|
|
340
375
|
awsEnv: localAwsEnv(),
|
|
341
376
|
}));
|
|
342
377
|
if (fabric && !fabric.ok)
|
|
@@ -352,7 +387,7 @@ export function registerLocalCommands(program) {
|
|
|
352
387
|
const idle = notStarted(service);
|
|
353
388
|
const icon = ok || oneShotDone ? chalk.green("●") : idle ? chalk.dim("○") : chalk.red("●");
|
|
354
389
|
const health = service.health ? ` (${service.health})` : "";
|
|
355
|
-
const note = idle ? chalk.dim(" (
|
|
390
|
+
const note = idle ? chalk.dim(" (not started this run)") : "";
|
|
356
391
|
console.log(` ${icon} ${service.name.padEnd(22)} ${service.state}${health}${note}`);
|
|
357
392
|
}
|
|
358
393
|
console.log("");
|
|
@@ -366,8 +401,11 @@ export function registerLocalCommands(program) {
|
|
|
366
401
|
const detail = fabric.ok ? "8 KB Advanced-tier round-trip OK" : fabric.error;
|
|
367
402
|
console.log(` ${icon} ${"AWS fabric check".padEnd(34)} ${chalk.dim(detail ?? "")}`);
|
|
368
403
|
}
|
|
404
|
+
if (!idpsStarted) {
|
|
405
|
+
console.log(` ${chalk.dim("○")} ${"Mock IdPs (SAML + OIDC)".padEnd(34)} ${chalk.dim("not started — mesh start --with-idps")}`);
|
|
406
|
+
}
|
|
369
407
|
const down = probes.filter((p) => !p.up);
|
|
370
|
-
const crashed = crashedServices(services, {
|
|
408
|
+
const crashed = crashedServices(services, { overlays });
|
|
371
409
|
if (fabric && !fabric.ok)
|
|
372
410
|
process.exitCode = 1;
|
|
373
411
|
if (down.length > 0 || crashed.length > 0) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { StackEndpoint } from "./stack.js";
|
|
2
|
+
export declare const MOCK_SAML_PORT = 4569;
|
|
3
|
+
export declare const MOCK_OIDC_PORT = 5556;
|
|
4
|
+
export declare const idpEndpoints: () => StackEndpoint[];
|
|
5
|
+
export declare const MOCK_SAML_IDP_NAME = "Mock SAML (local)";
|
|
6
|
+
export declare const MOCK_OIDC_IDP_NAME = "Mock OIDC (local)";
|
|
7
|
+
export declare const mockSamlMetadataUrl: () => string;
|
|
8
|
+
export declare const mockOidcEndpoints: () => {
|
|
9
|
+
authorization: string;
|
|
10
|
+
token: string;
|
|
11
|
+
user: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function ensureMockSamlSigningKey(): {
|
|
14
|
+
publicKeyB64: string;
|
|
15
|
+
privateKeyB64: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const mockIdpComposeEnv: () => Record<string, string>;
|
|
18
|
+
export declare const buildMockSamlIdpBody: (metadataXml: string) => {
|
|
19
|
+
name: string;
|
|
20
|
+
metadataXml: string;
|
|
21
|
+
binding: string;
|
|
22
|
+
withSignedRequest: boolean;
|
|
23
|
+
providerOptions: {
|
|
24
|
+
isLinkingAllowed: boolean;
|
|
25
|
+
isCreationAllowed: boolean;
|
|
26
|
+
isAutoCreation: boolean;
|
|
27
|
+
isAutoUpdate: boolean;
|
|
28
|
+
autoLinking: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare const buildMockOauthIdpBody: () => {
|
|
32
|
+
name: string;
|
|
33
|
+
clientId: string;
|
|
34
|
+
clientSecret: string;
|
|
35
|
+
authorizationEndpoint: string;
|
|
36
|
+
tokenEndpoint: string;
|
|
37
|
+
userEndpoint: string;
|
|
38
|
+
scopes: string[];
|
|
39
|
+
idAttribute: string;
|
|
40
|
+
providerOptions: {
|
|
41
|
+
isLinkingAllowed: boolean;
|
|
42
|
+
isCreationAllowed: boolean;
|
|
43
|
+
isAutoCreation: boolean;
|
|
44
|
+
isAutoUpdate: boolean;
|
|
45
|
+
autoLinking: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export declare const missingLoginPolicyIdps: (attached: readonly string[], wanted: readonly string[]) => string[];
|