@zitadel/cli 0.1.0-alpha.14 → 0.1.0-alpha.15

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,10 +1,11 @@
1
- import { C as isObject, E as ZitadelError } from "./oclif-Bm-FkF6z.mjs";
1
+ import { C as isObject, E as ZitadelError, T as stableStringify } from "./oclif-Bm-FkF6z.mjs";
2
2
  import { t as SCHEMAS_DIR } from "./user-schema-DDz5-lX5.mjs";
3
3
  import { readFile, readdir, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { consola as consola$1 } from "consola";
6
6
  import { createHash } from "node:crypto";
7
7
  import { DEFAULT_FLOW_SCHEMA_URI } from "@zitadel/config/defaults";
8
+ import { normalizeFlowBody, normalizeSchemaBody } from "@zitadel/config/normalize";
8
9
  import { flowConfigSchema, schemaConfigSchema } from "@zitadel/config/schemas";
9
10
  //#region src/lib/flows/env-refs.ts
10
11
  /**
@@ -66,6 +67,7 @@ var SchemaSyncer = class {
66
67
  directory = SCHEMAS_DIR;
67
68
  mutable = false;
68
69
  revisioned = true;
70
+ normalize = normalizeSchemaBody;
69
71
  constructor(client, projectId, env) {
70
72
  this.client = client;
71
73
  this.projectId = projectId;
@@ -85,9 +87,21 @@ var SchemaSyncer = class {
85
87
  /**
86
88
  * `POST /schemas` mints a new immutable row. The server allocates the
87
89
  * opaque id; the CLI records it in state and re-pins flows against it.
90
+ * The create response carries only the id, so the canonical stored body
91
+ * comes from a follow-up fetch; a fetch failure degrades to no
92
+ * write-back rather than failing the create.
88
93
  */
89
94
  async create(data) {
90
- return (await this.client.createSchema(data, { project_id: this.projectId })).id;
95
+ const result = await this.client.createSchema(data, { project_id: this.projectId });
96
+ try {
97
+ return {
98
+ id: result.id,
99
+ canonical: await this.fetch(result.id)
100
+ };
101
+ } catch (err) {
102
+ consola$1.debug(`fetch created schema ${result.id} failed:`, err);
103
+ return { id: result.id };
104
+ }
91
105
  }
92
106
  /**
93
107
  * Not called by the sync loop: schemas are `revisioned`, so a hash change
@@ -110,6 +124,8 @@ var FlowDefinitionSyncer = class {
110
124
  directory = FLOWS_DIR;
111
125
  mutable = true;
112
126
  revisioned = false;
127
+ normalize = normalizeFlowBody;
128
+ normalizeWrite = normalizeFlowBody;
113
129
  constructor(client, projectId, env) {
114
130
  this.client = client;
115
131
  this.projectId = projectId;
@@ -132,11 +148,15 @@ var FlowDefinitionSyncer = class {
132
148
  * envelope.
133
149
  */
134
150
  async create(data) {
135
- return (await this.client.createFlowDefinition({
151
+ const result = await this.client.createFlowDefinition({
136
152
  project_id: this.projectId,
137
153
  schema_uri: DEFAULT_FLOW_SCHEMA_URI,
138
154
  flow_definition: data
139
- })).id;
155
+ });
156
+ return {
157
+ id: result.id,
158
+ canonical: result.flow_definition
159
+ };
140
160
  }
141
161
  /**
142
162
  * PUT completely replaces the flow definition. The wire request wraps the
@@ -146,7 +166,7 @@ var FlowDefinitionSyncer = class {
146
166
  * it is human-editable.
147
167
  */
148
168
  async update(id, data) {
149
- await this.client.updateFlowDefinition(id, { flow_definition: data }, { project_id: this.projectId });
169
+ return { canonical: (await this.client.updateFlowDefinition(id, { flow_definition: data }, { project_id: this.projectId })).flow_definition };
150
170
  }
151
171
  async delete(id) {
152
172
  await this.client.deleteFlowDefinition(id, { project_id: this.projectId });
@@ -229,6 +249,13 @@ async function buildSyncPlan(cwd, syncers, fetchOld = false) {
229
249
  const state = await readState(cwd);
230
250
  const actions = [];
231
251
  const localFlows = await readLocalFlowUserSchemas(cwd);
252
+ const pendingRevisions = /* @__PURE__ */ new Map();
253
+ const recoveredRevisions = /* @__PURE__ */ new Map();
254
+ for (const [schemaPath, entry] of Object.entries(state.resources)) if (entry.previousId && entry.id && entry.previousId !== entry.id) recoveredRevisions.set(entry.previousId, {
255
+ schemaPath,
256
+ newId: entry.id
257
+ });
258
+ const scannedContents = /* @__PURE__ */ new Map();
232
259
  for (const syncer of syncers) {
233
260
  const dirPath = join(cwd, syncer.directory);
234
261
  consola$1.debug(`scanning ${syncer.directory}`);
@@ -253,19 +280,33 @@ async function buildSyncPlan(cwd, syncers, fetchOld = false) {
253
280
  }
254
281
  for (const [absPath, content] of onDisk.entries()) {
255
282
  const relPath = absPath.slice(cwd.length + 1);
283
+ scannedContents.set(relPath, content);
256
284
  const entry = state.resources[relPath];
257
- const hash = hashResourceContent(content);
285
+ const hash = hashForState(syncer, content);
286
+ const flowRef = localFlows.get(relPath);
287
+ const pending = flowRef ? pendingRevisions.get(flowRef) : void 0;
288
+ const recovered = flowRef ? recoveredRevisions.get(flowRef) : void 0;
289
+ const repin = pending ? {
290
+ previousId: flowRef,
291
+ schemaPath: pending.schemaPath
292
+ } : recovered ? {
293
+ previousId: flowRef,
294
+ schemaPath: recovered.schemaPath,
295
+ newId: recovered.newId
296
+ } : void 0;
297
+ if (repin) assertRepinnedFlowFields(relPath, content, repin.schemaPath, scannedContents.get(repin.schemaPath));
258
298
  if (!entry?.id) {
259
299
  actions.push({
260
300
  kind: "create",
261
301
  path: relPath,
262
302
  syncer,
263
303
  content,
264
- hash
304
+ hash,
305
+ ...repin ? { repin } : {}
265
306
  });
266
307
  continue;
267
308
  }
268
- if (entry.hash === hash) {
309
+ if ((entry.hash === hash || entry.hash === hashResourceContent(content) || entry.hash === hashResourceContent(JSON.parse(stableStringify(content)))) && !(repin && syncer.mutable)) {
269
310
  actions.push({
270
311
  kind: "skip",
271
312
  path: relPath,
@@ -275,6 +316,7 @@ async function buildSyncPlan(cwd, syncers, fetchOld = false) {
275
316
  }
276
317
  if (syncer.revisioned) {
277
318
  const oldContent = await fetchOldIfAsked(syncer, entry.id, fetchOld);
319
+ pendingRevisions.set(entry.id, { schemaPath: relPath });
278
320
  actions.push({
279
321
  kind: "revise",
280
322
  path: relPath,
@@ -303,7 +345,8 @@ async function buildSyncPlan(cwd, syncers, fetchOld = false) {
303
345
  id: entry.id,
304
346
  content,
305
347
  hash,
306
- oldContent
348
+ oldContent,
349
+ ...repin ? { repin } : {}
307
350
  });
308
351
  }
309
352
  }
@@ -312,7 +355,10 @@ async function buildSyncPlan(cwd, syncers, fetchOld = false) {
312
355
  /**
313
356
  * Execute every action returned by {@link buildSyncPlan} against the
314
357
  * platform. Updates the local state file (`.zitadel/state.json`) as
315
- * each action completes so an interrupted run can resume.
358
+ * each action completes so an interrupted run can resume. After each
359
+ * mutation, the server's canonical body is written back to the local
360
+ * file (when it differs in normalized form), so repo config matches
361
+ * live state by construction and the next `plan` is empty.
316
362
  *
317
363
  * The platform target (base URL + bearer auth) lives in the api
318
364
  * package's runtime registries; callers set them before invoking this.
@@ -323,33 +369,69 @@ async function buildSyncPlan(cwd, syncers, fetchOld = false) {
323
369
  */
324
370
  async function runSyncLoop(cwd, syncers) {
325
371
  const actions = await buildSyncPlan(cwd, syncers);
372
+ const filesUpdated = [];
373
+ const repinned = /* @__PURE__ */ new Map();
374
+ const writeBack = async (action, canonical, fallbackHash) => {
375
+ if (!canonical) return fallbackHash;
376
+ const { hash, changed } = await writeBackResource(cwd, action.path, action.syncer, canonical);
377
+ if (changed) {
378
+ filesUpdated.push(action.path);
379
+ consola$1.info(`Updated ${action.path} from the server's canonical response`);
380
+ }
381
+ return hash;
382
+ };
326
383
  for (const action of actions) switch (action.kind) {
327
384
  case "create": {
328
- const id = await action.syncer.create(action.content);
385
+ let content = action.content;
386
+ const newId = action.repin ? repinned.get(action.repin.previousId) ?? action.repin.newId : void 0;
387
+ if (newId) content = {
388
+ ...content,
389
+ user_schema: newId
390
+ };
391
+ const { id, canonical } = await action.syncer.create(content);
329
392
  const entry = {
330
393
  id,
331
- hash: action.hash
394
+ hash: await writeBack(action, canonical, newId ? hashForState(action.syncer, content) : action.hash)
332
395
  };
333
396
  await updateState(cwd, action.path, entry);
334
397
  consola$1.info(`Created a new ${action.syncer.kind} on Zitadel from ${action.path} (id ${id})`);
335
398
  break;
336
399
  }
337
400
  case "revise": {
338
- const id = await action.syncer.create(action.content);
401
+ const { id, canonical } = await action.syncer.create(action.content);
339
402
  const entry = {
340
403
  id,
341
- hash: action.hash
404
+ hash: await writeBack(action, canonical, action.hash),
405
+ previousId: action.previousId
342
406
  };
343
407
  await updateState(cwd, action.path, entry);
408
+ repinned.set(action.previousId, id);
344
409
  consola$1.info(`Published a new ${action.syncer.kind} revision on Zitadel from ${action.path} (id ${id})`);
345
- if (action.affectedPaths.length > 0) consola$1.warn(`New ${action.syncer.kind} revision ${id}. Update user_schema in these flow definitions to adopt it:\n` + action.affectedPaths.map((path) => ` - ${path}`).join("\n"));
410
+ for (const flowPath of action.affectedPaths) if (await repinFlowFile(cwd, flowPath, action.previousId, id)) {
411
+ filesUpdated.push(flowPath);
412
+ consola$1.info(`Re-pinned user_schema in ${flowPath} to ${id}`);
413
+ }
346
414
  break;
347
415
  }
348
- case "update":
349
- await action.syncer.update(action.id, action.content);
350
- await updateState(cwd, action.path, { hash: action.hash });
416
+ case "update": {
417
+ let content = action.content;
418
+ const newId = action.repin ? repinned.get(action.repin.previousId) ?? action.repin.newId : void 0;
419
+ if (newId && action.repin) {
420
+ content = {
421
+ ...content,
422
+ user_schema: newId
423
+ };
424
+ if (await repinFlowFile(cwd, action.path, action.repin.previousId, newId)) {
425
+ filesUpdated.push(action.path);
426
+ consola$1.info(`Re-pinned user_schema in ${action.path} to ${newId}`);
427
+ }
428
+ }
429
+ const { canonical } = await action.syncer.update(action.id, content);
430
+ const fallbackHash = newId ? hashForState(action.syncer, content) : action.hash;
431
+ await updateState(cwd, action.path, { hash: await writeBack(action, canonical, fallbackHash) });
351
432
  consola$1.info(`Updated the ${action.syncer.kind} on Zitadel from ${action.path}`);
352
433
  break;
434
+ }
353
435
  case "delete":
354
436
  await action.syncer.delete(action.id);
355
437
  await removeFromState(cwd, action.path);
@@ -359,6 +441,73 @@ async function runSyncLoop(cwd, syncers) {
359
441
  consola$1.debug(`Skipped ${action.path} (${action.reason})`);
360
442
  break;
361
443
  }
444
+ const remainingPins = new Set((await readLocalFlowUserSchemas(cwd)).values());
445
+ const finalState = await readState(cwd);
446
+ for (const [path, entry] of Object.entries(finalState.resources)) if (entry.previousId && !remainingPins.has(entry.previousId)) await updateState(cwd, path, { previousId: void 0 });
447
+ return { filesUpdated: [...new Set(filesUpdated)] };
448
+ }
449
+ /**
450
+ * Rewrite a flow file's `user_schema` pin from `previousId` to `newId`,
451
+ * lockfile-style. Prefers a targeted text replacement so the author's
452
+ * formatting survives a one-string change; falls back to parse +
453
+ * `stableStringify` when the raw text doesn't contain exactly one pin.
454
+ * Returns false when the file doesn't pin `previousId` (already re-pinned
455
+ * or hand-edited) — never throws for an unreadable file.
456
+ */
457
+ async function repinFlowFile(cwd, relPath, previousId, newId) {
458
+ const absPath = join(cwd, relPath);
459
+ let raw;
460
+ try {
461
+ raw = await readFile(absPath, "utf8");
462
+ } catch (err) {
463
+ consola$1.debug(`read ${relPath} for re-pin failed:`, err);
464
+ return false;
465
+ }
466
+ const pinPattern = new RegExp(`("user_schema"\\s*:\\s*)${escapeRegExp(JSON.stringify(previousId))}`, "g");
467
+ if (raw.match(pinPattern)?.length === 1) {
468
+ await writeFile(absPath, raw.replace(pinPattern, (_match, prefix) => `${prefix}${JSON.stringify(newId)}`));
469
+ return true;
470
+ }
471
+ try {
472
+ const doc = JSON.parse(raw);
473
+ if (doc.user_schema !== previousId) return false;
474
+ doc.user_schema = newId;
475
+ await writeFile(absPath, `${stableStringify(doc)}\n`);
476
+ return true;
477
+ } catch (err) {
478
+ consola$1.debug(`re-pin ${relPath} failed:`, err);
479
+ return false;
480
+ }
481
+ }
482
+ function escapeRegExp(value) {
483
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
484
+ }
485
+ /**
486
+ * Reconcile a local file with the server's canonical body. What gets
487
+ * written is the `normalizeWrite` form (strips pure transport noise like
488
+ * the empty `audience` echo; for schemas the canonical body verbatim —
489
+ * spelled-out x-* defaults must survive, or the next apply would publish
490
+ * a revision without them). Equality is judged in the `normalize`
491
+ * comparison form, so the file is rewritten only when it materially
492
+ * differs from live state; hand-formatted files stay untouched otherwise.
493
+ * Returns the state hash of the written form.
494
+ */
495
+ async function writeBackResource(cwd, relPath, syncer, canonical) {
496
+ const writeBody = syncer.normalizeWrite?.(canonical) ?? canonical;
497
+ const compare = (body) => stableStringify(syncer.normalize?.(body) ?? body);
498
+ const absPath = join(cwd, relPath);
499
+ let changed = true;
500
+ try {
501
+ const onDisk = JSON.parse(await readFile(absPath, "utf8"));
502
+ changed = compare(writeBody) !== compare(onDisk);
503
+ } catch (err) {
504
+ consola$1.debug(`read ${relPath} for write-back failed:`, err);
505
+ }
506
+ if (changed) await writeFile(absPath, `${stableStringify(writeBody)}\n`);
507
+ return {
508
+ hash: hashForState(syncer, writeBody),
509
+ changed
510
+ };
362
511
  }
363
512
  async function fetchOldIfAsked(syncer, id, fetchOld) {
364
513
  if (!fetchOld || !syncer.fetch) return null;
@@ -400,14 +549,53 @@ async function readLocalFlowUserSchemas(cwd) {
400
549
  }
401
550
  return result;
402
551
  }
552
+ /**
553
+ * Fail fast when a flow that is about to adopt a new schema revision
554
+ * references properties the new revision no longer has (the server would
555
+ * reject the flow update with `flow field: not a property in the user
556
+ * schema`). Runs at plan time, before any platform mutation — otherwise
557
+ * the revise would publish first and the run would die half-applied.
558
+ * Only plain property fields are checked; reserved credential tokens
559
+ * (`x-auth-methods#…`) resolve outside the schema.
560
+ */
561
+ function assertRepinnedFlowFields(flowPath, flowContent, schemaPath, schemaContent) {
562
+ if (!schemaContent) return;
563
+ const properties = schemaContent.properties;
564
+ if (typeof properties !== "object" || properties === null) return;
565
+ const steps = flowContent.steps;
566
+ const missing = [];
567
+ for (const step of Array.isArray(steps) ? steps : []) {
568
+ const fields = Array.isArray(step.fields) ? step.fields : [];
569
+ for (const field of fields) {
570
+ if (typeof field !== "string" || field.includes("#")) continue;
571
+ if (!Object.prototype.hasOwnProperty.call(properties, field)) missing.push(`step ${JSON.stringify(step.name ?? "?")}: ${JSON.stringify(field)}`);
572
+ }
573
+ }
574
+ if (missing.length > 0) throw new ZitadelError("E_VALIDATION", `${flowPath} cannot adopt the new revision of ${schemaPath}: flow fields missing from the edited schema — ${missing.join(", ")}`, { hint: "Update the flow's steps[].fields to match the edited schema (or restore the removed/renamed properties), then re-run plan/apply." });
575
+ }
403
576
  function findFlowsPinnedTo(previousId, localFlows) {
404
577
  const affected = [];
405
578
  for (const [relPath, ref] of localFlows.entries()) if (ref === previousId) affected.push(relPath);
406
579
  return affected;
407
580
  }
581
+ /**
582
+ * Legacy content hash: order-sensitive and normalization-blind. Kept only
583
+ * so state entries written by older CLI versions still match; new hashes
584
+ * come from {@link hashForState}.
585
+ */
408
586
  function hashResourceContent(data) {
409
587
  return createHash("sha256").update(JSON.stringify(data)).digest("hex");
410
588
  }
589
+ /**
590
+ * The content hash stored in `.zitadel/state.json`: key-order-insensitive
591
+ * (via `stableStringify`) and computed on the syncer's normalized form, so
592
+ * reordering keys or spelling out a meta-schema default does not read as an
593
+ * edit.
594
+ */
595
+ function hashForState(syncer, data) {
596
+ const normalized = syncer.normalize?.(data) ?? data;
597
+ return createHash("sha256").update(stableStringify(normalized)).digest("hex");
598
+ }
411
599
  //#endregion
412
600
  //#region src/lib/sync/plan-renderer.ts
413
601
  /**
@@ -604,7 +792,7 @@ function renderDiff(oldObj, newObj, prefixCol, tty, lines) {
604
792
  const col = (s) => paint(s, A.yellow, tty);
605
793
  lines.push(col(`${pad}~ ${pk} = ${fmtPrimitive(oldVal)} -> ${fmtPrimitive(newVal)}`));
606
794
  }
607
- else if (Array.isArray(oldVal) && Array.isArray(newVal)) if (JSON.stringify(oldVal) === JSON.stringify(newVal)) if (newVal.length === 0) lines.push(`${pad} ${pk} = []`);
795
+ else if (Array.isArray(oldVal) && Array.isArray(newVal)) if (stableStringify(oldVal) === stableStringify(newVal)) if (newVal.length === 0) lines.push(`${pad} ${pk} = []`);
608
796
  else {
609
797
  lines.push(`${pad} ${pk} = [`);
610
798
  renderArrayItems(newVal, " ", prefixCol + 4, {
@@ -671,6 +859,14 @@ function resourceName(path) {
671
859
  return path.split("/").pop() ?? path;
672
860
  }
673
861
  /**
862
+ * Diff both sides in the syncer's canonical form so server-echoed noise
863
+ * (empty `audience`, spelled-out meta-schema defaults) never renders as a
864
+ * change the author didn't make. Rendering only — upload payloads stay raw.
865
+ */
866
+ function normalized(syncer, content) {
867
+ return syncer.normalize?.(content) ?? content;
868
+ }
869
+ /**
674
870
  * Renders one Terraform-style resource block for a single `SyncAction`.
675
871
  *
676
872
  * Per-case notes:
@@ -694,10 +890,12 @@ function renderBlock(action, tty) {
694
890
  const opening = `${blkPad}+ resource "${action.syncer.kind}" "${resourceName(action.path)}" {`;
695
891
  lines.push(paint(header, A.bold, tty));
696
892
  lines.push(paint(opening, A.green, tty));
697
- renderFields({
893
+ const display = {
698
894
  id: KNOWN_AFTER_APPLY,
699
895
  ...action.content
700
- }, "+", FIELD_COL, {
896
+ };
897
+ if (action.repin) display.user_schema = action.repin.newId ?? KNOWN_AFTER_APPLY;
898
+ renderFields(display, "+", FIELD_COL, {
701
899
  tty,
702
900
  deleteMode: false
703
901
  }, lines);
@@ -721,11 +919,17 @@ function renderBlock(action, tty) {
721
919
  break;
722
920
  }
723
921
  case "update": {
724
- const header = `${blkPad}# ${action.path} will be updated in-place`;
922
+ const headerSuffix = action.repin ? " (re-pin user_schema)" : "";
923
+ const header = `${blkPad}# ${action.path} will be updated in-place${headerSuffix}`;
725
924
  const opening = `${blkPad}~ resource "${action.syncer.kind}" "${resourceName(action.path)}" {`;
726
925
  lines.push(paint(header, A.bold, tty));
727
926
  lines.push(paint(opening, A.yellow, tty));
728
- if (action.oldContent) renderDiff(action.oldContent, action.content, FIELD_COL, tty, lines);
927
+ const newContent = action.repin ? {
928
+ ...normalized(action.syncer, action.content),
929
+ user_schema: action.repin.newId ?? KNOWN_AFTER_APPLY
930
+ } : normalized(action.syncer, action.content);
931
+ if (action.oldContent) renderDiff(normalized(action.syncer, action.oldContent), newContent, FIELD_COL, tty, lines);
932
+ else if (action.repin) lines.push(paint(`${" ".repeat(FIELD_COL)}~ user_schema = "${action.repin.previousId}" -> ${action.repin.newId ? `"${action.repin.newId}"` : KNOWN_AFTER_APPLY}`, A.yellow, tty));
729
933
  else lines.push(`${" ".repeat(FIELD_COL)} # (field diff unavailable — no read endpoint for ${action.syncer.kind})`);
730
934
  lines.push(`${closePad}}`);
731
935
  break;
@@ -737,15 +941,15 @@ function renderBlock(action, tty) {
737
941
  lines.push(paint(opening, A.yellow, tty));
738
942
  if (action.oldContent) renderDiff({
739
943
  id: action.previousId,
740
- ...action.oldContent
944
+ ...normalized(action.syncer, action.oldContent)
741
945
  }, {
742
946
  id: KNOWN_AFTER_APPLY,
743
- ...action.content
947
+ ...normalized(action.syncer, action.content)
744
948
  }, FIELD_COL, tty, lines);
745
949
  else lines.push(`${" ".repeat(FIELD_COL)} # (field diff unavailable — no read endpoint for ${action.syncer.kind})`);
746
950
  lines.push(`${closePad}}`);
747
951
  if (action.affectedPaths.length > 0) {
748
- lines.push(paint(`${blkPad}# after apply, update user_schema in these flow definitions:`, A.yellow, tty));
952
+ lines.push(paint(`${blkPad}# user_schema will be re-pinned to the new revision ${KNOWN_AFTER_APPLY} in:`, A.yellow, tty));
749
953
  for (const path of action.affectedPaths) lines.push(paint(`${blkPad}# - ${path}`, A.yellow, tty));
750
954
  }
751
955
  break;
@@ -755,6 +959,6 @@ function renderBlock(action, tty) {
755
959
  return lines;
756
960
  }
757
961
  //#endregion
758
- export { runSyncLoop as a, FLOWS_DIR as c, hashResourceContent as i, summarizePlan as n, updateState as o, buildSyncPlan as r, makeSyncers as s, renderPlan as t };
962
+ export { runSyncLoop as a, makeSyncers as c, hashForState as i, FLOWS_DIR as l, summarizePlan as n, writeBackResource as o, buildSyncPlan as r, updateState as s, renderPlan as t };
759
963
 
760
- //# sourceMappingURL=sync-nSLnVhKK.mjs.map
964
+ //# sourceMappingURL=sync-CYuHVQT5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-CYuHVQT5.mjs","names":[],"sources":["../src/lib/flows/env-refs.ts","../src/lib/flows/index.ts","../src/lib/sync/syncers.ts","../src/lib/sync/state.ts","../src/lib/sync/loop.ts","../src/lib/sync/plan-renderer.ts"],"sourcesContent":["import { isObject } from \"../json\";\n\n/**\n * Collects the environment variables a flows document depends on, sorted and\n * de-duplicated. Recognises two reference styles: inline `${VAR}` interpolations\n * inside string values, and keys ending in `_env` whose value names a single\n * variable. `apply`/`plan` use this to fail before contacting the platform when\n * a required variable is absent.\n */\nexport function flowEnvRefs(value: unknown): string[] {\n const refs = new Set<string>();\n const visit = (node: unknown): void => {\n if (typeof node === \"string\") {\n for (const match of node.matchAll(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g)) {\n const ref = match[1];\n if (ref) {\n refs.add(ref);\n }\n }\n } else if (Array.isArray(node)) {\n node.forEach(visit);\n } else if (isObject(node)) {\n for (const [key, child] of Object.entries(node)) {\n if (key.endsWith(\"_env\") && typeof child === \"string\" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(child)) {\n refs.add(child);\n } else {\n visit(child);\n }\n }\n }\n };\n visit(value);\n return [...refs].sort();\n}\n","/**\n * Public surface for the flow domain. Every caller outside this module\n * imports from here (not from individual files) so the package\n * boundary stays observable.\n *\n * **Source of truth.** The wire shape lives in\n * `@zitadel/api/generated/model` (orval-generated from the\n * OpenAPI spec). Callers that need the type import\n * `CreateFlowDefinitionBodyFlowDefinition` from there directly;\n * callers that need the runtime validator import\n * `CreateFlowDefinitionBody` from\n * `@zitadel/api/generated/endpoints/zitadelNextGen.zod`. This\n * module owns only the CLI-specific concerns: the password-flow\n * builder, env-var reference scanning, and the file-level\n * `validateFlows` helper that surfaces `E_VALIDATION` errors against\n * the generated Zod.\n *\n * **Dependency rule.** No upward imports (`commands/`, `sync/`, etc.)\n * and no filesystem I/O. It depends sideways only on shared utilities\n * under `apps/cli/src/lib/` — today `lib/errors` (`ZitadelError`).\n */\nexport { buildFlow } from \"./build\";\nexport { validateFlows } from \"./validate\";\nexport { flowEnvRefs } from \"./env-refs\";\n\n/**\n * Relative directory (from the project root) where local flow files\n * live. Owned here so callers (`commands/*`, `sync/syncers.ts`) and\n * tests share a single source of truth for the path; the runtime\n * never depends on it directly because `lib/flows` does not touch\n * the filesystem.\n */\nexport const FLOWS_DIR = \".zitadel/flows\";\n","import type {\n CreateFlowDefinition201,\n CreateFlowDefinitionBodyFlowDefinition,\n UpdateFlowDefinition200,\n UpdateFlowDefinitionBodyFlowDefinition,\n CreateSchemaBody,\n GetSchemaById200,\n GetFlowDefinition200,\n} from \"@zitadel/api/generated/model\";\nimport { consola } from \"consola\";\n\nimport type { ZitadelClient } from \"@zitadel/api/client\";\nimport { DEFAULT_FLOW_SCHEMA_URI } from \"@zitadel/config/defaults\";\nimport { normalizeFlowBody, normalizeSchemaBody } from \"@zitadel/config/normalize\";\nimport { flowConfigSchema, schemaConfigSchema } from \"@zitadel/config/schemas\";\n\nimport { FLOWS_DIR, flowEnvRefs } from \"../flows\";\nimport { SCHEMAS_DIR } from \"../user-schema\";\nimport { ZitadelError } from \"../errors\";\nimport type { ResourceSyncer } from \"./types.js\";\n\n/** Runtime environment lookup used to resolve `${VAR}` / `*_env` references. */\ntype EnvLookup = Record<string, string | undefined>;\n\n/**\n * Build the syncer list with the context every syncer needs: the\n * `project_id` flow creates carry, and the runtime `env` against which\n * each file's `${VAR}` / `*_env` references are checked. Callers (apply /\n * plan / setup) read `project_id` from `.zitadel/secret` and pass the\n * process environment. The returned array is treated as read-only by the\n * sync loop.\n */\nexport function makeSyncers(opts: {\n client: ZitadelClient;\n projectId: string;\n env: EnvLookup;\n}): ReadonlyArray<ResourceSyncer> {\n return [\n new SchemaSyncer(opts.client, opts.projectId, opts.env),\n new FlowDefinitionSyncer(opts.client, opts.projectId, opts.env),\n ];\n}\n\n/**\n * Assert that every env var a resource references — `${VAR}` placeholders and\n * the `*_env` convention — is present in `env`, throwing `E_VALIDATION` listing\n * the missing names. Shared by every syncer so the check is identical for\n * schemas and flows, and runs in the sync engine before any platform call.\n */\nfunction assertEnvRefs(data: object, env: EnvLookup): void {\n const missing = flowEnvRefs(data).filter((name) => !env[name]);\n if (missing.length > 0) {\n throw new ZitadelError(\"E_VALIDATION\", `Missing environment variables: ${missing.join(\", \")}`);\n }\n}\n\nclass SchemaSyncer implements ResourceSyncer {\n readonly kind = \"schema\";\n readonly directory = SCHEMAS_DIR;\n readonly mutable = false;\n readonly revisioned = true;\n readonly normalize = normalizeSchemaBody;\n // Deliberately no `normalizeWrite`: the server stores schema bytes\n // verbatim, so stripping spelled-out x-* defaults from the local file\n // would drop them from the next published revision. Canonical schema\n // bodies are written back as-is; `normalize` is comparison-only.\n\n constructor(\n private readonly client: ZitadelClient,\n private readonly projectId: string,\n private readonly env: EnvLookup,\n ) {}\n\n /**\n * Parse against the generated `CreateSchemaBody` Zod (the orval-emitted\n * equivalent of `api/openapi/endpoints/schemas/user-schema.yaml`). The\n * generated schema is a union of `user-schema` and `schema-url`\n * discriminated on `kind`; both are valid on-disk bodies.\n */\n validate(data: object): void {\n const result = schemaConfigSchema.safeParse(data);\n if (!result.success) {\n throw new ZitadelError(\"E_VALIDATION\", \"Schema file is not a valid Zitadel schema body\", {\n details: { issues: result.error.issues },\n });\n }\n assertEnvRefs(data, this.env);\n }\n\n /**\n * `POST /schemas` mints a new immutable row. The server allocates the\n * opaque id; the CLI records it in state and re-pins flows against it.\n * The create response carries only the id, so the canonical stored body\n * comes from a follow-up fetch; a fetch failure degrades to no\n * write-back rather than failing the create.\n */\n async create(data: object): Promise<{ id: string; canonical?: object }> {\n const result = await this.client.createSchema(data as CreateSchemaBody, {\n project_id: this.projectId,\n });\n try {\n return { id: result.id, canonical: await this.fetch(result.id) };\n } catch (err) {\n consola.debug(`fetch created schema ${result.id} failed:`, err);\n return { id: result.id };\n }\n }\n\n /**\n * Not called by the sync loop: schemas are `revisioned`, so a hash change\n * publishes a new immutable revision through {@link create} rather than\n * mutating an existing row. Kept as a required interface member; throws\n * loudly if a caller reaches it.\n */\n async update(_id: string, _data: object): Promise<{ canonical?: object }> {\n throw new ZitadelError(\"E_NOT_IMPLEMENTED\", \"schemas are revisioned — edit publishes a new revision, not an update\");\n }\n\n async delete(id: string): Promise<void> {\n // Schemas are immutable on the platform: no PATCH, no DELETE in the\n // generated client. The sync loop's delete branch (`loop.ts`) still\n // schedules a delete action when a state entry exists and the\n // on-disk file is gone — `mutable` only gates updates, not deletes.\n // We deliberately fail loud here so the user notices that removing\n // a schema file is not a supported way to retire it.\n throw new ZitadelError(\"E_NOT_IMPLEMENTED\", `schema delete is not supported (${id})`);\n }\n\n async fetch(id: string): Promise<object> {\n const body = await this.client.getSchemaById(encodeURIComponent(id), {\n project_id: this.projectId,\n });\n return body as unknown as GetSchemaById200;\n }\n}\n\nclass FlowDefinitionSyncer implements ResourceSyncer {\n readonly kind = \"flow\";\n readonly directory = FLOWS_DIR;\n readonly mutable = true;\n readonly revisioned = false;\n readonly normalize = normalizeFlowBody;\n // For flows the comparison form doubles as the file form: everything it\n // strips (envelope keys, the empty `audience` echo) is transport noise.\n readonly normalizeWrite = normalizeFlowBody;\n\n constructor(\n private readonly client: ZitadelClient,\n private readonly projectId: string,\n private readonly env: EnvLookup,\n ) {}\n\n /**\n * Validates one flow file against the canonical `flowConfigSchema` (the\n * same Zod `validateFlows` and doctor use), then checks env references.\n */\n validate(data: object): void {\n const result = flowConfigSchema.safeParse(data);\n if (!result.success) {\n throw new ZitadelError(\"E_VALIDATION\", \"Flow file is not a valid Zitadel flow body\", {\n details: { issues: result.error.issues },\n });\n }\n assertEnvRefs(data, this.env);\n }\n\n /**\n * Wraps the bare on-disk flow body in the spec's create-envelope\n * (`api/openapi/components/flows/flow-definition-create-request.yaml`)\n * before sending. The file on disk stays bare so it is human-editable;\n * only the wire request carries `project_id` and the surrounding\n * envelope.\n */\n async create(data: object): Promise<{ id: string; canonical?: object }> {\n const result = (await this.client.createFlowDefinition({\n project_id: this.projectId,\n schema_uri: DEFAULT_FLOW_SCHEMA_URI,\n flow_definition: data as CreateFlowDefinitionBodyFlowDefinition,\n })) as CreateFlowDefinition201;\n return { id: result.id, canonical: result.flow_definition as object };\n }\n\n /**\n * PUT completely replaces the flow definition. The wire request wraps the\n * bare on-disk flow in the `{ flow_definition }` update envelope\n * (`api/openapi/components/flows/flow-definition-update-request.yaml`) and\n * carries `project_id` as a query parameter; the file on disk stays bare so\n * it is human-editable.\n */\n async update(id: string, data: object): Promise<{ canonical?: object }> {\n const result = (await this.client.updateFlowDefinition(\n id,\n { flow_definition: data as UpdateFlowDefinitionBodyFlowDefinition },\n { project_id: this.projectId },\n )) as UpdateFlowDefinition200;\n return { canonical: result.flow_definition as object };\n }\n\n async delete(id: string): Promise<void> {\n await this.client.deleteFlowDefinition(id, { project_id: this.projectId });\n }\n\n /**\n * `GET /flow_definitions/:id` returns a detail envelope with metadata\n * (`id`, `project_id`, `created_at`, `updated_at`) plus `flow_definition`.\n * Return only `flow_definition` so diffs compare with the on-disk bare body.\n */\n async fetch(id: string): Promise<object> {\n const envelope = (await this.client.getFlowDefinition(\n id,\n { project_id: this.projectId },\n )) as GetFlowDefinition200;\n\n return envelope.flow_definition as object;\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport type { ResourceEntry, ZitadelState } from \"./types.js\";\n\n/**\n * Read and parse `.zitadel/state.json`. Throws if the file is\n * missing or malformed; callers run `zitadel setup` first to bring\n * the file into existence.\n */\nexport async function readState(cwd: string): Promise<ZitadelState> {\n const raw = await readFile(join(cwd, \".zitadel/state.json\"), \"utf8\");\n return JSON.parse(raw) as ZitadelState;\n}\n\n/**\n * Merge an entry into the state file under `key`, preserving any\n * fields the caller did not override. Reads the file, writes it back\n * with sorted keys disabled (state is engine-managed, not human-\n * authored, so deterministic ordering isn't required here).\n */\nexport async function updateState(\n cwd: string,\n key: string,\n entry: ResourceEntry,\n): Promise<void> {\n const current = await readState(cwd);\n const updated: ZitadelState = {\n ...current,\n resources: {\n ...current.resources,\n [key]: { ...current.resources[key], ...entry },\n },\n };\n await writeFile(join(cwd, \".zitadel/state.json\"), JSON.stringify(updated, null, 2));\n}\n\n/**\n * Remove an entry from the state file. No-op if the key is absent.\n */\nexport async function removeFromState(cwd: string, key: string): Promise<void> {\n const current = await readState(cwd);\n const { [key]: _removed, ...rest } = current.resources;\n const updated: ZitadelState = { ...current, resources: rest };\n await writeFile(join(cwd, \".zitadel/state.json\"), JSON.stringify(updated, null, 2));\n}\n","import { createHash } from \"node:crypto\";\nimport { readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { consola } from \"consola\";\n\nimport { FLOWS_DIR } from \"../flows\";\nimport { stableStringify } from \"../json\";\nimport { ZitadelError } from \"../errors\";\nimport { readState, removeFromState, updateState } from \"./state.js\";\nimport type { ResourceEntry, ResourceSyncer, SyncAction } from \"./types.js\";\n\n/**\n * Compute the sync plan for `cwd` against the state file and (when\n * `fetchOld` is true) the platform API. The plan is read-only: it\n * decides what create/update/revise/delete operations need to happen but\n * performs none of them. Pass it to {@link runSyncLoop} to execute.\n *\n * Validates every on-disk file (via `syncer.validate`) before planning any\n * work — a single malformed schema or flow aborts the whole run with\n * `E_VALIDATION` before any platform mutation. Both `plan` and `apply`\n * reach this code path.\n *\n * Bearer auth + base URL live in the api package's runtime registries\n * (`runtime/{auth,base-url}`). Callers set them once at command boot;\n * the sync engine doesn't carry a client.\n *\n * @param cwd - Project root.\n * @param syncers - Per-resource adapters. Order is preserved in the output.\n * @param fetchOld - When true, the planner fetches each delete/update target\n * from the platform to populate `oldContent` for diff rendering.\n */\nexport async function buildSyncPlan(\n cwd: string,\n syncers: ReadonlyArray<ResourceSyncer>,\n fetchOld = false,\n): Promise<ReadonlyArray<SyncAction>> {\n const state = await readState(cwd);\n const actions: SyncAction[] = [];\n\n // Walk local flow files once, up front: revisioned schemas need to name every\n // flow whose `user_schema` currently pins the previous revision, so the\n // executor can re-pin those flows after the new revision is published.\n const localFlows = await readLocalFlowUserSchemas(cwd);\n\n // Revisions this plan will publish, keyed by the superseded id. Schema\n // syncers run before the flow syncer (makeSyncers order), so every pending\n // revise is known by the time flow files are planned.\n const pendingRevisions = new Map<string, { schemaPath: string }>();\n\n // Revisions an interrupted earlier run already published (state advanced,\n // `previousId` recorded) whose flows were never rewritten: superseded id →\n // the concrete new id. Lets a rerun finish the re-pin with no new revise.\n const recoveredRevisions = new Map<string, { schemaPath: string; newId: string }>();\n for (const [schemaPath, entry] of Object.entries(state.resources)) {\n if (entry.previousId && entry.id && entry.previousId !== entry.id) {\n recoveredRevisions.set(entry.previousId, { schemaPath, newId: entry.id });\n }\n }\n\n // Every scanned file body, keyed by project-relative path. Schema syncers\n // run before the flow syncer, so by the time a re-pin is planned the new\n // schema revision's content is available for the pre-flight field check.\n const scannedContents = new Map<string, object>();\n\n for (const syncer of syncers) {\n const dirPath = join(cwd, syncer.directory);\n consola.debug(`scanning ${syncer.directory}`);\n const onDisk = await readJsonDir(dirPath);\n\n for (const content of onDisk.values()) {\n syncer.validate(content);\n }\n\n for (const [filePath, entry] of Object.entries(state.resources)) {\n if (!filePath.startsWith(syncer.directory)) {\n continue;\n }\n if (onDisk.has(join(cwd, filePath)) || !entry.id) {\n continue;\n }\n\n let oldContent: object | null = null;\n if (fetchOld && syncer.fetch) {\n try {\n oldContent = await syncer.fetch(entry.id);\n } catch (err) {\n consola.debug(`fetch ${syncer.kind} ${entry.id} failed:`, err);\n }\n }\n actions.push({ kind: \"delete\", path: filePath, syncer, id: entry.id, oldContent });\n }\n\n for (const [absPath, content] of onDisk.entries()) {\n const relPath = absPath.slice(cwd.length + 1);\n scannedContents.set(relPath, content);\n const entry = state.resources[relPath];\n const hash = hashForState(syncer, content);\n\n // A flow pinned to a schema revision superseded in this plan (or by an\n // interrupted earlier run) needs its `user_schema` rewritten — whether\n // the flow is untouched, edited, or brand new (a create in the same\n // run as the revise must not POST the stale id).\n const flowRef = localFlows.get(relPath);\n const pending = flowRef ? pendingRevisions.get(flowRef) : undefined;\n const recovered = flowRef ? recoveredRevisions.get(flowRef) : undefined;\n const repin = pending\n ? { previousId: flowRef as string, schemaPath: pending.schemaPath }\n : recovered\n ? {\n previousId: flowRef as string,\n schemaPath: recovered.schemaPath,\n newId: recovered.newId,\n }\n : undefined;\n if (repin) {\n assertRepinnedFlowFields(\n relPath,\n content,\n repin.schemaPath,\n scannedContents.get(repin.schemaPath),\n );\n }\n\n if (!entry?.id) {\n actions.push({\n kind: \"create\",\n path: relPath,\n syncer,\n content,\n hash,\n ...(repin ? { repin } : {}),\n });\n continue;\n }\n\n // State files written before normalized hashing hold legacy hashes\n // (order-sensitive, un-normalized). Accepting the legacy format —\n // both over the raw file and over its stably-sorted form, since\n // setup-era hashes were computed on sorted keys — keeps an untouched\n // or merely reordered file a skip; a spurious mismatch here would\n // publish a garbage schema revision. Writes always store the new\n // format, so state converges on the next real change.\n const unchanged =\n entry.hash === hash ||\n entry.hash === hashResourceContent(content) ||\n entry.hash === hashResourceContent(JSON.parse(stableStringify(content)) as object);\n if (unchanged && !(repin && syncer.mutable)) {\n actions.push({ kind: \"skip\", path: relPath, reason: \"no-change\" });\n continue;\n }\n\n if (syncer.revisioned) {\n const oldContent = await fetchOldIfAsked(syncer, entry.id, fetchOld);\n pendingRevisions.set(entry.id, { schemaPath: relPath });\n actions.push({\n kind: \"revise\",\n path: relPath,\n syncer,\n content,\n hash,\n previousId: entry.id,\n oldContent,\n affectedPaths: findFlowsPinnedTo(entry.id, localFlows),\n });\n continue;\n }\n\n if (!syncer.mutable) {\n actions.push({ kind: \"skip\", path: relPath, reason: \"immutable\" });\n continue;\n }\n\n const oldContent = await fetchOldIfAsked(syncer, entry.id, fetchOld);\n actions.push({\n kind: \"update\",\n path: relPath,\n syncer,\n id: entry.id,\n content,\n hash,\n oldContent,\n ...(repin ? { repin } : {}),\n });\n }\n }\n\n return actions;\n}\n\n/** Result of {@link runSyncLoop}: the local files the loop rewrote. */\nexport type SyncLoopResult = {\n /**\n * Project-relative paths of files updated from the server's canonical\n * responses (write-back). Surfaced in human and `--json` output so a\n * local rewrite is never silent.\n */\n filesUpdated: string[];\n};\n\n/**\n * Execute every action returned by {@link buildSyncPlan} against the\n * platform. Updates the local state file (`.zitadel/state.json`) as\n * each action completes so an interrupted run can resume. After each\n * mutation, the server's canonical body is written back to the local\n * file (when it differs in normalized form), so repo config matches\n * live state by construction and the next `plan` is empty.\n *\n * The platform target (base URL + bearer auth) lives in the api\n * package's runtime registries; callers set them before invoking this.\n *\n * @param cwd - Project root.\n * @param syncers - Per-resource adapters; same list passed to\n * `buildSyncPlan`.\n */\nexport async function runSyncLoop(\n cwd: string,\n syncers: ReadonlyArray<ResourceSyncer>,\n): Promise<SyncLoopResult> {\n const actions = await buildSyncPlan(cwd, syncers);\n const filesUpdated: string[] = [];\n // Revisions published by this run: superseded id → new id. Update actions\n // carrying a `repin` patch their `user_schema` from here.\n const repinned = new Map<string, string>();\n\n const writeBack = async (\n action: Extract<SyncAction, { kind: \"create\" | \"revise\" | \"update\" }>,\n canonical: object | undefined,\n fallbackHash: string,\n ): Promise<string> => {\n if (!canonical) {\n return fallbackHash;\n }\n const { hash, changed } = await writeBackResource(cwd, action.path, action.syncer, canonical);\n if (changed) {\n filesUpdated.push(action.path);\n consola.info(`Updated ${action.path} from the server's canonical response`);\n }\n return hash;\n };\n\n for (const action of actions) {\n switch (action.kind) {\n case \"create\": {\n let content = action.content;\n const newId = action.repin\n ? (repinned.get(action.repin.previousId) ?? action.repin.newId)\n : undefined;\n if (newId) {\n // A flow created in the same run as (or after an interrupted)\n // schema revise must adopt the new revision — POSTing the stale\n // pin would fail validation, and its canonical echo would revert\n // the re-pinned local file.\n content = { ...(content as Record<string, unknown>), user_schema: newId };\n }\n const { id, canonical } = await action.syncer.create(content);\n const fallbackHash = newId ? hashForState(action.syncer, content) : action.hash;\n const entry: ResourceEntry = { id, hash: await writeBack(action, canonical, fallbackHash) };\n await updateState(cwd, action.path, entry);\n consola.info(\n `Created a new ${action.syncer.kind} on Zitadel from ${action.path} (id ${id})`,\n );\n break;\n }\n case \"revise\": {\n const { id, canonical } = await action.syncer.create(action.content);\n // `previousId` lands in state before the flow files are rewritten:\n // if the process dies in between, the next plan recovers the re-pin\n // from state instead of publishing a duplicate revision.\n const entry: ResourceEntry = {\n id,\n hash: await writeBack(action, canonical, action.hash),\n previousId: action.previousId,\n };\n await updateState(cwd, action.path, entry);\n repinned.set(action.previousId, id);\n consola.info(\n `Published a new ${action.syncer.kind} revision on Zitadel from ${action.path} (id ${id})`,\n );\n for (const flowPath of action.affectedPaths) {\n if (await repinFlowFile(cwd, flowPath, action.previousId, id)) {\n filesUpdated.push(flowPath);\n consola.info(`Re-pinned user_schema in ${flowPath} to ${id}`);\n }\n }\n break;\n }\n case \"update\": {\n let content = action.content;\n const newId = action.repin\n ? (repinned.get(action.repin.previousId) ?? action.repin.newId)\n : undefined;\n if (newId && action.repin) {\n // The plan captured the flow before the revise rewrote its file;\n // patch the pin in memory so the wire request adopts the new\n // revision without re-reading disk. The file rewrite below is a\n // no-op when this run's revise already re-pinned it — it matters\n // for crash recovery, where no revise ran this time.\n content = { ...(content as Record<string, unknown>), user_schema: newId };\n if (await repinFlowFile(cwd, action.path, action.repin.previousId, newId)) {\n filesUpdated.push(action.path);\n consola.info(`Re-pinned user_schema in ${action.path} to ${newId}`);\n }\n }\n const { canonical } = await action.syncer.update(action.id, content);\n const fallbackHash = newId ? hashForState(action.syncer, content) : action.hash;\n await updateState(cwd, action.path, {\n hash: await writeBack(action, canonical, fallbackHash),\n });\n consola.info(`Updated the ${action.syncer.kind} on Zitadel from ${action.path}`);\n break;\n }\n case \"delete\": {\n await action.syncer.delete(action.id);\n await removeFromState(cwd, action.path);\n consola.info(\n `Deleted the ${action.syncer.kind} on Zitadel because ${action.path} was removed locally`,\n );\n break;\n }\n case \"skip\": {\n consola.debug(`Skipped ${action.path} (${action.reason})`);\n break;\n }\n }\n }\n\n // `previousId` exists to recover interrupted re-pins; once no local flow\n // pins the superseded revision, drop it — otherwise a developer who later\n // pins that old revision on purpose would get force-bumped by recovery.\n const remainingPins = new Set((await readLocalFlowUserSchemas(cwd)).values());\n const finalState = await readState(cwd);\n for (const [path, entry] of Object.entries(finalState.resources)) {\n if (entry.previousId && !remainingPins.has(entry.previousId)) {\n await updateState(cwd, path, { previousId: undefined });\n }\n }\n\n return { filesUpdated: [...new Set(filesUpdated)] };\n}\n\n/**\n * Rewrite a flow file's `user_schema` pin from `previousId` to `newId`,\n * lockfile-style. Prefers a targeted text replacement so the author's\n * formatting survives a one-string change; falls back to parse +\n * `stableStringify` when the raw text doesn't contain exactly one pin.\n * Returns false when the file doesn't pin `previousId` (already re-pinned\n * or hand-edited) — never throws for an unreadable file.\n */\nasync function repinFlowFile(\n cwd: string,\n relPath: string,\n previousId: string,\n newId: string,\n): Promise<boolean> {\n const absPath = join(cwd, relPath);\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf8\");\n } catch (err) {\n consola.debug(`read ${relPath} for re-pin failed:`, err);\n return false;\n }\n\n const pinPattern = new RegExp(\n `(\"user_schema\"\\\\s*:\\\\s*)${escapeRegExp(JSON.stringify(previousId))}`,\n \"g\",\n );\n if (raw.match(pinPattern)?.length === 1) {\n await writeFile(\n absPath,\n raw.replace(pinPattern, (_match, prefix: string) => `${prefix}${JSON.stringify(newId)}`),\n );\n return true;\n }\n\n try {\n const doc = JSON.parse(raw) as Record<string, unknown>;\n if (doc.user_schema !== previousId) {\n return false;\n }\n doc.user_schema = newId;\n await writeFile(absPath, `${stableStringify(doc)}\\n`);\n return true;\n } catch (err) {\n consola.debug(`re-pin ${relPath} failed:`, err);\n return false;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Reconcile a local file with the server's canonical body. What gets\n * written is the `normalizeWrite` form (strips pure transport noise like\n * the empty `audience` echo; for schemas the canonical body verbatim —\n * spelled-out x-* defaults must survive, or the next apply would publish\n * a revision without them). Equality is judged in the `normalize`\n * comparison form, so the file is rewritten only when it materially\n * differs from live state; hand-formatted files stay untouched otherwise.\n * Returns the state hash of the written form.\n */\nexport async function writeBackResource(\n cwd: string,\n relPath: string,\n syncer: Pick<ResourceSyncer, \"normalize\" | \"normalizeWrite\">,\n canonical: object,\n): Promise<{ hash: string; changed: boolean }> {\n const writeBody = syncer.normalizeWrite?.(canonical) ?? canonical;\n const compare = (body: object) => stableStringify(syncer.normalize?.(body) ?? body);\n const absPath = join(cwd, relPath);\n let changed = true;\n try {\n const onDisk = JSON.parse(await readFile(absPath, \"utf8\")) as object;\n changed = compare(writeBody) !== compare(onDisk);\n } catch (err) {\n consola.debug(`read ${relPath} for write-back failed:`, err);\n }\n if (changed) {\n await writeFile(absPath, `${stableStringify(writeBody)}\\n`);\n }\n return { hash: hashForState(syncer, writeBody), changed };\n}\n\nasync function fetchOldIfAsked(\n syncer: ResourceSyncer,\n id: string,\n fetchOld: boolean,\n): Promise<object | null> {\n if (!fetchOld || !syncer.fetch) {\n return null;\n }\n try {\n return await syncer.fetch(id);\n } catch (err) {\n consola.debug(`fetch ${syncer.kind} ${id} failed:`, err);\n return null;\n }\n}\n\nasync function readJsonDir(dirPath: string): Promise<Map<string, object>> {\n const result = new Map<string, object>();\n let entries: string[];\n try {\n entries = await readdir(dirPath);\n } catch (err) {\n if (typeof err === \"object\" && err !== null && \"code\" in err && err.code === \"ENOENT\") {\n return result;\n }\n throw err;\n }\n for (const entry of entries.filter((e) => e.endsWith(\".json\"))) {\n const filePath = join(dirPath, entry);\n const raw = await readFile(filePath, \"utf8\");\n result.set(filePath, JSON.parse(raw) as object);\n }\n return result;\n}\n\n/**\n * Walk `.zitadel/flows/*.json` once and return each flow's `user_schema` value\n * keyed by project-root-relative path. A flow file without a `user_schema` (or\n * with a non-string one) is skipped: it does not pin a schema revision, so it\n * cannot be affected by one.\n */\nasync function readLocalFlowUserSchemas(cwd: string): Promise<Map<string, string>> {\n const result = new Map<string, string>();\n const flows = await readJsonDir(join(cwd, FLOWS_DIR));\n for (const [absPath, content] of flows.entries()) {\n const relPath = absPath.slice(cwd.length + 1);\n if (\n typeof content === \"object\" &&\n content !== null &&\n \"user_schema\" in content &&\n typeof (content as { user_schema: unknown }).user_schema === \"string\"\n ) {\n result.set(relPath, (content as { user_schema: string }).user_schema);\n }\n }\n return result;\n}\n\n/**\n * Fail fast when a flow that is about to adopt a new schema revision\n * references properties the new revision no longer has (the server would\n * reject the flow update with `flow field: not a property in the user\n * schema`). Runs at plan time, before any platform mutation — otherwise\n * the revise would publish first and the run would die half-applied.\n * Only plain property fields are checked; reserved credential tokens\n * (`x-auth-methods#…`) resolve outside the schema.\n */\nfunction assertRepinnedFlowFields(\n flowPath: string,\n flowContent: object,\n schemaPath: string,\n schemaContent: object | undefined,\n): void {\n if (!schemaContent) {\n return;\n }\n const properties = (schemaContent as { properties?: unknown }).properties;\n if (typeof properties !== \"object\" || properties === null) {\n return;\n }\n const steps = (flowContent as { steps?: Array<{ name?: unknown; fields?: unknown }> }).steps;\n const missing: string[] = [];\n for (const step of Array.isArray(steps) ? steps : []) {\n const fields = Array.isArray(step.fields) ? step.fields : [];\n for (const field of fields) {\n if (typeof field !== \"string\" || field.includes(\"#\")) {\n continue;\n }\n if (!Object.prototype.hasOwnProperty.call(properties, field)) {\n missing.push(`step ${JSON.stringify(step.name ?? \"?\")}: ${JSON.stringify(field)}`);\n }\n }\n }\n if (missing.length > 0) {\n throw new ZitadelError(\n \"E_VALIDATION\",\n `${flowPath} cannot adopt the new revision of ${schemaPath}: ` +\n `flow fields missing from the edited schema — ${missing.join(\", \")}`,\n {\n hint:\n \"Update the flow's steps[].fields to match the edited schema \" +\n \"(or restore the removed/renamed properties), then re-run plan/apply.\",\n },\n );\n }\n}\n\nfunction findFlowsPinnedTo(\n previousId: string,\n localFlows: Map<string, string>,\n): ReadonlyArray<string> {\n const affected: string[] = [];\n for (const [relPath, ref] of localFlows.entries()) {\n if (ref === previousId) {\n affected.push(relPath);\n }\n }\n return affected;\n}\n\n/**\n * Legacy content hash: order-sensitive and normalization-blind. Kept only\n * so state entries written by older CLI versions still match; new hashes\n * come from {@link hashForState}.\n */\nexport function hashResourceContent(data: object): string {\n return createHash(\"sha256\").update(JSON.stringify(data)).digest(\"hex\");\n}\n\n/**\n * The content hash stored in `.zitadel/state.json`: key-order-insensitive\n * (via `stableStringify`) and computed on the syncer's normalized form, so\n * reordering keys or spelling out a meta-schema default does not read as an\n * edit.\n */\nexport function hashForState(\n syncer: Pick<ResourceSyncer, \"normalize\">,\n data: object,\n): string {\n const normalized = syncer.normalize?.(data) ?? data;\n return createHash(\"sha256\").update(stableStringify(normalized)).digest(\"hex\");\n}\n","import { stableStringify } from \"../json\";\nimport type { ResourceSyncer, SyncAction, SyncPlanSummary } from \"./types.js\";\n\n/**\n * Count the non-`skip` actions in a {@link buildSyncPlan} result. Pure; the\n * single source of truth for the plan counts shared by the `plan` /\n * `apply --dry-run` JSON payload and {@link renderPlan}'s summary line.\n */\nexport function summarizePlan(actions: ReadonlyArray<SyncAction>): SyncPlanSummary {\n const active = actions.filter((a) => a.kind !== \"skip\");\n return {\n creates: active.filter((a) => a.kind === \"create\").length,\n updates: active.filter((a) => a.kind === \"update\").length,\n revisions: active.filter((a) => a.kind === \"revise\").length,\n deletes: active.filter((a) => a.kind === \"delete\").length,\n total: active.length,\n };\n}\n\n/**\n * Render a {@link buildSyncPlan} result as a human-readable Terraform-style\n * plan. TTY-aware: colors and bold are emitted only when `tty` is true.\n * Returns the empty-state message when every action is `skip`.\n *\n * @param actions - The action list produced by `buildSyncPlan`. Read-only;\n * the function never mutates the input.\n * @param tty - True when stdout is a TTY; controls ANSI emission.\n */\nexport function renderPlan(actions: ReadonlyArray<SyncAction>, tty: boolean): string {\n const active = actions.filter((a) => a.kind !== \"skip\");\n\n if (active.length === 0) {\n return paint(\n \"No changes. Your Zitadel configuration matches the current state.\",\n A.bold,\n tty,\n );\n }\n\n const out: string[] = [];\n out.push(paint(\"Zitadel will perform the following actions:\", A.bold, tty));\n\n for (const action of active) {\n out.push(\"\");\n out.push(...renderBlock(action, tty));\n }\n\n out.push(\"\");\n\n const { creates, updates, revisions, deletes } = summarizePlan(actions);\n\n const parts: string[] = [];\n if (creates > 0) {\n parts.push(`${creates} to add`);\n }\n if (updates > 0) {\n parts.push(`${updates} to change`);\n }\n if (revisions > 0) {\n parts.push(`${revisions} new revision${revisions === 1 ? \"\" : \"s\"}`);\n }\n if (deletes > 0) {\n parts.push(`${deletes} to destroy`);\n }\n\n out.push(paint(`Plan: ${parts.join(\", \")}.`, A.bold, tty));\n return out.join(\"\\n\");\n}\n\nconst A = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n green: \"\\x1b[32m\",\n red: \"\\x1b[31m\",\n yellow: \"\\x1b[33m\",\n} as const;\n\nfunction paint(text: string, code: string, tty: boolean): string {\n return tty ? `${code}${text}${A.reset}` : text;\n}\n\nfunction isPrimitive(v: unknown): v is string | number | boolean | null {\n return v === null || typeof v === \"string\" || typeof v === \"number\" || typeof v === \"boolean\";\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nconst KNOWN_AFTER_APPLY = \"(known after apply)\";\n\nfunction escapeString(s: string): string {\n return s\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\");\n}\n\nfunction fmtPrimitive(v: string | number | boolean | null): string {\n if (v === null) {\n return \"null\";\n }\n if (typeof v === \"string\" && v === KNOWN_AFTER_APPLY) {\n return KNOWN_AFTER_APPLY;\n }\n if (typeof v === \"string\") {\n return `\"${escapeString(v)}\"`;\n }\n return String(v);\n}\n\n/**\n * Indentation contract (matches Terraform exactly):\n * prefixCol = column index of the +/-/~ character\n * field content starts at prefixCol + 2 (one space gap after prefix)\n * nested object/array content: prefixCol + 4 for the child prefixCol\n * closing } or ] : prefixCol + 2 columns of plain spaces, no prefix\n */\ntype ChangePrefix = \"+\" | \"-\" | \"~\" | \" \";\n\nfunction prefixAnsi(p: ChangePrefix): string {\n if (p === \"+\") {\n return A.green;\n }\n if (p === \"-\") {\n return A.red;\n }\n if (p === \"~\") {\n return A.yellow;\n }\n return \"\";\n}\n\ninterface RenderCtx {\n tty: boolean;\n deleteMode: boolean;\n}\n\nfunction renderFields(\n obj: Record<string, unknown>,\n prefix: ChangePrefix,\n prefixCol: number,\n ctx: RenderCtx,\n lines: string[],\n): void {\n const pad = \" \".repeat(prefixCol);\n const ansi = prefixAnsi(prefix);\n const col = (s: string) => paint(s, ansi, ctx.tty);\n\n const keys = Object.keys(obj).sort();\n const maxLen = keys.reduce((m, k) => Math.max(m, k.length), 0);\n\n for (const key of keys) {\n const val = obj[key];\n const pk = key.padEnd(maxLen);\n\n if (isPrimitive(val)) {\n const formatted = fmtPrimitive(val);\n const suffix = ctx.deleteMode ? \" -> null\" : \"\";\n lines.push(col(`${pad}${prefix} ${pk} = ${formatted}${suffix}`));\n } else if (Array.isArray(val)) {\n if (val.length === 0) {\n lines.push(col(`${pad}${prefix} ${pk} = []`));\n } else {\n lines.push(col(`${pad}${prefix} ${pk} = [`));\n renderArrayItems(val, prefix, prefixCol + 4, ctx, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}]`));\n }\n } else if (isPlainObject(val)) {\n if (Object.keys(val).length === 0) {\n lines.push(col(`${pad}${prefix} ${pk} = {}`));\n } else {\n lines.push(col(`${pad}${prefix} ${pk} = {`));\n renderFields(val, prefix, prefixCol + 4, ctx, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}}`));\n }\n }\n }\n}\n\n/**\n * Renders the items of an array. Unlike {@link renderFields}, primitive\n * elements never get a trailing ` -> null` suffix even under `deleteMode` —\n * Terraform only annotates scalar object-field removals that way, not array\n * items.\n */\nfunction renderArrayItems(\n arr: ReadonlyArray<unknown>,\n prefix: ChangePrefix,\n prefixCol: number,\n ctx: RenderCtx,\n lines: string[],\n): void {\n const pad = \" \".repeat(prefixCol);\n const ansi = prefixAnsi(prefix);\n const col = (s: string) => paint(s, ansi, ctx.tty);\n\n for (const item of arr) {\n if (isPrimitive(item)) {\n const formatted = fmtPrimitive(item);\n lines.push(col(`${pad}${prefix} ${formatted},`));\n } else if (Array.isArray(item)) {\n if (item.length === 0) {\n lines.push(col(`${pad}${prefix} [],`));\n } else {\n lines.push(col(`${pad}${prefix} [`));\n renderArrayItems(item, prefix, prefixCol + 4, ctx, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}],`));\n }\n } else if (isPlainObject(item)) {\n if (Object.keys(item).length === 0) {\n lines.push(col(`${pad}${prefix} {},`));\n } else {\n lines.push(col(`${pad}${prefix} {`));\n renderFields(item, prefix, prefixCol + 4, ctx, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}},`));\n }\n }\n }\n}\n\n/**\n * Walks both old and new objects, emitting Terraform-style change lines.\n * Returns true if any actual change line (+ / - / ~) was emitted.\n *\n * Edge cases:\n * - Changed arrays render as a full remove + full add (no LCS diff).\n * - Nested objects recurse, and the outer key is only marked `~` if a child\n * actually changed; unchanged children render with the neutral prefix.\n * - A value whose type changed (e.g. string → object) also renders as a\n * remove + add pair.\n */\nfunction renderDiff(\n oldObj: Record<string, unknown>,\n newObj: Record<string, unknown>,\n prefixCol: number,\n tty: boolean,\n lines: string[],\n): boolean {\n const allKeys = [...new Set([...Object.keys(oldObj), ...Object.keys(newObj)])].sort();\n const maxLen = allKeys.reduce((m, k) => Math.max(m, k.length), 0);\n const pad = \" \".repeat(prefixCol);\n let hasChanges = false;\n\n for (const key of allKeys) {\n const pk = key.padEnd(maxLen);\n const hasOld = Object.prototype.hasOwnProperty.call(oldObj, key);\n const hasNew = Object.prototype.hasOwnProperty.call(newObj, key);\n const oldVal = oldObj[key];\n const newVal = newObj[key];\n\n if (!hasOld) {\n hasChanges = true;\n const col = (s: string) => paint(s, A.green, tty);\n if (isPrimitive(newVal)) {\n lines.push(col(`${pad}+ ${pk} = ${fmtPrimitive(newVal)}`));\n } else if (Array.isArray(newVal)) {\n lines.push(col(`${pad}+ ${pk} = [`));\n renderArrayItems(newVal, \"+\", prefixCol + 4, { tty, deleteMode: false }, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}]`));\n } else if (isPlainObject(newVal)) {\n lines.push(col(`${pad}+ ${pk} = {`));\n renderFields(newVal, \"+\", prefixCol + 4, { tty, deleteMode: false }, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}}`));\n }\n } else if (!hasNew) {\n hasChanges = true;\n const col = (s: string) => paint(s, A.red, tty);\n if (isPrimitive(oldVal)) {\n lines.push(col(`${pad}- ${pk} = ${fmtPrimitive(oldVal)} -> null`));\n } else if (Array.isArray(oldVal)) {\n lines.push(col(`${pad}- ${pk} = [`));\n renderArrayItems(oldVal, \"-\", prefixCol + 4, { tty, deleteMode: false }, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}]`));\n } else if (isPlainObject(oldVal)) {\n lines.push(col(`${pad}- ${pk} = {`));\n renderFields(oldVal, \"-\", prefixCol + 4, { tty, deleteMode: true }, lines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}}`));\n }\n } else if (isPrimitive(oldVal) && isPrimitive(newVal)) {\n if (oldVal === newVal) {\n lines.push(`${pad} ${pk} = ${fmtPrimitive(newVal)}`);\n } else {\n hasChanges = true;\n const col = (s: string) => paint(s, A.yellow, tty);\n lines.push(col(`${pad}~ ${pk} = ${fmtPrimitive(oldVal)} -> ${fmtPrimitive(newVal)}`));\n }\n } else if (Array.isArray(oldVal) && Array.isArray(newVal)) {\n // Key-order-insensitive equality: the server echoes objects in its own\n // field order while local files are stably sorted — that difference is\n // not a change.\n if (stableStringify(oldVal) === stableStringify(newVal)) {\n if (newVal.length === 0) {\n lines.push(`${pad} ${pk} = []`);\n } else {\n lines.push(`${pad} ${pk} = [`);\n renderArrayItems(newVal, \" \", prefixCol + 4, { tty, deleteMode: false }, lines);\n lines.push(`${\" \".repeat(prefixCol + 2)}]`);\n }\n } else {\n hasChanges = true;\n const colR = (s: string) => paint(s, A.red, tty);\n const colA = (s: string) => paint(s, A.green, tty);\n if (oldVal.length === 0) {\n lines.push(colR(`${pad}- ${pk} = []`));\n } else {\n lines.push(colR(`${pad}- ${pk} = [`));\n renderArrayItems(oldVal, \"-\", prefixCol + 4, { tty, deleteMode: false }, lines);\n lines.push(colR(`${\" \".repeat(prefixCol + 2)}]`));\n }\n if (newVal.length === 0) {\n lines.push(colA(`${pad}+ ${pk} = []`));\n } else {\n lines.push(colA(`${pad}+ ${pk} = [`));\n renderArrayItems(newVal, \"+\", prefixCol + 4, { tty, deleteMode: false }, lines);\n lines.push(colA(`${\" \".repeat(prefixCol + 2)}]`));\n }\n }\n } else if (isPlainObject(oldVal) && isPlainObject(newVal)) {\n const childLines: string[] = [];\n const childHasChanges = renderDiff(oldVal, newVal, prefixCol + 4, tty, childLines);\n if (childHasChanges) {\n hasChanges = true;\n const col = (s: string) => paint(s, A.yellow, tty);\n lines.push(col(`${pad}~ ${pk} = {`));\n lines.push(...childLines);\n lines.push(col(`${\" \".repeat(prefixCol + 2)}}`));\n } else if (childLines.length > 0) {\n lines.push(`${pad} ${pk} = {`);\n lines.push(...childLines);\n lines.push(`${\" \".repeat(prefixCol + 2)}}`);\n } else {\n lines.push(`${pad} ${pk} = {}`);\n }\n } else {\n hasChanges = true;\n const colR = (s: string) => paint(s, A.red, tty);\n const colA = (s: string) => paint(s, A.green, tty);\n if (isPrimitive(oldVal)) {\n lines.push(colR(`${pad}- ${pk} = ${fmtPrimitive(oldVal)} -> null`));\n }\n if (isPrimitive(newVal)) {\n lines.push(colA(`${pad}+ ${pk} = ${fmtPrimitive(newVal)}`));\n }\n }\n }\n\n return hasChanges;\n}\n\n/**\n * Column layout (matches Terraform's per-block format):\n * BLOCK_COL = 2 — where the +/-/~ sits on the resource opening line\n * FIELD_COL = 6 — where the +/-/~ sits on first-level field lines\n * closing } — at BLOCK_COL + 2 = 4, no prefix\n */\nconst BLOCK_COL = 2;\nconst FIELD_COL = 6;\n\nfunction resourceName(path: string): string {\n return path.split(\"/\").pop() ?? path;\n}\n\n/**\n * Diff both sides in the syncer's canonical form so server-echoed noise\n * (empty `audience`, spelled-out meta-schema defaults) never renders as a\n * change the author didn't make. Rendering only — upload payloads stay raw.\n */\nfunction normalized(\n syncer: Pick<ResourceSyncer, \"normalize\">,\n content: object,\n): Record<string, unknown> {\n return (syncer.normalize?.(content) ?? content) as Record<string, unknown>;\n}\n\n/**\n * Renders one Terraform-style resource block for a single `SyncAction`.\n *\n * Per-case notes:\n * - **create**: a synthetic `id = (known after apply)` is injected into the\n * rendered fields so it sorts alphabetically alongside the real keys.\n * - **delete**: when `oldContent` is null (the fetch failed), the body\n * collapses to a single `- id = \"<id>\" -> null` line.\n * - **update**: when `oldContent` is null (no read endpoint for this\n * resource kind), the field diff is replaced with a placeholder\n * \"field diff unavailable\" line.\n * - **skip**: omitted from the output entirely, matching Terraform's\n * default of not showing no-change resources.\n */\nfunction renderBlock(action: SyncAction, tty: boolean): string[] {\n const lines: string[] = [];\n const blkPad = \" \".repeat(BLOCK_COL);\n const closePad = \" \".repeat(BLOCK_COL + 2);\n\n switch (action.kind) {\n case \"create\": {\n const header = `${blkPad}# ${action.path} will be created`;\n const opening = `${blkPad}+ resource \"${action.syncer.kind}\" \"${resourceName(action.path)}\" {`;\n lines.push(paint(header, A.bold, tty));\n lines.push(paint(opening, A.green, tty));\n\n const display: Record<string, unknown> = {\n id: KNOWN_AFTER_APPLY,\n ...(action.content as Record<string, unknown>),\n };\n if (action.repin) {\n // The executor POSTs this flow with the new revision id, not the\n // stale pin still in the file — render what will actually be sent.\n display.user_schema = action.repin.newId ?? KNOWN_AFTER_APPLY;\n }\n renderFields(display, \"+\", FIELD_COL, { tty, deleteMode: false }, lines);\n lines.push(`${closePad}}`);\n break;\n }\n\n case \"delete\": {\n const header = `${blkPad}# ${action.path} will be destroyed`;\n const opening = `${blkPad}- resource \"${action.syncer.kind}\" \"${resourceName(action.path)}\" {`;\n lines.push(paint(header, A.bold, tty));\n lines.push(paint(opening, A.red, tty));\n\n if (action.oldContent) {\n const display: Record<string, unknown> = {\n id: action.id,\n ...(action.oldContent as Record<string, unknown>),\n };\n renderFields(display, \"-\", FIELD_COL, { tty, deleteMode: true }, lines);\n } else {\n lines.push(paint(`${\" \".repeat(FIELD_COL)}- id = \"${action.id}\" -> null`, A.red, tty));\n }\n lines.push(`${closePad}}`);\n break;\n }\n\n case \"update\": {\n const headerSuffix = action.repin ? \" (re-pin user_schema)\" : \"\";\n const header = `${blkPad}# ${action.path} will be updated in-place${headerSuffix}`;\n const opening = `${blkPad}~ resource \"${action.syncer.kind}\" \"${resourceName(action.path)}\" {`;\n lines.push(paint(header, A.bold, tty));\n lines.push(paint(opening, A.yellow, tty));\n\n // A repin update ships with `user_schema` rewritten to the revision id\n // the revise mints (or already minted, for crash recovery) — render the\n // content the executor will actually PUT.\n const newContent = action.repin\n ? {\n ...normalized(action.syncer, action.content),\n user_schema: action.repin.newId ?? KNOWN_AFTER_APPLY,\n }\n : normalized(action.syncer, action.content);\n\n if (action.oldContent) {\n renderDiff(normalized(action.syncer, action.oldContent), newContent, FIELD_COL, tty, lines);\n } else if (action.repin) {\n lines.push(\n paint(\n `${\" \".repeat(FIELD_COL)}~ user_schema = \"${action.repin.previousId}\" -> ${action.repin.newId ? `\"${action.repin.newId}\"` : KNOWN_AFTER_APPLY}`,\n A.yellow,\n tty,\n ),\n );\n } else {\n lines.push(\n `${\" \".repeat(FIELD_COL)} # (field diff unavailable — no read endpoint for ${action.syncer.kind})`,\n );\n }\n lines.push(`${closePad}}`);\n break;\n }\n\n case \"revise\": {\n const header = `${blkPad}# ${action.path} will publish a new revision`;\n const opening = `${blkPad}~ resource \"${action.syncer.kind}\" \"${resourceName(action.path)}\" {`;\n lines.push(paint(header, A.bold, tty));\n lines.push(paint(opening, A.yellow, tty));\n\n if (action.oldContent) {\n const oldWithId: Record<string, unknown> = {\n id: action.previousId,\n ...normalized(action.syncer, action.oldContent),\n };\n const newWithId: Record<string, unknown> = {\n id: KNOWN_AFTER_APPLY,\n ...normalized(action.syncer, action.content),\n };\n renderDiff(oldWithId, newWithId, FIELD_COL, tty, lines);\n } else {\n lines.push(\n `${\" \".repeat(FIELD_COL)} # (field diff unavailable — no read endpoint for ${action.syncer.kind})`,\n );\n }\n lines.push(`${closePad}}`);\n if (action.affectedPaths.length > 0) {\n lines.push(\n paint(\n `${blkPad}# user_schema will be re-pinned to the new revision ${KNOWN_AFTER_APPLY} in:`,\n A.yellow,\n tty,\n ),\n );\n for (const path of action.affectedPaths) {\n lines.push(paint(`${blkPad}# - ${path}`, A.yellow, tty));\n }\n }\n break;\n }\n\n case \"skip\":\n break;\n }\n\n return lines;\n}\n"],"mappings":"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"}
@@ -1077,5 +1077,5 @@
1077
1077
  ]
1078
1078
  }
1079
1079
  },
1080
- "version": "0.1.0-alpha.14"
1080
+ "version": "0.1.0-alpha.15"
1081
1081
  }