@zitadel/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { A as compareInGroup, O as COMMAND_GROUPS, i as exploreAllCommands, k as CommandGroups, t as showSkillsHint } from "../../skills-hint-CBoak9HL.mjs";
1
+ import { M as COMMAND_GROUPS, N as CommandGroups, P as compareInGroup, i as exploreAllCommands, t as showSkillsHint } from "../../skills-hint-N1H5-ny1.mjs";
2
2
  import { Help, toConfiguredId, ux } from "@oclif/core";
3
3
  //#region src/lib/oclif/help.ts
4
4
  /**
@@ -13,7 +13,7 @@ import { promisify } from "node:util";
13
13
  import { BlockList, isIP } from "node:net";
14
14
  import pc from "picocolors";
15
15
  import { Agent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
16
- import { brandingConfigSchema, flowConfigSchema, idpConnectionConfigSchema, policyConfigSchema, schemaConfigSchema } from "@zitadel/config/schemas";
16
+ import { RECOMMENDED_PASSWORD_MIN_LENGTH, brandingConfigSchema, flowConfigSchema, idpConnectionConfigSchema, policyConfigSchema, schemaConfigSchema } from "@zitadel/config/schemas";
17
17
  import { PURPOSE_FLIP_TARGETS, validateFlowDefinition, validateProject } from "@zitadel/config/validate";
18
18
  import { CUSTOM_PROVIDER, IDP_PROVIDERS, RESERVED_SLUGS, credentialVariables, idpProvider, isLoopbackIssuer, isVariableReference, missingEndpoints, parseConnectionSlug, parseDisplayName, parseIssuer, parseProviderSelection, parseScopes, referencedVariable, selectionName } from "@zitadel/config/idp";
19
19
  import { SETUP_PRESETS, getDefaultLoginFlow } from "@zitadel/config/defaults";
@@ -3232,9 +3232,11 @@ async function readJsonDir(absDir) {
3232
3232
  }
3233
3233
  /**
3234
3234
  * {@link readJsonDir}, keeping each body's file name — for callers that report
3235
- * a problem against the file it came from.
3235
+ * a problem against the file it came from. `skip` leaves out files by name
3236
+ * before they are read, so a file the caller does not own (a policy test file
3237
+ * beside the policies) can never fail it.
3236
3238
  */
3237
- async function readJsonDirEntries(absDir) {
3239
+ async function readJsonDirEntries(absDir, { skip } = {}) {
3238
3240
  let entries;
3239
3241
  try {
3240
3242
  entries = await readdir(absDir);
@@ -3243,7 +3245,7 @@ async function readJsonDirEntries(absDir) {
3243
3245
  throw error;
3244
3246
  }
3245
3247
  const result = [];
3246
- for (const entry of entries.filter((name) => name.endsWith(".json")).sort()) {
3248
+ for (const entry of entries.filter((name) => name.endsWith(".json") && !skip?.(name)).sort()) {
3247
3249
  const abs = join(absDir, entry);
3248
3250
  let raw;
3249
3251
  try {
@@ -3288,6 +3290,15 @@ function toPolicyWireBody(file) {
3288
3290
  config: file.config
3289
3291
  };
3290
3292
  }
3293
+ /**
3294
+ * The suffix of a policy test file (`<operation>.test.json`), which sits
3295
+ * beside the policies but is never deployed: `zitadel policies test` runs it.
3296
+ */
3297
+ const POLICY_TEST_SUFFIX = ".test.json";
3298
+ /** Whether a file in `.zitadel/policies/` is a test file rather than a policy. */
3299
+ function isPolicyTestFile(name) {
3300
+ return name.endsWith(POLICY_TEST_SUFFIX);
3301
+ }
3291
3302
  /** The name a policy file must have: its operation, so one file holds one operation's policy. */
3292
3303
  function policyFileName(operation) {
3293
3304
  return `${operation}.json`;
@@ -3317,7 +3328,7 @@ const PASSWORD_POLICY_PATH = `${POLICIES_DIR}/${policyFileName(PASSWORD_POLICY_O
3317
3328
  * empty `config`, which enforces nothing, for an explicit "none".
3318
3329
  */
3319
3330
  function passwordPolicyFile(minLength) {
3320
- const value = minLength === void 0 ? 15 : minLength;
3331
+ const value = minLength === void 0 ? RECOMMENDED_PASSWORD_MIN_LENGTH : minLength;
3321
3332
  return {
3322
3333
  $schema: POLICY_FILE_SCHEMA_REF,
3323
3334
  operation: PASSWORD_POLICY_OPERATION,
@@ -3367,7 +3378,7 @@ async function buildBundle(cwd) {
3367
3378
  const flow_definitions = (await readJsonDir(join(cwd, FLOWS_DIR))).map(stripSchema);
3368
3379
  const idpEntries = await readJsonDirEntries(join(cwd, IDPS_DIR));
3369
3380
  const idps = idpEntries.map((entry) => stripSchema(entry.body));
3370
- const policyEntries = await readJsonDirEntries(join(cwd, POLICIES_DIR));
3381
+ const policyEntries = await readJsonDirEntries(join(cwd, POLICIES_DIR), { skip: isPolicyTestFile });
3371
3382
  const policies = policyEntries.map((entry) => stripSchema(entry.body));
3372
3383
  const brandingSources = (await readJsonDir(join(cwd, BRANDING_DIR))).map((descriptor) => ({ ...descriptor }));
3373
3384
  if (brandingSources.length > 1) throw new ZitadelError("E_VALIDATION", `A release carries at most one branding, but .zitadel/branding/ holds ${brandingSources.length}`, { hint: "A project has a single branding; keep one descriptor under .zitadel/branding/." });
@@ -3496,12 +3507,87 @@ async function worktreeIsDirty(cwd) {
3496
3507
  return stdout !== null && stdout.trim() !== "";
3497
3508
  }
3498
3509
  /**
3510
+ * The `git diff` options the terminal's diff and the `--json` one share, so
3511
+ * the two never disagree: git's own unified diff, never an external diff
3512
+ * program's, with paths relative to the project, as the CLI reports them
3513
+ * everywhere else.
3514
+ */
3515
+ const DIFF_OPTIONS = ["--no-ext-diff", "--relative"];
3516
+ /**
3499
3517
  * The coloured diff of `.zitadel/` against `sha`, or null when no diff can be
3500
3518
  * shown: git is missing, this is not a repo, or the commit is not in local
3501
3519
  * history (a shallow clone, say). A dead commit is told apart from a clean tree
3502
3520
  * by first checking the commit resolves.
3503
3521
  */
3504
3522
  async function diffConfigAgainst(cwd, sha, color, referenced = []) {
3523
+ return diffConfig(cwd, sha, [color ? "--color=always" : "--no-color"], referenced);
3524
+ }
3525
+ /**
3526
+ * {@link diffConfigAgainst}, uncoloured and split by file: joined back
3527
+ * together, the patches are the diff the terminal shows. Null when no diff can
3528
+ * be shown, for the same reasons.
3529
+ */
3530
+ async function diffConfigFilesAgainst(cwd, sha, referenced = []) {
3531
+ const output = await diffConfig(cwd, sha, [
3532
+ "--no-color",
3533
+ "--raw",
3534
+ "-z",
3535
+ "--patch"
3536
+ ], referenced);
3537
+ return output === null ? null : splitByFile(output);
3538
+ }
3539
+ /**
3540
+ * Pair the `--raw -z` records with the patch's per-file sections. git writes
3541
+ * both from one list of files, in one order, so they line up one to one, except
3542
+ * for a file whose type changed (a file replaced by a symlink): git shows it as
3543
+ * a deletion and a creation, two sections under one header, joined here.
3544
+ */
3545
+ function splitByFile(output) {
3546
+ let at = 0;
3547
+ const field = () => {
3548
+ const end = output.indexOf("\0", at);
3549
+ const value = output.slice(at, end === -1 ? output.length : end);
3550
+ at = end === -1 ? output.length : end + 1;
3551
+ return value;
3552
+ };
3553
+ const records = [];
3554
+ while (output.startsWith(":", at)) {
3555
+ const meta = field();
3556
+ const letter = meta.charAt(meta.lastIndexOf(" ") + 1);
3557
+ const path = field();
3558
+ if (letter === "R" || letter === "C") {
3559
+ const status = letter === "R" ? "renamed" : "copied";
3560
+ records.push({
3561
+ path: field(),
3562
+ status,
3563
+ previous_path: path
3564
+ });
3565
+ } else {
3566
+ const status = letter === "A" ? "added" : letter === "D" ? "deleted" : "modified";
3567
+ records.push({
3568
+ path,
3569
+ status
3570
+ });
3571
+ }
3572
+ }
3573
+ const sections = output.slice(at + 1).split(/^(?=diff --git )/m);
3574
+ let next = 0;
3575
+ return records.map((record) => {
3576
+ let patch = sections[next++] ?? "";
3577
+ while (next < sections.length && headerOf(sections[next]) === headerOf(patch)) patch += sections[next++];
3578
+ return {
3579
+ ...record,
3580
+ patch
3581
+ };
3582
+ });
3583
+ }
3584
+ /** A patch section's first line, its `diff --git` header. */
3585
+ function headerOf(section = "") {
3586
+ const end = section.indexOf("\n");
3587
+ return end === -1 ? section : section.slice(0, end);
3588
+ }
3589
+ /** `git diff` of the release's paths against `sha`, in `format`. */
3590
+ async function diffConfig(cwd, sha, format, referenced) {
3505
3591
  const paths = [...CONFIG_PATHS, ...referenced];
3506
3592
  if (await git(cwd, [
3507
3593
  "cat-file",
@@ -3510,7 +3596,8 @@ async function diffConfigAgainst(cwd, sha, color, referenced = []) {
3510
3596
  ]) === null) return null;
3511
3597
  const diffArgs = [
3512
3598
  "diff",
3513
- color ? "--color=always" : "--no-color",
3599
+ ...DIFF_OPTIONS,
3600
+ ...format,
3514
3601
  sha,
3515
3602
  "--",
3516
3603
  ...paths
@@ -4159,42 +4246,51 @@ function assertEnvRefsResolve(ctx) {
4159
4246
  * library and no server fallback: when git is missing, this is not a repo, the
4160
4247
  * commit is not in local history, or the live release carries no `git_sha`, it
4161
4248
  * prints one line saying no diff is available and carries on. Colour is
4162
- * suppressed under `--json`, `--no-color` and a non-terminal, and the whole
4163
- * line is withheld under `--json` so the envelope travels alone on stdout.
4249
+ * suppressed under `--no-color` and a non-terminal. Under `--json` nothing is
4250
+ * printed, so the envelope travels alone on stdout: the diff, split by file, or
4251
+ * the line saying why there is none, is reported as `diff` instead.
4164
4252
  */
4165
4253
  const diffStep = {
4166
4254
  name: "diff",
4167
4255
  runsFor: ["deploy", "preview"],
4168
4256
  async run(ctx) {
4169
- const note = (line) => {
4170
- if (!ctx.json) process.stdout.write(`${line}\n`);
4171
- };
4172
4257
  const deployments = await listLiveDeployments(ctx.client, ctx.projectId, { withRelease: true });
4173
4258
  const live = deployments.find(servesPrimary)?.release?.metadata;
4174
4259
  const sha = live?.git_sha;
4175
- if (!sha) {
4176
- note("No diff available: nothing is deployed yet, or the live release has no commit.");
4177
- return;
4178
- }
4179
- if (live?.git_dirty) {
4180
- note(`No reliable diff available: the live release was built from an uncommitted tree at ${sha.slice(0, 12)}.`);
4181
- return;
4182
- }
4260
+ if (!sha) return noDiff(ctx, null, "No diff available: nothing is deployed yet, or the live release has no commit.");
4261
+ if (live?.git_dirty) return noDiff(ctx, sha, `No reliable diff available: the live release was built from an uncommitted tree at ${sha.slice(0, 12)}.`);
4183
4262
  const releases = new Set(deployments.filter(servesPrimary).map((entry) => entry.release_id));
4184
- if (releases.size > 1) note(`The primary origins serve ${releases.size} different releases; the diff below is against the newest, ${sha.slice(0, 12)}.`);
4263
+ const note = releases.size > 1 ? `The primary origins serve ${releases.size} different releases; the diff is against the newest, ${sha.slice(0, 12)}.` : void 0;
4264
+ const unknown = `No diff available: ${sha.slice(0, 12)} is not in this repository's history.`;
4185
4265
  const referenced = ctx.brandingSources.flatMap((descriptor) => brandingReferencePaths(ctx.cwd, descriptor));
4186
- const diff = await diffConfigAgainst(ctx.cwd, sha, ctx.color, referenced);
4187
- if (diff === null) {
4188
- note(`No diff available: ${sha.slice(0, 12)} is not in this repository's history.`);
4189
- return;
4190
- }
4191
- if (diff.trim() === "") {
4192
- note(`No changes to .zitadel/ since ${sha.slice(0, 12)}.`);
4193
- return;
4266
+ if (ctx.json) {
4267
+ const files = await diffConfigFilesAgainst(ctx.cwd, sha, referenced);
4268
+ if (files === null) return noDiff(ctx, sha, unknown);
4269
+ return { diff: {
4270
+ against: sha,
4271
+ files,
4272
+ ...note === void 0 ? {} : { note }
4273
+ } };
4194
4274
  }
4195
- if (!ctx.json) process.stdout.write(diff.endsWith("\n") ? diff : `${diff}\n`);
4275
+ if (note !== void 0) process.stdout.write(`${note}\n`);
4276
+ const diff = await diffConfigAgainst(ctx.cwd, sha, ctx.color, referenced);
4277
+ if (diff === null) return noDiff(ctx, sha, unknown);
4278
+ const shown = diff.trim() === "" ? `No changes to .zitadel/ since ${sha.slice(0, 12)}.` : diff;
4279
+ process.stdout.write(shown.endsWith("\n") ? shown : `${shown}\n`);
4196
4280
  }
4197
4281
  };
4282
+ /** No diff to show: the line saying why, printed, or reported under `--json`. */
4283
+ function noDiff(ctx, against, note) {
4284
+ if (!ctx.json) {
4285
+ process.stdout.write(`${note}\n`);
4286
+ return;
4287
+ }
4288
+ return { diff: {
4289
+ against,
4290
+ files: null,
4291
+ note
4292
+ } };
4293
+ }
4198
4294
  //#endregion
4199
4295
  //#region src/lib/release/index.ts
4200
4296
  /**
@@ -4279,7 +4375,7 @@ function exploreAllCommands(cliVersion) {
4279
4375
  * the run was about.
4280
4376
  */
4281
4377
  function customizeAndPublishActions(cliVersion, selector) {
4282
- return ["Customize what you ask for and how people sign in: edit .zitadel/schemas/ to change the information you collect, and .zitadel/flows/ to change the sign-up/sign-in experience. Each folder has a README with examples.", `See your changes before they go live: ${projectCommand("deploy --dry-run", selector, cliVersion)} to preview, then ${projectCommand(DEPLOY_ARGS, selector, cliVersion)} to publish.`];
4378
+ return ["Customize what you ask for and how people sign in: edit .zitadel/schemas/ to change the information you collect, and .zitadel/flows/ to change the sign-up/sign-in experience. Each folder has a README with examples.", `See your changes before they go live: ${projectCommand("deploy --dry-run", selector, cliVersion)} to preview, then ${projectCommand(DEPLOY_ARGS, selector, cliVersion)} to publish. Nothing is released yet, so publishing is what pins this configuration rather than leaving the project on whatever its newest revisions happen to be.`];
4283
4379
  }
4284
4380
  /**
4285
4381
  * How to change the login's look after setup. Setup never ejects a template
@@ -4343,6 +4439,6 @@ function isOptedOut(env) {
4343
4439
  return value !== void 0 && value !== "" && value !== "0" && value !== "false";
4344
4440
  }
4345
4441
  //#endregion
4346
- export { flagText as $, normalizePublicCliCommands as $t, compareInGroup as A, variableInventoryCells as At, reportSecretOutcome as B, loadOrCreateIdentity as Bt, passwordPolicyFile as C, parseVariableValue as Ct, BRANDING_DIR as D, toVariableRows as Dt, readJsonDirEntries as E, toVariableRow as Et, readSchemaFiles as F, buildUserAgent as Ft, ENABLE_FLAG_KEYS as G, portableSuggestions as Gt, republishCommands as H, envEnabled as Ht, selectSchema as I, installUserAgent as It, SETUP_FLAG_NAMES as J, unscopedCommand as Jt, ENABLE_FLAG_NAMES as K, projectCommand as Kt, SCHEMAS_DIR as L, processUserAgentFacts as Lt, enabledMethods as M, variableScope as Mt, methodsOf as N, reportWarning as Nt, COMMAND_GROUPS as O, valuesOfKind as Ot, readFlowFiles as P, takeReportedWarnings as Pt, customFlagValues as Q, normalizePublicCliCommand as Qt, publishClientId as R, hostAgent as Rt, parsePasswordMinLength as S, assertVariableName as St, readJsonDir as T, toInventoryRows as Tt, storeClientSecret as U, asEnvironmentSelector as Ut, republishArgs as V, resolveConsent as Vt, askConnectionEndpoints as W, inShell as Wt, customExample as X, isTrainVersion as Xt, checkGivenCustomFlags as Y, isPortableShellWord as Yt, customFlagText as Z, maskPublicCliVersion as Zt, servesPrimary as _, stopListeningForInterrupt as _n, storedCustomSettings as _t, serverLogAction as a, collectPages as an, applySsoToSchema as at, PASSWORD_POLICY_PATH as b, isMissingEndpoint as bn, offeredSignInMethods as bt, wrapForBox as c, setTopLevelJsonKey as cn, ssoEditRefusal as ct, validateStep as d, createZitadelClient$1 as dn, IDPS_DIR as dt, normalizePublicCliJson as en, givenCustomFlags as et, headSha as f, escapeControlCharacters as fn, checkCustomSlug as ft, rollbackDeployment as g, listenForInterrupt as gn, refuseChangedIssuer as gt, listLiveDeployments as h, interruptSignal as hn, readConnectionFiles as ht, exploreAllCommands as i, sdkVersionForCli as in, applySsoToFlow as it, authMethods as j, variableReadScope as jt, CommandGroups as k, variableCells as kt, DEPLOY_ARGS as l, stableStringify as ln, ssoProvidersRefusal as lt, createDeployment as m, sanitizeResponse as mn, planConnection as mt, brandingGuidanceAction as n, optionArgs as nn, selectionFromFlags as nt, verifyLoginAction as o, isObject$1 as on, removeSsoFromFlow as ot, worktreeIsDirty as p, sanitizeRejection as pn, credentialVariablesOf as pt, SETUP_FLAG_KEYS as q, shellCommand as qt, customizeAndPublishActions as r, publicCliCommand as rn, ssoEnableArgs as rt, renderBoxActions as s, parseJsonObject as sn, removeSsoFromSchema as st, showSkillsHint as t, normalizePublicCliProse as tn, loopbackIssuerWarning as tt, diffStep as u, REQUEST_TIMEOUT_MS as un, CONNECTION_SCHEMA_REF as ut, buildBundle as v, ZitadelError as vn, FLOWS_DIR as vt, toPolicyWireBody as w, readStdin as wt, POLICIES_DIR as x, toZitadelError as xn, VARIABLE_TYPES as xt, PASSWORD_POLICY_OPERATION as y, isErrno$1 as yn, checkFlow as yt, reportClientIdOutcome as z, ciProvider as zt };
4442
+ export { checkGivenCustomFlags as $, isPortableShellWord as $t, readJsonDirEntries as A, toVariableRow as At, selectSchema as B, installUserAgent as Bt, isPolicyTestFile as C, isErrno$1 as Cn, checkFlow as Ct, policyFileName as D, parseVariableValue as Dt, policyFileIssues as E, assertVariableName as Et, authMethods as F, variableReadScope as Ft, republishArgs as G, resolveConsent as Gt, publishClientId as H, hostAgent as Ht, enabledMethods as I, variableScope as It, askConnectionEndpoints as J, inShell as Jt, republishCommands as K, envEnabled as Kt, methodsOf as L, reportWarning as Lt, COMMAND_GROUPS as M, valuesOfKind as Mt, CommandGroups as N, variableCells as Nt, toPolicyWireBody as O, readStdin as Ot, compareInGroup as P, variableInventoryCells as Pt, SETUP_FLAG_NAMES as Q, unscopedCommand as Qt, readFlowFiles as R, takeReportedWarnings as Rt, POLICY_TEST_SUFFIX as S, ZitadelError as Sn, FLOWS_DIR as St, passwordPolicyFile as T, toZitadelError as Tn, VARIABLE_TYPES as Tt, reportClientIdOutcome as U, ciProvider as Ut, SCHEMAS_DIR as V, processUserAgentFacts as Vt, reportSecretOutcome as W, loadOrCreateIdentity as Wt, ENABLE_FLAG_NAMES as X, projectCommand as Xt, ENABLE_FLAG_KEYS as Y, portableSuggestions as Yt, SETUP_FLAG_KEYS as Z, shellCommand as Zt, servesPrimary as _, sanitizeRejection as _n, credentialVariablesOf as _t, serverLogAction as a, normalizePublicCliProse as an, loopbackIssuerWarning as at, PASSWORD_POLICY_PATH as b, listenForInterrupt as bn, refuseChangedIssuer as bt, wrapForBox as c, sdkVersionForCli as cn, applySsoToFlow as ct, validateStep as d, parseJsonObject as dn, removeSsoFromSchema as dt, isTrainVersion as en, customExample as et, headSha as f, setTopLevelJsonKey as fn, ssoEditRefusal as ft, rollbackDeployment as g, escapeControlCharacters as gn, checkCustomSlug as gt, listLiveDeployments as h, createZitadelClient$1 as hn, IDPS_DIR as ht, exploreAllCommands as i, normalizePublicCliJson as in, givenCustomFlags as it, BRANDING_DIR as j, toVariableRows as jt, readJsonDir as k, toInventoryRows as kt, DEPLOY_ARGS as l, collectPages as ln, applySsoToSchema as lt, createDeployment as m, REQUEST_TIMEOUT_MS as mn, CONNECTION_SCHEMA_REF as mt, brandingGuidanceAction as n, normalizePublicCliCommand as nn, customFlagValues as nt, verifyLoginAction as o, optionArgs as on, selectionFromFlags as ot, worktreeIsDirty as p, stableStringify as pn, ssoProvidersRefusal as pt, storeClientSecret as q, asEnvironmentSelector as qt, customizeAndPublishActions as r, normalizePublicCliCommands as rn, flagText as rt, renderBoxActions as s, publicCliCommand as sn, ssoEnableArgs as st, showSkillsHint as t, maskPublicCliVersion as tn, customFlagText as tt, diffStep as u, isObject$1 as un, removeSsoFromFlow as ut, buildBundle as v, sanitizeResponse as vn, planConnection as vt, parsePasswordMinLength as w, isMissingEndpoint as wn, offeredSignInMethods as wt, POLICIES_DIR as x, stopListeningForInterrupt as xn, storedCustomSettings as xt, PASSWORD_POLICY_OPERATION as y, interruptSignal as yn, readConnectionFiles as yt, readSchemaFiles as z, buildUserAgent as zt };
4347
4443
 
4348
- //# sourceMappingURL=skills-hint-CBoak9HL.mjs.map
4444
+ //# sourceMappingURL=skills-hint-N1H5-ny1.mjs.map