@zeroxsolutions/zaku 0.0.3 → 0.1.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.
Files changed (3) hide show
  1. package/dist/rules.js +1 -1
  2. package/dist/zaku.mjs +1266 -209
  3. package/package.json +3 -3
package/dist/zaku.mjs CHANGED
@@ -12066,7 +12066,7 @@ var require_websocket = __commonJS({
12066
12066
  var http = __require("http");
12067
12067
  var net = __require("net");
12068
12068
  var tls = __require("tls");
12069
- var { randomBytes, createHash: createHash2 } = __require("crypto");
12069
+ var { randomBytes, createHash: createHash3 } = __require("crypto");
12070
12070
  var { Duplex, Readable } = __require("stream");
12071
12071
  var { URL: URL2 } = __require("url");
12072
12072
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -12747,7 +12747,7 @@ var require_websocket = __commonJS({
12747
12747
  abortHandshake(websocket, socket, "Invalid Upgrade header");
12748
12748
  return;
12749
12749
  }
12750
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
12750
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
12751
12751
  if (res.headers["sec-websocket-accept"] !== digest) {
12752
12752
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
12753
12753
  return;
@@ -13116,7 +13116,7 @@ var require_websocket_server = __commonJS({
13116
13116
  var EventEmitter2 = __require("events");
13117
13117
  var http = __require("http");
13118
13118
  var { Duplex } = __require("stream");
13119
- var { createHash: createHash2 } = __require("crypto");
13119
+ var { createHash: createHash3 } = __require("crypto");
13120
13120
  var extension2 = require_extension();
13121
13121
  var PerMessageDeflate2 = require_permessage_deflate();
13122
13122
  var subprotocol2 = require_subprotocol();
@@ -13423,7 +13423,7 @@ var require_websocket_server = __commonJS({
13423
13423
  );
13424
13424
  }
13425
13425
  if (this._state > RUNNING) return abortHandshake(socket, 503);
13426
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
13426
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
13427
13427
  const headers = [
13428
13428
  "HTTP/1.1 101 Switching Protocols",
13429
13429
  "Upgrade: websocket",
@@ -17351,49 +17351,49 @@ var require_fast_uri = __commonJS({
17351
17351
  schemelessOptions.skipEscape = true;
17352
17352
  return serialize(resolved, schemelessOptions);
17353
17353
  }
17354
- function resolveComponent(base, relative2, options, skipNormalization) {
17354
+ function resolveComponent(base, relative3, options, skipNormalization) {
17355
17355
  const target = {};
17356
17356
  if (!skipNormalization) {
17357
17357
  base = parse4(serialize(base, options), options);
17358
- relative2 = parse4(serialize(relative2, options), options);
17358
+ relative3 = parse4(serialize(relative3, options), options);
17359
17359
  }
17360
17360
  options = options || {};
17361
- if (!options.tolerant && relative2.scheme) {
17362
- target.scheme = relative2.scheme;
17363
- target.userinfo = relative2.userinfo;
17364
- target.host = relative2.host;
17365
- target.port = relative2.port;
17366
- target.path = removeDotSegments(relative2.path || "");
17367
- target.query = relative2.query;
17361
+ if (!options.tolerant && relative3.scheme) {
17362
+ target.scheme = relative3.scheme;
17363
+ target.userinfo = relative3.userinfo;
17364
+ target.host = relative3.host;
17365
+ target.port = relative3.port;
17366
+ target.path = removeDotSegments(relative3.path || "");
17367
+ target.query = relative3.query;
17368
17368
  } else {
17369
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
17370
- target.userinfo = relative2.userinfo;
17371
- target.host = relative2.host;
17372
- target.port = relative2.port;
17373
- target.path = removeDotSegments(relative2.path || "");
17374
- target.query = relative2.query;
17369
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
17370
+ target.userinfo = relative3.userinfo;
17371
+ target.host = relative3.host;
17372
+ target.port = relative3.port;
17373
+ target.path = removeDotSegments(relative3.path || "");
17374
+ target.query = relative3.query;
17375
17375
  } else {
17376
- if (!relative2.path) {
17376
+ if (!relative3.path) {
17377
17377
  target.path = base.path;
17378
- if (relative2.query !== void 0) {
17379
- target.query = relative2.query;
17378
+ if (relative3.query !== void 0) {
17379
+ target.query = relative3.query;
17380
17380
  } else {
17381
17381
  target.query = base.query;
17382
17382
  }
17383
17383
  } else {
17384
- if (relative2.path[0] === "/") {
17385
- target.path = removeDotSegments(relative2.path);
17384
+ if (relative3.path[0] === "/") {
17385
+ target.path = removeDotSegments(relative3.path);
17386
17386
  } else {
17387
17387
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
17388
- target.path = "/" + relative2.path;
17388
+ target.path = "/" + relative3.path;
17389
17389
  } else if (!base.path) {
17390
- target.path = relative2.path;
17390
+ target.path = relative3.path;
17391
17391
  } else {
17392
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
17392
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
17393
17393
  }
17394
17394
  target.path = removeDotSegments(target.path);
17395
17395
  }
17396
- target.query = relative2.query;
17396
+ target.query = relative3.query;
17397
17397
  }
17398
17398
  target.userinfo = base.userinfo;
17399
17399
  target.host = base.host;
@@ -17401,7 +17401,7 @@ var require_fast_uri = __commonJS({
17401
17401
  }
17402
17402
  target.scheme = base.scheme;
17403
17403
  }
17404
- target.fragment = relative2.fragment;
17404
+ target.fragment = relative3.fragment;
17405
17405
  return target;
17406
17406
  }
17407
17407
  function equal(uriA, uriB, options) {
@@ -20681,7 +20681,7 @@ var import_reflect_metadata3 = __toESM(require_Reflect(), 1);
20681
20681
  import { readFile as readFile6 } from "node:fs/promises";
20682
20682
  import { createRequire } from "node:module";
20683
20683
  import { homedir as homedir2 } from "node:os";
20684
- import { dirname as dirname4, join as join11, resolve as resolve2, sep } from "node:path";
20684
+ import { dirname as dirname4, join as join12, resolve as resolve2, sep } from "node:path";
20685
20685
  import { fileURLToPath } from "node:url";
20686
20686
 
20687
20687
  // node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
@@ -24178,11 +24178,15 @@ var WriteRecipe = class extends DomainCommand {
24178
24178
  // packages/zaku/dist/lib/commands/save-library.js
24179
24179
  var SaveLibrary = class extends DomainCommand {
24180
24180
  root;
24181
+ key;
24181
24182
  part;
24182
24183
  constructor(input2) {
24183
24184
  super();
24184
24185
  this.root = input2.root;
24185
- this.part = input2.part;
24186
+ if (input2.key !== void 0)
24187
+ this.key = input2.key;
24188
+ if (input2.part !== void 0)
24189
+ this.part = input2.part;
24186
24190
  }
24187
24191
  };
24188
24192
 
@@ -44393,6 +44397,18 @@ var FigmaTokenMissing = class extends Error {
44393
44397
  }
44394
44398
  };
44395
44399
 
44400
+ // packages/zaku/dist/lib/domain/errors/library-key-missing.js
44401
+ var LibraryKeyMissing = class extends Error {
44402
+ keys;
44403
+ given;
44404
+ constructor(keys, given) {
44405
+ super(given === void 0 ? `zaku.yaml lists several libraries and kits; name one with --key: ${keys.join(", ")}` : `${given} is not a library or kit zaku.yaml lists; name one with --key: ${keys.join(", ")}`);
44406
+ this.keys = keys;
44407
+ this.given = given;
44408
+ this.name = "LibraryKeyMissing";
44409
+ }
44410
+ };
44411
+
44396
44412
  // packages/zaku/dist/lib/domain/errors/playwright-missing.js
44397
44413
  var PlaywrightMissing = class extends Error {
44398
44414
  constructor() {
@@ -44611,12 +44627,99 @@ WriteRecipeHandler = __decorate2([
44611
44627
 
44612
44628
  // packages/zaku/dist/lib/handlers/save-library.js
44613
44629
  var import_tsyringe3 = __toESM(require_cjs(), 1);
44630
+ import { relative as relative2 } from "node:path";
44614
44631
 
44615
44632
  // packages/zaku/dist/lib/domain/copy-defaults.js
44616
44633
  var DEFAULT_COPY = { locales: ["en"], currencies: [] };
44617
44634
 
44635
+ // packages/zaku/dist/lib/domain/design-graph.js
44636
+ var DESKTOP_FAMILIES = /* @__PURE__ */ new Set(["macos", "windows", "gnome", "kde"]);
44637
+ function defaultBranch(family) {
44638
+ if (family === "web")
44639
+ return "ui";
44640
+ return DESKTOP_FAMILIES.has(family) ? void 0 : "native";
44641
+ }
44642
+ var branchOf = (target) => target.branch ?? defaultBranch(target.family);
44643
+ var unique = (values) => [...new Set(values)];
44644
+ function legacyFiles(config2) {
44645
+ const branches = unique(config2.targets.flatMap((target) => branchOf(target) ?? []));
44646
+ const families2 = unique(config2.targets.map((target) => target.family));
44647
+ const { library, product } = config2.figma;
44648
+ return {
44649
+ libraries: library === void 0 ? [] : [{ key: library, layer: "foundations", branches: branches.length > 0 ? branches : ["ui"], extends: [] }],
44650
+ products: product === void 0 ? [] : families2.map((family) => ({ key: product, family }))
44651
+ };
44652
+ }
44653
+ function designGraph(config2) {
44654
+ const { figma } = config2;
44655
+ const legacy = figma.library !== void 0 || figma.product !== void 0;
44656
+ const { libraries, products } = legacy ? legacyFiles(config2) : { libraries: figma.libraries ?? [], products: figma.products ?? [] };
44657
+ const kits = figma.kits ?? [];
44658
+ const prototypes = figma.prototypes ?? [];
44659
+ const byKey = new Map(libraries.map((library) => [library.key, library]));
44660
+ const closure = (key) => {
44661
+ const out = [];
44662
+ const visit2 = (next) => {
44663
+ if (out.includes(next))
44664
+ return;
44665
+ out.push(next);
44666
+ for (const base of byKey.get(next)?.extends ?? [])
44667
+ visit2(base);
44668
+ };
44669
+ visit2(key);
44670
+ return out;
44671
+ };
44672
+ return {
44673
+ libraries,
44674
+ kits,
44675
+ products,
44676
+ prototypes,
44677
+ legacy,
44678
+ branchOf,
44679
+ targetsOf: (family) => config2.targets.filter((target) => target.family === family),
44680
+ librariesFor: (target) => {
44681
+ if (legacy)
44682
+ return libraries;
44683
+ const branch = branchOf(target);
44684
+ return libraries.filter((library) => branch !== void 0 && library.branches.includes(branch));
44685
+ },
44686
+ kitsFor: (target) => kits.filter((kit) => kit.family === target.family),
44687
+ closure,
44688
+ multiBranch: (key) => (byKey.get(key)?.branches.length ?? 0) > 1,
44689
+ productOf: (prototypeKey) => {
44690
+ const prototype2 = prototypes.find((candidate) => candidate.key === prototypeKey);
44691
+ return products.find((product) => product.family === prototype2?.family)?.key;
44692
+ }
44693
+ };
44694
+ }
44695
+ function snapshotsFor(input2, target) {
44696
+ const graph = designGraph(input2.config);
44697
+ const branch = target ? graph.branchOf(target) : void 0;
44698
+ const missing = [];
44699
+ const libraries = (target ? graph.librariesFor(target) : graph.libraries).flatMap((library) => {
44700
+ const snapshot = input2.libraries[library.key];
44701
+ if (!snapshot) {
44702
+ missing.push(library.key);
44703
+ return [];
44704
+ }
44705
+ if (!target || !graph.multiBranch(library.key))
44706
+ return [snapshot];
44707
+ const components = snapshot.components.filter((component2) => component2.branch === void 0 || component2.branch === branch);
44708
+ return [{ ...snapshot, components }];
44709
+ });
44710
+ const kits = (target ? graph.kitsFor(target) : graph.kits).flatMap((kit) => {
44711
+ const snapshot = input2.kits[kit.key];
44712
+ if (!snapshot)
44713
+ missing.push(kit.key);
44714
+ return snapshot ? [snapshot] : [];
44715
+ });
44716
+ return { libraries, kits, missing };
44717
+ }
44718
+
44618
44719
  // packages/zaku/dist/lib/schema/zaku-config.js
44619
44720
  var PLATFORM_FAMILIES = ["web", "ios", "android", "macos", "windows", "gnome", "kde"];
44721
+ var LAYERS = ["foundations", "components"];
44722
+ var BRANCHES = ["ui", "native"];
44620
44723
  var DEFAULT_INTERACTIVE = [
44621
44724
  "Button",
44622
44725
  "Button Group",
@@ -44643,7 +44746,9 @@ var targetSchema = external_exports.object({
44643
44746
  family: external_exports.enum(PLATFORM_FAMILIES),
44644
44747
  name: external_exports.string().regex(/^\S(.*\S)?$/, "a name has no leading or trailing space").refine((name) => !name.includes("/"), "a name has no slash, which separates the parts of a frame name"),
44645
44748
  /** The frame size every frame of this target is drawn at; the frame check compares it. */
44646
- size: external_exports.object({ width: external_exports.number().positive(), height: external_exports.number().positive() }).strict().optional()
44749
+ size: external_exports.object({ width: external_exports.number().positive(), height: external_exports.number().positive() }).strict().optional(),
44750
+ /** The renderer whose libraries its frames draw from; `defaultBranch` in the design graph gives it when left out. */
44751
+ branch: external_exports.enum(BRANCHES).optional()
44647
44752
  }).strict();
44648
44753
  var DEFAULT_SYSTEM_BARS = {
44649
44754
  web: [],
@@ -44663,7 +44768,28 @@ var budgetSchema = external_exports.object({
44663
44768
  mcpPerRun: external_exports.number().int().positive().default(30),
44664
44769
  reserve: external_exports.number().min(0).max(1).default(0.2)
44665
44770
  }).strict();
44666
- var files = external_exports.object({ library: external_exports.string().min(1).optional(), product: external_exports.string().min(1).optional() }).strict();
44771
+ var fileKey = external_exports.string().min(1);
44772
+ var kitSchema = external_exports.object({ key: fileKey, family: external_exports.enum(PLATFORM_FAMILIES) }).strict();
44773
+ var librarySchema = external_exports.object({
44774
+ key: fileKey,
44775
+ layer: external_exports.enum(LAYERS),
44776
+ branches: external_exports.array(external_exports.enum(BRANCHES)).min(1),
44777
+ /** The libraries and kits this one is built on, by key. */
44778
+ extends: external_exports.array(fileKey).default([])
44779
+ }).strict();
44780
+ var productSchema = external_exports.object({ key: fileKey, family: external_exports.enum(PLATFORM_FAMILIES) }).strict();
44781
+ var prototypeSchema = external_exports.object({ key: fileKey, family: external_exports.enum(PLATFORM_FAMILIES) }).strict();
44782
+ var files = external_exports.object({
44783
+ library: fileKey.optional(),
44784
+ product: fileKey.optional(),
44785
+ kits: external_exports.array(kitSchema).optional(),
44786
+ libraries: external_exports.array(librarySchema).optional(),
44787
+ products: external_exports.array(productSchema).optional(),
44788
+ prototypes: external_exports.array(prototypeSchema).optional()
44789
+ }).strict();
44790
+ function byRole(figma) {
44791
+ return [figma.kits, figma.libraries, figma.products, figma.prototypes].some((list) => list !== void 0);
44792
+ }
44667
44793
  var shadcnSystem = external_exports.object({ preset: external_exports.string().min(1) }).strict();
44668
44794
  var tokenFiles = external_exports.array(external_exports.string().min(1)).min(1);
44669
44795
  var resolverInputs = external_exports.record(external_exports.string(), external_exports.string());
@@ -44715,7 +44841,7 @@ var copySchema = external_exports.object({
44715
44841
  locales: external_exports.array(external_exports.string().refine(writtenInAScript, "a BCP 47 language tag, such as en or vi")).min(1).default(["en"]),
44716
44842
  currencies: external_exports.array(external_exports.string().refine((code) => Intl.supportedValuesOf("currency").includes(code), "an ISO 4217 code, such as VND or USD")).default([])
44717
44843
  }).strict();
44718
- var zakuConfigSchema = external_exports.object({
44844
+ var configShape = external_exports.object({
44719
44845
  product: external_exports.string().min(1),
44720
44846
  designSystem: designSystemSchema,
44721
44847
  figma: files.default({}),
@@ -44727,7 +44853,8 @@ var zakuConfigSchema = external_exports.object({
44727
44853
  budget: budgetSchema.default({ mcpPerDay: 200, mcpPerRun: 30, reserve: 0.2 }),
44728
44854
  recipe: recipeSchema.optional(),
44729
44855
  copy: copySchema.default(DEFAULT_COPY)
44730
- }).strict().superRefine((config2, ctx) => {
44856
+ }).strict();
44857
+ var zakuConfigSchema = configShape.superRefine((config2, ctx) => {
44731
44858
  for (const key of ["id", "name"]) {
44732
44859
  const seen = /* @__PURE__ */ new Set();
44733
44860
  config2.targets.forEach((target, index) => {
@@ -44741,7 +44868,117 @@ var zakuConfigSchema = external_exports.object({
44741
44868
  seen.add(target[key]);
44742
44869
  });
44743
44870
  }
44871
+ if (byRole(config2.figma))
44872
+ refineFilesByRole(config2, ctx);
44744
44873
  });
44874
+ function refineFilesByRole(config2, ctx) {
44875
+ const { figma } = config2;
44876
+ if (figma.library !== void 0 || figma.product !== void 0) {
44877
+ ctx.addIssue({
44878
+ code: "custom",
44879
+ path: ["figma"],
44880
+ message: "figma.library and figma.product describe one library and one product; list files by role instead"
44881
+ });
44882
+ }
44883
+ const kits = figma.kits ?? [];
44884
+ const libraries = figma.libraries ?? [];
44885
+ const products = figma.products ?? [];
44886
+ const seen = /* @__PURE__ */ new Set();
44887
+ const lists = [
44888
+ ["kits", kits],
44889
+ ["libraries", libraries],
44890
+ ["products", products],
44891
+ ["prototypes", figma.prototypes ?? []]
44892
+ ];
44893
+ for (const [list, entries] of lists) {
44894
+ entries.forEach((entry, index) => {
44895
+ if (seen.has(entry.key))
44896
+ ctx.addIssue({
44897
+ code: "custom",
44898
+ path: ["figma", list, index, "key"],
44899
+ message: `a second file with key ${entry.key}`
44900
+ });
44901
+ seen.add(entry.key);
44902
+ });
44903
+ }
44904
+ const byKey = new Map(libraries.map((library) => [library.key, library]));
44905
+ const kitKeys = new Set(kits.map((kit) => kit.key));
44906
+ libraries.forEach((library, index) => {
44907
+ const path2 = ["figma", "libraries", index, "extends"];
44908
+ for (const key of library.extends) {
44909
+ const base = byKey.get(key);
44910
+ if (!base && !kitKeys.has(key)) {
44911
+ ctx.addIssue({
44912
+ code: "custom",
44913
+ path: path2,
44914
+ message: `${library.key} extends ${key}, which is no library or kit listed`
44915
+ });
44916
+ } else if (base?.layer === "components" && library.layer === "foundations") {
44917
+ ctx.addIssue({ code: "custom", path: path2, message: `foundations ${library.key} extends components ${key}` });
44918
+ } else if (base && !base.branches.some((branch) => library.branches.includes(branch))) {
44919
+ ctx.addIssue({
44920
+ code: "custom",
44921
+ path: path2,
44922
+ message: `${library.key} extends ${key}, which holds none of its branches`
44923
+ });
44924
+ }
44925
+ }
44926
+ });
44927
+ const cycle = findCycle(libraries);
44928
+ if (cycle)
44929
+ ctx.addIssue({ code: "custom", path: ["figma", "libraries"], message: `extends comes back: ${cycle}` });
44930
+ for (const [list, entries] of [
44931
+ ["products", products],
44932
+ ["prototypes", figma.prototypes ?? []]
44933
+ ]) {
44934
+ const families2 = /* @__PURE__ */ new Set();
44935
+ entries.forEach((entry, index) => {
44936
+ if (families2.has(entry.family))
44937
+ ctx.addIssue({
44938
+ code: "custom",
44939
+ path: ["figma", list, index, "family"],
44940
+ message: `a second ${list === "products" ? "product" : "prototype"} file for ${entry.family}`
44941
+ });
44942
+ families2.add(entry.family);
44943
+ });
44944
+ }
44945
+ (figma.prototypes ?? []).forEach((prototype2, index) => {
44946
+ if (!products.some((product) => product.family === prototype2.family))
44947
+ ctx.addIssue({
44948
+ code: "custom",
44949
+ path: ["figma", "prototypes", index, "family"],
44950
+ message: `no product file for ${prototype2.family} to copy its frames from`
44951
+ });
44952
+ });
44953
+ config2.targets.forEach((target, index) => {
44954
+ const branch = target.branch ?? defaultBranch(target.family);
44955
+ const path2 = ["targets", index, "branch"];
44956
+ if (branch === void 0) {
44957
+ ctx.addIssue({ code: "custom", path: path2, message: `a ${target.family} target names its branch, ui or native` });
44958
+ } else if (libraries.length > 0 && !libraries.some((library) => library.branches.includes(branch))) {
44959
+ ctx.addIssue({ code: "custom", path: path2, message: `no library holds the ${branch} branch` });
44960
+ }
44961
+ });
44962
+ }
44963
+ function findCycle(libraries) {
44964
+ const byKey = new Map(libraries.map((library) => [library.key, library]));
44965
+ const visit2 = (key, path2) => {
44966
+ if (path2.includes(key))
44967
+ return [...path2.slice(path2.indexOf(key)), key].join(" -> ");
44968
+ for (const next of byKey.get(key)?.extends ?? []) {
44969
+ const found = visit2(next, [...path2, key]);
44970
+ if (found)
44971
+ return found;
44972
+ }
44973
+ return void 0;
44974
+ };
44975
+ for (const library of libraries) {
44976
+ const found = visit2(library.key, []);
44977
+ if (found)
44978
+ return found;
44979
+ }
44980
+ return void 0;
44981
+ }
44745
44982
 
44746
44983
  // packages/zaku/dist/lib/repositories/design-file.js
44747
44984
  var import_yaml = __toESM(require_dist(), 1);
@@ -44782,6 +45019,12 @@ async function writeDesignFile(path2, value) {
44782
45019
 
44783
45020
  // packages/zaku/dist/lib/repositories/design-paths.js
44784
45021
  import { join as join2 } from "node:path";
45022
+ function librarySnapshotDir(paths, key) {
45023
+ if (key === null)
45024
+ return paths;
45025
+ const library = join2(paths.library, key);
45026
+ return { root: paths.root, library, libraryIndex: join2(library, "index.json"), legacyLibrary: null };
45027
+ }
44785
45028
  function designPaths(root) {
44786
45029
  return {
44787
45030
  root,
@@ -44825,8 +45068,13 @@ var libraryItemSchema = external_exports.object({
44825
45068
  fontFamily: external_exports.string().nullable(),
44826
45069
  textStyleKey: external_exports.string().nullable(),
44827
45070
  componentKey: external_exports.string().nullable(),
44828
- bindings: external_exports.record(external_exports.string(), external_exports.string().nullable())
45071
+ bindings: external_exports.record(external_exports.string(), external_exports.string().nullable()),
45072
+ /** Every variable the layer's own properties are bound to, by id without its `VariableID:` prefix. */
45073
+ variableIds: external_exports.array(external_exports.string()).optional(),
45074
+ effectStyleKey: external_exports.string().optional()
44829
45075
  }).strict();
45076
+ var variableKeySchema = external_exports.object({ key: external_exports.string(), local: external_exports.boolean() }).strict();
45077
+ var effectStyleSchema = external_exports.object({ key: external_exports.string(), name: external_exports.string() }).strict();
44830
45078
  var libraryVariantSchema = external_exports.object({
44831
45079
  key: external_exports.string(),
44832
45080
  name: external_exports.string(),
@@ -44837,6 +45085,8 @@ var libraryComponentSchema = external_exports.object({
44837
45085
  name: external_exports.string(),
44838
45086
  key: external_exports.string(),
44839
45087
  page: external_exports.string(),
45088
+ /** The branch of the view that holds it, in a file that holds several; none where it serves every branch. */
45089
+ branch: external_exports.string().optional(),
44840
45090
  codePath: external_exports.string().nullable(),
44841
45091
  map: external_exports.object({ props: external_exports.record(external_exports.string(), external_exports.string()), parts: external_exports.record(external_exports.string(), external_exports.string()) }).strict().nullable(),
44842
45092
  variants: external_exports.array(libraryVariantSchema).min(1)
@@ -44856,12 +45106,22 @@ var libraryPartSchema = external_exports.object({
44856
45106
  exportedAt: external_exports.string(),
44857
45107
  components: external_exports.array(libraryComponentSchema),
44858
45108
  textStyles: external_exports.array(textStyleSchema),
44859
- tokens: external_exports.array(tokenSnapshotSchema)
45109
+ tokens: external_exports.array(tokenSnapshotSchema),
45110
+ /**
45111
+ * What the items' references resolve against, absent from a snapshot saved before they were recorded: each
45112
+ * variable id's key, the file's effect styles, and the keys of the components the file holds unpublished.
45113
+ */
45114
+ variableKeys: external_exports.record(external_exports.string(), variableKeySchema).optional(),
45115
+ effectStyles: external_exports.array(effectStyleSchema).optional(),
45116
+ localComponents: external_exports.array(external_exports.string()).optional()
44860
45117
  }).strict();
44861
45118
  var variablePartSchema = external_exports.object({
44862
45119
  exportedAt: external_exports.string(),
44863
45120
  variables: external_exports.record(external_exports.string(), external_exports.string()),
44864
45121
  textStyles: external_exports.array(textStyleSchema),
45122
+ /** Each variable's key, keyed like `variables`. */
45123
+ variableKeys: external_exports.record(external_exports.string(), variableKeySchema).optional(),
45124
+ effectStyles: external_exports.array(effectStyleSchema).optional(),
44865
45125
  tokens: external_exports.array(external_exports.object({
44866
45126
  combo: external_exports.record(external_exports.string(), external_exports.string()),
44867
45127
  values: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]))
@@ -44872,10 +45132,17 @@ var variablePartSchema = external_exports.object({
44872
45132
  key: external_exports.string(),
44873
45133
  name: external_exports.string(),
44874
45134
  page: external_exports.string(),
45135
+ /** The view that holds it: the frame directly on its page, where it sits in one. */
45136
+ frame: external_exports.string().optional(),
44875
45137
  description: external_exports.string(),
44876
45138
  set: external_exports.boolean()
44877
45139
  }).strict()).optional()
44878
45140
  }).strict();
45141
+ var kitSnapshotSchema = external_exports.object({
45142
+ key: external_exports.string(),
45143
+ family: external_exports.string(),
45144
+ components: external_exports.array(external_exports.object({ key: external_exports.string(), name: external_exports.string() }).strict())
45145
+ }).strict();
44879
45146
  var librarySnapshotSchema = libraryPartSchema.extend({ source: external_exports.string(), version: external_exports.string() }).strict();
44880
45147
 
44881
45148
  // packages/zaku/dist/lib/domain/library-files.js
@@ -44944,7 +45211,9 @@ function expandItem(stored, values) {
44944
45211
  fontFamily: stored.fontFamily ?? null,
44945
45212
  textStyleKey: stored.textStyleKey ?? null,
44946
45213
  componentKey: stored.componentKey ?? null,
44947
- bindings
45214
+ bindings,
45215
+ ...stored.variableIds ? { variableIds: stored.variableIds } : {},
45216
+ ...stored.effectStyleKey ? { effectStyleKey: stored.effectStyleKey } : {}
44948
45217
  };
44949
45218
  }
44950
45219
  function compactItem(item, values) {
@@ -44960,6 +45229,10 @@ function compactItem(item, values) {
44960
45229
  stored[field] = item[field];
44961
45230
  if (item.glyph)
44962
45231
  stored["glyph"] = item.glyph;
45232
+ if (item.variableIds)
45233
+ stored["variableIds"] = item.variableIds;
45234
+ if (item.effectStyleKey)
45235
+ stored["effectStyleKey"] = item.effectStyleKey;
44963
45236
  const bindings = Object.fromEntries(Object.entries(item.bindings).filter(([name, value]) => value !== null || name === "glyph"));
44964
45237
  if (Object.keys(bindings).length > 0)
44965
45238
  stored["bindings"] = bindings;
@@ -45077,7 +45350,7 @@ async function writeText(path2, text) {
45077
45350
  async function readLibrarySnapshot(paths) {
45078
45351
  const index = await readOptionalDesignFile(paths.libraryIndex, libraryIndexSchema);
45079
45352
  if (!index)
45080
- return readOptionalDesignFile(paths.legacyLibrary, librarySnapshotSchema);
45353
+ return paths.legacyLibrary ? readOptionalDesignFile(paths.legacyLibrary, librarySnapshotSchema) : null;
45081
45354
  const files2 = /* @__PURE__ */ new Map();
45082
45355
  for (const component2 of index.components) {
45083
45356
  const path2 = join3(paths.library, component2.file);
@@ -45118,12 +45391,49 @@ async function writeLibrarySnapshot(paths, snapshot) {
45118
45391
  removed.push(file2);
45119
45392
  }
45120
45393
  await writeText(paths.libraryIndex, formatLibraryFile(index));
45121
- if (await readText(paths.legacyLibrary) !== null) {
45394
+ if (paths.legacyLibrary && await readText(paths.legacyLibrary) !== null) {
45122
45395
  await rm(paths.legacyLibrary);
45123
45396
  removed.push(relative(paths.root, paths.legacyLibrary));
45124
45397
  }
45125
45398
  return { written, unchanged, removed };
45126
45399
  }
45400
+ async function writeKitSnapshot(paths, kit) {
45401
+ await mkdir(paths.library, { recursive: true });
45402
+ const path2 = join3(paths.library, "kit.json");
45403
+ await writeText(path2, formatLibraryFile(kit));
45404
+ return path2;
45405
+ }
45406
+ async function readDesignSnapshots(paths, config2) {
45407
+ const graph = designGraph(config2);
45408
+ const libraries = {};
45409
+ const kits = {};
45410
+ const errors = [];
45411
+ const attempt = async (read) => {
45412
+ try {
45413
+ await read();
45414
+ } catch (error62) {
45415
+ if (!(error62 instanceof DesignFileInvalid))
45416
+ throw error62;
45417
+ errors.push(error62);
45418
+ }
45419
+ };
45420
+ for (const library of graph.libraries) {
45421
+ await attempt(async () => {
45422
+ const snapshot = await readLibrarySnapshot(librarySnapshotDir(paths, graph.legacy ? null : library.key));
45423
+ if (snapshot)
45424
+ libraries[library.key] = snapshot;
45425
+ });
45426
+ }
45427
+ for (const kit of graph.kits) {
45428
+ await attempt(async () => {
45429
+ const path2 = join3(librarySnapshotDir(paths, kit.key).library, "kit.json");
45430
+ const snapshot = await readOptionalDesignFile(path2, kitSnapshotSchema);
45431
+ if (snapshot)
45432
+ kits[kit.key] = snapshot;
45433
+ });
45434
+ }
45435
+ return { libraries, kits, errors };
45436
+ }
45127
45437
 
45128
45438
  // packages/zaku/dist/lib/adapters/figma-rest-library.js
45129
45439
  var DOCUMENTATION_PREFIXES = ["DS/"];
@@ -45197,6 +45507,23 @@ function strokeWeightsOf(node2) {
45197
45507
  const weight = node2.strokeWeight ?? 0;
45198
45508
  return [weight, weight, weight, weight];
45199
45509
  }
45510
+ function aliasIds(node2) {
45511
+ const out = [];
45512
+ const visit2 = (value) => {
45513
+ if (Array.isArray(value))
45514
+ value.forEach(visit2);
45515
+ else if (value && typeof value === "object") {
45516
+ if ("id" in value && typeof value.id === "string")
45517
+ out.push(value.id.replace(/^VariableID:/, ""));
45518
+ else
45519
+ Object.values(value).forEach(visit2);
45520
+ }
45521
+ };
45522
+ visit2(node2.boundVariables);
45523
+ for (const paint of [...node2.fills ?? [], ...node2.strokes ?? []])
45524
+ visit2(paint.boundVariables);
45525
+ return [...new Set(out)];
45526
+ }
45200
45527
  var GLYPH_TYPES = /* @__PURE__ */ new Set(["VECTOR", "BOOLEAN_OPERATION", "LINE", "ELLIPSE", "POLYGON", "STAR"]);
45201
45528
  function glyphOf(instance, at) {
45202
45529
  let text = false;
@@ -45236,6 +45563,9 @@ function itemsOf(root, at) {
45236
45563
  const textStyle = node2.styles?.["text"];
45237
45564
  const glyph = path2 !== "" && node2.type === "INSTANCE" ? glyphOf(node2, at) : null;
45238
45565
  const componentId = node2.type === "INSTANCE" ? node2.componentId : path2 === "" ? node2.id : void 0;
45566
+ const variableIds = aliasIds(node2);
45567
+ const effectStyle = node2.styles?.["effect"];
45568
+ const effectStyleKey = effectStyle ? at.entry.styles[effectStyle]?.key : void 0;
45239
45569
  out.push({
45240
45570
  nodeId: node2.id,
45241
45571
  path: path2,
@@ -45260,7 +45590,9 @@ function itemsOf(root, at) {
45260
45590
  textColor: isText ? fill.binding : null,
45261
45591
  radius: radiusName,
45262
45592
  ...glyph?.value ? { glyph: glyph.binding } : {}
45263
- }
45593
+ },
45594
+ ...variableIds.length > 0 ? { variableIds } : {},
45595
+ ...effectStyleKey ? { effectStyleKey } : {}
45264
45596
  });
45265
45597
  if (path2 === "" || node2.type !== "INSTANCE") {
45266
45598
  for (const child of node2.children ?? [])
@@ -45279,28 +45611,55 @@ function asPublished(component2) {
45279
45611
  containing_frame: { pageName: component2.page }
45280
45612
  };
45281
45613
  }
45614
+ function viewBranch(view, branches) {
45615
+ const prefix = view?.split(" / ")[0];
45616
+ return view?.includes(" / ") && prefix !== void 0 && branches.includes(prefix) ? prefix : void 0;
45617
+ }
45618
+ function viewsByPage(document) {
45619
+ return new Map((document.children ?? []).map((page) => [
45620
+ page.name,
45621
+ (page.children ?? []).flatMap((view) => view.absoluteBoundingBox ? [{ name: view.name, box: view.absoluteBoundingBox }] : [])
45622
+ ]));
45623
+ }
45624
+ function viewHolding(views, box) {
45625
+ if (!box)
45626
+ return void 0;
45627
+ return views.find(({ box: view }) => box.x >= view.x && box.y >= view.y && box.x < view.x + view.width && box.y < view.y + view.height)?.name;
45628
+ }
45282
45629
  async function readLibrary(options) {
45283
- const { rest, fileKey: fileKey2, part } = options;
45630
+ const { rest, fileKey: fileKey3, part } = options;
45631
+ const branches = options.branches ?? [];
45632
+ const multiBranch = branches.length > 1;
45633
+ const exported = new Map((part.components ?? []).map((component2) => [component2.id, component2.frame]));
45284
45634
  const listed = part.components ? {
45285
45635
  sets: part.components.filter((component2) => component2.set).map(asPublished),
45286
45636
  components: part.components.filter((component2) => !component2.set).map(asPublished)
45287
- } : await rest.published(fileKey2);
45637
+ } : await rest.published(fileKey3);
45288
45638
  const published = [
45289
45639
  ...listed.sets,
45290
45640
  ...listed.components.filter((component2) => !component2.containing_frame?.containingComponentSet)
45291
45641
  ].filter((meta3) => !DOCUMENTATION_PREFIXES.some((prefix) => meta3.name.startsWith(prefix)));
45292
- const file2 = await rest.file(fileKey2, 1);
45293
- const response = published.length > 0 ? await rest.nodes(fileKey2, published.map((meta3) => meta3.node_id)) : { version: file2.version, nodes: {} };
45642
+ const file2 = await rest.file(fileKey3, multiBranch ? 2 : 1);
45643
+ const views = multiBranch ? viewsByPage(file2.document) : /* @__PURE__ */ new Map();
45644
+ const response = published.length > 0 ? await rest.nodes(fileKey3, published.map((meta3) => meta3.node_id)) : { version: file2.version, nodes: {} };
45294
45645
  const components = [];
45646
+ const localComponents = /* @__PURE__ */ new Set();
45647
+ for (const entry of Object.values(response.nodes))
45648
+ for (const meta3 of Object.values(entry?.components ?? {}))
45649
+ if (meta3.remote === false)
45650
+ localComponents.add(meta3.key);
45295
45651
  for (const meta3 of published) {
45296
45652
  const entry = response.nodes[meta3.node_id];
45297
45653
  if (!entry)
45298
45654
  continue;
45299
45655
  const variantNodes = entry.document.type === "COMPONENT_SET" ? (entry.document.children ?? []).filter((child) => child.type === "COMPONENT") : [entry.document];
45656
+ const view = exported.has(meta3.node_id) ? exported.get(meta3.node_id) : viewHolding(views.get(meta3.containing_frame?.pageName ?? "") ?? [], entry.document.absoluteBoundingBox);
45657
+ const branch = multiBranch ? viewBranch(view, branches) : void 0;
45300
45658
  components.push({
45301
45659
  name: meta3.name,
45302
45660
  key: meta3.key,
45303
45661
  page: (meta3.containing_frame?.pageName ?? "").replace(/^\W+\s*/, ""),
45662
+ ...branch ? { branch } : {},
45304
45663
  ...descriptionMap(meta3.description),
45305
45664
  variants: variantNodes.map((variant) => ({
45306
45665
  key: entry.components[variant.id]?.key ?? "",
@@ -45314,11 +45673,16 @@ async function readLibrary(options) {
45314
45673
  });
45315
45674
  }
45316
45675
  return {
45317
- source: fileKey2,
45676
+ source: fileKey3,
45318
45677
  version: file2.version,
45319
45678
  exportedAt: part.exportedAt,
45320
45679
  components,
45321
45680
  textStyles: part.textStyles,
45681
+ ...part.variableKeys ? {
45682
+ variableKeys: part.variableKeys,
45683
+ effectStyles: part.effectStyles ?? [],
45684
+ localComponents: [...localComponents].sort()
45685
+ } : {},
45322
45686
  tokens: part.tokens.map((token) => ({
45323
45687
  combo: token.combo,
45324
45688
  values: Object.fromEntries(Object.entries(token.values).map(([name, value]) => [
@@ -45328,16 +45692,57 @@ async function readLibrary(options) {
45328
45692
  }))
45329
45693
  };
45330
45694
  }
45695
+ async function readKit(options) {
45696
+ const components = await options.rest.components(options.key);
45697
+ return {
45698
+ key: options.key,
45699
+ family: options.family,
45700
+ components: components.map((component2) => ({ key: component2.key, name: component2.name }))
45701
+ };
45702
+ }
45331
45703
 
45332
45704
  // packages/zaku/dist/lib/handlers/save-library.js
45705
+ function librarySaveTarget(config2, key) {
45706
+ const graph = designGraph(config2);
45707
+ const keys = [...graph.libraries, ...graph.kits].map((file2) => file2.key);
45708
+ if (keys.length === 0) {
45709
+ const byRole2 = config2.figma.libraries !== void 0 || config2.figma.kits !== void 0;
45710
+ throw new FigmaFileKeyMissing(byRole2 ? "figma.libraries" : "figma.library");
45711
+ }
45712
+ const library = key === void 0 ? graph.libraries.length === 1 ? graph.libraries[0] : void 0 : void 0;
45713
+ if (library)
45714
+ return { kind: "library", library, legacy: graph.legacy };
45715
+ if (key === void 0)
45716
+ throw new LibraryKeyMissing(keys);
45717
+ const named = graph.libraries.find((candidate) => candidate.key === key);
45718
+ if (named)
45719
+ return { kind: "library", library: named, legacy: graph.legacy };
45720
+ const kit = graph.kits.find((candidate) => candidate.key === key);
45721
+ if (kit)
45722
+ return { kind: "kit", kit };
45723
+ throw new LibraryKeyMissing(keys, key);
45724
+ }
45333
45725
  async function saveLibrary(options) {
45334
45726
  const paths = designPaths(options.root);
45335
45727
  const config2 = await readDesignFile(paths.config, zakuConfigSchema);
45336
- const fileKey2 = config2.figma.library;
45337
- if (fileKey2 === void 0)
45338
- throw new FigmaFileKeyMissing("figma.library");
45339
- const snapshot = librarySnapshotSchema.parse(await readLibrary({ rest: options.rest, fileKey: fileKey2, part: options.part }));
45340
- return { snapshot, ...await writeLibrarySnapshot(paths, snapshot) };
45728
+ const target = librarySaveTarget(config2, options.key);
45729
+ if (target.kind === "kit") {
45730
+ const at2 = librarySnapshotDir(paths, target.kit.key);
45731
+ const kit = await readKit({ rest: options.rest, key: target.kit.key, family: target.kit.family });
45732
+ await writeKitSnapshot(at2, kit);
45733
+ return { kind: "kit", dir: relative2(paths.root, at2.library), kit };
45734
+ }
45735
+ if (!options.part)
45736
+ throw new TypeError(`saving library ${target.library.key} needs the variables export`);
45737
+ const { key, branches } = target.library;
45738
+ const at = librarySnapshotDir(paths, target.legacy ? null : key);
45739
+ const snapshot = librarySnapshotSchema.parse(await readLibrary({ rest: options.rest, fileKey: key, part: options.part, branches }));
45740
+ return {
45741
+ kind: "library",
45742
+ dir: relative2(paths.root, at.library),
45743
+ snapshot,
45744
+ ...await writeLibrarySnapshot(at, snapshot)
45745
+ };
45341
45746
  }
45342
45747
  var SaveLibraryHandler = class SaveLibraryHandler2 {
45343
45748
  rest;
@@ -45346,7 +45751,12 @@ var SaveLibraryHandler = class SaveLibraryHandler2 {
45346
45751
  this.rest = rest;
45347
45752
  }
45348
45753
  handle(command) {
45349
- return saveLibrary({ root: command.root, part: command.part, rest: this.rest });
45754
+ return saveLibrary({
45755
+ root: command.root,
45756
+ rest: this.rest,
45757
+ ...command.key !== void 0 ? { key: command.key } : {},
45758
+ ...command.part !== void 0 ? { part: command.part } : {}
45759
+ });
45350
45760
  }
45351
45761
  };
45352
45762
  SaveLibraryHandler = __decorate2([
@@ -45356,9 +45766,10 @@ SaveLibraryHandler = __decorate2([
45356
45766
 
45357
45767
  // packages/zaku/dist/lib/handlers/write-outline.js
45358
45768
  var import_tsyringe4 = __toESM(require_cjs(), 1);
45769
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
45359
45770
  var import_yaml2 = __toESM(require_dist(), 1);
45360
45771
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
45361
- import { dirname as dirname2 } from "node:path";
45772
+ import { dirname as dirname2, join as join6 } from "node:path";
45362
45773
 
45363
45774
  // packages/zaku/dist/lib/domain/documentation-measures.js
45364
45775
  var COVER = {
@@ -45866,6 +46277,9 @@ function indexTitles(maps) {
45866
46277
  return { titles, duplicates };
45867
46278
  }
45868
46279
 
46280
+ // packages/zaku/dist/lib/adapters/figma-rest-outline.js
46281
+ import { createHash as createHash2 } from "node:crypto";
46282
+
45869
46283
  // packages/zaku/dist/lib/domain/layer-names.js
45870
46284
  var DEFAULT_NAMES = {
45871
46285
  FRAME: ["Frame"],
@@ -45934,7 +46348,7 @@ function outlineFrame(frame, where, ctx) {
45934
46348
  const images2 = [];
45935
46349
  const defaultNames = [];
45936
46350
  const links = [];
45937
- const relative2 = (node2) => {
46351
+ const relative3 = (node2) => {
45938
46352
  const box = node2.absoluteBoundingBox;
45939
46353
  return box ? { x: box.x - origin.x, y: box.y - origin.y, width: box.width, height: box.height } : null;
45940
46354
  };
@@ -45963,7 +46377,7 @@ function outlineFrame(frame, where, ctx) {
45963
46377
  componentKey: ctx.components[child.componentId ?? ""]?.key ?? "",
45964
46378
  component: component2,
45965
46379
  interactive: ctx.interactive.has(component2),
45966
- bounds: relative2(child)
46380
+ bounds: relative3(child)
45967
46381
  });
45968
46382
  } else if (child.type === "TEXT") {
45969
46383
  instance.texts.push({
@@ -46004,7 +46418,7 @@ function outlineFrame(frame, where, ctx) {
46004
46418
  texts: [],
46005
46419
  interactive: ctx.interactive.has(component2),
46006
46420
  local: meta3?.remote === false,
46007
- bounds: relative2(node2),
46421
+ bounds: relative3(node2),
46008
46422
  sizing: {
46009
46423
  horizontal: node2.layoutSizingHorizontal ?? "FIXED",
46010
46424
  vertical: node2.layoutSizingVertical ?? "FIXED"
@@ -46053,6 +46467,17 @@ function outlineFrame(frame, where, ctx) {
46053
46467
  links.push(...linksOf(frame));
46054
46468
  for (const child of shown(frame))
46055
46469
  walk(child);
46470
+ const drawn = {
46471
+ size: [origin.width, origin.height],
46472
+ instances: instances.map((instance) => [
46473
+ instance.componentKey,
46474
+ instance.variant,
46475
+ instance.properties,
46476
+ instance.texts.map((text) => text.characters),
46477
+ instance.bounds
46478
+ ]),
46479
+ texts: texts.map((text) => text.characters)
46480
+ };
46056
46481
  return {
46057
46482
  nodeId: frame.id,
46058
46483
  name: frame.name,
@@ -46066,7 +46491,8 @@ function outlineFrame(frame, where, ctx) {
46066
46491
  raw,
46067
46492
  images: images2,
46068
46493
  defaultNames,
46069
- links
46494
+ links,
46495
+ digest: createHash2("sha256").update(JSON.stringify(drawn)).digest("hex").slice(0, 16)
46070
46496
  };
46071
46497
  }
46072
46498
 
@@ -46132,36 +46558,78 @@ var frameOutlineSchema = external_exports.object({
46132
46558
  raw: external_exports.array(rawOutlineSchema),
46133
46559
  images: external_exports.array(imageOutlineSchema),
46134
46560
  defaultNames: external_exports.array(external_exports.object({ nodeId: external_exports.string(), name: external_exports.string() }).strict()),
46135
- links: external_exports.array(linkOutlineSchema)
46561
+ links: external_exports.array(linkOutlineSchema),
46562
+ /** In a product file of one page per screen: the page the frame is on, and the product file it is in. */
46563
+ page: external_exports.string().optional(),
46564
+ source: external_exports.string().optional(),
46565
+ /** What the frame draws, without its node ids, so a copy and its source digest alike. */
46566
+ digest: external_exports.string().optional()
46136
46567
  }).strict();
46137
- var screenOutlineSchema = external_exports.object({
46568
+ var flowOutlineSchema = external_exports.object({
46569
+ /** The Figma file key the page is in. */
46570
+ source: external_exports.string(),
46571
+ page: external_exports.string(),
46572
+ fileVersion: external_exports.string(),
46573
+ frames: external_exports.array(external_exports.object({
46574
+ nodeId: external_exports.string(),
46575
+ name: external_exports.string(),
46576
+ screen: external_exports.string().nullable(),
46577
+ target: external_exports.string(),
46578
+ state: external_exports.string(),
46579
+ start: external_exports.boolean(),
46580
+ links: external_exports.array(linkOutlineSchema),
46581
+ digest: external_exports.string()
46582
+ }).strict())
46583
+ }).strict();
46584
+ var LEGACY_LIBRARY_VERSION = "figma.library";
46585
+ function legacyVersion(value) {
46586
+ if (value === null || typeof value !== "object" || !("libraryVersion" in value))
46587
+ return value;
46588
+ const { libraryVersion, ...rest } = value;
46589
+ return {
46590
+ ...rest,
46591
+ libraryVersions: typeof libraryVersion === "string" ? { [LEGACY_LIBRARY_VERSION]: libraryVersion } : {}
46592
+ };
46593
+ }
46594
+ var screenOutlineSchema = external_exports.preprocess(legacyVersion, external_exports.object({
46138
46595
  feature: external_exports.string(),
46139
46596
  screen: external_exports.string(),
46140
46597
  /** The Figma file key the frames were read from. */
46141
46598
  source: external_exports.string(),
46142
46599
  fileVersion: external_exports.string(),
46143
46600
  mapDigest: external_exports.string(),
46144
- libraryVersion: external_exports.string().nullable(),
46601
+ /** The version of each library snapshot the frames were read against, by key. */
46602
+ libraryVersions: external_exports.record(external_exports.string(), external_exports.string()),
46603
+ /** The version of every product file zaku.yaml lists, when it lists them by family. */
46604
+ fileVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
46145
46605
  frames: external_exports.array(frameOutlineSchema)
46146
- }).strict();
46606
+ }).strict());
46147
46607
  var unmappedSchema = external_exports.object({
46148
46608
  fileVersion: external_exports.string(),
46149
- frames: external_exports.array(external_exports.object({ nodeId: external_exports.string(), name: external_exports.string() }).strict())
46609
+ frames: external_exports.array(external_exports.object({ nodeId: external_exports.string(), name: external_exports.string() }).strict()),
46610
+ /** A component drawn on a product page, which belongs in a Components library. */
46611
+ components: external_exports.array(external_exports.object({ nodeId: external_exports.string(), name: external_exports.string(), page: external_exports.string(), source: external_exports.string() }).strict()).optional()
46150
46612
  }).strict();
46151
46613
 
46152
46614
  // packages/zaku/dist/lib/repositories/outlines.js
46615
+ var FLOWS_DIR = "flows";
46616
+ function flowOutlinePath(dir, source, page) {
46617
+ const slug2 = page.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
46618
+ return join5(dir, FLOWS_DIR, source, `${slug2}.yaml`);
46619
+ }
46153
46620
  function outlinePath(dir, feature, screen) {
46154
46621
  return join5(dir, feature, `${screen}.yaml`);
46155
46622
  }
46156
46623
  async function loadOutlines(dir) {
46157
46624
  const outlines = [];
46625
+ const flows = [];
46158
46626
  const errors = [];
46159
46627
  let features;
46160
46628
  try {
46161
- features = (await readdir3(dir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
46629
+ features = (await readdir3(dir, { withFileTypes: true })).filter((entry) => entry.isDirectory() && entry.name !== FLOWS_DIR).map((entry) => entry.name).sort();
46162
46630
  } catch (error62) {
46163
46631
  if (error62.code === "ENOENT")
46164
- return { outlines, unmapped: null, errors };
46632
+ return { outlines, flows, unmapped: null, errors };
46165
46633
  throw error62;
46166
46634
  }
46167
46635
  for (const feature of features) {
@@ -46177,6 +46645,24 @@ async function loadOutlines(dir) {
46177
46645
  }
46178
46646
  }
46179
46647
  }
46648
+ const sources = await readdir3(join5(dir, FLOWS_DIR)).catch((error62) => {
46649
+ if (error62.code === "ENOENT")
46650
+ return [];
46651
+ throw error62;
46652
+ });
46653
+ for (const source of sources.sort()) {
46654
+ const files2 = (await readdir3(join5(dir, FLOWS_DIR, source))).filter((name) => name.endsWith(".yaml")).sort();
46655
+ for (const file2 of files2) {
46656
+ try {
46657
+ flows.push(await readDesignFile(join5(dir, FLOWS_DIR, source, file2), flowOutlineSchema));
46658
+ } catch (error62) {
46659
+ if (error62 instanceof DesignFileInvalid)
46660
+ errors.push(error62);
46661
+ else
46662
+ throw error62;
46663
+ }
46664
+ }
46665
+ }
46180
46666
  let unmapped = null;
46181
46667
  try {
46182
46668
  unmapped = await readOptionalDesignFile(join5(dir, "unmapped.yaml"), unmappedSchema);
@@ -46186,7 +46672,7 @@ async function loadOutlines(dir) {
46186
46672
  else
46187
46673
  throw error62;
46188
46674
  }
46189
- return { outlines, unmapped, errors };
46675
+ return { outlines, flows, unmapped, errors };
46190
46676
  }
46191
46677
 
46192
46678
  // packages/zaku/dist/lib/handlers/write-outline.js
@@ -46223,95 +46709,202 @@ function resolveFrame(frame, parsed, maps) {
46223
46709
  return candidates.find((ref) => ref.feature === pageFeature);
46224
46710
  return candidates.length === 1 ? candidates[0] : void 0;
46225
46711
  }
46712
+ function resolveTitle(parsed, maps) {
46713
+ const candidates = [...indexScreens(maps).values()].filter((ref) => ref.screen.title === parsed.title);
46714
+ return candidates.length === 1 ? candidates[0] : void 0;
46715
+ }
46226
46716
  function looksLikeScreenFrame(name) {
46227
46717
  return name.includes("/");
46228
46718
  }
46229
- function holdsScreens(page) {
46719
+ function holdsScreens(page, prototypes = /* @__PURE__ */ new Set()) {
46230
46720
  if (page === COVER.page || page === DOCUMENTATION_COMPONENTS_PAGE || page === DIRECTIONS_PAGE)
46231
46721
  return false;
46722
+ if (prototypes.has(page))
46723
+ return false;
46232
46724
  return !page.startsWith("\u2756 ") && !page.includes(" / ") && !/^-+$/.test(page);
46233
46725
  }
46726
+ function listComponents(document, source) {
46727
+ const out = [];
46728
+ for (const page of document.children ?? []) {
46729
+ if (page.type !== "CANVAS" || !holdsScreens(page.name))
46730
+ continue;
46731
+ const walk = (node2) => {
46732
+ for (const child of node2.children ?? []) {
46733
+ if (child.type === "COMPONENT" || child.type === "COMPONENT_SET")
46734
+ out.push({ nodeId: child.id, name: child.name, page: page.name, source });
46735
+ else if (child.type === "SECTION")
46736
+ walk(child);
46737
+ }
46738
+ };
46739
+ walk(page);
46740
+ }
46741
+ return out;
46742
+ }
46743
+ function productFiles(config2) {
46744
+ const graph = designGraph(config2);
46745
+ if (graph.legacy)
46746
+ return config2.figma.product === void 0 ? [] : [{ key: config2.figma.product, targets: config2.targets }];
46747
+ return graph.products.map((product) => ({
46748
+ key: product.key,
46749
+ targets: graph.targetsOf(product.family)
46750
+ }));
46751
+ }
46234
46752
  async function runOutline(options) {
46235
46753
  const paths = designPaths(options.root);
46236
46754
  const config2 = await readDesignFile(paths.config, zakuConfigSchema);
46237
- const fileKey2 = config2.figma.product;
46238
- if (fileKey2 === void 0)
46239
- throw new FigmaFileKeyMissing("figma.product");
46755
+ const { libraries: listed, kits, products: byFamily, prototypes: flows } = config2.figma;
46756
+ const legacy = [listed, kits, byFamily, flows].every((files2) => files2 === void 0);
46757
+ const products = productFiles(config2);
46758
+ if (products.length === 0)
46759
+ throw new FigmaFileKeyMissing(legacy ? "figma.product" : "figma.products");
46240
46760
  const { maps, errors } = await loadMaps(paths.maps);
46241
46761
  if (errors[0])
46242
46762
  throw errors[0];
46243
46763
  const digests = new Map(maps.map((loaded) => [loaded.map.feature, loaded.digest]));
46244
- const library = await readLibrarySnapshot(paths);
46764
+ const { libraries } = await readDesignSnapshots(paths, config2);
46765
+ const libraryVersions = Object.fromEntries(Object.entries(libraries).map(([key, library]) => [key, library.version]));
46245
46766
  const existing = await loadOutlines(paths.outline);
46246
- const file2 = await options.rest.file(fileKey2, INDEX_DEPTH);
46247
- const listed = listFrames(file2.document);
46767
+ const prototypes = new Set(legacy ? config2.targets.map((target) => `${target.name} prototype`) : []);
46768
+ const versions = {};
46769
+ const flowPages = /* @__PURE__ */ new Map();
46770
+ const flowFrame = (source, frame, targets) => {
46771
+ const parsed = parseFrameName(frame.name, targets);
46772
+ if (!parsed)
46773
+ return;
46774
+ const key = `${source}
46775
+ ${frame.page}`;
46776
+ const page = flowPages.get(key) ?? { source, page: frame.page, frames: [] };
46777
+ page.frames.push({ frame, parsed });
46778
+ flowPages.set(key, page);
46779
+ };
46248
46780
  const resolved = /* @__PURE__ */ new Map();
46249
- const unmapped = { fileVersion: file2.version, frames: [] };
46250
- for (const frame of listed.filter((listed2) => holdsScreens(listed2.page))) {
46251
- const parsed = parseFrameName(frame.name, config2.targets);
46252
- if (!parsed) {
46253
- if (looksLikeScreenFrame(frame.name))
46781
+ const unmapped = { fileVersion: "", frames: [] };
46782
+ const components = [];
46783
+ for (const product of products) {
46784
+ const file2 = await options.rest.file(product.key, INDEX_DEPTH);
46785
+ versions[product.key] = file2.version;
46786
+ const listed2 = listFrames(file2.document);
46787
+ for (const frame of listed2.filter((candidate) => prototypes.has(candidate.page)))
46788
+ flowFrame(product.key, frame, product.targets);
46789
+ for (const frame of listed2.filter((candidate) => holdsScreens(candidate.page, prototypes))) {
46790
+ const parsed = parseFrameName(frame.name, product.targets);
46791
+ if (!parsed) {
46792
+ if (looksLikeScreenFrame(frame.name))
46793
+ unmapped.frames.push({ nodeId: frame.id, name: frame.name });
46794
+ continue;
46795
+ }
46796
+ const ref = legacy ? resolveFrame(frame, parsed, maps) : resolveTitle(parsed, maps);
46797
+ if (ref)
46798
+ resolved.set(frame.id, { frame, parsed, ref, source: product.key });
46799
+ else
46254
46800
  unmapped.frames.push({ nodeId: frame.id, name: frame.name });
46255
- continue;
46256
46801
  }
46257
- const ref = resolveFrame(frame, parsed, maps);
46258
- if (ref)
46259
- resolved.set(frame.id, { frame, parsed, ref });
46260
- else
46261
- unmapped.frames.push({ nodeId: frame.id, name: frame.name });
46802
+ if (!legacy)
46803
+ components.push(...listComponents(file2.document, product.key));
46804
+ }
46805
+ const graph = designGraph(config2);
46806
+ for (const file2 of legacy ? [] : graph.prototypes) {
46807
+ const read = await options.rest.file(file2.key, INDEX_DEPTH);
46808
+ versions[file2.key] = read.version;
46809
+ const targets = graph.targetsOf(file2.family);
46810
+ for (const frame of listFrames(read.document).filter((candidate) => holdsScreens(candidate.page)))
46811
+ flowFrame(file2.key, frame, targets);
46262
46812
  }
46813
+ unmapped.fileVersion = Object.values(versions).join(",");
46814
+ if (!legacy)
46815
+ unmapped.components = components;
46263
46816
  const outlined = new Set(existing.outlines.flatMap((outline) => outline.frames.map((frame) => frame.nodeId)));
46264
46817
  const drawnScreens = new Set([...resolved.values()].map(({ ref }) => `${ref.feature}/${ref.id}`));
46265
- const current = existing.outlines.every((outline) => outline.fileVersion === file2.version && outline.mapDigest === digests.get(outline.feature) && outline.libraryVersion === (library?.version ?? null)) && [...resolved.keys()].every((id2) => outlined.has(id2)) && existing.outlines.every((outline) => drawnScreens.has(`${outline.feature}/${outline.screen}`));
46818
+ const current = existing.outlines.every((outline) => (legacy ? outline.fileVersion === versions[outline.source] : isDeepStrictEqual2(outline.fileVersions, versions)) && outline.mapDigest === digests.get(outline.feature) && isDeepStrictEqual2(outline.libraryVersions, libraryVersions)) && [...resolved.keys()].every((id2) => outlined.has(id2)) && existing.flows.length === flowPages.size && existing.flows.every((flow) => flow.fileVersion === versions[flow.source]) && existing.outlines.every((outline) => drawnScreens.has(`${outline.feature}/${outline.screen}`));
46266
46819
  if (options.frames === null && existing.outlines.length > 0 && current) {
46267
46820
  return { skipped: true, read: 0, written: [], unmapped: unmapped.frames.length };
46268
46821
  }
46269
46822
  const ids = options.frames ?? [...resolved.keys()];
46270
- const response = ids.length > 0 ? await options.rest.nodes(fileKey2, ids) : { version: file2.version, nodes: {} };
46271
46823
  const byScreen = /* @__PURE__ */ new Map();
46272
46824
  if (options.frames !== null) {
46273
46825
  for (const outline of existing.outlines)
46274
46826
  byScreen.set(`${outline.feature}/${outline.screen}`, outline);
46275
46827
  }
46276
46828
  const interactive = new Set(config2.interactive);
46277
- const resized = new Set(Object.values(response.nodes).flatMap((entry) => entry ? resizedLocalComponents(entry.document, entry.components) : []));
46278
- const sizes = /* @__PURE__ */ new Map();
46279
- if (resized.size > 0) {
46280
- const read = await options.rest.nodes(fileKey2, [...resized]);
46281
- for (const [id2, entry] of Object.entries(read.nodes)) {
46282
- const box = entry?.document.absoluteBoundingBox;
46283
- if (box)
46284
- sizes.set(id2, { width: box.width, height: box.height });
46285
- }
46286
- }
46287
- for (const [id2, entry] of Object.entries(response.nodes)) {
46288
- const found = resolved.get(id2);
46289
- if (!entry || !found)
46829
+ for (const product of products) {
46830
+ const own2 = ids.filter((id2) => resolved.has(id2) ? resolved.get(id2)?.source === product.key : product === products[0]);
46831
+ if (own2.length === 0)
46290
46832
  continue;
46291
- const { frame: place, parsed, ref } = found;
46292
- const key = `${ref.feature}/${ref.id}`;
46293
- const outline = byScreen.get(key) ?? {
46294
- feature: ref.feature,
46295
- screen: ref.id,
46296
- source: fileKey2,
46297
- fileVersion: file2.version,
46298
- mapDigest: "",
46299
- libraryVersion: null,
46300
- frames: []
46301
- };
46302
- const frame = outlineFrame(entry.document, {
46303
- target: parsed.target.id,
46304
- state: parsed.state,
46305
- containers: place.containers,
46306
- start: place.start
46307
- }, { ...entry, interactive, sizes });
46308
- outline.frames = [...outline.frames.filter((kept) => kept.nodeId !== id2), frame].sort((a, b) => a.name.localeCompare(b.name));
46309
- outline.fileVersion = file2.version;
46310
- outline.mapDigest = digests.get(ref.feature) ?? "";
46311
- outline.libraryVersion = library?.version ?? null;
46312
- byScreen.set(key, outline);
46833
+ const response = await options.rest.nodes(product.key, own2);
46834
+ const resized = new Set(Object.values(response.nodes).flatMap((entry) => entry ? resizedLocalComponents(entry.document, entry.components) : []));
46835
+ const sizes = /* @__PURE__ */ new Map();
46836
+ if (resized.size > 0) {
46837
+ const read = await options.rest.nodes(product.key, [...resized]);
46838
+ for (const [id2, entry] of Object.entries(read.nodes)) {
46839
+ const box = entry?.document.absoluteBoundingBox;
46840
+ if (box)
46841
+ sizes.set(id2, { width: box.width, height: box.height });
46842
+ }
46843
+ }
46844
+ for (const [id2, entry] of Object.entries(response.nodes)) {
46845
+ const found = resolved.get(id2);
46846
+ if (!entry || !found)
46847
+ continue;
46848
+ const { frame: place, parsed, ref } = found;
46849
+ const key = `${ref.feature}/${ref.id}`;
46850
+ const outline = byScreen.get(key) ?? {
46851
+ feature: ref.feature,
46852
+ screen: ref.id,
46853
+ source: product.key,
46854
+ fileVersion: versions[product.key] ?? "",
46855
+ mapDigest: "",
46856
+ libraryVersions: {},
46857
+ frames: []
46858
+ };
46859
+ const frame = outlineFrame(entry.document, {
46860
+ target: parsed.target.id,
46861
+ state: parsed.state,
46862
+ containers: place.containers,
46863
+ start: place.start
46864
+ }, { ...entry, interactive, sizes });
46865
+ if (!legacy)
46866
+ Object.assign(frame, { page: place.page, source: product.key });
46867
+ outline.frames = [...outline.frames.filter((kept) => kept.nodeId !== id2), frame].sort((a, b) => a.name.localeCompare(b.name));
46868
+ outline.mapDigest = digests.get(ref.feature) ?? "";
46869
+ outline.libraryVersions = libraryVersions;
46870
+ if (legacy)
46871
+ outline.fileVersion = versions[product.key] ?? "";
46872
+ else
46873
+ outline.fileVersions = versions;
46874
+ byScreen.set(key, outline);
46875
+ }
46313
46876
  }
46314
46877
  if (options.frames === null) {
46878
+ await rm2(join6(paths.outline, FLOWS_DIR), { recursive: true, force: true });
46879
+ for (const page of flowPages.values()) {
46880
+ const response = await options.rest.nodes(page.source, page.frames.map(({ frame }) => frame.id));
46881
+ const flow = {
46882
+ source: page.source,
46883
+ page: page.page,
46884
+ fileVersion: versions[page.source] ?? "",
46885
+ frames: []
46886
+ };
46887
+ for (const { frame, parsed } of page.frames) {
46888
+ const entry = response.nodes[frame.id];
46889
+ if (!entry)
46890
+ continue;
46891
+ const read = outlineFrame(entry.document, { target: parsed.target.id, state: parsed.state, containers: frame.containers, start: frame.start }, { ...entry, interactive, sizes: /* @__PURE__ */ new Map() });
46892
+ const ref = resolveTitle(parsed, maps);
46893
+ flow.frames.push({
46894
+ nodeId: frame.id,
46895
+ name: frame.name,
46896
+ screen: ref ? `${ref.feature}/${ref.id}` : null,
46897
+ target: parsed.target.id,
46898
+ state: parsed.state,
46899
+ start: frame.start,
46900
+ links: read.links,
46901
+ digest: read.digest ?? ""
46902
+ });
46903
+ }
46904
+ const path2 = flowOutlinePath(paths.outline, page.source, page.page);
46905
+ await mkdir2(dirname2(path2), { recursive: true });
46906
+ await writeFile3(path2, (0, import_yaml2.stringify)(flow));
46907
+ }
46315
46908
  for (const outline of existing.outlines) {
46316
46909
  if (!byScreen.has(`${outline.feature}/${outline.screen}`))
46317
46910
  await rm2(outlinePath(paths.outline, outline.feature, outline.screen), { force: true });
@@ -46441,13 +47034,13 @@ RecipeRepository = __decorate2([
46441
47034
  // packages/zaku/dist/lib/repositories/json-schema-files.js
46442
47035
  var import_tsyringe10 = __toESM(require_cjs(), 1);
46443
47036
  import { mkdir as mkdir3 } from "node:fs/promises";
46444
- import { join as join6 } from "node:path";
47037
+ import { join as join7 } from "node:path";
46445
47038
  var JsonSchemaFiles = class JsonSchemaFiles2 {
46446
47039
  async write(dir, schemas) {
46447
47040
  await mkdir3(dir, { recursive: true });
46448
47041
  const written = [];
46449
47042
  for (const [name, schema] of Object.entries(schemas)) {
46450
- const path2 = join6(dir, name);
47043
+ const path2 = join7(dir, name);
46451
47044
  await writeDesignFile(path2, schema);
46452
47045
  written.push(path2);
46453
47046
  }
@@ -46475,10 +47068,10 @@ SourceFiles = __decorate2([
46475
47068
 
46476
47069
  // packages/zaku/dist/lib/repositories/budget-ledger.js
46477
47070
  import { mkdir as mkdir4, readFile as readFile5, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
46478
- import { dirname as dirname3, join as join7 } from "node:path";
47071
+ import { dirname as dirname3, join as join8 } from "node:path";
46479
47072
  function ledgerPath(env, platform, home) {
46480
- const cache = env["XDG_CACHE_HOME"] ?? (platform === "darwin" ? join7(home, "Library", "Caches") : join7(home, ".cache"));
46481
- return join7(cache, "zaku", "ledger.json");
47073
+ const cache = env["XDG_CACHE_HOME"] ?? (platform === "darwin" ? join8(home, "Library", "Caches") : join8(home, ".cache"));
47074
+ return join8(cache, "zaku", "ledger.json");
46482
47075
  }
46483
47076
  function today(now) {
46484
47077
  return now.toISOString().slice(0, 10);
@@ -46547,6 +47140,31 @@ var pairingSchema = external_exports.object({
46547
47140
  }).strict();
46548
47141
  var pairingsFileSchema = external_exports.object({ pairings: external_exports.array(pairingSchema.extend({ hash: external_exports.string() }).strict()) }).strict();
46549
47142
 
47143
+ // packages/zaku/dist/lib/domain/print-graph.js
47144
+ function printGraph(config2, snapshots) {
47145
+ const graph = designGraph(config2);
47146
+ const lines = ["targets"];
47147
+ for (const target of config2.targets) {
47148
+ const files2 = [
47149
+ ...graph.librariesFor(target).map((library) => `${library.key} ${library.layer}`),
47150
+ ...graph.kitsFor(target).map((kit) => `${kit.key} ${kit.family} kit`)
47151
+ ];
47152
+ const branch = graph.branchOf(target);
47153
+ const product = graph.products.find((file2) => file2.family === target.family)?.key;
47154
+ lines.push(` ${target.name} (${target.id}, ${branch ? `branch ${branch}` : "no branch"}, ${product ? `product ${product}` : "no product"}): ${files2.join(", ") || "nothing"}`);
47155
+ }
47156
+ lines.push("files");
47157
+ const extendedBy = (key) => graph.libraries.filter((library) => library.extends.includes(key)).map((library) => library.key).join(", ") || "nothing";
47158
+ const saved = (key, present) => present ? "" : ` (no snapshot; run zaku library save --key ${key})`;
47159
+ for (const library of graph.libraries) {
47160
+ lines.push(` ${library.key} ${library.layer} [${library.branches.join(", ")}]: extends ${library.extends.join(", ") || "nothing"}; extended by ${extendedBy(library.key)}${saved(library.key, snapshots.libraries[library.key] !== void 0)}`);
47161
+ }
47162
+ for (const kit of graph.kits) {
47163
+ lines.push(` ${kit.key} ${kit.family} kit: extended by ${extendedBy(kit.key)}${saved(kit.key, snapshots.kits[kit.key] !== void 0)}`);
47164
+ }
47165
+ return lines;
47166
+ }
47167
+
46550
47168
  // packages/zaku/dist/lib/adapters/figma-rest.js
46551
47169
  var FigmaRateLimited = class extends Error {
46552
47170
  url;
@@ -46591,26 +47209,30 @@ function createFigmaRest(options) {
46591
47209
  throw new FigmaRateLimited(url2);
46592
47210
  }
46593
47211
  const chunks = (ids) => Array.from({ length: Math.ceil(ids.length / BATCH) }, (_, index) => ids.slice(index * BATCH, (index + 1) * BATCH));
47212
+ async function components(fileKey3) {
47213
+ const listed = await call(`${BASE}/files/${fileKey3}/components`);
47214
+ return listed.meta.components;
47215
+ }
46594
47216
  return {
46595
- file: (fileKey2, depth) => call(`${BASE}/files/${fileKey2}?depth=${depth}`),
46596
- async nodes(fileKey2, ids) {
47217
+ file: (fileKey3, depth) => call(`${BASE}/files/${fileKey3}?depth=${depth}`),
47218
+ async nodes(fileKey3, ids) {
46597
47219
  const merged = { version: "", nodes: {} };
46598
47220
  for (const chunk of chunks(ids)) {
46599
- const part = await call(`${BASE}/files/${fileKey2}/nodes?ids=${encodeURIComponent(chunk.join(","))}`);
47221
+ const part = await call(`${BASE}/files/${fileKey3}/nodes?ids=${encodeURIComponent(chunk.join(","))}`);
46600
47222
  merged.version = part.version;
46601
47223
  Object.assign(merged.nodes, part.nodes);
46602
47224
  }
46603
47225
  return merged;
46604
47226
  },
46605
- async published(fileKey2) {
46606
- const sets = await call(`${BASE}/files/${fileKey2}/component_sets`);
46607
- const components = await call(`${BASE}/files/${fileKey2}/components`);
46608
- return { sets: sets.meta.component_sets, components: components.meta.components };
47227
+ async published(fileKey3) {
47228
+ const sets = await call(`${BASE}/files/${fileKey3}/component_sets`);
47229
+ return { sets: sets.meta.component_sets, components: await components(fileKey3) };
46609
47230
  },
46610
- async images(fileKey2, ids) {
47231
+ components,
47232
+ async images(fileKey3, ids) {
46611
47233
  const merged = {};
46612
47234
  for (const chunk of chunks(ids)) {
46613
- const part = await call(`${BASE}/images/${fileKey2}?ids=${encodeURIComponent(chunk.join(","))}&format=png&scale=1`);
47235
+ const part = await call(`${BASE}/images/${fileKey3}?ids=${encodeURIComponent(chunk.join(","))}&format=png&scale=1`);
46614
47236
  Object.assign(merged, part.images);
46615
47237
  }
46616
47238
  return merged;
@@ -46620,17 +47242,40 @@ function createFigmaRest(options) {
46620
47242
 
46621
47243
  // packages/zaku/dist/lib/adapters/figma-variables-script.js
46622
47244
  async function exportVariables(figma, options) {
46623
- const collections = await figma.variables.getLocalVariableCollectionsAsync();
47245
+ const collections = [...await figma.variables.getLocalVariableCollectionsAsync()];
47246
+ if (!collections.some((collection) => collection.name === "semantic")) {
47247
+ const available = await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync();
47248
+ const bases = [
47249
+ ...new Set(available.filter((entry) => entry.name === "semantic").map((entry) => entry.libraryName))
47250
+ ];
47251
+ if (bases.length === 0)
47252
+ throw new Error("the file has no semantic collection and no enabled library has one");
47253
+ if (bases.length > 1)
47254
+ throw new Error(`several enabled libraries hold a semantic collection: ${bases.join(", ")}; enable only the one this library extends`);
47255
+ const imported = /* @__PURE__ */ new Map();
47256
+ for (const entry of available.filter((candidate) => candidate.libraryName === bases[0])) {
47257
+ for (const listed of await figma.teamLibrary.getVariablesInLibraryCollectionAsync(entry.key)) {
47258
+ const variable = await figma.variables.importVariableByKeyAsync(listed.key);
47259
+ if (!imported.has(variable.variableCollectionId))
47260
+ imported.set(variable.variableCollectionId, await figma.variables.getVariableCollectionByIdAsync(variable.variableCollectionId));
47261
+ }
47262
+ }
47263
+ collections.push(...[...imported.values()].filter(Boolean));
47264
+ }
46624
47265
  const semantic = collections.find((collection) => collection.name === "semantic");
46625
47266
  if (!semantic)
46626
47267
  throw new Error("the library has no semantic variable collection");
46627
47268
  const hex3 = (value) => Math.round(Math.min(1, Math.max(0, value)) * 255).toString(16).padStart(2, "0");
46628
47269
  const variables = {};
47270
+ const variableKeys = {};
46629
47271
  for (const collection of collections) {
46630
47272
  for (const id2 of collection.variableIds) {
46631
47273
  const variable = await figma.variables.getVariableByIdAsync(id2);
46632
- if (variable)
46633
- variables[String(id2).replace(/^VariableID:/, "")] = variable.name;
47274
+ if (!variable)
47275
+ continue;
47276
+ const short = String(id2).replace(/^VariableID:/, "");
47277
+ variables[short] = variable.name;
47278
+ variableKeys[short] = { key: variable.key, local: variable.remote !== true };
46634
47279
  }
46635
47280
  }
46636
47281
  const holder = figma.createFrame();
@@ -46652,6 +47297,8 @@ async function exportVariables(figma, options) {
46652
47297
  const values = {};
46653
47298
  for (const id2 of semantic.variableIds) {
46654
47299
  const variable = await figma.variables.getVariableByIdAsync(id2);
47300
+ if (!variable)
47301
+ continue;
46655
47302
  const resolved = variable.resolveForConsumer(holder).value;
46656
47303
  values[variable.name] = typeof resolved === "object" && resolved !== null ? `#${hex3(resolved.r)}${hex3(resolved.g)}${hex3(resolved.b)}${hex3(resolved.a ?? 1)}` : resolved;
46657
47304
  }
@@ -46671,21 +47318,31 @@ async function exportVariables(figma, options) {
46671
47318
  for (const node2 of page.findAllWithCriteria({ types: ["COMPONENT_SET", "COMPONENT"] })) {
46672
47319
  if (node2.type === "COMPONENT" && node2.parent?.type === "COMPONENT_SET")
46673
47320
  continue;
47321
+ let view = node2;
47322
+ while (view.parent && view.parent.type !== "PAGE")
47323
+ view = view.parent;
46674
47324
  components.push({
46675
47325
  id: node2.id,
46676
47326
  key: node2.key,
46677
47327
  name: node2.name,
46678
47328
  page: page.name,
47329
+ ...view !== node2 && view.parent ? { frame: view.name } : {},
46679
47330
  description: node2.description ?? "",
46680
47331
  set: node2.type === "COMPONENT_SET"
46681
47332
  });
46682
47333
  }
46683
47334
  }
46684
47335
  }
47336
+ const effectStyles = (await figma.getLocalEffectStylesAsync()).map((style) => ({
47337
+ key: style.key,
47338
+ name: style.name
47339
+ }));
46685
47340
  return {
46686
47341
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
46687
47342
  variables,
47343
+ variableKeys,
46688
47344
  textStyles,
47345
+ effectStyles,
46689
47346
  tokens,
46690
47347
  ...options.local ? { components } : {}
46691
47348
  };
@@ -47277,15 +47934,15 @@ async function startPluginBridge(options = {}) {
47277
47934
  });
47278
47935
  });
47279
47936
  });
47280
- function request(fileKey2, message, timeoutMs) {
47937
+ function request(fileKey3, message, timeoutMs) {
47281
47938
  if (port === null)
47282
47939
  return Promise.resolve({
47283
47940
  ok: false,
47284
47941
  error: `no port from ${ports[0]} to ${ports[ports.length - 1]} was free, so zaku mcp could not listen for the plugin`
47285
47942
  });
47286
- const socket = holderOf(fileKey2);
47943
+ const socket = holderOf(fileKey3);
47287
47944
  if (!socket)
47288
- return Promise.resolve({ ok: false, error: `file ${fileKey2} is not open in Figma with the zaku plugin` });
47945
+ return Promise.resolve({ ok: false, error: `file ${fileKey3} is not open in Figma with the zaku plugin` });
47289
47946
  const id2 = `c${++next}`;
47290
47947
  return new Promise((resolve3) => {
47291
47948
  const timer = setTimeout(() => settle2(id2, { ok: false, error: `no answer from the zaku panel in ${timeoutMs / 1e3} s; ${PARTIAL}` }), timeoutMs);
@@ -47295,8 +47952,8 @@ async function startPluginBridge(options = {}) {
47295
47952
  }
47296
47953
  return {
47297
47954
  port,
47298
- run: (fileKey2, call, timeoutMs) => request(fileKey2, { type: "run", ...call }, timeoutMs),
47299
- screenshot: (fileKey2, call, timeoutMs) => request(fileKey2, { type: "screenshot", ...call }, timeoutMs),
47955
+ run: (fileKey3, call, timeoutMs) => request(fileKey3, { type: "run", ...call }, timeoutMs),
47956
+ screenshot: (fileKey3, call, timeoutMs) => request(fileKey3, { type: "screenshot", ...call }, timeoutMs),
47300
47957
  close: () => new Promise((done) => {
47301
47958
  if (!server || !bound)
47302
47959
  return done();
@@ -47307,12 +47964,66 @@ async function startPluginBridge(options = {}) {
47307
47964
  };
47308
47965
  }
47309
47966
 
47967
+ // packages/zaku/dist/lib/domain/checks/libraries.js
47968
+ var comboKey2 = (combo) => JSON.stringify(Object.entries(combo).sort(([a], [b]) => a.localeCompare(b)));
47969
+ function joinLibraries(snapshots) {
47970
+ if (snapshots.length === 1 && snapshots[0])
47971
+ return snapshots[0];
47972
+ const tokens = /* @__PURE__ */ new Map();
47973
+ for (const snapshot of snapshots) {
47974
+ for (const token of snapshot.tokens) {
47975
+ const key = comboKey2(token.combo);
47976
+ tokens.set(key, { combo: token.combo, values: { ...tokens.get(key)?.values, ...token.values } });
47977
+ }
47978
+ }
47979
+ return {
47980
+ source: snapshots.map((snapshot) => snapshot.source).join(","),
47981
+ version: snapshots.map((snapshot) => snapshot.version).join(","),
47982
+ exportedAt: snapshots.map((snapshot) => snapshot.exportedAt).sort().at(-1) ?? "",
47983
+ components: snapshots.flatMap((snapshot) => snapshot.components),
47984
+ textStyles: snapshots.flatMap((snapshot) => snapshot.textStyles),
47985
+ tokens: [...tokens.values()]
47986
+ };
47987
+ }
47988
+ function missingSnapshots(graph, keys) {
47989
+ const paths = keys.map((key) => graph.legacy ? "library/index.json" : `library/${key}/index.json`);
47990
+ return `${paths.join(", ")} ${paths.length === 1 ? "is" : "are"} missing`;
47991
+ }
47992
+ function checkLibraries(input2) {
47993
+ const graph = designGraph(input2.config);
47994
+ const { libraries, missing } = snapshotsFor(input2);
47995
+ const kits = new Set(graph.kits.map((kit) => kit.key));
47996
+ const absent = missing.filter((key) => !kits.has(key));
47997
+ if (absent.length > 0)
47998
+ return { notRun: missingSnapshots(graph, absent) };
47999
+ if (libraries.length === 0)
48000
+ return { notRun: "library/index.json is missing" };
48001
+ const all = joinLibraries(libraries);
48002
+ const joined = /* @__PURE__ */ new Map();
48003
+ return {
48004
+ all,
48005
+ forTarget(targetId) {
48006
+ const target = input2.config.targets.find((candidate) => candidate.id === targetId);
48007
+ if (!target)
48008
+ return all;
48009
+ const cached2 = joined.get(targetId);
48010
+ if (cached2)
48011
+ return cached2;
48012
+ const own2 = joinLibraries(snapshotsFor(input2, target).libraries);
48013
+ joined.set(targetId, own2);
48014
+ return own2;
48015
+ }
48016
+ };
48017
+ }
48018
+
47310
48019
  // packages/zaku/dist/lib/domain/checks/binding.js
47311
48020
  var PAINTS = ["fill", "stroke", "textColor", "glyph"];
47312
48021
  var SEMANTIC = /^(color|mode|radius)\//;
47313
- var binding = ({ library }) => {
47314
- if (!library)
47315
- return { notRun: "library.json is missing" };
48022
+ var binding = (input2) => {
48023
+ const libraries = checkLibraries(input2);
48024
+ if ("notRun" in libraries)
48025
+ return libraries;
48026
+ const library = libraries.all;
47316
48027
  const findings = [];
47317
48028
  for (const component2 of library.components) {
47318
48029
  for (const variant of component2.variants) {
@@ -47678,8 +48389,9 @@ var frameCheck = ({ config: config2, outlines }) => {
47678
48389
  }
47679
48390
  return { findings };
47680
48391
  };
47681
- var systemBars = ({ config: config2, outlines }) => {
48392
+ var systemBars = ({ config: config2, outlines, kits }) => {
47682
48393
  const families2 = new Map(config2.targets.map((target) => [target.id, target.family]));
48394
+ const graph = designGraph(config2);
47683
48395
  const bars = { ...DEFAULT_SYSTEM_BARS, ...config2.systemBars };
47684
48396
  const findings = [];
47685
48397
  for (const outline of outlines) {
@@ -47687,7 +48399,10 @@ var systemBars = ({ config: config2, outlines }) => {
47687
48399
  const family = families2.get(frame.target);
47688
48400
  if (!family)
47689
48401
  continue;
47690
- const held = new Set(frame.instances.map((instance) => instance.component));
48402
+ const own2 = graph.kits.filter((kit) => kit.family === family && kits[kit.key]);
48403
+ const kitKeys = new Set(own2.flatMap((kit) => kits[kit.key]?.components.map((component2) => component2.key) ?? []));
48404
+ const from = own2.length > 0 ? ` from ${own2.map((kit) => kit.key).join(" or ")}` : "";
48405
+ const held = new Set(frame.instances.filter((instance) => own2.length === 0 || kitKeys.has(instance.componentKey)).map((instance) => instance.component));
47691
48406
  for (const names of bars[family]) {
47692
48407
  if (!names.some((name) => held.has(name))) {
47693
48408
  findings.push({
@@ -47696,7 +48411,7 @@ var systemBars = ({ config: config2, outlines }) => {
47696
48411
  screen: outline.screen,
47697
48412
  frame: frame.name,
47698
48413
  nodeId: frame.nodeId,
47699
- message: `no ${names.join(" or ")} instance on a ${family} frame`
48414
+ message: `no ${names.join(" or ")} instance${from} on a ${family} frame`
47700
48415
  });
47701
48416
  }
47702
48417
  }
@@ -47710,6 +48425,18 @@ var placement = ({ maps, outlines }) => {
47710
48425
  for (const outline of outlines) {
47711
48426
  const title = titles.get(`${outline.feature}/${outline.screen}`);
47712
48427
  for (const frame of outline.frames) {
48428
+ if (frame.page !== void 0) {
48429
+ if (title !== void 0 && frame.page !== title)
48430
+ findings.push({
48431
+ check: "placement",
48432
+ feature: outline.feature,
48433
+ screen: outline.screen,
48434
+ frame: frame.name,
48435
+ nodeId: frame.nodeId,
48436
+ message: `sits on the page ${frame.page}, and its screen is drawn on the page ${title}`
48437
+ });
48438
+ continue;
48439
+ }
47713
48440
  const expected = [title, frame.state];
47714
48441
  if (frame.containers.length !== 2 || frame.containers[0] !== expected[0] || frame.containers[1] !== expected[1]) {
47715
48442
  findings.push({
@@ -47743,8 +48470,19 @@ var naming = ({ outlines }) => {
47743
48470
  }
47744
48471
  return { findings };
47745
48472
  };
47746
- var component = ({ outlines }) => {
48473
+ var component = ({ config: config2, outlines, productComponents }) => {
47747
48474
  const findings = [];
48475
+ const graph = designGraph(config2);
48476
+ for (const drawn of productComponents) {
48477
+ const families2 = graph.products.filter((product) => product.key === drawn.source).map((product) => product.family);
48478
+ const branches = new Set(families2.flatMap((family) => graph.targetsOf(family)).flatMap((target) => graph.branchOf(target) ?? []));
48479
+ const homes = graph.libraries.filter((library) => library.layer === "components" && library.branches.some((branch) => branches.has(branch))).map((library) => library.key);
48480
+ findings.push({
48481
+ check: "component",
48482
+ nodeId: drawn.nodeId,
48483
+ message: `${drawn.name} is a component on the product page ${drawn.page}; a product file draws screens only, so draw it in ${homes.length > 0 ? homes.join(" or ") : `a Components library of the ${[...branches].join(" or ")} branch`}`
48484
+ });
48485
+ }
47748
48486
  for (const outline of outlines) {
47749
48487
  const seen = /* @__PURE__ */ new Map();
47750
48488
  for (const frame of outline.frames) {
@@ -47776,13 +48514,16 @@ var component = ({ outlines }) => {
47776
48514
  };
47777
48515
 
47778
48516
  // packages/zaku/dist/lib/domain/checks/font.js
47779
- var font = ({ outlines, library }) => {
47780
- if (!library)
47781
- return { notRun: "library.json is missing" };
47782
- const keys = new Set(library.textStyles.map((style) => style.key));
48517
+ var font = (input2) => {
48518
+ const libraries = checkLibraries(input2);
48519
+ if ("notRun" in libraries)
48520
+ return libraries;
48521
+ const keys = /* @__PURE__ */ new Map();
47783
48522
  const findings = [];
47784
- for (const outline of outlines) {
48523
+ for (const outline of input2.outlines) {
47785
48524
  for (const frame of outline.frames) {
48525
+ const styles = keys.get(frame.target) ?? new Set(libraries.forTarget(frame.target).textStyles.map((style) => style.key));
48526
+ keys.set(frame.target, styles);
47786
48527
  const where = { feature: outline.feature, screen: outline.screen, frame: frame.name };
47787
48528
  for (const text of frame.texts) {
47788
48529
  if (text.styleKey === null) {
@@ -47792,7 +48533,7 @@ var font = ({ outlines, library }) => {
47792
48533
  nodeId: text.nodeId,
47793
48534
  message: "text outside a component uses no library text style"
47794
48535
  });
47795
- } else if (!keys.has(text.styleKey)) {
48536
+ } else if (!styles.has(text.styleKey)) {
47796
48537
  findings.push({
47797
48538
  check: "font",
47798
48539
  ...where,
@@ -47807,8 +48548,9 @@ var font = ({ outlines, library }) => {
47807
48548
  };
47808
48549
 
47809
48550
  // packages/zaku/dist/lib/domain/checks/freshness.js
47810
- var freshness = ({ maps, outlines, library }) => {
48551
+ var freshness = ({ config: config2, maps, outlines, flows, libraries }) => {
47811
48552
  const digests = new Map(maps.map((loaded) => [loaded.map.feature, loaded.digest]));
48553
+ const graph = designGraph(config2);
47812
48554
  const findings = [];
47813
48555
  for (const outline of outlines) {
47814
48556
  const where = { feature: outline.feature, screen: outline.screen };
@@ -47820,11 +48562,35 @@ var freshness = ({ maps, outlines, library }) => {
47820
48562
  message: "the map changed after this outline was read; run zaku outline"
47821
48563
  });
47822
48564
  }
47823
- if (library && outline.libraryVersion !== library.version) {
48565
+ for (const [key, library] of Object.entries(libraries)) {
48566
+ const read = outline.libraryVersions[key] ?? (graph.legacy ? outline.libraryVersions[LEGACY_LIBRARY_VERSION] : void 0);
48567
+ if (read === library.version)
48568
+ continue;
47824
48569
  findings.push({
47825
48570
  check: "freshness",
47826
48571
  ...where,
47827
- message: "the library changed after this outline was read; run zaku outline"
48572
+ message: graph.legacy ? "the library changed after this outline was read; run zaku outline" : `library ${key} changed after this outline was read; run zaku outline`
48573
+ });
48574
+ }
48575
+ }
48576
+ const drawn = new Map(outlines.flatMap((outline) => outline.frames.map((frame) => [`${outline.feature}/${outline.screen}
48577
+ ${frame.name}`, frame])));
48578
+ for (const page of flows) {
48579
+ for (const copy2 of page.frames) {
48580
+ if (copy2.screen === null)
48581
+ continue;
48582
+ const source = drawn.get(`${copy2.screen}
48583
+ ${copy2.name}`);
48584
+ if (source?.digest === void 0 || source.digest === copy2.digest)
48585
+ continue;
48586
+ const [feature = "", screen = ""] = copy2.screen.split("/");
48587
+ findings.push({
48588
+ check: "freshness",
48589
+ feature,
48590
+ screen,
48591
+ frame: copy2.name,
48592
+ nodeId: copy2.nodeId,
48593
+ message: `the copy on the flow page ${page.page} differs from the frame it copies; copy it again`
47828
48594
  });
47829
48595
  }
47830
48596
  }
@@ -47832,13 +48598,43 @@ var freshness = ({ maps, outlines, library }) => {
47832
48598
  };
47833
48599
 
47834
48600
  // packages/zaku/dist/lib/domain/checks/library.js
47835
- var libraryCheck = ({ config: config2, maps, outlines, library }) => {
47836
- if (!library)
47837
- return { notRun: "library.json is missing" };
48601
+ function frameFindings(input2) {
48602
+ const libraries = checkLibraries(input2);
48603
+ if ("notRun" in libraries)
48604
+ return libraries;
48605
+ const { config: config2, maps, outlines } = input2;
48606
+ const library = libraries.all;
47838
48607
  const bars = systemBarNames(config2);
47839
48608
  const owned = outlines.flatMap((outline) => outline.frames.flatMap((frame) => frame.instances.filter((instance) => instance.local).map((instance) => instance.component)));
47840
48609
  const names = /* @__PURE__ */ new Set([...library.components.map((component2) => component2.name), ...owned]);
47841
48610
  const keys = new Set(library.components.flatMap((component2) => component2.variants.map((variant) => variant.key)));
48611
+ const graph = designGraph(config2);
48612
+ const owners = /* @__PURE__ */ new Map();
48613
+ for (const [key, snapshot] of Object.entries(input2.libraries)) {
48614
+ const holds = graph.libraries.find((candidate) => candidate.key === key)?.branches ?? [];
48615
+ for (const component2 of snapshot.components) {
48616
+ const branches = component2.branch ? [component2.branch] : holds;
48617
+ for (const variant of component2.variants)
48618
+ owners.set(variant.key, `${key}, a ${branches.join(" and ")} library`);
48619
+ }
48620
+ }
48621
+ for (const [key, kit] of Object.entries(input2.kits)) {
48622
+ for (const component2 of kit.components)
48623
+ owners.set(component2.key, `${key}, an ${kit.family} kit`);
48624
+ }
48625
+ const allowed2 = /* @__PURE__ */ new Map();
48626
+ const allowedFor = (target) => {
48627
+ const cached2 = allowed2.get(target.id);
48628
+ if (cached2)
48629
+ return cached2;
48630
+ const { libraries: own2, kits } = snapshotsFor(input2, target);
48631
+ const set2 = /* @__PURE__ */ new Set([
48632
+ ...own2.flatMap((snapshot) => snapshot.components.flatMap((component2) => component2.variants.map((v) => v.key))),
48633
+ ...kits.flatMap((kit) => kit.components.map((component2) => component2.key))
48634
+ ]);
48635
+ allowed2.set(target.id, set2);
48636
+ return set2;
48637
+ };
47842
48638
  const findings = [];
47843
48639
  for (const { map: map2 } of maps) {
47844
48640
  for (const [id2, screen] of Object.entries(map2.screens)) {
@@ -47858,8 +48654,22 @@ var libraryCheck = ({ config: config2, maps, outlines, library }) => {
47858
48654
  for (const outline of outlines) {
47859
48655
  for (const frame of outline.frames) {
47860
48656
  const where = { feature: outline.feature, screen: outline.screen, frame: frame.name };
48657
+ const target = config2.targets.find((candidate) => candidate.id === frame.target);
47861
48658
  for (const instance of frame.instances) {
47862
- if (!instance.local && !bars.has(instance.component) && !keys.has(instance.componentKey)) {
48659
+ if (instance.local)
48660
+ continue;
48661
+ const owner = owners.get(instance.componentKey);
48662
+ if (target && owner && !allowedFor(target).has(instance.componentKey)) {
48663
+ const draws = owner.endsWith(" kit") ? `${target.family} kits` : graph.branchOf(target) ?? "no branch";
48664
+ findings.push({
48665
+ check: "library",
48666
+ ...where,
48667
+ nodeId: instance.nodeId,
48668
+ message: `an instance of ${instance.component} comes from ${owner}; a ${target.name} frame draws from ${draws}`
48669
+ });
48670
+ continue;
48671
+ }
48672
+ if (!bars.has(instance.component) && !keys.has(instance.componentKey) && !owner) {
47863
48673
  findings.push({
47864
48674
  check: "library",
47865
48675
  ...where,
@@ -47879,6 +48689,82 @@ var libraryCheck = ({ config: config2, maps, outlines, library }) => {
47879
48689
  }
47880
48690
  }
47881
48691
  return { findings };
48692
+ }
48693
+ function referable(snapshot) {
48694
+ return {
48695
+ variable: Object.values(snapshot.variableKeys ?? {}).flatMap((variable) => variable.local ? [variable.key] : []),
48696
+ "text style": snapshot.textStyles.map((style) => style.key),
48697
+ "effect style": (snapshot.effectStyles ?? []).map((style) => style.key),
48698
+ instance: snapshot.components.flatMap((component2) => [
48699
+ component2.key,
48700
+ ...component2.variants.map((variant) => variant.key)
48701
+ ])
48702
+ };
48703
+ }
48704
+ function referenceFindings(input2) {
48705
+ const graph = designGraph(input2.config);
48706
+ const findings = [];
48707
+ for (const [key, snapshot] of Object.entries(input2.libraries)) {
48708
+ if (!snapshot.variableKeys)
48709
+ continue;
48710
+ const bases = graph.closure(key).slice(1);
48711
+ const missing = bases.filter((base) => !input2.libraries[base] && !input2.kits[base]);
48712
+ if (missing.length > 0) {
48713
+ const paths = missing.map((base) => graph.kits.some((kit) => kit.key === base) ? `library/${base}/kit.json` : `library/${base}/index.json`);
48714
+ return { notRun: `${paths.join(", ")} ${paths.length === 1 ? "is" : "are"} missing` };
48715
+ }
48716
+ const allowed2 = {
48717
+ variable: /* @__PURE__ */ new Set(),
48718
+ "text style": /* @__PURE__ */ new Set(),
48719
+ "effect style": /* @__PURE__ */ new Set(),
48720
+ instance: new Set(snapshot.localComponents ?? [])
48721
+ };
48722
+ for (const from of [key, ...bases]) {
48723
+ const library = input2.libraries[from];
48724
+ if (library)
48725
+ for (const [kind, keys] of Object.entries(referable(library)))
48726
+ for (const ref of keys)
48727
+ allowed2[kind].add(ref);
48728
+ for (const component2 of input2.kits[from]?.components ?? [])
48729
+ allowed2.instance.add(component2.key);
48730
+ }
48731
+ const extended2 = bases.length > 0 ? `from ${bases.join(", ")}` : "from a library it extends, and it extends none";
48732
+ for (const component2 of snapshot.components) {
48733
+ for (const variant of component2.variants) {
48734
+ for (const item of variant.modes[0]?.items ?? []) {
48735
+ const refs = [
48736
+ ...(item.variableIds ?? []).map((id2) => [
48737
+ "variable",
48738
+ snapshot.variableKeys?.[id2]?.key ?? id2
48739
+ ]),
48740
+ ...item.textStyleKey ? [["text style", item.textStyleKey]] : [],
48741
+ ...item.effectStyleKey ? [["effect style", item.effectStyleKey]] : [],
48742
+ ...item.componentKey ? [["instance", item.componentKey]] : []
48743
+ ];
48744
+ for (const [kind, ref] of refs) {
48745
+ if (allowed2[kind].has(ref))
48746
+ continue;
48747
+ findings.push({
48748
+ check: "library",
48749
+ nodeId: item.nodeId,
48750
+ field: "references",
48751
+ message: `${key}: ${component2.name} ${variant.name} ${item.path || "(root)"}: ${kind} ${ref} is neither the library's own nor ${extended2}`
48752
+ });
48753
+ }
48754
+ }
48755
+ }
48756
+ }
48757
+ }
48758
+ return { findings };
48759
+ }
48760
+ var libraryCheck = (input2) => {
48761
+ const frames = frameFindings(input2);
48762
+ if ("notRun" in frames)
48763
+ return frames;
48764
+ const references = referenceFindings(input2);
48765
+ if ("notRun" in references)
48766
+ return references;
48767
+ return { findings: [...frames.findings, ...references.findings] };
47882
48768
  };
47883
48769
 
47884
48770
  // packages/zaku/dist/lib/domain/checks/navigation.js
@@ -47949,7 +48835,11 @@ var ALLOWED_OVERRIDES = /* @__PURE__ */ new Set([
47949
48835
  "name",
47950
48836
  "componentProperties",
47951
48837
  "componentPropertyReferences",
47952
- "exposedInstances"
48838
+ "exposedInstances",
48839
+ "reactions",
48840
+ "transitionNodeID",
48841
+ "transitionDuration",
48842
+ "transitionEasing"
47953
48843
  ]);
47954
48844
  var SIZING_MODES = /* @__PURE__ */ new Set(["primaryAxisSizingMode", "counterAxisSizingMode"]);
47955
48845
  function componentSized(field, size2, main2) {
@@ -47963,9 +48853,12 @@ function stretches(sizing) {
47963
48853
  function insideSwap(instance, nodeId) {
47964
48854
  return instance.nested.some((nested) => nodeId.startsWith(`${nested.nodeId};`) && instance.swaps.includes(nested.componentKey));
47965
48855
  }
47966
- var overrides = ({ outlines, library }) => {
47967
- if (!library)
47968
- return { notRun: "library.json is missing" };
48856
+ var overrides = (input2) => {
48857
+ const libraries = checkLibraries(input2);
48858
+ if ("notRun" in libraries)
48859
+ return libraries;
48860
+ const { outlines } = input2;
48861
+ const library = libraries.all;
47969
48862
  const nestedOf = /* @__PURE__ */ new Map();
47970
48863
  const componentOf = /* @__PURE__ */ new Map();
47971
48864
  for (const component2 of library.components) {
@@ -48026,18 +48919,58 @@ var overrides = ({ outlines, library }) => {
48026
48919
  return { findings };
48027
48920
  };
48028
48921
 
48922
+ // packages/zaku/dist/lib/domain/flows.js
48923
+ function flowsOf(maps) {
48924
+ const index = indexScreens(maps);
48925
+ const flows = [];
48926
+ for (const root of index.values()) {
48927
+ if (!root.screen.root)
48928
+ continue;
48929
+ const screens = [root];
48930
+ for (let at = 0; at < screens.length; at += 1) {
48931
+ const from = screens[at];
48932
+ if (!from)
48933
+ continue;
48934
+ for (const exit of from.screen.exits) {
48935
+ const next = index.get(qualify(exit.to, from.feature));
48936
+ if (next && !screens.includes(next))
48937
+ screens.push(next);
48938
+ }
48939
+ }
48940
+ flows.push({ root, screens });
48941
+ }
48942
+ return flows;
48943
+ }
48944
+
48029
48945
  // packages/zaku/dist/lib/domain/checks/prototype.js
48030
- var prototype = ({ maps, outlines }) => {
48031
- if (outlines.length === 0)
48032
- return { findings: [] };
48946
+ function legacyScreens(input2) {
48947
+ if (input2.flows.length === 0)
48948
+ return input2.outlines;
48949
+ const byScreen = /* @__PURE__ */ new Map();
48950
+ for (const page of input2.flows) {
48951
+ for (const frame of page.frames) {
48952
+ if (frame.screen === null)
48953
+ continue;
48954
+ const [feature = "", screen = ""] = frame.screen.split("/");
48955
+ const entry = byScreen.get(frame.screen) ?? { feature, screen, frames: [] };
48956
+ entry.frames.push(frame);
48957
+ byScreen.set(frame.screen, entry);
48958
+ }
48959
+ }
48960
+ return [...byScreen.values()];
48961
+ }
48962
+ function legacyFindings(input2) {
48963
+ const screens = legacyScreens(input2);
48964
+ if (screens.length === 0)
48965
+ return [];
48033
48966
  const screenOfFrame = /* @__PURE__ */ new Map();
48034
- const outlineOf = new Map(outlines.map((outline) => [`${outline.feature}/${outline.screen}`, outline]));
48035
- for (const outline of outlines) {
48967
+ const outlineOf = new Map(screens.map((outline) => [`${outline.feature}/${outline.screen}`, outline]));
48968
+ for (const outline of screens) {
48036
48969
  for (const frame of outline.frames)
48037
48970
  screenOfFrame.set(frame.nodeId, `${outline.feature}/${outline.screen}`);
48038
48971
  }
48039
48972
  const findings = [];
48040
- for (const { map: map2 } of maps) {
48973
+ for (const { map: map2 } of input2.maps) {
48041
48974
  for (const [id2, screen] of Object.entries(map2.screens)) {
48042
48975
  const outline = outlineOf.get(`${map2.feature}/${id2}`);
48043
48976
  if (!outline)
@@ -48074,8 +49007,64 @@ var prototype = ({ maps, outlines }) => {
48074
49007
  }
48075
49008
  }
48076
49009
  }
48077
- return { findings };
48078
- };
49010
+ return findings;
49011
+ }
49012
+ function flowFindings(input2) {
49013
+ const prototypes = new Set(designGraph(input2.config).prototypes.map((file2) => file2.key));
49014
+ const findings = [];
49015
+ for (const flow of flowsOf(input2.maps)) {
49016
+ const { root } = flow;
49017
+ const title = root.screen.title;
49018
+ const keyOf = (ref) => `${ref.feature}/${ref.id}`;
49019
+ const pages = input2.flows.filter((page) => page.page === title && prototypes.has(page.source));
49020
+ if (pages.length === 0) {
49021
+ findings.push({
49022
+ check: "prototype",
49023
+ feature: root.feature,
49024
+ screen: root.id,
49025
+ message: `no flow page is named ${title}`
49026
+ });
49027
+ continue;
49028
+ }
49029
+ const inFlow = new Set(flow.screens.map(keyOf));
49030
+ for (const page of pages) {
49031
+ const screenOf = new Map(page.frames.map((frame) => [frame.nodeId, frame.screen]));
49032
+ const first = root.screen.states[0];
49033
+ if (!page.frames.some((frame) => frame.screen === keyOf(root) && frame.state === first && frame.start))
49034
+ findings.push({
49035
+ check: "prototype",
49036
+ feature: root.feature,
49037
+ screen: root.id,
49038
+ message: `the flow page ${title} does not start on ${title}'s first state`
49039
+ });
49040
+ for (const ref of flow.screens) {
49041
+ const where = { check: "prototype", feature: ref.feature, screen: ref.id };
49042
+ const copies = page.frames.filter((frame) => frame.screen === keyOf(ref));
49043
+ for (const exit of ref.screen.exits) {
49044
+ const target = qualify(exit.to, ref.feature);
49045
+ if (!inFlow.has(target))
49046
+ continue;
49047
+ const connected = copies.some((frame) => frame.links.some((link) => link.to !== "back" && screenOf.get(link.to.nodeId) === target));
49048
+ if (!connected)
49049
+ findings.push({
49050
+ ...where,
49051
+ field: "exits",
49052
+ message: `on the flow page ${title}, the exit to ${exit.to} (${exit.via}) has no connection`
49053
+ });
49054
+ }
49055
+ if (ref !== root && copies.length > 0 && !copies.some((frame) => frame.links.some((link) => link.to === "back")))
49056
+ findings.push({
49057
+ ...where,
49058
+ message: `on the flow page ${title}, a screen that is not the root has no Back action`
49059
+ });
49060
+ }
49061
+ }
49062
+ }
49063
+ return findings;
49064
+ }
49065
+ var prototype = (input2) => ({
49066
+ findings: designGraph(input2.config).prototypes.length > 0 ? flowFindings(input2) : legacyFindings(input2)
49067
+ });
48079
49068
 
48080
49069
  // packages/zaku/dist/lib/domain/checks/recipe.js
48081
49070
  function propsLabel(props) {
@@ -48155,9 +49144,14 @@ function comparePart(at, part) {
48155
49144
  });
48156
49145
  }
48157
49146
  }
48158
- var recipe = ({ config: config2, library, recipe: doc }) => {
48159
- if (!doc || !library)
48160
- return { notRun: "recipe.json or library.json is missing" };
49147
+ var recipe = (input2) => {
49148
+ const { config: config2, recipe: doc } = input2;
49149
+ const libraries = checkLibraries(input2);
49150
+ if ("notRun" in libraries)
49151
+ return doc ? libraries : { notRun: `recipe.json or ${libraries.notRun}` };
49152
+ if (!doc)
49153
+ return { notRun: "recipe.json is missing" };
49154
+ const library = libraries.all;
48161
49155
  const findings = [];
48162
49156
  for (const code of doc.components) {
48163
49157
  const component2 = library.components.find((candidate) => candidate.name === code.name);
@@ -48248,9 +49242,12 @@ var MIN_TARGET = {
48248
49242
  function controlsOf(frame) {
48249
49243
  return frame.instances.flatMap((instance) => [instance, ...instance.nested].flatMap((control) => control.interactive && control.bounds ? [{ nodeId: control.nodeId, componentKey: control.componentKey, bounds: control.bounds }] : []));
48250
49244
  }
48251
- var targetSize = ({ config: config2, outlines, library }) => {
48252
- if (!library)
48253
- return { notRun: "library.json is missing" };
49245
+ var targetSize = (input2) => {
49246
+ const libraries = checkLibraries(input2);
49247
+ if ("notRun" in libraries)
49248
+ return libraries;
49249
+ const { config: config2, outlines } = input2;
49250
+ const library = libraries.all;
48254
49251
  const presetHeight = /* @__PURE__ */ new Map();
48255
49252
  for (const component2 of library.components) {
48256
49253
  for (const variant of component2.variants) {
@@ -48332,9 +49329,14 @@ function rgbaOf(token) {
48332
49329
  const [r, g, b] = token.$value.components;
48333
49330
  return { r, g, b, a: token.$value.alpha };
48334
49331
  }
48335
- var tokensCheck = ({ config: config2, library, tokens }) => {
48336
- if (!library || !tokens)
48337
- return { notRun: "library.json or tokens.json is missing" };
49332
+ var tokensCheck = (input2) => {
49333
+ const { config: config2, tokens } = input2;
49334
+ const libraries = checkLibraries(input2);
49335
+ if ("notRun" in libraries)
49336
+ return libraries;
49337
+ if (!tokens)
49338
+ return { notRun: "tokens.json is missing" };
49339
+ const library = libraries.all;
48338
49340
  const sets = tokenSets(tokens);
48339
49341
  const findings = [];
48340
49342
  for (const [scheme, set2] of [
@@ -48638,13 +49640,25 @@ async function loadDesign(root) {
48638
49640
  message: `two screens share the title ${title}; a frame name could not tell them apart`
48639
49641
  });
48640
49642
  }
48641
- const { outlines, unmapped, errors: outlineErrors } = await loadOutlines(paths.outline);
49643
+ const { outlines, flows, unmapped, errors: outlineErrors } = await loadOutlines(paths.outline);
48642
49644
  findings.push(...outlineErrors.map(schemaFinding));
48643
- const library = await optional3(() => readLibrarySnapshot(paths), findings);
49645
+ const { libraries, kits, errors: snapshotErrors } = await readDesignSnapshots(paths, config2);
49646
+ findings.push(...snapshotErrors.map(schemaFinding));
48644
49647
  const tokens = await optional3(() => readOptionalDesignFile(paths.tokens, tokenDocumentSchema), findings);
48645
49648
  const recipe2 = await optional3(() => readOptionalDesignFile(paths.recipe, recipeDocumentSchema), findings);
48646
49649
  return {
48647
- input: { config: config2, maps, outlines, unmapped: unmapped?.frames ?? [], library, tokens, recipe: recipe2 },
49650
+ input: {
49651
+ config: config2,
49652
+ maps,
49653
+ outlines,
49654
+ flows,
49655
+ unmapped: unmapped?.frames ?? [],
49656
+ productComponents: unmapped?.components ?? [],
49657
+ libraries,
49658
+ kits,
49659
+ tokens,
49660
+ recipe: recipe2
49661
+ },
48648
49662
  findings
48649
49663
  };
48650
49664
  }
@@ -48679,6 +49693,7 @@ function refusalOf(error62) {
48679
49693
  }
48680
49694
  if (error62 instanceof FigmaFileKeyMissing) return { code: 3, line: error62.message };
48681
49695
  if (error62 instanceof FigmaTokenMissing) return { code: 3, line: error62.message };
49696
+ if (error62 instanceof LibraryKeyMissing) return { code: 3, line: error62.message };
48682
49697
  if (error62 instanceof RecipePageFailed) return { code: 2, line: `zaku recipe: ${error62.message}` };
48683
49698
  return null;
48684
49699
  }
@@ -58368,11 +59383,11 @@ var StdioServerTransport = class {
58368
59383
  // apps/zaku-cli/src/mcp/server.ts
58369
59384
  import { mkdir as mkdir5 } from "node:fs/promises";
58370
59385
  import { tmpdir } from "node:os";
58371
- import { join as join10 } from "node:path";
59386
+ import { join as join11 } from "node:path";
58372
59387
 
58373
59388
  // apps/zaku-cli/src/mcp/screenshots.ts
58374
59389
  import { readdir as readdir4, rm as rm3, stat } from "node:fs/promises";
58375
- import { join as join8 } from "node:path";
59390
+ import { join as join9 } from "node:path";
58376
59391
  var KEPT_MS = 24 * 60 * 60 * 1e3;
58377
59392
  async function pruneScreenshots(dir, now) {
58378
59393
  let names;
@@ -58382,7 +59397,7 @@ async function pruneScreenshots(dir, now) {
58382
59397
  return;
58383
59398
  }
58384
59399
  for (const name of names.filter((entry) => entry.endsWith(".png"))) {
58385
- const path2 = join8(dir, name);
59400
+ const path2 = join9(dir, name);
58386
59401
  const written = await stat(path2).then(
58387
59402
  (stats) => stats.mtimeMs,
58388
59403
  () => now
@@ -58393,8 +59408,8 @@ async function pruneScreenshots(dir, now) {
58393
59408
 
58394
59409
  // apps/zaku-cli/src/mcp/tools.ts
58395
59410
  import { writeFile as writeFile5 } from "node:fs/promises";
58396
- import { join as join9 } from "node:path";
58397
- var fileKey = external_exports.string().min(1).regex(/^[0-9a-zA-Z]{22,128}$/).describe(
59411
+ import { join as join10 } from "node:path";
59412
+ var fileKey2 = external_exports.string().min(1).regex(/^[0-9a-zA-Z]{22,128}$/).describe(
58398
59413
  "The key of the Figma file to use. If the URL is provided, extract the file key from the URL: https://figma.com/design/:fileKey/:fileName."
58399
59414
  );
58400
59415
  var FALLBACK = "zaku's fallback for Figma's MCP server: the same tool and input, run in the file through the zaku plugin, which the person has open in that file with its key given.";
@@ -58406,7 +59421,7 @@ function registerFigmaTools(server, bridge, options) {
58406
59421
  inputSchema: external_exports.object({
58407
59422
  code: external_exports.string().max(5e4).describe("JavaScript code to execute. Has access to the `figma` global (Figma Plugin API)"),
58408
59423
  description: external_exports.string().max(2e3).describe("A concise description of what the code aims to do"),
58409
- fileKey,
59424
+ fileKey: fileKey2,
58410
59425
  skillNames: external_exports.string().optional().describe("A comma-separated list of Figma skill names being followed, if any.")
58411
59426
  }).strict()
58412
59427
  },
@@ -58424,7 +59439,7 @@ function registerFigmaTools(server, bridge, options) {
58424
59439
  inputSchema: external_exports.object({
58425
59440
  contentsOnly: external_exports.boolean().optional().describe("When true, renders the node in isolation. Defaults to false."),
58426
59441
  enableBase64Response: external_exports.boolean().default(false).describe("When true, the response also includes the screenshot inline as a base64-encoded image entry."),
58427
- fileKey,
59442
+ fileKey: fileKey2,
58428
59443
  maxDimension: external_exports.number().int().max(65536).default(1024).describe("Maximum pixel size of the longer edge of the rendered screenshot. Defaults to 1024."),
58429
59444
  nodeId: external_exports.string().min(1).regex(/^\d+[:-]\d+$/).describe('The ID of the node in the Figma document, eg. "123:456" or "123-456".')
58430
59445
  }).strict()
@@ -58437,7 +59452,7 @@ function registerFigmaTools(server, bridge, options) {
58437
59452
  options.timeoutMs
58438
59453
  );
58439
59454
  if (!answer.ok) return { isError: true, content: [{ type: "text", text: answer.error }] };
58440
- const path2 = join9(options.imageDir, `${id2.replace(":", "-")}-${Date.now()}.png`);
59455
+ const path2 = join10(options.imageDir, `${id2.replace(":", "-")}-${Date.now()}.png`);
58441
59456
  await writeFile5(path2, Buffer.from(answer.png, "base64"));
58442
59457
  const text = JSON.stringify({
58443
59458
  path: path2,
@@ -58463,7 +59478,7 @@ async function serveMcp(log) {
58463
59478
  log(
58464
59479
  bridge.port === null ? "zaku mcp: no port from 7337 to 7346 was free; every call will say so" : `zaku mcp: listening for the zaku plugin on localhost:${bridge.port}`
58465
59480
  );
58466
- const imageDir = join10(tmpdir(), "zaku-screenshots");
59481
+ const imageDir = join11(tmpdir(), "zaku-screenshots");
58467
59482
  await mkdir5(imageDir, { recursive: true });
58468
59483
  await pruneScreenshots(imageDir, Date.now());
58469
59484
  const server = new McpServer({ name: "zaku", version: "1" });
@@ -58480,7 +59495,7 @@ async function serveMcp(log) {
58480
59495
 
58481
59496
  // apps/zaku-cli/src/cli.ts
58482
59497
  var USE_FIGMA_CODE_LIMIT = 5e4;
58483
- var USAGE = "usage: zaku <check|outline|tokens|recipe|library|rules|budget|schema|mcp> [--root docs/design]";
59498
+ var USAGE = "usage: zaku <check|outline|tokens|recipe|library|libraries|rules|budget|schema|mcp> [--root docs/design]";
58484
59499
  async function sendOrRefuse(cli, io, finish, command) {
58485
59500
  try {
58486
59501
  return await cli.bus.send(command);
@@ -58498,6 +59513,14 @@ async function recordRestCalls(cli, io) {
58498
59513
  const { seat, run } = seatAndRun(io.env);
58499
59514
  await cli.bus.send(new RecordBudgetCalls({ day: today(clock.now()), seat, run, kind: "rest", count }));
58500
59515
  }
59516
+ var librariesCommand = (program2, io, finish) => {
59517
+ program2.command("libraries").description("print the design graph: what each target draws from, and what each file extends and is extended by").option("--root <dir>", "the design directory", "docs/design").action(async (options) => {
59518
+ const paths = designPaths(resolve2(io.cwd, options.root));
59519
+ const config2 = await readDesignFile(paths.config, zakuConfigSchema);
59520
+ for (const line of printGraph(config2, await readDesignSnapshots(paths, config2))) io.out(line);
59521
+ finish(0);
59522
+ });
59523
+ };
58501
59524
  var schemaCommand = (program2, io, finish, cli) => {
58502
59525
  program2.command("schema").description("write the JSON Schemas an editor reads for the design files").option("--out <dir>", "where to write them", "docs/design").action(async (options) => {
58503
59526
  const written = await cli.bus.send(new WriteSchemas({ out: resolve2(io.cwd, options.out) }));
@@ -58524,7 +59547,7 @@ var checkCommand = (program2, io, finish) => {
58524
59547
  });
58525
59548
  };
58526
59549
  async function productPlaywright(cwd) {
58527
- const require2 = createRequire(join11(cwd, "package.json"));
59550
+ const require2 = createRequire(join12(cwd, "package.json"));
58528
59551
  let resolved;
58529
59552
  try {
58530
59553
  resolved = require2.resolve("playwright");
@@ -58536,7 +59559,7 @@ async function productPlaywright(cwd) {
58536
59559
  }
58537
59560
  function fromProductTree(cwd, resolved) {
58538
59561
  for (let dir = resolve2(cwd); ; dir = dirname4(dir)) {
58539
- if (resolved.startsWith(join11(dir, "node_modules") + sep)) return true;
59562
+ if (resolved.startsWith(join12(dir, "node_modules") + sep)) return true;
58540
59563
  if (dirname4(dir) === dir) return false;
58541
59564
  }
58542
59565
  }
@@ -58661,13 +59684,26 @@ var libraryCommand = (program2, io, finish, cli) => {
58661
59684
  finish(0);
58662
59685
  }
58663
59686
  );
58664
- library.command("save").description("write library/: the library components read over Figma REST, joined to the variables export").option("--root <dir>", "the design directory", "docs/design").option(
59687
+ library.command("save").description(
59688
+ "write a library's snapshot from its components over Figma REST, joined to the variables export, or a kit's published components"
59689
+ ).option("--root <dir>", "the design directory", "docs/design").option("--key <key>", "the library or kit to save, as zaku.yaml lists it; needed when it lists several").option(
58665
59690
  "--input <variables.json>",
58666
59691
  "what use_figma returned for zaku library script; once per page export for a library in the product file",
58667
59692
  (value, previous = []) => [...previous, value]
58668
59693
  ).action(async (options) => {
58669
59694
  const root = resolve2(io.cwd, options.root);
58670
- if (!options.input || options.input.length === 0) {
59695
+ const config2 = await readDesignFile(designPaths(root).config, zakuConfigSchema);
59696
+ let target;
59697
+ try {
59698
+ target = librarySaveTarget(config2, options.key);
59699
+ } catch (error62) {
59700
+ const refusal = refusalOf(error62);
59701
+ if (!refusal) throw error62;
59702
+ io.err(refusal.line);
59703
+ finish(refusal.code);
59704
+ return;
59705
+ }
59706
+ if (target.kind === "library" && (!options.input || options.input.length === 0)) {
58671
59707
  io.err("zaku library save needs --input, the variables use_figma returned for zaku library script");
58672
59708
  finish(3);
58673
59709
  return;
@@ -58677,26 +59713,36 @@ var libraryCommand = (program2, io, finish, cli) => {
58677
59713
  finish(3);
58678
59714
  return;
58679
59715
  }
58680
- const part = mergeParts(
59716
+ const part = target.kind === "library" ? mergeParts(
58681
59717
  await Promise.all(
58682
- options.input.map(
59718
+ (options.input ?? []).map(
58683
59719
  async (input2) => parseDesignFile(input2, await readFile6(resolve2(io.cwd, input2), "utf8"), variablePartSchema)
58684
59720
  )
58685
59721
  )
58686
- );
59722
+ ) : void 0;
58687
59723
  try {
58688
- const { snapshot, written, unchanged, removed } = await cli.bus.send(
58689
- new SaveLibrary({ root, part })
59724
+ const saved = await cli.bus.send(
59725
+ new SaveLibrary({
59726
+ root,
59727
+ ...options.key !== void 0 ? { key: options.key } : {},
59728
+ ...part ? { part } : {}
59729
+ })
58690
59730
  );
59731
+ if (saved.kind === "kit") {
59732
+ io.out(`wrote ${saved.dir}/kit.json: ${saved.kit.components.length} components`);
59733
+ finish(0);
59734
+ return;
59735
+ }
59736
+ const { snapshot, written, unchanged, removed } = saved;
58691
59737
  if (snapshot.components.length === 0) {
58692
59738
  io.err(
58693
- "library/ holds no components: the file publishes none and the export listed none; for a library in the product file, run zaku library script --local"
59739
+ `${saved.dir}/ holds no components: the file publishes none and the export listed none; for a library in the product file, run zaku library script --local`
58694
59740
  );
58695
59741
  finish(1);
58696
59742
  return;
58697
59743
  }
58698
59744
  io.out(
58699
- `wrote library/: ${snapshot.components.length} components at version ${snapshot.version}; ${written.length} component files written, ${unchanged} unchanged, ${removed.length} removed`
59745
+ `wrote ${saved.dir}/: ${snapshot.components.length} components at version ${snapshot.version}; ${written.length} component files written, ${unchanged} unchanged, ${removed.length} removed`
58700
59746
  );
58701
59747
  finish(0);
58702
59748
  } finally {
@@ -58706,7 +59752,7 @@ var libraryCommand = (program2, io, finish, cli) => {
58706
59752
  };
58707
59753
  var rulesCommand = (program2, io, finish) => {
58708
59754
  const rules = program2.command("rules").description("zaku's rule checks, for a run that reaches Figma through use_figma");
58709
- rules.command("script").description("print the Plugin API code that runs the rules over a page or a node, for use_figma").option("--root <dir>", "the design directory", "docs/design").option("--page <page>", "the page to check, by id or name").option("--node <id>", "the node to check, with everything inside it").option("--after <write.js>", "run this use_figma write first, then the rules, and answer both").action(async (options) => {
59755
+ rules.command("script").description("print the Plugin API code that runs the rules over a page or a node, for use_figma").option("--root <dir>", "the design directory", "docs/design").option("--page <page>", "the page to check, by id or name").option("--node <id>", "the node to check, with everything inside it").option("--after <write.js>", "run this use_figma write first, then the rules, and answer both").option("--key <key>", "the library file the page is in, so its views are held to the branches it holds").action(async (options) => {
58710
59756
  if (options.page === void 0 === (options.node === void 0)) {
58711
59757
  io.err("zaku rules script needs --page or --node, one of them");
58712
59758
  finish(3);
@@ -58714,7 +59760,17 @@ var rulesCommand = (program2, io, finish) => {
58714
59760
  }
58715
59761
  const config2 = await readDesignFile(designPaths(resolve2(io.cwd, options.root)).config, zakuConfigSchema);
58716
59762
  const scope = options.node !== void 0 ? { nodeId: options.node } : { page: options.page };
58717
- const check2 = `await zakuRulesLib.zakuRules(figma, ${JSON.stringify({ scope, copy: resolveCopy(config2.copy) })})`;
59763
+ const graph = designGraph(config2);
59764
+ const file2 = options.key === void 0 ? graph.libraries.length === 1 ? graph.libraries[0] : void 0 : graph.libraries.find((library2) => library2.key === options.key);
59765
+ if (options.key !== void 0 && !file2) {
59766
+ io.err(
59767
+ `zaku rules script: ${options.key} is not a library zaku.yaml lists; name one with --key: ${graph.libraries.map((library2) => library2.key).join(", ")}`
59768
+ );
59769
+ finish(3);
59770
+ return;
59771
+ }
59772
+ const branches = file2?.branches ?? [];
59773
+ const check2 = `await zakuRulesLib.zakuRules(figma, ${JSON.stringify({ scope, copy: resolveCopy(config2.copy), ...branches.length > 1 ? { branches } : {} })})`;
58718
59774
  const library = await runners.rulesLibrary();
58719
59775
  if (options.after === void 0) {
58720
59776
  io.out(`${library}
@@ -58753,6 +59809,7 @@ var COMMANDS = [
58753
59809
  recipeCommand,
58754
59810
  budgetCommand,
58755
59811
  schemaCommand,
59812
+ librariesCommand,
58756
59813
  mcpCommand
58757
59814
  ];
58758
59815
  async function main(argv, io) {