@zitadel/config 1.0.0-alpha.21 → 1.0.0-alpha.23
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 +2 -0
- package/defaults/README-branding.md +1 -1
- package/defaults/README-flows.md +9 -4
- package/defaults/default-login.json +0 -27
- package/dist/branding-contrast.d.mts +107 -0
- package/dist/branding-contrast.d.mts.map +1 -0
- package/dist/branding-contrast.mjs +229 -0
- package/dist/branding-contrast.mjs.map +1 -0
- package/dist/css-color.d.mts +36 -0
- package/dist/css-color.d.mts.map +1 -0
- package/dist/css-color.mjs +231 -0
- package/dist/css-color.mjs.map +1 -0
- package/dist/{defaults-Bh4_xVbh.d.mts → defaults-BxGCGdqS.d.mts} +5 -3
- package/dist/defaults-BxGCGdqS.d.mts.map +1 -0
- package/dist/{defaults-DPq0o3iU.mjs → defaults-DV6hY2KT.mjs} +25 -53
- package/dist/defaults-DV6hY2KT.mjs.map +1 -0
- package/dist/defaults.d.mts +1 -1
- package/dist/defaults.mjs +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +4 -4
- package/dist/meta-schemas-OowgSUfU.mjs +1173 -0
- package/dist/meta-schemas-OowgSUfU.mjs.map +1 -0
- package/dist/meta-schemas.d.mts +4 -3
- package/dist/meta-schemas.d.mts.map +1 -1
- package/dist/meta-schemas.mjs +1 -1
- package/dist/schemas-7oNU25Qz.mjs +173 -0
- package/dist/schemas-7oNU25Qz.mjs.map +1 -0
- package/dist/schemas.d.mts +12 -5
- package/dist/schemas.d.mts.map +1 -1
- package/dist/schemas.mjs +3 -69
- package/dist/validate.d.mts +8 -2
- package/dist/validate.d.mts.map +1 -1
- package/dist/validate.mjs +9 -2
- package/dist/validate.mjs.map +1 -1
- package/meta-schemas/auth-method.json +1 -0
- package/meta-schemas/auth-methods.json +5 -3
- package/meta-schemas/branding.json +268 -19
- package/meta-schemas/flow-definition.json +375 -171
- package/meta-schemas/idp-connection.json +397 -0
- package/meta-schemas/property-name.json +1 -0
- package/meta-schemas/sso-auth-method.json +54 -0
- package/meta-schemas/user-property.json +10 -13
- package/meta-schemas/user-schema.json +20 -13
- package/package.json +18 -5
- package/dist/defaults-Bh4_xVbh.d.mts.map +0 -1
- package/dist/defaults-DPq0o3iU.mjs.map +0 -1
- package/dist/meta-schemas-z4IlqbLd.mjs +0 -531
- package/dist/meta-schemas-z4IlqbLd.mjs.map +0 -1
- package/dist/schemas.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-schemas-OowgSUfU.mjs","names":["flowDefinitionMetaSchema","userSchemaMetaSchema","userPropertyMetaSchema","propertyNameMetaSchema","authMethodsMetaSchema","authMethodMetaSchema","ssoAuthMethodMetaSchema","idpConnectionMetaSchema","brandingMetaSchema"],"sources":["../meta-schemas/auth-method.json","../meta-schemas/auth-methods.json","../meta-schemas/branding.json","../meta-schemas/flow-definition.json","../meta-schemas/idp-connection.json","../meta-schemas/property-name.json","../meta-schemas/sso-auth-method.json","../meta-schemas/user-property.json","../meta-schemas/user-schema.json","../src/meta-schemas.ts"],"sourcesContent":["","","","","","","","","","/**\n * The dialect meta-schemas `zitadel setup` copies into a project's\n * `.zitadel/meta/` so the flow/schema dialect is machine-readable offline:\n * editors validate flow files against `flow-definition.json` via a relative\n * `$schema` pointer, and agents read the dialect files before authoring\n * edits — no docs crawl, no hosted schema required.\n *\n * The files under `meta-schemas/` are generated: `scripts/sync-meta-schemas.mjs`\n * copies them from `api/openapi/endpoints/schemas/*.json` (the files the\n * server embeds) ahead of build, typecheck and test. Those are generated in\n * turn from the OpenAPI YAML by `scripts/generate-meta-schemas.ts` at the repo\n * root, so the YAML is the only thing to edit.\n */\nimport authMethodMetaSchema from \"../meta-schemas/auth-method.json\" with {\n type: \"json\",\n};\nimport authMethodsMetaSchema from \"../meta-schemas/auth-methods.json\" with {\n type: \"json\",\n};\nimport brandingMetaSchema from \"../meta-schemas/branding.json\" with {\n type: \"json\",\n};\nimport flowDefinitionMetaSchema from \"../meta-schemas/flow-definition.json\" with {\n type: \"json\",\n};\nimport idpConnectionMetaSchema from \"../meta-schemas/idp-connection.json\" with {\n type: \"json\",\n};\nimport propertyNameMetaSchema from \"../meta-schemas/property-name.json\" with {\n type: \"json\",\n};\nimport ssoAuthMethodMetaSchema from \"../meta-schemas/sso-auth-method.json\" with {\n type: \"json\",\n};\nimport userPropertyMetaSchema from \"../meta-schemas/user-property.json\" with {\n type: \"json\",\n};\nimport userSchemaMetaSchema from \"../meta-schemas/user-schema.json\" with {\n type: \"json\",\n};\n\n/** Project-relative directory setup writes the meta-schemas to. */\nexport const META_SCHEMA_DIR = \".zitadel/meta\";\n\n/**\n * The `$schema` value scaffolded flow files carry, relative to\n * `.zitadel/flows/` — resolves to `{@link META_SCHEMA_DIR}/flow-definition.json`.\n */\nexport const FLOW_FILE_SCHEMA_REF = \"../meta/flow-definition.json\";\n\n/**\n * The `$schema` value scaffolded branding files carry, relative to\n * `.zitadel/branding/` — resolves to `{@link META_SCHEMA_DIR}/branding.json`.\n */\nexport const BRANDING_FILE_SCHEMA_REF = \"../meta/branding.json\";\n\nexport type MetaSchemaFile = { name: string; body: object };\n\n/**\n * The dialect files to materialize, in write order. `auth-methods.json`,\n * `auth-method.json`, and `sso-auth-method.json` are pulled in by\n * `user-schema.json`'s relative `$ref`s — without them the copied dialect\n * cannot resolve offline.\n */\nexport function metaSchemaFiles(): ReadonlyArray<MetaSchemaFile> {\n return [\n { name: \"flow-definition.json\", body: flowDefinitionMetaSchema as object },\n { name: \"user-schema.json\", body: userSchemaMetaSchema as object },\n { name: \"user-property.json\", body: userPropertyMetaSchema as object },\n { name: \"property-name.json\", body: propertyNameMetaSchema as object },\n { name: \"auth-methods.json\", body: authMethodsMetaSchema as object },\n { name: \"auth-method.json\", body: authMethodMetaSchema as object },\n { name: \"sso-auth-method.json\", body: ssoAuthMethodMetaSchema as object },\n { name: \"idp-connection.json\", body: idpConnectionMetaSchema as object },\n { name: \"branding.json\", body: brandingMetaSchema as object },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AS0CA,MAAa,kBAAkB;;;;;AAM/B,MAAa,uBAAuB;;;;;AAMpC,MAAa,2BAA2B;;;;;;;AAUxC,SAAgB,kBAAiD;AAC/D,QAAO;EACL;GAAE,MAAM;GAAwB,MAAMA;GAAoC;EAC1E;GAAE,MAAM;GAAoB,MAAMC;GAAgC;EAClE;GAAE,MAAM;GAAsB,MAAMC;GAAkC;EACtE;GAAE,MAAM;GAAsB,MAAMC;GAAkC;EACtE;GAAE,MAAM;GAAqB,MAAMC;GAAiC;EACpE;GAAE,MAAM;GAAoB,MAAMC;GAAgC;EAClE;GAAE,MAAM;GAAwB,MAAMC;GAAmC;EACzE;GAAE,MAAM;GAAuB,MAAMC;GAAmC;EACxE;GAAE,MAAM;GAAiB,MAAMC;GAA8B;EAC9D"}
|
package/dist/meta-schemas.d.mts
CHANGED
|
@@ -16,9 +16,10 @@ type MetaSchemaFile = {
|
|
|
16
16
|
body: object;
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
* The dialect files to materialize, in write order. `auth-methods.json
|
|
20
|
-
* `auth-method.json
|
|
21
|
-
* without them the copied dialect
|
|
19
|
+
* The dialect files to materialize, in write order. `auth-methods.json`,
|
|
20
|
+
* `auth-method.json`, and `sso-auth-method.json` are pulled in by
|
|
21
|
+
* `user-schema.json`'s relative `$ref`s — without them the copied dialect
|
|
22
|
+
* cannot resolve offline.
|
|
22
23
|
*/
|
|
23
24
|
declare function metaSchemaFiles(): ReadonlyArray<MetaSchemaFile>;
|
|
24
25
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-schemas.d.mts","names":[],"sources":["../src/meta-schemas.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"meta-schemas.d.mts","names":[],"sources":["../src/meta-schemas.ts"],"mappings":";;cA0Ca,eAAA;;;;;cAMA,oBAAA;;;;;cAMA,wBAAA;AAAA,KAED,cAAA;EAAmB,IAAA;EAAc,IAAA;AAAA;AAA7C;;;;;AAQA;AARA,iBAQgB,eAAA,CAAA,GAAmB,aAAA,CAAc,cAAA"}
|
package/dist/meta-schemas.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-
|
|
1
|
+
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-OowgSUfU.mjs";
|
|
2
2
|
export { BRANDING_FILE_SCHEMA_REF, FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { isCanonicalLoopbackHttpUrl } from "./branding-url.mjs";
|
|
2
|
+
import { CreateBrandingBody, CreateFlowDefinitionBody, CreateSchemaBody } from "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/** Asset URL cap in UTF-8 bytes, like the server's `MaxBrandingURLBytes`. */
|
|
5
|
+
const MAX_BRANDING_URL_BYTES = 2048;
|
|
6
|
+
const HEX_COLOR = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
7
|
+
/** `url` is absent by construction: it is not a colour function. */
|
|
8
|
+
const COLOR_FUNCTIONS = new Set([
|
|
9
|
+
"rgb",
|
|
10
|
+
"rgba",
|
|
11
|
+
"hsl",
|
|
12
|
+
"hsla",
|
|
13
|
+
"hwb",
|
|
14
|
+
"lab",
|
|
15
|
+
"lch",
|
|
16
|
+
"oklab",
|
|
17
|
+
"oklch",
|
|
18
|
+
"color",
|
|
19
|
+
"color-mix"
|
|
20
|
+
]);
|
|
21
|
+
const COLOR_ARGS = /^[0-9a-zA-Z%.,+\-/ ()#]*$/;
|
|
22
|
+
/**
|
|
23
|
+
* A bare colour name. Which names CSS knows is the browser's business: an
|
|
24
|
+
* unknown one paints nothing, and every name of this shape is inert inside a
|
|
25
|
+
* declaration whatever it spells.
|
|
26
|
+
*/
|
|
27
|
+
const COLOR_NAME = /^[a-zA-Z]{3,24}$/;
|
|
28
|
+
const FONT_IDENT = /^[A-Za-z0-9_-]+( [A-Za-z0-9_-]+)*$/;
|
|
29
|
+
function isColorFunction(value) {
|
|
30
|
+
const open = value.indexOf("(");
|
|
31
|
+
if (open <= 0 || !value.endsWith(")")) return false;
|
|
32
|
+
if (!COLOR_FUNCTIONS.has(value.slice(0, open))) return false;
|
|
33
|
+
const args = value.slice(open + 1, -1);
|
|
34
|
+
if (!COLOR_ARGS.test(args)) return false;
|
|
35
|
+
let depth = 0;
|
|
36
|
+
for (const char of args) if (char === "(") {
|
|
37
|
+
depth += 1;
|
|
38
|
+
if (depth > 4) return false;
|
|
39
|
+
} else if (char === ")") {
|
|
40
|
+
depth -= 1;
|
|
41
|
+
if (depth < 0) return false;
|
|
42
|
+
}
|
|
43
|
+
return depth === 0;
|
|
44
|
+
}
|
|
45
|
+
/** Whether a value is hex, a CSS colour name, or a colour function. */
|
|
46
|
+
function isBrandingColor(value) {
|
|
47
|
+
if (value.length > 128) return false;
|
|
48
|
+
if (value.trim() === "") return false;
|
|
49
|
+
if (HEX_COLOR.test(value)) return true;
|
|
50
|
+
return COLOR_NAME.test(value) || isColorFunction(value.toLowerCase());
|
|
51
|
+
}
|
|
52
|
+
/** Whether a value is a CSS font stack of identifiers and quoted names. */
|
|
53
|
+
function isBrandingFontFamily(value) {
|
|
54
|
+
if (value.length > 256) return false;
|
|
55
|
+
if (value.trim() === "") return false;
|
|
56
|
+
return value.split(",").every((family) => isFontName(trimSpaces(family)));
|
|
57
|
+
}
|
|
58
|
+
/** Spaces around a comma are ordinary; other whitespace is not a separator. */
|
|
59
|
+
function trimSpaces(value) {
|
|
60
|
+
return value.replace(/^ +/, "").replace(/ +$/, "");
|
|
61
|
+
}
|
|
62
|
+
function isFontName(family) {
|
|
63
|
+
if (family === "") return false;
|
|
64
|
+
const quote = family[0];
|
|
65
|
+
if (quote === "\"" || quote === "'") {
|
|
66
|
+
if (family.length < 2 || family.at(-1) !== quote) return false;
|
|
67
|
+
const inner = family.slice(1, -1);
|
|
68
|
+
return !inner.includes(quote) && !/[\\;{}]/.test(inner);
|
|
69
|
+
}
|
|
70
|
+
return FONT_IDENT.test(family);
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/schemas.ts
|
|
74
|
+
const schemaConfigSchema = CreateSchemaBody;
|
|
75
|
+
const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;
|
|
76
|
+
const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;
|
|
77
|
+
/** The wire shape of a branding revision (`POST /branding` request body). */
|
|
78
|
+
const brandingWireSchema = CreateBrandingBody;
|
|
79
|
+
/**
|
|
80
|
+
* `{ "$file": "<path>" }`, which a `.zitadel/` file may hold in place of a
|
|
81
|
+
* string field the OpenAPI YAML marks `x-local: file`. The path is relative to
|
|
82
|
+
* the file holding it; the CLI inlines the content before publishing.
|
|
83
|
+
*/
|
|
84
|
+
const localFileReferenceSchema = z.strictObject({ $file: z.string().min(1) });
|
|
85
|
+
/**
|
|
86
|
+
* The local `.zitadel/branding/*.json` dialect: the wire shape, where
|
|
87
|
+
* `liquid_template` may also be a `$file` reference to the sibling `.liquid`
|
|
88
|
+
* template (the meta-schema `branding.json` mirrors this).
|
|
89
|
+
*/
|
|
90
|
+
const brandingConfigSchema = z.strictObject({
|
|
91
|
+
...CreateBrandingBody.shape,
|
|
92
|
+
logo_url: z.string().optional(),
|
|
93
|
+
hero_url: z.string().optional(),
|
|
94
|
+
$schema: z.string().optional(),
|
|
95
|
+
liquid_template: z.union([CreateBrandingBody.shape.liquid_template.unwrap(), localFileReferenceSchema]).optional()
|
|
96
|
+
}).superRefine((value, ctx) => {
|
|
97
|
+
validateBrandingAssetUrl(value.typography?.font_url, "typography.font_url", ctx, { loopback: false });
|
|
98
|
+
if (value.typography?.font_url !== void 0 && value.typography.font_family === void 0) ctx.addIssue({
|
|
99
|
+
code: "custom",
|
|
100
|
+
message: "typography.font_url needs a typography.font_family to load; a stylesheet alone names no face to render in."
|
|
101
|
+
});
|
|
102
|
+
validateBrandingAssetUrl(value.logo_url, "logo_url", ctx);
|
|
103
|
+
validateBrandingAssetUrl(value.hero_url, "hero_url", ctx);
|
|
104
|
+
validateBrandingAssetUrl(value.theme?.light?.logo_url, "theme.light.logo_url", ctx);
|
|
105
|
+
validateBrandingAssetUrl(value.theme?.dark?.logo_url, "theme.dark.logo_url", ctx);
|
|
106
|
+
validateBrandingPalette(value.theme?.light?.palette, "theme.light", ctx);
|
|
107
|
+
validateBrandingPalette(value.theme?.dark?.palette, "theme.dark", ctx);
|
|
108
|
+
if (value.typography?.font_family !== void 0 && !isBrandingFontFamily(value.typography.font_family)) ctx.addIssue({
|
|
109
|
+
code: "custom",
|
|
110
|
+
message: "typography.font_family must be comma-separated font names, each an identifier (Inter, ui-sans-serif) or a quoted name (\"APK Futural\")."
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
function validateBrandingPalette(palette, side, ctx) {
|
|
114
|
+
if (!palette) return;
|
|
115
|
+
for (const [key, colour] of Object.entries(palette)) {
|
|
116
|
+
if (typeof colour !== "string") continue;
|
|
117
|
+
if (isBrandingColor(colour)) continue;
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: "custom",
|
|
120
|
+
message: `${side}.palette.${key} must be hex (#4F46E5), a CSS colour name, or a colour function such as rgb(), hsl() or oklch().`
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function validateBrandingAssetUrl(value, field, ctx, options = {}) {
|
|
125
|
+
if (value === void 0 || value === "") return;
|
|
126
|
+
if (new TextEncoder().encode(value).length > 2048) {
|
|
127
|
+
ctx.addIssue({
|
|
128
|
+
code: "custom",
|
|
129
|
+
message: `${field} must be at most ${MAX_BRANDING_URL_BYTES} bytes.`
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (/[\s\\]/.test(value)) {
|
|
134
|
+
ctx.addIssue({
|
|
135
|
+
code: "custom",
|
|
136
|
+
message: `${field} is not a valid URL.`
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!/^https?:\/\//i.test(value)) {
|
|
141
|
+
ctx.addIssue({
|
|
142
|
+
code: "custom",
|
|
143
|
+
message: `${field} must be an absolute https URL.`
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = new URL(value);
|
|
150
|
+
} catch {
|
|
151
|
+
ctx.addIssue({
|
|
152
|
+
code: "custom",
|
|
153
|
+
message: `${field} is not a valid URL.`
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
158
|
+
ctx.addIssue({
|
|
159
|
+
code: "custom",
|
|
160
|
+
message: `${field} must not carry credentials; the URL is fetched by every visitor's browser.`
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (options.loopback !== false && parsed.protocol === "http:" && isCanonicalLoopbackHttpUrl(value)) return;
|
|
165
|
+
if (parsed.protocol !== "https:" || parsed.host === "") ctx.addIssue({
|
|
166
|
+
code: "custom",
|
|
167
|
+
message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { localFileReferenceSchema as a, flowConfigSchema as i, brandingWireSchema as n, schemaConfigSchema as o, createFlowDefinitionRequestSchema as r, brandingConfigSchema as t };
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=schemas-7oNU25Qz.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas-7oNU25Qz.mjs","names":[],"sources":["../src/branding-css.ts","../src/schemas.ts"],"sourcesContent":["/**\n * Mirror of the server's appearance-value gates\n * (`internal/domain/branding_css.go`), so `plan` rejects what `apply` would.\n *\n * The widget writes these values straight into a CSS declaration, so a value\n * able to close its own declaration can emit rules of its own. Both gates are\n * allowlists: a value is accepted only when it matches a form we recognise,\n * never merely because it avoided the characters someone thought to ban.\n */\n\nexport const MAX_BRANDING_COLOR_LENGTH = 128;\nexport const MAX_BRANDING_FONT_FAMILY_LENGTH = 256;\n/** Asset URL cap in UTF-8 bytes, like the server's `MaxBrandingURLBytes`. */\nexport const MAX_BRANDING_URL_BYTES = 2048;\n\nconst HEX_COLOR = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n/** `url` is absent by construction: it is not a colour function. */\nconst COLOR_FUNCTIONS = new Set([\n \"rgb\",\n \"rgba\",\n \"hsl\",\n \"hsla\",\n \"hwb\",\n \"lab\",\n \"lch\",\n \"oklab\",\n \"oklch\",\n \"color\",\n \"color-mix\",\n]);\n\nconst COLOR_ARGS = /^[0-9a-zA-Z%.,+\\-/ ()#]*$/;\n/**\n * A bare colour name. Which names CSS knows is the browser's business: an\n * unknown one paints nothing, and every name of this shape is inert inside a\n * declaration whatever it spells.\n */\nconst COLOR_NAME = /^[a-zA-Z]{3,24}$/;\nconst FONT_IDENT = /^[A-Za-z0-9_-]+( [A-Za-z0-9_-]+)*$/;\n\nfunction isColorFunction(value: string): boolean {\n const open = value.indexOf(\"(\");\n if (open <= 0 || !value.endsWith(\")\")) return false;\n if (!COLOR_FUNCTIONS.has(value.slice(0, open))) return false;\n\n const args = value.slice(open + 1, -1);\n if (!COLOR_ARGS.test(args)) return false;\n\n // Nesting is legal (`color-mix(in srgb, rgb(1 2 3), red)`), so the\n // parentheses only have to balance, at a bounded depth.\n let depth = 0;\n for (const char of args) {\n if (char === \"(\") {\n depth += 1;\n if (depth > 4) return false;\n } else if (char === \")\") {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n return depth === 0;\n}\n\n/** Whether a value is hex, a CSS colour name, or a colour function. */\nexport function isBrandingColor(value: string): boolean {\n if (value.length > MAX_BRANDING_COLOR_LENGTH) return false;\n if (value.trim() === \"\") return false;\n // Not trimmed: the value is stored and painted verbatim.\n if (HEX_COLOR.test(value)) return true;\n return COLOR_NAME.test(value) || isColorFunction(value.toLowerCase());\n}\n\n/** Whether a value is a CSS font stack of identifiers and quoted names. */\nexport function isBrandingFontFamily(value: string): boolean {\n if (value.length > MAX_BRANDING_FONT_FAMILY_LENGTH) return false;\n if (value.trim() === \"\") return false;\n return value.split(\",\").every((family) => isFontName(trimSpaces(family)));\n}\n\n/** Spaces around a comma are ordinary; other whitespace is not a separator. */\nfunction trimSpaces(value: string): string {\n return value.replace(/^ +/, \"\").replace(/ +$/, \"\");\n}\n\nfunction isFontName(family: string): boolean {\n if (family === \"\") return false;\n const quote = family[0];\n if (quote === '\"' || quote === \"'\") {\n // A quoted name ends at its matching quote and holds no other, so it\n // cannot leave the string it opened.\n if (family.length < 2 || family.at(-1) !== quote) return false;\n const inner = family.slice(1, -1);\n return !inner.includes(quote) && !/[\\\\;{}]/.test(inner);\n }\n return FONT_IDENT.test(family);\n}\n","import {\n CreateBrandingBody,\n CreateFlowDefinitionBody,\n CreateSchemaBody,\n} from \"@zitadel/api/generated/endpoints/zitadelNextGen.zod\";\nimport { z } from \"zod\";\n\nimport {\n isBrandingColor,\n isBrandingFontFamily,\n MAX_BRANDING_URL_BYTES,\n} from \"./branding-css.js\";\nimport { isCanonicalLoopbackHttpUrl } from \"./branding-url.js\";\n\nexport const schemaConfigSchema = CreateSchemaBody;\nexport const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;\nexport const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;\n\n/** The wire shape of a branding revision (`POST /branding` request body). */\nexport const brandingWireSchema = CreateBrandingBody;\n\n/**\n * `{ \"$file\": \"<path>\" }`, which a `.zitadel/` file may hold in place of a\n * string field the OpenAPI YAML marks `x-local: file`. The path is relative to\n * the file holding it; the CLI inlines the content before publishing.\n */\nexport const localFileReferenceSchema = z.strictObject({ $file: z.string().min(1) });\n\n/**\n * The local `.zitadel/branding/*.json` dialect: the wire shape, where\n * `liquid_template` may also be a `$file` reference to the sibling `.liquid`\n * template (the meta-schema `branding.json` mirrors this).\n */\nexport const brandingConfigSchema = z\n .strictObject({\n // Strict, unlike the generated wire schema: a typo like `hero_urll`\n // must fail plan, not silently pass, get ignored by the server, and\n // vanish on canonical write-back. The editor meta-schema already\n // rejects unknown keys; the CLI gate has to agree.\n ...CreateBrandingBody.shape,\n // Raw strings instead of the generated uri fields: those trim and\n // normalize before refinement runs, hiding exactly the forms the wire\n // body still carries verbatim and Go's url.Parse rejects.\n logo_url: z.string().optional(),\n hero_url: z.string().optional(),\n $schema: z.string().optional(),\n liquid_template: z\n .union([CreateBrandingBody.shape.liquid_template.unwrap(), localFileReferenceSchema])\n .optional(),\n })\n .superRefine((value, ctx) => {\n // A stylesheet alone names no face to render in, so the server rejects the\n // pair as a pair (validateBrandingFontURL in\n // internal/domain/branding_validator.go). plan has to agree, or apply\n // fails after schemas and flows have already been written.\n validateBrandingAssetUrl(value.typography?.font_url, \"typography.font_url\", ctx, {\n loopback: false,\n });\n if (value.typography?.font_url !== undefined && value.typography.font_family === undefined) {\n ctx.addIssue({\n code: \"custom\",\n message:\n \"typography.font_url needs a typography.font_family to load; a stylesheet alone names no face to render in.\",\n });\n }\n // Mirror the server's asset URL gate (validateBrandingAssetURL in\n // internal/domain/branding_validator.go) so plan rejects what apply would.\n // Apply mutates schemas and flows before branding, so a late 400 here would\n // leave a half-applied run.\n validateBrandingAssetUrl(value.logo_url, \"logo_url\", ctx);\n validateBrandingAssetUrl(value.hero_url, \"hero_url\", ctx);\n validateBrandingAssetUrl(value.theme?.light?.logo_url, \"theme.light.logo_url\", ctx);\n validateBrandingAssetUrl(value.theme?.dark?.logo_url, \"theme.dark.logo_url\", ctx);\n // The appearance values land in a CSS declaration, so the server stores\n // only forms it recognises (ValidateBrandingColor / ValidateBrandingFontFamily\n // in internal/domain/branding_css.go).\n validateBrandingPalette(value.theme?.light?.palette, \"theme.light\", ctx);\n validateBrandingPalette(value.theme?.dark?.palette, \"theme.dark\", ctx);\n if (value.typography?.font_family !== undefined && !isBrandingFontFamily(value.typography.font_family)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n 'typography.font_family must be comma-separated font names, each an identifier (Inter, ui-sans-serif) or a quoted name (\"APK Futural\").',\n });\n }\n });\n\nfunction validateBrandingPalette(\n palette: Record<string, unknown> | undefined,\n side: string,\n ctx: z.RefinementCtx,\n): void {\n if (!palette) {\n return;\n }\n for (const [key, colour] of Object.entries(palette)) {\n // A non-string never reaches here: the generated object schema types every\n // palette value as a string, and Zod skips refinements once the base parse\n // has failed. The check is what narrows `unknown` for the call below.\n if (typeof colour !== \"string\") {\n continue;\n }\n if (isBrandingColor(colour)) {\n continue;\n }\n ctx.addIssue({\n code: \"custom\",\n message: `${side}.palette.${key} must be hex (#4F46E5), a CSS colour name, or a colour function such as rgb(), hsl() or oklch().`,\n });\n }\n}\n\nfunction validateBrandingAssetUrl(\n value: string | undefined,\n field: string,\n ctx: z.RefinementCtx,\n options: { loopback?: boolean } = {},\n): void {\n if (value === undefined || value === \"\") {\n return;\n }\n // The server counts bytes (validateBrandingAssetURL), so a URL with\n // non-ASCII characters can be short in characters and still too long.\n if (new TextEncoder().encode(value).length > MAX_BRANDING_URL_BYTES) {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must be at most ${MAX_BRANDING_URL_BYTES} bytes.`,\n });\n return;\n }\n // Stricter-or-equal than the Go gate (validateBrandingAssetURL): the WHATWG\n // parser forgives what Go's url.Parse rejects — `https:example.com`,\n // backslashes, and whitespace all get silently normalized. plan must never\n // accept what apply would reject (the reverse is harmless), so reject the\n // raw forms before parsing.\n if (/[\\s\\\\]/.test(value)) {\n ctx.addIssue({ code: \"custom\", message: `${field} is not a valid URL.` });\n return;\n }\n if (!/^https?:\\/\\//i.test(value)) {\n ctx.addIssue({ code: \"custom\", message: `${field} must be an absolute https URL.` });\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n ctx.addIssue({ code: \"custom\", message: `${field} is not a valid URL.` });\n return;\n }\n // Fetched by every visitor's browser, so userinfo in it is a credential in\n // every access log — the server refuses it and so must plan.\n if (parsed.username !== \"\" || parsed.password !== \"\") {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must not carry credentials; the URL is fetched by every visitor's browser.`,\n });\n return;\n }\n // Loopback HTTP is the dev-posture carve-out (assets served from the app's\n // own dev server), and it covers images only: a stylesheet is styling rather\n // than an image, so it stays https everywhere. Check the raw URL so WHATWG\n // normalisation cannot make plan accept a host spelling that the Go save\n // gate rejects.\n if (options.loopback !== false && parsed.protocol === \"http:\" && isCanonicalLoopbackHttpUrl(value)) {\n return;\n }\n if (parsed.protocol !== \"https:\" || parsed.host === \"\") {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`,\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/schemas.d.mts
CHANGED
|
@@ -7,14 +7,21 @@ declare const createFlowDefinitionRequestSchema: any;
|
|
|
7
7
|
/** The wire shape of a branding revision (`POST /branding` request body). */
|
|
8
8
|
declare const brandingWireSchema: any;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
10
|
+
* `{ "$file": "<path>" }`, which a `.zitadel/` file may hold in place of a
|
|
11
|
+
* string field the OpenAPI YAML marks `x-local: file`. The path is relative to
|
|
12
|
+
* the file holding it; the CLI inlines the content before publishing.
|
|
13
|
+
*/
|
|
14
|
+
declare const localFileReferenceSchema: z.ZodObject<{
|
|
15
|
+
$file: z.ZodString;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
/**
|
|
18
|
+
* The local `.zitadel/branding/*.json` dialect: the wire shape, where
|
|
19
|
+
* `liquid_template` may also be a `$file` reference to the sibling `.liquid`
|
|
20
|
+
* template (the meta-schema `branding.json` mirrors this).
|
|
14
21
|
*/
|
|
15
22
|
declare const brandingConfigSchema: z.ZodObject<{
|
|
16
23
|
[x: string]: any;
|
|
17
24
|
}, z.core.$strict>;
|
|
18
25
|
//#endregion
|
|
19
|
-
export { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, schemaConfigSchema };
|
|
26
|
+
export { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, localFileReferenceSchema, schemaConfigSchema };
|
|
20
27
|
//# sourceMappingURL=schemas.d.mts.map
|
package/dist/schemas.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.mts","names":[],"sources":["../src/schemas.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"schemas.d.mts","names":[],"sources":["../src/schemas.ts"],"mappings":";;;cAca,kBAAA;AAAA,cACA,gBAAA;AAAA,cACA,iCAAA;;cAGA,kBAAA;;;AAJb;;;cAWa,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;AAPrC;cAca,oBAAA,EAAoB,CAAA,CAAA,SAAA;EAAA;GAoD7B,CAAA,CAAA,IAAA,CAAA,OAAA"}
|
package/dist/schemas.mjs
CHANGED
|
@@ -1,69 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
//#region src/schemas.ts
|
|
5
|
-
const schemaConfigSchema = CreateSchemaBody;
|
|
6
|
-
const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;
|
|
7
|
-
const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;
|
|
8
|
-
/** The wire shape of a branding revision (`POST /branding` request body). */
|
|
9
|
-
const brandingWireSchema = CreateBrandingBody;
|
|
10
|
-
/**
|
|
11
|
-
* The local `.zitadel/branding/*.json` dialect: the wire shape plus
|
|
12
|
-
* `liquid_template_file`, a descriptor-relative path the CLI inlines into
|
|
13
|
-
* `liquid_template` when publishing. Exactly one of the two template carriers
|
|
14
|
-
* may be present (the meta-schema `branding.json` mirrors this).
|
|
15
|
-
*/
|
|
16
|
-
const brandingConfigSchema = z.strictObject({
|
|
17
|
-
...CreateBrandingBody.shape,
|
|
18
|
-
logo_url: z.string().optional(),
|
|
19
|
-
hero_url: z.string().optional(),
|
|
20
|
-
$schema: z.string().optional(),
|
|
21
|
-
liquid_template_file: z.string().min(1).optional()
|
|
22
|
-
}).superRefine((value, ctx) => {
|
|
23
|
-
if (value.liquid_template !== void 0 && value.liquid_template_file !== void 0) ctx.addIssue({
|
|
24
|
-
code: "custom",
|
|
25
|
-
message: "Use either liquid_template_file or an inline liquid_template, not both."
|
|
26
|
-
});
|
|
27
|
-
if (value.font_url !== void 0) ctx.addIssue({
|
|
28
|
-
code: "custom",
|
|
29
|
-
message: "font_url is not writable yet (tenant font delivery needs a safe design, see ADR 040); load fonts from the embedding page instead."
|
|
30
|
-
});
|
|
31
|
-
validateBrandingAssetUrl(value.logo_url, "logo_url", ctx);
|
|
32
|
-
validateBrandingAssetUrl(value.hero_url, "hero_url", ctx);
|
|
33
|
-
});
|
|
34
|
-
function validateBrandingAssetUrl(value, field, ctx) {
|
|
35
|
-
if (value === void 0 || value === "") return;
|
|
36
|
-
if (/[\s\\]/.test(value)) {
|
|
37
|
-
ctx.addIssue({
|
|
38
|
-
code: "custom",
|
|
39
|
-
message: `${field} is not a valid URL.`
|
|
40
|
-
});
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (!/^https?:\/\//i.test(value)) {
|
|
44
|
-
ctx.addIssue({
|
|
45
|
-
code: "custom",
|
|
46
|
-
message: `${field} must be an absolute https URL.`
|
|
47
|
-
});
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
let parsed;
|
|
51
|
-
try {
|
|
52
|
-
parsed = new URL(value);
|
|
53
|
-
} catch {
|
|
54
|
-
ctx.addIssue({
|
|
55
|
-
code: "custom",
|
|
56
|
-
message: `${field} is not a valid URL.`
|
|
57
|
-
});
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (parsed.protocol === "http:" && isCanonicalLoopbackHttpUrl(value)) return;
|
|
61
|
-
if (parsed.protocol !== "https:" || parsed.host === "") ctx.addIssue({
|
|
62
|
-
code: "custom",
|
|
63
|
-
message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
|
-
export { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, schemaConfigSchema };
|
|
68
|
-
|
|
69
|
-
//# sourceMappingURL=schemas.mjs.map
|
|
1
|
+
import "./branding-url.mjs";
|
|
2
|
+
import { a as localFileReferenceSchema, i as flowConfigSchema, n as brandingWireSchema, o as schemaConfigSchema, r as createFlowDefinitionRequestSchema, t as brandingConfigSchema } from "./schemas-7oNU25Qz.mjs";
|
|
3
|
+
export { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, localFileReferenceSchema, schemaConfigSchema };
|
package/dist/validate.d.mts
CHANGED
|
@@ -76,10 +76,16 @@ declare const FLOW_VALIDATION_RULES: {
|
|
|
76
76
|
};
|
|
77
77
|
type FlowValidationRuleId = keyof typeof FLOW_VALIDATION_RULES;
|
|
78
78
|
/** Mirrors `reservedOutcomes` in flow_definition_validator.go. */
|
|
79
|
-
declare const RESERVED_OUTCOMES: readonly ["user_not_found", "user_already_exists", "callback"];
|
|
79
|
+
declare const RESERVED_OUTCOMES: readonly ["user_not_found", "user_already_exists", "identity_unknown", "callback"];
|
|
80
80
|
/** Mirrors the `FlowDefinitionPurpose` enum (snake transform) in flow_definition.go. */
|
|
81
81
|
declare const FLOW_PURPOSES: readonly ["login", "register", "recovery", "profiling", "reauth", "link_account"];
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Mirrors `purposeFlipTargets` in flow_definition_validator.go: the
|
|
84
|
+
* identifier outcomes only. `identity_unknown` comes only from SSO
|
|
85
|
+
* resolution and is left out here; requiring it on every combined entry
|
|
86
|
+
* step would reject the shipped default flow. A rule on steps carrying
|
|
87
|
+
* sso_providers is future work (#1044).
|
|
88
|
+
*/
|
|
83
89
|
declare const PURPOSE_FLIP_TARGETS: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
84
90
|
/**
|
|
85
91
|
* Validate a flow-definition config body (the `.zitadel/flows/*.json`
|
package/dist/validate.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.mts","names":[],"sources":["../src/validate.ts"],"mappings":";;AA8BA;;;;;AAEA;;;;;;;;;;;;AAcA;;;;;;;;;;;KAhBY,sBAAA;AAAA,KAEA,mBAAA;EACV,QAAA,EAAU,sBAAA;EAEV,IAAA,EAAM,oBAAA;EAEN,OAAA;EAEA,IAAA;AAAA;;;;;cAOW,qBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAcD,oBAAA,gBAAoC,qBAAA;;cAGnC,iBAAA;;
|
|
1
|
+
{"version":3,"file":"validate.d.mts","names":[],"sources":["../src/validate.ts"],"mappings":";;AA8BA;;;;;AAEA;;;;;;;;;;;;AAcA;;;;;;;;;;;KAhBY,sBAAA;AAAA,KAEA,mBAAA;EACV,QAAA,EAAU,sBAAA;EAEV,IAAA,EAAM,oBAAA;EAEN,OAAA;EAEA,IAAA;AAAA;;;;;cAOW,qBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAcD,oBAAA,gBAAoC,qBAAA;;cAGnC,iBAAA;;cAQA,aAAA;;;;;;;;cAgBA,oBAAA,EAAsB,QAAA,CAAS,MAAA,SAAe,QAAA,CAAS,MAAA;;;;;;;;iBAoCpD,sBAAA,CAAuB,IAAA,UAAc,MAAA,YAAkB,mBAAA"}
|
package/dist/validate.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const FLOW_VALIDATION_RULES = {
|
|
|
20
20
|
const RESERVED_OUTCOMES = [
|
|
21
21
|
"user_not_found",
|
|
22
22
|
"user_already_exists",
|
|
23
|
+
"identity_unknown",
|
|
23
24
|
"callback"
|
|
24
25
|
];
|
|
25
26
|
/** Mirrors the `FlowDefinitionPurpose` enum (snake transform) in flow_definition.go. */
|
|
@@ -31,7 +32,13 @@ const FLOW_PURPOSES = [
|
|
|
31
32
|
"reauth",
|
|
32
33
|
"link_account"
|
|
33
34
|
];
|
|
34
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Mirrors `purposeFlipTargets` in flow_definition_validator.go: the
|
|
37
|
+
* identifier outcomes only. `identity_unknown` comes only from SSO
|
|
38
|
+
* resolution and is left out here; requiring it on every combined entry
|
|
39
|
+
* step would reject the shipped default flow. A rule on steps carrying
|
|
40
|
+
* sso_providers is future work (#1044).
|
|
41
|
+
*/
|
|
35
42
|
const PURPOSE_FLIP_TARGETS = {
|
|
36
43
|
login: { user_not_found: "register" },
|
|
37
44
|
register: { user_already_exists: "login" }
|
|
@@ -188,7 +195,7 @@ function validateStep(step) {
|
|
|
188
195
|
if (step.fields.length === 0 && step.actions.length === 0 && step.ssoProviderCount === 0 && step.gateCount === 0 && !hasCallback) issues.push(error("steps", `step ${q(name)} is non-terminal but has no fields, actions, sso_providers, gates, or transitions.callback`, name));
|
|
189
196
|
if (step.ssoProviderCount > 0 && !hasCallback) issues.push(error("steps", `step ${q(name)}: has sso_providers but is missing transitions.callback`, name));
|
|
190
197
|
for (const actionName of actionNames) if (!step.transitions.has(actionName)) issues.push(error("steps", `step ${q(name)}: action ${q(actionName)} has no matching transition`, name));
|
|
191
|
-
for (const transitionKey of step.transitions.keys()) if (!actionNames.has(transitionKey) && !RESERVED_OUTCOMES.includes(transitionKey)) issues.push(error("steps", `step ${q(name)}: transition key ${q(transitionKey)} is not an action name or reserved outcome (user_not_found, user_already_exists, callback)`, name));
|
|
198
|
+
for (const transitionKey of step.transitions.keys()) if (!actionNames.has(transitionKey) && !RESERVED_OUTCOMES.includes(transitionKey)) issues.push(error("steps", `step ${q(name)}: transition key ${q(transitionKey)} is not an action name or reserved outcome (user_not_found, user_already_exists, identity_unknown, callback)`, name));
|
|
192
199
|
return issues;
|
|
193
200
|
}
|
|
194
201
|
/** A transition with no cross-flow action targets a step in this flow. */
|