@zitadel/cli 0.3.0 → 0.4.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 +39 -0
- package/dist/index.mjs +1450 -1047
- package/dist/index.mjs.map +1 -1
- package/dist/lib/oclif/help.mjs +3 -2
- package/dist/lib/oclif/help.mjs.map +1 -1
- package/dist/skills/zitadel/SKILL.md +111 -0
- package/dist/skills/zitadel-cli/SKILL.md +635 -0
- package/dist/skills/zitadel-cli/references/driving-login-ui.md +124 -0
- package/dist/skills/zitadel-components/SKILL.md +152 -0
- package/dist/skills/zitadel-sdk-angular/SKILL.md +149 -0
- package/dist/skills/zitadel-sdk-next/SKILL.md +174 -0
- package/dist/skills/zitadel-sdk-nuxt/SKILL.md +149 -0
- package/dist/skills/zitadel-sdk-qwik/SKILL.md +136 -0
- package/dist/skills/zitadel-sdk-qwik-router/SKILL.md +126 -0
- package/dist/skills/zitadel-sdk-react/SKILL.md +149 -0
- package/dist/skills/zitadel-sdk-solid/SKILL.md +144 -0
- package/dist/skills/zitadel-sdk-solid-start/SKILL.md +149 -0
- package/dist/skills/zitadel-sdk-svelte/SKILL.md +137 -0
- package/dist/skills/zitadel-sdk-sveltekit/SKILL.md +147 -0
- package/dist/skills/zitadel-sdk-tanstack-start/SKILL.md +150 -0
- package/dist/skills/zitadel-sdk-vue/SKILL.md +147 -0
- package/dist/{skills-hint-N1H5-ny1.mjs → skills-hint-C8oe8cCo.mjs} +1446 -231
- package/dist/skills-hint-C8oe8cCo.mjs.map +1 -0
- package/oclif.manifest.json +102 -3
- package/package.json +8 -5
- package/skills/zitadel-cli/SKILL.md +62 -9
- package/dist/skills-hint-N1H5-ny1.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as republishCommands, $t as processUserAgentFacts, A as isPolicyTestFile, An as sanitizeResponse, At as checkFlow, B as CommandGroups, Bt as valuesOfKind, C as createReleaseFromBundle, Cn as parseJsonObject, Ct as checkCustomSlug, D as PASSWORD_POLICY_PATH, Dn as createZitadelClient$1, Dt as refuseChangedIssuer, En as REQUEST_TIMEOUT_MS$1, Et as readConnectionFiles, F as toPolicyWireBody, Fn as isErrno$2, Ft as parseVariableValue, G as readFlowFiles, Gt as renderDetail, H as authMethods, Ht as variableInventoryCells, I as readJsonDir, In as isMissingEndpoint, It as readStdin, J as SCHEMAS_DIR, Jt as renderTable, K as readSchemaFiles, Kt as renderList, L as readJsonDirEntries, Ln as toZitadelError, Lt as toInventoryRows, M as passwordPolicyFile, Mn as listenForInterrupt, Mt as VARIABLE_TYPES, N as policyFileIssues, Nn as stopListeningForInterrupt, Nt as assertVariableName, O as POLICIES_DIR, On as escapeControlCharacters, Ot as storedCustomSettings, P as policyFileName, Pn as ZitadelError, Pt as frozenVariables, Q as republishArgs, Qt as installUserAgent, R as BRANDING_DIR, Rt as toVariableRow, S as servesPrimary, Sn as isObject, St as IDPS_DIR, T as buildBundle, Tn as stableStringify, Tt as planConnection, U as enabledMethods, Ut as variableReadScope, Vt as variableCells, W as methodsOf, Wt as variableScope, X as reportClientIdOutcome, Xt as takeReportedWarnings, Y as publishClientId, Yt as reportWarning, Z as reportSecretOutcome, Zt as buildUserAgent, _ as worktreeIsDirty, _n as normalizePublicCliJson, _t as removeSsoFromFlow, a as serverLogAction, an as nonBlankArg, at as SETUP_FLAG_NAMES, b as createDeployment, bn as publicCliCommand, bt as ssoProvidersRefusal, c as wrapForBox, cn as inShell, ct as customFlagText, d as deployMessage, dn as shellCommand, dt as givenCustomFlags, en as hostAgent, et as storeClientSecret, f as messageFlag, fn as unscopedCommand, ft as loopbackIssuerWarning, g as headSha, gn as normalizePublicCliCommands, gt as applySsoToSchema, h as runSteps, hn as maskPublicCliVersion, ht as applySsoToFlow, i as exploreAllCommands, in as envEnabled, it as SETUP_FLAG_KEYS, j as parsePasswordMinLength, jn as interruptSignal, jt as offeredSignInMethods, k as POLICY_TEST_SUFFIX, kn as sanitizeRejection, kt as FLOWS_DIR, l as DEPLOY_ARGS, ln as portableSuggestions, lt as customFlagValues, m as validateStep, mn as isTrainVersion, mt as ssoEnableArgs, n as brandingGuidanceAction, nn as loadOrCreateIdentity, nt as ENABLE_FLAG_KEYS, o as verifyLoginAction, on as nonBlankString, ot as checkGivenCustomFlags, p as deployInitialRelease, pn as isPortableShellWord, pt as selectionFromFlags, q as selectSchema, qt as renderRows, r as customizeAndPublishActions, rn as resolveConsent, rt as ENABLE_FLAG_NAMES, s as renderBoxActions, sn as asEnvironmentSelector, st as customExample, t as showSkillsHint, tn as ciProvider, tt as askConnectionEndpoints, u as diffStep, un as projectCommand, ut as flagText, v as Repository, vn as normalizePublicCliProse, vt as removeSsoFromSchema, w as revokeRelease, wn as setTopLevelJsonKey, wt as credentialVariablesOf, x as rollbackDeployment, xn as sdkVersionForCli, xt as CONNECTION_SCHEMA_REF, y as RepositoryError, yn as optionArgs, yt as ssoEditRefusal, zt as toVariableRows } from "./skills-hint-C8oe8cCo.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Args, Command, Flags } from "@oclif/core";
|
|
4
4
|
import consola$1, { consola } from "consola";
|
|
5
5
|
import { ApiError, CSRF_HEADER, request } from "@zitadel/api/runtime/fetch";
|
|
6
|
-
import {
|
|
6
|
+
import { itemsOf, nextToken, pages } from "@zitadel/api/runtime/paging";
|
|
7
|
+
import { pagedFunctions } from "@zitadel/api/client";
|
|
8
|
+
import { access, chmod, cp, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
9
|
import { text } from "node:stream/consumers";
|
|
8
10
|
import * as schemas from "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
|
|
9
11
|
import { CreateGrantBody, CreateIdpBody, CreateTeamBody, CreateUserBody, GetBrandingByIdResponse, GetFlowDefinitionResponse, GetGrantResponse, GetIdpByIdResponse, GetPolicyResponse, GetProjectResponse, GetReleaseByIdResponse, GetSchemaByIdResponse, GetSessionResponse, GetTeamResponse, GetUserByIDResponse, ListBrandingResponse, ListEventsResponse, ListFlowDefinitionsResponse, ListPoliciesResponse, ListReleasesResponse, ListSchemasResponse, PatchProjectBody, PatchUserByIDBody, PublishPolicyBody, QueryGrantsBody, QueryGrantsResponse, QueryIdpsBody, QueryIdpsResponse, QueryProjectsBody, QueryProjectsResponse, QuerySessionsBody, QuerySessionsResponse, QueryTeamsBody, QueryTeamsResponse, QueryUsersBody, QueryUsersResponse, UpdateTeamBody, getClaimStatusQueryChallengeIdRegExp } from "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
|
|
12
|
+
import { API_PREFIX, apiBaseUrl } from "@zitadel/api/runtime/base-url";
|
|
10
13
|
import { operations } from "@zitadel/api/generated/operations";
|
|
11
14
|
import { createHash, pbkdf2Sync, randomBytes, randomUUID } from "node:crypto";
|
|
12
15
|
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
13
16
|
import { basename, delimiter, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
14
17
|
import { constants, existsSync } from "node:fs";
|
|
15
18
|
import { homedir, tmpdir } from "node:os";
|
|
16
|
-
import { isDeepStrictEqual, parseEnv
|
|
19
|
+
import { isDeepStrictEqual, parseEnv } from "node:util";
|
|
17
20
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
18
21
|
import { StringDecoder } from "node:string_decoder";
|
|
19
22
|
import { resolveServerBinary, serverBinaryPath } from "@zitadel/server/binary";
|
|
@@ -24,12 +27,41 @@ import mixpanelLib from "mixpanel";
|
|
|
24
27
|
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, password, select, spinner, text as text$1 } from "@clack/prompts";
|
|
25
28
|
import { RECOMMENDED_PASSWORD_MIN_LENGTH, policyConfigSchema, policyTestFileSchema, schemaConfigSchema } from "@zitadel/config/schemas";
|
|
26
29
|
import { CUSTOM_PROVIDER, DEFAULT_SCOPES, IDP_PROVIDERS, RESERVED_SLUGS, credentialVariables, idpProvider, isVariableReference, parseClientId, parseClientSecret, parseConnectionSlug, parseDisplayName, parseIssuer, parseScopes, providerOf, selectionName, validated } from "@zitadel/config/idp";
|
|
27
|
-
import { BRANDING_DESIGNS, DEFAULT_BRANDING_CONFIG_PATH, DEFAULT_BRANDING_DESIGN, DEFAULT_BRANDING_TEMPLATE_PATH, DEFAULT_FLOW_CONFIG_PATH, DEFAULT_FLOW_SCHEMA_URI, DEFAULT_SCHEMA_CONFIG_PATH, DEFAULT_SETUP_PRESET, DEFAULT_SETUP_USE_CASE, SETUP_PRESETS, SETUP_USE_CASES, brandingReadmeContent, flowsReadmeContent, getDefaultBrandingConfig,
|
|
30
|
+
import { BRANDING_DESIGNS, DEFAULT_BRANDING_CONFIG_PATH, DEFAULT_BRANDING_DESIGN, DEFAULT_BRANDING_TEMPLATE_PATH, DEFAULT_FLOW_CONFIG_PATH, DEFAULT_FLOW_SCHEMA_URI, DEFAULT_SCHEMA_CONFIG_PATH, DEFAULT_SETUP_PRESET, DEFAULT_SETUP_USE_CASE, SETUP_PRESETS, SETUP_USE_CASES, brandingReadmeContent, flowsReadmeContent, getDefaultBrandingConfig, getDefaultHumanUserSchemaBody, getDefaultLoginFlow, policiesReadmeContent, schemasReadmeContent } from "@zitadel/config/defaults";
|
|
28
31
|
import { BRANDING_FILE_SCHEMA_REF, META_SCHEMA_DIR, PROJECT_FILE_SCHEMA_REF, metaSchemaFiles } from "@zitadel/config/meta-schemas";
|
|
29
32
|
import { defaultHumanUserSchemaUrl } from "@zitadel/config";
|
|
30
33
|
import semver from "semver";
|
|
31
34
|
import { builders, generateCode, parseModule } from "magicast";
|
|
35
|
+
import { ListEventsCategoryItem, ListFlowDefinitionsRevisions, ListSchemasRevisions, QueryGrantsBodyFilterItemOperation, QueryIdpsBodyFilterItemOperation, QueryProjectsBodyFilterItemOperation, QuerySessionsBodyFilterItemOperation, QueryTeamsBodyFilterItemOperation, QueryUsersBodyFilterItemOperation } from "@zitadel/api/generated/model";
|
|
32
36
|
import { normalizeFlowBody, normalizeIdpBody, normalizeSchemaBody } from "@zitadel/config/normalize";
|
|
37
|
+
import * as rosie from "rosie-skills";
|
|
38
|
+
//#region src/lib/oclif/crud/paging.ts
|
|
39
|
+
/**
|
|
40
|
+
* The first page `pages` yields, or with `all` every page in order, as one
|
|
41
|
+
* list of the items `items` takes from each page. `next` reads a page's
|
|
42
|
+
* cursor to report it: the cursor to the page after the first, or `null` once
|
|
43
|
+
* `all` has read the last page.
|
|
44
|
+
*
|
|
45
|
+
* Following the cursor is the walk's job, not this one's: a client function's
|
|
46
|
+
* `.pages()` reads it from the page as the server sent it, before the CLI
|
|
47
|
+
* escapes the page, so it goes back to the server unchanged. Without `all`
|
|
48
|
+
* the walk is left after its first page, so no second one is fetched.
|
|
49
|
+
*/
|
|
50
|
+
const collectPages = async (pages, { items, all, next }) => {
|
|
51
|
+
const collected = [];
|
|
52
|
+
for await (const page of pages) {
|
|
53
|
+
collected.push(...items(page));
|
|
54
|
+
if (!all) return {
|
|
55
|
+
items: collected,
|
|
56
|
+
next: next(page) || null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
items: collected,
|
|
61
|
+
next: null
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
33
65
|
//#region src/lib/oclif/crud/zod.ts
|
|
34
66
|
/**
|
|
35
67
|
* Depth limit for peeling wrappers. A generated schema nests a handful at
|
|
@@ -549,7 +581,7 @@ const fieldExample = (fields) => {
|
|
|
549
581
|
//#endregion
|
|
550
582
|
//#region src/lib/api/operations.ts
|
|
551
583
|
/** Every operation of the API the CLI was built with. */
|
|
552
|
-
const OPERATIONS = operations;
|
|
584
|
+
const OPERATIONS$1 = operations;
|
|
553
585
|
const LIST = unscopedCommand("api ls --json");
|
|
554
586
|
/**
|
|
555
587
|
* Why the CLI cannot call an operation, or `undefined` when it can. It holds a
|
|
@@ -566,7 +598,7 @@ const uncallable = (operation) => {
|
|
|
566
598
|
* The operations `zitadel api ls` lists: those of one tag, and those whose id,
|
|
567
599
|
* path, summary or tag contains the filter, both in any case.
|
|
568
600
|
*/
|
|
569
|
-
const findOperations = ({ filter, tag }) => OPERATIONS.filter((operation) => (tag === void 0 || operation.tag.toLowerCase() === tag.toLowerCase()) && (filter === void 0 || [
|
|
601
|
+
const findOperations = ({ filter, tag }) => OPERATIONS$1.filter((operation) => (tag === void 0 || operation.tag.toLowerCase() === tag.toLowerCase()) && (filter === void 0 || [
|
|
570
602
|
operation.id,
|
|
571
603
|
operation.path,
|
|
572
604
|
operation.summary,
|
|
@@ -576,12 +608,13 @@ const findOperations = ({ filter, tag }) => OPERATIONS.filter((operation) => (ta
|
|
|
576
608
|
const pathParameterNames = (operation) => [...operation.path.matchAll(/\{([^}]+)\}/g)].map((match) => match[1] ?? "");
|
|
577
609
|
/**
|
|
578
610
|
* The operation an argument names: an `operationId` (in any case, as the docs
|
|
579
|
-
* and `@zitadel/api` spell it), or a path starting with `/`,
|
|
580
|
-
* the spec's templates. `method` is `-X`: with a path it picks among the
|
|
611
|
+
* and `@zitadel/api` spell it), or a path starting with `/`, with or without
|
|
612
|
+
* the API prefix, matched against the spec's templates. `method` is `-X`: with a path it picks among the
|
|
581
613
|
* path's methods; with an id it must be the operation's own.
|
|
582
614
|
*/
|
|
583
615
|
const resolveTarget = (ref, method) => {
|
|
584
616
|
const wanted = method?.toUpperCase();
|
|
617
|
+
if (ref.startsWith(`${API_PREFIX}/`)) return byPath(ref.slice(API_PREFIX.length), wanted);
|
|
585
618
|
if (!ref.startsWith("/")) {
|
|
586
619
|
const operation = byId(ref);
|
|
587
620
|
if (wanted !== void 0 && wanted !== operation.method) throw new ZitadelError("E_VALIDATION", `${operation.id} is ${operation.method} ${operation.path}, not ${wanted}`, { hint: `Leave out -X, or pass -X ${operation.method}.` });
|
|
@@ -594,10 +627,10 @@ const resolveTarget = (ref, method) => {
|
|
|
594
627
|
return byPath(ref, wanted);
|
|
595
628
|
};
|
|
596
629
|
const byId = (ref) => {
|
|
597
|
-
const operation = OPERATIONS.find((candidate) => candidate.id.toLowerCase() === ref.toLowerCase());
|
|
630
|
+
const operation = OPERATIONS$1.find((candidate) => candidate.id.toLowerCase() === ref.toLowerCase());
|
|
598
631
|
if (operation) return operation;
|
|
599
632
|
const wanted = ref.toLowerCase();
|
|
600
|
-
const near = OPERATIONS.filter((candidate) => {
|
|
633
|
+
const near = OPERATIONS$1.filter((candidate) => {
|
|
601
634
|
const id = candidate.id.toLowerCase();
|
|
602
635
|
return id.includes(wanted) || distance(id, wanted) <= 2;
|
|
603
636
|
}).map((candidate) => candidate.id);
|
|
@@ -653,7 +686,7 @@ const byPath = (ref, method) => {
|
|
|
653
686
|
const path = at === -1 ? ref : ref.slice(0, at);
|
|
654
687
|
const query = new URLSearchParams(at === -1 ? "" : ref.slice(at + 1));
|
|
655
688
|
const segments = segmentsOf(path.length > 1 ? path.replace(/\/$/, "") : path);
|
|
656
|
-
const matches = (segments ? OPERATIONS : []).map((operation) => ({
|
|
689
|
+
const matches = (segments ? OPERATIONS$1 : []).map((operation) => ({
|
|
657
690
|
operation,
|
|
658
691
|
match: matchOf(operation.path, segments ?? [])
|
|
659
692
|
})).filter((candidate) => candidate.match !== void 0);
|
|
@@ -663,7 +696,7 @@ const byPath = (ref, method) => {
|
|
|
663
696
|
hint: "List the paths with `zitadel api ls`.",
|
|
664
697
|
nextCommands: [LIST]
|
|
665
698
|
});
|
|
666
|
-
const methods = OPERATIONS.filter((operation) => operation.path === template.operation.path);
|
|
699
|
+
const methods = OPERATIONS$1.filter((operation) => operation.path === template.operation.path);
|
|
667
700
|
const operation = method === void 0 ? methods.length === 1 ? methods[0] : methods.find((candidate) => candidate.method === "GET") : methods.find((candidate) => candidate.method === method);
|
|
668
701
|
if (!operation) {
|
|
669
702
|
const offered = methods.map((candidate) => candidate.method).join(", ");
|
|
@@ -679,11 +712,11 @@ const byPath = (ref, method) => {
|
|
|
679
712
|
//#region src/lib/api/request.ts
|
|
680
713
|
/** The flag a field is given with, which errors name. */
|
|
681
714
|
const FIELD = "--field";
|
|
682
|
-
/** The URL a request goes to on `server
|
|
715
|
+
/** The URL a request goes to on `server`: the API under `/api/v1` there, then the spec's path. */
|
|
683
716
|
const urlOf = (server, request) => {
|
|
684
717
|
const query = new URLSearchParams(request.query.map(([name, value]) => [name, value]));
|
|
685
718
|
const search = query.size > 0 ? `?${query}` : "";
|
|
686
|
-
return `${server
|
|
719
|
+
return `${apiBaseUrl(server)}${request.path}${search}`;
|
|
687
720
|
};
|
|
688
721
|
/**
|
|
689
722
|
* The generated Zod schema for one part of an operation's request:
|
|
@@ -834,7 +867,15 @@ const readBody = async (res) => {
|
|
|
834
867
|
* (`createZitadelClient`), and what the server says is escaped the same way:
|
|
835
868
|
* a non-2xx is the {@link ApiError} `toZitadelError` maps to an exit code.
|
|
836
869
|
*/
|
|
837
|
-
const send = async (server, token, request
|
|
870
|
+
const send = async (server, token, request) => {
|
|
871
|
+
const { status, raw } = await sendRaw(server, token, request);
|
|
872
|
+
return {
|
|
873
|
+
status,
|
|
874
|
+
body: sanitizeResponse(raw)
|
|
875
|
+
};
|
|
876
|
+
};
|
|
877
|
+
/** {@link send}, answering the body as the server sent it; a failure is escaped all the same. */
|
|
878
|
+
const sendRaw = async (server, token, request$1) => {
|
|
838
879
|
const url = urlOf(server, request$1);
|
|
839
880
|
const { method } = request$1.operation;
|
|
840
881
|
const headers = new Headers({ accept: "application/json" });
|
|
@@ -851,7 +892,7 @@ const send = async (server, token, request$1) => {
|
|
|
851
892
|
if (!res.ok) sanitizeRejection(new ApiError(res.status, url, rawBody, `${method} ${url} returned ${res.status}`));
|
|
852
893
|
return {
|
|
853
894
|
status: res.status,
|
|
854
|
-
|
|
895
|
+
raw: rawBody
|
|
855
896
|
};
|
|
856
897
|
};
|
|
857
898
|
/** The request for the page `token` names; `undefined` leaves the request as it is. */
|
|
@@ -872,33 +913,39 @@ const pageOf = (request, token) => {
|
|
|
872
913
|
};
|
|
873
914
|
/**
|
|
874
915
|
* Send a paged request until the server's `next_page_token` runs out, and
|
|
875
|
-
* answer the last page with the page field holding every page's items.
|
|
876
|
-
* walk is `collectPages`, the resource lists' `--all`, so a server that
|
|
877
|
-
* repeats a cursor is caught the same way.
|
|
916
|
+
* answer the last page with the page field holding every page's items.
|
|
878
917
|
*/
|
|
879
918
|
const sendAll = async (server, token, request) => {
|
|
880
919
|
const paging = request.operation.paging;
|
|
881
920
|
if (paging === null) return send(server, token, request);
|
|
882
921
|
let last = {
|
|
883
922
|
status: 200,
|
|
884
|
-
|
|
923
|
+
raw: void 0
|
|
885
924
|
};
|
|
886
|
-
const
|
|
887
|
-
last = await
|
|
888
|
-
return last.
|
|
889
|
-
}, {
|
|
890
|
-
|
|
925
|
+
const walk = pages(async (cursor) => {
|
|
926
|
+
last = await sendRaw(server, token, pageOf(request, cursor));
|
|
927
|
+
return last.raw;
|
|
928
|
+
}, { next: nextToken });
|
|
929
|
+
const { items } = await collectPages(walk, {
|
|
930
|
+
items: (raw) => {
|
|
931
|
+
try {
|
|
932
|
+
return sanitizeResponse(itemsOf(raw, paging.items));
|
|
933
|
+
} catch (error) {
|
|
934
|
+
throw new ZitadelError("E_VALIDATION", error instanceof Error ? error.message : String(error), { details: { response: sanitizeResponse(raw) } });
|
|
935
|
+
}
|
|
936
|
+
},
|
|
891
937
|
all: true,
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
938
|
+
next: nextToken
|
|
939
|
+
});
|
|
940
|
+
const page = isObject(last.raw) ? last.raw : {};
|
|
941
|
+
const body = sanitizeResponse({
|
|
942
|
+
...page,
|
|
943
|
+
[paging.items]: []
|
|
897
944
|
});
|
|
898
945
|
return {
|
|
899
946
|
status: last.status,
|
|
900
947
|
body: {
|
|
901
|
-
...
|
|
948
|
+
...body,
|
|
902
949
|
[paging.items]: items
|
|
903
950
|
}
|
|
904
951
|
};
|
|
@@ -919,41 +966,6 @@ const curlOf = (server, request) => [
|
|
|
919
966
|
...request.body === void 0 ? [] : ["-H 'Content-Type: application/json'", `-d ${quote(JSON.stringify(request.body))}`]
|
|
920
967
|
].join(" \\\n ");
|
|
921
968
|
//#endregion
|
|
922
|
-
//#region src/lib/oclif/flags.ts
|
|
923
|
-
/**
|
|
924
|
-
* Refuse a value that is present but holds nothing.
|
|
925
|
-
*
|
|
926
|
-
* `required: true` is no help: it asks whether a token arrived in argv, and an
|
|
927
|
-
* empty one satisfies it. The way this happens is the shell, not a typo —
|
|
928
|
-
* `--client-id "$CLIENT_ID"` with the variable unset sends exactly that, the
|
|
929
|
-
* expansion dropping the value while the quotes keep the word, so argv carries
|
|
930
|
-
* `["--client-id", ""]`. Unquoted, the word vanishes and oclif refuses with
|
|
931
|
-
* "expects a value"; quoted, nothing complained and the empty string travelled
|
|
932
|
-
* on as if it were a real answer.
|
|
933
|
-
*
|
|
934
|
-
* Refusing during parse rather than in each command puts it at the same point
|
|
935
|
-
* oclif's own refusal lands, so both spellings of a missing value fail the same
|
|
936
|
-
* way, and the value that reaches `run` is already trimmed.
|
|
937
|
-
*/
|
|
938
|
-
function refuseBlank(input, label, message) {
|
|
939
|
-
const value = input.trim();
|
|
940
|
-
if (value === "") throw new ZitadelError("E_VALIDATION", message, {
|
|
941
|
-
hint: `Pass a value for ${label}, or omit it entirely.`,
|
|
942
|
-
details: { argument: label }
|
|
943
|
-
});
|
|
944
|
-
return value;
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* A string flag whose value must not be blank. See {@link refuseBlank}.
|
|
948
|
-
*
|
|
949
|
-
* Not for a flag that writes a field value: an empty string is a legitimate
|
|
950
|
-
* thing to store, so the generated resource flags in `crud/fields.ts` keep
|
|
951
|
-
* plain `Flags.string`.
|
|
952
|
-
*/
|
|
953
|
-
const nonBlankString = Flags.custom({ parse: async (input, _command, flag) => refuseBlank(input, `--${flag.name}`, "the value is empty") });
|
|
954
|
-
/** A positional argument whose value must not be blank. See {@link refuseBlank}. */
|
|
955
|
-
const nonBlankArg = Args.custom({ parse: async (input, _command, arg) => refuseBlank(input, arg.name, `${arg.name} was given an empty value`) });
|
|
956
|
-
//#endregion
|
|
957
969
|
//#region src/lib/local-server/processes.ts
|
|
958
970
|
/** The real answer: the machine's processes, read with `ps`. */
|
|
959
971
|
var PsLocalServers = class {
|
|
@@ -4428,6 +4440,36 @@ function deviceProfileProperties(meta, distinctId) {
|
|
|
4428
4440
|
};
|
|
4429
4441
|
}
|
|
4430
4442
|
//#endregion
|
|
4443
|
+
//#region src/lib/oclif/next-steps.ts
|
|
4444
|
+
/** Made right after a command printed its next steps itself; nothing else calls it. */
|
|
4445
|
+
function asStepsShown() {
|
|
4446
|
+
return {};
|
|
4447
|
+
}
|
|
4448
|
+
/**
|
|
4449
|
+
* The steps as envelope keys: `next_actions` and `next_commands`, each one
|
|
4450
|
+
* present exactly when the command set that list.
|
|
4451
|
+
*/
|
|
4452
|
+
function nextStepsData(next) {
|
|
4453
|
+
return {
|
|
4454
|
+
...next.actions === void 0 ? {} : { next_actions: [...next.actions] },
|
|
4455
|
+
...next.commands === void 0 ? {} : { next_commands: [...next.commands] }
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
/**
|
|
4459
|
+
* The block a run ends with: the prose, then each command on a `$` line, or
|
|
4460
|
+
* `""` when there is nothing to do next.
|
|
4461
|
+
*/
|
|
4462
|
+
function renderNextSteps(next) {
|
|
4463
|
+
const actions = next.actions ?? [];
|
|
4464
|
+
const commands = next.commands ?? [];
|
|
4465
|
+
if (actions.length === 0 && commands.length === 0) return "";
|
|
4466
|
+
return [
|
|
4467
|
+
"Next:",
|
|
4468
|
+
...actions.map((action) => ` ${action}`),
|
|
4469
|
+
...commands.map((command) => ` $ ${command}`)
|
|
4470
|
+
].join("\n");
|
|
4471
|
+
}
|
|
4472
|
+
//#endregion
|
|
4431
4473
|
//#region src/lib/shell.ts
|
|
4432
4474
|
/**
|
|
4433
4475
|
* Quote a value for safe interpolation into a `nextCommands` string an agent or
|
|
@@ -4687,9 +4729,10 @@ var BaseCommand = class extends Command {
|
|
|
4687
4729
|
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
4688
4730
|
}
|
|
4689
4731
|
/**
|
|
4690
|
-
* Final step of every command: in human mode it prints the rendered result
|
|
4691
|
-
*
|
|
4692
|
-
*
|
|
4732
|
+
* Final step of every command: in human mode it prints the rendered result,
|
|
4733
|
+
* then the result's next steps, then its footer (oclif suppresses
|
|
4734
|
+
* {@link Command.log} under `--json`); it returns the envelope so oclif's
|
|
4735
|
+
* `--json` path serialises it.
|
|
4693
4736
|
*/
|
|
4694
4737
|
emit(result) {
|
|
4695
4738
|
const normalized = normalizeCommandResult(result, this.meta);
|
|
@@ -4698,8 +4741,11 @@ var BaseCommand = class extends Command {
|
|
|
4698
4741
|
status: result.status,
|
|
4699
4742
|
duration_ms: Date.now() - this.telemetryStartedAt
|
|
4700
4743
|
}));
|
|
4701
|
-
const
|
|
4744
|
+
const next = normalized.status === "ok" && normalized.nextShown !== void 0 ? "" : renderNextSteps(normalized.next ?? {});
|
|
4745
|
+
const beside = normalized.status === "ok" && normalized.machineReadable === true;
|
|
4746
|
+
const rendered = renderPretty(normalized, this.meta, beside ? "" : next);
|
|
4702
4747
|
if (rendered !== "") this.log(rendered);
|
|
4748
|
+
if (beside && next !== "") this.logToStderr(rendered !== "" && process.stdout.isTTY ? `\n${next}` : next);
|
|
4703
4749
|
if (normalized.status === "ok" && normalized.footer !== void 0) this.log(`\n${normalized.footer}`);
|
|
4704
4750
|
const reported = takeReportedWarnings();
|
|
4705
4751
|
const envelope = toEnvelope(withReportedWarnings(normalized, reported), this.meta);
|
|
@@ -4774,22 +4820,26 @@ var BaseCommand = class extends Command {
|
|
|
4774
4820
|
};
|
|
4775
4821
|
}
|
|
4776
4822
|
};
|
|
4823
|
+
/** Rewrites each suggested `zitadel …` command to the public form the reader can run. */
|
|
4777
4824
|
function normalizeCommandResult(result, meta) {
|
|
4778
|
-
if (result.
|
|
4779
|
-
|
|
4780
|
-
data: normalizeDataNextCommands(result.data, meta)
|
|
4781
|
-
};
|
|
4825
|
+
if (result.next?.commands === void 0) return result;
|
|
4826
|
+
const commands = normalizePublicCliCommands(result.next.commands, meta.cliVersion);
|
|
4782
4827
|
return {
|
|
4783
4828
|
...result,
|
|
4784
|
-
|
|
4785
|
-
|
|
4829
|
+
next: {
|
|
4830
|
+
...result.next,
|
|
4831
|
+
commands
|
|
4832
|
+
}
|
|
4786
4833
|
};
|
|
4787
4834
|
}
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4835
|
+
/**
|
|
4836
|
+
* The data with its next steps written in. Data that is not an object (a
|
|
4837
|
+
* write the server answered without a body) has nowhere to hold them.
|
|
4838
|
+
*/
|
|
4839
|
+
function withNextSteps(data, next) {
|
|
4840
|
+
return next === void 0 || !isObject(data) ? data : {
|
|
4791
4841
|
...data,
|
|
4792
|
-
|
|
4842
|
+
...nextStepsData(next)
|
|
4793
4843
|
};
|
|
4794
4844
|
}
|
|
4795
4845
|
/** Add the warnings reported while the command ran to an ok result. */
|
|
@@ -4820,7 +4870,7 @@ function toEnvelope(result, meta) {
|
|
|
4820
4870
|
if (result.status === "ok") return {
|
|
4821
4871
|
...base,
|
|
4822
4872
|
status: "ok",
|
|
4823
|
-
data: result.data,
|
|
4873
|
+
data: withNextSteps(result.data, result.next),
|
|
4824
4874
|
warnings: result.warnings ? [...result.warnings] : []
|
|
4825
4875
|
};
|
|
4826
4876
|
return {
|
|
@@ -4828,7 +4878,7 @@ function toEnvelope(result, meta) {
|
|
|
4828
4878
|
status: "skipped",
|
|
4829
4879
|
reason: result.reason,
|
|
4830
4880
|
data: result.data,
|
|
4831
|
-
next_commands: result.
|
|
4881
|
+
next_commands: result.next?.commands ? [...result.next.commands] : void 0
|
|
4832
4882
|
};
|
|
4833
4883
|
}
|
|
4834
4884
|
/** Builds the failure envelope from a {@link ZitadelError} and the invocation metadata. */
|
|
@@ -4849,20 +4899,21 @@ function toErrorEnvelope(error, meta) {
|
|
|
4849
4899
|
* Renders a {@link CommandResult} as human-facing text for non-JSON mode. A
|
|
4850
4900
|
* command may supply a bespoke `pretty` string (e.g. `deploy`'s summary);
|
|
4851
4901
|
* otherwise success payloads are summarised by {@link formatData} and skips are
|
|
4852
|
-
* shown with their reason
|
|
4902
|
+
* shown with their reason. `next` is the rendered next-step block, which
|
|
4903
|
+
* follows the rendering whichever it is, and comes before the warnings.
|
|
4853
4904
|
*/
|
|
4854
|
-
function renderPretty(result, meta) {
|
|
4905
|
+
function renderPretty(result, meta, next) {
|
|
4855
4906
|
if (result.pretty !== void 0) {
|
|
4856
4907
|
const warnings = result.status === "ok" ? result.warnings ?? [] : [];
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
for (const cmd of result.nextCommands) lines.push(` $ ${cmd}`);
|
|
4908
|
+
const gap = result.status === "ok" ? "\n" : "";
|
|
4909
|
+
return [
|
|
4910
|
+
next === "" || result.pretty === "" ? result.pretty : `${result.pretty.replace(/\n+$/, "")}${gap}`,
|
|
4911
|
+
next,
|
|
4912
|
+
...warnings.map((warning) => `Warning: ${warning}`)
|
|
4913
|
+
].filter((line) => line !== "").join("\n");
|
|
4864
4914
|
}
|
|
4865
|
-
|
|
4915
|
+
if (result.status === "ok") return formatData(result.data, result.warnings ? [...result.warnings] : [], meta, next);
|
|
4916
|
+
return [`Skipped: ${result.reason}${suffixBlock(meta)}`, next].filter(Boolean).join("\n");
|
|
4866
4917
|
}
|
|
4867
4918
|
/**
|
|
4868
4919
|
* Renders a {@link ZitadelError} as a human-readable block for stderr: the
|
|
@@ -4871,38 +4922,23 @@ function renderPretty(result, meta) {
|
|
|
4871
4922
|
function renderError(error, meta) {
|
|
4872
4923
|
const lines = [`Error ${error.code}: ${error.message}`];
|
|
4873
4924
|
if (error.hint) lines.push(error.hint);
|
|
4874
|
-
const
|
|
4875
|
-
if (
|
|
4876
|
-
lines.push("Next:");
|
|
4877
|
-
for (const cmd of nextCommands) lines.push(` $ ${cmd}`);
|
|
4878
|
-
}
|
|
4925
|
+
const next = renderNextSteps({ commands: normalizePublicCliCommands(error.nextCommands, meta.cliVersion) });
|
|
4926
|
+
if (next !== "") lines.push(next);
|
|
4879
4927
|
return lines.join("\n");
|
|
4880
4928
|
}
|
|
4881
|
-
function formatData(data, warnings, opts) {
|
|
4929
|
+
function formatData(data, warnings, opts, next) {
|
|
4882
4930
|
if (typeof data === "string") {
|
|
4883
4931
|
const suffix = sourceSuffix(opts);
|
|
4884
|
-
|
|
4932
|
+
const text = suffix ? `${data}\n${suffix}` : data;
|
|
4933
|
+
return next === "" ? text : `${text}\n\n${next}`;
|
|
4885
4934
|
}
|
|
4886
4935
|
const lines = [];
|
|
4887
4936
|
const titleLine = isObject(data) && typeof data.title === "string" ? String(data.title) : "Zitadel command completed.";
|
|
4888
4937
|
lines.push(titleLine);
|
|
4889
4938
|
const suffix = sourceSuffix(opts);
|
|
4890
4939
|
if (suffix) lines.push(suffix);
|
|
4891
|
-
if (isObject(data))
|
|
4892
|
-
|
|
4893
|
-
if (Array.isArray(data.next_actions) && data.next_actions.length > 0) {
|
|
4894
|
-
lines.push("");
|
|
4895
|
-
lines.push("Next:");
|
|
4896
|
-
for (const action of data.next_actions) lines.push(` ${String(action)}`);
|
|
4897
|
-
}
|
|
4898
|
-
if (Array.isArray(data.next_commands) && data.next_commands.length > 0) {
|
|
4899
|
-
if (!Array.isArray(data.next_actions) || data.next_actions.length === 0) {
|
|
4900
|
-
lines.push("");
|
|
4901
|
-
lines.push("Next:");
|
|
4902
|
-
}
|
|
4903
|
-
for (const cmd of data.next_commands) lines.push(` $ ${String(cmd)}`);
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4940
|
+
if (isObject(data)) renderKnownSections(lines, data);
|
|
4941
|
+
if (next !== "") lines.push("", next);
|
|
4906
4942
|
for (const warning of warnings.filter((warning) => !warningRenderedInChecks(data, warning))) lines.push(`Warning: ${warning}`);
|
|
4907
4943
|
return lines.join("\n");
|
|
4908
4944
|
}
|
|
@@ -5772,8 +5808,8 @@ var EnvironmentCommand = class extends BaseCommand {
|
|
|
5772
5808
|
* terminal: on a pipe the result travels alone, and `--json` silences them
|
|
5773
5809
|
* either way. `quiet` is for a command whose own output is that summary.
|
|
5774
5810
|
*
|
|
5775
|
-
* `verbatim` is for a caller that stores or
|
|
5776
|
-
* `SanitizeOptions`).
|
|
5811
|
+
* `verbatim` is for a caller that stores, compares or sends back what it
|
|
5812
|
+
* reads (see `SanitizeOptions`).
|
|
5777
5813
|
* `prompt: false` is for a best-effort probe that must never stop to ask
|
|
5778
5814
|
* (or write `.env.<name>.local`): a missing value then fails as it would
|
|
5779
5815
|
* non-interactively.
|
|
@@ -5797,7 +5833,7 @@ var EnvironmentCommand = class extends BaseCommand {
|
|
|
5797
5833
|
consola.info(`Server ${server}`);
|
|
5798
5834
|
}
|
|
5799
5835
|
return {
|
|
5800
|
-
client: createZitadelClient({
|
|
5836
|
+
client: createZitadelClient$1({
|
|
5801
5837
|
baseUrl: server,
|
|
5802
5838
|
token
|
|
5803
5839
|
}, { verbatim: options.verbatim }),
|
|
@@ -6149,71 +6185,6 @@ async function publishedIdsOf(cwd, path) {
|
|
|
6149
6185
|
}
|
|
6150
6186
|
}
|
|
6151
6187
|
//#endregion
|
|
6152
|
-
//#region src/lib/oclif/envelope.ts
|
|
6153
|
-
/**
|
|
6154
|
-
* Builders for the `data` of a command's JSON envelope.
|
|
6155
|
-
*
|
|
6156
|
-
* Every command emits `data`, and an agent reads all of them, so the shapes
|
|
6157
|
-
* must not drift. These builders are the one place each shape is defined; the
|
|
6158
|
-
* spec harness (`assertEnvelope`) enforces the same shapes on every `--json`
|
|
6159
|
-
* invocation, so a command that hand-builds a divergent shape fails in its own
|
|
6160
|
-
* spec. Use the builder rather than an object literal.
|
|
6161
|
-
*/
|
|
6162
|
-
/**
|
|
6163
|
-
* The canonical list/collection envelope: `{ items, count, next_page_token }`,
|
|
6164
|
-
* plus optional `next_commands`. Every command that returns a collection emits
|
|
6165
|
-
* this, so `{ items, count, next_page_token }` reads uniformly across all of
|
|
6166
|
-
* them. A single-page endpoint passes `nextPageToken: null` (the default); a
|
|
6167
|
-
* paginated one passes the server cursor (or `null` once drained).
|
|
6168
|
-
*/
|
|
6169
|
-
function listData(items, opts = {}) {
|
|
6170
|
-
const base = {
|
|
6171
|
-
items,
|
|
6172
|
-
count: items.length,
|
|
6173
|
-
next_page_token: opts.nextPageToken ?? null
|
|
6174
|
-
};
|
|
6175
|
-
return opts.nextCommands && opts.nextCommands.length > 0 ? {
|
|
6176
|
-
...base,
|
|
6177
|
-
next_commands: opts.nextCommands
|
|
6178
|
-
} : base;
|
|
6179
|
-
}
|
|
6180
|
-
/**
|
|
6181
|
-
* The canonical delete/remove confirmation: the resource's identifier plus
|
|
6182
|
-
* `deleted: true`. The verb is always `deleted` — never `removed` — so an agent
|
|
6183
|
-
* reads "did the delete succeed?" the same way for every resource, whether it is
|
|
6184
|
-
* addressed by `id` or by a natural key (`url`, `pattern`, `name`). A lifecycle
|
|
6185
|
-
* verb (revoke, deactivate) uses {@link mutatedData} instead.
|
|
6186
|
-
*/
|
|
6187
|
-
function deletedData(identifier) {
|
|
6188
|
-
return {
|
|
6189
|
-
...identifier,
|
|
6190
|
-
deleted: true
|
|
6191
|
-
};
|
|
6192
|
-
}
|
|
6193
|
-
/**
|
|
6194
|
-
* A mutation confirmation whose verb is the lifecycle action, not `deleted`:
|
|
6195
|
-
* the identifier plus `<verb>: true` (e.g. `revoked`, `deactivated`). Keeps the
|
|
6196
|
-
* `{ identifier, <past-tense>: true }` shape of {@link deletedData} while naming
|
|
6197
|
-
* the specific action.
|
|
6198
|
-
*/
|
|
6199
|
-
function mutatedData(identifier, verb) {
|
|
6200
|
-
return {
|
|
6201
|
-
...identifier,
|
|
6202
|
-
[verb]: true
|
|
6203
|
-
};
|
|
6204
|
-
}
|
|
6205
|
-
/**
|
|
6206
|
-
* The `next_commands` a command's `data` carries, spread into the data object:
|
|
6207
|
-
* `{ ...result, ...nextCommandsData(commands) }`. It takes `SuggestedCommand`s,
|
|
6208
|
-
* which only the `lib/suggestion` builders make, so every entry has had its
|
|
6209
|
-
* environment selector decided (#1689). A Biome GritQL rule
|
|
6210
|
-
* (`biome-plugins/no-raw-next-commands.grit`) rejects a hand-written
|
|
6211
|
-
* `next_commands:` key anywhere else.
|
|
6212
|
-
*/
|
|
6213
|
-
function nextCommandsData(commands) {
|
|
6214
|
-
return { next_commands: commands };
|
|
6215
|
-
}
|
|
6216
|
-
//#endregion
|
|
6217
6188
|
//#region src/lib/oclif/promptable.ts
|
|
6218
6189
|
/**
|
|
6219
6190
|
* Marks a command that can ask a person a question, and gives it the one way
|
|
@@ -6353,7 +6324,8 @@ var AuthMethodCommand = class extends Promptable(EnvironmentCommand) {
|
|
|
6353
6324
|
}
|
|
6354
6325
|
/**
|
|
6355
6326
|
* The follow-up commands of a result, as strings when every argument is
|
|
6356
|
-
* safe to run as written, and always as
|
|
6327
|
+
* safe to run as written (the result's `next` commands), and always as
|
|
6328
|
+
* argument lists (its `next_args`).
|
|
6357
6329
|
*
|
|
6358
6330
|
* @param changed - Whether a file changed, which `deploy` then publishes.
|
|
6359
6331
|
* @param cwdArgs - `--cwd` for `deploy`, from {@link target}.
|
|
@@ -6372,8 +6344,8 @@ var AuthMethodCommand = class extends Promptable(EnvironmentCommand) {
|
|
|
6372
6344
|
...cwdArgs
|
|
6373
6345
|
]] : []];
|
|
6374
6346
|
return {
|
|
6375
|
-
|
|
6376
|
-
|
|
6347
|
+
commands: portableSuggestions(args, this.meta.cliVersion, [DEPLOY_MESSAGE]),
|
|
6348
|
+
args
|
|
6377
6349
|
};
|
|
6378
6350
|
}
|
|
6379
6351
|
/**
|
|
@@ -6443,12 +6415,14 @@ var AuthMethodCommand = class extends Promptable(EnvironmentCommand) {
|
|
|
6443
6415
|
consola.success(`Updated ${schema.path}`);
|
|
6444
6416
|
} else consola.info(`${method} is already ${verb}d for ${schema.name}`);
|
|
6445
6417
|
this.reportOutcome(outcome);
|
|
6418
|
+
const followUps = this.followUps(change.changed, cwdArgs);
|
|
6446
6419
|
return this.emit({
|
|
6447
6420
|
status: "ok",
|
|
6448
6421
|
data: {
|
|
6449
6422
|
...data,
|
|
6450
|
-
|
|
6423
|
+
next_args: followUps.args
|
|
6451
6424
|
},
|
|
6425
|
+
next: { commands: followUps.commands },
|
|
6452
6426
|
pretty: change.changed ? `${enabled ? "Enabled" : "Disabled"} ${method} for ${schema.name}` : `Nothing to ${verb} for ${schema.name}`
|
|
6453
6427
|
});
|
|
6454
6428
|
}
|
|
@@ -6607,7 +6581,7 @@ var Api = class Api extends Promptable(BaseCommand) {
|
|
|
6607
6581
|
}
|
|
6608
6582
|
/** Choose an operation the CLI can call: its part of the API, then the operation. */
|
|
6609
6583
|
async chooseOperation() {
|
|
6610
|
-
const callable = OPERATIONS.filter((operation) => uncallable(operation) === void 0);
|
|
6584
|
+
const callable = OPERATIONS$1.filter((operation) => uncallable(operation) === void 0);
|
|
6611
6585
|
const retry = this.suggestUnscoped("api ls --json");
|
|
6612
6586
|
const tag = await this.input({
|
|
6613
6587
|
kind: "select",
|
|
@@ -6652,69 +6626,6 @@ var Api = class Api extends Promptable(BaseCommand) {
|
|
|
6652
6626
|
}
|
|
6653
6627
|
};
|
|
6654
6628
|
//#endregion
|
|
6655
|
-
//#region src/lib/oclif/crud/table.ts
|
|
6656
|
-
/**
|
|
6657
|
-
* The machine form of a list: one record per line, fields separated by tabs,
|
|
6658
|
-
* no header and no padding, so `cut -f2` and `awk -F'\t'` work. This is what a
|
|
6659
|
-
* piped run emits — the aligned table is for a terminal, where nothing is
|
|
6660
|
-
* parsing it. Tabs and newlines inside a value would break the record, so they
|
|
6661
|
-
* are escaped rather than passed through.
|
|
6662
|
-
*/
|
|
6663
|
-
const renderRows = (columns, items) => items.map((item) => columns.map((column) => readCell(column, item).replaceAll(" ", "\\t").replaceAll("\n", "\\n")).join(" ")).join("\n");
|
|
6664
|
-
/**
|
|
6665
|
-
* Render `items` as an aligned text table, one column per dot-path in
|
|
6666
|
-
* `columns`, headed by each path's last segment. Nested paths resolve
|
|
6667
|
-
* through objects; missing values render empty; non-strings render as JSON.
|
|
6668
|
-
*/
|
|
6669
|
-
const renderTable = (columns, items) => {
|
|
6670
|
-
const headers = columns.map((column) => column.split(".").at(-1) ?? column);
|
|
6671
|
-
const rows = items.map((item) => columns.map((column) => readCell(column, item)));
|
|
6672
|
-
const widths = rows.reduce((current, row) => current.map((width, i) => Math.max(width, row[i]?.length ?? 0)), headers.map((header) => header.length));
|
|
6673
|
-
const line = (cells) => cells.map((cell, i) => cell.padEnd(widths[i] ?? 0)).join(" ").trimEnd();
|
|
6674
|
-
return [
|
|
6675
|
-
line(headers),
|
|
6676
|
-
line(widths.map((width) => "-".repeat(width))),
|
|
6677
|
-
...rows.map(line)
|
|
6678
|
-
].join("\n");
|
|
6679
|
-
};
|
|
6680
|
-
/**
|
|
6681
|
-
* The whole terminal rendering of a list, so no command re-derives the
|
|
6682
|
-
* plain/TTY/empty/footer logic (and so none forgets `--plain`): a pipe (or
|
|
6683
|
-
* `--plain`) gets tab-separated rows with no header and nothing when empty, so
|
|
6684
|
-
* `cut`/`awk`/`wc -l` work; a terminal gets the aligned table, a blank line, a
|
|
6685
|
-
* `${n} ${singular|plural}` count, and any `footer` lines. Pair it with
|
|
6686
|
-
* `listData()` for the JSON envelope.
|
|
6687
|
-
*/
|
|
6688
|
-
const renderList = (columns, rows, opts) => {
|
|
6689
|
-
if (opts.plain) return renderRows(columns, rows);
|
|
6690
|
-
if (rows.length === 0) return opts.empty;
|
|
6691
|
-
const count = `${rows.length} ${rows.length === 1 ? opts.singular : opts.plural ?? `${opts.singular}s`}`;
|
|
6692
|
-
return [
|
|
6693
|
-
renderTable(columns, rows),
|
|
6694
|
-
"",
|
|
6695
|
-
count,
|
|
6696
|
-
...opts.footer ?? []
|
|
6697
|
-
].join("\n");
|
|
6698
|
-
};
|
|
6699
|
-
/** One cell: a dot-path resolved through objects, rendered as text. */
|
|
6700
|
-
const readCell = (column, item) => {
|
|
6701
|
-
const value = column.split(".").reduce((current, key) => isObject(current) ? current[key] : void 0, item);
|
|
6702
|
-
return value == null ? "" : typeof value === "string" ? value : JSON.stringify(value);
|
|
6703
|
-
};
|
|
6704
|
-
/**
|
|
6705
|
-
* One record for a person: the heading first, then a labelled field per line.
|
|
6706
|
-
* Nested paths are labelled by their last segment, so `metadata.status` reads
|
|
6707
|
-
* as `status`. It is the terminal rendering of `get`; a pipe (and `--json`)
|
|
6708
|
-
* still receives the whole object, since a script wants the record, not a view
|
|
6709
|
-
* of it.
|
|
6710
|
-
*/
|
|
6711
|
-
const renderDetail = (heading, fields, item) => {
|
|
6712
|
-
const rows = fields.map((field) => [field.split(".").at(-1) ?? field, readCell(field, item)]).filter(([, value]) => value !== "");
|
|
6713
|
-
const width = Math.max(0, ...rows.map(([label]) => label.length));
|
|
6714
|
-
const body = rows.map(([label, value]) => `${label.padEnd(width)} ${value}`);
|
|
6715
|
-
return [...heading ? [heading, ""] : [], ...body].join("\n");
|
|
6716
|
-
};
|
|
6717
|
-
//#endregion
|
|
6718
6629
|
//#region src/commands/api/ls.ts
|
|
6719
6630
|
/** One operation as `api ls` lists it. */
|
|
6720
6631
|
const entryOf = (operation) => ({
|
|
@@ -6920,12 +6831,14 @@ var AuthMethodSsoDisable = class AuthMethodSsoDisable extends AuthMethodCommand
|
|
|
6920
6831
|
for (const path of changed) consola.success(`Updated ${path}`);
|
|
6921
6832
|
if (changed.length === 0) consola.info(`${schema.name} does not offer ${provider}`);
|
|
6922
6833
|
this.reportOutcome(outcome);
|
|
6834
|
+
const followUps = this.followUps(changed.length > 0, cwdArgs);
|
|
6923
6835
|
return this.emit({
|
|
6924
6836
|
status: "ok",
|
|
6925
6837
|
data: {
|
|
6926
6838
|
...data,
|
|
6927
|
-
|
|
6839
|
+
next_args: followUps.args
|
|
6928
6840
|
},
|
|
6841
|
+
next: { commands: followUps.commands },
|
|
6929
6842
|
pretty: changed.length > 0 ? `Removed ${provider} from ${where}` : `${schema.name} does not offer ${provider}`
|
|
6930
6843
|
});
|
|
6931
6844
|
}
|
|
@@ -7166,11 +7079,11 @@ var AuthMethodSsoEnable = class AuthMethodSsoEnable extends AuthMethodCommand {
|
|
|
7166
7079
|
const unpublished = !reusing || written.length > 0;
|
|
7167
7080
|
const stored = secret?.published === "stored" && (clientIdState === void 0 || clientIdState === "stored") ? ["Provider credentials are stored on your Project."] : [];
|
|
7168
7081
|
const name = entry.displayName;
|
|
7082
|
+
const followUps = this.followUps(unpublished, scopeArgs, recovery);
|
|
7169
7083
|
return this.emit({
|
|
7170
7084
|
status: "ok",
|
|
7171
7085
|
data: {
|
|
7172
7086
|
title: unpublished ? [`${name} sign-in is configured locally.`, ...stored].join(" ") : `${name} sign-in is already configured locally.`,
|
|
7173
|
-
...unpublished ? { next_actions: [`Deploy the configuration to make ${name} sign-in available in your app, then reload your app and test ${name} sign-in.`] } : {},
|
|
7174
7087
|
...this.payload({
|
|
7175
7088
|
projectId,
|
|
7176
7089
|
provider,
|
|
@@ -7183,7 +7096,11 @@ var AuthMethodSsoEnable = class AuthMethodSsoEnable extends AuthMethodCommand {
|
|
|
7183
7096
|
changed: written,
|
|
7184
7097
|
skipped: edits.skipped
|
|
7185
7098
|
}),
|
|
7186
|
-
|
|
7099
|
+
next_args: followUps.args
|
|
7100
|
+
},
|
|
7101
|
+
next: {
|
|
7102
|
+
...unpublished ? { actions: [`Deploy the configuration to make ${name} sign-in available in your app, then reload your app and test ${name} sign-in.`] } : {},
|
|
7103
|
+
commands: followUps.commands
|
|
7187
7104
|
}
|
|
7188
7105
|
});
|
|
7189
7106
|
}
|
|
@@ -7557,50 +7474,6 @@ async function linkOnPath(cwd, path) {
|
|
|
7557
7474
|
}
|
|
7558
7475
|
}
|
|
7559
7476
|
//#endregion
|
|
7560
|
-
//#region src/lib/release/release.ts
|
|
7561
|
-
/**
|
|
7562
|
-
* Build a release from the `.zitadel/` bundle (`POST /releases/build`). The
|
|
7563
|
-
* server reuses the revisions that already match and allocates the rest, and
|
|
7564
|
-
* dedupes on content: re-posting the same bundle returns the release that
|
|
7565
|
-
* already carries it. (`POST /releases` pins pre-existing revision ids and is
|
|
7566
|
-
* not a CLI flow.)
|
|
7567
|
-
*/
|
|
7568
|
-
async function createReleaseFromBundle(client, projectId, bundle, source) {
|
|
7569
|
-
const body = {
|
|
7570
|
-
bundle,
|
|
7571
|
-
...source.message === void 0 ? {} : { message: source.message },
|
|
7572
|
-
...source.gitSha === null ? {} : { git_sha: source.gitSha },
|
|
7573
|
-
git_dirty: source.gitDirty
|
|
7574
|
-
};
|
|
7575
|
-
return await client.buildRelease(body, { project_id: projectId });
|
|
7576
|
-
}
|
|
7577
|
-
/** Revoke a release so its content is never served again. Idempotent. */
|
|
7578
|
-
async function revokeRelease(client, projectId, releaseId) {
|
|
7579
|
-
return client.revokeRelease(releaseId, { project_id: projectId });
|
|
7580
|
-
}
|
|
7581
|
-
//#endregion
|
|
7582
|
-
//#region src/lib/release/pipeline.ts
|
|
7583
|
-
/**
|
|
7584
|
-
* Run the before-release steps in order, skipping the ones that do not apply to
|
|
7585
|
-
* this publish or that write on a dry run, and return what they report, for the
|
|
7586
|
-
* command's `--json` data. The command creates the release and the deployment
|
|
7587
|
-
* itself, between and after the step groups: before-release steps → create
|
|
7588
|
-
* release → before-deploy steps → create deployment.
|
|
7589
|
-
*/
|
|
7590
|
-
async function runSteps(steps, ctx) {
|
|
7591
|
-
let report = {};
|
|
7592
|
-
for (const step of steps) {
|
|
7593
|
-
if (!step.runsFor.includes(ctx.mode)) continue;
|
|
7594
|
-
if (ctx.dryRun && step.writes) continue;
|
|
7595
|
-
const reported = await step.run(ctx);
|
|
7596
|
-
if (reported) report = {
|
|
7597
|
-
...report,
|
|
7598
|
-
...reported
|
|
7599
|
-
};
|
|
7600
|
-
}
|
|
7601
|
-
return report;
|
|
7602
|
-
}
|
|
7603
|
-
//#endregion
|
|
7604
7477
|
//#region src/lib/release/origins.ts
|
|
7605
7478
|
/**
|
|
7606
7479
|
* Whether an origin pattern covers a concrete origin URL, matching the server's
|
|
@@ -7681,9 +7554,11 @@ var BrandingEject = class BrandingEject extends Promptable(BaseCommand) {
|
|
|
7681
7554
|
status: "ok",
|
|
7682
7555
|
data: {
|
|
7683
7556
|
design,
|
|
7684
|
-
files_written: filesWritten
|
|
7685
|
-
|
|
7686
|
-
|
|
7557
|
+
files_written: filesWritten
|
|
7558
|
+
},
|
|
7559
|
+
next: {
|
|
7560
|
+
actions: [`Edit ${DEFAULT_BRANDING_TEMPLATE_PATH} — the login UI is yours now.`, "Deploy --dry-run validates the template and shows the diff; deploy publishes it."],
|
|
7561
|
+
commands: [this.suggestUnscoped("deploy --dry-run"), this.suggestUnscoped(DEPLOY_ARGS)]
|
|
7687
7562
|
}
|
|
7688
7563
|
});
|
|
7689
7564
|
}
|
|
@@ -7811,12 +7686,15 @@ async function policiesFromFiles(cwd, operations = []) {
|
|
|
7811
7686
|
async function policiesFromRelease(client, projectId, releaseId) {
|
|
7812
7687
|
const release = await client.getReleaseById(releaseId, { project_id: projectId });
|
|
7813
7688
|
const pinned = release.pointers.filter((pointer) => pointer.kind === "policy");
|
|
7814
|
-
const revisions = await Promise.all(pinned.map((pointer) =>
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
config:
|
|
7689
|
+
const revisions = await Promise.all(pinned.map(async (pointer) => {
|
|
7690
|
+
const revision = await client.getPolicyRevision(pointer.revision_id, { project_id: projectId });
|
|
7691
|
+
return [pointer.handle, {
|
|
7692
|
+
config: revision.config,
|
|
7818
7693
|
source: { revision_id: pointer.revision_id }
|
|
7819
|
-
}]
|
|
7694
|
+
}];
|
|
7695
|
+
}));
|
|
7696
|
+
return {
|
|
7697
|
+
policies: new Map(revisions),
|
|
7820
7698
|
revoked: release.revoked_at !== null,
|
|
7821
7699
|
pinsFlow: release.pointers.some((pointer) => pointer.kind === "flow_definition")
|
|
7822
7700
|
};
|
|
@@ -8408,7 +8286,7 @@ var Claim = class Claim extends EnvironmentCommand {
|
|
|
8408
8286
|
project_id: projectId,
|
|
8409
8287
|
would: localRecord && !flags["no-wait"] ? `${action}, then record the owning team in .zitadel/secret.` : `${action}.`
|
|
8410
8288
|
},
|
|
8411
|
-
|
|
8289
|
+
next: { commands: [challengeId === void 0 ? startOver : this.suggest(`claim --challenge-id ${challengeId}`)] }
|
|
8412
8290
|
});
|
|
8413
8291
|
}
|
|
8414
8292
|
const { client } = prompted ?? await this.connect();
|
|
@@ -8478,9 +8356,9 @@ var Claim = class Claim extends EnvironmentCommand {
|
|
|
8478
8356
|
project_id: projectId,
|
|
8479
8357
|
team_id: completed.team_id,
|
|
8480
8358
|
claimed_at: completed.claimed_at,
|
|
8481
|
-
dashboard_url: completed.dashboard_url
|
|
8482
|
-
|
|
8483
|
-
}
|
|
8359
|
+
dashboard_url: completed.dashboard_url
|
|
8360
|
+
},
|
|
8361
|
+
next: { actions: ["Manage your Project and collaborate in the Console:", completed.dashboard_url] }
|
|
8484
8362
|
});
|
|
8485
8363
|
}
|
|
8486
8364
|
/**
|
|
@@ -8558,9 +8436,11 @@ var Claim = class Claim extends EnvironmentCommand {
|
|
|
8558
8436
|
project_id: projectId,
|
|
8559
8437
|
claim_url: challenge.claim_url,
|
|
8560
8438
|
challenge_id: challenge.challenge_id,
|
|
8561
|
-
expires_at: challenge.expires_at
|
|
8562
|
-
|
|
8563
|
-
|
|
8439
|
+
expires_at: challenge.expires_at
|
|
8440
|
+
},
|
|
8441
|
+
next: {
|
|
8442
|
+
actions: ["Wait for the browser step to finish with:"],
|
|
8443
|
+
commands: isChallengeId(challenge.challenge_id) ? [this.suggest(`claim --challenge-id ${challenge.challenge_id}`)] : []
|
|
8564
8444
|
}
|
|
8565
8445
|
});
|
|
8566
8446
|
}
|
|
@@ -8834,14 +8714,18 @@ async function consoleSignInUrl(serverUrl, admin) {
|
|
|
8834
8714
|
const { handoffToken } = await signIn(serverUrl, admin);
|
|
8835
8715
|
return `${serverUrl}/ui/console/login?handoff=${encodeURIComponent(handoffToken)}`;
|
|
8836
8716
|
}
|
|
8717
|
+
/** The names the server sets its session cookie under (ADR 083), over plain http to a loopback host and over https. */
|
|
8718
|
+
const SESSION_COOKIES = ["zitadel_session", "__Host-zitadel_session"];
|
|
8837
8719
|
/**
|
|
8838
|
-
* Signs in and trades the handoff token for the
|
|
8839
|
-
*
|
|
8840
|
-
*
|
|
8720
|
+
* Signs in and trades the handoff token for the session cookie — the
|
|
8721
|
+
* credential endpoints like `claim/complete` authenticate against, which a bare
|
|
8722
|
+
* handoff token cannot satisfy. It is `zitadel_session` from a server on
|
|
8723
|
+
* loopback http and `__Host-zitadel_session` from one on https, and goes back
|
|
8724
|
+
* under the name it came with.
|
|
8841
8725
|
*/
|
|
8842
8726
|
async function adminSessionCookie(serverUrl, admin) {
|
|
8843
8727
|
const { handoffToken, publishableKey } = await signIn(serverUrl, admin);
|
|
8844
|
-
const { res } = await request(`${serverUrl}/sessions/exchange?project_id=${encodeURIComponent(PLATFORM_PROJECT_ID)}`, {
|
|
8728
|
+
const { res } = await request(`${apiBaseUrl(serverUrl)}/sessions/exchange?project_id=${encodeURIComponent(PLATFORM_PROJECT_ID)}`, {
|
|
8845
8729
|
...localAdminRequest(serverUrl, {
|
|
8846
8730
|
"content-type": "application/json",
|
|
8847
8731
|
authorization: `Bearer ${publishableKey}`
|
|
@@ -8849,7 +8733,7 @@ async function adminSessionCookie(serverUrl, admin) {
|
|
|
8849
8733
|
method: "POST",
|
|
8850
8734
|
body: JSON.stringify({ handoff_token: handoffToken })
|
|
8851
8735
|
}, { signal: interruptSignal() });
|
|
8852
|
-
const cookie = res.headers.getSetCookie().
|
|
8736
|
+
const cookie = res.headers.getSetCookie().map((value) => value.split(";", 1)[0] ?? "").find((pair) => SESSION_COOKIES.some((name) => pair.startsWith(`${name}=`)));
|
|
8853
8737
|
if (!res.ok || !cookie) throw new ZitadelError("E_AUTH", `Local admin session exchange failed (${String(res.status)})`, { details: { status: res.status } });
|
|
8854
8738
|
return cookie;
|
|
8855
8739
|
}
|
|
@@ -8864,7 +8748,7 @@ async function signIn(serverUrl, admin) {
|
|
|
8864
8748
|
nextCommands: [unscopedCommand("stop"), unscopedCommand("start")],
|
|
8865
8749
|
details: { server_url: serverUrl }
|
|
8866
8750
|
});
|
|
8867
|
-
const client = createZitadelClient({
|
|
8751
|
+
const client = createZitadelClient$1({
|
|
8868
8752
|
baseUrl: serverUrl,
|
|
8869
8753
|
token: runtime.publishable_key
|
|
8870
8754
|
});
|
|
@@ -8957,9 +8841,9 @@ var Console = class Console extends BaseCommand {
|
|
|
8957
8841
|
title: "Local console sign-in link.",
|
|
8958
8842
|
signed_in_as: admin.email,
|
|
8959
8843
|
sign_in_url: signInUrl,
|
|
8960
|
-
browser_opened: opened
|
|
8961
|
-
|
|
8962
|
-
}
|
|
8844
|
+
browser_opened: opened
|
|
8845
|
+
},
|
|
8846
|
+
next: { actions: ["The link signs you in once. Run `zitadel console` again for a fresh one."] }
|
|
8963
8847
|
});
|
|
8964
8848
|
}
|
|
8965
8849
|
};
|
|
@@ -9017,30 +8901,30 @@ var Deploy = class Deploy extends EnvironmentCommand {
|
|
|
9017
8901
|
static group = CommandGroups.configuration;
|
|
9018
8902
|
static groupOrder = 1;
|
|
9019
8903
|
static examples = ["<%= config.bin %> deploy -m 'add phone_number to human-user'", "<%= config.bin %> deploy --dry-run"];
|
|
9020
|
-
static flags = { message:
|
|
9021
|
-
char: "m",
|
|
9022
|
-
description: "A short summary of what this release changes."
|
|
9023
|
-
}) };
|
|
8904
|
+
static flags = { message: messageFlag("A short summary of what this release changes. Defaults to the pull request's title or the current commit's subject.") };
|
|
9024
8905
|
async run() {
|
|
9025
8906
|
const { flags } = await this.parse(Deploy);
|
|
9026
8907
|
await this.toMeta(flags);
|
|
9027
8908
|
const { cwd, env, dryRun, isTTY, nonInteractive } = this.meta;
|
|
9028
8909
|
const json = this.jsonEnabled();
|
|
9029
8910
|
const color = !json && isTTY && flags.color !== false && !process.env.NO_COLOR;
|
|
9030
|
-
const message =
|
|
9031
|
-
if (nonInteractive && !dryRun && message === void 0) throw new ZitadelError("E_VALIDATION", "A deploy needs a message in non-interactive mode", { hint: "Pass -m/--message with a short summary of what this release changes." });
|
|
8911
|
+
const message = dryRun ? void 0 : await deployMessage(flags.message, cwd, env);
|
|
8912
|
+
if (nonInteractive && !dryRun && message === void 0) throw new ZitadelError("E_VALIDATION", "A deploy needs a message in non-interactive mode", { hint: "Pass -m/--message with a short summary of what this release changes, or deploy from a clean git commit." });
|
|
9032
8913
|
const { client, projectId } = await this.connect();
|
|
9033
8914
|
let landing = [];
|
|
9034
8915
|
if (!dryRun) {
|
|
9035
|
-
const origins = (await client.getProject(projectId)).origins
|
|
8916
|
+
const origins = (await client.getProject(projectId)).origins;
|
|
9036
8917
|
landing = origins.filter((entry) => entry.kind === "primary").map((entry) => entry.pattern);
|
|
9037
|
-
if (landing.length === 0)
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
8918
|
+
if (landing.length === 0) {
|
|
8919
|
+
const setupProject = await setupProjectId(cwd);
|
|
8920
|
+
throw noPrimaryOriginError({
|
|
8921
|
+
origins,
|
|
8922
|
+
issuer: this.environment.values.ZITADEL_ISSUER?.value,
|
|
8923
|
+
setupProject: setupProject === projectId,
|
|
8924
|
+
selector: this.environmentSelector(),
|
|
8925
|
+
cliVersion: this.meta.cliVersion
|
|
8926
|
+
});
|
|
8927
|
+
}
|
|
9044
8928
|
}
|
|
9045
8929
|
const { bundle, resourceCount, brandingSources, idpFiles, policyFiles } = await buildBundle(cwd);
|
|
9046
8930
|
this.recordTelemetry({ resources: resourceCount });
|
|
@@ -9076,7 +8960,10 @@ var Deploy = class Deploy extends EnvironmentCommand {
|
|
|
9076
8960
|
gitSha: await headSha(cwd),
|
|
9077
8961
|
gitDirty
|
|
9078
8962
|
});
|
|
9079
|
-
const observed = (await
|
|
8963
|
+
const observed = (await client.listDeployments.all({
|
|
8964
|
+
project_id: projectId,
|
|
8965
|
+
live: true
|
|
8966
|
+
})).find((deployment) => deployment.targets.some((entry) => landing.includes(entry.origin)))?.id;
|
|
9080
8967
|
const targets = ["primary"];
|
|
9081
8968
|
let deployment;
|
|
9082
8969
|
try {
|
|
@@ -9095,7 +8982,10 @@ var Deploy = class Deploy extends EnvironmentCommand {
|
|
|
9095
8982
|
content_hash: release.content_hash
|
|
9096
8983
|
}
|
|
9097
8984
|
});
|
|
9098
|
-
const current = (await
|
|
8985
|
+
const current = (await client.listDeployments.all({
|
|
8986
|
+
project_id: projectId,
|
|
8987
|
+
live: true
|
|
8988
|
+
})).find(servesPrimary);
|
|
9099
8989
|
throw new ZitadelError("E_CONFLICT", "Another deploy changed the targets since this one was prepared", {
|
|
9100
8990
|
hint: `Re-run \`zitadel ${DEPLOY_ARGS}${this.environmentSelector()}\`; it will build and deploy against the current state.`,
|
|
9101
8991
|
details: current === void 0 ? void 0 : { current_deployment: current },
|
|
@@ -9112,9 +9002,9 @@ var Deploy = class Deploy extends EnvironmentCommand {
|
|
|
9112
9002
|
deployment_id: deployment.id,
|
|
9113
9003
|
resources: resourceCount,
|
|
9114
9004
|
origins: servedOrigins,
|
|
9115
|
-
...reported
|
|
9116
|
-
...nextCommandsData([this.suggest("deployment list")])
|
|
9005
|
+
...reported
|
|
9117
9006
|
},
|
|
9007
|
+
next: { commands: [this.suggest("deployment list")] },
|
|
9118
9008
|
pretty: [
|
|
9119
9009
|
`Deployed ${release.content_hash}`,
|
|
9120
9010
|
` ${resourceCount} resource${resourceCount === 1 ? "" : "s"} to ${servedOrigins.length} origin${servedOrigins.length === 1 ? "" : "s"}`,
|
|
@@ -9222,9 +9112,9 @@ var DeploymentCreate = class DeploymentCreate extends EnvironmentCommand {
|
|
|
9222
9112
|
data: {
|
|
9223
9113
|
deployment_id: deployment.id,
|
|
9224
9114
|
release_id: deployment.release_id,
|
|
9225
|
-
origins
|
|
9226
|
-
...nextCommandsData([this.suggest("deployment list")])
|
|
9115
|
+
origins
|
|
9227
9116
|
},
|
|
9117
|
+
next: { commands: [this.suggest("deployment list")] },
|
|
9228
9118
|
pretty: [`Deployed ${deployment.release_id} to ${origins.length} origin${origins.length === 1 ? "" : "s"}`, ...origins.map((origin) => ` - ${origin}`)].join("\n")
|
|
9229
9119
|
});
|
|
9230
9120
|
}
|
|
@@ -9233,7 +9123,8 @@ var DeploymentCreate = class DeploymentCreate extends EnvironmentCommand {
|
|
|
9233
9123
|
//#region src/commands/deployment/get.ts
|
|
9234
9124
|
/**
|
|
9235
9125
|
* `zitadel deployment get <deployment_id>` — read one deployment record: the
|
|
9236
|
-
* release it made live, the origins it went to, and
|
|
9126
|
+
* release it made live, the origins it went to, when, and the variables it
|
|
9127
|
+
* froze.
|
|
9237
9128
|
*/
|
|
9238
9129
|
var DeploymentGet = class DeploymentGet extends EnvironmentCommand {
|
|
9239
9130
|
static description = "Read one deployment by id.";
|
|
@@ -9247,7 +9138,8 @@ var DeploymentGet = class DeploymentGet extends EnvironmentCommand {
|
|
|
9247
9138
|
const { args, flags } = await this.parse(DeploymentGet);
|
|
9248
9139
|
await this.toMeta(flags);
|
|
9249
9140
|
const { client, projectId } = await this.connect();
|
|
9250
|
-
const
|
|
9141
|
+
const scope = { project_id: projectId };
|
|
9142
|
+
const [deployment, variables] = await Promise.all([client.getDeploymentById(args.deployment_id, scope), this.jsonEnabled() ? void 0 : client.getDeploymentVariables(args.deployment_id, scope)]);
|
|
9251
9143
|
const origins = deployment.targets.map((target) => target.origin);
|
|
9252
9144
|
return this.emit({
|
|
9253
9145
|
status: "ok",
|
|
@@ -9257,12 +9149,62 @@ var DeploymentGet = class DeploymentGet extends EnvironmentCommand {
|
|
|
9257
9149
|
` release ${deployment.release_id}`,
|
|
9258
9150
|
` reason ${deployment.metadata.reason ?? "deploy"}`,
|
|
9259
9151
|
` deployed ${deployment.deployed_at}`,
|
|
9260
|
-
` origins ${origins.join(", ")}
|
|
9152
|
+
` origins ${origins.join(", ")}`,
|
|
9153
|
+
...variables === void 0 ? [] : ["", frozenVariables(toVariableRows(variables))]
|
|
9261
9154
|
].join("\n")
|
|
9262
9155
|
});
|
|
9263
9156
|
}
|
|
9264
9157
|
};
|
|
9265
9158
|
//#endregion
|
|
9159
|
+
//#region src/lib/oclif/envelope.ts
|
|
9160
|
+
/**
|
|
9161
|
+
* Builders for the `data` of a command's JSON envelope.
|
|
9162
|
+
*
|
|
9163
|
+
* Every command emits `data`, and an agent reads all of them, so the shapes
|
|
9164
|
+
* must not drift. These builders are the one place each shape is defined; the
|
|
9165
|
+
* spec harness (`assertEnvelope`) enforces the same shapes on every `--json`
|
|
9166
|
+
* invocation, so a command that hand-builds a divergent shape fails in its own
|
|
9167
|
+
* spec. Use the builder rather than an object literal.
|
|
9168
|
+
*/
|
|
9169
|
+
/**
|
|
9170
|
+
* The canonical list/collection envelope: `{ items, count, next_page_token }`.
|
|
9171
|
+
* Every command that returns a collection emits this, so it reads uniformly
|
|
9172
|
+
* across all of them. A single-page endpoint passes `nextPageToken: null` (the
|
|
9173
|
+
* default); a paginated one passes the server cursor (or `null` once drained).
|
|
9174
|
+
*/
|
|
9175
|
+
function listData(items, opts = {}) {
|
|
9176
|
+
return {
|
|
9177
|
+
items,
|
|
9178
|
+
count: items.length,
|
|
9179
|
+
next_page_token: opts.nextPageToken ?? null
|
|
9180
|
+
};
|
|
9181
|
+
}
|
|
9182
|
+
/**
|
|
9183
|
+
* The canonical delete/remove confirmation: the resource's identifier plus
|
|
9184
|
+
* `deleted: true`. The verb is always `deleted` — never `removed` — so an agent
|
|
9185
|
+
* reads "did the delete succeed?" the same way for every resource, whether it is
|
|
9186
|
+
* addressed by `id` or by a natural key (`url`, `pattern`, `name`). A lifecycle
|
|
9187
|
+
* verb (revoke, deactivate) uses {@link mutatedData} instead.
|
|
9188
|
+
*/
|
|
9189
|
+
function deletedData(identifier) {
|
|
9190
|
+
return {
|
|
9191
|
+
...identifier,
|
|
9192
|
+
deleted: true
|
|
9193
|
+
};
|
|
9194
|
+
}
|
|
9195
|
+
/**
|
|
9196
|
+
* A mutation confirmation whose verb is the lifecycle action, not `deleted`:
|
|
9197
|
+
* the identifier plus `<verb>: true` (e.g. `revoked`, `deactivated`). Keeps the
|
|
9198
|
+
* `{ identifier, <past-tense>: true }` shape of {@link deletedData} while naming
|
|
9199
|
+
* the specific action.
|
|
9200
|
+
*/
|
|
9201
|
+
function mutatedData(identifier, verb) {
|
|
9202
|
+
return {
|
|
9203
|
+
...identifier,
|
|
9204
|
+
[verb]: true
|
|
9205
|
+
};
|
|
9206
|
+
}
|
|
9207
|
+
//#endregion
|
|
9266
9208
|
//#region src/commands/deployment/list.ts
|
|
9267
9209
|
/** The columns `list` renders, in order. */
|
|
9268
9210
|
const COLUMNS$6 = [
|
|
@@ -9310,24 +9252,22 @@ var DeploymentList = class DeploymentList extends EnvironmentCommand {
|
|
|
9310
9252
|
const { flags } = await this.parse(DeploymentList);
|
|
9311
9253
|
await this.toMeta(flags);
|
|
9312
9254
|
const { client, projectId } = await this.connect();
|
|
9313
|
-
const
|
|
9255
|
+
const params = {
|
|
9314
9256
|
project_id: projectId,
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
}
|
|
9320
|
-
const { items, next } = await collectPages(
|
|
9321
|
-
items:
|
|
9257
|
+
origin: flags.origin,
|
|
9258
|
+
live: flags.live,
|
|
9259
|
+
limit: flags.limit,
|
|
9260
|
+
page_token: flags["page-token"]
|
|
9261
|
+
};
|
|
9262
|
+
const { items: deployments, next } = await collectPages(client.listDeployments.pages(params), {
|
|
9263
|
+
items: (page) => page.deployments,
|
|
9322
9264
|
all: flags.all === true,
|
|
9323
|
-
|
|
9324
|
-
nextPageToken: "next_page_token"
|
|
9265
|
+
next: (page) => page.next_page_token
|
|
9325
9266
|
});
|
|
9326
|
-
const deployments = items;
|
|
9327
9267
|
const rows = deployments.map((deployment) => ({
|
|
9328
9268
|
id: deployment.id,
|
|
9329
9269
|
release_id: deployment.release_id,
|
|
9330
|
-
reason: deployment.metadata.reason
|
|
9270
|
+
reason: deployment.metadata.reason,
|
|
9331
9271
|
origins: deployment.targets.map((target) => target.origin).join(", "),
|
|
9332
9272
|
deployed_at: deployment.deployed_at
|
|
9333
9273
|
}));
|
|
@@ -9342,10 +9282,9 @@ var DeploymentList = class DeploymentList extends EnvironmentCommand {
|
|
|
9342
9282
|
const plain = flags.plain || !process.stdout.isTTY;
|
|
9343
9283
|
return this.emit({
|
|
9344
9284
|
status: "ok",
|
|
9345
|
-
data: listData(deployments, {
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
}),
|
|
9285
|
+
data: listData(deployments, { nextPageToken: next }),
|
|
9286
|
+
...nextCommand === void 0 ? {} : { next: { commands: [nextCommand] } },
|
|
9287
|
+
machineReadable: plain,
|
|
9349
9288
|
pretty: renderList(COLUMNS$6, rows, {
|
|
9350
9289
|
plain,
|
|
9351
9290
|
singular: "deployment",
|
|
@@ -9380,31 +9319,26 @@ var DeploymentRollback = class DeploymentRollback extends Promptable(Environment
|
|
|
9380
9319
|
required: false,
|
|
9381
9320
|
description: "The deployment to re-apply. Omit to undo the last deploy."
|
|
9382
9321
|
}) };
|
|
9383
|
-
static flags = {
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9322
|
+
static flags = {
|
|
9323
|
+
force: Flags.boolean({
|
|
9324
|
+
char: "f",
|
|
9325
|
+
description: "Roll back without the confirmation prompt. Required when non-interactive."
|
|
9326
|
+
}),
|
|
9327
|
+
message: messageFlag("A short summary of why this rolls back.")
|
|
9328
|
+
};
|
|
9387
9329
|
async run() {
|
|
9388
9330
|
const { args, flags } = await this.parse(DeploymentRollback);
|
|
9389
9331
|
await this.toMeta(flags);
|
|
9390
9332
|
const { dryRun } = this.meta;
|
|
9391
9333
|
const { client, projectId } = await this.connect();
|
|
9392
9334
|
let targetId = args.deployment_id;
|
|
9393
|
-
let
|
|
9335
|
+
let expectedDeploymentId;
|
|
9394
9336
|
if (targetId === void 0) {
|
|
9395
|
-
const
|
|
9396
|
-
project_id: projectId,
|
|
9397
|
-
...token === void 0 ? {} : { page_token: token }
|
|
9398
|
-
}), {
|
|
9399
|
-
items: "deployments",
|
|
9400
|
-
all: true,
|
|
9401
|
-
nextPageToken: "next_page_token"
|
|
9402
|
-
});
|
|
9403
|
-
const [latest, prior] = items.filter(servesPrimary);
|
|
9337
|
+
const [latest, prior] = (await client.listDeployments.all({ project_id: projectId })).filter(servesPrimary);
|
|
9404
9338
|
if (!latest) throw new ZitadelError("E_VALIDATION", "There is no deployment to roll back", { hint: `Nothing has been deployed yet; check \`zitadel deployment list${this.environmentSelector()}\`.` });
|
|
9405
9339
|
if (!prior) throw new ZitadelError("E_VALIDATION", "There is no earlier deployment to roll back to", { hint: `Name a deployment to re-apply, or check \`zitadel deployment list${this.environmentSelector()}\`.` });
|
|
9406
9340
|
targetId = prior.id;
|
|
9407
|
-
|
|
9341
|
+
expectedDeploymentId = latest.id;
|
|
9408
9342
|
}
|
|
9409
9343
|
if (dryRun) return this.emit({
|
|
9410
9344
|
status: "ok",
|
|
@@ -9414,7 +9348,10 @@ var DeploymentRollback = class DeploymentRollback extends Promptable(Environment
|
|
|
9414
9348
|
},
|
|
9415
9349
|
pretty: `Would re-apply ${targetId}. Nothing was rolled back (dry run).`
|
|
9416
9350
|
});
|
|
9417
|
-
|
|
9351
|
+
if (expectedDeploymentId === void 0) expectedDeploymentId = (await client.listDeployments.all({
|
|
9352
|
+
project_id: projectId,
|
|
9353
|
+
live: true
|
|
9354
|
+
})).find(servesPrimary)?.id;
|
|
9418
9355
|
if (!await this.input({
|
|
9419
9356
|
kind: "confirm",
|
|
9420
9357
|
name: `re-apply ${targetId}`,
|
|
@@ -9424,7 +9361,10 @@ var DeploymentRollback = class DeploymentRollback extends Promptable(Environment
|
|
|
9424
9361
|
status: "skipped",
|
|
9425
9362
|
reason: "rollback-cancelled"
|
|
9426
9363
|
});
|
|
9427
|
-
const deployment = await rollbackDeployment(client, projectId, targetId, {
|
|
9364
|
+
const deployment = await rollbackDeployment(client, projectId, targetId, {
|
|
9365
|
+
...flags.message === void 0 ? {} : { message: flags.message },
|
|
9366
|
+
...expectedDeploymentId === void 0 ? {} : { expectedDeploymentId }
|
|
9367
|
+
});
|
|
9428
9368
|
let pointers;
|
|
9429
9369
|
try {
|
|
9430
9370
|
pointers = (await client.getReleaseById(deployment.release_id, { project_id: projectId })).pointers;
|
|
@@ -9454,10 +9394,6 @@ var DeploymentRollback = class DeploymentRollback extends Promptable(Environment
|
|
|
9454
9394
|
});
|
|
9455
9395
|
}
|
|
9456
9396
|
};
|
|
9457
|
-
/** The project's newest primary deployment, which a rollback is guarded by. */
|
|
9458
|
-
async function liveGuard(client, projectId) {
|
|
9459
|
-
return (await listLiveDeployments(client, projectId)).find(servesPrimary)?.id;
|
|
9460
|
-
}
|
|
9461
9397
|
//#endregion
|
|
9462
9398
|
//#region src/lib/orca/detectors/package-json.ts
|
|
9463
9399
|
/**
|
|
@@ -10590,7 +10526,7 @@ function agentsGuidanceSection(ctx) {
|
|
|
10590
10526
|
const jsxTypesNote = ctx.framework.id === "next" ? " React JSX types for the `<zitadel-*>` elements ship with the SDK — `custom-elements.d.ts` references `@zitadel/sdk-next/jsx`." : "";
|
|
10591
10527
|
const presentationParagraph = ctx.posture === "widget" ? "Presentation, by contrast, is edited in the generated pages: this app pre-dates setup, so they embed the sign-in widgets as `variant=\"widget\"` cards (with `theme=\"auto\"`) inside your existing layout; switch to `variant=\"page\"` for the widget's own full-page chrome, and set `theme` (`light` | `dark` | `auto`) to pick the color scheme — `auto` follows the OS `prefers-color-scheme`, not this app's own theme, so pin `light` or `dark` when the app fixes its scheme." : "Presentation, by contrast, is edited in the generated pages: they pin the sign-in widgets to `variant=\"page\"` (full-page chrome); switch to `variant=\"widget\"` to embed a card inside your own layout, and set `theme` (`light` | `dark` | `auto`) to pick the color scheme — `auto` follows the OS `prefers-color-scheme`, not this app's own theme, so pin `light` or `dark` when the app fixes its scheme.";
|
|
10592
10528
|
const themingParagraph = "Theming the widgets from your app: two hooks in your own stylesheet reach into their shadow DOM — the stable `--zl-*` design tokens, set on the element, and the parts `<zitadel-login>` forwards from its inner components as `zitadel-login::part(<atom>-<part>)` (the `card` part of `zl-card` is `zitadel-login::part(card-card)`). Both are listed under \"Style the login from your app\" in https://preview.zitadel.com/docs/guides/customize-design#style-the-login-from-your-app; any other `--zl-*` variable is internal. When your page already carries the heading, set the `suppress-header` attribute on `<zitadel-login>` / `<zitadel-session>` (SDK wrappers: `suppressHeader`) to visually hide the widget's own heading block — it stays in the accessibility tree. The login moves between sign-in and sign-up by itself (its own switch link, and an email it does or does not know), so a heading of your own must follow its `zitadel-flow-step` event, which the SDK wrappers forward: `step.purpose` is `login` while it signs in and `register` while it signs up. When it switches without being asked, the login tells the visitor why itself. To only reword the widget's heading, pass `locales` instead. Page layout around the widget (split screens, hero panes, marketing copy) is your app's code, not a Zitadel template.";
|
|
10593
|
-
const sessionStateParagraph = ctx.framework.id === "next" ? "Your app's own chrome (header navigation, account menus) reads session state with `getSession({ projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID })` from `@zitadel/sdk-next/session` — a no-store client read of the same-origin `/__nextgen/sessions/me?project_id=…` that the session card itself uses. It returns identity only for 200 with a non-empty `user_id` in this app's project, and signed-out for the backend's canonical `401/auth.unauthorized`, `403/sess.project_mismatch` (a session of another project on the same host, such as the console's on localhost) or `404/sess.not_found`; any other response is unknown/error, never signed-out. In Server Components, `auth()` from `@zitadel/sdk-next/server` works too, on every page: the request boundary's `matcher` covers every route, which also makes `protectedRoutes` the one list of routes that need a sign-in (a protected route that a narrower `matcher` leaves out stays public). Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load without extra wiring." : ctx.framework.id === "nuxt" ? "Your app's own chrome (header navigation, account menus) reads session state with the auto-imported `useAuth()` composable — seeded from the server on every render by the scaffolded auth plugin. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so the state is fresh on every page load." : "Your app's own chrome (header navigation, account menus) can read session state from the same-origin `/__nextgen/sessions/me?project_id=<project id>`, naming the project id the app passes to `configureZitadel()` — the same server-answered read the session card performs. Fetch with `credentials: \"include\"`, `cache: \"no-store\"`, and `Accept: application/json`; treat 200 as signed-in only with a non-empty `user_id` and that `project_id`, and signed-out only for the canonical error envelope `401/auth.unauthorized`, `403/sess.project_mismatch` (a session of another project on the same host, such as the console's on localhost) or `404/sess.not_found`. Any other response is unknown/error, never signed-out. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load.";
|
|
10529
|
+
const sessionStateParagraph = ctx.framework.id === "next" ? "Your app's own chrome (header navigation, account menus) reads session state with `getSession({ projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID })` from `@zitadel/sdk-next/session` — a no-store client read of the same-origin `/__nextgen/api/v1/sessions/me?project_id=…` that the session card itself uses. It returns identity only for 200 with a non-empty `user_id` in this app's project, and signed-out for the backend's canonical `401/auth.unauthorized`, `403/sess.project_mismatch` (a session of another project on the same host, such as the console's on localhost) or `404/sess.not_found`; any other response is unknown/error, never signed-out. In Server Components, `auth()` from `@zitadel/sdk-next/server` works too, on every page: the request boundary's `matcher` covers every route, which also makes `protectedRoutes` the one list of routes that need a sign-in (a protected route that a narrower `matcher` leaves out stays public). Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load without extra wiring." : ctx.framework.id === "nuxt" ? "Your app's own chrome (header navigation, account menus) reads session state with the auto-imported `useAuth()` composable — seeded from the server on every render by the scaffolded auth plugin. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so the state is fresh on every page load." : "Your app's own chrome (header navigation, account menus) can read session state from the same-origin `/__nextgen/api/v1/sessions/me?project_id=<project id>`, naming the project id the app passes to `configureZitadel()` — the same server-answered read the session card performs. Fetch with `credentials: \"include\"`, `cache: \"no-store\"`, and `Accept: application/json`; treat 200 as signed-in only with a non-empty `user_id` and that `project_id`, and signed-out only for the canonical error envelope `401/auth.unauthorized`, `403/sess.project_mismatch` (a session of another project on the same host, such as the console's on localhost) or `404/sess.not_found`. Any other response is unknown/error, never signed-out. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load.";
|
|
10594
10530
|
return `## Authentication (Zitadel)
|
|
10595
10531
|
|
|
10596
10532
|
This app's login is managed by Zitadel. Local config is the source of truth for what the login collects and how sign-in works (step 3 below). The app files setup generated are yours to change, from the pages to the request boundary that protects routes in a server-rendered app: \`doctor\` reports a file you changed as \`edited\`, or as \`adopted\` once you delete its \`zitadel-cli: managed-file\` comment, and \`doctor --fix\` restores only a missing file, never one you changed.
|
|
@@ -10866,6 +10802,11 @@ var AbstractRulePatcher = class {
|
|
|
10866
10802
|
path,
|
|
10867
10803
|
wiring: op.wiring,
|
|
10868
10804
|
state: verdict
|
|
10805
|
+
} : "outdated" in verdict ? {
|
|
10806
|
+
path,
|
|
10807
|
+
wiring: op.wiring,
|
|
10808
|
+
state: "outdated",
|
|
10809
|
+
reason: verdict.outdated
|
|
10869
10810
|
} : {
|
|
10870
10811
|
path,
|
|
10871
10812
|
wiring: op.wiring,
|
|
@@ -11571,8 +11512,27 @@ function angularRoutesEdit() {
|
|
|
11571
11512
|
const PROXY_PATH = "/__nextgen";
|
|
11572
11513
|
//#endregion
|
|
11573
11514
|
//#region src/lib/orca/patchers/rule/proxy-credential-policy.ts
|
|
11574
|
-
/**
|
|
11575
|
-
|
|
11515
|
+
/**
|
|
11516
|
+
* Marker emitted with the exchange-only credential policy. Its v3 also attaches
|
|
11517
|
+
* the secret to the exchange under the API prefix (ADR 077).
|
|
11518
|
+
*/
|
|
11519
|
+
const PROXY_CREDENTIAL_POLICY_MARKER = "zitadel:proxy:v3";
|
|
11520
|
+
/** The marker of the exchange-only policy as it was before it knew the API prefix. */
|
|
11521
|
+
const PROXY_CREDENTIAL_POLICY_V2_MARKER = "zitadel:proxy:v2";
|
|
11522
|
+
/** Whether `source` carries the credential-policy marker of either version. */
|
|
11523
|
+
function hasProxyCredentialPolicyMarker(source) {
|
|
11524
|
+
return source.includes("zitadel:proxy:v3") || source.includes("zitadel:proxy:v2");
|
|
11525
|
+
}
|
|
11526
|
+
/** The session exchange under the API prefix (ADR 077), as a browser calls it through the proxy. */
|
|
11527
|
+
const PREFIXED_EXCHANGE_PATH = `${API_PREFIX}/sessions/exchange`;
|
|
11528
|
+
/**
|
|
11529
|
+
* The generated proxies' path check: the exchange with and without the API
|
|
11530
|
+
* prefix, since the server answers both until 1.0.0 and an app's packages may
|
|
11531
|
+
* call either.
|
|
11532
|
+
*/
|
|
11533
|
+
const EXCHANGE_PATH_CHECK = `(pathname === "/sessions/exchange" || pathname === "${PREFIXED_EXCHANGE_PATH}")`;
|
|
11534
|
+
/** The path check of a v2 proxy, which knew only the unprefixed exchange. */
|
|
11535
|
+
const V2_EXCHANGE_PATH_CHECK = "pathname === \"/sessions/exchange\"";
|
|
11576
11536
|
/**
|
|
11577
11537
|
* Rejects a browser-reachable proxy that still appears to attach the project
|
|
11578
11538
|
* secret without the complete exchange-only guard. Known generated legacy
|
|
@@ -11582,7 +11542,47 @@ const PROXY_CREDENTIAL_POLICY_MARKER = "zitadel:proxy:v2";
|
|
|
11582
11542
|
*/
|
|
11583
11543
|
function assertNoUnreviewedProjectSecretProxy(source, label) {
|
|
11584
11544
|
if (!source.includes("/__nextgen") || !source.includes("ZITADEL_PROJECT_SECRET") || !/authorization/i.test(source) || hasExchangeOnlyGuard(source)) return;
|
|
11585
|
-
throw new ZitadelError("E_VALIDATION", `${label} may forward ZITADEL_PROJECT_SECRET outside POST /sessions/exchange`, { hint:
|
|
11545
|
+
throw new ZitadelError("E_VALIDATION", `${label} may forward ZITADEL_PROJECT_SECRET outside POST /sessions/exchange`, { hint: `Review the proxy manually: restrict the bearer to the exact POST /sessions/exchange or ${PREFIXED_EXCHANGE_PATH} pathname and preserve caller-provided authorization.` });
|
|
11546
|
+
}
|
|
11547
|
+
/** `code` without whitespace, with double quotes, so a formatter's layout compares equal. */
|
|
11548
|
+
function normalized(code) {
|
|
11549
|
+
return code.replace(/\s+/g, "").replace(/'/g, "\"");
|
|
11550
|
+
}
|
|
11551
|
+
/** The terms of the conditions a guard may be built from, normalized. */
|
|
11552
|
+
const POST_TERM = "proxyReq.method===\"POST\"";
|
|
11553
|
+
const NO_OVERWRITE_TERM = "!proxyReq.getHeader(\"authorization\")";
|
|
11554
|
+
const PATH_TERMS = /* @__PURE__ */ new Set([
|
|
11555
|
+
normalized(V2_EXCHANGE_PATH_CHECK),
|
|
11556
|
+
normalized(EXCHANGE_PATH_CHECK),
|
|
11557
|
+
normalized(`(pathname === "${PREFIXED_EXCHANGE_PATH}" || pathname === "/sessions/exchange")`)
|
|
11558
|
+
]);
|
|
11559
|
+
/** `condition`'s terms joined by `&&` outside any parentheses. */
|
|
11560
|
+
function andTerms(condition) {
|
|
11561
|
+
const terms = [];
|
|
11562
|
+
let depth = 0;
|
|
11563
|
+
let start = 0;
|
|
11564
|
+
for (let index = 0; index < condition.length; index++) {
|
|
11565
|
+
const char = condition[index];
|
|
11566
|
+
if (char === "(") depth++;
|
|
11567
|
+
else if (char === ")") depth--;
|
|
11568
|
+
else if (depth === 0 && condition.startsWith("&&", index)) {
|
|
11569
|
+
terms.push(condition.slice(start, index));
|
|
11570
|
+
start = index + 2;
|
|
11571
|
+
index++;
|
|
11572
|
+
}
|
|
11573
|
+
}
|
|
11574
|
+
terms.push(condition.slice(start));
|
|
11575
|
+
return terms;
|
|
11576
|
+
}
|
|
11577
|
+
/**
|
|
11578
|
+
* Whether an `if` condition holds the bearer to the exchange: exactly the POST
|
|
11579
|
+
* check, the path check (the exchange, or both its spellings joined by `||`)
|
|
11580
|
+
* and the no-overwrite check, in any order. Anything more could widen it, as
|
|
11581
|
+
* `… || true` would, so it does not qualify.
|
|
11582
|
+
*/
|
|
11583
|
+
function isExchangeOnlyCondition(condition) {
|
|
11584
|
+
const terms = andTerms(normalized(condition));
|
|
11585
|
+
return terms.length === 3 && terms.includes(POST_TERM) && terms.includes(NO_OVERWRITE_TERM) && terms.some((term) => PATH_TERMS.has(term));
|
|
11586
11586
|
}
|
|
11587
11587
|
function hasExchangeOnlyGuard(source) {
|
|
11588
11588
|
const setterOffsets = [...source.matchAll(/proxyReq\s*\.\s*setHeader\s*\(\s*["']authorization["']\s*,/gi)].map((match) => match.index);
|
|
@@ -11590,7 +11590,7 @@ function hasExchangeOnlyGuard(source) {
|
|
|
11590
11590
|
const safeBodyRanges = [];
|
|
11591
11591
|
for (const match of source.matchAll(/if\s*\(([\s\S]*?)\)\s*\{([\s\S]*?)\}/g)) {
|
|
11592
11592
|
const [whole = "", condition = "", body = ""] = match;
|
|
11593
|
-
if (
|
|
11593
|
+
if (!isExchangeOnlyCondition(condition) || !/proxyReq\s*\.\s*setHeader\s*\(\s*["']authorization["']\s*,/i.test(body)) continue;
|
|
11594
11594
|
const bodyOffset = whole.lastIndexOf(body);
|
|
11595
11595
|
const start = match.index + bodyOffset;
|
|
11596
11596
|
safeBodyRanges.push({
|
|
@@ -11600,21 +11600,61 @@ function hasExchangeOnlyGuard(source) {
|
|
|
11600
11600
|
}
|
|
11601
11601
|
return setterOffsets.every((offset) => safeBodyRanges.some(({ start, end }) => offset >= start && offset < end));
|
|
11602
11602
|
}
|
|
11603
|
+
//#endregion
|
|
11604
|
+
//#region src/lib/orca/patchers/rule/session-cookie.ts
|
|
11605
|
+
/**
|
|
11606
|
+
* The session-cookie hooks of the dev proxies the CLI writes for the SPA
|
|
11607
|
+
* frameworks (ADR 073, ADR 083). The server sets one session cookie,
|
|
11608
|
+
* `__Host-zitadel_session`, or `zitadel_session` over plain http to a loopback
|
|
11609
|
+
* host, and reads it as `__Host-zitadel_session`; a browser shares a host's
|
|
11610
|
+
* cookies across every port, so under that name alone an SPA on `localhost`
|
|
11611
|
+
* and the Console of a local server would keep their sessions in one slot and
|
|
11612
|
+
* sign each other out. The hooks keep the app's session in
|
|
11613
|
+
* `zitadel_session_<project id>` on a dev server reached over plain http to a
|
|
11614
|
+
* loopback host, and in `__Host-zitadel_session_<project id>` on any other,
|
|
11615
|
+
* the names the server SDKs use (`sessionCookieName` in `@zitadel/sdk-core`):
|
|
11616
|
+
*
|
|
11617
|
+
* - on the way up, the app's cookie goes once, under the server's
|
|
11618
|
+
* `__Host-zitadel_session`, and every other session cookie stays back, those
|
|
11619
|
+
* from before ADR 083 included;
|
|
11620
|
+
* - on the way back, the server's `Set-Cookie`, under either name, becomes the
|
|
11621
|
+
* app's: its value and expiry with `Path=/`, `HttpOnly`, `SameSite=Lax`, and
|
|
11622
|
+
* `Secure` unless the dev server is loopback http. A `Set-Cookie` of any
|
|
11623
|
+
* other session cookie is dropped.
|
|
11624
|
+
*
|
|
11625
|
+
* The dev server's scheme is the first `X-Forwarded-Proto` (the entry's `xfwd`
|
|
11626
|
+
* sets it), else its own connection's, and its host the first
|
|
11627
|
+
* `X-Forwarded-Host`, else `Host`: the rule of `secureFor` in sdk-core.
|
|
11628
|
+
*
|
|
11629
|
+
* Without a project id the cookies pass through unchanged. The code is plain
|
|
11630
|
+
* JavaScript, so it runs in a `vite.config.*` and in Angular's CommonJS
|
|
11631
|
+
* `proxy.conf.cjs` alike, and in a `vite.config.ts` it type-checks under
|
|
11632
|
+
* `strict`: every parameter is one Vite's `configure(proxy)` typing reaches.
|
|
11633
|
+
*/
|
|
11634
|
+
/** Marks a generated dev proxy that carries the session-cookie hooks. */
|
|
11635
|
+
const SESSION_COOKIE_MARKER = "zitadel:session-cookie:v2";
|
|
11636
|
+
/**
|
|
11637
|
+
* Marks the hooks the CLI wrote before ADR 083, which name the
|
|
11638
|
+
* `__nextgen_session` cookies. The doctor fails on them, and `doctor --fix`
|
|
11639
|
+
* replaces them where they are as the CLI wrote them.
|
|
11640
|
+
*/
|
|
11641
|
+
const SESSION_COOKIE_MARKER_V1 = "zitadel:session-cookie:v1";
|
|
11603
11642
|
/** Says what the hooks do, above them in the generated proxy. */
|
|
11604
11643
|
const EXPLANATION = [
|
|
11605
|
-
`/*
|
|
11644
|
+
`/* ${SESSION_COOKIE_MARKER} */`,
|
|
11606
11645
|
"// The browser keeps this app's session in a cookie named after its project,",
|
|
11607
|
-
"//
|
|
11608
|
-
"//
|
|
11646
|
+
"// zitadel_session_<project id>, prefixed __Host- unless this dev server is",
|
|
11647
|
+
"// plain http on a loopback host; the server takes it as __Host-zitadel_session",
|
|
11648
|
+
"// (ADR 083). Without a project id, cookies pass through."
|
|
11609
11649
|
];
|
|
11610
11650
|
/**
|
|
11611
|
-
* `
|
|
11612
|
-
* one. It percent-encodes every byte outside
|
|
11613
|
-
* `sessionCookieName` in sdk-core does.
|
|
11651
|
+
* `appCookieSuffix`, what the app's cookie name adds for `projectId`, or
|
|
11652
|
+
* `undefined` without one. It percent-encodes every byte outside
|
|
11653
|
+
* `[A-Za-z0-9_-]`, as `sessionCookieName` in sdk-core does.
|
|
11614
11654
|
*/
|
|
11615
|
-
const
|
|
11616
|
-
"const
|
|
11617
|
-
" ? \"
|
|
11655
|
+
const APP_COOKIE_SUFFIX = [
|
|
11656
|
+
"const appCookieSuffix = projectId",
|
|
11657
|
+
" ? \"_\" +",
|
|
11618
11658
|
" projectId.replace(/[^\\w-]/gu, (char) =>",
|
|
11619
11659
|
" Array.from(new TextEncoder().encode(char), (byte) =>",
|
|
11620
11660
|
" \"%\" + byte.toString(16).toUpperCase().padStart(2, \"0\"),",
|
|
@@ -11622,11 +11662,28 @@ const APP_COOKIE = [
|
|
|
11622
11662
|
" )",
|
|
11623
11663
|
" : undefined;"
|
|
11624
11664
|
];
|
|
11665
|
+
/**
|
|
11666
|
+
* `secure` and `appCookie` for the browser's request `req`: the app's cookie
|
|
11667
|
+
* carries the prefix and `Secure` unless the dev server was reached over plain
|
|
11668
|
+
* http to a loopback host.
|
|
11669
|
+
*/
|
|
11670
|
+
const APP_COOKIE = [
|
|
11671
|
+
"const proto = String(req.headers[\"x-forwarded-proto\"] ?? \"\").split(\",\")[0]?.trim() ||",
|
|
11672
|
+
" (\"encrypted\" in req.socket && req.socket.encrypted ? \"https\" : \"http\");",
|
|
11673
|
+
"const host = String(req.headers[\"x-forwarded-host\"] ?? \"\").split(\",\")[0]?.trim() || (req.headers.host ?? \"\");",
|
|
11674
|
+
"const secure =",
|
|
11675
|
+
" proto.toLowerCase() !== \"http\" ||",
|
|
11676
|
+
" !/^(?:localhost|127(?:\\.\\d{1,3}){3}|\\[::1\\])(?::\\d+)?$/i.test(host);",
|
|
11677
|
+
"const appCookie = (secure ? \"__Host-zitadel_session\" : \"zitadel_session\") + appCookieSuffix;"
|
|
11678
|
+
];
|
|
11679
|
+
/** Matches a cookie of any session cookie family, the server's or an app's, on either transport or from before ADR 083. */
|
|
11680
|
+
const ANY_SESSION_COOKIE = "/^(?:__Host-)?(?:zitadel_session|__nextgen_session)(?:_[^=]*)?=/";
|
|
11625
11681
|
/** The request hook's body, on `proxyReq`: only the app's cookie goes up, under the server's name. */
|
|
11626
11682
|
const FORWARD_BODY = [
|
|
11627
|
-
"if (!
|
|
11683
|
+
"if (!appCookieSuffix) {",
|
|
11628
11684
|
" return;",
|
|
11629
11685
|
"}",
|
|
11686
|
+
...APP_COOKIE,
|
|
11630
11687
|
"const cookies = String(proxyReq.getHeader(\"cookie\") ?? \"\")",
|
|
11631
11688
|
" .split(\";\")",
|
|
11632
11689
|
" .map((cookie) => cookie.trim())",
|
|
@@ -11634,9 +11691,9 @@ const FORWARD_BODY = [
|
|
|
11634
11691
|
"const own = cookies.find(",
|
|
11635
11692
|
" (cookie) => cookie.startsWith(appCookie + \"=\") && cookie.length > appCookie.length + 1,",
|
|
11636
11693
|
");",
|
|
11637
|
-
|
|
11694
|
+
`const forwarded = cookies.filter((cookie) => !${ANY_SESSION_COOKIE}.test(cookie));`,
|
|
11638
11695
|
"if (own) {",
|
|
11639
|
-
" forwarded.push(\"
|
|
11696
|
+
" forwarded.push(\"__Host-zitadel_session\" + own.slice(appCookie.length));",
|
|
11640
11697
|
"}",
|
|
11641
11698
|
"if (forwarded.length > 0) {",
|
|
11642
11699
|
" proxyReq.setHeader(\"cookie\", forwarded.join(\"; \"));",
|
|
@@ -11644,39 +11701,64 @@ const FORWARD_BODY = [
|
|
|
11644
11701
|
" proxyReq.removeHeader(\"cookie\");",
|
|
11645
11702
|
"}"
|
|
11646
11703
|
];
|
|
11647
|
-
/**
|
|
11704
|
+
/**
|
|
11705
|
+
* The response hook's body, on `proxyRes`: the server's `Set-Cookie` comes back
|
|
11706
|
+
* as the app's, and one of any other session cookie not at all.
|
|
11707
|
+
*/
|
|
11648
11708
|
const RENAME_BODY = [
|
|
11649
11709
|
"const setCookies = proxyRes.headers[\"set-cookie\"];",
|
|
11650
|
-
"if (
|
|
11651
|
-
"
|
|
11652
|
-
"
|
|
11653
|
-
|
|
11654
|
-
"
|
|
11710
|
+
"if (!appCookieSuffix || !setCookies) {",
|
|
11711
|
+
" return;",
|
|
11712
|
+
"}",
|
|
11713
|
+
...APP_COOKIE,
|
|
11714
|
+
"proxyRes.headers[\"set-cookie\"] = setCookies.flatMap((setCookie) => {",
|
|
11715
|
+
" if (/^(?:__Host-)?zitadel_session=/.test(setCookie)) {",
|
|
11716
|
+
" const [pair = \"\", ...attributes] = setCookie.split(\";\");",
|
|
11717
|
+
" const expiry = attributes",
|
|
11718
|
+
" .map((attribute) => attribute.trim())",
|
|
11719
|
+
" .filter((attribute) => /^(?:max-age|expires)=/i.test(attribute));",
|
|
11720
|
+
" return [",
|
|
11721
|
+
" [",
|
|
11722
|
+
" appCookie + pair.slice(pair.indexOf(\"=\")),",
|
|
11723
|
+
" \"Path=/\",",
|
|
11724
|
+
" ...expiry,",
|
|
11725
|
+
" \"HttpOnly\",",
|
|
11726
|
+
" \"SameSite=Lax\",",
|
|
11727
|
+
" ...(secure ? [\"Secure\"] : []),",
|
|
11728
|
+
" ].join(\"; \"),",
|
|
11729
|
+
" ];",
|
|
11730
|
+
" }",
|
|
11731
|
+
` return ${ANY_SESSION_COOKIE}.test(setCookie) ? [] : [setCookie];`,
|
|
11732
|
+
"});"
|
|
11655
11733
|
];
|
|
11656
11734
|
/** `lines`, each indented by two more spaces. */
|
|
11657
11735
|
function nested(lines) {
|
|
11658
11736
|
return lines.map((line) => line === "" ? "" : ` ${line}`);
|
|
11659
11737
|
}
|
|
11738
|
+
/** How the Vite proxy entry reads the project id: from `.env.local` through Vite's `loadEnv`. */
|
|
11739
|
+
const VITE_PROJECT_ID = "const projectId = loadEnv(\"development\", process.cwd(), \"ZITADEL_\").ZITADEL_PROJECT_ID;";
|
|
11660
11740
|
/**
|
|
11661
11741
|
* The hooks as statements of a Vite proxy entry's `configure(proxy)` body,
|
|
11662
11742
|
* after its bearer hook. The project id comes from `.env.local` through Vite's
|
|
11663
11743
|
* `loadEnv`, as the project secret does. The hooks are the listeners
|
|
11664
|
-
* themselves, not functions they call, so Vite's types reach `proxyReq
|
|
11665
|
-
* `proxyRes` and a strict `vite.config.ts` type-checks.
|
|
11744
|
+
* themselves, not functions they call, so Vite's types reach `proxyReq`,
|
|
11745
|
+
* `proxyRes` and `req`, and a strict `vite.config.ts` type-checks.
|
|
11666
11746
|
*/
|
|
11667
11747
|
function viteSessionCookieStatements() {
|
|
11668
11748
|
return [
|
|
11669
11749
|
...EXPLANATION,
|
|
11670
|
-
|
|
11671
|
-
...
|
|
11672
|
-
"proxy.on(\"proxyReq\", (proxyReq) => {",
|
|
11750
|
+
VITE_PROJECT_ID,
|
|
11751
|
+
...APP_COOKIE_SUFFIX,
|
|
11752
|
+
"proxy.on(\"proxyReq\", (proxyReq, req) => {",
|
|
11673
11753
|
...nested(FORWARD_BODY),
|
|
11674
11754
|
"});",
|
|
11675
|
-
"proxy.on(\"proxyRes\", (proxyRes) => {",
|
|
11755
|
+
"proxy.on(\"proxyRes\", (proxyRes, req) => {",
|
|
11676
11756
|
...nested(RENAME_BODY),
|
|
11677
11757
|
"});"
|
|
11678
11758
|
];
|
|
11679
11759
|
}
|
|
11760
|
+
/** How Angular's `proxy.conf.cjs` reads the project id: from the environment, else `.env.local`. */
|
|
11761
|
+
const PROXY_CONF_PROJECT_ID = ["const projectId =", " process.env.ZITADEL_PROJECT_ID ?? (typeof env === \"undefined\" ? undefined : env.ZITADEL_PROJECT_ID);"];
|
|
11680
11762
|
/**
|
|
11681
11763
|
* The hooks as top-level statements of Angular's `proxy.conf.cjs`, after its
|
|
11682
11764
|
* bearer hook: the functions its entry attaches as `onProxyReq`/`onProxyRes`
|
|
@@ -11688,19 +11770,93 @@ function viteSessionCookieStatements() {
|
|
|
11688
11770
|
function proxyConfSessionCookieStatements() {
|
|
11689
11771
|
return [
|
|
11690
11772
|
...EXPLANATION,
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
...APP_COOKIE,
|
|
11773
|
+
...PROXY_CONF_PROJECT_ID,
|
|
11774
|
+
...APP_COOKIE_SUFFIX,
|
|
11694
11775
|
"",
|
|
11695
|
-
"function forwardSessionCookie(proxyReq) {",
|
|
11776
|
+
"function forwardSessionCookie(proxyReq, req) {",
|
|
11696
11777
|
...nested(FORWARD_BODY),
|
|
11697
11778
|
"}",
|
|
11698
11779
|
"",
|
|
11699
|
-
"function renameSessionCookie(proxyRes) {",
|
|
11780
|
+
"function renameSessionCookie(proxyRes, req) {",
|
|
11700
11781
|
...nested(RENAME_BODY),
|
|
11701
11782
|
"}"
|
|
11702
11783
|
];
|
|
11703
11784
|
}
|
|
11785
|
+
const EXPLANATION_V1 = [
|
|
11786
|
+
`/* ${SESSION_COOKIE_MARKER_V1} */`,
|
|
11787
|
+
"// The browser keeps this app's session in a cookie named after its project,",
|
|
11788
|
+
"// the server in __nextgen_session (ADR 073). Without a project id, cookies",
|
|
11789
|
+
"// pass through."
|
|
11790
|
+
];
|
|
11791
|
+
const APP_COOKIE_V1 = [
|
|
11792
|
+
"const appCookie = projectId",
|
|
11793
|
+
" ? \"__nextgen_session_\" +",
|
|
11794
|
+
" projectId.replace(/[^\\w-]/gu, (char) =>",
|
|
11795
|
+
" Array.from(new TextEncoder().encode(char), (byte) =>",
|
|
11796
|
+
" \"%\" + byte.toString(16).toUpperCase().padStart(2, \"0\"),",
|
|
11797
|
+
" ).join(\"\"),",
|
|
11798
|
+
" )",
|
|
11799
|
+
" : undefined;"
|
|
11800
|
+
];
|
|
11801
|
+
const FORWARD_BODY_V1 = [
|
|
11802
|
+
"if (!appCookie) {",
|
|
11803
|
+
" return;",
|
|
11804
|
+
"}",
|
|
11805
|
+
"const cookies = String(proxyReq.getHeader(\"cookie\") ?? \"\")",
|
|
11806
|
+
" .split(\";\")",
|
|
11807
|
+
" .map((cookie) => cookie.trim())",
|
|
11808
|
+
" .filter((cookie) => cookie !== \"\");",
|
|
11809
|
+
"const own = cookies.find(",
|
|
11810
|
+
" (cookie) => cookie.startsWith(appCookie + \"=\") && cookie.length > appCookie.length + 1,",
|
|
11811
|
+
");",
|
|
11812
|
+
"const forwarded = cookies.filter((cookie) => !/^__nextgen_session(?:_[^=]*)?=/.test(cookie));",
|
|
11813
|
+
"if (own) {",
|
|
11814
|
+
" forwarded.push(\"__nextgen_session\" + own.slice(appCookie.length));",
|
|
11815
|
+
"}",
|
|
11816
|
+
"if (forwarded.length > 0) {",
|
|
11817
|
+
" proxyReq.setHeader(\"cookie\", forwarded.join(\"; \"));",
|
|
11818
|
+
"} else {",
|
|
11819
|
+
" proxyReq.removeHeader(\"cookie\");",
|
|
11820
|
+
"}"
|
|
11821
|
+
];
|
|
11822
|
+
const RENAME_BODY_V1 = [
|
|
11823
|
+
"const setCookies = proxyRes.headers[\"set-cookie\"];",
|
|
11824
|
+
"if (appCookie && setCookies) {",
|
|
11825
|
+
" proxyRes.headers[\"set-cookie\"] = setCookies.map((setCookie) =>",
|
|
11826
|
+
" setCookie.replace(/^__nextgen_session=/, appCookie + \"=\"),",
|
|
11827
|
+
" );",
|
|
11828
|
+
"}"
|
|
11829
|
+
];
|
|
11830
|
+
/** The statements {@link viteSessionCookieStatements} produced before ADR 083. */
|
|
11831
|
+
function viteSessionCookieStatementsV1() {
|
|
11832
|
+
return [
|
|
11833
|
+
...EXPLANATION_V1,
|
|
11834
|
+
VITE_PROJECT_ID,
|
|
11835
|
+
...APP_COOKIE_V1,
|
|
11836
|
+
"proxy.on(\"proxyReq\", (proxyReq) => {",
|
|
11837
|
+
...nested(FORWARD_BODY_V1),
|
|
11838
|
+
"});",
|
|
11839
|
+
"proxy.on(\"proxyRes\", (proxyRes) => {",
|
|
11840
|
+
...nested(RENAME_BODY_V1),
|
|
11841
|
+
"});"
|
|
11842
|
+
];
|
|
11843
|
+
}
|
|
11844
|
+
/** The statements {@link proxyConfSessionCookieStatements} produced before ADR 083. */
|
|
11845
|
+
function proxyConfSessionCookieStatementsV1() {
|
|
11846
|
+
return [
|
|
11847
|
+
...EXPLANATION_V1,
|
|
11848
|
+
...PROXY_CONF_PROJECT_ID,
|
|
11849
|
+
...APP_COOKIE_V1,
|
|
11850
|
+
"",
|
|
11851
|
+
"function forwardSessionCookie(proxyReq) {",
|
|
11852
|
+
...nested(FORWARD_BODY_V1),
|
|
11853
|
+
"}",
|
|
11854
|
+
"",
|
|
11855
|
+
"function renameSessionCookie(proxyRes) {",
|
|
11856
|
+
...nested(RENAME_BODY_V1),
|
|
11857
|
+
"}"
|
|
11858
|
+
];
|
|
11859
|
+
}
|
|
11704
11860
|
/** `lines`, each indented by `indent` (blank lines stay blank), joined with `eol`. */
|
|
11705
11861
|
function indented(lines, indent, eol = "\n") {
|
|
11706
11862
|
return lines.map((line) => line === "" ? "" : `${indent}${line}`).join(eol);
|
|
@@ -11819,7 +11975,8 @@ function appTemplateHtml(ctx) {
|
|
|
11819
11975
|
/**
|
|
11820
11976
|
* The managed `proxy.conf.cjs` for `ng serve`: forwards `/__nextgen/*` to the
|
|
11821
11977
|
* backend and attaches the project's service-key secret only to the current
|
|
11822
|
-
* app-plane `POST /sessions/exchange` request
|
|
11978
|
+
* app-plane `POST /sessions/exchange` request, which it knows with and without
|
|
11979
|
+
* the API prefix (`/api/v1`, ADR 077). Both the backend URL
|
|
11823
11980
|
* (`ZITADEL_URL`) and the secret
|
|
11824
11981
|
* (`ZITADEL_PROJECT_SECRET`) are read from `.env.local`, which `zitadel setup`
|
|
11825
11982
|
* writes and `.gitignore` excludes — Angular's CLI does not auto-load env files
|
|
@@ -11831,7 +11988,8 @@ function appTemplateHtml(ctx) {
|
|
|
11831
11988
|
* backend by its own name (`changeOrigin`), which a remote https server
|
|
11832
11989
|
* requires, and forwards the app's origin in `X-Forwarded-Host` and
|
|
11833
11990
|
* `X-Forwarded-Proto` (`xfwd`), and it keeps the session cookie under the
|
|
11834
|
-
* app's project's name in the browser (`ZITADEL_PROJECT_ID`, ADR 073
|
|
11991
|
+
* app's project's name in the browser (`ZITADEL_PROJECT_ID`, ADR 073,
|
|
11992
|
+
* ADR 083).
|
|
11835
11993
|
*/
|
|
11836
11994
|
function proxyConfTemplate() {
|
|
11837
11995
|
return `${MANAGED_MARKER}
|
|
@@ -11884,25 +12042,37 @@ ${SESSION_COOKIE_HOOKS} },
|
|
|
11884
12042
|
const LEGACY_UNCONDITIONAL_BEARER = `function setBearer(proxyReq) {
|
|
11885
12043
|
proxyReq.setHeader("authorization", bearer);
|
|
11886
12044
|
}`;
|
|
11887
|
-
|
|
12045
|
+
/** The bearer hook, marked with the credential policy `marker`, testing the path with `pathCheck`. */
|
|
12046
|
+
function exchangeOnlyBearer(marker, pathCheck) {
|
|
12047
|
+
return `/* ${marker} */
|
|
11888
12048
|
function setBearer(proxyReq) {
|
|
11889
12049
|
const pathname = new URL(proxyReq.path, "http://zitadel.local").pathname;
|
|
11890
12050
|
if (
|
|
11891
12051
|
proxyReq.method === "POST" &&
|
|
11892
|
-
|
|
12052
|
+
${pathCheck} &&
|
|
11893
12053
|
!proxyReq.getHeader("authorization")
|
|
11894
12054
|
) {
|
|
11895
12055
|
proxyReq.setHeader("authorization", bearer);
|
|
11896
12056
|
}
|
|
11897
12057
|
}`;
|
|
11898
|
-
|
|
12058
|
+
}
|
|
12059
|
+
const EXCHANGE_ONLY_BEARER = exchangeOnlyBearer(PROXY_CREDENTIAL_POLICY_MARKER, EXCHANGE_PATH_CHECK);
|
|
12060
|
+
/** The bearer hook as the CLI wrote it before it knew the API prefix (ADR 077). */
|
|
12061
|
+
const V2_EXCHANGE_ONLY_BEARER = exchangeOnlyBearer(PROXY_CREDENTIAL_POLICY_V2_MARKER, V2_EXCHANGE_PATH_CHECK);
|
|
12062
|
+
/** The exchange-only bearer hook `text` carries, of either version, or `undefined`. */
|
|
12063
|
+
function bearerIn(text) {
|
|
12064
|
+
return [EXCHANGE_ONLY_BEARER, V2_EXCHANGE_ONLY_BEARER].find((bearer) => text.includes(bearer));
|
|
12065
|
+
}
|
|
12066
|
+
/** The session-cookie hooks' functions, after the bearer hook (ADR 073, ADR 083). */
|
|
11899
12067
|
const SESSION_COOKIE_FUNCTIONS = indented(proxyConfSessionCookieStatements(), "");
|
|
12068
|
+
/** The session-cookie hooks' functions as the CLI wrote them before ADR 083. */
|
|
12069
|
+
const SESSION_COOKIE_FUNCTIONS_V1 = indented(proxyConfSessionCookieStatementsV1(), "");
|
|
11900
12070
|
/** How the managed entry attached its hooks before it carried the session-cookie ones. */
|
|
11901
12071
|
const BEARER_ONLY_HOOKS = ` onProxyReq: setBearer,
|
|
11902
12072
|
configure: (proxy) => proxy.on("proxyReq", setBearer),
|
|
11903
12073
|
`;
|
|
11904
|
-
/** How the managed entry
|
|
11905
|
-
const
|
|
12074
|
+
/** How the managed entry attached the bearer and the session-cookie hooks before ADR 083. */
|
|
12075
|
+
const SESSION_COOKIE_HOOKS_V1 = ` onProxyReq: (proxyReq) => {
|
|
11906
12076
|
setBearer(proxyReq);
|
|
11907
12077
|
forwardSessionCookie(proxyReq);
|
|
11908
12078
|
},
|
|
@@ -11914,6 +12084,21 @@ const SESSION_COOKIE_HOOKS = ` onProxyReq: (proxyReq) => {
|
|
|
11914
12084
|
},
|
|
11915
12085
|
`;
|
|
11916
12086
|
/**
|
|
12087
|
+
* How the managed entry attaches the bearer and the session-cookie hooks. Each
|
|
12088
|
+
* cookie hook takes the browser's request too, whose scheme names the cookie.
|
|
12089
|
+
*/
|
|
12090
|
+
const SESSION_COOKIE_HOOKS = ` onProxyReq: (proxyReq, req) => {
|
|
12091
|
+
setBearer(proxyReq);
|
|
12092
|
+
forwardSessionCookie(proxyReq, req);
|
|
12093
|
+
},
|
|
12094
|
+
onProxyRes: renameSessionCookie,
|
|
12095
|
+
configure: (proxy) => {
|
|
12096
|
+
proxy.on("proxyReq", setBearer);
|
|
12097
|
+
proxy.on("proxyReq", forwardSessionCookie);
|
|
12098
|
+
proxy.on("proxyRes", renameSessionCookie);
|
|
12099
|
+
},
|
|
12100
|
+
`;
|
|
12101
|
+
/**
|
|
11917
12102
|
* The managed entry as emitted before it forwarded the app's origin, and what
|
|
11918
12103
|
* replaces it. Keeping the browser's `Host` also kept `localhost` as the TLS
|
|
11919
12104
|
* server name, so that proxy could reach no remote https server.
|
|
@@ -11949,21 +12134,31 @@ function proxyConfEdit() {
|
|
|
11949
12134
|
}
|
|
11950
12135
|
/** Whether `text` (LF line ends) is the managed proxy as an earlier CLI wrote it, without the session-cookie hooks. */
|
|
11951
12136
|
function hasBearerOnlyHooks(text) {
|
|
11952
|
-
return text.includes("// zitadel-cli: managed-file v1") && text
|
|
12137
|
+
return text.includes("// zitadel-cli: managed-file v1") && bearerIn(text) !== void 0 && text.includes(BEARER_ONLY_HOOKS);
|
|
12138
|
+
}
|
|
12139
|
+
/** Whether `text` (LF line ends) is the managed proxy with the session-cookie hooks the CLI wrote before ADR 083. */
|
|
12140
|
+
function hasV1Hooks(text) {
|
|
12141
|
+
return text.includes("// zitadel-cli: managed-file v1") && text.includes(SESSION_COOKIE_FUNCTIONS_V1) && text.includes(SESSION_COOKIE_HOOKS_V1);
|
|
11953
12142
|
}
|
|
11954
12143
|
/**
|
|
11955
|
-
* Gives the managed proxy the session-cookie hooks (ADR 073) when it
|
|
11956
|
-
* exact file an earlier CLI wrote: the hook functions go after the
|
|
11957
|
-
* and the entry attaches them beside it
|
|
11958
|
-
*
|
|
11959
|
-
*
|
|
11960
|
-
*
|
|
11961
|
-
* is
|
|
12144
|
+
* Gives the managed proxy the session-cookie hooks (ADR 073, ADR 083) when it
|
|
12145
|
+
* is the exact file an earlier CLI wrote: the hook functions go after the
|
|
12146
|
+
* bearer hook, and the entry attaches them beside it; the hooks from before
|
|
12147
|
+
* ADR 083 are replaced where they stand. A file that carries the current hooks
|
|
12148
|
+
* already, an unmanaged one, and a managed one whose hooks were changed by
|
|
12149
|
+
* hand are left as they are; {@link proxyConfSessionCookieVerdict} tells the
|
|
12150
|
+
* doctor which it is. Line ends are the file's own (CRLF or LF). Creates the
|
|
12151
|
+
* managed proxy when it is absent, as {@link proxyConfEdit} does.
|
|
11962
12152
|
*/
|
|
11963
12153
|
function proxyConfSessionCookieEdit() {
|
|
11964
12154
|
return (source) => {
|
|
11965
12155
|
if (source === void 0) return proxyConfTemplate();
|
|
11966
|
-
return withLfLineEnds(source, (text) =>
|
|
12156
|
+
return withLfLineEnds(source, (text) => {
|
|
12157
|
+
if (text.includes("zitadel:session-cookie:v2")) return text;
|
|
12158
|
+
if (hasV1Hooks(text)) return text.replace(SESSION_COOKIE_FUNCTIONS_V1, SESSION_COOKIE_FUNCTIONS).replace(SESSION_COOKIE_HOOKS_V1, SESSION_COOKIE_HOOKS);
|
|
12159
|
+
const bearer = bearerIn(text);
|
|
12160
|
+
return bearer && hasBearerOnlyHooks(text) ? text.replace(bearer, `${bearer}\n\n${SESSION_COOKIE_FUNCTIONS}`).replace(BEARER_ONLY_HOOKS, SESSION_COOKIE_HOOKS) : text;
|
|
12161
|
+
});
|
|
11967
12162
|
};
|
|
11968
12163
|
}
|
|
11969
12164
|
/**
|
|
@@ -11971,13 +12166,48 @@ function proxyConfSessionCookieEdit() {
|
|
|
11971
12166
|
* The managed file as an earlier CLI wrote it is `detached` (`doctor --fix`
|
|
11972
12167
|
* adds them). One the edit cannot place them in, a managed file changed by
|
|
11973
12168
|
* hand or a proxy of the developer's own, is `unknown`: only the developer can.
|
|
12169
|
+
* Hooks from before ADR 083 are `outdated`: they keep the session in cookies
|
|
12170
|
+
* the server no longer reads.
|
|
11974
12171
|
*/
|
|
11975
12172
|
function proxyConfSessionCookieVerdict(source) {
|
|
11976
12173
|
const text = lfText(source);
|
|
11977
|
-
if (text.includes("zitadel:session-cookie:
|
|
12174
|
+
if (text.includes("zitadel:session-cookie:v2")) return "applied";
|
|
12175
|
+
if (text.includes("zitadel:session-cookie:v1")) return { outdated: hasV1Hooks(text) ? "its session-cookie hooks name the session cookies before ADR 083; doctor --fix updates them" : "its session-cookie hooks name the session cookies before ADR 083 and were changed since zitadel setup wrote them, so they must be updated by hand" };
|
|
11978
12176
|
if (hasBearerOnlyHooks(text)) return "detached";
|
|
11979
12177
|
return { unknown: text.includes("// zitadel-cli: managed-file v1") ? "it was changed since zitadel setup wrote it, so the session-cookie hooks (ADR 073) must be added by hand" : "it is not the proxy zitadel setup writes, so the session-cookie hooks (ADR 073) must be added by hand" };
|
|
11980
12178
|
}
|
|
12179
|
+
/**
|
|
12180
|
+
* Moves the managed proxy's bearer hook from v2, which knew only
|
|
12181
|
+
* `/sessions/exchange`, to v3, which knows the exchange under the API prefix
|
|
12182
|
+
* too (ADR 077): once an app's packages call `/api/v1`, a v2 proxy no longer
|
|
12183
|
+
* attaches the project secret to the exchange. Only the exact hook an earlier
|
|
12184
|
+
* CLI wrote is replaced; a file that carries v3 already, an unmanaged one and a
|
|
12185
|
+
* hook changed by hand are left as they are, and
|
|
12186
|
+
* {@link proxyConfApiPrefixVerdict} tells the doctor which it is. Line ends are
|
|
12187
|
+
* the file's own. Creates the managed proxy when it is absent, as
|
|
12188
|
+
* {@link proxyConfEdit} does.
|
|
12189
|
+
*/
|
|
12190
|
+
function proxyConfApiPrefixEdit() {
|
|
12191
|
+
return (source) => {
|
|
12192
|
+
if (source === void 0) return proxyConfTemplate();
|
|
12193
|
+
return withLfLineEnds(source, (text) => text.includes("// zitadel-cli: managed-file v1") && text.includes(V2_EXCHANGE_ONLY_BEARER) ? text.replace(V2_EXCHANGE_ONLY_BEARER, EXCHANGE_ONLY_BEARER) : text);
|
|
12194
|
+
};
|
|
12195
|
+
}
|
|
12196
|
+
/**
|
|
12197
|
+
* Whether `proxy.conf.cjs` attaches the project secret to the exchange under
|
|
12198
|
+
* the API prefix too, for the doctor. The managed file with an earlier CLI's
|
|
12199
|
+
* v2 hook is `detached` (`doctor --fix` moves it to v3). A managed file whose
|
|
12200
|
+
* hook was changed by hand, and a proxy of the developer's own that attaches
|
|
12201
|
+
* the secret but never names `/api/v1/sessions/exchange`, are `unknown`: only
|
|
12202
|
+
* the developer can change them.
|
|
12203
|
+
*/
|
|
12204
|
+
function proxyConfApiPrefixVerdict(source) {
|
|
12205
|
+
const text = lfText(source);
|
|
12206
|
+
if (text.includes(EXCHANGE_ONLY_BEARER)) return "applied";
|
|
12207
|
+
if (text.includes("// zitadel-cli: managed-file v1") && text.includes(V2_EXCHANGE_ONLY_BEARER)) return "detached";
|
|
12208
|
+
if (!text.includes("ZITADEL_PROJECT_SECRET") || text.includes(PREFIXED_EXCHANGE_PATH)) return "applied";
|
|
12209
|
+
return { unknown: text.includes("// zitadel-cli: managed-file v1") ? `it was changed since zitadel setup wrote it, so its project-secret check must accept ${PREFIXED_EXCHANGE_PATH} by hand (ADR 077)` : `it is not the proxy zitadel setup writes, so its project-secret check must accept ${PREFIXED_EXCHANGE_PATH} by hand (ADR 077)` };
|
|
12210
|
+
}
|
|
11981
12211
|
//#endregion
|
|
11982
12212
|
//#region src/lib/orca/patchers/rule/angular/index.ts
|
|
11983
12213
|
const SDK_DEPENDENCY$10 = "@zitadel/sdk-angular";
|
|
@@ -12076,6 +12306,13 @@ var AngularPatcher = class extends AbstractRulePatcher {
|
|
|
12076
12306
|
wiring: "convenience",
|
|
12077
12307
|
verify: proxyConfSessionCookieVerdict
|
|
12078
12308
|
},
|
|
12309
|
+
{
|
|
12310
|
+
kind: "edit",
|
|
12311
|
+
path: "proxy.conf.cjs",
|
|
12312
|
+
edit: proxyConfApiPrefixEdit(),
|
|
12313
|
+
wiring: "infrastructure",
|
|
12314
|
+
verify: proxyConfApiPrefixVerdict
|
|
12315
|
+
},
|
|
12079
12316
|
{
|
|
12080
12317
|
kind: "edit",
|
|
12081
12318
|
path: "angular.json",
|
|
@@ -12372,7 +12609,7 @@ const ZitadelSession = dynamic(
|
|
|
12372
12609
|
"@zitadel/sdk-next/components"
|
|
12373
12610
|
);
|
|
12374
12611
|
// Build the SDK project handle and pass it to the session card via the
|
|
12375
|
-
// \`project\` prop. The card reads identity from "/__nextgen/sessions/me"
|
|
12612
|
+
// \`project\` prop. The card reads identity from "/__nextgen/api/v1/sessions/me"
|
|
12376
12613
|
// and exposes a Sign out action. Your own components (a header, an
|
|
12377
12614
|
// account menu) can make the same read with getSession() from
|
|
12378
12615
|
// "@zitadel/sdk-next/session" to swap sign-in CTAs for account chrome.
|
|
@@ -13039,7 +13276,8 @@ var NuxtPatcher = class extends AbstractRulePatcher {
|
|
|
13039
13276
|
* `/__nextgen/*` calls to the
|
|
13040
13277
|
* backend, strips the prefix, and attaches the project's service-key secret
|
|
13041
13278
|
* (read from `ZITADEL_PROJECT_SECRET` in `.env.local`) only to the current
|
|
13042
|
-
* app-plane `POST /sessions/exchange` operation
|
|
13279
|
+
* app-plane `POST /sessions/exchange` operation, which it knows with and
|
|
13280
|
+
* without the API prefix (`/api/v1`, ADR 077). The secret stays server-side:
|
|
13043
13281
|
* Vite only exposes vars with the configured `envPrefix` (default `VITE_`) to
|
|
13044
13282
|
* client bundles, so this server-only key never leaks into the browser.
|
|
13045
13283
|
*
|
|
@@ -13053,7 +13291,8 @@ var NuxtPatcher = class extends AbstractRulePatcher {
|
|
|
13053
13291
|
* browser's `Origin`, which the proxy passes on untouched.
|
|
13054
13292
|
*
|
|
13055
13293
|
* The session cookie travels under the app's project's name in the browser and
|
|
13056
|
-
* the server's name upstream ({@link viteSessionCookieStatements}, ADR 073
|
|
13294
|
+
* the server's name upstream ({@link viteSessionCookieStatements}, ADR 073,
|
|
13295
|
+
* ADR 083).
|
|
13057
13296
|
*/
|
|
13058
13297
|
function proxyEntryCode(server) {
|
|
13059
13298
|
return `/* ${PROXY_CREDENTIAL_POLICY_MARKER} */ {
|
|
@@ -13072,13 +13311,17 @@ ${indented(viteSessionCookieStatements(), " ")}
|
|
|
13072
13311
|
},
|
|
13073
13312
|
}`;
|
|
13074
13313
|
}
|
|
13075
|
-
/**
|
|
13076
|
-
|
|
13314
|
+
/**
|
|
13315
|
+
* The generated hook that attaches the project secret to the session exchange
|
|
13316
|
+
* only. `pathCheck` is the exchange's path test: both spellings, or the one a
|
|
13317
|
+
* v2 proxy knew.
|
|
13318
|
+
*/
|
|
13319
|
+
function bearerListener(indent, pathCheck = EXCHANGE_PATH_CHECK) {
|
|
13077
13320
|
return `${indent}proxy.on("proxyReq", (proxyReq) => {
|
|
13078
13321
|
${indent} const pathname = new URL(proxyReq.path, "http://zitadel.local").pathname;
|
|
13079
13322
|
${indent} if (
|
|
13080
13323
|
${indent} proxyReq.method === "POST" &&
|
|
13081
|
-
${indent}
|
|
13324
|
+
${indent} ${pathCheck} &&
|
|
13082
13325
|
${indent} !proxyReq.getHeader("authorization")
|
|
13083
13326
|
${indent} ) {
|
|
13084
13327
|
${indent} proxyReq.setHeader("authorization", bearer);
|
|
@@ -13096,7 +13339,7 @@ ${bearerListener(indent)}`;
|
|
|
13096
13339
|
* surrounding `/__nextgen` proxy and any user changes elsewhere in the entry.
|
|
13097
13340
|
*/
|
|
13098
13341
|
function isLegacyManagedProxy(source) {
|
|
13099
|
-
if (source
|
|
13342
|
+
if (hasProxyCredentialPolicyMarker(source)) return false;
|
|
13100
13343
|
return [
|
|
13101
13344
|
"loadEnv(\"development\", process.cwd(), \"ZITADEL_\").ZITADEL_PROJECT_SECRET",
|
|
13102
13345
|
"throw new Error(\"ZITADEL_PROJECT_SECRET is not set; add it to .env.local (zitadel setup writes it).\")",
|
|
@@ -13109,7 +13352,7 @@ function isLegacyManagedProxy(source) {
|
|
|
13109
13352
|
* also kept `localhost` as the TLS server name, so that proxy could reach no
|
|
13110
13353
|
* remote https server.
|
|
13111
13354
|
*/
|
|
13112
|
-
const HOST_KEEPING_ENTRY = /(["']\/__nextgen["']:\s*(?:\/\* zitadel:proxy:
|
|
13355
|
+
const HOST_KEEPING_ENTRY = /(["']\/__nextgen["']:\s*(?:\/\* zitadel:proxy:v[23] \*\/\s*)?\{\r?\n([\t ]+)target: [^\r\n]+,\r?\n)\2changeOrigin: false,(\r?\n)/;
|
|
13113
13356
|
/**
|
|
13114
13357
|
* Moves the CLI's own host-keeping entry to the origin-forwarding shape of
|
|
13115
13358
|
* {@link proxyEntryCode}. Only the generated layout in a config that carries
|
|
@@ -13118,7 +13361,7 @@ const HOST_KEEPING_ENTRY = /(["']\/__nextgen["']:\s*(?:\/\* zitadel:proxy:v2 \*\
|
|
|
13118
13361
|
* is never touched.
|
|
13119
13362
|
*/
|
|
13120
13363
|
function forwardAppOrigin(source) {
|
|
13121
|
-
if (!source
|
|
13364
|
+
if (!hasProxyCredentialPolicyMarker(source) || /\bxfwd\s*:/.test(source)) return source;
|
|
13122
13365
|
return source.replace(HOST_KEEPING_ENTRY, (_entry, head, indent, eol) => `${head}${indent}changeOrigin: true,${eol}${indent}xfwd: true,${eol}`);
|
|
13123
13366
|
}
|
|
13124
13367
|
/** The imports that the injected proxy entry depends on. */
|
|
@@ -13189,42 +13432,81 @@ function propertyOf(node, name) {
|
|
|
13189
13432
|
function fingerprint(source) {
|
|
13190
13433
|
return source.replace(/\s+/g, "").replace(/'/g, "\"").replace(/;/g, "").replace(/,(?=[)\]}])/g, "").replace(/(^|[(,:=])([A-Za-z_$][\w$]*)=>/g, "$1($2)=>");
|
|
13191
13434
|
}
|
|
13435
|
+
/** The bearer hook as the CLI writes it now, and as a v2 proxy carries it, fingerprinted. */
|
|
13436
|
+
const HOOK_FINGERPRINTS = [{
|
|
13437
|
+
version: "v3",
|
|
13438
|
+
fingerprint: fingerprint(bearerListener(""))
|
|
13439
|
+
}, {
|
|
13440
|
+
version: "v2",
|
|
13441
|
+
fingerprint: fingerprint(bearerListener("", V2_EXCHANGE_PATH_CHECK))
|
|
13442
|
+
}];
|
|
13192
13443
|
/**
|
|
13193
|
-
* The
|
|
13194
|
-
*
|
|
13195
|
-
*
|
|
13196
|
-
*
|
|
13444
|
+
* The `/__nextgen` entry the CLI wrote: one carrying the credential-policy
|
|
13445
|
+
* marker and the CLI's own exchange-only bearer hook, of either version,
|
|
13446
|
+
* however it was formatted since. A hand-written entry, or one whose hook was
|
|
13447
|
+
* changed, yields `undefined`.
|
|
13197
13448
|
*/
|
|
13198
|
-
function
|
|
13449
|
+
function cliEntryOf(config, source) {
|
|
13199
13450
|
const root = config.$ast;
|
|
13200
13451
|
const proxy = propertyOf(propertyOf(root, "server")?.value, "proxy")?.value;
|
|
13201
13452
|
const entry = propertyOf(proxy, PROXY_PATH);
|
|
13202
13453
|
const configure = propertyOf(entry?.value, "configure");
|
|
13203
13454
|
const body = (configure?.type === "ObjectMethod" ? configure : configure?.value)?.body;
|
|
13204
|
-
if (!entry || !body || Array.isArray(body) || body.type !== "BlockStatement") return;
|
|
13205
|
-
const
|
|
13206
|
-
const statements
|
|
13207
|
-
|
|
13455
|
+
if (!entry || !body || Array.isArray(body) || body.type !== "BlockStatement" || !hasProxyCredentialPolicyMarker(source.slice(entry.start, entry.end))) return;
|
|
13456
|
+
const statements = body.body;
|
|
13457
|
+
for (const node of statements) {
|
|
13458
|
+
const printed = fingerprint(source.slice(node.start, node.end));
|
|
13459
|
+
const known = HOOK_FINGERPRINTS.find((hook) => hook.fingerprint === printed);
|
|
13460
|
+
if (known) return {
|
|
13461
|
+
entry,
|
|
13462
|
+
statements,
|
|
13463
|
+
hook: {
|
|
13464
|
+
node,
|
|
13465
|
+
version: known.version
|
|
13466
|
+
}
|
|
13467
|
+
};
|
|
13468
|
+
}
|
|
13208
13469
|
}
|
|
13209
13470
|
/** What the session-cookie edit and its verdict call the file they read. */
|
|
13210
13471
|
const VITE_CONFIG_LABEL = "the Vite config (vite.config.*)";
|
|
13211
13472
|
/**
|
|
13212
|
-
* The
|
|
13213
|
-
*
|
|
13473
|
+
* The CLI's own `/__nextgen` entry in `source` (LF line ends), or `undefined`
|
|
13474
|
+
* when there is no such entry.
|
|
13214
13475
|
*/
|
|
13215
|
-
function
|
|
13216
|
-
return
|
|
13476
|
+
function cliEntry(source) {
|
|
13477
|
+
return cliEntryOf(resolveDefaultExportObject(parseConfigModule(source, VITE_CONFIG_LABEL, { tabWidth: 1 }), VITE_CONFIG_LABEL), source);
|
|
13478
|
+
}
|
|
13479
|
+
/** How many statements the hooks before ADR 083 are: the project id, the cookie name and the two listeners. */
|
|
13480
|
+
const V1_HOOK_STATEMENTS = 4;
|
|
13481
|
+
/**
|
|
13482
|
+
* `text` (LF line ends) with the session-cookie hooks the CLI wrote before
|
|
13483
|
+
* ADR 083 replaced by the current ones, at their indentation, or `undefined`
|
|
13484
|
+
* when the CLI's entry does not carry them as the CLI wrote them, whatever a
|
|
13485
|
+
* formatter did to their layout.
|
|
13486
|
+
*/
|
|
13487
|
+
function withCurrentHooks(text) {
|
|
13488
|
+
const marker = text.indexOf(`/* ${SESSION_COOKIE_MARKER_V1} */`);
|
|
13489
|
+
const statements = marker === -1 ? void 0 : cliEntry(text)?.statements;
|
|
13490
|
+
const first = statements?.findIndex((statement) => statement.start > marker) ?? -1;
|
|
13491
|
+
const last = statements?.[first + V1_HOOK_STATEMENTS - 1];
|
|
13492
|
+
if (first === -1 || !last) return;
|
|
13493
|
+
const lineStart = text.lastIndexOf("\n", marker - 1) + 1;
|
|
13494
|
+
if (fingerprint(text.slice(lineStart, last.end)) !== fingerprint(viteSessionCookieStatementsV1().join("\n"))) return;
|
|
13495
|
+
const indent = /^[\t ]*/.exec(text.slice(lineStart, marker))?.[0] ?? "";
|
|
13496
|
+
return `${text.slice(0, lineStart)}${indented(viteSessionCookieStatements(), indent)}${text.slice(last.end)}`;
|
|
13217
13497
|
}
|
|
13218
13498
|
/**
|
|
13219
13499
|
* Builds the `edit` transform that gives the CLI's own `/__nextgen` entry the
|
|
13220
|
-
* session-cookie hooks (ADR 073), for apps set up before the entry
|
|
13221
|
-
* them: the hooks go after the last statement of its `configure(proxy)`
|
|
13222
|
-
* at that statement's indentation, and nothing else in the file changes.
|
|
13223
|
-
*
|
|
13224
|
-
*
|
|
13225
|
-
*
|
|
13226
|
-
*
|
|
13227
|
-
* or
|
|
13500
|
+
* session-cookie hooks (ADR 073, ADR 083), for apps set up before the entry
|
|
13501
|
+
* carried them: the hooks go after the last statement of its `configure(proxy)`
|
|
13502
|
+
* body, at that statement's indentation, and nothing else in the file changes.
|
|
13503
|
+
* The hooks the CLI wrote before ADR 083 are replaced where they stand. A
|
|
13504
|
+
* config whose entry carries the current hooks already, a hand-written entry,
|
|
13505
|
+
* one whose bearer hook was changed and one whose hooks before ADR 083 were
|
|
13506
|
+
* changed are returned untouched; {@link viteSessionCookieVerdict} tells the
|
|
13507
|
+
* doctor which it is. Line ends are the file's own (CRLF or LF). Throws
|
|
13508
|
+
* `E_VALIDATION`, as {@link viteProxyEdit} does, when the file is absent or
|
|
13509
|
+
* unreadable.
|
|
13228
13510
|
*/
|
|
13229
13511
|
function viteSessionCookieEdit() {
|
|
13230
13512
|
return (source) => {
|
|
@@ -13233,8 +13515,9 @@ function viteSessionCookieEdit() {
|
|
|
13233
13515
|
return "";
|
|
13234
13516
|
}
|
|
13235
13517
|
return withLfLineEnds(source, (text) => {
|
|
13236
|
-
if (text.includes("zitadel:session-cookie:
|
|
13237
|
-
|
|
13518
|
+
if (text.includes("zitadel:session-cookie:v2")) return text;
|
|
13519
|
+
if (text.includes("zitadel:session-cookie:v1")) return withCurrentHooks(text) ?? text;
|
|
13520
|
+
const last = cliEntry(text)?.statements.at(-1);
|
|
13238
13521
|
if (!last) return text;
|
|
13239
13522
|
const lineStart = text.lastIndexOf("\n", last.start - 1) + 1;
|
|
13240
13523
|
const indent = /^[\t ]*/.exec(text.slice(lineStart, last.start))?.[0] ?? "";
|
|
@@ -13247,13 +13530,61 @@ function viteSessionCookieEdit() {
|
|
|
13247
13530
|
* The CLI's entry without them is `detached` (`doctor --fix` adds them), and so
|
|
13248
13531
|
* is a config without the CLI's entry, which the proxy edit writes with them.
|
|
13249
13532
|
* An entry the CLI wrote whose bearer hook was changed by hand is `unknown`:
|
|
13250
|
-
* the edit cannot place the hooks there, and only the developer can.
|
|
13533
|
+
* the edit cannot place the hooks there, and only the developer can. Hooks
|
|
13534
|
+
* from before ADR 083 are `outdated`: they keep the session in cookies the
|
|
13535
|
+
* server no longer reads.
|
|
13251
13536
|
*/
|
|
13252
13537
|
function viteSessionCookieVerdict(source) {
|
|
13253
13538
|
const text = lfText(source);
|
|
13254
|
-
if (text.includes("zitadel:session-cookie:
|
|
13255
|
-
if (
|
|
13256
|
-
|
|
13539
|
+
if (text.includes("zitadel:session-cookie:v2")) return "applied";
|
|
13540
|
+
if (text.includes("zitadel:session-cookie:v1")) return { outdated: withCurrentHooks(text) === void 0 ? "its session-cookie hooks name the session cookies before ADR 083 and were changed since zitadel setup wrote them, so they must be updated by hand" : "its session-cookie hooks name the session cookies before ADR 083; doctor --fix updates them" };
|
|
13541
|
+
if (cliEntry(text)) return "detached";
|
|
13542
|
+
return hasProxyCredentialPolicyMarker(text) ? { unknown: "its /__nextgen proxy entry was changed since zitadel setup wrote it, so the session-cookie hooks (ADR 073) must be added by hand" } : "detached";
|
|
13543
|
+
}
|
|
13544
|
+
/**
|
|
13545
|
+
* Builds the `edit` transform that moves the CLI's own `/__nextgen` entry from
|
|
13546
|
+
* v2, whose bearer hook knew only `/sessions/exchange`, to v3, which knows the
|
|
13547
|
+
* exchange under the API prefix too (ADR 077): once an app's packages call
|
|
13548
|
+
* `/api/v1`, a v2 proxy no longer attaches the project secret to the exchange.
|
|
13549
|
+
* Only the hook statement and the entry's marker change, whatever the
|
|
13550
|
+
* formatter did to the rest; a hand-written entry, a hook changed by hand and
|
|
13551
|
+
* a v3 entry are returned untouched, and {@link viteApiPrefixVerdict} tells
|
|
13552
|
+
* the doctor which it is. Line ends are the file's own. Throws `E_VALIDATION`,
|
|
13553
|
+
* as {@link viteProxyEdit} does, when the file is absent or unreadable.
|
|
13554
|
+
*/
|
|
13555
|
+
function viteApiPrefixEdit() {
|
|
13556
|
+
return (source) => {
|
|
13557
|
+
if (source === void 0) {
|
|
13558
|
+
parseConfigModule(source, VITE_CONFIG_LABEL, { tabWidth: 1 });
|
|
13559
|
+
return "";
|
|
13560
|
+
}
|
|
13561
|
+
return withLfLineEnds(source, (text) => {
|
|
13562
|
+
const found = cliEntry(text);
|
|
13563
|
+
if (found?.hook.version !== "v2") return text;
|
|
13564
|
+
const { entry, hook } = found;
|
|
13565
|
+
const lineStart = text.lastIndexOf("\n", hook.node.start - 1) + 1;
|
|
13566
|
+
const indent = /^[\t ]*/.exec(text.slice(lineStart, hook.node.start))?.[0] ?? "";
|
|
13567
|
+
const migrated = text.slice(entry.start, hook.node.start) + bearerListener(indent).slice(indent.length) + text.slice(hook.node.end, entry.end);
|
|
13568
|
+
return text.slice(0, entry.start) + migrated.replaceAll(PROXY_CREDENTIAL_POLICY_V2_MARKER, PROXY_CREDENTIAL_POLICY_MARKER) + text.slice(entry.end);
|
|
13569
|
+
});
|
|
13570
|
+
};
|
|
13571
|
+
}
|
|
13572
|
+
/**
|
|
13573
|
+
* Whether the Vite config's dev proxy attaches the project secret to the
|
|
13574
|
+
* exchange under the API prefix too, for the doctor. The CLI's v2 entry is
|
|
13575
|
+
* `detached` (`doctor --fix` moves it to v3); an entry the CLI wrote whose hook
|
|
13576
|
+
* was changed by hand, and a hand-written proxy that attaches the secret but
|
|
13577
|
+
* never names `/api/v1/sessions/exchange`, are `unknown`: only the developer can
|
|
13578
|
+
* change them. A config with no proxy that attaches the secret has nothing to
|
|
13579
|
+
* move.
|
|
13580
|
+
*/
|
|
13581
|
+
function viteApiPrefixVerdict(source) {
|
|
13582
|
+
const text = lfText(source);
|
|
13583
|
+
const found = cliEntry(text);
|
|
13584
|
+
if (found) return found.hook.version === "v3" ? "applied" : "detached";
|
|
13585
|
+
if (hasProxyCredentialPolicyMarker(text)) return { unknown: `its /__nextgen proxy entry was changed since zitadel setup wrote it, so its project-secret check must accept ${PREFIXED_EXCHANGE_PATH} by hand (ADR 077)` };
|
|
13586
|
+
if (text.includes("/__nextgen") && text.includes("ZITADEL_PROJECT_SECRET") && !text.includes(PREFIXED_EXCHANGE_PATH)) return { unknown: `its /__nextgen proxy is not the one zitadel setup writes, so its project-secret check must accept ${PREFIXED_EXCHANGE_PATH} by hand (ADR 077)` };
|
|
13587
|
+
return "applied";
|
|
13257
13588
|
}
|
|
13258
13589
|
/**
|
|
13259
13590
|
* Candidate Vite config filenames, in resolution priority. The patcher hands
|
|
@@ -13280,6 +13611,16 @@ function buildViteSessionCookieOp() {
|
|
|
13280
13611
|
verify: viteSessionCookieVerdict
|
|
13281
13612
|
};
|
|
13282
13613
|
}
|
|
13614
|
+
/** Builds the shared API-prefix {@link FileOp} for a {@link ViteSupport} patcher. */
|
|
13615
|
+
function buildViteApiPrefixOp() {
|
|
13616
|
+
return {
|
|
13617
|
+
kind: "edit",
|
|
13618
|
+
path: [...VITE_CONFIG_PATHS$1],
|
|
13619
|
+
edit: viteApiPrefixEdit(),
|
|
13620
|
+
wiring: "infrastructure",
|
|
13621
|
+
verify: viteApiPrefixVerdict
|
|
13622
|
+
};
|
|
13623
|
+
}
|
|
13283
13624
|
//#endregion
|
|
13284
13625
|
//#region src/lib/orca/patchers/rule/qwik/templates.ts
|
|
13285
13626
|
/**
|
|
@@ -13388,6 +13729,9 @@ var QwikPatcher = class extends AbstractRulePatcher {
|
|
|
13388
13729
|
viteSessionCookieOp() {
|
|
13389
13730
|
return buildViteSessionCookieOp();
|
|
13390
13731
|
}
|
|
13732
|
+
viteApiPrefixOp() {
|
|
13733
|
+
return buildViteApiPrefixOp();
|
|
13734
|
+
}
|
|
13391
13735
|
async preflight(_ctx, cwd) {
|
|
13392
13736
|
await assertMountsRootComponent(cwd, await viteEntry(cwd), ROOT$4);
|
|
13393
13737
|
}
|
|
@@ -13403,6 +13747,7 @@ var QwikPatcher = class extends AbstractRulePatcher {
|
|
|
13403
13747
|
},
|
|
13404
13748
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
13405
13749
|
this.viteSessionCookieOp(),
|
|
13750
|
+
this.viteApiPrefixOp(),
|
|
13406
13751
|
{
|
|
13407
13752
|
kind: "merge-env",
|
|
13408
13753
|
path: ".env.example",
|
|
@@ -13759,6 +14104,9 @@ var ReactPatcher = class extends AbstractRulePatcher {
|
|
|
13759
14104
|
viteSessionCookieOp() {
|
|
13760
14105
|
return buildViteSessionCookieOp();
|
|
13761
14106
|
}
|
|
14107
|
+
viteApiPrefixOp() {
|
|
14108
|
+
return buildViteApiPrefixOp();
|
|
14109
|
+
}
|
|
13762
14110
|
async preflight(_ctx, cwd) {
|
|
13763
14111
|
await assertMountsRootComponent(cwd, await viteEntry(cwd), ROOT$3);
|
|
13764
14112
|
}
|
|
@@ -13774,6 +14122,7 @@ var ReactPatcher = class extends AbstractRulePatcher {
|
|
|
13774
14122
|
},
|
|
13775
14123
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
13776
14124
|
this.viteSessionCookieOp(),
|
|
14125
|
+
this.viteApiPrefixOp(),
|
|
13777
14126
|
{
|
|
13778
14127
|
kind: "merge-env",
|
|
13779
14128
|
path: ".env.example",
|
|
@@ -13913,6 +14262,9 @@ var SolidPatcher = class extends AbstractRulePatcher {
|
|
|
13913
14262
|
viteSessionCookieOp() {
|
|
13914
14263
|
return buildViteSessionCookieOp();
|
|
13915
14264
|
}
|
|
14265
|
+
viteApiPrefixOp() {
|
|
14266
|
+
return buildViteApiPrefixOp();
|
|
14267
|
+
}
|
|
13916
14268
|
async preflight(_ctx, cwd) {
|
|
13917
14269
|
await assertMountsRootComponent(cwd, await viteEntry(cwd), ROOT$2);
|
|
13918
14270
|
}
|
|
@@ -13928,6 +14280,7 @@ var SolidPatcher = class extends AbstractRulePatcher {
|
|
|
13928
14280
|
},
|
|
13929
14281
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
13930
14282
|
this.viteSessionCookieOp(),
|
|
14283
|
+
this.viteApiPrefixOp(),
|
|
13931
14284
|
{
|
|
13932
14285
|
kind: "merge-env",
|
|
13933
14286
|
path: ".env.example",
|
|
@@ -14387,6 +14740,9 @@ var SveltePatcher = class extends AbstractRulePatcher {
|
|
|
14387
14740
|
viteSessionCookieOp() {
|
|
14388
14741
|
return buildViteSessionCookieOp();
|
|
14389
14742
|
}
|
|
14743
|
+
viteApiPrefixOp() {
|
|
14744
|
+
return buildViteApiPrefixOp();
|
|
14745
|
+
}
|
|
14390
14746
|
async preflight(_ctx, cwd) {
|
|
14391
14747
|
await assertMountsRootComponent(cwd, await viteEntry(cwd), ROOT$1);
|
|
14392
14748
|
}
|
|
@@ -14402,6 +14758,7 @@ var SveltePatcher = class extends AbstractRulePatcher {
|
|
|
14402
14758
|
},
|
|
14403
14759
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
14404
14760
|
this.viteSessionCookieOp(),
|
|
14761
|
+
this.viteApiPrefixOp(),
|
|
14405
14762
|
{
|
|
14406
14763
|
kind: "merge-env",
|
|
14407
14764
|
path: ".env.example",
|
|
@@ -15149,6 +15506,9 @@ var VuePatcher = class extends AbstractRulePatcher {
|
|
|
15149
15506
|
viteSessionCookieOp() {
|
|
15150
15507
|
return buildViteSessionCookieOp();
|
|
15151
15508
|
}
|
|
15509
|
+
viteApiPrefixOp() {
|
|
15510
|
+
return buildViteApiPrefixOp();
|
|
15511
|
+
}
|
|
15152
15512
|
async preflight(_ctx, cwd) {
|
|
15153
15513
|
await assertMountsRootComponent(cwd, await viteEntry(cwd), ROOT);
|
|
15154
15514
|
}
|
|
@@ -15164,6 +15524,7 @@ var VuePatcher = class extends AbstractRulePatcher {
|
|
|
15164
15524
|
},
|
|
15165
15525
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
15166
15526
|
this.viteSessionCookieOp(),
|
|
15527
|
+
this.viteApiPrefixOp(),
|
|
15167
15528
|
{
|
|
15168
15529
|
kind: "merge-env",
|
|
15169
15530
|
path: ".env.example",
|
|
@@ -16588,6 +16949,7 @@ var ManagedFilesCheck = class {
|
|
|
16588
16949
|
const detachedInfrastructure = wiring.filter((s) => s.state === "detached" && s.wiring === "infrastructure");
|
|
16589
16950
|
const detachedConvenience = wiring.filter((s) => s.state === "detached" && s.wiring === "convenience");
|
|
16590
16951
|
const unverifiable = wiring.filter((s) => s.state === "unknown");
|
|
16952
|
+
const outdated = wiring.filter((s) => s.state === "outdated");
|
|
16591
16953
|
const conflicts = boundaries.filter((b) => b.disposition === "conflict");
|
|
16592
16954
|
const removable = boundaries.filter((b) => b.disposition === "removable");
|
|
16593
16955
|
const details = {
|
|
@@ -16599,6 +16961,7 @@ var ManagedFilesCheck = class {
|
|
|
16599
16961
|
const failures = [];
|
|
16600
16962
|
if (missingInfrastructure.length > 0) failures.push(`missing scaffolded infrastructure file(s): ${listPaths(missingInfrastructure)}`);
|
|
16601
16963
|
if (detachedInfrastructure.length > 0) failures.push(`detached managed config wiring: ${detachedInfrastructure.map((s) => s.path).join(", ")}`);
|
|
16964
|
+
if (outdated.length > 0) failures.push(`outdated managed config wiring: ${outdated.map((s) => s.reason ? `${s.path} (${s.reason})` : s.path).join(", ")}`);
|
|
16602
16965
|
for (const conflict of conflicts) failures.push(`conflicting request boundaries: ${conflict.retired} is user-modified while the current templates use ${conflict.current} — migrate your changes and remove one of them manually`);
|
|
16603
16966
|
if (failures.length > 0) return {
|
|
16604
16967
|
name: this.name,
|
|
@@ -17338,13 +17701,13 @@ async function inspect(cwd) {
|
|
|
17338
17701
|
status: "pass",
|
|
17339
17702
|
message: `${issuer} is not recorded as a preview origin`
|
|
17340
17703
|
};
|
|
17341
|
-
const client = createZitadelClient({
|
|
17704
|
+
const client = createZitadelClient$1({
|
|
17342
17705
|
baseUrl: server,
|
|
17343
17706
|
token: secret.project_secret
|
|
17344
17707
|
});
|
|
17345
17708
|
let origins;
|
|
17346
17709
|
try {
|
|
17347
|
-
origins = (await client.getProject(secret.project_id)).origins
|
|
17710
|
+
origins = (await client.getProject(secret.project_id)).origins;
|
|
17348
17711
|
} catch (error) {
|
|
17349
17712
|
return {
|
|
17350
17713
|
status: "pass",
|
|
@@ -17614,18 +17977,24 @@ var Doctor = class Doctor extends BaseCommand {
|
|
|
17614
17977
|
port
|
|
17615
17978
|
} : {},
|
|
17616
17979
|
project: { lifecycle: hasConfig ? "configured" : "not-configured" },
|
|
17617
|
-
checks
|
|
17618
|
-
|
|
17619
|
-
|
|
17980
|
+
checks
|
|
17981
|
+
};
|
|
17982
|
+
const next = {
|
|
17983
|
+
...nextActions.length > 0 ? { actions: nextActions } : {},
|
|
17984
|
+
...warningAdvice ? { commands: warningAdvice.nextCommands } : {}
|
|
17620
17985
|
};
|
|
17621
17986
|
if (failed.length > 0) {
|
|
17622
17987
|
const advice = failureAdvice(failed, image, port, this.meta.cliVersion, selector, parentRuntimeDir, cwdFlag);
|
|
17988
|
+
const report = {
|
|
17989
|
+
...data,
|
|
17990
|
+
...nextStepsData(next)
|
|
17991
|
+
};
|
|
17623
17992
|
const code = failed.some((check) => check.name === "port") ? "E_PORT_IN_USE" : failed.find((check) => check.code !== void 0)?.code ?? "E_VALIDATION";
|
|
17624
17993
|
throw new ZitadelError(code, "Zitadel doctor found issues", {
|
|
17625
17994
|
hint: advice.hint,
|
|
17626
17995
|
nextCommands: advice.nextCommands,
|
|
17627
|
-
details: advice.suggestedArgs === void 0 ?
|
|
17628
|
-
...
|
|
17996
|
+
details: advice.suggestedArgs === void 0 ? report : {
|
|
17997
|
+
...report,
|
|
17629
17998
|
suggested_args: advice.suggestedArgs
|
|
17630
17999
|
}
|
|
17631
18000
|
});
|
|
@@ -17633,6 +18002,7 @@ var Doctor = class Doctor extends BaseCommand {
|
|
|
17633
18002
|
return this.emit({
|
|
17634
18003
|
status: "ok",
|
|
17635
18004
|
data,
|
|
18005
|
+
next,
|
|
17636
18006
|
warnings: warnings.map((warning) => `${warning.name}: ${warning.message}`)
|
|
17637
18007
|
});
|
|
17638
18008
|
}
|
|
@@ -17798,7 +18168,7 @@ async function runEnvironmentChecks(input) {
|
|
|
17798
18168
|
const token = environment.values.ZITADEL_PROJECT_SECRET?.value;
|
|
17799
18169
|
if (server === void 0 || projectId === void 0) throw new Error(`ZITADEL_URL and ZITADEL_PROJECT_ID must resolve for the ${name} environment`);
|
|
17800
18170
|
if (token === void 0) throw new Error(`ZITADEL_PROJECT_SECRET must resolve to verify project ${projectId} on ${server}`);
|
|
17801
|
-
const project = await createZitadelClient({
|
|
18171
|
+
const project = await createZitadelClient$1({
|
|
17802
18172
|
baseUrl: server,
|
|
17803
18173
|
token
|
|
17804
18174
|
}).getProject(projectId);
|
|
@@ -18191,9 +18561,9 @@ var Eject = class Eject extends Promptable(BaseCommand) {
|
|
|
18191
18561
|
files_removed: removed,
|
|
18192
18562
|
files_preserved: preserved,
|
|
18193
18563
|
backed_up: backedUp,
|
|
18194
|
-
...nextCommandsData(nextCommands),
|
|
18195
18564
|
manual_steps: manualSteps
|
|
18196
|
-
}
|
|
18565
|
+
},
|
|
18566
|
+
next: { commands: nextCommands }
|
|
18197
18567
|
});
|
|
18198
18568
|
}
|
|
18199
18569
|
};
|
|
@@ -18276,7 +18646,7 @@ var Env = class Env extends EnvironmentCommand {
|
|
|
18276
18646
|
/** The frameworks whose server needs the project secret at runtime (the session exchange). */
|
|
18277
18647
|
const SERVER_RENDERED = ["next", "nuxt"];
|
|
18278
18648
|
/** The message the release this command publishes carries. */
|
|
18279
|
-
const INITIAL_DEPLOY_MESSAGE = "Initial deployment from zitadel env add";
|
|
18649
|
+
const INITIAL_DEPLOY_MESSAGE$1 = "Initial deployment from zitadel env add";
|
|
18280
18650
|
/**
|
|
18281
18651
|
* `zitadel env add` — bind an environment to a project: create one on the
|
|
18282
18652
|
* server, or bind an id a teammate sent over, and write the
|
|
@@ -18356,7 +18726,7 @@ var EnvAdd = class EnvAdd extends Promptable(BaseCommand) {
|
|
|
18356
18726
|
let projectSecret = env.ZITADEL_PROJECT_SECRET;
|
|
18357
18727
|
let previewToken = env.ZITADEL_PREVIEW_TOKEN;
|
|
18358
18728
|
if (projectId === void 0) {
|
|
18359
|
-
const created = await createZitadelClient({ baseUrl: server }).createProject({
|
|
18729
|
+
const created = await createZitadelClient$1({ baseUrl: server }).createProject({
|
|
18360
18730
|
name: `${basename(cwd)}-${name}`,
|
|
18361
18731
|
...issuer === void 0 ? {} : { origins: [{
|
|
18362
18732
|
pattern: issuer,
|
|
@@ -18488,19 +18858,16 @@ var EnvAdd = class EnvAdd extends Promptable(BaseCommand) {
|
|
|
18488
18858
|
content_hash: deployment.contentHash,
|
|
18489
18859
|
deployment_id: deployment.deploymentId,
|
|
18490
18860
|
origins: [...deployment.origins]
|
|
18491
|
-
}
|
|
18492
|
-
...nextCommandsData([
|
|
18493
|
-
...needsOrigin && issuer !== void 0 && writtenKeys.has("ZITADEL_PROJECT_SECRET") ? [projectCommand(`origin add ${shellArg(issuer)} --kind primary`, newEnvironment, cliVersion)] : [],
|
|
18494
|
-
...hasSomewhereToLand && deployment === void 0 ? [projectCommand(DEPLOY_ARGS, newEnvironment, cliVersion)] : [],
|
|
18495
|
-
projectCommand("env", newEnvironment, cliVersion)
|
|
18496
|
-
])
|
|
18861
|
+
}
|
|
18497
18862
|
},
|
|
18863
|
+
next: { commands: [
|
|
18864
|
+
...needsOrigin && issuer !== void 0 && writtenKeys.has("ZITADEL_PROJECT_SECRET") ? [projectCommand(`origin add ${shellArg(issuer)} --kind primary`, newEnvironment, cliVersion)] : [],
|
|
18865
|
+
...hasSomewhereToLand && deployment === void 0 ? [projectCommand(DEPLOY_ARGS, newEnvironment, cliVersion)] : [],
|
|
18866
|
+
projectCommand("env", newEnvironment, cliVersion)
|
|
18867
|
+
] },
|
|
18498
18868
|
pretty: [
|
|
18499
18869
|
`Wrote ${file} with ${Object.keys(values).join(", ")}`,
|
|
18500
|
-
"",
|
|
18501
|
-
...needsOrigin ? [writtenKeys.has("ZITADEL_PROJECT_SECRET") ? `Allow this project's origin: zitadel origin add ${issuer === void 0 ? "'<your-app-url>'" : shellArg(issuer)} --kind primary${newEnvironment}` : `Allow this project's origin once ZITADEL_PROJECT_SECRET is in ${file}: zitadel origin add ${issuer === void 0 ? "'<your-app-url>'" : shellArg(issuer)} --kind primary${newEnvironment}`] : [],
|
|
18502
|
-
...createdProject && deployment === void 0 ? [`Nothing is deployed to ${name} yet, so it serves the server's defaults rather than .zitadel/.`, ...hasSomewhereToLand ? [`Deploy to it: zitadel ${DEPLOY_ARGS}${newEnvironment}`] : [`Give it an origin first: zitadel origin add '<your-app-url>' --kind primary${newEnvironment}`, `Then deploy to it: zitadel ${DEPLOY_ARGS}${newEnvironment}`]] : [],
|
|
18503
|
-
`Check what resolves: zitadel env${newEnvironment}`,
|
|
18870
|
+
...guidanceBlock([...needsOrigin ? writtenKeys.has("ZITADEL_PROJECT_SECRET") ? issuer === void 0 ? [`Allow this project's origin: zitadel origin add '<your-app-url>' --kind primary${newEnvironment}`] : [] : [`Allow this project's origin once ZITADEL_PROJECT_SECRET is in ${file}: zitadel origin add ${issuer === void 0 ? "'<your-app-url>'" : shellArg(issuer)} --kind primary${newEnvironment}`] : [], ...createdProject && deployment === void 0 ? [`Nothing is deployed to ${name} yet, so it serves the server's defaults rather than .zitadel/.`, ...hasSomewhereToLand ? [] : [`Give it an origin first: zitadel origin add '<your-app-url>' --kind primary${newEnvironment}`, `Then deploy to it: zitadel ${DEPLOY_ARGS}${newEnvironment}`]] : []]),
|
|
18504
18871
|
"",
|
|
18505
18872
|
"The deployed app never reads that file. Set these where it is built and run.",
|
|
18506
18873
|
"",
|
|
@@ -18516,10 +18883,8 @@ var EnvAdd = class EnvAdd extends Promptable(BaseCommand) {
|
|
|
18516
18883
|
});
|
|
18517
18884
|
}
|
|
18518
18885
|
/**
|
|
18519
|
-
* Publish `.zitadel/` to the project this run just created
|
|
18520
|
-
*
|
|
18521
|
-
* every primary origin. `deploy`'s diff step is left out, because nothing
|
|
18522
|
-
* has ever been deployed here and there is no baseline to diff against.
|
|
18886
|
+
* Publish `.zitadel/` to the project this run just created (see
|
|
18887
|
+
* {@link deployInitialRelease}).
|
|
18523
18888
|
*
|
|
18524
18889
|
* It authenticates with the new project's own secret rather than the one
|
|
18525
18890
|
* this directory resolves to, since `env add` binds a different project from
|
|
@@ -18528,53 +18893,33 @@ var EnvAdd = class EnvAdd extends Promptable(BaseCommand) {
|
|
|
18528
18893
|
*/
|
|
18529
18894
|
async deployInitialRelease(opts) {
|
|
18530
18895
|
const json = this.jsonEnabled();
|
|
18531
|
-
const client = createZitadelClient({
|
|
18532
|
-
baseUrl: opts.server,
|
|
18533
|
-
token: opts.projectSecret
|
|
18534
|
-
});
|
|
18535
18896
|
consola.start("Deploying .zitadel/ to the new environment");
|
|
18536
18897
|
try {
|
|
18537
|
-
const
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18898
|
+
const deployment = await deployInitialRelease({
|
|
18899
|
+
client: createZitadelClient$1({
|
|
18900
|
+
baseUrl: opts.server,
|
|
18901
|
+
token: opts.projectSecret
|
|
18902
|
+
}),
|
|
18541
18903
|
projectId: opts.projectId,
|
|
18904
|
+
cwd: opts.cwd,
|
|
18542
18905
|
env: this.meta.env,
|
|
18543
|
-
mode: "deploy",
|
|
18544
|
-
dryRun: false,
|
|
18545
18906
|
json,
|
|
18546
18907
|
color: !json && Boolean(process.stdout.isTTY) && !process.env.NO_COLOR,
|
|
18547
|
-
|
|
18548
|
-
brandingSources,
|
|
18549
|
-
idpFiles,
|
|
18550
|
-
policyFiles,
|
|
18908
|
+
message: INITIAL_DEPLOY_MESSAGE$1,
|
|
18551
18909
|
suggest: (args) => projectCommand(args, opts.selector, this.meta.cliVersion)
|
|
18552
|
-
};
|
|
18553
|
-
await runSteps([validateStep], ctx);
|
|
18554
|
-
const release = await createReleaseFromBundle(client, opts.projectId, bundle, {
|
|
18555
|
-
message: INITIAL_DEPLOY_MESSAGE,
|
|
18556
|
-
gitSha: await headSha(opts.cwd),
|
|
18557
|
-
gitDirty: await worktreeIsDirty(opts.cwd)
|
|
18558
18910
|
});
|
|
18559
|
-
|
|
18560
|
-
|
|
18561
|
-
targets: ["primary"],
|
|
18562
|
-
message: INITIAL_DEPLOY_MESSAGE
|
|
18563
|
-
});
|
|
18564
|
-
const origins = deployment.targets.map((target) => target.origin);
|
|
18565
|
-
consola.success(`Deployed ${release.content_hash} to ${origins.length} origin${origins.length === 1 ? "" : "s"}`);
|
|
18566
|
-
return {
|
|
18567
|
-
releaseId: release.id,
|
|
18568
|
-
contentHash: release.content_hash,
|
|
18569
|
-
deploymentId: deployment.id,
|
|
18570
|
-
origins
|
|
18571
|
-
};
|
|
18911
|
+
consola.success(`Deployed ${deployment.contentHash} to ${deployment.origins.length} origin${deployment.origins.length === 1 ? "" : "s"}`);
|
|
18912
|
+
return deployment;
|
|
18572
18913
|
} catch (error) {
|
|
18573
18914
|
reportWarning(`The environment was bound but the first deploy failed: ${toZitadelError(error).message}. Run \`zitadel ${DEPLOY_ARGS}${opts.selector}\` to publish it.`);
|
|
18574
18915
|
return;
|
|
18575
18916
|
}
|
|
18576
18917
|
}
|
|
18577
18918
|
};
|
|
18919
|
+
/** Lines under a blank line that sets them apart, or nothing when there are none. */
|
|
18920
|
+
function guidanceBlock(lines) {
|
|
18921
|
+
return lines.length === 0 ? [] : ["", ...lines];
|
|
18922
|
+
}
|
|
18578
18923
|
//#endregion
|
|
18579
18924
|
//#region src/commands/env/list.ts
|
|
18580
18925
|
/** The columns `list` renders, in order. */
|
|
@@ -18975,9 +19320,9 @@ var OriginAdd = class OriginAdd extends EnvironmentCommand {
|
|
|
18975
19320
|
status: "ok",
|
|
18976
19321
|
data: {
|
|
18977
19322
|
pattern: added.pattern,
|
|
18978
|
-
kind: added.kind
|
|
18979
|
-
...nextCommandsData([this.suggest("origin list")])
|
|
19323
|
+
kind: added.kind
|
|
18980
19324
|
},
|
|
19325
|
+
next: { commands: [this.suggest("origin list")] },
|
|
18981
19326
|
pretty: `Added ${added.pattern} (${added.kind})`
|
|
18982
19327
|
});
|
|
18983
19328
|
}
|
|
@@ -19002,7 +19347,7 @@ var OriginList = class OriginList extends EnvironmentCommand {
|
|
|
19002
19347
|
const { flags } = await this.parse(OriginList);
|
|
19003
19348
|
await this.toMeta(flags);
|
|
19004
19349
|
const { client, projectId } = await this.connect();
|
|
19005
|
-
const origins = (await client.getProject(projectId)).origins
|
|
19350
|
+
const origins = (await client.getProject(projectId)).origins;
|
|
19006
19351
|
const plain = flags.plain || !process.stdout.isTTY;
|
|
19007
19352
|
return this.emit({
|
|
19008
19353
|
status: "ok",
|
|
@@ -19059,7 +19404,7 @@ var OriginRm = class OriginRm extends Promptable(EnvironmentCommand) {
|
|
|
19059
19404
|
reason: "remove-cancelled"
|
|
19060
19405
|
});
|
|
19061
19406
|
await client.removeOrigin(projectId, { pattern });
|
|
19062
|
-
const remaining = await client.getProject(projectId).then((project) => project.origins
|
|
19407
|
+
const remaining = await client.getProject(projectId).then((project) => project.origins, (error) => {
|
|
19063
19408
|
reportWarning(`${pattern} was removed, but the project's remaining origins could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
19064
19409
|
});
|
|
19065
19410
|
if (remaining?.length === 0) reportWarning(`${pattern} was the project's last origin, so the project now admits every origin. Add the ones it should admit with \`zitadel origin add <url> --kind primary${this.environmentSelector()}\`.`);
|
|
@@ -19192,10 +19537,12 @@ var Preview = class Preview extends PreviewCommand {
|
|
|
19192
19537
|
static examples = [
|
|
19193
19538
|
"<%= config.bin %> preview",
|
|
19194
19539
|
"<%= config.bin %> preview --origin https://acme-git-sso-acmeinc.vercel.app",
|
|
19195
|
-
"<%= config.bin %> preview --ttl 24h"
|
|
19540
|
+
"<%= config.bin %> preview --ttl 24h",
|
|
19541
|
+
"<%= config.bin %> preview -m 'add phone_number to human-user'"
|
|
19196
19542
|
];
|
|
19197
19543
|
static flags = {
|
|
19198
19544
|
...previewFlags,
|
|
19545
|
+
message: messageFlag("A short summary of what this release changes. Defaults to the pull request's title or the current commit's subject."),
|
|
19199
19546
|
strict: Flags.boolean({ description: `Fail instead of warning when no preview credential (${CREDENTIALS[PREVIEW_CREDENTIAL]}) is set.` })
|
|
19200
19547
|
};
|
|
19201
19548
|
async run() {
|
|
@@ -19266,13 +19613,16 @@ var Preview = class Preview extends PreviewCommand {
|
|
|
19266
19613
|
},
|
|
19267
19614
|
pretty: [`Validated the preview for ${urls.length} URL${urls.length === 1 ? "" : "s"}. Nothing was deployed (dry run).`, ...urls.map((url) => ` - ${url}`)].join("\n")
|
|
19268
19615
|
});
|
|
19616
|
+
const message = await deployMessage(flags.message, cwd, env);
|
|
19269
19617
|
const release = await createReleaseFromBundle(client, projectId, bundle, {
|
|
19618
|
+
message,
|
|
19270
19619
|
gitSha: await headSha(cwd),
|
|
19271
19620
|
gitDirty: await worktreeIsDirty(cwd)
|
|
19272
19621
|
});
|
|
19273
19622
|
const deployment = await createDeployment(client, projectId, {
|
|
19274
19623
|
releaseId: release.id,
|
|
19275
19624
|
targets: urls,
|
|
19625
|
+
message,
|
|
19276
19626
|
...ttl === void 0 ? {} : { ttl: ttl.iso }
|
|
19277
19627
|
});
|
|
19278
19628
|
const previews = deployment.targets.map((target) => ({
|
|
@@ -19304,7 +19654,7 @@ var Preview = class Preview extends PreviewCommand {
|
|
|
19304
19654
|
*/
|
|
19305
19655
|
async function primaryOrigins(client, projectId) {
|
|
19306
19656
|
try {
|
|
19307
|
-
return (
|
|
19657
|
+
return (await client.getProject(projectId)).origins.filter((entry) => entry.kind === "primary").map((entry) => entry.pattern);
|
|
19308
19658
|
} catch (error) {
|
|
19309
19659
|
if (error instanceof ApiError && error.status === 403) return [];
|
|
19310
19660
|
throw error;
|
|
@@ -19361,17 +19711,15 @@ var PreviewList = class PreviewList extends PreviewCommand {
|
|
|
19361
19711
|
const { flags } = await this.parse(PreviewList);
|
|
19362
19712
|
await this.toMeta(flags);
|
|
19363
19713
|
const { client, projectId } = await this.connect();
|
|
19364
|
-
const
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
}
|
|
19368
|
-
const { items, next } = await collectPages(
|
|
19369
|
-
items:
|
|
19714
|
+
const params = {
|
|
19715
|
+
limit: flags.limit,
|
|
19716
|
+
page_token: flags["page-token"]
|
|
19717
|
+
};
|
|
19718
|
+
const { items: previews, next } = await collectPages(client.listPreviews.pages(projectId, params), {
|
|
19719
|
+
items: (page) => page.previews,
|
|
19370
19720
|
all: flags.all === true,
|
|
19371
|
-
|
|
19372
|
-
nextPageToken: "next_page_token"
|
|
19721
|
+
next: (page) => page.next_page_token
|
|
19373
19722
|
});
|
|
19374
|
-
const previews = items;
|
|
19375
19723
|
const rows = previews.map((preview) => ({
|
|
19376
19724
|
url: preview.origin,
|
|
19377
19725
|
expires: preview.expires_at,
|
|
@@ -19386,10 +19734,9 @@ var PreviewList = class PreviewList extends PreviewCommand {
|
|
|
19386
19734
|
const plain = flags.plain || !process.stdout.isTTY;
|
|
19387
19735
|
return this.emit({
|
|
19388
19736
|
status: "ok",
|
|
19389
|
-
data: listData(previews, {
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
}),
|
|
19737
|
+
data: listData(previews, { nextPageToken: next }),
|
|
19738
|
+
...nextCommand === void 0 ? {} : { next: { commands: [nextCommand] } },
|
|
19739
|
+
machineReadable: plain,
|
|
19393
19740
|
pretty: renderList(COLUMNS$3, rows, {
|
|
19394
19741
|
plain,
|
|
19395
19742
|
singular: "preview",
|
|
@@ -19433,16 +19780,11 @@ var PreviewRm = class PreviewRm extends Promptable(PreviewCommand) {
|
|
|
19433
19780
|
});
|
|
19434
19781
|
}
|
|
19435
19782
|
const { client, projectId } = await this.connect();
|
|
19436
|
-
|
|
19437
|
-
items: "previews",
|
|
19438
|
-
all: true,
|
|
19439
|
-
nextPageToken: "next_page_token"
|
|
19440
|
-
});
|
|
19441
|
-
if (!items.some((preview) => preview.origin === url)) return this.emit({
|
|
19783
|
+
if (!(await client.listPreviews.all(projectId)).some((preview) => preview.origin === url)) return this.emit({
|
|
19442
19784
|
status: "skipped",
|
|
19443
19785
|
reason: "not-live",
|
|
19444
19786
|
pretty: `${url} is not a live preview of the project; nothing was removed`,
|
|
19445
|
-
|
|
19787
|
+
next: { commands: [this.suggest("preview list")] }
|
|
19446
19788
|
});
|
|
19447
19789
|
if (!await this.input({
|
|
19448
19790
|
kind: "confirm",
|
|
@@ -19628,9 +19970,9 @@ var Pull = class Pull extends EnvironmentCommand {
|
|
|
19628
19970
|
release_id: release.id,
|
|
19629
19971
|
pulled,
|
|
19630
19972
|
skipped,
|
|
19631
|
-
...dryRun ? { dry_run: true } : {}
|
|
19632
|
-
...nextCommandsData(nextCommands)
|
|
19973
|
+
...dryRun ? { dry_run: true } : {}
|
|
19633
19974
|
},
|
|
19975
|
+
next: { commands: nextCommands },
|
|
19634
19976
|
pretty: dryRun ? `Would write ${pulled.length} file${pulled.length === 1 ? "" : "s"} from ${release.id} (dry run).\n${summary}` : `Pulled ${pulled.length} file${pulled.length === 1 ? "" : "s"} from ${release.id}.\n${summary}`
|
|
19635
19977
|
});
|
|
19636
19978
|
}
|
|
@@ -19645,7 +19987,10 @@ var Pull = class Pull extends EnvironmentCommand {
|
|
|
19645
19987
|
if (classified.kind === "release") return classified.id;
|
|
19646
19988
|
if (classified.kind === "deployment") return (await client.getDeploymentById(classified.id, { project_id: projectId })).release_id;
|
|
19647
19989
|
if (classified.kind === "latest") {
|
|
19648
|
-
const deployment = (await
|
|
19990
|
+
const deployment = (await client.listDeployments.all({
|
|
19991
|
+
project_id: projectId,
|
|
19992
|
+
live: true
|
|
19993
|
+
})).find(servesPrimary);
|
|
19649
19994
|
if (deployment === void 0) throw new ZitadelError("E_NOT_FOUND", "Nothing is deployed to a primary origin yet.", { hint: `Deploy first with \`zitadel ${DEPLOY_ARGS}${this.environmentSelector()}\`, then pull.` });
|
|
19650
19995
|
return deployment.release_id;
|
|
19651
19996
|
}
|
|
@@ -19727,9 +20072,9 @@ var ReleaseCreate = class ReleaseCreate extends EnvironmentCommand {
|
|
|
19727
20072
|
data: {
|
|
19728
20073
|
release_id: release.id,
|
|
19729
20074
|
content_hash: release.content_hash,
|
|
19730
|
-
resources: resourceCount
|
|
19731
|
-
...nextCommandsData([this.suggest(`deployment create --release ${release.id}`)])
|
|
20075
|
+
resources: resourceCount
|
|
19732
20076
|
},
|
|
20077
|
+
next: { commands: [this.suggest(`deployment create --release ${release.id}`)] },
|
|
19733
20078
|
pretty: `Created ${release.id} (${release.content_hash})`
|
|
19734
20079
|
});
|
|
19735
20080
|
}
|
|
@@ -19819,9 +20164,9 @@ var Reset = class Reset extends Promptable(BaseCommand) {
|
|
|
19819
20164
|
log_path: runtime.log_path
|
|
19820
20165
|
} : { container_name: containerName },
|
|
19821
20166
|
data_deleted: true
|
|
19822
|
-
}
|
|
19823
|
-
|
|
19824
|
-
}
|
|
20167
|
+
}
|
|
20168
|
+
},
|
|
20169
|
+
next: { commands: [this.suggestUnscoped("reset --force")] }
|
|
19825
20170
|
});
|
|
19826
20171
|
if (!await this.input({
|
|
19827
20172
|
kind: "confirm",
|
|
@@ -19872,9 +20217,13 @@ var ResourceCommand = class extends Promptable(EnvironmentCommand) {
|
|
|
19872
20217
|
args: parsed.args
|
|
19873
20218
|
}, meta));
|
|
19874
20219
|
}
|
|
19875
|
-
/**
|
|
19876
|
-
|
|
19877
|
-
|
|
20220
|
+
/**
|
|
20221
|
+
* Open the platform connection; deferred so `--dry-run` never connects.
|
|
20222
|
+
* `verbatim` is for a caller that sends back what it reads, as a list sends
|
|
20223
|
+
* back its cursor.
|
|
20224
|
+
*/
|
|
20225
|
+
context(options = {}) {
|
|
20226
|
+
return this.definition.options.connect(() => this.connect(options));
|
|
19878
20227
|
}
|
|
19879
20228
|
/**
|
|
19880
20229
|
* The operation's work. Every concrete operation overrides this; the base
|
|
@@ -19968,8 +20317,10 @@ var DeleteOperation = class extends ResourceCommand {
|
|
|
19968
20317
|
*
|
|
19969
20318
|
* These live behind {@link WireConventions} rather than inline in the list
|
|
19970
20319
|
* operation so the factory stays honest about what it assumes: a platform
|
|
19971
|
-
*
|
|
19972
|
-
* list, overrides the vocabulary instead of
|
|
20320
|
+
* that sends its cursor as `cursor`, reports it as `next_cursor`, or takes its
|
|
20321
|
+
* filters as a map rather than a list, overrides the vocabulary instead of
|
|
20322
|
+
* forking the factory. A response is never read by these names: each list
|
|
20323
|
+
* reads its items and cursor through its own typed `items` and `next`.
|
|
19973
20324
|
*/
|
|
19974
20325
|
const DEFAULT_WIRE = {
|
|
19975
20326
|
limit: "limit",
|
|
@@ -19988,8 +20339,12 @@ const wireOf = (overrides) => ({
|
|
|
19988
20339
|
});
|
|
19989
20340
|
//#endregion
|
|
19990
20341
|
//#region src/lib/oclif/crud/query.ts
|
|
19991
|
-
/**
|
|
19992
|
-
|
|
20342
|
+
/**
|
|
20343
|
+
* `field=operation:value`, or `field=value` for `equals`. `known` is every
|
|
20344
|
+
* operation the platform has, so one the field does not offer is refused
|
|
20345
|
+
* rather than read as part of a value.
|
|
20346
|
+
*/
|
|
20347
|
+
const parseFilter = (raw, filters, known) => {
|
|
19993
20348
|
const names = filters.map((filter) => filter.field);
|
|
19994
20349
|
const [name, ...rest] = raw.split("=");
|
|
19995
20350
|
if (!name || rest.length === 0) throw new ZitadelError("E_VALIDATION", `Invalid --filter "${raw}"`, { hint: `Use field=operation:value, e.g. ${names[0]}=${filters[0]?.operations[0]}:<value>.` });
|
|
@@ -20008,7 +20363,7 @@ const parseFilter = (raw, filters) => {
|
|
|
20008
20363
|
const expression = rest.join("=");
|
|
20009
20364
|
const [candidate = "", ...rawValue] = expression.split(":");
|
|
20010
20365
|
const operations = field.operations;
|
|
20011
|
-
if (operations.includes(candidate) && rawValue.length > 0) return checked({
|
|
20366
|
+
if (operations.includes(candidate) && rawValue.length > 0) return checked$1({
|
|
20012
20367
|
field,
|
|
20013
20368
|
operation: candidate,
|
|
20014
20369
|
value: rawValue.join(":")
|
|
@@ -20023,7 +20378,7 @@ const parseFilter = (raw, filters) => {
|
|
|
20023
20378
|
operations: [...operations]
|
|
20024
20379
|
}
|
|
20025
20380
|
});
|
|
20026
|
-
if (!operations.includes(candidate) &&
|
|
20381
|
+
if (!operations.includes(candidate) && known.includes(candidate)) throw new ZitadelError("E_VALIDATION", `Filter field "${name}" does not support the ${candidate} operation`, {
|
|
20027
20382
|
hint: `${name} accepts: ${operations.join(", ")}.`,
|
|
20028
20383
|
details: {
|
|
20029
20384
|
field: name,
|
|
@@ -20039,29 +20394,14 @@ const parseFilter = (raw, filters) => {
|
|
|
20039
20394
|
operations: [...operations]
|
|
20040
20395
|
}
|
|
20041
20396
|
});
|
|
20042
|
-
return checked({
|
|
20397
|
+
return checked$1({
|
|
20043
20398
|
field,
|
|
20044
20399
|
operation: "equals",
|
|
20045
20400
|
value: expression
|
|
20046
20401
|
});
|
|
20047
20402
|
};
|
|
20048
|
-
/**
|
|
20049
|
-
* Operation names the CLI recognises as operations at all, so a value that
|
|
20050
|
-
* merely looks like `something:else` is not mistaken for one.
|
|
20051
|
-
*/
|
|
20052
|
-
const KNOWN_OPERATIONS = /* @__PURE__ */ new Set([
|
|
20053
|
-
"equals",
|
|
20054
|
-
"not_equals",
|
|
20055
|
-
"contains",
|
|
20056
|
-
"not_contains",
|
|
20057
|
-
"less_than",
|
|
20058
|
-
"less_than_or_equal",
|
|
20059
|
-
"greater_than",
|
|
20060
|
-
"greater_than_or_equal",
|
|
20061
|
-
"array_contains"
|
|
20062
|
-
]);
|
|
20063
20403
|
/** Reject a value outside a field's closed set before any request is made. */
|
|
20064
|
-
const checked = (parsed) => {
|
|
20404
|
+
const checked$1 = (parsed) => {
|
|
20065
20405
|
const { values } = parsed.field;
|
|
20066
20406
|
if (values && !values.includes(parsed.value)) throw new ZitadelError("E_VALIDATION", `Invalid value "${parsed.value}" for filter field "${parsed.field.field}"`, {
|
|
20067
20407
|
hint: `Accepted values: ${values.join(", ")}.`,
|
|
@@ -20122,14 +20462,6 @@ const shapeOf = (schema) => {
|
|
|
20122
20462
|
const merged = Object.assign({}, ...options.map((option) => shapeOf(option) ?? {}));
|
|
20123
20463
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
20124
20464
|
};
|
|
20125
|
-
/**
|
|
20126
|
-
* The element schema of a list response's item array. `key` names the property
|
|
20127
|
-
* holding the array; without one the response schema is the array itself.
|
|
20128
|
-
*/
|
|
20129
|
-
const itemSchemaOf = (response, key) => {
|
|
20130
|
-
const array = key === void 0 ? unwrap(response) : unwrap(shapeOf(response)?.[key]);
|
|
20131
|
-
return unwrap(array?.def?.element) ?? void 0;
|
|
20132
|
-
};
|
|
20133
20465
|
/** Walk a record schema into the paths a caller may name. */
|
|
20134
20466
|
const fieldPaths = (schema, depth = 3) => {
|
|
20135
20467
|
const shape = shapeOf(schema);
|
|
@@ -20257,15 +20589,15 @@ var ListOperation = class extends ResourceCommand {
|
|
|
20257
20589
|
async execute({ flags }, _meta) {
|
|
20258
20590
|
const { topic, resource, spec, options } = this.definition;
|
|
20259
20591
|
const wire = wireOf(options.wire);
|
|
20260
|
-
const shape = fieldPaths(
|
|
20592
|
+
const shape = fieldPaths(spec.item);
|
|
20261
20593
|
const early = shape ? chosenColumns(flags.fields, resource.columns, [], shape) : void 0;
|
|
20262
|
-
const ctx = await this.context();
|
|
20594
|
+
const ctx = await this.context({ verbatim: true });
|
|
20263
20595
|
const paged = spec.paged !== false;
|
|
20264
20596
|
const paging = (token) => paged ? {
|
|
20265
20597
|
...typeof flags.limit === "number" && { [wire.limit]: flags.limit },
|
|
20266
20598
|
...token && { [wire.pageToken]: token }
|
|
20267
20599
|
} : {};
|
|
20268
|
-
const given = (Array.isArray(flags.filter) ? flags.filter : []).map((raw) => parseFilter(String(raw), spec.filters ?? []));
|
|
20600
|
+
const given = (Array.isArray(flags.filter) ? flags.filter : []).map((raw) => parseFilter(String(raw), spec.filters ?? [], options.operations));
|
|
20269
20601
|
const defaults = (spec.filters ?? []).flatMap((field) => field.default !== void 0 && !given.some((one) => one.field.field === field.field) ? [{
|
|
20270
20602
|
field,
|
|
20271
20603
|
operation: "equals",
|
|
@@ -20292,16 +20624,17 @@ var ListOperation = class extends ResourceCommand {
|
|
|
20292
20624
|
progress?.start(`Fetching ${topic}`);
|
|
20293
20625
|
let fetched = 0;
|
|
20294
20626
|
let drained = false;
|
|
20295
|
-
const
|
|
20296
|
-
|
|
20627
|
+
const first = typeof flags["page-token"] === "string" ? flags["page-token"] : void 0;
|
|
20628
|
+
const walk = pages(async (token) => {
|
|
20629
|
+
const page = await request(token ?? first);
|
|
20297
20630
|
fetched += 1;
|
|
20298
20631
|
progress?.message(`Fetching ${topic} — page ${fetched}`);
|
|
20299
20632
|
return page;
|
|
20300
|
-
}, {
|
|
20301
|
-
|
|
20633
|
+
}, { next: (response) => spec.next?.(response) || void 0 });
|
|
20634
|
+
const { items, next } = await collectPages(walk, {
|
|
20635
|
+
items: (response) => sanitizeResponse(spec.items(response)),
|
|
20302
20636
|
all,
|
|
20303
|
-
|
|
20304
|
-
token: paged && typeof flags["page-token"] === "string" ? flags["page-token"] : void 0
|
|
20637
|
+
next: (response) => sanitizeResponse(spec.next?.(response))
|
|
20305
20638
|
}).then((page) => {
|
|
20306
20639
|
drained = true;
|
|
20307
20640
|
return page;
|
|
@@ -20322,9 +20655,10 @@ var ListOperation = class extends ResourceCommand {
|
|
|
20322
20655
|
data: {
|
|
20323
20656
|
items,
|
|
20324
20657
|
count: items.length,
|
|
20325
|
-
[wire.nextPageToken]: next
|
|
20326
|
-
...nextCommands.length > 0 ? nextCommandsData(nextCommands) : {}
|
|
20658
|
+
[wire.nextPageToken]: next
|
|
20327
20659
|
},
|
|
20660
|
+
...nextCommands.length > 0 ? { next: { commands: nextCommands } } : {},
|
|
20661
|
+
machineReadable: plain,
|
|
20328
20662
|
pretty: plain ? renderRows(columns, items) : items.length === 0 ? `No ${topic} found.` : [
|
|
20329
20663
|
renderTable(columns, items),
|
|
20330
20664
|
"",
|
|
@@ -20394,10 +20728,11 @@ var GetOperation = class extends ResourceCommand {
|
|
|
20394
20728
|
data: item,
|
|
20395
20729
|
pretty: JSON.stringify(item, null, 2)
|
|
20396
20730
|
};
|
|
20731
|
+
const sections = flags.fields === void 0 ? spec.sections?.(item) ?? [] : [];
|
|
20397
20732
|
return {
|
|
20398
20733
|
status: "ok",
|
|
20399
20734
|
data: item,
|
|
20400
|
-
pretty: renderDetail(headingOf(resource.heading ?? resource.idField, item), fields, item)
|
|
20735
|
+
pretty: [renderDetail(headingOf(resource.heading ?? resource.idField, item), fields, item), ...sections].join("\n\n")
|
|
20401
20736
|
};
|
|
20402
20737
|
}
|
|
20403
20738
|
};
|
|
@@ -20577,15 +20912,12 @@ var WriteOperation = class extends ResourceCommand {
|
|
|
20577
20912
|
references: options.references
|
|
20578
20913
|
});
|
|
20579
20914
|
const result = await this.send(await this.context(), body, id);
|
|
20580
|
-
const resultId = isObject(result) ? result[resource.idField] : void 0;
|
|
20581
|
-
const nextCommands = typeof resultId === "string" && resource.get ? [this.suggest(`${topic} get ${resultId} --json`)] : [];
|
|
20582
20915
|
return {
|
|
20583
20916
|
status: "ok",
|
|
20584
|
-
data:
|
|
20585
|
-
|
|
20586
|
-
|
|
20587
|
-
|
|
20588
|
-
pretty: JSON.stringify(result, null, 2)
|
|
20917
|
+
data: result ?? {},
|
|
20918
|
+
...result && resource.get ? { next: { commands: [this.suggest(`${topic} get ${String(resource.idOf(result))} --json`)] } } : {},
|
|
20919
|
+
pretty: JSON.stringify(result, null, 2),
|
|
20920
|
+
machineReadable: true
|
|
20589
20921
|
};
|
|
20590
20922
|
}
|
|
20591
20923
|
};
|
|
@@ -20705,26 +21037,49 @@ const update = (schema, call, shape = {}) => ({
|
|
|
20705
21037
|
schema,
|
|
20706
21038
|
call
|
|
20707
21039
|
});
|
|
21040
|
+
const checked = Symbol("checked");
|
|
21041
|
+
const ranged = Symbol("ranged");
|
|
21042
|
+
/**
|
|
21043
|
+
* A field a `GET` list sends as one parameter per operation (a range, such as
|
|
21044
|
+
* `created_after` and `created_before`), keyed by operation: the operations it
|
|
21045
|
+
* offers are the keys, so each has the parameter it travels as.
|
|
21046
|
+
*/
|
|
21047
|
+
const range = (params) => ({
|
|
21048
|
+
operations: Object.keys(params),
|
|
21049
|
+
params,
|
|
21050
|
+
[ranged]: true
|
|
21051
|
+
});
|
|
20708
21052
|
const list = (spec) => spec;
|
|
20709
|
-
|
|
20710
|
-
const
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20714
|
-
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
const
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
|
|
20726
|
-
|
|
20727
|
-
|
|
21053
|
+
const get = (spec) => spec;
|
|
21054
|
+
const resource = (descriptor) => ({
|
|
21055
|
+
...descriptor,
|
|
21056
|
+
idOf: (written) => written[descriptor.idField],
|
|
21057
|
+
[checked]: true
|
|
21058
|
+
});
|
|
21059
|
+
const USER_OPERATIONS = Object.values(QueryUsersBodyFilterItemOperation);
|
|
21060
|
+
const TEAM_OPERATIONS = Object.values(QueryTeamsBodyFilterItemOperation);
|
|
21061
|
+
const SESSION_OPERATIONS = Object.values(QuerySessionsBodyFilterItemOperation);
|
|
21062
|
+
const GRANT_OPERATIONS = Object.values(QueryGrantsBodyFilterItemOperation);
|
|
21063
|
+
const IDP_OPERATIONS = Object.values(QueryIdpsBodyFilterItemOperation);
|
|
21064
|
+
const PROJECT_OPERATIONS = Object.values(QueryProjectsBodyFilterItemOperation);
|
|
21065
|
+
/** Every filter operation the API knows, so `--filter` tells one from a value. */
|
|
21066
|
+
const OPERATIONS = [.../* @__PURE__ */ new Set([
|
|
21067
|
+
...USER_OPERATIONS,
|
|
21068
|
+
...TEAM_OPERATIONS,
|
|
21069
|
+
...SESSION_OPERATIONS,
|
|
21070
|
+
...GRANT_OPERATIONS,
|
|
21071
|
+
...IDP_OPERATIONS,
|
|
21072
|
+
...PROJECT_OPERATIONS
|
|
21073
|
+
])];
|
|
21074
|
+
/** A release's pointers as an indented table, one pinned revision per row. */
|
|
21075
|
+
const releasePointers = (release) => {
|
|
21076
|
+
const { pointers } = release;
|
|
21077
|
+
return ["resources", ...renderTable([
|
|
21078
|
+
"kind",
|
|
21079
|
+
"handle",
|
|
21080
|
+
"revision_id"
|
|
21081
|
+
], pointers).split("\n").map((line) => ` ${line}`)].join("\n");
|
|
21082
|
+
};
|
|
20728
21083
|
/** The operations a policy can guard, as the generated publish body names them. */
|
|
20729
21084
|
const POLICY_OPERATIONS = PublishPolicyBody.shape.operation.options;
|
|
20730
21085
|
/**
|
|
@@ -20735,7 +21090,7 @@ const POLICY_OPERATIONS = PublishPolicyBody.shape.operation.options;
|
|
|
20735
21090
|
* truth. Adding a backend resource is adding an entry here.
|
|
20736
21091
|
*/
|
|
20737
21092
|
const RESOURCES = {
|
|
20738
|
-
users: {
|
|
21093
|
+
users: resource({
|
|
20739
21094
|
group: CommandGroups.usersAndTeams,
|
|
20740
21095
|
groupOrder: 1,
|
|
20741
21096
|
singular: "user",
|
|
@@ -20757,21 +21112,20 @@ const RESOURCES = {
|
|
|
20757
21112
|
"metadata.updated_at"
|
|
20758
21113
|
],
|
|
20759
21114
|
list: list({
|
|
20760
|
-
items: "users",
|
|
20761
21115
|
body: QueryUsersBody,
|
|
20762
|
-
|
|
21116
|
+
item: QueryUsersResponse.shape.users.element,
|
|
20763
21117
|
filters: [
|
|
20764
21118
|
{
|
|
20765
21119
|
field: "created_at",
|
|
20766
|
-
operations:
|
|
21120
|
+
operations: USER_OPERATIONS
|
|
20767
21121
|
},
|
|
20768
21122
|
{
|
|
20769
21123
|
field: "id",
|
|
20770
|
-
operations:
|
|
21124
|
+
operations: USER_OPERATIONS
|
|
20771
21125
|
},
|
|
20772
21126
|
{
|
|
20773
21127
|
field: "schema",
|
|
20774
|
-
operations:
|
|
21128
|
+
operations: USER_OPERATIONS
|
|
20775
21129
|
},
|
|
20776
21130
|
{
|
|
20777
21131
|
field: "schema_object_type",
|
|
@@ -20783,15 +21137,15 @@ const RESOURCES = {
|
|
|
20783
21137
|
},
|
|
20784
21138
|
{
|
|
20785
21139
|
field: "status",
|
|
20786
|
-
operations:
|
|
21140
|
+
operations: USER_OPERATIONS
|
|
20787
21141
|
},
|
|
20788
21142
|
{
|
|
20789
21143
|
field: "team_id",
|
|
20790
|
-
operations:
|
|
21144
|
+
operations: USER_OPERATIONS
|
|
20791
21145
|
},
|
|
20792
21146
|
{
|
|
20793
21147
|
field: "lifecycle_owner_team_id",
|
|
20794
|
-
operations:
|
|
21148
|
+
operations: USER_OPERATIONS
|
|
20795
21149
|
}
|
|
20796
21150
|
],
|
|
20797
21151
|
sorts: [
|
|
@@ -20801,17 +21155,19 @@ const RESOURCES = {
|
|
|
20801
21155
|
"status",
|
|
20802
21156
|
"lifecycle_owner_team_id"
|
|
20803
21157
|
],
|
|
20804
|
-
call: ({ client }, body) => client.queryUsers(body)
|
|
21158
|
+
call: ({ client }, body) => client.queryUsers(body),
|
|
21159
|
+
items: pagedFunctions.queryUsers.items,
|
|
21160
|
+
next: pagedFunctions.queryUsers.next
|
|
20805
21161
|
}),
|
|
20806
|
-
get: {
|
|
21162
|
+
get: get({
|
|
20807
21163
|
call: ({ client }, id) => client.getUserByID(id),
|
|
20808
21164
|
response: GetUserByIDResponse
|
|
20809
|
-
},
|
|
21165
|
+
}),
|
|
20810
21166
|
create: create(CreateUserBody, ({ client, projectId }, body) => client.createUser(body, { project_id: projectId })),
|
|
20811
21167
|
update: update(PatchUserByIDBody, ({ client }, id, body) => client.patchUserByID(id, body)),
|
|
20812
21168
|
delete: { call: ({ client }, id) => client.deleteUserByID(id) }
|
|
20813
|
-
},
|
|
20814
|
-
teams: {
|
|
21169
|
+
}),
|
|
21170
|
+
teams: resource({
|
|
20815
21171
|
group: CommandGroups.usersAndTeams,
|
|
20816
21172
|
groupOrder: 2,
|
|
20817
21173
|
singular: "team",
|
|
@@ -20830,21 +21186,20 @@ const RESOURCES = {
|
|
|
20830
21186
|
"updated_at"
|
|
20831
21187
|
],
|
|
20832
21188
|
list: list({
|
|
20833
|
-
items: "teams",
|
|
20834
21189
|
body: QueryTeamsBody,
|
|
20835
|
-
|
|
21190
|
+
item: QueryTeamsResponse.shape.teams.element,
|
|
20836
21191
|
filters: [
|
|
20837
21192
|
{
|
|
20838
21193
|
field: "created_at",
|
|
20839
|
-
operations:
|
|
21194
|
+
operations: TEAM_OPERATIONS
|
|
20840
21195
|
},
|
|
20841
21196
|
{
|
|
20842
21197
|
field: "name",
|
|
20843
|
-
operations:
|
|
21198
|
+
operations: TEAM_OPERATIONS
|
|
20844
21199
|
},
|
|
20845
21200
|
{
|
|
20846
21201
|
field: "status",
|
|
20847
|
-
operations:
|
|
21202
|
+
operations: TEAM_OPERATIONS
|
|
20848
21203
|
}
|
|
20849
21204
|
],
|
|
20850
21205
|
sorts: [
|
|
@@ -20852,20 +21207,22 @@ const RESOURCES = {
|
|
|
20852
21207
|
"name",
|
|
20853
21208
|
"status"
|
|
20854
21209
|
],
|
|
20855
|
-
call: ({ client, projectId }, body) => client.queryTeams(body, { project_id: projectId })
|
|
21210
|
+
call: ({ client, projectId }, body) => client.queryTeams(body, { project_id: projectId }),
|
|
21211
|
+
items: pagedFunctions.queryTeams.items,
|
|
21212
|
+
next: pagedFunctions.queryTeams.next
|
|
20856
21213
|
}),
|
|
20857
|
-
get: {
|
|
21214
|
+
get: get({
|
|
20858
21215
|
call: ({ client }, id) => client.getTeam(id),
|
|
20859
21216
|
response: GetTeamResponse
|
|
20860
|
-
},
|
|
21217
|
+
}),
|
|
20861
21218
|
create: create(CreateTeamBody, ({ client, projectId }, body) => client.createTeam(body, { project_id: projectId })),
|
|
20862
21219
|
update: update(UpdateTeamBody, ({ client }, id, body) => client.updateTeam(id, body)),
|
|
20863
21220
|
delete: {
|
|
20864
21221
|
verb: "deactivate",
|
|
20865
21222
|
call: ({ client }, id) => client.deleteTeam(id)
|
|
20866
21223
|
}
|
|
20867
|
-
},
|
|
20868
|
-
sessions: {
|
|
21224
|
+
}),
|
|
21225
|
+
sessions: resource({
|
|
20869
21226
|
group: CommandGroups.authentication,
|
|
20870
21227
|
singular: "session",
|
|
20871
21228
|
idField: "session_id",
|
|
@@ -20885,40 +21242,41 @@ const RESOURCES = {
|
|
|
20885
21242
|
"expires_at"
|
|
20886
21243
|
],
|
|
20887
21244
|
list: list({
|
|
20888
|
-
items: "sessions",
|
|
20889
21245
|
body: QuerySessionsBody,
|
|
20890
|
-
|
|
21246
|
+
item: QuerySessionsResponse.shape.sessions.element,
|
|
20891
21247
|
filters: [
|
|
20892
21248
|
{
|
|
20893
21249
|
field: "created_at",
|
|
20894
|
-
operations:
|
|
21250
|
+
operations: SESSION_OPERATIONS
|
|
20895
21251
|
},
|
|
20896
21252
|
{
|
|
20897
21253
|
field: "user_id",
|
|
20898
|
-
operations:
|
|
21254
|
+
operations: SESSION_OPERATIONS
|
|
20899
21255
|
},
|
|
20900
21256
|
{
|
|
20901
21257
|
field: "state",
|
|
20902
|
-
operations:
|
|
21258
|
+
operations: SESSION_OPERATIONS
|
|
20903
21259
|
},
|
|
20904
21260
|
{
|
|
20905
21261
|
field: "lifecycle_owner_team_id",
|
|
20906
|
-
operations:
|
|
21262
|
+
operations: SESSION_OPERATIONS
|
|
20907
21263
|
}
|
|
20908
21264
|
],
|
|
20909
21265
|
sorts: ["created_at", "user_id"],
|
|
20910
|
-
call: ({ client, projectId }, body) => client.querySessions(body, { project_id: projectId })
|
|
21266
|
+
call: ({ client, projectId }, body) => client.querySessions(body, { project_id: projectId }),
|
|
21267
|
+
items: pagedFunctions.querySessions.items,
|
|
21268
|
+
next: pagedFunctions.querySessions.next
|
|
20911
21269
|
}),
|
|
20912
|
-
get: {
|
|
21270
|
+
get: get({
|
|
20913
21271
|
call: ({ client }, id) => client.getSession(id),
|
|
20914
21272
|
response: GetSessionResponse
|
|
20915
|
-
},
|
|
21273
|
+
}),
|
|
20916
21274
|
delete: {
|
|
20917
21275
|
verb: "revoke",
|
|
20918
21276
|
call: ({ client }, id) => client.revokeSession(id)
|
|
20919
21277
|
}
|
|
20920
|
-
},
|
|
20921
|
-
events: {
|
|
21278
|
+
}),
|
|
21279
|
+
events: resource({
|
|
20922
21280
|
group: CommandGroups.diagnostics,
|
|
20923
21281
|
singular: "event",
|
|
20924
21282
|
idField: "id",
|
|
@@ -20940,14 +21298,13 @@ const RESOURCES = {
|
|
|
20940
21298
|
"request_id",
|
|
20941
21299
|
"occurred_at"
|
|
20942
21300
|
],
|
|
20943
|
-
list: {
|
|
20944
|
-
|
|
20945
|
-
response: ListEventsResponse,
|
|
21301
|
+
list: list({
|
|
21302
|
+
item: ListEventsResponse.shape.data.element,
|
|
20946
21303
|
filters: [
|
|
20947
21304
|
{
|
|
20948
21305
|
field: "category",
|
|
20949
21306
|
operations: ["equals"],
|
|
20950
|
-
values:
|
|
21307
|
+
values: Object.values(ListEventsCategoryItem),
|
|
20951
21308
|
combine: "or"
|
|
20952
21309
|
},
|
|
20953
21310
|
{
|
|
@@ -20985,11 +21342,10 @@ const RESOURCES = {
|
|
|
20985
21342
|
},
|
|
20986
21343
|
{
|
|
20987
21344
|
field: "created_at",
|
|
20988
|
-
|
|
20989
|
-
params: {
|
|
21345
|
+
...range({
|
|
20990
21346
|
greater_than_or_equal: "created_after",
|
|
20991
21347
|
less_than: "created_before"
|
|
20992
|
-
}
|
|
21348
|
+
})
|
|
20993
21349
|
}
|
|
20994
21350
|
],
|
|
20995
21351
|
sorts: ["occurred_at"],
|
|
@@ -20997,11 +21353,13 @@ const RESOURCES = {
|
|
|
20997
21353
|
call: ({ client, projectId }, params) => client.listEvents({
|
|
20998
21354
|
project_id: projectId,
|
|
20999
21355
|
...params
|
|
21000
|
-
})
|
|
21001
|
-
|
|
21002
|
-
|
|
21003
|
-
|
|
21004
|
-
|
|
21356
|
+
}),
|
|
21357
|
+
items: pagedFunctions.listEvents.items,
|
|
21358
|
+
next: pagedFunctions.listEvents.next
|
|
21359
|
+
}),
|
|
21360
|
+
get: get({ call: ({ client, projectId }, id) => client.getEvent(id, { project_id: projectId }) })
|
|
21361
|
+
}),
|
|
21362
|
+
grants: resource({
|
|
21005
21363
|
group: CommandGroups.usersAndTeams,
|
|
21006
21364
|
groupOrder: 3,
|
|
21007
21365
|
singular: "grant",
|
|
@@ -21024,29 +21382,28 @@ const RESOURCES = {
|
|
|
21024
21382
|
"expires_at"
|
|
21025
21383
|
],
|
|
21026
21384
|
list: list({
|
|
21027
|
-
items: "grants",
|
|
21028
21385
|
body: QueryGrantsBody,
|
|
21029
|
-
|
|
21386
|
+
item: QueryGrantsResponse.shape.grants.element,
|
|
21030
21387
|
filters: [
|
|
21031
21388
|
{
|
|
21032
21389
|
field: "created_at",
|
|
21033
|
-
operations:
|
|
21390
|
+
operations: GRANT_OPERATIONS
|
|
21034
21391
|
},
|
|
21035
21392
|
{
|
|
21036
21393
|
field: "user_id",
|
|
21037
|
-
operations:
|
|
21394
|
+
operations: GRANT_OPERATIONS
|
|
21038
21395
|
},
|
|
21039
21396
|
{
|
|
21040
21397
|
field: "team_id",
|
|
21041
|
-
operations:
|
|
21398
|
+
operations: GRANT_OPERATIONS
|
|
21042
21399
|
},
|
|
21043
21400
|
{
|
|
21044
21401
|
field: "relation",
|
|
21045
|
-
operations:
|
|
21402
|
+
operations: GRANT_OPERATIONS
|
|
21046
21403
|
},
|
|
21047
21404
|
{
|
|
21048
21405
|
field: "expires_at",
|
|
21049
|
-
operations:
|
|
21406
|
+
operations: GRANT_OPERATIONS
|
|
21050
21407
|
}
|
|
21051
21408
|
],
|
|
21052
21409
|
sorts: [
|
|
@@ -21054,16 +21411,18 @@ const RESOURCES = {
|
|
|
21054
21411
|
"expires_at",
|
|
21055
21412
|
"id"
|
|
21056
21413
|
],
|
|
21057
|
-
call: ({ client, projectId }, body) => client.queryGrants(body, { project_id: projectId })
|
|
21414
|
+
call: ({ client, projectId }, body) => client.queryGrants(body, { project_id: projectId }),
|
|
21415
|
+
items: pagedFunctions.queryGrants.items,
|
|
21416
|
+
next: pagedFunctions.queryGrants.next
|
|
21058
21417
|
}),
|
|
21059
|
-
get: {
|
|
21418
|
+
get: get({
|
|
21060
21419
|
call: ({ client, projectId }, id) => client.getGrant(id, { project_id: projectId }),
|
|
21061
21420
|
response: GetGrantResponse
|
|
21062
|
-
},
|
|
21421
|
+
}),
|
|
21063
21422
|
create: create(CreateGrantBody, ({ client, projectId }, body) => client.createGrant(body, { project_id: projectId }), { example: "--relation viewer --user-identifier alice@acme.com" }),
|
|
21064
21423
|
delete: { call: ({ client, projectId }, id) => client.deleteGrant(id, { project_id: projectId }) }
|
|
21065
|
-
},
|
|
21066
|
-
idps: {
|
|
21424
|
+
}),
|
|
21425
|
+
idps: resource({
|
|
21067
21426
|
group: CommandGroups.authentication,
|
|
21068
21427
|
singular: "identity provider connection",
|
|
21069
21428
|
idField: "id",
|
|
@@ -21083,29 +21442,30 @@ const RESOURCES = {
|
|
|
21083
21442
|
"updated_at"
|
|
21084
21443
|
],
|
|
21085
21444
|
list: list({
|
|
21086
|
-
items: "idps",
|
|
21087
21445
|
body: QueryIdpsBody,
|
|
21088
|
-
|
|
21446
|
+
item: QueryIdpsResponse.shape.idps.element,
|
|
21089
21447
|
filters: [{
|
|
21090
21448
|
field: "slug",
|
|
21091
|
-
operations:
|
|
21449
|
+
operations: IDP_OPERATIONS
|
|
21092
21450
|
}, {
|
|
21093
21451
|
field: "created_at",
|
|
21094
|
-
operations:
|
|
21452
|
+
operations: IDP_OPERATIONS
|
|
21095
21453
|
}],
|
|
21096
21454
|
sorts: ["slug", "created_at"],
|
|
21097
|
-
call: ({ client, projectId }, body) => client.queryIdps(body, { project_id: projectId })
|
|
21455
|
+
call: ({ client, projectId }, body) => client.queryIdps(body, { project_id: projectId }),
|
|
21456
|
+
items: pagedFunctions.queryIdps.items,
|
|
21457
|
+
next: pagedFunctions.queryIdps.next
|
|
21098
21458
|
}),
|
|
21099
|
-
get: {
|
|
21459
|
+
get: get({
|
|
21100
21460
|
call: ({ client, projectId }, id) => client.getIdpById(id, { project_id: projectId }),
|
|
21101
21461
|
response: GetIdpByIdResponse
|
|
21102
|
-
},
|
|
21462
|
+
}),
|
|
21103
21463
|
create: create(CreateIdpBody, ({ client, projectId }, body) => client.createIdp(body, { project_id: projectId }), {
|
|
21104
21464
|
root: "idp",
|
|
21105
21465
|
example: "--slug acme-sso --protocol oidc --display-name 'Acme SSO' --oidc-issuer https://sso.acme.example --oidc-client-id <client_id> --oidc-client-secret '${{ ACME_SSO_CLIENT_SECRET }}' --oidc-scopes openid --oidc-scopes profile --oidc-scopes email"
|
|
21106
21466
|
})
|
|
21107
|
-
},
|
|
21108
|
-
projects: {
|
|
21467
|
+
}),
|
|
21468
|
+
projects: resource({
|
|
21109
21469
|
group: CommandGroups.configuration,
|
|
21110
21470
|
singular: "project",
|
|
21111
21471
|
idField: "id",
|
|
@@ -21122,23 +21482,24 @@ const RESOURCES = {
|
|
|
21122
21482
|
"updated_at"
|
|
21123
21483
|
],
|
|
21124
21484
|
list: list({
|
|
21125
|
-
items: "projects",
|
|
21126
21485
|
body: QueryProjectsBody,
|
|
21127
|
-
|
|
21486
|
+
item: QueryProjectsResponse.shape.projects.element,
|
|
21128
21487
|
filters: [{
|
|
21129
21488
|
field: "created_at",
|
|
21130
|
-
operations:
|
|
21489
|
+
operations: PROJECT_OPERATIONS
|
|
21131
21490
|
}],
|
|
21132
21491
|
sorts: ["created_at"],
|
|
21133
|
-
call: ({ client }, body) => client.queryProjects(body)
|
|
21492
|
+
call: ({ client }, body) => client.queryProjects(body),
|
|
21493
|
+
items: pagedFunctions.queryProjects.items,
|
|
21494
|
+
next: pagedFunctions.queryProjects.next
|
|
21134
21495
|
}),
|
|
21135
|
-
get: {
|
|
21496
|
+
get: get({
|
|
21136
21497
|
call: ({ client }, id) => client.getProject(id),
|
|
21137
21498
|
response: GetProjectResponse
|
|
21138
|
-
},
|
|
21499
|
+
}),
|
|
21139
21500
|
update: update(PatchProjectBody, ({ client }, id, body) => client.patchProject(id, body))
|
|
21140
|
-
},
|
|
21141
|
-
schemas: {
|
|
21501
|
+
}),
|
|
21502
|
+
schemas: resource({
|
|
21142
21503
|
group: CommandGroups.configuration,
|
|
21143
21504
|
singular: "schema",
|
|
21144
21505
|
idField: "id",
|
|
@@ -21157,9 +21518,8 @@ const RESOURCES = {
|
|
|
21157
21518
|
"schema.kind",
|
|
21158
21519
|
"metadata.created_at"
|
|
21159
21520
|
],
|
|
21160
|
-
list: {
|
|
21161
|
-
|
|
21162
|
-
response: ListSchemasResponse,
|
|
21521
|
+
list: list({
|
|
21522
|
+
item: ListSchemasResponse.shape.schemas.element,
|
|
21163
21523
|
filters: [
|
|
21164
21524
|
{
|
|
21165
21525
|
field: "object_type",
|
|
@@ -21172,16 +21532,18 @@ const RESOURCES = {
|
|
|
21172
21532
|
{
|
|
21173
21533
|
field: "revisions",
|
|
21174
21534
|
operations: ["equals"],
|
|
21175
|
-
values:
|
|
21176
|
-
default:
|
|
21535
|
+
values: Object.values(ListSchemasRevisions),
|
|
21536
|
+
default: ListSchemasRevisions.latest
|
|
21177
21537
|
}
|
|
21178
21538
|
],
|
|
21179
21539
|
call: ({ client, projectId }, params) => client.listSchemas({
|
|
21180
21540
|
...params,
|
|
21181
21541
|
project_id: projectId
|
|
21182
|
-
})
|
|
21183
|
-
|
|
21184
|
-
|
|
21542
|
+
}),
|
|
21543
|
+
items: pagedFunctions.listSchemas.items,
|
|
21544
|
+
next: pagedFunctions.listSchemas.next
|
|
21545
|
+
}),
|
|
21546
|
+
get: get({
|
|
21185
21547
|
response: GetSchemaByIdResponse,
|
|
21186
21548
|
call: async ({ client, projectId }, ref) => {
|
|
21187
21549
|
try {
|
|
@@ -21198,9 +21560,9 @@ const RESOURCES = {
|
|
|
21198
21560
|
if (!current) throw new ZitadelError("E_NOT_FOUND", `No schema named "${ref}"`, { hint: "Run `schemas list` to see the object types this project has." });
|
|
21199
21561
|
return current;
|
|
21200
21562
|
}
|
|
21201
|
-
}
|
|
21202
|
-
},
|
|
21203
|
-
release: {
|
|
21563
|
+
})
|
|
21564
|
+
}),
|
|
21565
|
+
release: resource({
|
|
21204
21566
|
group: CommandGroups.configuration,
|
|
21205
21567
|
singular: "release",
|
|
21206
21568
|
idField: "id",
|
|
@@ -21208,7 +21570,8 @@ const RESOURCES = {
|
|
|
21208
21570
|
"id",
|
|
21209
21571
|
"metadata.message",
|
|
21210
21572
|
"metadata.git_sha",
|
|
21211
|
-
"revoked_at"
|
|
21573
|
+
"revoked_at",
|
|
21574
|
+
"metadata.created_at"
|
|
21212
21575
|
],
|
|
21213
21576
|
heading: "id",
|
|
21214
21577
|
detail: [
|
|
@@ -21220,20 +21583,22 @@ const RESOURCES = {
|
|
|
21220
21583
|
"metadata.git_sha",
|
|
21221
21584
|
"metadata.git_dirty"
|
|
21222
21585
|
],
|
|
21223
|
-
list: {
|
|
21224
|
-
|
|
21225
|
-
response: ListReleasesResponse,
|
|
21586
|
+
list: list({
|
|
21587
|
+
item: ListReleasesResponse.shape.releases.element,
|
|
21226
21588
|
call: ({ client, projectId }, params) => client.listReleases({
|
|
21227
21589
|
...params,
|
|
21228
21590
|
project_id: projectId
|
|
21229
|
-
})
|
|
21230
|
-
|
|
21231
|
-
|
|
21591
|
+
}),
|
|
21592
|
+
items: pagedFunctions.listReleases.items,
|
|
21593
|
+
next: pagedFunctions.listReleases.next
|
|
21594
|
+
}),
|
|
21595
|
+
get: get({
|
|
21232
21596
|
call: ({ client, projectId }, id) => client.getReleaseById(id, { project_id: projectId }),
|
|
21233
|
-
response: GetReleaseByIdResponse
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21597
|
+
response: GetReleaseByIdResponse,
|
|
21598
|
+
sections: (release) => [releasePointers(release)]
|
|
21599
|
+
})
|
|
21600
|
+
}),
|
|
21601
|
+
"flow-definitions": resource({
|
|
21237
21602
|
group: CommandGroups.configuration,
|
|
21238
21603
|
singular: "flow definition",
|
|
21239
21604
|
idField: "id",
|
|
@@ -21253,9 +21618,8 @@ const RESOURCES = {
|
|
|
21253
21618
|
"created_at",
|
|
21254
21619
|
"updated_at"
|
|
21255
21620
|
],
|
|
21256
|
-
list: {
|
|
21257
|
-
|
|
21258
|
-
response: ListFlowDefinitionsResponse,
|
|
21621
|
+
list: list({
|
|
21622
|
+
item: ListFlowDefinitionsResponse.shape.flow_definitions.element,
|
|
21259
21623
|
filters: [
|
|
21260
21624
|
{
|
|
21261
21625
|
field: "name",
|
|
@@ -21268,16 +21632,18 @@ const RESOURCES = {
|
|
|
21268
21632
|
{
|
|
21269
21633
|
field: "revisions",
|
|
21270
21634
|
operations: ["equals"],
|
|
21271
|
-
values:
|
|
21272
|
-
default:
|
|
21635
|
+
values: Object.values(ListFlowDefinitionsRevisions),
|
|
21636
|
+
default: ListFlowDefinitionsRevisions.latest
|
|
21273
21637
|
}
|
|
21274
21638
|
],
|
|
21275
21639
|
call: ({ client, projectId }, params) => client.listFlowDefinitions({
|
|
21276
21640
|
...params,
|
|
21277
21641
|
project_id: projectId
|
|
21278
|
-
})
|
|
21279
|
-
|
|
21280
|
-
|
|
21642
|
+
}),
|
|
21643
|
+
items: pagedFunctions.listFlowDefinitions.items,
|
|
21644
|
+
next: pagedFunctions.listFlowDefinitions.next
|
|
21645
|
+
}),
|
|
21646
|
+
get: get({
|
|
21281
21647
|
response: GetFlowDefinitionResponse,
|
|
21282
21648
|
call: async ({ client, projectId }, ref) => {
|
|
21283
21649
|
if (ref.startsWith("flowdef_")) return client.getFlowDefinition(ref);
|
|
@@ -21289,26 +21655,27 @@ const RESOURCES = {
|
|
|
21289
21655
|
if (!current) throw new ZitadelError("E_NOT_FOUND", `No flow definition named "${ref}"`, { hint: "Run `flow-definitions list` to see the flows this project has." });
|
|
21290
21656
|
return current;
|
|
21291
21657
|
}
|
|
21292
|
-
}
|
|
21293
|
-
},
|
|
21294
|
-
branding: {
|
|
21658
|
+
})
|
|
21659
|
+
}),
|
|
21660
|
+
branding: resource({
|
|
21295
21661
|
group: CommandGroups.configuration,
|
|
21296
21662
|
singular: "branding revision",
|
|
21297
21663
|
idField: "id",
|
|
21298
21664
|
columns: ["id", "created_at"],
|
|
21299
21665
|
heading: "id",
|
|
21300
21666
|
detail: ["id", "created_at"],
|
|
21301
|
-
list: {
|
|
21667
|
+
list: list({
|
|
21302
21668
|
paged: false,
|
|
21303
|
-
|
|
21304
|
-
call: ({ client, projectId }) => client.listBranding({ project_id: projectId })
|
|
21305
|
-
|
|
21306
|
-
|
|
21669
|
+
item: ListBrandingResponse.element,
|
|
21670
|
+
call: ({ client, projectId }) => client.listBranding({ project_id: projectId }),
|
|
21671
|
+
items: (page) => page
|
|
21672
|
+
}),
|
|
21673
|
+
get: get({
|
|
21307
21674
|
call: ({ client }, id) => client.getBrandingById(id),
|
|
21308
21675
|
response: GetBrandingByIdResponse
|
|
21309
|
-
}
|
|
21310
|
-
},
|
|
21311
|
-
policies: {
|
|
21676
|
+
})
|
|
21677
|
+
}),
|
|
21678
|
+
policies: resource({
|
|
21312
21679
|
group: CommandGroups.configuration,
|
|
21313
21680
|
singular: "policy",
|
|
21314
21681
|
idField: "id",
|
|
@@ -21330,9 +21697,8 @@ const RESOURCES = {
|
|
|
21330
21697
|
"created_at",
|
|
21331
21698
|
"updated_at"
|
|
21332
21699
|
],
|
|
21333
|
-
list: {
|
|
21334
|
-
|
|
21335
|
-
response: ListPoliciesResponse,
|
|
21700
|
+
list: list({
|
|
21701
|
+
item: ListPoliciesResponse.shape.policies.element,
|
|
21336
21702
|
filters: [{
|
|
21337
21703
|
field: "operation",
|
|
21338
21704
|
operations: ["equals"]
|
|
@@ -21340,9 +21706,11 @@ const RESOURCES = {
|
|
|
21340
21706
|
call: ({ client, projectId }, params) => client.listPolicies({
|
|
21341
21707
|
...params,
|
|
21342
21708
|
project_id: projectId
|
|
21343
|
-
})
|
|
21344
|
-
|
|
21345
|
-
|
|
21709
|
+
}),
|
|
21710
|
+
items: pagedFunctions.listPolicies.items,
|
|
21711
|
+
next: pagedFunctions.listPolicies.next
|
|
21712
|
+
}),
|
|
21713
|
+
get: get({
|
|
21346
21714
|
response: GetPolicyResponse,
|
|
21347
21715
|
call: async ({ client, projectId }, ref) => {
|
|
21348
21716
|
if (ref.startsWith("pol_")) return client.getPolicy(ref, { project_id: projectId });
|
|
@@ -21357,8 +21725,8 @@ const RESOURCES = {
|
|
|
21357
21725
|
if (!current) throw notFound();
|
|
21358
21726
|
return current;
|
|
21359
21727
|
}
|
|
21360
|
-
}
|
|
21361
|
-
}
|
|
21728
|
+
})
|
|
21729
|
+
})
|
|
21362
21730
|
};
|
|
21363
21731
|
/**
|
|
21364
21732
|
* The `<resource> <verb>` commands, ready for the explicit command table. They
|
|
@@ -21366,7 +21734,7 @@ const RESOURCES = {
|
|
|
21366
21734
|
* `EnvironmentCommand`), so they take `--env` and `--env-file` too.
|
|
21367
21735
|
*/
|
|
21368
21736
|
const RESOURCE_COMMANDS = buildResourceCommands(RESOURCES, {
|
|
21369
|
-
operations:
|
|
21737
|
+
operations: OPERATIONS,
|
|
21370
21738
|
references: {
|
|
21371
21739
|
matches: isVariableReference,
|
|
21372
21740
|
example: "'${{ NAME }}'"
|
|
@@ -21437,12 +21805,12 @@ var ResourcesList = class ResourcesList extends BaseCommand {
|
|
|
21437
21805
|
*/
|
|
21438
21806
|
async function claimProjectAsAdmin(input) {
|
|
21439
21807
|
const { serverUrl, projectId, projectSecret, admin } = input;
|
|
21440
|
-
const { challenge_id } = await createZitadelClient({
|
|
21808
|
+
const { challenge_id } = await createZitadelClient$1({
|
|
21441
21809
|
baseUrl: serverUrl,
|
|
21442
21810
|
token: projectSecret
|
|
21443
21811
|
}).initClaim(projectId, localAdminRequest(serverUrl));
|
|
21444
21812
|
const cookie = await adminSessionCookie(serverUrl, admin);
|
|
21445
|
-
const session = createZitadelClient({ baseUrl: serverUrl });
|
|
21813
|
+
const session = createZitadelClient$1({ baseUrl: serverUrl });
|
|
21446
21814
|
const { csrf_token } = await session.getMySessionCsrfToken(void 0, localAdminRequest(serverUrl, { cookie }));
|
|
21447
21815
|
const { team_id, claimed_at } = await session.completeClaim(projectId, { challenge_id }, localAdminRequest(serverUrl, {
|
|
21448
21816
|
cookie,
|
|
@@ -21507,7 +21875,10 @@ async function materializeSetupResources(opts) {
|
|
|
21507
21875
|
const filesWritten = [];
|
|
21508
21876
|
const preset = opts.preset ?? DEFAULT_SETUP_PRESET;
|
|
21509
21877
|
const useCase = opts.useCase ?? DEFAULT_SETUP_USE_CASE;
|
|
21510
|
-
const schemaTemplate =
|
|
21878
|
+
const schemaTemplate = getDefaultHumanUserSchemaBody({
|
|
21879
|
+
preset,
|
|
21880
|
+
useCase
|
|
21881
|
+
});
|
|
21511
21882
|
const planned = planConnections(schemaTemplate, opts.sso);
|
|
21512
21883
|
await refuseResourceConflicts(opts.cwd, opts.force, planned);
|
|
21513
21884
|
const slugs = planned.map((entry) => entry.slug);
|
|
@@ -21516,7 +21887,7 @@ async function materializeSetupResources(opts) {
|
|
|
21516
21887
|
await mkdir(join(opts.cwd, IDPS_DIR), { recursive: true });
|
|
21517
21888
|
if (await writeResourceFile(opts.cwd, connectionPath, connection, false, connectionExists(selection))) filesWritten.push(join(opts.cwd, connectionPath));
|
|
21518
21889
|
await updateState(opts.cwd, connectionPath, {
|
|
21519
|
-
id:
|
|
21890
|
+
id: created.id,
|
|
21520
21891
|
hash: hashForState({ normalize: normalizeIdpBody }, connection)
|
|
21521
21892
|
});
|
|
21522
21893
|
}
|
|
@@ -21524,7 +21895,7 @@ async function materializeSetupResources(opts) {
|
|
|
21524
21895
|
if (await writeResourceFile(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH, schemaBody, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH));
|
|
21525
21896
|
const schema = await opts.client.createSchema(schemaBody, { project_id: opts.projectId });
|
|
21526
21897
|
await updateState(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH, {
|
|
21527
|
-
id:
|
|
21898
|
+
id: schema.id,
|
|
21528
21899
|
hash: hashForState({ normalize: normalizeSchemaBody }, schemaBody)
|
|
21529
21900
|
});
|
|
21530
21901
|
const flowTemplate = getDefaultLoginFlow({
|
|
@@ -21539,7 +21910,7 @@ async function materializeSetupResources(opts) {
|
|
|
21539
21910
|
flow_definition: flowBody
|
|
21540
21911
|
});
|
|
21541
21912
|
await updateState(opts.cwd, DEFAULT_FLOW_CONFIG_PATH, {
|
|
21542
|
-
id:
|
|
21913
|
+
id: flow.id,
|
|
21543
21914
|
hash: hashForState({ normalize: normalizeFlowBody }, flowBody),
|
|
21544
21915
|
name: flowBody.name,
|
|
21545
21916
|
status: flowBody.status
|
|
@@ -21557,7 +21928,7 @@ async function materializeSetupResources(opts) {
|
|
|
21557
21928
|
await mkdir(join(opts.cwd, POLICIES_DIR), { recursive: true });
|
|
21558
21929
|
if ((kept === void 0 || policy !== kept) && await writeResourceFile(opts.cwd, PASSWORD_POLICY_PATH, policy, opts.force)) filesWritten.push(join(opts.cwd, PASSWORD_POLICY_PATH));
|
|
21559
21930
|
await updateState(opts.cwd, PASSWORD_POLICY_PATH, {
|
|
21560
|
-
id:
|
|
21931
|
+
id: published.id,
|
|
21561
21932
|
hash: hashForState({ normalize: (body) => toPolicyWireBody(body) }, policy)
|
|
21562
21933
|
});
|
|
21563
21934
|
}
|
|
@@ -21583,16 +21954,11 @@ async function materializeSetupResources(opts) {
|
|
|
21583
21954
|
* only the keys that file is missing.
|
|
21584
21955
|
*/
|
|
21585
21956
|
async function assertNoSetupResourceConflicts(opts) {
|
|
21586
|
-
const schemaTemplate =
|
|
21587
|
-
|
|
21588
|
-
|
|
21589
|
-
/** The default human user schema for a preset and use case, without the template's `$id`. */
|
|
21590
|
-
function defaultSchemaTemplate(preset, useCase) {
|
|
21591
|
-
const { $id: _templateId, ...schemaTemplate } = getDefaultHumanUserSchema({
|
|
21592
|
-
preset,
|
|
21593
|
-
useCase
|
|
21957
|
+
const schemaTemplate = getDefaultHumanUserSchemaBody({
|
|
21958
|
+
preset: opts.preset ?? DEFAULT_SETUP_PRESET,
|
|
21959
|
+
useCase: opts.useCase ?? DEFAULT_SETUP_USE_CASE
|
|
21594
21960
|
});
|
|
21595
|
-
|
|
21961
|
+
await refuseResourceConflicts(opts.cwd, opts.force, planConnections(schemaTemplate, opts.sso));
|
|
21596
21962
|
}
|
|
21597
21963
|
/** The connection each chosen provider gets, and the file it is written to. */
|
|
21598
21964
|
function planConnections(schemaTemplate, sso) {
|
|
@@ -21738,10 +22104,6 @@ function resourceExists(relPath, onExists) {
|
|
|
21738
22104
|
nextCommands: onExists?.nextCommands
|
|
21739
22105
|
});
|
|
21740
22106
|
}
|
|
21741
|
-
function requiredString(value, label) {
|
|
21742
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
21743
|
-
throw new ZitadelError("E_VALIDATION", `Missing ${label} in server response.`);
|
|
21744
|
-
}
|
|
21745
22107
|
function isErrno(error, code) {
|
|
21746
22108
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
21747
22109
|
}
|
|
@@ -21862,8 +22224,31 @@ function errorShape(error) {
|
|
|
21862
22224
|
return { message: String(error) };
|
|
21863
22225
|
}
|
|
21864
22226
|
//#endregion
|
|
22227
|
+
//#region src/lib/secret-paths.ts
|
|
22228
|
+
/**
|
|
22229
|
+
* Whether `path` must never be committed: a `.zitadel/secret`, anything under `.zitadel/local/`, or an `.env*` file other than `.env.example`.
|
|
22230
|
+
*
|
|
22231
|
+
* Matched without regard to case, since `.ENV` is the same file as `.env` on
|
|
22232
|
+
* macOS and Windows.
|
|
22233
|
+
*/
|
|
22234
|
+
function isSecretPath(path) {
|
|
22235
|
+
const lower = path.toLowerCase();
|
|
22236
|
+
const name = lower.split("/").pop() ?? lower;
|
|
22237
|
+
if (lower === ".zitadel/secret" || lower.endsWith("/.zitadel/secret")) return true;
|
|
22238
|
+
if (isLocalRuntimePath(lower)) return true;
|
|
22239
|
+
return name.startsWith(".env") && name !== ".env.example";
|
|
22240
|
+
}
|
|
22241
|
+
/**
|
|
22242
|
+
* Whether `path` is under `.zitadel/local/`, at any depth, where the local server keeps its admin credential and data.
|
|
22243
|
+
*
|
|
22244
|
+
* Matched with its case, since it names the directory `start` writes; on a
|
|
22245
|
+
* case-sensitive filesystem `.Zitadel/Local/` is someone else's.
|
|
22246
|
+
*/
|
|
22247
|
+
function isLocalRuntimePath(path) {
|
|
22248
|
+
return /(^|\/)\.zitadel\/local(\/|$)/.test(path);
|
|
22249
|
+
}
|
|
22250
|
+
//#endregion
|
|
21865
22251
|
//#region src/commands/setup/initial-commit.ts
|
|
21866
|
-
const run = promisify(execFile);
|
|
21867
22252
|
/**
|
|
21868
22253
|
* The subject of the commit setup makes. Conventional-commit style, like every
|
|
21869
22254
|
* other commit this repository's tooling writes, and with no trailer of any
|
|
@@ -21871,19 +22256,6 @@ const run = promisify(execFile);
|
|
|
21871
22256
|
*/
|
|
21872
22257
|
const INITIAL_COMMIT_SUBJECT = "chore: scaffold the project with zitadel setup";
|
|
21873
22258
|
/**
|
|
21874
|
-
* Paths that must never be committed, whatever `.gitignore` happens to say.
|
|
21875
|
-
*
|
|
21876
|
-
* `.zitadel/local/` holds the local admin credential and the server's data
|
|
21877
|
-
* directory. Only the `.gitignore` line `start` appends keeps it out, so a
|
|
21878
|
-
* project missing that line would otherwise commit the credential.
|
|
21879
|
-
*/
|
|
21880
|
-
function isSecretPath(path) {
|
|
21881
|
-
const name = path.split("/").pop() ?? path;
|
|
21882
|
-
if (path === ".zitadel/secret" || path.endsWith("/.zitadel/secret")) return true;
|
|
21883
|
-
if (isLocalRuntimePath(path)) return true;
|
|
21884
|
-
return name.startsWith(".env") && name !== ".env.example";
|
|
21885
|
-
}
|
|
21886
|
-
/**
|
|
21887
22259
|
* Paths the CLI writes itself, which therefore do not count as the
|
|
21888
22260
|
* developer's uncommitted work. Matched against a path already made relative
|
|
21889
22261
|
* to the project directory, never the repository root: in a monorepo the two
|
|
@@ -21900,156 +22272,16 @@ function isCliOwned(path) {
|
|
|
21900
22272
|
return path === ".gitignore" || path.endsWith("/.gitignore") || isLocalRuntimePath(path);
|
|
21901
22273
|
}
|
|
21902
22274
|
/**
|
|
21903
|
-
*
|
|
21904
|
-
*
|
|
21905
|
-
|
|
21906
|
-
|
|
21907
|
-
return /(^|\/)\.zitadel\/local(\/|$)/.test(path);
|
|
21908
|
-
}
|
|
21909
|
-
/**
|
|
21910
|
-
* Run git in `cwd`, returning its stdout.
|
|
21911
|
-
*
|
|
21912
|
-
* `core.quotePath=false` stops git C-quoting any path holding a non-ASCII byte
|
|
21913
|
-
* or a space (`"caf\303\251.txt"`). A quoted path is not the path: it matches
|
|
21914
|
-
* no pathspec and no suffix test, so a secret under such a name would be
|
|
21915
|
-
* neither recognised nor unstaged.
|
|
21916
|
-
*/
|
|
21917
|
-
async function git(cwd, args, env) {
|
|
21918
|
-
const { stdout } = await run("git", [
|
|
21919
|
-
"-c",
|
|
21920
|
-
"core.quotePath=false",
|
|
21921
|
-
...args
|
|
21922
|
-
], {
|
|
21923
|
-
cwd,
|
|
21924
|
-
maxBuffer: 16777216,
|
|
21925
|
-
...env ? { env: {
|
|
21926
|
-
...process.env,
|
|
21927
|
-
...env
|
|
21928
|
-
} } : {}
|
|
21929
|
-
});
|
|
21930
|
-
return stdout;
|
|
21931
|
-
}
|
|
21932
|
-
/**
|
|
21933
|
-
* Whether `cwd` already sits inside a git repository.
|
|
21934
|
-
*
|
|
21935
|
-
* Only git's own "not a git repository" counts as no. Every other failure — a
|
|
21936
|
-
* dubious-ownership refusal, a permission error, a broken `HOME` — is raised,
|
|
21937
|
-
* because answering "no" to one of those would skip the clean-tree check and
|
|
21938
|
-
* then `git init` a second repository nested inside the first.
|
|
22275
|
+
* Last resort, used only where git can build no identity of its own: a bare
|
|
22276
|
+
* CI container has no configured identity and often no passwd entry or
|
|
22277
|
+
* hostname to guess from, and there git refuses to commit. No commit means no
|
|
22278
|
+
* `git_sha`, and the next deploy has nothing to diff against.
|
|
21939
22279
|
*/
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
return true;
|
|
21944
|
-
} catch (error) {
|
|
21945
|
-
const message = messageOf(error).toLowerCase();
|
|
21946
|
-
if (message.includes("not a git repository") || message.includes("enoent")) return false;
|
|
21947
|
-
throw error;
|
|
21948
|
-
}
|
|
21949
|
-
}
|
|
21950
|
-
/**
|
|
21951
|
-
* Where the project sits inside its repository (`apps/web/`, or `""` at the
|
|
21952
|
-
* root). `git status` reports paths from the repository root whatever
|
|
21953
|
-
* directory it runs in, so this is what turns one into a project-relative path.
|
|
21954
|
-
*/
|
|
21955
|
-
async function projectPrefix(cwd) {
|
|
21956
|
-
return (await git(cwd, ["rev-parse", "--show-prefix"])).trim();
|
|
21957
|
-
}
|
|
21958
|
-
/**
|
|
21959
|
-
* Whether the developer has an identity of their own for git to commit with,
|
|
21960
|
-
* from config or from `GIT_AUTHOR_*`/`GIT_COMMITTER_*`/`EMAIL`.
|
|
21961
|
-
*
|
|
21962
|
-
* Without one git does not fail: it guesses from the username and hostname and
|
|
21963
|
-
* commits anyway. `user.useConfigOnly` is what turns that guess back into an
|
|
21964
|
-
* error, so asking under it is the way to tell a real identity from a guessed
|
|
21965
|
-
* one. Nothing is passed to the commit either way — an identity this invented
|
|
21966
|
-
* would be a worse author than the one git derives from the machine.
|
|
21967
|
-
*/
|
|
21968
|
-
async function hasOwnIdentity(cwd) {
|
|
21969
|
-
try {
|
|
21970
|
-
await git(cwd, [
|
|
21971
|
-
"-c",
|
|
21972
|
-
"user.useConfigOnly=true",
|
|
21973
|
-
"var",
|
|
21974
|
-
"GIT_AUTHOR_IDENT"
|
|
21975
|
-
]);
|
|
21976
|
-
return true;
|
|
21977
|
-
} catch {
|
|
21978
|
-
return false;
|
|
21979
|
-
}
|
|
21980
|
-
}
|
|
21981
|
-
/**
|
|
21982
|
-
* Whether git can produce any identity at all, its own guess included.
|
|
21983
|
-
*
|
|
21984
|
-
* A bare CI container has no configured identity and often no passwd entry or
|
|
21985
|
-
* hostname to guess from, and there git refuses to commit. Only then is an
|
|
21986
|
-
* identity supplied, because no commit means no `git_sha` and the next deploy
|
|
21987
|
-
* has nothing to diff against.
|
|
21988
|
-
*/
|
|
21989
|
-
async function canBuildIdentity(cwd) {
|
|
21990
|
-
try {
|
|
21991
|
-
await git(cwd, ["var", "GIT_AUTHOR_IDENT"]);
|
|
21992
|
-
return true;
|
|
21993
|
-
} catch {
|
|
21994
|
-
return false;
|
|
21995
|
-
}
|
|
21996
|
-
}
|
|
21997
|
-
/**
|
|
21998
|
-
* Last resort, used only where git can build no identity of its own.
|
|
21999
|
-
*
|
|
22000
|
-
* Passed as environment variables rather than `-c user.*`, because an
|
|
22001
|
-
* inherited `GIT_AUTHOR_NAME=` (empty, as some runners leave it) outranks
|
|
22002
|
-
* `-c` and git then refuses with "empty ident name". The environment beats
|
|
22003
|
-
* both the config and whatever was inherited.
|
|
22004
|
-
*/
|
|
22005
|
-
const FALLBACK_IDENTITY = {
|
|
22006
|
-
GIT_AUTHOR_NAME: "Zitadel Setup",
|
|
22007
|
-
GIT_AUTHOR_EMAIL: "setup@zitadel.local",
|
|
22008
|
-
GIT_COMMITTER_NAME: "Zitadel Setup",
|
|
22009
|
-
GIT_COMMITTER_EMAIL: "setup@zitadel.local"
|
|
22280
|
+
const FALLBACK_AUTHOR = {
|
|
22281
|
+
name: "Zitadel Setup",
|
|
22282
|
+
email: "setup@zitadel.local"
|
|
22010
22283
|
};
|
|
22011
22284
|
/**
|
|
22012
|
-
* Everything git considers a change under the project directory, untracked
|
|
22013
|
-
* files included. An untracked file counts: setup stages the whole project, so
|
|
22014
|
-
* one left lying about would be swept into setup's commit.
|
|
22015
|
-
*
|
|
22016
|
-
* Scoped to `.` so that in a monorepo a sibling package's work-in-progress —
|
|
22017
|
-
* which `git add` here would never touch — does not refuse the setup.
|
|
22018
|
-
*/
|
|
22019
|
-
async function changes(cwd) {
|
|
22020
|
-
const fields = (await git(cwd, [
|
|
22021
|
-
"status",
|
|
22022
|
-
"--porcelain",
|
|
22023
|
-
"-z",
|
|
22024
|
-
"--untracked-files=all",
|
|
22025
|
-
"--",
|
|
22026
|
-
"."
|
|
22027
|
-
])).split("\0");
|
|
22028
|
-
const found = [];
|
|
22029
|
-
for (let index = 0; index < fields.length; index += 1) {
|
|
22030
|
-
const entry = fields[index];
|
|
22031
|
-
if (entry === void 0 || entry === "") continue;
|
|
22032
|
-
const status = entry.slice(0, 2);
|
|
22033
|
-
if (status.includes("R") || status.includes("C")) index += 1;
|
|
22034
|
-
found.push({
|
|
22035
|
-
path: entry.slice(3),
|
|
22036
|
-
untracked: status === "??"
|
|
22037
|
-
});
|
|
22038
|
-
}
|
|
22039
|
-
return found;
|
|
22040
|
-
}
|
|
22041
|
-
/** The paths staged in `cwd`, relative to it and never quoted. */
|
|
22042
|
-
async function stagedPaths(cwd, env) {
|
|
22043
|
-
return (await git(cwd, [
|
|
22044
|
-
"diff",
|
|
22045
|
-
"--cached",
|
|
22046
|
-
"-z",
|
|
22047
|
-
"--name-only",
|
|
22048
|
-
"--no-renames",
|
|
22049
|
-
"--relative"
|
|
22050
|
-
], env)).split("\0").filter((path) => path !== "");
|
|
22051
|
-
}
|
|
22052
|
-
/**
|
|
22053
22285
|
* Refuse a project whose repository has anything uncommitted, before setup
|
|
22054
22286
|
* writes a single file.
|
|
22055
22287
|
*
|
|
@@ -22060,19 +22292,22 @@ async function stagedPaths(cwd, env) {
|
|
|
22060
22292
|
* apart, and committing someone's work-in-progress under setup's name is worse
|
|
22061
22293
|
* than any diff it would buy. Checked up front rather than at commit time, so
|
|
22062
22294
|
* a refusal costs nothing and leaves no half-made project behind.
|
|
22295
|
+
*
|
|
22296
|
+
* A repository where a commit would land where it should not (during a
|
|
22297
|
+
* merge, rebase, cherry-pick, revert or bisect, or on a detached HEAD) is
|
|
22298
|
+
* refused here too, for the same reason: found only at commit time, setup
|
|
22299
|
+
* would already have done all its work.
|
|
22300
|
+
*
|
|
22301
|
+
* When git cannot answer, setup refuses rather than carries on: the commit
|
|
22302
|
+
* does not check again, so going ahead would stage and commit the very work
|
|
22303
|
+
* this guard exists to protect.
|
|
22063
22304
|
*/
|
|
22064
22305
|
async function assertCleanWorktree(cwd) {
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
...change,
|
|
22071
|
-
path: change.path.startsWith(prefix) ? change.path.slice(prefix.length) : change.path
|
|
22072
|
-
})).filter((change) => !isCliOwned(change.path));
|
|
22073
|
-
} catch (error) {
|
|
22074
|
-
throw new ZitadelError("E_CONFLICT", `Could not tell whether the git working tree is clean: ${messageOf(error)}`, { hint: "Setup commits the files it writes, and will not do that without knowing the tree started clean. Fix what git is reporting, then run setup again, or pass --no-git to set up without touching version control." });
|
|
22075
|
-
}
|
|
22306
|
+
const repository = await Repository.open(cwd);
|
|
22307
|
+
if (repository === null) return;
|
|
22308
|
+
const blocked = await repository.readyToCommit().catch(cannotTell);
|
|
22309
|
+
if (blocked !== null) throw new ZitadelError("E_CONFLICT", `Setup cannot commit here: ${blocked}`, { hint: "Setup commits the project so the first release records a commit. Finish or abort what is under way, or check out a branch, then run setup again, or pass --no-git to set up without touching version control." });
|
|
22310
|
+
const dirty = (await repository.uncommittedFiles().catch(cannotTell)).filter((file) => !isCliOwned(file.path));
|
|
22076
22311
|
if (dirty.length === 0) return;
|
|
22077
22312
|
const modified = dirty.filter((change) => !change.untracked).map((change) => change.path);
|
|
22078
22313
|
const untracked = dirty.filter((change) => change.untracked).map((change) => change.path);
|
|
@@ -22085,6 +22320,10 @@ async function assertCleanWorktree(cwd) {
|
|
|
22085
22320
|
}
|
|
22086
22321
|
});
|
|
22087
22322
|
}
|
|
22323
|
+
/** The refusal for a repository git could not answer about. */
|
|
22324
|
+
function cannotTell(error) {
|
|
22325
|
+
throw new ZitadelError("E_CONFLICT", `Could not tell whether the git working tree is clean: ${messageOf(error)}`, { hint: "Setup commits the files it writes, and will not do that without knowing the tree started clean. Fix what git is reporting, then run setup again, or pass --no-git to set up without touching version control." });
|
|
22326
|
+
}
|
|
22088
22327
|
/** `s` unless the list holds exactly one entry. */
|
|
22089
22328
|
function plural(list) {
|
|
22090
22329
|
return list.length === 1 ? "" : "s";
|
|
@@ -22102,87 +22341,79 @@ function plural(list) {
|
|
|
22102
22341
|
*
|
|
22103
22342
|
* Nothing here prompts, so it behaves the same interactively and in CI, and no
|
|
22104
22343
|
* failure is fatal: the worst case is the first release carrying no commit,
|
|
22105
|
-
* which is what happened before this existed.
|
|
22344
|
+
* which is what happened before this existed. When the commit fails after
|
|
22345
|
+
* setup ran `git init`, the outcome says so, since the repository now exists.
|
|
22106
22346
|
*/
|
|
22107
22347
|
async function ensureInitialCommit(cwd) {
|
|
22108
|
-
let scratch;
|
|
22109
22348
|
try {
|
|
22110
|
-
const existing = await
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
22119
|
-
"."
|
|
22120
|
-
], index);
|
|
22121
|
-
const paths = await stagedPaths(cwd, index);
|
|
22122
|
-
const secrets = paths.filter(isSecretPath);
|
|
22123
|
-
if (secrets.length > 0) await git(cwd, [
|
|
22124
|
-
"reset",
|
|
22125
|
-
"--quiet",
|
|
22126
|
-
"--",
|
|
22127
|
-
...secrets
|
|
22128
|
-
], index);
|
|
22129
|
-
const stillStaged = (await stagedPaths(cwd, index)).filter(isSecretPath);
|
|
22130
|
-
if (stillStaged.length > 0) return {
|
|
22349
|
+
const existing = await Repository.open(cwd);
|
|
22350
|
+
const outcome = await (existing ?? await Repository.create(cwd)).commitAll(INITIAL_COMMIT_SUBJECT, {
|
|
22351
|
+
except: isSecretPath,
|
|
22352
|
+
authorIfGitHasNone: FALLBACK_AUTHOR
|
|
22353
|
+
}).catch((error) => ({
|
|
22354
|
+
committed: false,
|
|
22355
|
+
reason: firstLineOf(error)
|
|
22356
|
+
}));
|
|
22357
|
+
if (!outcome.committed) return {
|
|
22131
22358
|
kind: "unavailable",
|
|
22132
|
-
reason:
|
|
22359
|
+
reason: outcome.reason,
|
|
22360
|
+
createdRepository: existing === null
|
|
22133
22361
|
};
|
|
22134
|
-
|
|
22135
|
-
|
|
22136
|
-
|
|
22362
|
+
const commit = {
|
|
22363
|
+
sha: outcome.sha,
|
|
22364
|
+
guessedIdentity: outcome.author !== "yours",
|
|
22365
|
+
invented: outcome.author === "fallback"
|
|
22137
22366
|
};
|
|
22138
|
-
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
"-c",
|
|
22142
|
-
"commit.gpgsign=false",
|
|
22143
|
-
"commit",
|
|
22144
|
-
"--quiet",
|
|
22145
|
-
"--no-verify",
|
|
22146
|
-
`--message=${INITIAL_COMMIT_SUBJECT}`
|
|
22147
|
-
], {
|
|
22148
|
-
...index,
|
|
22149
|
-
...invented ? FALLBACK_IDENTITY : {}
|
|
22150
|
-
});
|
|
22151
|
-
const sha = await headSha(cwd);
|
|
22152
|
-
if (sha === null) return {
|
|
22153
|
-
kind: "unavailable",
|
|
22154
|
-
reason: "the commit left no HEAD to record"
|
|
22367
|
+
if (existing === null) return {
|
|
22368
|
+
kind: "created",
|
|
22369
|
+
...commit
|
|
22155
22370
|
};
|
|
22156
|
-
await git(cwd, [
|
|
22157
|
-
"reset",
|
|
22158
|
-
"--quiet",
|
|
22159
|
-
"--",
|
|
22160
|
-
"."
|
|
22161
|
-
]).catch(() => void 0);
|
|
22162
22371
|
return {
|
|
22163
|
-
kind:
|
|
22164
|
-
|
|
22165
|
-
guessedIdentity,
|
|
22166
|
-
invented
|
|
22372
|
+
kind: "committed",
|
|
22373
|
+
...commit
|
|
22167
22374
|
};
|
|
22168
22375
|
} catch (error) {
|
|
22169
22376
|
return {
|
|
22170
22377
|
kind: "unavailable",
|
|
22171
|
-
reason:
|
|
22378
|
+
reason: firstLineOf(error),
|
|
22379
|
+
createdRepository: false
|
|
22172
22380
|
};
|
|
22173
|
-
} finally {
|
|
22174
|
-
if (scratch) await rm(scratch, {
|
|
22175
|
-
recursive: true,
|
|
22176
|
-
force: true
|
|
22177
|
-
});
|
|
22178
22381
|
}
|
|
22179
22382
|
}
|
|
22180
|
-
/**
|
|
22383
|
+
/** The one line that says why the commit failed, since it goes into a one-line warning. */
|
|
22384
|
+
function firstLineOf(error) {
|
|
22385
|
+
if (error instanceof RepositoryError) return error.headline();
|
|
22386
|
+
return messageOf(error).split("\n")[0] ?? "";
|
|
22387
|
+
}
|
|
22388
|
+
/** git's own message, so the warning names the real cause. */
|
|
22181
22389
|
function messageOf(error) {
|
|
22182
|
-
const stderr = typeof error === "object" && error !== null ? error.stderr : void 0;
|
|
22183
|
-
if (typeof stderr === "string" && stderr.trim() !== "") return stderr.trim().split("\n")[0] ?? stderr.trim();
|
|
22184
22390
|
return error instanceof Error ? error.message : String(error);
|
|
22185
22391
|
}
|
|
22392
|
+
/** What `setup --json` reports under `git`: whether setup ran `git init`, even if the commit then failed, and the commit, or null. */
|
|
22393
|
+
function gitReport(outcome) {
|
|
22394
|
+
if (outcome.kind === "created") return {
|
|
22395
|
+
initialized: true,
|
|
22396
|
+
commit: outcome.sha
|
|
22397
|
+
};
|
|
22398
|
+
if (outcome.kind === "committed") return {
|
|
22399
|
+
initialized: false,
|
|
22400
|
+
commit: outcome.sha
|
|
22401
|
+
};
|
|
22402
|
+
if (outcome.kind === "unavailable") return {
|
|
22403
|
+
initialized: outcome.createdRepository,
|
|
22404
|
+
commit: null
|
|
22405
|
+
};
|
|
22406
|
+
return {
|
|
22407
|
+
initialized: false,
|
|
22408
|
+
commit: null
|
|
22409
|
+
};
|
|
22410
|
+
}
|
|
22411
|
+
/** The warning for a commit that could not be made, saying so when setup left a new, empty repository behind. */
|
|
22412
|
+
function unavailableWarning(outcome) {
|
|
22413
|
+
const warning = `Could not commit the scaffolded project (${outcome.reason || "git gave no reason"}); the first release records no commit, so the next deploy cannot show a diff.`;
|
|
22414
|
+
if (outcome.createdRepository) return `${warning} Setup created a new, empty git repository here.`;
|
|
22415
|
+
return warning;
|
|
22416
|
+
}
|
|
22186
22417
|
//#endregion
|
|
22187
22418
|
//#region src/commands/setup/move-dev-port.ts
|
|
22188
22419
|
/**
|
|
@@ -22204,7 +22435,7 @@ async function moveDevPort(opts) {
|
|
|
22204
22435
|
const issuer = issuerFromPort(devPort);
|
|
22205
22436
|
const secret = await readZitadelSecret(cwd);
|
|
22206
22437
|
const moving = previousIssuer !== issuer;
|
|
22207
|
-
const client = createZitadelClient({
|
|
22438
|
+
const client = createZitadelClient$1({
|
|
22208
22439
|
baseUrl: source,
|
|
22209
22440
|
token: secret.project_secret
|
|
22210
22441
|
});
|
|
@@ -22943,6 +23174,8 @@ const FRAMEWORK_OPTIONS = createOrca().availableFrameworks().map((framework) =>
|
|
|
22943
23174
|
*/
|
|
22944
23175
|
const UNAVAILABLE_RENDERER_IDS = RENDERER_IDS.filter((id) => !AVAILABLE_RENDERER_IDS.includes(id));
|
|
22945
23176
|
const RENDERER_FLAG_DESCRIPTION = UNAVAILABLE_RENDERER_IDS.length === 0 ? "Renderer (default: react)." : `Renderer (default: react). Not yet available: ${UNAVAILABLE_RENDERER_IDS.join(", ")}.`;
|
|
23177
|
+
/** The message the release setup publishes carries. */
|
|
23178
|
+
const INITIAL_DEPLOY_MESSAGE = "Initial deployment from zitadel setup";
|
|
22946
23179
|
/**
|
|
22947
23180
|
* The selector appended to setup's credential-recovery commands. `variable set`
|
|
22948
23181
|
* is an `EnvironmentCommand` that resolves the project and server from the
|
|
@@ -23185,7 +23418,7 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23185
23418
|
force
|
|
23186
23419
|
});
|
|
23187
23420
|
consola.start(`Creating project on ${answers.server}${dryRun ? " (dry run)" : ""}`);
|
|
23188
|
-
const unauthClient = createZitadelClient({ baseUrl: answers.server });
|
|
23421
|
+
const unauthClient = createZitadelClient$1({ baseUrl: answers.server });
|
|
23189
23422
|
const projectName = defaultProjectName(cwd, app.framework);
|
|
23190
23423
|
project = dryRun ? dryRunProject(issuer) : await createProjectWithLocalHint(unauthClient, answers.server, this.meta.cliVersion, projectName, issuer, {
|
|
23191
23424
|
...retryOptionsFromFlags(flags),
|
|
@@ -23235,7 +23468,7 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23235
23468
|
passwordMinLengthSet: false
|
|
23236
23469
|
} : await materializeSetupResources({
|
|
23237
23470
|
cwd,
|
|
23238
|
-
client: createZitadelClient({
|
|
23471
|
+
client: createZitadelClient$1({
|
|
23239
23472
|
baseUrl: answers.server,
|
|
23240
23473
|
token: project.project_secret
|
|
23241
23474
|
}, { verbatim: true }),
|
|
@@ -23335,6 +23568,26 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23335
23568
|
package_manager: installOutcome.install.package_manager
|
|
23336
23569
|
});
|
|
23337
23570
|
const commit = await this.makeInitialCommit(cwd, dryRun, flags.git !== false);
|
|
23571
|
+
const deployment = await this.deployScaffoldedProject({
|
|
23572
|
+
cwd,
|
|
23573
|
+
server: answers.server,
|
|
23574
|
+
project,
|
|
23575
|
+
dryRun,
|
|
23576
|
+
selector: recoverySelector(flags.cwd)
|
|
23577
|
+
});
|
|
23578
|
+
const deployCommand = projectCommand(DEPLOY_ARGS, recoverySelector(flags.cwd), this.meta.cliVersion);
|
|
23579
|
+
const deployStep = deployment === void 0 && !dryRun ? {
|
|
23580
|
+
actions: [`Deploy .zitadel/ so the project serves its release: ${deployCommand}`],
|
|
23581
|
+
boxActions: [{
|
|
23582
|
+
text: "Deploy .zitadel/ so the project serves its release:",
|
|
23583
|
+
command: deployCommand
|
|
23584
|
+
}],
|
|
23585
|
+
commands: [deployCommand]
|
|
23586
|
+
} : {
|
|
23587
|
+
actions: [],
|
|
23588
|
+
boxActions: [],
|
|
23589
|
+
commands: []
|
|
23590
|
+
};
|
|
23338
23591
|
const writtenRel = allFilesWritten.map((file) => relativeDisplayPath(cwd, file));
|
|
23339
23592
|
let ownedByLocalAdmin;
|
|
23340
23593
|
const isLocal = serverKind.value(answers.server) === "local";
|
|
@@ -23389,6 +23642,7 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23389
23642
|
boxActions: [],
|
|
23390
23643
|
commands: []
|
|
23391
23644
|
};
|
|
23645
|
+
let nextShown;
|
|
23392
23646
|
if (!this.jsonEnabled()) {
|
|
23393
23647
|
const sections = buildSummary({
|
|
23394
23648
|
projectFacts: await detectProjectFacts(cwd, app.framework),
|
|
@@ -23407,6 +23661,7 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23407
23661
|
renderSummary(sections),
|
|
23408
23662
|
"",
|
|
23409
23663
|
renderBoxActions([
|
|
23664
|
+
...deployStep.boxActions,
|
|
23410
23665
|
...installOutcome.boxActions,
|
|
23411
23666
|
...consoleStep.boxActions,
|
|
23412
23667
|
...claimNudge.boxActions
|
|
@@ -23418,10 +23673,12 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23418
23673
|
borderColor: "green"
|
|
23419
23674
|
}
|
|
23420
23675
|
});
|
|
23676
|
+
nextShown = asStepsShown();
|
|
23421
23677
|
consola.log(`\n${exploreAllCommands(this.meta.cliVersion)}`);
|
|
23422
23678
|
await showSkillsHint({
|
|
23423
23679
|
env: process.env,
|
|
23424
|
-
interactive: !dryRun && !flags["non-interactive"] && Boolean(process.stdout.isTTY && process.stdin.isTTY)
|
|
23680
|
+
interactive: !dryRun && !flags["non-interactive"] && Boolean(process.stdout.isTTY && process.stdin.isTTY),
|
|
23681
|
+
cliVersion: this.meta.cliVersion
|
|
23425
23682
|
});
|
|
23426
23683
|
}
|
|
23427
23684
|
return this.emit({
|
|
@@ -23443,10 +23700,7 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23443
23700
|
})),
|
|
23444
23701
|
files_skipped: result.filesSkipped.map((file) => relativeDisplayPath(cwd, file)),
|
|
23445
23702
|
install: installOutcome.install,
|
|
23446
|
-
git:
|
|
23447
|
-
initialized: commit.kind === "created",
|
|
23448
|
-
commit: commit.kind === "created" || commit.kind === "committed" ? commit.sha : null
|
|
23449
|
-
},
|
|
23703
|
+
git: gitReport(commit),
|
|
23450
23704
|
sso: answers.sso.map((answer) => {
|
|
23451
23705
|
const slug = providerOf(answer.selection).slug;
|
|
23452
23706
|
const outcome = ssoOutcomes.get(slug);
|
|
@@ -23466,13 +23720,22 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23466
23720
|
} : null
|
|
23467
23721
|
};
|
|
23468
23722
|
}),
|
|
23469
|
-
|
|
23723
|
+
deployment: deployment === void 0 ? null : {
|
|
23724
|
+
release_id: deployment.releaseId,
|
|
23725
|
+
content_hash: deployment.contentHash,
|
|
23726
|
+
deployment_id: deployment.deploymentId,
|
|
23727
|
+
origins: [...deployment.origins]
|
|
23728
|
+
}
|
|
23729
|
+
},
|
|
23730
|
+
next: {
|
|
23731
|
+
actions: [
|
|
23732
|
+
...deployStep.actions,
|
|
23470
23733
|
...installOutcome.nextActions,
|
|
23471
23734
|
brandingGuidanceAction(this.meta.cliVersion),
|
|
23472
23735
|
...consoleStep.actions,
|
|
23473
23736
|
...claimNudge.actions
|
|
23474
23737
|
],
|
|
23475
|
-
|
|
23738
|
+
commands: [
|
|
23476
23739
|
...republishCommands(answers.sso.flatMap((answer) => {
|
|
23477
23740
|
const slug = providerOf(answer.selection).slug;
|
|
23478
23741
|
const outcome = ssoOutcomes.get(slug);
|
|
@@ -23486,11 +23749,13 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23486
23749
|
published: outcome?.secret.published
|
|
23487
23750
|
}];
|
|
23488
23751
|
}), this.meta.cliVersion, recoverySelector(flags.cwd)),
|
|
23752
|
+
...deployStep.commands,
|
|
23489
23753
|
...installOutcome.nextCommands,
|
|
23490
23754
|
...consoleStep.commands,
|
|
23491
23755
|
...claimNudge.commands
|
|
23492
|
-
]
|
|
23493
|
-
}
|
|
23756
|
+
]
|
|
23757
|
+
},
|
|
23758
|
+
nextShown
|
|
23494
23759
|
});
|
|
23495
23760
|
}
|
|
23496
23761
|
/**
|
|
@@ -23510,10 +23775,43 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23510
23775
|
if (outcome.invented) reportWarning("git could build no author identity on this machine, so the commit was made as Zitadel Setup <setup@zitadel.local>. Set user.name and user.email, then run `git commit --amend --reset-author` to put your own name on it.");
|
|
23511
23776
|
else if (outcome.guessedIdentity) reportWarning("This machine has no git user.name/user.email, so git guessed the author from your username and hostname. Set your identity and run `git commit --amend --reset-author` to put your own name on the commit.");
|
|
23512
23777
|
}
|
|
23513
|
-
if (outcome.kind === "unavailable") reportWarning(
|
|
23778
|
+
if (outcome.kind === "unavailable") reportWarning(unavailableWarning(outcome));
|
|
23514
23779
|
return outcome;
|
|
23515
23780
|
}
|
|
23516
23781
|
/**
|
|
23782
|
+
* Deploy `.zitadel/` to the project setup just created, to its primary
|
|
23783
|
+
* origin. Narrated but never fatal: the app is scaffolded and committed, and
|
|
23784
|
+
* a failure leaves the deploy one command away.
|
|
23785
|
+
*/
|
|
23786
|
+
async deployScaffoldedProject(opts) {
|
|
23787
|
+
if (opts.dryRun) {
|
|
23788
|
+
consola.info("Would deploy .zitadel/ to the project (dry run)");
|
|
23789
|
+
return;
|
|
23790
|
+
}
|
|
23791
|
+
const json = this.jsonEnabled();
|
|
23792
|
+
consola.start("Deploying .zitadel/ to the project");
|
|
23793
|
+
try {
|
|
23794
|
+
const deployment = await deployInitialRelease({
|
|
23795
|
+
client: createZitadelClient$1({
|
|
23796
|
+
baseUrl: opts.server,
|
|
23797
|
+
token: opts.project.project_secret
|
|
23798
|
+
}),
|
|
23799
|
+
projectId: opts.project.id,
|
|
23800
|
+
cwd: opts.cwd,
|
|
23801
|
+
env: this.meta.env,
|
|
23802
|
+
json,
|
|
23803
|
+
color: !json && Boolean(process.stdout.isTTY) && !process.env.NO_COLOR,
|
|
23804
|
+
message: INITIAL_DEPLOY_MESSAGE,
|
|
23805
|
+
suggest: (args) => projectCommand(args, opts.selector, this.meta.cliVersion)
|
|
23806
|
+
});
|
|
23807
|
+
consola.success(`Deployed ${deployment.contentHash} to ${deployment.origins.length} origin${deployment.origins.length === 1 ? "" : "s"}`);
|
|
23808
|
+
return deployment;
|
|
23809
|
+
} catch (error) {
|
|
23810
|
+
reportWarning(`The project was set up but its first deploy failed: ${toZitadelError(error).message}. Run \`zitadel ${DEPLOY_ARGS}${opts.selector}\` to publish .zitadel/.`);
|
|
23811
|
+
return;
|
|
23812
|
+
}
|
|
23813
|
+
}
|
|
23814
|
+
/**
|
|
23517
23815
|
* `setup --dev-port <n>` on an app setup already patched: moves the app to
|
|
23518
23816
|
* the new port (see {@link moveDevPort}) instead of skipping the directory.
|
|
23519
23817
|
*/
|
|
@@ -23542,9 +23840,9 @@ var Setup = class Setup extends Promptable(BaseCommand) {
|
|
|
23542
23840
|
previous_issuer: move.previousIssuer,
|
|
23543
23841
|
issuer: move.issuer,
|
|
23544
23842
|
dev_port: devPort,
|
|
23545
|
-
files_written: move.result.filesWritten.map((file) => relativeDisplayPath(cwd, file))
|
|
23546
|
-
|
|
23547
|
-
}
|
|
23843
|
+
files_written: move.result.filesWritten.map((file) => relativeDisplayPath(cwd, file))
|
|
23844
|
+
},
|
|
23845
|
+
next: { actions: [`Restart the dev server and open ${move.issuer}/login.`] }
|
|
23548
23846
|
});
|
|
23549
23847
|
}
|
|
23550
23848
|
};
|
|
@@ -23801,7 +24099,7 @@ function retryOptionsFromFlags(flags) {
|
|
|
23801
24099
|
*/
|
|
23802
24100
|
function ssoCredentialPublisher(server, project) {
|
|
23803
24101
|
if (server === "mock") return;
|
|
23804
|
-
const client = createZitadelClient({
|
|
24102
|
+
const client = createZitadelClient$1({
|
|
23805
24103
|
baseUrl: server,
|
|
23806
24104
|
token: project.project_secret
|
|
23807
24105
|
});
|
|
@@ -24138,6 +24436,99 @@ function buildSummary(opts) {
|
|
|
24138
24436
|
];
|
|
24139
24437
|
}
|
|
24140
24438
|
//#endregion
|
|
24439
|
+
//#region src/lib/skills/add.ts
|
|
24440
|
+
/** Where most coding agents read a project's skills: Codex, Cursor, GitHub Copilot, Gemini CLI and more. */
|
|
24441
|
+
const SHARED_SKILLS_DIR = ".agents/skills";
|
|
24442
|
+
/** Where Claude Code reads them, which reads no other folder. */
|
|
24443
|
+
const CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
24444
|
+
/**
|
|
24445
|
+
* The skills this CLI ships, which its build copied into `dist/skills/`
|
|
24446
|
+
* (`./bundle.ts`): the set of the release the user runs.
|
|
24447
|
+
*/
|
|
24448
|
+
async function bundledSkills(packageRoot) {
|
|
24449
|
+
return (await readdir(bundleDir(packageRoot), { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
24450
|
+
}
|
|
24451
|
+
/**
|
|
24452
|
+
* The project's folders to add the skills to: the shared one, and Claude
|
|
24453
|
+
* Code's as well when Claude Code is installed on this machine or the project
|
|
24454
|
+
* already has a `.claude/` folder, as one a teammate works on with it does.
|
|
24455
|
+
*/
|
|
24456
|
+
async function skillDirs(projectDir) {
|
|
24457
|
+
return existsSync(join(projectDir, ".claude")) || await claudeCodeInstalled() ? [SHARED_SKILLS_DIR, CLAUDE_SKILLS_DIR] : [SHARED_SKILLS_DIR];
|
|
24458
|
+
}
|
|
24459
|
+
/**
|
|
24460
|
+
* Whether Claude Code is installed on this machine. A detection that fails
|
|
24461
|
+
* counts as no: it only decides the extra folder, never whether the skills
|
|
24462
|
+
* are added.
|
|
24463
|
+
*/
|
|
24464
|
+
async function claudeCodeInstalled() {
|
|
24465
|
+
try {
|
|
24466
|
+
return (await rosie.agents()).some((agent) => agent.name === "claude" && agent.detected);
|
|
24467
|
+
} catch {
|
|
24468
|
+
return false;
|
|
24469
|
+
}
|
|
24470
|
+
}
|
|
24471
|
+
/**
|
|
24472
|
+
* Puts each skill's bundled folder at `<dir>/<skill>` in the project,
|
|
24473
|
+
* replacing whatever was there: an older version's copy, or the link
|
|
24474
|
+
* `npx skills add` leaves, which is removed rather than followed.
|
|
24475
|
+
*/
|
|
24476
|
+
async function addSkills(options) {
|
|
24477
|
+
for (const dir of options.dirs) for (const skill of options.skills) {
|
|
24478
|
+
const target = join(options.projectDir, dir, skill);
|
|
24479
|
+
await rm(target, {
|
|
24480
|
+
recursive: true,
|
|
24481
|
+
force: true
|
|
24482
|
+
});
|
|
24483
|
+
await cp(join(bundleDir(options.packageRoot), skill), target, { recursive: true });
|
|
24484
|
+
}
|
|
24485
|
+
}
|
|
24486
|
+
function bundleDir(packageRoot) {
|
|
24487
|
+
return join(packageRoot, "dist", "skills");
|
|
24488
|
+
}
|
|
24489
|
+
//#endregion
|
|
24490
|
+
//#region src/commands/skills/add.ts
|
|
24491
|
+
/**
|
|
24492
|
+
* `zitadel skills add` — give the project's coding agents the Zitadel skills.
|
|
24493
|
+
*
|
|
24494
|
+
* The skills ship inside the CLI, so the ones added are those of the release
|
|
24495
|
+
* the user runs, which `setup` also pins the app's `@zitadel/*` packages to.
|
|
24496
|
+
* Adding them is this command's whole job, so it asks nothing; running it
|
|
24497
|
+
* again replaces them, which is how they follow a CLI update.
|
|
24498
|
+
*/
|
|
24499
|
+
var SkillsAdd = class SkillsAdd extends BaseCommand {
|
|
24500
|
+
static description = "Add the Zitadel agent skills to this project.\n\nAdds the skills of this CLI's version to .agents/skills, which most coding agents read, and to .claude/skills for Claude Code. Run it again after updating the CLI to replace them with the new version's.";
|
|
24501
|
+
static group = CommandGroups.gettingStarted;
|
|
24502
|
+
static groupOrder = 5;
|
|
24503
|
+
static examples = ["<%= config.bin %> <%= command.id %>", "<%= config.bin %> <%= command.id %> --cwd my-app"];
|
|
24504
|
+
async run() {
|
|
24505
|
+
const { flags } = await this.parse(SkillsAdd);
|
|
24506
|
+
await this.toMeta(flags, { resolveServer: false });
|
|
24507
|
+
const skills = await bundledSkills(this.config.root);
|
|
24508
|
+
const dirs = await skillDirs(this.meta.cwd);
|
|
24509
|
+
if (!this.meta.dryRun) await addSkills({
|
|
24510
|
+
packageRoot: this.config.root,
|
|
24511
|
+
projectDir: this.meta.cwd,
|
|
24512
|
+
skills,
|
|
24513
|
+
dirs
|
|
24514
|
+
});
|
|
24515
|
+
return this.emit({
|
|
24516
|
+
status: "ok",
|
|
24517
|
+
data: {
|
|
24518
|
+
title: this.meta.dryRun ? "Zitadel agent skills to add." : "Zitadel agent skills added.",
|
|
24519
|
+
skills,
|
|
24520
|
+
directories: dirs,
|
|
24521
|
+
...this.meta.dryRun ? { dry_run: true } : {}
|
|
24522
|
+
},
|
|
24523
|
+
pretty: [
|
|
24524
|
+
`${this.meta.dryRun ? "Would add" : "Added"} the ${skills.length} Zitadel agent skills of CLI ${this.meta.cliVersion} to:`,
|
|
24525
|
+
...dirs.map((dir) => ` ${dir}${dir === ".claude/skills" ? " (Claude Code)" : ""}`),
|
|
24526
|
+
"Run this again after updating the CLI to keep them in step with it."
|
|
24527
|
+
].join("\n")
|
|
24528
|
+
});
|
|
24529
|
+
}
|
|
24530
|
+
};
|
|
24531
|
+
//#endregion
|
|
24141
24532
|
//#region src/commands/start.ts
|
|
24142
24533
|
var Start = class Start extends BaseCommand {
|
|
24143
24534
|
static description = "Start a local Zitadel server.";
|
|
@@ -24186,9 +24577,9 @@ var Start = class Start extends BaseCommand {
|
|
|
24186
24577
|
urls: {
|
|
24187
24578
|
api: serverUrl,
|
|
24188
24579
|
console: `${serverUrl}/ui/console/`
|
|
24189
|
-
}
|
|
24190
|
-
|
|
24191
|
-
}
|
|
24580
|
+
}
|
|
24581
|
+
},
|
|
24582
|
+
next: { commands: [this.suggestUnscoped("start")] }
|
|
24192
24583
|
});
|
|
24193
24584
|
await ensureLocalState(this.meta.cwd);
|
|
24194
24585
|
const env = await loadProjectEnv(this.meta.cwd, this.meta.env);
|
|
@@ -24213,7 +24604,8 @@ var Start = class Start extends BaseCommand {
|
|
|
24213
24604
|
};
|
|
24214
24605
|
return this.emit({
|
|
24215
24606
|
status: "ok",
|
|
24216
|
-
data: readyData(runtime, alreadyRunning,
|
|
24607
|
+
data: readyData(runtime, alreadyRunning),
|
|
24608
|
+
next: readyNext(this.meta.cliVersion, this.suggestUnscoped("setup --server local"))
|
|
24217
24609
|
});
|
|
24218
24610
|
}
|
|
24219
24611
|
};
|
|
@@ -24227,7 +24619,7 @@ function platformBootstrapEnabled(env) {
|
|
|
24227
24619
|
const pinned = env.NEXTGEN_PLATFORM_PROJECT_ID;
|
|
24228
24620
|
return !pinned || pinned === "proj_platform";
|
|
24229
24621
|
}
|
|
24230
|
-
function readyData(metadata, alreadyRunning
|
|
24622
|
+
function readyData(metadata, alreadyRunning) {
|
|
24231
24623
|
return {
|
|
24232
24624
|
title: alreadyRunning ? "Local Zitadel server is already running." : "Local Zitadel server is ready.",
|
|
24233
24625
|
runtime: {
|
|
@@ -24249,13 +24641,17 @@ function readyData(metadata, alreadyRunning, cliVersion, setupCommand) {
|
|
|
24249
24641
|
urls: {
|
|
24250
24642
|
api: metadata.server_url,
|
|
24251
24643
|
console: `${metadata.server_url}/ui/console/`
|
|
24252
|
-
}
|
|
24253
|
-
|
|
24644
|
+
}
|
|
24645
|
+
};
|
|
24646
|
+
}
|
|
24647
|
+
function readyNext(cliVersion, setupCommand) {
|
|
24648
|
+
return {
|
|
24649
|
+
actions: [
|
|
24254
24650
|
"Create an app with authentication. Run setup in this directory or a subdirectory.",
|
|
24255
24651
|
"Setup asks for a framework and sign-in method, creates or updates your app, and installs its dependencies.",
|
|
24256
24652
|
serverLogAction(cliVersion)
|
|
24257
24653
|
],
|
|
24258
|
-
|
|
24654
|
+
commands: [setupCommand]
|
|
24259
24655
|
};
|
|
24260
24656
|
}
|
|
24261
24657
|
function validatePort(port) {
|
|
@@ -24316,9 +24712,11 @@ var Status = class Status extends EnvironmentCommand {
|
|
|
24316
24712
|
url: server.runtime?.server_url
|
|
24317
24713
|
}
|
|
24318
24714
|
},
|
|
24319
|
-
project
|
|
24320
|
-
|
|
24321
|
-
|
|
24715
|
+
project
|
|
24716
|
+
},
|
|
24717
|
+
next: {
|
|
24718
|
+
actions: nextActions,
|
|
24719
|
+
commands: nextCommands
|
|
24322
24720
|
},
|
|
24323
24721
|
footer: exploreAllCommands(this.meta.cliVersion)
|
|
24324
24722
|
});
|
|
@@ -24433,9 +24831,9 @@ var Stop = class Stop extends BaseCommand {
|
|
|
24433
24831
|
} : { container_name: containerName },
|
|
24434
24832
|
data_preserved: true,
|
|
24435
24833
|
data_dir: runtime?.data_dir
|
|
24436
|
-
}
|
|
24437
|
-
|
|
24438
|
-
}
|
|
24834
|
+
}
|
|
24835
|
+
},
|
|
24836
|
+
next: { commands: [this.suggestUnscoped("stop")] }
|
|
24439
24837
|
});
|
|
24440
24838
|
const { stopped, outcome } = await LocalServer.at(this.meta.cwd, { cliVersion: this.meta.cliVersion }).stop();
|
|
24441
24839
|
return this.emit({
|
|
@@ -24620,7 +25018,11 @@ var VariableResolve = class VariableResolve extends EnvironmentCommand {
|
|
|
24620
25018
|
await this.toMeta(flags);
|
|
24621
25019
|
const { client, projectId } = await this.connect();
|
|
24622
25020
|
const origin = flags.origin;
|
|
24623
|
-
const deployments = await
|
|
25021
|
+
const deployments = await client.listDeployments.all({
|
|
25022
|
+
project_id: projectId,
|
|
25023
|
+
live: true,
|
|
25024
|
+
origin
|
|
25025
|
+
});
|
|
24624
25026
|
if (origin === void 0 && deployments.length > 1) {
|
|
24625
25027
|
const origins = [...new Set(deployments.flatMap((deployment) => deployment.targets.map((t) => t.origin)))].sort();
|
|
24626
25028
|
throw new ZitadelError("E_VALIDATION", "More than one deployment is live; name which origin to resolve with --origin.", {
|
|
@@ -24661,7 +25063,7 @@ var VariableResolve = class VariableResolve extends EnvironmentCommand {
|
|
|
24661
25063
|
hint: `Deploy first with \`zitadel ${DEPLOY_ARGS}${this.environmentSelector()}\`, then resolve its variables.`,
|
|
24662
25064
|
nextCommands: [this.suggest(DEPLOY_ARGS)]
|
|
24663
25065
|
});
|
|
24664
|
-
const origins = (await client.getProject(projectId)).origins
|
|
25066
|
+
const origins = (await client.getProject(projectId)).origins;
|
|
24665
25067
|
if (origins.some((entry) => entry.kind === "primary" && entry.pattern === origin)) return new ZitadelError("E_NOT_FOUND", `Nothing is deployed to ${origin}.`, {
|
|
24666
25068
|
hint: `Deploy to it first with \`zitadel ${DEPLOY_ARGS}${this.environmentSelector()}\`, then resolve its variables.`,
|
|
24667
25069
|
nextCommands: [this.suggest(DEPLOY_ARGS)]
|
|
@@ -24968,6 +25370,7 @@ const COMMANDS = {
|
|
|
24968
25370
|
"preview:rm": PreviewRm,
|
|
24969
25371
|
"release:create": ReleaseCreate,
|
|
24970
25372
|
"release:revoke": ReleaseRevoke,
|
|
25373
|
+
"skills:add": SkillsAdd,
|
|
24971
25374
|
"variable:set": VariableSet,
|
|
24972
25375
|
"variable:get": VariableGet,
|
|
24973
25376
|
"variable:list": VariableList,
|