@zitadel/cli 1.0.0-alpha.22 → 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.
Files changed (66) hide show
  1. package/README.md +2248 -227
  2. package/SKILLS.md +237 -65
  3. package/dist/groups-CMB0fMzE.mjs +22 -0
  4. package/dist/groups-CMB0fMzE.mjs.map +1 -0
  5. package/dist/index.mjs +16027 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/dist/lib/oclif/help.mjs +67 -0
  8. package/dist/lib/oclif/help.mjs.map +1 -0
  9. package/oclif.manifest.json +4577 -185
  10. package/package.json +40 -8
  11. package/dist/box-B_mwKole.mjs +0 -35
  12. package/dist/box-B_mwKole.mjs.map +0 -1
  13. package/dist/branding-DvoH8e0D.mjs +0 -79
  14. package/dist/branding-DvoH8e0D.mjs.map +0 -1
  15. package/dist/claim-state-B6ZSKmhW.mjs +0 -125
  16. package/dist/claim-state-B6ZSKmhW.mjs.map +0 -1
  17. package/dist/commands/apply.mjs +0 -81
  18. package/dist/commands/apply.mjs.map +0 -1
  19. package/dist/commands/branding/eject.mjs +0 -104
  20. package/dist/commands/branding/eject.mjs.map +0 -1
  21. package/dist/commands/claim.mjs +0 -445
  22. package/dist/commands/claim.mjs.map +0 -1
  23. package/dist/commands/doctor.mjs +0 -1138
  24. package/dist/commands/doctor.mjs.map +0 -1
  25. package/dist/commands/eject.mjs +0 -157
  26. package/dist/commands/eject.mjs.map +0 -1
  27. package/dist/commands/logs.mjs +0 -58
  28. package/dist/commands/logs.mjs.map +0 -1
  29. package/dist/commands/plan.mjs +0 -65
  30. package/dist/commands/plan.mjs.map +0 -1
  31. package/dist/commands/reset.mjs +0 -79
  32. package/dist/commands/reset.mjs.map +0 -1
  33. package/dist/commands/schemas/list.mjs +0 -146
  34. package/dist/commands/schemas/list.mjs.map +0 -1
  35. package/dist/commands/setup.mjs +0 -1309
  36. package/dist/commands/setup.mjs.map +0 -1
  37. package/dist/commands/start.mjs +0 -288
  38. package/dist/commands/start.mjs.map +0 -1
  39. package/dist/commands/status.mjs +0 -151
  40. package/dist/commands/status.mjs.map +0 -1
  41. package/dist/commands/stop.mjs +0 -105
  42. package/dist/commands/stop.mjs.map +0 -1
  43. package/dist/designs-Ckz18Dpo.mjs +0 -38
  44. package/dist/designs-Ckz18Dpo.mjs.map +0 -1
  45. package/dist/docker-B1MjOMFo.mjs +0 -436
  46. package/dist/docker-B1MjOMFo.mjs.map +0 -1
  47. package/dist/environment-fibXmzM8.mjs +0 -17
  48. package/dist/environment-fibXmzM8.mjs.map +0 -1
  49. package/dist/journey-guidance-C9MxJNTg.mjs +0 -40
  50. package/dist/journey-guidance-C9MxJNTg.mjs.map +0 -1
  51. package/dist/oclif-JlsN1lfO.mjs +0 -1769
  52. package/dist/oclif-JlsN1lfO.mjs.map +0 -1
  53. package/dist/orca-CPMHzmg3.mjs +0 -3917
  54. package/dist/orca-CPMHzmg3.mjs.map +0 -1
  55. package/dist/package-manager-CDUsoGBy.mjs +0 -194
  56. package/dist/package-manager-CDUsoGBy.mjs.map +0 -1
  57. package/dist/ports-hidBnSW-.mjs +0 -116
  58. package/dist/ports-hidBnSW-.mjs.map +0 -1
  59. package/dist/processes-Cv19mJ2I.mjs +0 -120
  60. package/dist/processes-Cv19mJ2I.mjs.map +0 -1
  61. package/dist/project-Ba0nriNm.mjs +0 -140
  62. package/dist/project-Ba0nriNm.mjs.map +0 -1
  63. package/dist/sync-ChlLQ4vZ.mjs +0 -1633
  64. package/dist/sync-ChlLQ4vZ.mjs.map +0 -1
  65. package/dist/user-schema-C2mC-JTN.mjs +0 -91
  66. package/dist/user-schema-C2mC-JTN.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zitadel/cli",
3
- "version": "1.0.0-alpha.22",
3
+ "version": "1.0.0-alpha.23",
4
4
  "description": "Agent-friendly Zitadel CLI",
5
5
  "homepage": "https://github.com/zitadel/nextgen/tree/main/apps/cli#readme",
6
6
  "bugs": {
@@ -30,7 +30,13 @@
30
30
  "oclif": {
31
31
  "bin": "zitadel",
32
32
  "dirname": "zitadel",
33
- "commands": "./dist/commands",
33
+ "description": "Manage your Zitadel project and local identity server from the command line.",
34
+ "commands": {
35
+ "strategy": "explicit",
36
+ "target": "./dist/index.mjs",
37
+ "identifier": "COMMANDS"
38
+ },
39
+ "helpClass": "./dist/lib/oclif/help",
34
40
  "topicSeparator": " ",
35
41
  "plugins": [
36
42
  "@oclif/plugin-help",
@@ -40,7 +46,33 @@
40
46
  "@oclif/plugin-autocomplete",
41
47
  "@oclif/plugin-search",
42
48
  "@oclif/plugin-version"
43
- ]
49
+ ],
50
+ "topics": {
51
+ "users": {
52
+ "description": "Manage the users of the project."
53
+ },
54
+ "teams": {
55
+ "description": "Manage the teams of the project."
56
+ },
57
+ "sessions": {
58
+ "description": "Inspect and revoke the sessions of the project."
59
+ },
60
+ "events": {
61
+ "description": "Read the audit event stream of the project."
62
+ },
63
+ "grants": {
64
+ "description": "Manage collaboration grants on the project."
65
+ },
66
+ "projects": {
67
+ "description": "Inspect and update the projects visible to this credential."
68
+ },
69
+ "schemas": {
70
+ "description": "Inspect user-schema revisions."
71
+ },
72
+ "branding": {
73
+ "description": "Work with login branding revisions."
74
+ }
75
+ }
44
76
  },
45
77
  "type": "module",
46
78
  "dependencies": {
@@ -63,9 +95,9 @@
63
95
  "wrap-ansi": "^7.0.0",
64
96
  "zod": "^4.3.6",
65
97
  "picocolors": "^1.1.1",
66
- "@zitadel/api": "1.0.0-alpha.22",
67
- "@zitadel/config": "1.0.0-alpha.22",
68
- "@zitadel/server": "1.0.0-alpha.22"
98
+ "@zitadel/api": "1.0.0-alpha.23",
99
+ "@zitadel/config": "1.0.0-alpha.23",
100
+ "@zitadel/server": "1.0.0-alpha.23"
69
101
  },
70
102
  "devDependencies": {
71
103
  "@types/node": "^25.6.0",
@@ -76,11 +108,11 @@
76
108
  "tsdown": "^0.21.10",
77
109
  "vitest": "^3.0.0",
78
110
  "@zitadel/api-mock": "0.0.0",
79
- "@zitadel/sdk-next": "1.0.0-alpha.22"
111
+ "@zitadel/sdk-next": "1.0.0-alpha.23"
80
112
  },
81
113
  "scripts": {
82
114
  "build": "tsdown",
83
- "typecheck": "tsc --noEmit",
115
+ "typecheck": "tsc --build tsconfig.app.json",
84
116
  "test": "vitest run",
85
117
  "readme": "oclif readme"
86
118
  }
@@ -1,35 +0,0 @@
1
- import pc from "picocolors";
2
- import wrapAnsi from "wrap-ansi";
3
- //#region src/lib/box.ts
4
- /**
5
- * Frame overhead of a `consola.box` with `padding: 1`: two border columns,
6
- * the padding columns, and consola's left margin. Content wrapped to the
7
- * terminal width minus this stays inside the frame instead of soft-wrapping
8
- * under the right border.
9
- */
10
- const BOX_OVERHEAD = 8;
11
- const MIN_CONTENT_WIDTH = 20;
12
- const MAX_TERMINAL_WIDTH = 80;
13
- /** Bold cyan, for commands the user should run (URLs/paths stay plain cyan). */
14
- const command = (s) => pc.bold(pc.cyan(s));
15
- /**
16
- * Wraps box content to the terminal (capped at 80 columns) so consola's
17
- * frame, which sizes itself to the longest content line, never exceeds the
18
- * window. ANSI-aware, preserves hard line breaks and indentation, and never
19
- * splits a token: URLs and commands stay whole for clicking, copying, and the
20
- * journey e2e scrape (claim.spec.ts), even when such a token alone is wider
21
- * than a very narrow window.
22
- */
23
- function wrapForBox(message, columns = process.stdout.columns) {
24
- return wrapAnsi(message, Math.max(MIN_CONTENT_WIDTH, Math.min(columns ?? MAX_TERMINAL_WIDTH, MAX_TERMINAL_WIDTH) - BOX_OVERHEAD), {
25
- hard: false,
26
- trim: false
27
- });
28
- }
29
- function renderBoxActions(actions) {
30
- return actions.map((action) => action.command ? `${action.text}\n\n ${command(action.command)}` : action.text).join("\n\n");
31
- }
32
- //#endregion
33
- export { wrapForBox as n, renderBoxActions as t };
34
-
35
- //# sourceMappingURL=box-B_mwKole.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"box-B_mwKole.mjs","names":[],"sources":["../src/lib/box.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport wrapAnsi from \"wrap-ansi\";\n\n/**\n * Frame overhead of a `consola.box` with `padding: 1`: two border columns,\n * the padding columns, and consola's left margin. Content wrapped to the\n * terminal width minus this stays inside the frame instead of soft-wrapping\n * under the right border.\n */\nconst BOX_OVERHEAD = 8;\nconst MIN_CONTENT_WIDTH = 20;\nconst MAX_TERMINAL_WIDTH = 80;\n\n/** Bold cyan, for commands the user should run (URLs/paths stay plain cyan). */\nexport const command = (s: string): string => pc.bold(pc.cyan(s));\n\n/**\n * Wraps box content to the terminal (capped at 80 columns) so consola's\n * frame, which sizes itself to the longest content line, never exceeds the\n * window. ANSI-aware, preserves hard line breaks and indentation, and never\n * splits a token: URLs and commands stay whole for clicking, copying, and the\n * journey e2e scrape (claim.spec.ts), even when such a token alone is wider\n * than a very narrow window.\n */\nexport function wrapForBox(message: string, columns = process.stdout.columns): string {\n const width = Math.max(\n MIN_CONTENT_WIDTH,\n Math.min(columns ?? MAX_TERMINAL_WIDTH, MAX_TERMINAL_WIDTH) - BOX_OVERHEAD,\n );\n return wrapAnsi(message, width, { hard: false, trim: false });\n}\n\n/**\n * A next-step line for the human box: prose plus, when the step is literally\n * a command to run, that command pulled out of the sentence. The JSON\n * envelope keeps its own plain `next_actions`/`next_commands` strings; this\n * shape exists only so {@link renderBoxActions} can put the command alone on\n * an indented styled line, where it stands out and a drag-select copies it\n * without surrounding prose. (Line selection such as triple-click still\n * includes the box border and padding — commands that must copy as a whole\n * physical line belong outside a frame, like the claim URL.)\n */\nexport type BoxAction = { text: string; command?: string };\n\nexport function renderBoxActions(actions: BoxAction[]): string {\n return actions\n .map((action) =>\n action.command ? `${action.text}\\n\\n ${command(action.command)}` : action.text,\n )\n .join(\"\\n\\n\");\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,eAAe;AACrB,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;;AAG3B,MAAa,WAAW,MAAsB,GAAG,KAAK,GAAG,KAAK,EAAE,CAAC;;;;;;;;;AAUjE,SAAgB,WAAW,SAAiB,UAAU,QAAQ,OAAO,SAAiB;AAKpF,QAAO,SAAS,SAJF,KAAK,IACjB,mBACA,KAAK,IAAI,WAAW,oBAAoB,mBAAmB,GAAG,aAElC,EAAE;EAAE,MAAM;EAAO,MAAM;EAAO,CAAC;;AAe/D,SAAgB,iBAAiB,SAA8B;AAC7D,QAAO,QACJ,KAAK,WACJ,OAAO,UAAU,GAAG,OAAO,KAAK,QAAQ,QAAQ,OAAO,QAAQ,KAAK,OAAO,KAC5E,CACA,KAAK,OAAO"}
@@ -1,79 +0,0 @@
1
- import { E as ZitadelError } from "./oclif-JlsN1lfO.mjs";
2
- import { isAbsolute, join, relative, resolve } from "node:path";
3
- import { readFileSync } from "node:fs";
4
- //#region src/lib/branding/index.ts
5
- /**
6
- * Relative directory (from the project root) where local branding files live:
7
- * the `branding.json` descriptor plus the sibling `.liquid` template it
8
- * references. Owned here so commands, syncers, and tests share one source of
9
- * truth; only `*.json` files are sync-discovered, the template rides along
10
- * via `liquid_template_file`.
11
- */
12
- const BRANDING_DIR = ".zitadel/branding";
13
- /**
14
- * Resolves a descriptor's `liquid_template_file` reference to an absolute
15
- * path. References are relative to the descriptor's directory (all
16
- * descriptors live flat in {@link BRANDING_DIR}) and must stay inside the
17
- * project — a reference escaping `cwd` is an error because apply would later
18
- * write server state back to that path.
19
- */
20
- function resolveTemplatePath(cwd, ref) {
21
- const absolute = isAbsolute(ref) ? ref : resolve(join(cwd, BRANDING_DIR), ref);
22
- const rel = relative(cwd, absolute);
23
- if (rel.startsWith("..") || isAbsolute(rel)) throw new ZitadelError("E_VALIDATION", `liquid_template_file ${JSON.stringify(ref)} points outside the project`, { hint: `Keep the template next to its descriptor in ${BRANDING_DIR}/.` });
24
- return absolute;
25
- }
26
- /**
27
- * Returns the template string a descriptor carries — the inline
28
- * `liquid_template`, or the content of `liquid_template_file`. Returns
29
- * undefined when the descriptor has neither (a legal descriptor that only
30
- * sets layout/asset URLs).
31
- */
32
- function readDescriptorTemplate(cwd, data) {
33
- const descriptor = data;
34
- if (typeof descriptor.liquid_template === "string") return descriptor.liquid_template;
35
- if (typeof descriptor.liquid_template_file !== "string") return;
36
- const path = resolveTemplatePath(cwd, descriptor.liquid_template_file);
37
- try {
38
- return readFileSync(path, "utf8");
39
- } catch (error) {
40
- throw new ZitadelError("E_VALIDATION", `liquid_template_file ${JSON.stringify(descriptor.liquid_template_file)} cannot be read`, {
41
- hint: "Create the template file or fix the reference; the `branding eject` command scaffolds a starting point.",
42
- details: { cause: error instanceof Error ? error.message : String(error) }
43
- });
44
- }
45
- }
46
- /**
47
- * Converts a local descriptor to the wire body of `POST /branding`: strips
48
- * the editor `$schema` affordance and replaces `liquid_template_file` with
49
- * the inlined template content. Non-throwing on a missing template file (the
50
- * field is simply left out) — `validate` reports that case with a hint
51
- * before any planning happens.
52
- */
53
- function toBrandingWireBody(cwd, data) {
54
- const { $schema, liquid_template_file, ...rest } = data;
55
- const out = { ...rest };
56
- if (typeof liquid_template_file === "string" && out.liquid_template === void 0) try {
57
- out.liquid_template = readFileSync(resolveTemplatePath(cwd, liquid_template_file), "utf8");
58
- } catch {}
59
- return out;
60
- }
61
- /**
62
- * Converts the server's canonical wire body back to the local descriptor
63
- * form: when the local descriptor references a template file, the template
64
- * string moves back out of the JSON into that reference. The caller writes
65
- * the template content to the referenced file separately.
66
- */
67
- function toLocalBrandingBody(canonicalWire, localData) {
68
- const local = localData;
69
- if (typeof local.liquid_template_file !== "string") return canonicalWire;
70
- const { liquid_template, ...rest } = canonicalWire;
71
- return {
72
- ...rest,
73
- liquid_template_file: local.liquid_template_file
74
- };
75
- }
76
- //#endregion
77
- export { toLocalBrandingBody as a, toBrandingWireBody as i, readDescriptorTemplate as n, resolveTemplatePath as r, BRANDING_DIR as t };
78
-
79
- //# sourceMappingURL=branding-DvoH8e0D.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branding-DvoH8e0D.mjs","names":[],"sources":["../src/lib/branding/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\n\nimport { ZitadelError } from \"../errors\";\n\n/**\n * Relative directory (from the project root) where local branding files live:\n * the `branding.json` descriptor plus the sibling `.liquid` template it\n * references. Owned here so commands, syncers, and tests share one source of\n * truth; only `*.json` files are sync-discovered, the template rides along\n * via `liquid_template_file`.\n */\nexport const BRANDING_DIR = \".zitadel/branding\";\n\ntype BrandingDescriptor = {\n liquid_template?: string;\n liquid_template_file?: string;\n [key: string]: unknown;\n};\n\n/**\n * Resolves a descriptor's `liquid_template_file` reference to an absolute\n * path. References are relative to the descriptor's directory (all\n * descriptors live flat in {@link BRANDING_DIR}) and must stay inside the\n * project — a reference escaping `cwd` is an error because apply would later\n * write server state back to that path.\n */\nexport function resolveTemplatePath(cwd: string, ref: string): string {\n const absolute = isAbsolute(ref) ? ref : resolve(join(cwd, BRANDING_DIR), ref);\n const rel = relative(cwd, absolute);\n if (rel.startsWith(\"..\") || isAbsolute(rel)) {\n throw new ZitadelError(\n \"E_VALIDATION\",\n `liquid_template_file ${JSON.stringify(ref)} points outside the project`,\n { hint: `Keep the template next to its descriptor in ${BRANDING_DIR}/.` },\n );\n }\n return absolute;\n}\n\n/**\n * Returns the template string a descriptor carries — the inline\n * `liquid_template`, or the content of `liquid_template_file`. Returns\n * undefined when the descriptor has neither (a legal descriptor that only\n * sets layout/asset URLs).\n */\nexport function readDescriptorTemplate(cwd: string, data: object): string | undefined {\n const descriptor = data as BrandingDescriptor;\n if (typeof descriptor.liquid_template === \"string\") {\n return descriptor.liquid_template;\n }\n if (typeof descriptor.liquid_template_file !== \"string\") {\n return undefined;\n }\n const path = resolveTemplatePath(cwd, descriptor.liquid_template_file);\n try {\n return readFileSync(path, \"utf8\");\n } catch (error) {\n throw new ZitadelError(\n \"E_VALIDATION\",\n `liquid_template_file ${JSON.stringify(descriptor.liquid_template_file)} cannot be read`,\n {\n hint: \"Create the template file or fix the reference; the `branding eject` command scaffolds a starting point.\",\n details: { cause: error instanceof Error ? error.message : String(error) },\n },\n );\n }\n}\n\n/**\n * Converts a local descriptor to the wire body of `POST /branding`: strips\n * the editor `$schema` affordance and replaces `liquid_template_file` with\n * the inlined template content. Non-throwing on a missing template file (the\n * field is simply left out) — `validate` reports that case with a hint\n * before any planning happens.\n */\nexport function toBrandingWireBody(cwd: string, data: object): object {\n const { $schema, liquid_template_file, ...rest } = data as BrandingDescriptor & {\n $schema?: string;\n };\n void $schema;\n const out: Record<string, unknown> = { ...rest };\n if (typeof liquid_template_file === \"string\" && out.liquid_template === undefined) {\n try {\n out.liquid_template = readFileSync(resolveTemplatePath(cwd, liquid_template_file), \"utf8\");\n } catch {\n // Reported by validate(); keep normalize/hashing total.\n }\n }\n return out;\n}\n\n/**\n * Converts the server's canonical wire body back to the local descriptor\n * form: when the local descriptor references a template file, the template\n * string moves back out of the JSON into that reference. The caller writes\n * the template content to the referenced file separately.\n */\nexport function toLocalBrandingBody(canonicalWire: object, localData: object): object {\n const local = localData as BrandingDescriptor;\n if (typeof local.liquid_template_file !== \"string\") {\n return canonicalWire;\n }\n const { liquid_template, ...rest } = canonicalWire as BrandingDescriptor;\n void liquid_template;\n return { ...rest, liquid_template_file: local.liquid_template_file };\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,eAAe;;;;;;;;AAe5B,SAAgB,oBAAoB,KAAa,KAAqB;CACpE,MAAM,WAAW,WAAW,IAAI,GAAG,MAAM,QAAQ,KAAK,KAAK,aAAa,EAAE,IAAI;CAC9E,MAAM,MAAM,SAAS,KAAK,SAAS;AACnC,KAAI,IAAI,WAAW,KAAK,IAAI,WAAW,IAAI,CACzC,OAAM,IAAI,aACR,gBACA,wBAAwB,KAAK,UAAU,IAAI,CAAC,8BAC5C,EAAE,MAAM,+CAA+C,aAAa,KAAK,CAC1E;AAEH,QAAO;;;;;;;;AAST,SAAgB,uBAAuB,KAAa,MAAkC;CACpF,MAAM,aAAa;AACnB,KAAI,OAAO,WAAW,oBAAoB,SACxC,QAAO,WAAW;AAEpB,KAAI,OAAO,WAAW,yBAAyB,SAC7C;CAEF,MAAM,OAAO,oBAAoB,KAAK,WAAW,qBAAqB;AACtE,KAAI;AACF,SAAO,aAAa,MAAM,OAAO;UAC1B,OAAO;AACd,QAAM,IAAI,aACR,gBACA,wBAAwB,KAAK,UAAU,WAAW,qBAAqB,CAAC,kBACxE;GACE,MAAM;GACN,SAAS,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE;GAC3E,CACF;;;;;;;;;;AAWL,SAAgB,mBAAmB,KAAa,MAAsB;CACpE,MAAM,EAAE,SAAS,sBAAsB,GAAG,SAAS;CAInD,MAAM,MAA+B,EAAE,GAAG,MAAM;AAChD,KAAI,OAAO,yBAAyB,YAAY,IAAI,oBAAoB,KAAA,EACtE,KAAI;AACF,MAAI,kBAAkB,aAAa,oBAAoB,KAAK,qBAAqB,EAAE,OAAO;SACpF;AAIV,QAAO;;;;;;;;AAST,SAAgB,oBAAoB,eAAuB,WAA2B;CACpF,MAAM,QAAQ;AACd,KAAI,OAAO,MAAM,yBAAyB,SACxC,QAAO;CAET,MAAM,EAAE,iBAAiB,GAAG,SAAS;AAErC,QAAO;EAAE,GAAG;EAAM,sBAAsB,MAAM;EAAsB"}
@@ -1,125 +0,0 @@
1
- import { M as serverKind, j as publicCliCommand } from "./oclif-JlsN1lfO.mjs";
2
- /**
3
- * The date after which the platform refuses to claim, from the project's
4
- * creation time. Falls back to `now` when creation time is unknown, which is
5
- * exact at setup time (the project was just created) and the only caller
6
- * without a recorded creation time.
7
- */
8
- function claimWindowDeadline(createdAt, now = Date.now()) {
9
- const base = createdAt === void 0 ? now : Date.parse(createdAt);
10
- return new Date((Number.isNaN(base) ? now : base) + 336 * 60 * 60 * 1e3);
11
- }
12
- function deadlinePhrase(deadline) {
13
- return `attach it ${deadline === void 0 ? `within 14 days of creation` : `before ${deadline.toLocaleString(void 0, {
14
- year: "numeric",
15
- month: "short",
16
- day: "numeric",
17
- hour: "2-digit",
18
- minute: "2-digit",
19
- timeZoneName: "short"
20
- })}`}, after that it can no longer be claimed`;
21
- }
22
- /**
23
- * Whether the local record says a team owns this project.
24
- *
25
- * Both fields or neither: a half-written record is not an attachment, and
26
- * treating it as one would hand callers a `team_id` with no idea when it
27
- * landed. Shared with `zitadel claim`, which asks the same question before it
28
- * decides to skip, so the two cannot drift as the record grows fields.
29
- *
30
- * Deliberately separate from {@link claimState}: the command must answer this
31
- * on any server, while the nudges only ask it on the cloud.
32
- */
33
- function isAttached(secret) {
34
- return Boolean(secret.claimed_at && secret.team_id);
35
- }
36
- /**
37
- * Decides the attachment state from local state alone.
38
- *
39
- * **Why no network call.** The contract exposes no ambient claim-state read:
40
- * `getClaimStatus` answers for one live challenge and rejects a secret that did
41
- * not mint it, and claim attributes were deliberately kept off
42
- * `GET /projects/{id}` because they belong to the grant, not the project. So
43
- * `.zitadel/secret` is the only source, which is also why `status` and `doctor`
44
- * stay fast and work offline. `zitadel claim` reaches the same conclusion the
45
- * same way before it decides to skip.
46
- *
47
- * **Why cloud only.** Claiming attaches a project to a team on the platform
48
- * that hosts it. The cloud always has one. A local or self-hosted server has
49
- * one only when it opted into `platform.bootstrap_project`, and that flag
50
- * also pins the deployment's console and hosted-login default project to
51
- * proj_platform, so it is deliberately NOT a `zitadel start` default — which
52
- * means this offline classifier cannot know whether a local claim could
53
- * complete. `setup` is online anyway and probes the local server's runtime
54
- * document to decide the nudge there; the offline surfaces (`status`,
55
- * `doctor`) stay cloud-gated rather than advertise a possibly impossible
56
- * action.
57
- *
58
- * The local record can be stale in one direction: a project claimed from
59
- * another machine, or a `.zitadel/secret` restored from a backup, reads as
60
- * `detached` here. That is tolerable because everything downstream is
61
- * advisory, and `zitadel claim` itself turns the resulting `409` into a clean
62
- * skip rather than an error.
63
- */
64
- function claimState(input) {
65
- if (serverKind.value(input.server) !== "cloud") return { kind: "not-applicable" };
66
- if (isAttached(input.secret)) return {
67
- kind: "attached",
68
- team_id: input.secret.team_id,
69
- claimed_at: input.secret.claimed_at
70
- };
71
- const createdAt = Date.parse(input.secret.created_at ?? "");
72
- if (Number.isNaN(createdAt)) return {
73
- kind: "detached",
74
- claimable: true
75
- };
76
- const deadline = claimWindowDeadline(input.secret.created_at);
77
- return {
78
- kind: "detached",
79
- claimable: deadline.getTime() > Date.now(),
80
- deadline: deadline.toISOString()
81
- };
82
- }
83
- /**
84
- * The nudge for `next_actions`, quoting the command the way `journey-guidance`
85
- * does.
86
- *
87
- * Names the claim window because the server now enforces it at claim time
88
- * (`proj.claim_window_expired`); before that enforcement existed this copy
89
- * deliberately promised nothing. It still says nothing about deletion, because
90
- * nothing deletes the project when the window closes — it only stops being
91
- * claimable (ADR 046 §Non-goals).
92
- */
93
- function claimAction(cliVersion, deadline) {
94
- return `This project is temporary until you attach it to a team: ${deadlinePhrase(deadline)}. Run ${publicCliCommand("claim", cliVersion)} to make it permanent. Nothing about the project changes, so users, passkeys, and the issuer keep working.`;
95
- }
96
- /**
97
- * The same nudge for the human box, with the command on its own styled line.
98
- */
99
- function claimBoxAction(cliVersion, deadline) {
100
- return {
101
- text: `This project is temporary until you attach it to a team: ${deadlinePhrase(deadline)}. Claiming is independent of the steps above, so you can do it right away; nothing about the project changes, and users, passkeys, and the issuer keep working:`,
102
- command: publicCliCommand("claim", cliVersion)
103
- };
104
- }
105
- /** The command for `next_commands`. */
106
- function claimCommand(cliVersion) {
107
- return publicCliCommand("claim", cliVersion);
108
- }
109
- /**
110
- * The nudge's counterpart for a closed claim window. Deliberately framed as
111
- * reconciliation, not a verdict: the local record can be stale in one
112
- * direction (a project claimed from another machine still reads detached
113
- * here), so "window closed" from local data alone must not send the user off
114
- * to duplicate a project that is already attached and working. Running
115
- * `zitadel claim` is safe either way — the server checks the grant before
116
- * the window, so an attached project answers with a clean already-claimed
117
- * skip, and only a genuinely unattached one gets the final refusal.
118
- */
119
- function claimWindowClosedAction(cliVersion) {
120
- return `The local record says this project's 14-day claim window has closed. Run ${publicCliCommand("claim", cliVersion)} to reconcile: if the project was attached to a team from another machine it confirms that, and if it was never attached it can no longer be claimed. Either way the project keeps working; ${publicCliCommand("setup", cliVersion)} in a fresh directory gets you a new project you can attach.`;
121
- }
122
- //#endregion
123
- export { claimWindowClosedAction as a, claimState as i, claimBoxAction as n, claimWindowDeadline as o, claimCommand as r, isAttached as s, claimAction as t };
124
-
125
- //# sourceMappingURL=claim-state-B6ZSKmhW.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claim-state-B6ZSKmhW.mjs","names":[],"sources":["../src/lib/claim-state.ts"],"sourcesContent":["import type { BoxAction } from \"./box\";\nimport { serverKind } from \"./oclif/server-kind\";\nimport type { ZitadelSecret } from \"./project\";\nimport { publicCliCommand } from \"./public-cli\";\n\n/**\n * Mirrors domain.ClaimWindow (internal/domain/claim.go): the server refuses\n * `claim/init` and `claim/complete` with `proj.claim_window_expired` once the\n * project is more than this many days old. Keep the two in sync — drift here\n * shows as a wrong printed deadline, the enforcement stays server-side.\n */\nexport const CLAIM_WINDOW_DAYS = 14;\n\n/**\n * The date after which the platform refuses to claim, from the project's\n * creation time. Falls back to `now` when creation time is unknown, which is\n * exact at setup time (the project was just created) and the only caller\n * without a recorded creation time.\n */\nexport function claimWindowDeadline(createdAt: string | undefined, now = Date.now()): Date {\n const base = createdAt === undefined ? now : Date.parse(createdAt);\n return new Date((Number.isNaN(base) ? now : base) + CLAIM_WINDOW_DAYS * 24 * 60 * 60 * 1000);\n}\n\nfunction deadlinePhrase(deadline?: Date): string {\n // Time and zone included, not just the date: the server closes the window\n // at an exact timestamp (created_at + 14 x 24h), and a bare \"before Sep 18\"\n // misleads in both directions at the boundary day.\n const when =\n deadline === undefined\n ? `within ${CLAIM_WINDOW_DAYS} days of creation`\n : `before ${deadline.toLocaleString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZoneName: \"short\",\n })}`;\n return `attach it ${when}, after that it can no longer be claimed`;\n}\n\n/**\n * Whether this project is attached to a team, as the CLI can tell locally.\n *\n * `not-applicable` is its own variant rather than a boolean flag on the caller\n * because \"there is nothing to attach here\" and \"nothing is attached yet\" want\n * opposite output: the first prints nothing at all, the second nudges.\n */\nexport type ClaimState =\n | { kind: \"attached\"; team_id: string; claimed_at: string }\n /**\n * `claimable` classifies the claim window from the locally recorded\n * creation time: once it has passed, nudges switch to reconciliation\n * wording (claimWindowClosedAction). Never proof of anything: the local\n * record can be stale (a project claimed from another machine still reads\n * detached), so the claim command stays advertised as the safe check —\n * the server resolves the grant before the window. Lenient on an unknown\n * or unparsable creation time (claimable, no deadline) because the server\n * is the enforcer and the CLI only advises. `deadline` is the ISO date\n * the window closes, when the creation time is known.\n */\n | { kind: \"detached\"; claimable: boolean; deadline?: string }\n | { kind: \"not-applicable\" };\n\n/**\n * Whether the local record says a team owns this project.\n *\n * Both fields or neither: a half-written record is not an attachment, and\n * treating it as one would hand callers a `team_id` with no idea when it\n * landed. Shared with `zitadel claim`, which asks the same question before it\n * decides to skip, so the two cannot drift as the record grows fields.\n *\n * Deliberately separate from {@link claimState}: the command must answer this\n * on any server, while the nudges only ask it on the cloud.\n */\nexport function isAttached(\n secret: Pick<ZitadelSecret, \"claimed_at\" | \"team_id\">,\n): secret is { claimed_at: string; team_id: string } {\n return Boolean(secret.claimed_at && secret.team_id);\n}\n\n/**\n * Decides the attachment state from local state alone.\n *\n * **Why no network call.** The contract exposes no ambient claim-state read:\n * `getClaimStatus` answers for one live challenge and rejects a secret that did\n * not mint it, and claim attributes were deliberately kept off\n * `GET /projects/{id}` because they belong to the grant, not the project. So\n * `.zitadel/secret` is the only source, which is also why `status` and `doctor`\n * stay fast and work offline. `zitadel claim` reaches the same conclusion the\n * same way before it decides to skip.\n *\n * **Why cloud only.** Claiming attaches a project to a team on the platform\n * that hosts it. The cloud always has one. A local or self-hosted server has\n * one only when it opted into `platform.bootstrap_project`, and that flag\n * also pins the deployment's console and hosted-login default project to\n * proj_platform, so it is deliberately NOT a `zitadel start` default — which\n * means this offline classifier cannot know whether a local claim could\n * complete. `setup` is online anyway and probes the local server's runtime\n * document to decide the nudge there; the offline surfaces (`status`,\n * `doctor`) stay cloud-gated rather than advertise a possibly impossible\n * action.\n *\n * The local record can be stale in one direction: a project claimed from\n * another machine, or a `.zitadel/secret` restored from a backup, reads as\n * `detached` here. That is tolerable because everything downstream is\n * advisory, and `zitadel claim` itself turns the resulting `409` into a clean\n * skip rather than an error.\n */\nexport function claimState(input: {\n secret: Pick<ZitadelSecret, \"claimed_at\" | \"team_id\"> & { created_at?: string };\n server: string;\n}): ClaimState {\n if (serverKind.value(input.server) !== \"cloud\") {\n return { kind: \"not-applicable\" };\n }\n if (isAttached(input.secret)) {\n return {\n kind: \"attached\",\n team_id: input.secret.team_id,\n claimed_at: input.secret.claimed_at,\n };\n }\n const createdAt = Date.parse(input.secret.created_at ?? \"\");\n if (Number.isNaN(createdAt)) {\n return { kind: \"detached\", claimable: true };\n }\n const deadline = claimWindowDeadline(input.secret.created_at);\n return {\n kind: \"detached\",\n claimable: deadline.getTime() > Date.now(),\n deadline: deadline.toISOString(),\n };\n}\n\n/**\n * The nudge for `next_actions`, quoting the command the way `journey-guidance`\n * does.\n *\n * Names the claim window because the server now enforces it at claim time\n * (`proj.claim_window_expired`); before that enforcement existed this copy\n * deliberately promised nothing. It still says nothing about deletion, because\n * nothing deletes the project when the window closes — it only stops being\n * claimable (ADR 046 §Non-goals).\n */\nexport function claimAction(cliVersion: string, deadline?: Date): string {\n return (\n `This project is temporary until you attach it to a team: ${deadlinePhrase(deadline)}. ` +\n `Run ${publicCliCommand(\"claim\", cliVersion)} to make it permanent. ` +\n \"Nothing about the project changes, so users, passkeys, and the issuer keep working.\"\n );\n}\n\n/**\n * The same nudge for the human box, with the command on its own styled line.\n */\nexport function claimBoxAction(cliVersion: string, deadline?: Date): BoxAction {\n return {\n text:\n `This project is temporary until you attach it to a team: ${deadlinePhrase(deadline)}. ` +\n \"Claiming is independent of the steps above, so you can do it right away; \" +\n \"nothing about the project changes, and users, passkeys, and the issuer keep working:\",\n command: publicCliCommand(\"claim\", cliVersion),\n };\n}\n\n/** The command for `next_commands`. */\nexport function claimCommand(cliVersion: string): string {\n return publicCliCommand(\"claim\", cliVersion);\n}\n\n/**\n * The nudge's counterpart for a closed claim window. Deliberately framed as\n * reconciliation, not a verdict: the local record can be stale in one\n * direction (a project claimed from another machine still reads detached\n * here), so \"window closed\" from local data alone must not send the user off\n * to duplicate a project that is already attached and working. Running\n * `zitadel claim` is safe either way — the server checks the grant before\n * the window, so an attached project answers with a clean already-claimed\n * skip, and only a genuinely unattached one gets the final refusal.\n */\nexport function claimWindowClosedAction(cliVersion: string): string {\n return (\n `The local record says this project's ${CLAIM_WINDOW_DAYS}-day claim window has ` +\n `closed. Run ${publicCliCommand(\"claim\", cliVersion)} to reconcile: if the project ` +\n \"was attached to a team from another machine it confirms that, and if it was never \" +\n \"attached it can no longer be claimed. Either way the project keeps working; \" +\n `${publicCliCommand(\"setup\", cliVersion)} in a fresh directory gets you a new ` +\n \"project you can attach.\"\n );\n}\n"],"mappings":";;;;;;;AAmBA,SAAgB,oBAAoB,WAA+B,MAAM,KAAK,KAAK,EAAQ;CACzF,MAAM,OAAO,cAAc,KAAA,IAAY,MAAM,KAAK,MAAM,UAAU;AAClE,QAAO,IAAI,MAAM,OAAO,MAAM,KAAK,GAAG,MAAM,QAAA,MAAiC,KAAK,KAAK,IAAK;;AAG9F,SAAS,eAAe,UAAyB;AAe/C,QAAO,aAVL,aAAa,KAAA,IACT,+BACA,UAAU,SAAS,eAAe,KAAA,GAAW;EAC3C,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,cAAc;EACf,CAAC,GACiB;;;;;;;;;;;;;AAqC3B,SAAgB,WACd,QACmD;AACnD,QAAO,QAAQ,OAAO,cAAc,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BrD,SAAgB,WAAW,OAGZ;AACb,KAAI,WAAW,MAAM,MAAM,OAAO,KAAK,QACrC,QAAO,EAAE,MAAM,kBAAkB;AAEnC,KAAI,WAAW,MAAM,OAAO,CAC1B,QAAO;EACL,MAAM;EACN,SAAS,MAAM,OAAO;EACtB,YAAY,MAAM,OAAO;EAC1B;CAEH,MAAM,YAAY,KAAK,MAAM,MAAM,OAAO,cAAc,GAAG;AAC3D,KAAI,OAAO,MAAM,UAAU,CACzB,QAAO;EAAE,MAAM;EAAY,WAAW;EAAM;CAE9C,MAAM,WAAW,oBAAoB,MAAM,OAAO,WAAW;AAC7D,QAAO;EACL,MAAM;EACN,WAAW,SAAS,SAAS,GAAG,KAAK,KAAK;EAC1C,UAAU,SAAS,aAAa;EACjC;;;;;;;;;;;;AAaH,SAAgB,YAAY,YAAoB,UAAyB;AACvE,QACE,4DAA4D,eAAe,SAAS,CAAC,QAC9E,iBAAiB,SAAS,WAAW,CAAC;;;;;AAQjD,SAAgB,eAAe,YAAoB,UAA4B;AAC7E,QAAO;EACL,MACE,4DAA4D,eAAe,SAAS,CAAC;EAGvF,SAAS,iBAAiB,SAAS,WAAW;EAC/C;;;AAIH,SAAgB,aAAa,YAA4B;AACvD,QAAO,iBAAiB,SAAS,WAAW;;;;;;;;;;;;AAa9C,SAAgB,wBAAwB,YAA4B;AAClE,QACE,4EACe,iBAAiB,SAAS,WAAW,CAAC,8LAGlD,iBAAiB,SAAS,WAAW,CAAC"}
@@ -1,81 +0,0 @@
1
- import { j as publicCliCommand, t as BaseCommand } from "../oclif-JlsN1lfO.mjs";
2
- import { l as readZitadelSecret } from "../project-Ba0nriNm.mjs";
3
- import { a as buildSyncPlan, i as summarizePlan, l as makeSyncers, n as enumeratePlanResources, r as renderPlan, s as runSyncLoop, t as collectPlanWarnings } from "../sync-ChlLQ4vZ.mjs";
4
- import { t as environmentSchema } from "../environment-fibXmzM8.mjs";
5
- import { Flags } from "@oclif/core";
6
- import { createZitadelClient } from "@zitadel/api/client";
7
- import { consola as consola$1 } from "consola";
8
- //#region src/commands/apply.ts
9
- /**
10
- * `zitadel apply` — validate and upload repo config to the platform.
11
- *
12
- * Runs the sync loop to convergence, or (with `--dry-run`) previews the diff
13
- * without mutating. All validation — structural shape and `${VAR}` / `*_env`
14
- * reference presence — happens inside the sync engine ({@link buildSyncPlan}),
15
- * so an invalid or under-configured file fails with `E_VALIDATION` before any
16
- * platform call.
17
- */
18
- var Apply = class Apply extends BaseCommand {
19
- static description = "Validate and upload repo config to the platform.";
20
- static flags = { environment: Flags.string({
21
- char: "e",
22
- description: "Target environment (default: development).",
23
- options: [...environmentSchema.options]
24
- }) };
25
- async run() {
26
- const { flags } = await this.parse(Apply);
27
- await this.toMeta(flags);
28
- const { cwd, source, env, dryRun, isTTY } = this.meta;
29
- const secret = await readZitadelSecret(cwd);
30
- consola$1.info(`Project ${secret.project_id}`);
31
- consola$1.info(`Server ${source}`);
32
- const syncers = makeSyncers({
33
- client: createZitadelClient({
34
- baseUrl: source,
35
- token: secret.project_secret
36
- }),
37
- projectId: secret.project_id,
38
- env,
39
- cwd
40
- });
41
- if (!dryRun) {
42
- consola$1.start("Syncing schemas and flows to Zitadel");
43
- const { filesUpdated, applied } = await runSyncLoop(cwd, syncers);
44
- consola$1.success("Sync complete");
45
- return this.emit({
46
- status: "ok",
47
- data: {
48
- synced: true,
49
- changes: applied,
50
- files_updated: filesUpdated,
51
- next_actions: applied.length === 0 ? ["Everything is already in sync — no changes were applied."] : ["Changes are live — reload your app to see them.", "Re-run plan to confirm local config and platform are in sync."],
52
- next_commands: [publicCliCommand("plan", this.meta.cliVersion)]
53
- }
54
- });
55
- }
56
- consola$1.start("Building plan (dry run)");
57
- const plan = await buildSyncPlan(cwd, syncers, true);
58
- const summary = summarizePlan(plan);
59
- this.recordTelemetry({
60
- creates: summary.creates,
61
- updates: summary.updates,
62
- revisions: summary.revisions,
63
- deletes: summary.deletes,
64
- total: summary.total
65
- });
66
- consola$1.success(`Plan: ${summary.creates} create${summary.creates === 1 ? "" : "s"}, ${summary.updates} update${summary.updates === 1 ? "" : "s"}, ${summary.revisions} new revision${summary.revisions === 1 ? "" : "s"}, ${summary.deletes} delete${summary.deletes === 1 ? "" : "s"}`);
67
- return this.emit({
68
- status: "ok",
69
- data: {
70
- ...summary,
71
- changes: enumeratePlanResources(plan),
72
- warnings: collectPlanWarnings(plan)
73
- },
74
- pretty: renderPlan(plan, isTTY)
75
- });
76
- }
77
- };
78
- //#endregion
79
- export { Apply as default };
80
-
81
- //# sourceMappingURL=apply.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"apply.mjs","names":[],"sources":["../../src/commands/apply.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\nimport { consola } from \"consola\";\n\nimport { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { environmentSchema } from \"../lib/environment\";\nimport {\n buildSyncPlan,\n collectPlanWarnings,\n enumeratePlanResources,\n makeSyncers,\n renderPlan,\n runSyncLoop,\n summarizePlan,\n} from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\nimport { publicCliCommand } from \"../lib/public-cli\";\n\n/**\n * `zitadel apply` — validate and upload repo config to the platform.\n *\n * Runs the sync loop to convergence, or (with `--dry-run`) previews the diff\n * without mutating. All validation — structural shape and `${VAR}` / `*_env`\n * reference presence — happens inside the sync engine ({@link buildSyncPlan}),\n * so an invalid or under-configured file fails with `E_VALIDATION` before any\n * platform call.\n */\nexport default class Apply extends BaseCommand {\n static override description = \"Validate and upload repo config to the platform.\";\n static override flags = {\n environment: Flags.string({\n char: \"e\",\n description: \"Target environment (default: development).\",\n options: [...environmentSchema.options],\n }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Apply);\n await this.toMeta(flags);\n const { cwd, source, env, dryRun, isTTY } = this.meta;\n\n const secret = await readZitadelSecret(cwd);\n consola.info(`Project ${secret.project_id}`);\n consola.info(`Server ${source}`);\n const client = createZitadelClient({\n baseUrl: source,\n token: secret.project_secret,\n });\n const syncers = makeSyncers({\n client,\n projectId: secret.project_id,\n env,\n cwd,\n });\n\n if (!dryRun) {\n consola.start(\"Syncing schemas and flows to Zitadel\");\n const { filesUpdated, applied } = await runSyncLoop(cwd, syncers);\n consola.success(\"Sync complete\");\n return this.emit({\n status: \"ok\",\n data: {\n synced: true,\n // Platform resources this run touched (with resulting ids);\n // `files_updated` stays the local write-backs only.\n changes: applied,\n files_updated: filesUpdated,\n next_actions:\n applied.length === 0\n ? [\"Everything is already in sync — no changes were applied.\"]\n : [\n \"Changes are live — reload your app to see them.\",\n \"Re-run plan to confirm local config and platform are in sync.\",\n ],\n next_commands: [publicCliCommand(\"plan\", this.meta.cliVersion)],\n },\n });\n }\n\n consola.start(\"Building plan (dry run)\");\n const plan = await buildSyncPlan(cwd, syncers, true);\n const summary = summarizePlan(plan);\n this.recordTelemetry({\n creates: summary.creates,\n updates: summary.updates,\n revisions: summary.revisions,\n deletes: summary.deletes,\n total: summary.total,\n });\n consola.success(\n `Plan: ${summary.creates} create${summary.creates === 1 ? \"\" : \"s\"}, ` +\n `${summary.updates} update${summary.updates === 1 ? \"\" : \"s\"}, ` +\n `${summary.revisions} new revision${summary.revisions === 1 ? \"\" : \"s\"}, ` +\n `${summary.deletes} delete${summary.deletes === 1 ? \"\" : \"s\"}`,\n );\n return this.emit({\n status: \"ok\",\n data: {\n ...summary,\n changes: enumeratePlanResources(plan),\n warnings: collectPlanWarnings(plan),\n },\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,IAAqB,QAArB,MAAqB,cAAc,YAAY;CAC7C,OAAgB,cAAc;CAC9B,OAAgB,QAAQ,EACtB,aAAa,MAAM,OAAO;EACxB,MAAM;EACN,aAAa;EACb,SAAS,CAAC,GAAG,kBAAkB,QAAQ;EACxC,CAAC,EACH;CAED,MAAM,MAA6B;EACjC,MAAM,EAAE,UAAU,MAAM,KAAK,MAAM,MAAM;AACzC,QAAM,KAAK,OAAO,MAAM;EACxB,MAAM,EAAE,KAAK,QAAQ,KAAK,QAAQ,UAAU,KAAK;EAEjD,MAAM,SAAS,MAAM,kBAAkB,IAAI;AAC3C,YAAQ,KAAK,aAAa,OAAO,aAAa;AAC9C,YAAQ,KAAK,aAAa,SAAS;EAKnC,MAAM,UAAU,YAAY;GAC1B,QALa,oBAAoB;IACjC,SAAS;IACT,OAAO,OAAO;IACf,CAEO;GACN,WAAW,OAAO;GAClB;GACA;GACD,CAAC;AAEF,MAAI,CAAC,QAAQ;AACX,aAAQ,MAAM,uCAAuC;GACrD,MAAM,EAAE,cAAc,YAAY,MAAM,YAAY,KAAK,QAAQ;AACjE,aAAQ,QAAQ,gBAAgB;AAChC,UAAO,KAAK,KAAK;IACf,QAAQ;IACR,MAAM;KACJ,QAAQ;KAGR,SAAS;KACT,eAAe;KACf,cACE,QAAQ,WAAW,IACf,CAAC,2DAA2D,GAC5D,CACE,mDACA,gEACD;KACP,eAAe,CAAC,iBAAiB,QAAQ,KAAK,KAAK,WAAW,CAAC;KAChE;IACF,CAAC;;AAGJ,YAAQ,MAAM,0BAA0B;EACxC,MAAM,OAAO,MAAM,cAAc,KAAK,SAAS,KAAK;EACpD,MAAM,UAAU,cAAc,KAAK;AACnC,OAAK,gBAAgB;GACnB,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB,WAAW,QAAQ;GACnB,SAAS,QAAQ;GACjB,OAAO,QAAQ;GAChB,CAAC;AACF,YAAQ,QACN,SAAS,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC9D,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC1D,QAAQ,UAAU,eAAe,QAAQ,cAAc,IAAI,KAAK,IAAI,IACpE,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,MAC5D;AACD,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;IACJ,GAAG;IACH,SAAS,uBAAuB,KAAK;IACrC,UAAU,oBAAoB,KAAK;IACpC;GACD,QAAQ,WAAW,MAAM,MAAM;GAChC,CAAC"}
@@ -1,104 +0,0 @@
1
- import { t as BRANDING_DESIGN_INFO } from "../../designs-Ckz18Dpo.mjs";
2
- import { E as ZitadelError, O as normalizePublicCliJson, T as stableStringify, j as publicCliCommand, k as normalizePublicCliProse, t as BaseCommand } from "../../oclif-JlsN1lfO.mjs";
3
- import { t as hasZitadelConfig } from "../../project-Ba0nriNm.mjs";
4
- import { t as BRANDING_DIR } from "../../branding-DvoH8e0D.mjs";
5
- import { mkdir, writeFile } from "node:fs/promises";
6
- import { dirname, join } from "node:path";
7
- import { cancel, isCancel, select } from "@clack/prompts";
8
- import { Flags } from "@oclif/core";
9
- import { BRANDING_DESIGNS, DEFAULT_BRANDING_CONFIG_PATH, DEFAULT_BRANDING_DESIGN, DEFAULT_BRANDING_TEMPLATE_PATH, brandingReadmeContent, getDefaultBrandingConfig } from "@zitadel/config/defaults";
10
- import { consola as consola$1 } from "consola";
11
- import { BRANDING_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles } from "@zitadel/config/meta-schemas";
12
- //#region src/commands/branding/eject.ts
13
- /**
14
- * The `branding eject` command — take ownership of the login template.
15
- *
16
- * Scaffolds `.zitadel/branding/` from a shipped design: the `branding.json`
17
- * descriptor, the `login.liquid` template it references, a README, and the
18
- * branding dialect meta-schema (for projects set up before it existed).
19
- * Purely local — nothing is uploaded; the first `zitadel apply` publishes
20
- * revision 1. Ejecting the template is the last rung of the branding
21
- * override ladder (docs/design/branding/override-ladder.md); unrelated to
22
- * `zitadel eject`, which removes the Zitadel integration.
23
- */
24
- var BrandingEject = class BrandingEject extends BaseCommand {
25
- static description = "Take ownership of the login template: scaffold .zitadel/branding/ from a shipped design.";
26
- static flags = { design: Flags.string({
27
- description: `Design to start from (default: ${DEFAULT_BRANDING_DESIGN}).`,
28
- options: [...BRANDING_DESIGNS]
29
- }) };
30
- async run() {
31
- const { flags } = await this.parse(BrandingEject);
32
- await this.toMeta(flags);
33
- const { cwd, force, nonInteractive } = this.meta;
34
- if (!await hasZitadelConfig(cwd)) throw new ZitadelError("E_VALIDATION", "This directory is not a Zitadel project", {
35
- hint: "Run setup first; branding files live under .zitadel/.",
36
- nextCommands: [publicCliCommand("setup", this.meta.cliVersion)]
37
- });
38
- const design = flags.design ?? await this.promptDesign(nonInteractive);
39
- this.recordTelemetry({
40
- design,
41
- force
42
- });
43
- const { branding, template } = getDefaultBrandingConfig(design);
44
- const filesWritten = [];
45
- const write = async (relPath, content, overwrite) => {
46
- const dest = join(cwd, relPath);
47
- await mkdir(dirname(dest), { recursive: true });
48
- try {
49
- await writeFile(dest, content, overwrite ? void 0 : { flag: "wx" });
50
- filesWritten.push(relPath);
51
- } catch (error) {
52
- if (isErrno(error, "EEXIST")) throw new ZitadelError("E_CONFLICT", `${relPath} already exists`, { hint: "Rerun with --force to replace it, or edit the existing file directly." });
53
- throw error;
54
- }
55
- };
56
- await write(DEFAULT_BRANDING_CONFIG_PATH, `${stableStringify({
57
- $schema: BRANDING_FILE_SCHEMA_REF,
58
- ...branding
59
- })}\n`, force);
60
- await write(DEFAULT_BRANDING_TEMPLATE_PATH, template, force);
61
- await write(join(BRANDING_DIR, "README.md"), normalizePublicCliProse(brandingReadmeContent(), this.meta.cliVersion), false).catch(swallowConflict);
62
- const brandingMeta = metaSchemaFiles().find((f) => f.name === "branding.json");
63
- if (brandingMeta) await write(join(META_SCHEMA_DIR, brandingMeta.name), `${stableStringify(normalizePublicCliJson(brandingMeta.body, this.meta.cliVersion))}\n`, false).catch(swallowConflict);
64
- for (const file of filesWritten) consola$1.info(`Wrote ${file}`);
65
- consola$1.success(`Ejected the ${design} design into ${BRANDING_DIR}/`);
66
- return this.emit({
67
- status: "ok",
68
- data: {
69
- design,
70
- files_written: filesWritten,
71
- next_actions: [`Edit ${DEFAULT_BRANDING_TEMPLATE_PATH} — the login UI is yours now.`, "Plan validates the template and shows the pending revision; apply publishes it."],
72
- next_commands: [publicCliCommand("plan", this.meta.cliVersion), publicCliCommand("apply", this.meta.cliVersion)]
73
- }
74
- });
75
- }
76
- async promptDesign(nonInteractive) {
77
- if (nonInteractive) return DEFAULT_BRANDING_DESIGN;
78
- const choice = await select({
79
- message: "Which design do you want to start from?",
80
- initialValue: DEFAULT_BRANDING_DESIGN,
81
- options: BRANDING_DESIGNS.map((design) => ({
82
- value: design,
83
- label: BRANDING_DESIGN_INFO[design].label,
84
- hint: BRANDING_DESIGN_INFO[design].hint
85
- }))
86
- });
87
- if (isCancel(choice)) {
88
- cancel("Eject cancelled.");
89
- throw new ZitadelError("E_VALIDATION", "Eject cancelled by user");
90
- }
91
- return choice;
92
- }
93
- };
94
- function swallowConflict(error) {
95
- if (error instanceof ZitadelError && error.code === "E_CONFLICT") return;
96
- throw error;
97
- }
98
- function isErrno(error, code) {
99
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
100
- }
101
- //#endregion
102
- export { BrandingEject as default };
103
-
104
- //# sourceMappingURL=eject.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"eject.mjs","names":[],"sources":["../../../src/commands/branding/eject.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { cancel, isCancel, select } from \"@clack/prompts\";\nimport { Flags } from \"@oclif/core\";\nimport { consola } from \"consola\";\n\nimport {\n BRANDING_DESIGNS,\n DEFAULT_BRANDING_CONFIG_PATH,\n DEFAULT_BRANDING_DESIGN,\n DEFAULT_BRANDING_TEMPLATE_PATH,\n brandingReadmeContent,\n getDefaultBrandingConfig,\n} from \"@zitadel/config/defaults\";\nimport { BRANDING_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles } from \"@zitadel/config/meta-schemas\";\n\nimport { BRANDING_DIR } from \"../../lib/branding\";\nimport { BRANDING_DESIGN_INFO } from \"../../lib/branding/designs\";\nimport { ZitadelError } from \"../../lib/errors\";\nimport { stableStringify } from \"../../lib/json\";\nimport { BaseCommand, type JsonEnvelope } from \"../../lib/oclif\";\nimport { hasZitadelConfig } from \"../../lib/project\";\nimport {\n normalizePublicCliJson,\n normalizePublicCliProse,\n publicCliCommand,\n} from \"../../lib/public-cli\";\n\n/**\n * The `branding eject` command — take ownership of the login template.\n *\n * Scaffolds `.zitadel/branding/` from a shipped design: the `branding.json`\n * descriptor, the `login.liquid` template it references, a README, and the\n * branding dialect meta-schema (for projects set up before it existed).\n * Purely local — nothing is uploaded; the first `zitadel apply` publishes\n * revision 1. Ejecting the template is the last rung of the branding\n * override ladder (docs/design/branding/override-ladder.md); unrelated to\n * `zitadel eject`, which removes the Zitadel integration.\n */\nexport default class BrandingEject extends BaseCommand {\n static override description =\n \"Take ownership of the login template: scaffold .zitadel/branding/ from a shipped design.\";\n static override flags = {\n design: Flags.string({\n description: `Design to start from (default: ${DEFAULT_BRANDING_DESIGN}).`,\n options: [...BRANDING_DESIGNS],\n }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(BrandingEject);\n await this.toMeta(flags);\n const { cwd, force, nonInteractive } = this.meta;\n\n if (!(await hasZitadelConfig(cwd))) {\n throw new ZitadelError(\"E_VALIDATION\", \"This directory is not a Zitadel project\", {\n hint: \"Run setup first; branding files live under .zitadel/.\",\n nextCommands: [publicCliCommand(\"setup\", this.meta.cliVersion)],\n });\n }\n\n const design = flags.design ?? (await this.promptDesign(nonInteractive));\n this.recordTelemetry({ design, force });\n const { branding, template } = getDefaultBrandingConfig(design);\n\n const filesWritten: string[] = [];\n const write = async (relPath: string, content: string, overwrite: boolean): Promise<void> => {\n const dest = join(cwd, relPath);\n await mkdir(dirname(dest), { recursive: true });\n try {\n await writeFile(dest, content, overwrite ? undefined : { flag: \"wx\" });\n filesWritten.push(relPath);\n } catch (error) {\n if (isErrno(error, \"EEXIST\")) {\n throw new ZitadelError(\"E_CONFLICT\", `${relPath} already exists`, {\n hint: \"Rerun with --force to replace it, or edit the existing file directly.\",\n });\n }\n throw error;\n }\n };\n\n await write(\n DEFAULT_BRANDING_CONFIG_PATH,\n `${stableStringify({ $schema: BRANDING_FILE_SCHEMA_REF, ...branding })}\\n`,\n force,\n );\n await write(DEFAULT_BRANDING_TEMPLATE_PATH, template, force);\n\n // Never overwrite a hand-edited README; setups from before the branding\n // dialect also miss the meta-schema, so materialize it when absent.\n await write(\n join(BRANDING_DIR, \"README.md\"),\n normalizePublicCliProse(brandingReadmeContent(), this.meta.cliVersion),\n false,\n ).catch(swallowConflict);\n const brandingMeta = metaSchemaFiles().find((f) => f.name === \"branding.json\");\n if (brandingMeta) {\n await write(\n join(META_SCHEMA_DIR, brandingMeta.name),\n `${stableStringify(normalizePublicCliJson(brandingMeta.body, this.meta.cliVersion))}\\n`,\n false,\n ).catch(swallowConflict);\n }\n\n for (const file of filesWritten) {\n consola.info(`Wrote ${file}`);\n }\n consola.success(`Ejected the ${design} design into ${BRANDING_DIR}/`);\n\n return this.emit({\n status: \"ok\",\n data: {\n design,\n files_written: filesWritten,\n next_actions: [\n `Edit ${DEFAULT_BRANDING_TEMPLATE_PATH} — the login UI is yours now.`,\n \"Plan validates the template and shows the pending revision; apply publishes it.\",\n ],\n next_commands: [\n publicCliCommand(\"plan\", this.meta.cliVersion),\n publicCliCommand(\"apply\", this.meta.cliVersion),\n ],\n },\n });\n }\n\n private async promptDesign(nonInteractive: boolean): Promise<string> {\n if (nonInteractive) {\n return DEFAULT_BRANDING_DESIGN;\n }\n const choice = await select({\n message: \"Which design do you want to start from?\",\n initialValue: DEFAULT_BRANDING_DESIGN as string,\n options: BRANDING_DESIGNS.map((design) => ({\n value: design as string,\n label: BRANDING_DESIGN_INFO[design].label,\n hint: BRANDING_DESIGN_INFO[design].hint,\n })),\n });\n if (isCancel(choice)) {\n cancel(\"Eject cancelled.\");\n throw new ZitadelError(\"E_VALIDATION\", \"Eject cancelled by user\");\n }\n return choice;\n }\n}\n\nfunction swallowConflict(error: unknown): void {\n if (error instanceof ZitadelError && error.code === \"E_CONFLICT\") {\n return;\n }\n throw error;\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as NodeJS.ErrnoException).code === code\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAqB,gBAArB,MAAqB,sBAAsB,YAAY;CACrD,OAAgB,cACd;CACF,OAAgB,QAAQ,EACtB,QAAQ,MAAM,OAAO;EACnB,aAAa,kCAAkC,wBAAwB;EACvE,SAAS,CAAC,GAAG,iBAAiB;EAC/B,CAAC,EACH;CAED,MAAM,MAA6B;EACjC,MAAM,EAAE,UAAU,MAAM,KAAK,MAAM,cAAc;AACjD,QAAM,KAAK,OAAO,MAAM;EACxB,MAAM,EAAE,KAAK,OAAO,mBAAmB,KAAK;AAE5C,MAAI,CAAE,MAAM,iBAAiB,IAAI,CAC/B,OAAM,IAAI,aAAa,gBAAgB,2CAA2C;GAChF,MAAM;GACN,cAAc,CAAC,iBAAiB,SAAS,KAAK,KAAK,WAAW,CAAC;GAChE,CAAC;EAGJ,MAAM,SAAS,MAAM,UAAW,MAAM,KAAK,aAAa,eAAe;AACvE,OAAK,gBAAgB;GAAE;GAAQ;GAAO,CAAC;EACvC,MAAM,EAAE,UAAU,aAAa,yBAAyB,OAAO;EAE/D,MAAM,eAAyB,EAAE;EACjC,MAAM,QAAQ,OAAO,SAAiB,SAAiB,cAAsC;GAC3F,MAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,SAAM,MAAM,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC/C,OAAI;AACF,UAAM,UAAU,MAAM,SAAS,YAAY,KAAA,IAAY,EAAE,MAAM,MAAM,CAAC;AACtE,iBAAa,KAAK,QAAQ;YACnB,OAAO;AACd,QAAI,QAAQ,OAAO,SAAS,CAC1B,OAAM,IAAI,aAAa,cAAc,GAAG,QAAQ,kBAAkB,EAChE,MAAM,yEACP,CAAC;AAEJ,UAAM;;;AAIV,QAAM,MACJ,8BACA,GAAG,gBAAgB;GAAE,SAAS;GAA0B,GAAG;GAAU,CAAC,CAAC,KACvE,MACD;AACD,QAAM,MAAM,gCAAgC,UAAU,MAAM;AAI5D,QAAM,MACJ,KAAK,cAAc,YAAY,EAC/B,wBAAwB,uBAAuB,EAAE,KAAK,KAAK,WAAW,EACtE,MACD,CAAC,MAAM,gBAAgB;EACxB,MAAM,eAAe,iBAAiB,CAAC,MAAM,MAAM,EAAE,SAAS,gBAAgB;AAC9E,MAAI,aACF,OAAM,MACJ,KAAK,iBAAiB,aAAa,KAAK,EACxC,GAAG,gBAAgB,uBAAuB,aAAa,MAAM,KAAK,KAAK,WAAW,CAAC,CAAC,KACpF,MACD,CAAC,MAAM,gBAAgB;AAG1B,OAAK,MAAM,QAAQ,aACjB,WAAQ,KAAK,SAAS,OAAO;AAE/B,YAAQ,QAAQ,eAAe,OAAO,eAAe,aAAa,GAAG;AAErE,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;IACJ;IACA,eAAe;IACf,cAAc,CACZ,QAAQ,+BAA+B,gCACvC,kFACD;IACD,eAAe,CACb,iBAAiB,QAAQ,KAAK,KAAK,WAAW,EAC9C,iBAAiB,SAAS,KAAK,KAAK,WAAW,CAChD;IACF;GACF,CAAC;;CAGJ,MAAc,aAAa,gBAA0C;AACnE,MAAI,eACF,QAAO;EAET,MAAM,SAAS,MAAM,OAAO;GAC1B,SAAS;GACT,cAAc;GACd,SAAS,iBAAiB,KAAK,YAAY;IACzC,OAAO;IACP,OAAO,qBAAqB,QAAQ;IACpC,MAAM,qBAAqB,QAAQ;IACpC,EAAE;GACJ,CAAC;AACF,MAAI,SAAS,OAAO,EAAE;AACpB,UAAO,mBAAmB;AAC1B,SAAM,IAAI,aAAa,gBAAgB,0BAA0B;;AAEnE,SAAO;;;AAIX,SAAS,gBAAgB,OAAsB;AAC7C,KAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD;AAEF,OAAM;;AAGR,SAAS,QAAQ,OAAgB,MAAuB;AACtD,QACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAAgC,SAAS"}