@zeroxsolutions/zaku 0.1.0 → 0.2.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/README.md +9 -1
  2. package/dist/zaku.mjs +1056 -614
  3. package/package.json +1 -1
package/dist/zaku.mjs CHANGED
@@ -1187,11 +1187,11 @@ function __metadata(metadataKey, metadataValue) {
1187
1187
  }
1188
1188
  function __awaiter(thisArg, _arguments, P, generator) {
1189
1189
  function adopt(value) {
1190
- return value instanceof P ? value : new P(function(resolve3) {
1191
- resolve3(value);
1190
+ return value instanceof P ? value : new P(function(resolve4) {
1191
+ resolve4(value);
1192
1192
  });
1193
1193
  }
1194
- return new (P || (P = Promise))(function(resolve3, reject) {
1194
+ return new (P || (P = Promise))(function(resolve4, reject) {
1195
1195
  function fulfilled(value) {
1196
1196
  try {
1197
1197
  step(generator.next(value));
@@ -1207,7 +1207,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
1207
1207
  }
1208
1208
  }
1209
1209
  function step(result) {
1210
- result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
1210
+ result.done ? resolve4(result.value) : adopt(result.value).then(fulfilled, rejected);
1211
1211
  }
1212
1212
  step((generator = generator.apply(thisArg, _arguments || [])).next());
1213
1213
  });
@@ -1385,14 +1385,14 @@ function __asyncValues(o) {
1385
1385
  }, i);
1386
1386
  function verb(n) {
1387
1387
  i[n] = o[n] && function(v) {
1388
- return new Promise(function(resolve3, reject) {
1389
- v = o[n](v), settle2(resolve3, reject, v.done, v.value);
1388
+ return new Promise(function(resolve4, reject) {
1389
+ v = o[n](v), settle2(resolve4, reject, v.done, v.value);
1390
1390
  });
1391
1391
  };
1392
1392
  }
1393
- function settle2(resolve3, reject, d, v) {
1393
+ function settle2(resolve4, reject, d, v) {
1394
1394
  Promise.resolve(v).then(function(v2) {
1395
- resolve3({ value: v2, done: d });
1395
+ resolve4({ value: v2, done: d });
1396
1396
  }, reject);
1397
1397
  }
1398
1398
  }
@@ -2217,7 +2217,7 @@ var require_injectable = __commonJS({
2217
2217
  var reflection_helpers_1 = require_reflection_helpers();
2218
2218
  var dependency_container_1 = require_dependency_container();
2219
2219
  var dependency_container_2 = require_dependency_container();
2220
- function injectable12(options) {
2220
+ function injectable11(options) {
2221
2221
  return function(target) {
2222
2222
  dependency_container_1.typeInfo.set(target, reflection_helpers_1.getParamInfo(target));
2223
2223
  if (options && options.token) {
@@ -2231,7 +2231,7 @@ var require_injectable = __commonJS({
2231
2231
  }
2232
2232
  };
2233
2233
  }
2234
- exports.default = injectable12;
2234
+ exports.default = injectable11;
2235
2235
  }
2236
2236
  });
2237
2237
 
@@ -16461,7 +16461,7 @@ var require_compile = __commonJS({
16461
16461
  const schOrFunc = root.refs[ref];
16462
16462
  if (schOrFunc)
16463
16463
  return schOrFunc;
16464
- let _sch = resolve3.call(this, root, ref);
16464
+ let _sch = resolve4.call(this, root, ref);
16465
16465
  if (_sch === void 0) {
16466
16466
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
16467
16467
  const { schemaId } = this.opts;
@@ -16488,7 +16488,7 @@ var require_compile = __commonJS({
16488
16488
  function sameSchemaEnv(s1, s2) {
16489
16489
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
16490
16490
  }
16491
- function resolve3(root, ref) {
16491
+ function resolve4(root, ref) {
16492
16492
  let sch;
16493
16493
  while (typeof (sch = this.refs[ref]) == "string")
16494
16494
  ref = sch;
@@ -17318,7 +17318,7 @@ var require_fast_uri = __commonJS({
17318
17318
  }
17319
17319
  return uri;
17320
17320
  }
17321
- function resolve3(baseURI, relativeURI, options) {
17321
+ function resolve4(baseURI, relativeURI, options) {
17322
17322
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
17323
17323
  const {
17324
17324
  parsed: baseParsed,
@@ -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, relative3, options, skipNormalization) {
17354
+ function resolveComponent(base, relative4, options, skipNormalization) {
17355
17355
  const target = {};
17356
17356
  if (!skipNormalization) {
17357
17357
  base = parse4(serialize(base, options), options);
17358
- relative3 = parse4(serialize(relative3, options), options);
17358
+ relative4 = parse4(serialize(relative4, options), options);
17359
17359
  }
17360
17360
  options = options || {};
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;
17361
+ if (!options.tolerant && relative4.scheme) {
17362
+ target.scheme = relative4.scheme;
17363
+ target.userinfo = relative4.userinfo;
17364
+ target.host = relative4.host;
17365
+ target.port = relative4.port;
17366
+ target.path = removeDotSegments(relative4.path || "");
17367
+ target.query = relative4.query;
17368
17368
  } else {
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;
17369
+ if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
17370
+ target.userinfo = relative4.userinfo;
17371
+ target.host = relative4.host;
17372
+ target.port = relative4.port;
17373
+ target.path = removeDotSegments(relative4.path || "");
17374
+ target.query = relative4.query;
17375
17375
  } else {
17376
- if (!relative3.path) {
17376
+ if (!relative4.path) {
17377
17377
  target.path = base.path;
17378
- if (relative3.query !== void 0) {
17379
- target.query = relative3.query;
17378
+ if (relative4.query !== void 0) {
17379
+ target.query = relative4.query;
17380
17380
  } else {
17381
17381
  target.query = base.query;
17382
17382
  }
17383
17383
  } else {
17384
- if (relative3.path[0] === "/") {
17385
- target.path = removeDotSegments(relative3.path);
17384
+ if (relative4.path[0] === "/") {
17385
+ target.path = removeDotSegments(relative4.path);
17386
17386
  } else {
17387
17387
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
17388
- target.path = "/" + relative3.path;
17388
+ target.path = "/" + relative4.path;
17389
17389
  } else if (!base.path) {
17390
- target.path = relative3.path;
17390
+ target.path = relative4.path;
17391
17391
  } else {
17392
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
17392
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
17393
17393
  }
17394
17394
  target.path = removeDotSegments(target.path);
17395
17395
  }
17396
- target.query = relative3.query;
17396
+ target.query = relative4.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 = relative3.fragment;
17404
+ target.fragment = relative4.fragment;
17405
17405
  return target;
17406
17406
  }
17407
17407
  function equal(uriA, uriB, options) {
@@ -17687,7 +17687,7 @@ var require_fast_uri = __commonJS({
17687
17687
  var fastUri = {
17688
17688
  SCHEMES,
17689
17689
  normalize,
17690
- resolve: resolve3,
17690
+ resolve: resolve4,
17691
17691
  resolveComponent,
17692
17692
  equal,
17693
17693
  serialize,
@@ -20678,10 +20678,10 @@ var require_dist2 = __commonJS({
20678
20678
 
20679
20679
  // apps/zaku-cli/src/cli.ts
20680
20680
  var import_reflect_metadata3 = __toESM(require_Reflect(), 1);
20681
- import { readFile as readFile6 } from "node:fs/promises";
20681
+ import { readFile as readFile7 } 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 join12, resolve as resolve2, sep } from "node:path";
20684
+ import { dirname as dirname5, join as join13, relative as relative3, resolve as resolve3, 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
@@ -24111,7 +24111,7 @@ function bootstrap(scope, tokens) {
24111
24111
  }
24112
24112
 
24113
24113
  // apps/zaku-cli/src/dependencies.ts
24114
- var import_tsyringe12 = __toESM(require_cjs(), 1);
24114
+ var import_tsyringe11 = __toESM(require_cjs(), 1);
24115
24115
  import { homedir } from "node:os";
24116
24116
 
24117
24117
  // packages/zaku/dist/index.js
@@ -24154,11 +24154,13 @@ async function decodePreset(code, run) {
24154
24154
  // packages/zaku/dist/lib/commands/write-tokens.js
24155
24155
  var WriteTokens = class extends DomainCommand {
24156
24156
  root;
24157
+ config;
24157
24158
  cwd;
24158
24159
  css;
24159
24160
  constructor(input2) {
24160
24161
  super();
24161
24162
  this.root = input2.root;
24163
+ this.config = input2.config;
24162
24164
  this.cwd = input2.cwd;
24163
24165
  this.css = input2.css;
24164
24166
  }
@@ -24167,10 +24169,12 @@ var WriteTokens = class extends DomainCommand {
24167
24169
  // packages/zaku/dist/lib/commands/write-recipe.js
24168
24170
  var WriteRecipe = class extends DomainCommand {
24169
24171
  root;
24172
+ config;
24170
24173
  url;
24171
24174
  constructor(input2) {
24172
24175
  super();
24173
24176
  this.root = input2.root;
24177
+ this.config = input2.config;
24174
24178
  this.url = input2.url;
24175
24179
  }
24176
24180
  };
@@ -24178,11 +24182,13 @@ var WriteRecipe = class extends DomainCommand {
24178
24182
  // packages/zaku/dist/lib/commands/save-library.js
24179
24183
  var SaveLibrary = class extends DomainCommand {
24180
24184
  root;
24185
+ config;
24181
24186
  key;
24182
24187
  part;
24183
24188
  constructor(input2) {
24184
24189
  super();
24185
24190
  this.root = input2.root;
24191
+ this.config = input2.config;
24186
24192
  if (input2.key !== void 0)
24187
24193
  this.key = input2.key;
24188
24194
  if (input2.part !== void 0)
@@ -24193,10 +24199,12 @@ var SaveLibrary = class extends DomainCommand {
24193
24199
  // packages/zaku/dist/lib/commands/write-outline.js
24194
24200
  var WriteOutline = class extends DomainCommand {
24195
24201
  root;
24202
+ config;
24196
24203
  frames;
24197
24204
  constructor(input2) {
24198
24205
  super();
24199
24206
  this.root = input2.root;
24207
+ this.config = input2.config;
24200
24208
  this.frames = input2.frames;
24201
24209
  }
24202
24210
  };
@@ -24236,7 +24244,6 @@ var TOKENS = {
24236
24244
  RUN_COMMAND: "zaku.runCommand",
24237
24245
  RECIPE_PAGE_READER: "zaku.recipePageReader",
24238
24246
  BUDGET_LEDGER: "zaku.budgetLedger",
24239
- DESIGN_CONFIG_REPOSITORY: "zaku.designConfigRepository",
24240
24247
  TOKEN_DOCUMENT_REPOSITORY: "zaku.tokenDocumentRepository",
24241
24248
  RECIPE_REPOSITORY: "zaku.recipeRepository",
24242
24249
  JSON_SCHEMA_FILES: "zaku.jsonSchemaFiles",
@@ -29385,7 +29392,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
29385
29392
  var NONE = 0;
29386
29393
  var ASSUMED = 1;
29387
29394
  var PROVEN = 2;
29388
- function isRecursive(inst, stack, resolve3) {
29395
+ function isRecursive(inst, stack, resolve4) {
29389
29396
  const cached2 = recursive.get(inst);
29390
29397
  if (cached2 !== void 0)
29391
29398
  return cached2 ? PROVEN : NONE;
@@ -29395,7 +29402,7 @@ function isRecursive(inst, stack, resolve3) {
29395
29402
  let result = NONE;
29396
29403
  const check2 = (child) => {
29397
29404
  if (result !== PROVEN && child?._zod) {
29398
- const answer = isRecursive(child, stack, resolve3);
29405
+ const answer = isRecursive(child, stack, resolve4);
29399
29406
  if (answer > result)
29400
29407
  result = answer;
29401
29408
  }
@@ -29406,7 +29413,7 @@ function isRecursive(inst, stack, resolve3) {
29406
29413
  const desc = Object.getOwnPropertyDescriptor(sh, key);
29407
29414
  if (spread && !desc.enumerable)
29408
29415
  continue;
29409
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve3) : NONE;
29416
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
29410
29417
  if (child > answer)
29411
29418
  answer = child;
29412
29419
  }
@@ -29470,7 +29477,7 @@ function isRecursive(inst, stack, resolve3) {
29470
29477
  break;
29471
29478
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
29472
29479
  case "lazy": {
29473
- const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
29480
+ const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
29474
29481
  merge3(inner ? isRecursive(inner, stack, false) : ASSUMED);
29475
29482
  break;
29476
29483
  }
@@ -44362,7 +44369,7 @@ function dtcgTokenDocument(system, schemes, name) {
44362
44369
  // packages/zaku/dist/lib/domain/errors/css-required.js
44363
44370
  var CssRequired = class extends Error {
44364
44371
  constructor() {
44365
- super("zaku tokens needs --css, the stylesheet that declares :root and .dark");
44372
+ super("zaku tokens needs designSystem.shadcn.css, the stylesheet that declares :root and .dark, or --css when it runs on one product");
44366
44373
  this.name = "CssRequired";
44367
44374
  }
44368
44375
  };
@@ -44417,6 +44424,34 @@ var PlaywrightMissing = class extends Error {
44417
44424
  }
44418
44425
  };
44419
44426
 
44427
+ // packages/zaku/dist/lib/domain/errors/product-config-missing.js
44428
+ var ProductConfigMissing = class extends Error {
44429
+ path;
44430
+ product;
44431
+ constructor(path2, product) {
44432
+ super(`no ${path2}, the zaku.yaml of the product ${product} the workspace config lists`);
44433
+ this.path = path2;
44434
+ this.product = product;
44435
+ this.name = "ProductConfigMissing";
44436
+ }
44437
+ };
44438
+
44439
+ // packages/zaku/dist/lib/domain/errors/product-required.js
44440
+ var ProductRequired = class extends Error {
44441
+ constructor(command, listed) {
44442
+ super(`${command} acts on one product; name one with --product: ${listed.join(", ")}`);
44443
+ this.name = "ProductRequired";
44444
+ }
44445
+ };
44446
+
44447
+ // packages/zaku/dist/lib/domain/errors/product-unknown.js
44448
+ var ProductUnknown = class extends Error {
44449
+ constructor(name, listed) {
44450
+ super(`no product ${name}; the config lists ${listed.join(", ")}`);
44451
+ this.name = "ProductUnknown";
44452
+ }
44453
+ };
44454
+
44420
44455
  // packages/zaku/dist/lib/domain/errors/recipe-page-failed.js
44421
44456
  var RecipePageFailed = class extends Error {
44422
44457
  constructor(cause) {
@@ -44433,6 +44468,18 @@ var RecipeUrlRequired = class extends Error {
44433
44468
  }
44434
44469
  };
44435
44470
 
44471
+ // packages/zaku/dist/lib/domain/errors/workspace-config-missing.js
44472
+ var WorkspaceConfigMissing = class extends Error {
44473
+ path;
44474
+ legacy;
44475
+ constructor(path2, legacy) {
44476
+ super(legacy === null ? `no ${path2}` : `no ${path2}, and no ${legacy} to fall back on; run zaku where zaku.config.yaml is, or name it with --config`);
44477
+ this.path = path2;
44478
+ this.legacy = legacy;
44479
+ this.name = "WorkspaceConfigMissing";
44480
+ }
44481
+ };
44482
+
44436
44483
  // packages/zaku/dist/lib/domain/theme-css.js
44437
44484
  function declarations(css, selector) {
44438
44485
  const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -44452,19 +44499,17 @@ function parseThemeCss(css) {
44452
44499
 
44453
44500
  // packages/zaku/dist/lib/handlers/write-tokens.js
44454
44501
  var WriteTokensHandler = class WriteTokensHandler2 {
44455
- configs;
44456
44502
  documents;
44457
44503
  files;
44458
44504
  run;
44459
44505
  command = WriteTokens;
44460
- constructor(configs, documents, files2, run) {
44461
- this.configs = configs;
44506
+ constructor(documents, files2, run) {
44462
44507
  this.documents = documents;
44463
44508
  this.files = files2;
44464
44509
  this.run = run;
44465
44510
  }
44466
44511
  async handle(command) {
44467
- const config2 = await this.configs.read(command.root);
44512
+ const { config: config2 } = command;
44468
44513
  const system = config2.designSystem;
44469
44514
  let doc;
44470
44515
  let from;
@@ -44489,10 +44534,9 @@ var WriteTokensHandler = class WriteTokensHandler2 {
44489
44534
  };
44490
44535
  WriteTokensHandler = __decorate2([
44491
44536
  (0, import_tsyringe.injectable)({ token: TOKENS.COMMAND_HANDLER }),
44492
- __param2(0, (0, import_tsyringe.inject)(TOKENS.DESIGN_CONFIG_REPOSITORY)),
44493
- __param2(1, (0, import_tsyringe.inject)(TOKENS.TOKEN_DOCUMENT_REPOSITORY)),
44494
- __param2(2, (0, import_tsyringe.inject)(TOKENS.SOURCE_FILES)),
44495
- __param2(3, (0, import_tsyringe.inject)(TOKENS.RUN_COMMAND))
44537
+ __param2(0, (0, import_tsyringe.inject)(TOKENS.TOKEN_DOCUMENT_REPOSITORY)),
44538
+ __param2(1, (0, import_tsyringe.inject)(TOKENS.SOURCE_FILES)),
44539
+ __param2(2, (0, import_tsyringe.inject)(TOKENS.RUN_COMMAND))
44496
44540
  ], WriteTokensHandler);
44497
44541
 
44498
44542
  // packages/zaku/dist/lib/handlers/write-recipe.js
@@ -44587,17 +44631,15 @@ ${JSON.stringify(Object.entries(props).sort(([a], [b]) => a.localeCompare(b)))}`
44587
44631
 
44588
44632
  // packages/zaku/dist/lib/handlers/write-recipe.js
44589
44633
  var WriteRecipeHandler = class WriteRecipeHandler2 {
44590
- configs;
44591
44634
  recipes;
44592
44635
  readPage;
44593
44636
  command = WriteRecipe;
44594
- constructor(configs, recipes, readPage) {
44595
- this.configs = configs;
44637
+ constructor(recipes, readPage) {
44596
44638
  this.recipes = recipes;
44597
44639
  this.readPage = readPage;
44598
44640
  }
44599
44641
  async handle(command) {
44600
- const config2 = await this.configs.read(command.root);
44642
+ const { config: config2 } = command;
44601
44643
  const url2 = command.url ?? config2.recipe?.url;
44602
44644
  if (!url2)
44603
44645
  throw new RecipeUrlRequired();
@@ -44620,17 +44662,41 @@ var WriteRecipeHandler = class WriteRecipeHandler2 {
44620
44662
  };
44621
44663
  WriteRecipeHandler = __decorate2([
44622
44664
  (0, import_tsyringe2.injectable)({ token: TOKENS.COMMAND_HANDLER }),
44623
- __param2(0, (0, import_tsyringe2.inject)(TOKENS.DESIGN_CONFIG_REPOSITORY)),
44624
- __param2(1, (0, import_tsyringe2.inject)(TOKENS.RECIPE_REPOSITORY)),
44625
- __param2(2, (0, import_tsyringe2.inject)(TOKENS.RECIPE_PAGE_READER))
44665
+ __param2(0, (0, import_tsyringe2.inject)(TOKENS.RECIPE_REPOSITORY)),
44666
+ __param2(1, (0, import_tsyringe2.inject)(TOKENS.RECIPE_PAGE_READER))
44626
44667
  ], WriteRecipeHandler);
44627
44668
 
44628
44669
  // packages/zaku/dist/lib/handlers/save-library.js
44629
44670
  var import_tsyringe3 = __toESM(require_cjs(), 1);
44630
44671
  import { relative as relative2 } from "node:path";
44631
44672
 
44632
- // packages/zaku/dist/lib/domain/copy-defaults.js
44633
- var DEFAULT_COPY = { locales: ["en"], currencies: [] };
44673
+ // packages/zaku/dist/lib/repositories/design-paths.js
44674
+ import { join as join2 } from "node:path";
44675
+ function librarySnapshotDir(paths, key) {
44676
+ if (key === null)
44677
+ return paths;
44678
+ const library = join2(paths.library, key);
44679
+ return { root: paths.root, library, libraryIndex: join2(library, "index.json"), legacyLibrary: null };
44680
+ }
44681
+ function designPaths(root) {
44682
+ return {
44683
+ root,
44684
+ config: join2(root, "zaku.yaml"),
44685
+ maps: join2(root, "map"),
44686
+ outline: join2(root, "outline"),
44687
+ unmapped: join2(root, "outline", "unmapped.yaml"),
44688
+ files: join2(root, "outline", "files.yaml"),
44689
+ library: join2(root, "library"),
44690
+ libraryIndex: join2(root, "library", "index.json"),
44691
+ legacyLibrary: join2(root, "library.json"),
44692
+ tokens: join2(root, "tokens.json"),
44693
+ recipe: join2(root, "recipe.json")
44694
+ };
44695
+ }
44696
+
44697
+ // packages/zaku/dist/lib/repositories/library-snapshot.js
44698
+ import { mkdir, readdir, readFile as readFile2, rename as rename2, rm, writeFile as writeFile2 } from "node:fs/promises";
44699
+ import { join as join3, relative } from "node:path";
44634
44700
 
44635
44701
  // packages/zaku/dist/lib/domain/design-graph.js
44636
44702
  var DESKTOP_FAMILIES = /* @__PURE__ */ new Set(["macos", "windows", "gnome", "kde"]);
@@ -44716,334 +44782,6 @@ function snapshotsFor(input2, target) {
44716
44782
  return { libraries, kits, missing };
44717
44783
  }
44718
44784
 
44719
- // packages/zaku/dist/lib/schema/zaku-config.js
44720
- var PLATFORM_FAMILIES = ["web", "ios", "android", "macos", "windows", "gnome", "kde"];
44721
- var LAYERS = ["foundations", "components"];
44722
- var BRANCHES = ["ui", "native"];
44723
- var DEFAULT_INTERACTIVE = [
44724
- "Button",
44725
- "Button Group",
44726
- "Checkbox",
44727
- "Combobox",
44728
- "Dropdown Menu",
44729
- "Input",
44730
- "Input Group",
44731
- "Input OTP",
44732
- "Native Select",
44733
- "Pagination",
44734
- "Radio Group",
44735
- "Select",
44736
- "Slider",
44737
- "Switch",
44738
- "Tabs",
44739
- "Textarea",
44740
- "Toggle",
44741
- "Toggle Group"
44742
- ];
44743
- var id = external_exports.string().regex(/^[a-z][a-z0-9-]*$/, "lowercase letters, digits and dashes, starting with a letter");
44744
- var targetSchema = external_exports.object({
44745
- id,
44746
- family: external_exports.enum(PLATFORM_FAMILIES),
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"),
44748
- /** The frame size every frame of this target is drawn at; the frame check compares it. */
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()
44752
- }).strict();
44753
- var DEFAULT_SYSTEM_BARS = {
44754
- web: [],
44755
- ios: [["Status Bar"], ["Home Indicator"]],
44756
- android: [["Status Bar"], ["Navigation Bar", "Gesture Handle"]],
44757
- macos: [["Window Controls"]],
44758
- windows: [["Title Bar"]],
44759
- gnome: [["Header Bar"]],
44760
- kde: [["Title Bar"]]
44761
- };
44762
- function systemBarNames(config2) {
44763
- const bars = { ...DEFAULT_SYSTEM_BARS, ...config2.systemBars };
44764
- return new Set(Object.values(bars).flat(2));
44765
- }
44766
- var budgetSchema = external_exports.object({
44767
- mcpPerDay: external_exports.number().int().positive().default(200),
44768
- mcpPerRun: external_exports.number().int().positive().default(30),
44769
- reserve: external_exports.number().min(0).max(1).default(0.2)
44770
- }).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
- }
44793
- var shadcnSystem = external_exports.object({ preset: external_exports.string().min(1) }).strict();
44794
- var tokenFiles = external_exports.array(external_exports.string().min(1)).min(1);
44795
- var resolverInputs = external_exports.record(external_exports.string(), external_exports.string());
44796
- var dtcgSystem = external_exports.object({
44797
- resolver: external_exports.string().min(1).optional(),
44798
- light: external_exports.union([tokenFiles, resolverInputs]),
44799
- dark: external_exports.union([tokenFiles, resolverInputs]),
44800
- /** The group whose tokens are the colour roles, and the one whose tokens are the radius steps. */
44801
- colors: external_exports.string().min(1).default("color"),
44802
- radii: external_exports.string().min(1).default("radius"),
44803
- /** Foreground and background token pairs a text is read in, each needing 4.5:1. */
44804
- contrast: external_exports.array(external_exports.tuple([external_exports.string().min(1), external_exports.string().min(1)])).default([])
44805
- }).strict().superRefine((dtcg, ctx) => {
44806
- for (const scheme of ["light", "dark"]) {
44807
- const files2 = Array.isArray(dtcg[scheme]);
44808
- if (dtcg.resolver !== void 0 && files2) {
44809
- ctx.addIssue({
44810
- code: "custom",
44811
- path: [scheme],
44812
- message: "with a resolver, name the modifier contexts, not files"
44813
- });
44814
- }
44815
- if (dtcg.resolver === void 0 && !files2) {
44816
- ctx.addIssue({
44817
- code: "custom",
44818
- path: [scheme],
44819
- message: "without a resolver, list the token files"
44820
- });
44821
- }
44822
- }
44823
- });
44824
- var designSystemSchema = external_exports.union([
44825
- external_exports.object({ shadcn: shadcnSystem }).strict(),
44826
- external_exports.object({ dtcg: dtcgSystem }).strict()
44827
- ]);
44828
- var recipeSchema = external_exports.object({
44829
- url: external_exports.url().optional(),
44830
- dark: external_exports.enum(["class", "media"]).default("class"),
44831
- darkClass: external_exports.string().min(1).default("dark")
44832
- }).strict();
44833
- function writtenInAScript(tag) {
44834
- try {
44835
- return new Intl.Locale(tag).maximize().script !== void 0;
44836
- } catch {
44837
- return false;
44838
- }
44839
- }
44840
- var copySchema = external_exports.object({
44841
- locales: external_exports.array(external_exports.string().refine(writtenInAScript, "a BCP 47 language tag, such as en or vi")).min(1).default(["en"]),
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([])
44843
- }).strict();
44844
- var configShape = external_exports.object({
44845
- product: external_exports.string().min(1),
44846
- designSystem: designSystemSchema,
44847
- figma: files.default({}),
44848
- targets: external_exports.array(targetSchema).min(1),
44849
- modes: external_exports.record(external_exports.string(), external_exports.string()).default({}),
44850
- interactive: external_exports.array(external_exports.string().min(1)).default([...DEFAULT_INTERACTIVE]),
44851
- /** The bars of the families whose kit names them its own way; every family left out keeps its default. */
44852
- systemBars: external_exports.partialRecord(external_exports.enum(PLATFORM_FAMILIES), external_exports.array(external_exports.array(external_exports.string().min(1)).min(1))).optional(),
44853
- budget: budgetSchema.default({ mcpPerDay: 200, mcpPerRun: 30, reserve: 0.2 }),
44854
- recipe: recipeSchema.optional(),
44855
- copy: copySchema.default(DEFAULT_COPY)
44856
- }).strict();
44857
- var zakuConfigSchema = configShape.superRefine((config2, ctx) => {
44858
- for (const key of ["id", "name"]) {
44859
- const seen = /* @__PURE__ */ new Set();
44860
- config2.targets.forEach((target, index) => {
44861
- if (seen.has(target[key])) {
44862
- ctx.addIssue({
44863
- code: "custom",
44864
- path: ["targets", index, key],
44865
- message: `a second target with ${key} ${target[key]}`
44866
- });
44867
- }
44868
- seen.add(target[key]);
44869
- });
44870
- }
44871
- if (byRole(config2.figma))
44872
- refineFilesByRole(config2, ctx);
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
- }
44982
-
44983
- // packages/zaku/dist/lib/repositories/design-file.js
44984
- var import_yaml = __toESM(require_dist(), 1);
44985
- import { readFile, rename, writeFile } from "node:fs/promises";
44986
- function parseDesignFile(path2, text, schema) {
44987
- let data;
44988
- try {
44989
- data = path2.endsWith(".json") ? JSON.parse(text) : (0, import_yaml.parse)(text);
44990
- } catch (error62) {
44991
- throw new DesignFileInvalid(path2, [error62.message]);
44992
- }
44993
- const result = schema.safeParse(data);
44994
- if (!result.success) {
44995
- throw new DesignFileInvalid(path2, result.error.issues.map((issue2) => `${issue2.path.map(String).join(".") || "(root)"}: ${issue2.message}`));
44996
- }
44997
- return result.data;
44998
- }
44999
- async function readDesignFile(path2, schema) {
45000
- return parseDesignFile(path2, await readFile(path2, "utf8"), schema);
45001
- }
45002
- async function readOptionalDesignFile(path2, schema) {
45003
- let text;
45004
- try {
45005
- text = await readFile(path2, "utf8");
45006
- } catch (error62) {
45007
- if (error62.code === "ENOENT")
45008
- return null;
45009
- throw error62;
45010
- }
45011
- return parseDesignFile(path2, text, schema);
45012
- }
45013
- async function writeDesignFile(path2, value) {
45014
- const temporary = `${path2}.${process.pid}.tmp`;
45015
- await writeFile(temporary, `${JSON.stringify(value, null, 2)}
45016
- `);
45017
- await rename(temporary, path2);
45018
- }
45019
-
45020
- // packages/zaku/dist/lib/repositories/design-paths.js
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
- }
45028
- function designPaths(root) {
45029
- return {
45030
- root,
45031
- config: join2(root, "zaku.yaml"),
45032
- maps: join2(root, "map"),
45033
- outline: join2(root, "outline"),
45034
- unmapped: join2(root, "outline", "unmapped.yaml"),
45035
- library: join2(root, "library"),
45036
- libraryIndex: join2(root, "library", "index.json"),
45037
- legacyLibrary: join2(root, "library.json"),
45038
- tokens: join2(root, "tokens.json"),
45039
- recipe: join2(root, "recipe.json")
45040
- };
45041
- }
45042
-
45043
- // packages/zaku/dist/lib/repositories/library-snapshot.js
45044
- import { mkdir, readdir, readFile as readFile2, rename as rename2, rm, writeFile as writeFile2 } from "node:fs/promises";
45045
- import { join as join3, relative } from "node:path";
45046
-
45047
44785
  // packages/zaku/dist/lib/domain/library.js
45048
44786
  var rgbaSchema = external_exports.object({ r: external_exports.number(), g: external_exports.number(), b: external_exports.number(), a: external_exports.number() }).strict();
45049
44787
  var libraryItemSchema = external_exports.object({
@@ -45332,6 +45070,43 @@ function formatLibraryFile(value) {
45332
45070
  `;
45333
45071
  }
45334
45072
 
45073
+ // packages/zaku/dist/lib/repositories/design-file.js
45074
+ var import_yaml = __toESM(require_dist(), 1);
45075
+ import { readFile, rename, writeFile } from "node:fs/promises";
45076
+ function parseDesignFile(path2, text, schema) {
45077
+ let data;
45078
+ try {
45079
+ data = path2.endsWith(".json") ? JSON.parse(text) : (0, import_yaml.parse)(text);
45080
+ } catch (error62) {
45081
+ throw new DesignFileInvalid(path2, [error62.message]);
45082
+ }
45083
+ const result = schema.safeParse(data);
45084
+ if (!result.success) {
45085
+ throw new DesignFileInvalid(path2, result.error.issues.map((issue2) => `${issue2.path.map(String).join(".") || "(root)"}: ${issue2.message}`));
45086
+ }
45087
+ return result.data;
45088
+ }
45089
+ async function readDesignFile(path2, schema) {
45090
+ return parseDesignFile(path2, await readFile(path2, "utf8"), schema);
45091
+ }
45092
+ async function readOptionalDesignFile(path2, schema) {
45093
+ let text;
45094
+ try {
45095
+ text = await readFile(path2, "utf8");
45096
+ } catch (error62) {
45097
+ if (error62.code === "ENOENT")
45098
+ return null;
45099
+ throw error62;
45100
+ }
45101
+ return parseDesignFile(path2, text, schema);
45102
+ }
45103
+ async function writeDesignFile(path2, value) {
45104
+ const temporary = `${path2}.${process.pid}.tmp`;
45105
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}
45106
+ `);
45107
+ await rename(temporary, path2);
45108
+ }
45109
+
45335
45110
  // packages/zaku/dist/lib/repositories/library-snapshot.js
45336
45111
  async function readText(path2) {
45337
45112
  try {
@@ -45724,8 +45499,7 @@ function librarySaveTarget(config2, key) {
45724
45499
  }
45725
45500
  async function saveLibrary(options) {
45726
45501
  const paths = designPaths(options.root);
45727
- const config2 = await readDesignFile(paths.config, zakuConfigSchema);
45728
- const target = librarySaveTarget(config2, options.key);
45502
+ const target = librarySaveTarget(options.config, options.key);
45729
45503
  if (target.kind === "kit") {
45730
45504
  const at2 = librarySnapshotDir(paths, target.kit.key);
45731
45505
  const kit = await readKit({ rest: options.rest, key: target.kit.key, family: target.kit.family });
@@ -45753,6 +45527,7 @@ var SaveLibraryHandler = class SaveLibraryHandler2 {
45753
45527
  handle(command) {
45754
45528
  return saveLibrary({
45755
45529
  root: command.root,
45530
+ config: command.config,
45756
45531
  rest: this.rest,
45757
45532
  ...command.key !== void 0 ? { key: command.key } : {},
45758
45533
  ...command.part !== void 0 ? { part: command.part } : {}
@@ -45771,6 +45546,85 @@ var import_yaml2 = __toESM(require_dist(), 1);
45771
45546
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
45772
45547
  import { dirname as dirname2, join as join6 } from "node:path";
45773
45548
 
45549
+ // packages/zaku/dist/lib/adapters/figma-rest.js
45550
+ var FigmaRateLimited = class extends Error {
45551
+ url;
45552
+ constructor(url2) {
45553
+ super(`Figma answered 429 twice for ${url2}; wait and run again`);
45554
+ this.url = url2;
45555
+ this.name = "FigmaRateLimited";
45556
+ }
45557
+ };
45558
+ var FigmaRequestFailed = class extends Error {
45559
+ url;
45560
+ status;
45561
+ constructor(url2, status, body) {
45562
+ super(`Figma answered ${status} for ${url2}: ${body.slice(0, 200)}`);
45563
+ this.url = url2;
45564
+ this.status = status;
45565
+ this.name = "FigmaRequestFailed";
45566
+ }
45567
+ };
45568
+ var BASE = "https://api.figma.com/v1";
45569
+ var BATCH = 50;
45570
+ var MAX_WAIT_MS = 12e4;
45571
+ function createFigmaRest(options) {
45572
+ const send = options.fetch ?? globalThis.fetch;
45573
+ const sleep = options.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
45574
+ const onCall = options.onCall ?? (() => void 0);
45575
+ async function call(url2) {
45576
+ for (let attempt = 0; attempt < 2; attempt += 1) {
45577
+ const res = await send(url2, { headers: { "X-Figma-Token": options.token } });
45578
+ onCall();
45579
+ if (res.status === 429) {
45580
+ if (attempt === 1)
45581
+ break;
45582
+ const seconds = Number(res.headers.get("Retry-After") ?? "60");
45583
+ await sleep(Math.min(Number.isFinite(seconds) ? seconds * 1e3 : 6e4, MAX_WAIT_MS));
45584
+ continue;
45585
+ }
45586
+ if (!res.ok)
45587
+ throw new FigmaRequestFailed(url2, res.status, await res.text());
45588
+ return await res.json();
45589
+ }
45590
+ throw new FigmaRateLimited(url2);
45591
+ }
45592
+ const chunks = (ids) => Array.from({ length: Math.ceil(ids.length / BATCH) }, (_, index) => ids.slice(index * BATCH, (index + 1) * BATCH));
45593
+ async function components(fileKey3) {
45594
+ const listed = await call(`${BASE}/files/${fileKey3}/components`);
45595
+ return listed.meta.components;
45596
+ }
45597
+ return {
45598
+ file: (fileKey3, depth) => call(`${BASE}/files/${fileKey3}?depth=${depth}`),
45599
+ async fileName(fileKey3) {
45600
+ const meta3 = await call(`${BASE}/files/${fileKey3}/meta`);
45601
+ return meta3.file.name;
45602
+ },
45603
+ async nodes(fileKey3, ids) {
45604
+ const merged = { version: "", nodes: {} };
45605
+ for (const chunk of chunks(ids)) {
45606
+ const part = await call(`${BASE}/files/${fileKey3}/nodes?ids=${encodeURIComponent(chunk.join(","))}`);
45607
+ merged.version = part.version;
45608
+ Object.assign(merged.nodes, part.nodes);
45609
+ }
45610
+ return merged;
45611
+ },
45612
+ async published(fileKey3) {
45613
+ const sets = await call(`${BASE}/files/${fileKey3}/component_sets`);
45614
+ return { sets: sets.meta.component_sets, components: await components(fileKey3) };
45615
+ },
45616
+ components,
45617
+ async images(fileKey3, ids) {
45618
+ const merged = {};
45619
+ for (const chunk of chunks(ids)) {
45620
+ const part = await call(`${BASE}/images/${fileKey3}?ids=${encodeURIComponent(chunk.join(","))}&format=png&scale=1`);
45621
+ Object.assign(merged, part.images);
45622
+ }
45623
+ return merged;
45624
+ }
45625
+ };
45626
+ }
45627
+
45774
45628
  // packages/zaku/dist/lib/domain/documentation-measures.js
45775
45629
  var COVER = {
45776
45630
  page: "Thumbnail",
@@ -46348,7 +46202,7 @@ function outlineFrame(frame, where, ctx) {
46348
46202
  const images2 = [];
46349
46203
  const defaultNames = [];
46350
46204
  const links = [];
46351
- const relative3 = (node2) => {
46205
+ const relative4 = (node2) => {
46352
46206
  const box = node2.absoluteBoundingBox;
46353
46207
  return box ? { x: box.x - origin.x, y: box.y - origin.y, width: box.width, height: box.height } : null;
46354
46208
  };
@@ -46377,7 +46231,7 @@ function outlineFrame(frame, where, ctx) {
46377
46231
  componentKey: ctx.components[child.componentId ?? ""]?.key ?? "",
46378
46232
  component: component2,
46379
46233
  interactive: ctx.interactive.has(component2),
46380
- bounds: relative3(child)
46234
+ bounds: relative4(child)
46381
46235
  });
46382
46236
  } else if (child.type === "TEXT") {
46383
46237
  instance.texts.push({
@@ -46418,7 +46272,7 @@ function outlineFrame(frame, where, ctx) {
46418
46272
  texts: [],
46419
46273
  interactive: ctx.interactive.has(component2),
46420
46274
  local: meta3?.remote === false,
46421
- bounds: relative3(node2),
46275
+ bounds: relative4(node2),
46422
46276
  sizing: {
46423
46277
  horizontal: node2.layoutSizingHorizontal ?? "FIXED",
46424
46278
  vertical: node2.layoutSizingVertical ?? "FIXED"
@@ -46610,6 +46464,7 @@ var unmappedSchema = external_exports.object({
46610
46464
  /** A component drawn on a product page, which belongs in a Components library. */
46611
46465
  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()
46612
46466
  }).strict();
46467
+ var outlineFilesSchema = external_exports.object({ files: external_exports.array(external_exports.object({ key: external_exports.string(), name: external_exports.string() }).strict()) }).strict();
46613
46468
 
46614
46469
  // packages/zaku/dist/lib/repositories/outlines.js
46615
46470
  var FLOWS_DIR = "flows";
@@ -46629,12 +46484,12 @@ async function loadOutlines(dir) {
46629
46484
  features = (await readdir3(dir, { withFileTypes: true })).filter((entry) => entry.isDirectory() && entry.name !== FLOWS_DIR).map((entry) => entry.name).sort();
46630
46485
  } catch (error62) {
46631
46486
  if (error62.code === "ENOENT")
46632
- return { outlines, flows, unmapped: null, errors };
46487
+ return { outlines, flows, unmapped: null, files: null, errors };
46633
46488
  throw error62;
46634
46489
  }
46635
46490
  for (const feature of features) {
46636
- const files2 = (await readdir3(join5(dir, feature))).filter((name) => name.endsWith(".yaml")).sort();
46637
- for (const file2 of files2) {
46491
+ const files3 = (await readdir3(join5(dir, feature))).filter((name) => name.endsWith(".yaml")).sort();
46492
+ for (const file2 of files3) {
46638
46493
  try {
46639
46494
  outlines.push(await readDesignFile(join5(dir, feature, file2), screenOutlineSchema));
46640
46495
  } catch (error62) {
@@ -46651,8 +46506,8 @@ async function loadOutlines(dir) {
46651
46506
  throw error62;
46652
46507
  });
46653
46508
  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) {
46509
+ const files3 = (await readdir3(join5(dir, FLOWS_DIR, source))).filter((name) => name.endsWith(".yaml")).sort();
46510
+ for (const file2 of files3) {
46656
46511
  try {
46657
46512
  flows.push(await readDesignFile(join5(dir, FLOWS_DIR, source, file2), flowOutlineSchema));
46658
46513
  } catch (error62) {
@@ -46672,7 +46527,16 @@ async function loadOutlines(dir) {
46672
46527
  else
46673
46528
  throw error62;
46674
46529
  }
46675
- return { outlines, flows, unmapped, errors };
46530
+ let files2 = null;
46531
+ try {
46532
+ files2 = await readOptionalDesignFile(join5(dir, "files.yaml"), outlineFilesSchema);
46533
+ } catch (error62) {
46534
+ if (error62 instanceof DesignFileInvalid)
46535
+ errors.push(error62);
46536
+ else
46537
+ throw error62;
46538
+ }
46539
+ return { outlines, flows, unmapped, files: files2, errors };
46676
46540
  }
46677
46541
 
46678
46542
  // packages/zaku/dist/lib/handlers/write-outline.js
@@ -46749,9 +46613,28 @@ function productFiles(config2) {
46749
46613
  targets: graph.targetsOf(product.family)
46750
46614
  }));
46751
46615
  }
46616
+ async function readFileNames(rest, config2, read) {
46617
+ const graph = designGraph(config2);
46618
+ const files2 = [];
46619
+ for (const file2 of [...graph.products, ...graph.prototypes]) {
46620
+ const name = read.get(file2.key);
46621
+ if (name !== void 0)
46622
+ files2.push({ key: file2.key, name });
46623
+ }
46624
+ for (const library of graph.libraries.filter((each) => each.layer === "components")) {
46625
+ try {
46626
+ files2.push({ key: library.key, name: await rest.fileName(library.key) });
46627
+ } catch (error62) {
46628
+ if (error62 instanceof FigmaRequestFailed && (error62.status === 403 || error62.status === 404))
46629
+ continue;
46630
+ throw error62;
46631
+ }
46632
+ }
46633
+ return { files: files2 };
46634
+ }
46752
46635
  async function runOutline(options) {
46753
46636
  const paths = designPaths(options.root);
46754
- const config2 = await readDesignFile(paths.config, zakuConfigSchema);
46637
+ const { config: config2 } = options;
46755
46638
  const { libraries: listed, kits, products: byFamily, prototypes: flows } = config2.figma;
46756
46639
  const legacy = [listed, kits, byFamily, flows].every((files2) => files2 === void 0);
46757
46640
  const products = productFiles(config2);
@@ -46766,6 +46649,7 @@ async function runOutline(options) {
46766
46649
  const existing = await loadOutlines(paths.outline);
46767
46650
  const prototypes = new Set(legacy ? config2.targets.map((target) => `${target.name} prototype`) : []);
46768
46651
  const versions = {};
46652
+ const names = /* @__PURE__ */ new Map();
46769
46653
  const flowPages = /* @__PURE__ */ new Map();
46770
46654
  const flowFrame = (source, frame, targets) => {
46771
46655
  const parsed = parseFrameName(frame.name, targets);
@@ -46783,6 +46667,7 @@ ${frame.page}`;
46783
46667
  for (const product of products) {
46784
46668
  const file2 = await options.rest.file(product.key, INDEX_DEPTH);
46785
46669
  versions[product.key] = file2.version;
46670
+ names.set(product.key, file2.name);
46786
46671
  const listed2 = listFrames(file2.document);
46787
46672
  for (const frame of listed2.filter((candidate) => prototypes.has(candidate.page)))
46788
46673
  flowFrame(product.key, frame, product.targets);
@@ -46806,6 +46691,7 @@ ${frame.page}`;
46806
46691
  for (const file2 of legacy ? [] : graph.prototypes) {
46807
46692
  const read = await options.rest.file(file2.key, INDEX_DEPTH);
46808
46693
  versions[file2.key] = read.version;
46694
+ names.set(file2.key, read.name);
46809
46695
  const targets = graph.targetsOf(file2.family);
46810
46696
  for (const frame of listFrames(read.document).filter((candidate) => holdsScreens(candidate.page)))
46811
46697
  flowFrame(file2.key, frame, targets);
@@ -46813,6 +46699,10 @@ ${frame.page}`;
46813
46699
  unmapped.fileVersion = Object.values(versions).join(",");
46814
46700
  if (!legacy)
46815
46701
  unmapped.components = components;
46702
+ if (!legacy) {
46703
+ await mkdir2(paths.outline, { recursive: true });
46704
+ await writeFile3(paths.files, (0, import_yaml2.stringify)(await readFileNames(options.rest, config2, names)));
46705
+ }
46816
46706
  const outlined = new Set(existing.outlines.flatMap((outline) => outline.frames.map((frame) => frame.nodeId)));
46817
46707
  const drawnScreens = new Set([...resolved.values()].map(({ ref }) => `${ref.feature}/${ref.id}`));
46818
46708
  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}`));
@@ -46933,7 +46823,7 @@ var WriteOutlineHandler = class WriteOutlineHandler2 {
46933
46823
  this.rest = rest;
46934
46824
  }
46935
46825
  handle(command) {
46936
- return runOutline({ root: command.root, rest: this.rest, frames: command.frames });
46826
+ return runOutline({ root: command.root, config: command.config, rest: this.rest, frames: command.frames });
46937
46827
  }
46938
46828
  };
46939
46829
  WriteOutlineHandler = __decorate2([
@@ -46944,10 +46834,389 @@ WriteOutlineHandler = __decorate2([
46944
46834
  // packages/zaku/dist/lib/handlers/write-schemas.js
46945
46835
  var import_tsyringe5 = __toESM(require_cjs(), 1);
46946
46836
 
46837
+ // packages/zaku/dist/lib/schema/zaku-config.js
46838
+ import { posix } from "node:path";
46839
+
46840
+ // packages/zaku/dist/lib/domain/copy-defaults.js
46841
+ var DEFAULT_COPY = { locales: ["en"], currencies: [] };
46842
+
46843
+ // packages/zaku/dist/lib/schema/zaku-config.js
46844
+ var PLATFORM_FAMILIES = ["web", "ios", "android", "macos", "windows", "gnome", "kde"];
46845
+ var LAYERS = ["foundations", "components"];
46846
+ var BRANCHES = ["ui", "native"];
46847
+ var DEFAULT_INTERACTIVE = [
46848
+ "Button",
46849
+ "Button Group",
46850
+ "Checkbox",
46851
+ "Combobox",
46852
+ "Dropdown Menu",
46853
+ "Input",
46854
+ "Input Group",
46855
+ "Input OTP",
46856
+ "Native Select",
46857
+ "Pagination",
46858
+ "Radio Group",
46859
+ "Select",
46860
+ "Slider",
46861
+ "Switch",
46862
+ "Tabs",
46863
+ "Textarea",
46864
+ "Toggle",
46865
+ "Toggle Group"
46866
+ ];
46867
+ var id = external_exports.string().regex(/^[a-z][a-z0-9-]*$/, "lowercase letters, digits and dashes, starting with a letter");
46868
+ var targetSchema = external_exports.object({
46869
+ id,
46870
+ family: external_exports.enum(PLATFORM_FAMILIES),
46871
+ 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"),
46872
+ /** The frame size every frame of this target is drawn at; the frame check compares it. */
46873
+ size: external_exports.object({ width: external_exports.number().positive(), height: external_exports.number().positive() }).strict().optional(),
46874
+ /** The renderer whose libraries its frames draw from; `defaultBranch` in the design graph gives it when left out. */
46875
+ branch: external_exports.enum(BRANCHES).optional()
46876
+ }).strict();
46877
+ var DEFAULT_SYSTEM_BARS = {
46878
+ web: [],
46879
+ ios: [["Status Bar"], ["Home Indicator"]],
46880
+ android: [["Status Bar"], ["Navigation Bar", "Gesture Handle"]],
46881
+ macos: [["Window Controls"]],
46882
+ windows: [["Title Bar"]],
46883
+ gnome: [["Header Bar"]],
46884
+ kde: [["Title Bar"]]
46885
+ };
46886
+ function systemBarNames(config2) {
46887
+ const bars = { ...DEFAULT_SYSTEM_BARS, ...config2.systemBars };
46888
+ return new Set(Object.values(bars).flat(2));
46889
+ }
46890
+ var budgetSchema = external_exports.object({
46891
+ mcpPerDay: external_exports.number().int().positive().default(200),
46892
+ mcpPerRun: external_exports.number().int().positive().default(30),
46893
+ reserve: external_exports.number().min(0).max(1).default(0.2)
46894
+ }).strict();
46895
+ var fileKey = external_exports.string().min(1);
46896
+ var kitSchema = external_exports.object({ key: fileKey, family: external_exports.enum(PLATFORM_FAMILIES) }).strict();
46897
+ var librarySchema = external_exports.object({
46898
+ key: fileKey,
46899
+ layer: external_exports.enum(LAYERS),
46900
+ branches: external_exports.array(external_exports.enum(BRANCHES)).min(1),
46901
+ /** The libraries and kits this one is built on, by key. */
46902
+ extends: external_exports.array(fileKey).default([])
46903
+ }).strict();
46904
+ var productSchema = external_exports.object({ key: fileKey, family: external_exports.enum(PLATFORM_FAMILIES) }).strict();
46905
+ var prototypeSchema = external_exports.object({ key: fileKey, family: external_exports.enum(PLATFORM_FAMILIES) }).strict();
46906
+ var files = external_exports.object({
46907
+ library: fileKey.optional(),
46908
+ product: fileKey.optional(),
46909
+ kits: external_exports.array(kitSchema).optional(),
46910
+ libraries: external_exports.array(librarySchema).optional(),
46911
+ products: external_exports.array(productSchema).optional(),
46912
+ prototypes: external_exports.array(prototypeSchema).optional()
46913
+ }).strict();
46914
+ function byRole(figma) {
46915
+ return [figma.kits, figma.libraries, figma.products, figma.prototypes].some((list) => list !== void 0);
46916
+ }
46917
+ var shadcnSystem = external_exports.object({
46918
+ preset: external_exports.string().min(1),
46919
+ /** The stylesheet `zaku tokens` reads, relative to the directory of the workspace config. */
46920
+ css: external_exports.string().min(1).optional()
46921
+ }).strict();
46922
+ var tokenFiles = external_exports.array(external_exports.string().min(1)).min(1);
46923
+ var resolverInputs = external_exports.record(external_exports.string(), external_exports.string());
46924
+ var dtcgSystem = external_exports.object({
46925
+ resolver: external_exports.string().min(1).optional(),
46926
+ light: external_exports.union([tokenFiles, resolverInputs]),
46927
+ dark: external_exports.union([tokenFiles, resolverInputs]),
46928
+ /** The group whose tokens are the colour roles, and the one whose tokens are the radius steps. */
46929
+ colors: external_exports.string().min(1).default("color"),
46930
+ radii: external_exports.string().min(1).default("radius"),
46931
+ /** Foreground and background token pairs a text is read in, each needing 4.5:1. */
46932
+ contrast: external_exports.array(external_exports.tuple([external_exports.string().min(1), external_exports.string().min(1)])).default([])
46933
+ }).strict().superRefine((dtcg, ctx) => {
46934
+ for (const scheme of ["light", "dark"]) {
46935
+ const files2 = Array.isArray(dtcg[scheme]);
46936
+ if (dtcg.resolver !== void 0 && files2) {
46937
+ ctx.addIssue({
46938
+ code: "custom",
46939
+ path: [scheme],
46940
+ message: "with a resolver, name the modifier contexts, not files"
46941
+ });
46942
+ }
46943
+ if (dtcg.resolver === void 0 && !files2) {
46944
+ ctx.addIssue({
46945
+ code: "custom",
46946
+ path: [scheme],
46947
+ message: "without a resolver, list the token files"
46948
+ });
46949
+ }
46950
+ }
46951
+ });
46952
+ var designSystemSchema = external_exports.union([
46953
+ external_exports.object({ shadcn: shadcnSystem }).strict(),
46954
+ external_exports.object({ dtcg: dtcgSystem }).strict()
46955
+ ]);
46956
+ var recipeSchema = external_exports.object({
46957
+ url: external_exports.url().optional(),
46958
+ dark: external_exports.enum(["class", "media"]).default("class"),
46959
+ darkClass: external_exports.string().min(1).default("dark")
46960
+ }).strict();
46961
+ function writtenInAScript(tag) {
46962
+ try {
46963
+ return new Intl.Locale(tag).maximize().script !== void 0;
46964
+ } catch {
46965
+ return false;
46966
+ }
46967
+ }
46968
+ var copySchema = external_exports.object({
46969
+ locales: external_exports.array(external_exports.string().refine(writtenInAScript, "a BCP 47 language tag, such as en or vi")).min(1).default(["en"]),
46970
+ currencies: external_exports.array(external_exports.string().refine((code) => Intl.supportedValuesOf("currency").includes(code), "an ISO 4217 code, such as VND or USD")).default([])
46971
+ }).strict();
46972
+ var configShape = external_exports.object({
46973
+ product: external_exports.string().min(1),
46974
+ designSystem: designSystemSchema,
46975
+ figma: files.default({}),
46976
+ targets: external_exports.array(targetSchema).min(1),
46977
+ modes: external_exports.record(external_exports.string(), external_exports.string()).default({}),
46978
+ interactive: external_exports.array(external_exports.string().min(1)).default([...DEFAULT_INTERACTIVE]),
46979
+ /** The bars of the families whose kit names them its own way; every family left out keeps its default. */
46980
+ systemBars: external_exports.partialRecord(external_exports.enum(PLATFORM_FAMILIES), external_exports.array(external_exports.array(external_exports.string().min(1)).min(1))).optional(),
46981
+ budget: budgetSchema.default({ mcpPerDay: 200, mcpPerRun: 30, reserve: 0.2 }),
46982
+ recipe: recipeSchema.optional(),
46983
+ copy: copySchema.default(DEFAULT_COPY)
46984
+ }).strict();
46985
+ var zakuConfigSchema = configShape.superRefine((config2, ctx) => {
46986
+ for (const key of ["id", "name"]) {
46987
+ const seen = /* @__PURE__ */ new Set();
46988
+ config2.targets.forEach((target, index) => {
46989
+ if (seen.has(target[key])) {
46990
+ ctx.addIssue({
46991
+ code: "custom",
46992
+ path: ["targets", index, key],
46993
+ message: `a second target with ${key} ${target[key]}`
46994
+ });
46995
+ }
46996
+ seen.add(target[key]);
46997
+ });
46998
+ }
46999
+ if (byRole(config2.figma))
47000
+ refineFilesByRole(config2, ctx);
47001
+ });
47002
+ function refineFilesByRole(config2, ctx) {
47003
+ const { figma } = config2;
47004
+ if (figma.library !== void 0 || figma.product !== void 0) {
47005
+ ctx.addIssue({
47006
+ code: "custom",
47007
+ path: ["figma"],
47008
+ message: "figma.library and figma.product describe one library and one product; list files by role instead"
47009
+ });
47010
+ }
47011
+ const kits = figma.kits ?? [];
47012
+ const libraries = figma.libraries ?? [];
47013
+ const products = figma.products ?? [];
47014
+ const seen = /* @__PURE__ */ new Set();
47015
+ const lists = [
47016
+ ["kits", kits],
47017
+ ["libraries", libraries],
47018
+ ["products", products],
47019
+ ["prototypes", figma.prototypes ?? []]
47020
+ ];
47021
+ for (const [list, entries] of lists) {
47022
+ entries.forEach((entry, index) => {
47023
+ if (seen.has(entry.key))
47024
+ ctx.addIssue({
47025
+ code: "custom",
47026
+ path: ["figma", list, index, "key"],
47027
+ message: `a second file with key ${entry.key}`
47028
+ });
47029
+ seen.add(entry.key);
47030
+ });
47031
+ }
47032
+ const byKey = new Map(libraries.map((library) => [library.key, library]));
47033
+ const kitKeys = new Set(kits.map((kit) => kit.key));
47034
+ libraries.forEach((library, index) => {
47035
+ const path2 = ["figma", "libraries", index, "extends"];
47036
+ for (const key of library.extends) {
47037
+ const base = byKey.get(key);
47038
+ if (!base && !kitKeys.has(key)) {
47039
+ ctx.addIssue({
47040
+ code: "custom",
47041
+ path: path2,
47042
+ message: `${library.key} extends ${key}, which is no library or kit listed`
47043
+ });
47044
+ } else if (base?.layer === "components" && library.layer === "foundations") {
47045
+ ctx.addIssue({ code: "custom", path: path2, message: `foundations ${library.key} extends components ${key}` });
47046
+ } else if (base && !base.branches.some((branch) => library.branches.includes(branch))) {
47047
+ ctx.addIssue({
47048
+ code: "custom",
47049
+ path: path2,
47050
+ message: `${library.key} extends ${key}, which holds none of its branches`
47051
+ });
47052
+ }
47053
+ }
47054
+ });
47055
+ const cycle = findCycle(libraries);
47056
+ if (cycle)
47057
+ ctx.addIssue({ code: "custom", path: ["figma", "libraries"], message: `extends comes back: ${cycle}` });
47058
+ for (const [list, entries] of [
47059
+ ["products", products],
47060
+ ["prototypes", figma.prototypes ?? []]
47061
+ ]) {
47062
+ const families2 = /* @__PURE__ */ new Set();
47063
+ entries.forEach((entry, index) => {
47064
+ if (families2.has(entry.family))
47065
+ ctx.addIssue({
47066
+ code: "custom",
47067
+ path: ["figma", list, index, "family"],
47068
+ message: `a second ${list === "products" ? "product" : "prototype"} file for ${entry.family}`
47069
+ });
47070
+ families2.add(entry.family);
47071
+ });
47072
+ }
47073
+ (figma.prototypes ?? []).forEach((prototype2, index) => {
47074
+ if (!products.some((product) => product.family === prototype2.family))
47075
+ ctx.addIssue({
47076
+ code: "custom",
47077
+ path: ["figma", "prototypes", index, "family"],
47078
+ message: `no product file for ${prototype2.family} to copy its frames from`
47079
+ });
47080
+ });
47081
+ config2.targets.forEach((target, index) => {
47082
+ const branch = target.branch ?? defaultBranch(target.family);
47083
+ const path2 = ["targets", index, "branch"];
47084
+ if (branch === void 0) {
47085
+ ctx.addIssue({ code: "custom", path: path2, message: `a ${target.family} target names its branch, ui or native` });
47086
+ } else if (libraries.length > 0 && !libraries.some((library) => library.branches.includes(branch))) {
47087
+ ctx.addIssue({ code: "custom", path: path2, message: `no library holds the ${branch} branch` });
47088
+ }
47089
+ });
47090
+ }
47091
+ function findCycle(libraries) {
47092
+ const byKey = new Map(libraries.map((library) => [library.key, library]));
47093
+ const visit2 = (key, path2) => {
47094
+ if (path2.includes(key))
47095
+ return [...path2.slice(path2.indexOf(key)), key].join(" -> ");
47096
+ for (const next of byKey.get(key)?.extends ?? []) {
47097
+ const found = visit2(next, [...path2, key]);
47098
+ if (found)
47099
+ return found;
47100
+ }
47101
+ return void 0;
47102
+ };
47103
+ for (const library of libraries) {
47104
+ const found = visit2(library.key, []);
47105
+ if (found)
47106
+ return found;
47107
+ }
47108
+ return void 0;
47109
+ }
47110
+ var SHARED_KEYS = ["designSystem", "modes", "copy", "interactive", "systemBars", "budget"];
47111
+ var sharedShape = {
47112
+ designSystem: designSystemSchema.optional(),
47113
+ modes: configShape.shape.modes.unwrap().optional(),
47114
+ copy: copySchema.optional(),
47115
+ interactive: configShape.shape.interactive.unwrap().optional(),
47116
+ systemBars: configShape.shape.systemBars,
47117
+ budget: budgetSchema.optional()
47118
+ };
47119
+ var PRODUCT_NAME = /^[A-Z0-9][A-Za-z0-9]*( [A-Z0-9][A-Za-z0-9]*)*$/;
47120
+ var workspaceProduct = external_exports.object({
47121
+ /** Title-case ASCII words: what people call the product and what its Figma files are named for. */
47122
+ name: external_exports.string().regex(PRODUCT_NAME, "title-case words of ASCII letters and digits, such as Back Office"),
47123
+ /** The directory of the product's design, relative to the config and inside its directory. */
47124
+ root: external_exports.string().min(1)
47125
+ }).strict();
47126
+ function cleanRoot(root) {
47127
+ if (root.includes("\\"))
47128
+ return { problem: "use forward slashes" };
47129
+ if (root.startsWith("/"))
47130
+ return { problem: "a root is relative to the config, not absolute" };
47131
+ const path2 = posix.normalize(root).replace(/\/$/, "");
47132
+ if (path2 === ".." || path2.startsWith("../"))
47133
+ return { problem: "a root stays inside the config's directory" };
47134
+ return { path: path2 };
47135
+ }
47136
+ function overlaps(a, b) {
47137
+ return a === "." || b === "." || a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
47138
+ }
47139
+ var workspaceConfigSchema = external_exports.object({
47140
+ ...sharedShape,
47141
+ products: external_exports.array(workspaceProduct).min(1).optional(),
47142
+ product: external_exports.string().min(1).optional(),
47143
+ figma: files.optional(),
47144
+ targets: external_exports.array(targetSchema).optional(),
47145
+ recipe: recipeSchema.optional()
47146
+ }).strict().superRefine((config2, ctx) => {
47147
+ const { products, ...single } = config2;
47148
+ if (products === void 0) {
47149
+ const result = zakuConfigSchema.safeParse(single);
47150
+ if (!result.success) {
47151
+ for (const issue2 of result.error.issues) {
47152
+ ctx.addIssue({ code: "custom", path: issue2.path, message: issue2.message });
47153
+ }
47154
+ }
47155
+ return;
47156
+ }
47157
+ for (const key of ["product", "figma", "targets", "recipe"]) {
47158
+ if (config2[key] !== void 0) {
47159
+ ctx.addIssue({
47160
+ code: "custom",
47161
+ path: [key],
47162
+ message: `a workspace that lists products keeps ${key} in each product`
47163
+ });
47164
+ }
47165
+ }
47166
+ const names = /* @__PURE__ */ new Set();
47167
+ const roots = [];
47168
+ products.forEach((product, index) => {
47169
+ if (names.has(product.name)) {
47170
+ ctx.addIssue({
47171
+ code: "custom",
47172
+ path: ["products", index, "name"],
47173
+ message: `a second product named ${product.name}`
47174
+ });
47175
+ }
47176
+ names.add(product.name);
47177
+ const root = cleanRoot(product.root);
47178
+ if ("problem" in root) {
47179
+ ctx.addIssue({ code: "custom", path: ["products", index, "root"], message: root.problem });
47180
+ return;
47181
+ }
47182
+ const clash = roots.find((other) => overlaps(other, root.path));
47183
+ if (clash !== void 0) {
47184
+ ctx.addIssue({
47185
+ code: "custom",
47186
+ path: ["products", index, "root"],
47187
+ message: `${product.root} overlaps ${clash}, which another product holds`
47188
+ });
47189
+ }
47190
+ roots.push(root.path);
47191
+ });
47192
+ });
47193
+ var productConfigSchema = external_exports.object({
47194
+ ...sharedShape,
47195
+ figma: files.default({}),
47196
+ targets: external_exports.array(targetSchema).min(1),
47197
+ recipe: recipeSchema.optional()
47198
+ }).strict();
47199
+ function mergeProductConfig(shared, name, product) {
47200
+ const own2 = productConfigSchema.safeParse(product);
47201
+ if (!own2.success)
47202
+ throw new DesignFileInvalid(`zaku.yaml of ${name}`, issueLines(own2.error));
47203
+ const merged = { ...own2.data, product: name };
47204
+ for (const key of SHARED_KEYS)
47205
+ merged[key] = own2.data[key] ?? shared[key];
47206
+ const result = zakuConfigSchema.safeParse(merged);
47207
+ if (!result.success)
47208
+ throw new DesignFileInvalid(`zaku.yaml of ${name}`, issueLines(result.error));
47209
+ return result.data;
47210
+ }
47211
+ function issueLines(error62) {
47212
+ return error62.issues.map((issue2) => `${issue2.path.map(String).join(".") || "(root)"}: ${issue2.message}`);
47213
+ }
47214
+
46947
47215
  // packages/zaku/dist/lib/schema/json-schemas.js
46948
47216
  function designJsonSchemas() {
46949
47217
  return {
46950
47218
  "zaku.schema.json": external_exports.toJSONSchema(zakuConfigSchema, { io: "input" }),
47219
+ "zaku-config.schema.json": external_exports.toJSONSchema(workspaceConfigSchema, { io: "input" }),
46951
47220
  "map.schema.json": external_exports.toJSONSchema(featureMapSchema, { io: "input" })
46952
47221
  };
46953
47222
  }
@@ -46991,22 +47260,8 @@ RecordBudgetCallsHandler = __decorate2([
46991
47260
  __param2(0, (0, import_tsyringe6.inject)(TOKENS.BUDGET_LEDGER))
46992
47261
  ], RecordBudgetCallsHandler);
46993
47262
 
46994
- // packages/zaku/dist/lib/repositories/design-config-repository.js
46995
- var import_tsyringe7 = __toESM(require_cjs(), 1);
46996
- var DesignConfigRepository = class DesignConfigRepository2 {
46997
- read(root) {
46998
- return readDesignFile(designPaths(root).config, zakuConfigSchema);
46999
- }
47000
- readOptional(root) {
47001
- return readOptionalDesignFile(designPaths(root).config, zakuConfigSchema);
47002
- }
47003
- };
47004
- DesignConfigRepository = __decorate2([
47005
- (0, import_tsyringe7.injectable)({ token: TOKENS.DESIGN_CONFIG_REPOSITORY })
47006
- ], DesignConfigRepository);
47007
-
47008
47263
  // packages/zaku/dist/lib/repositories/token-document-repository.js
47009
- var import_tsyringe8 = __toESM(require_cjs(), 1);
47264
+ var import_tsyringe7 = __toESM(require_cjs(), 1);
47010
47265
  var TokenDocumentRepository = class TokenDocumentRepository2 {
47011
47266
  async save(root, doc) {
47012
47267
  const path2 = designPaths(root).tokens;
@@ -47015,11 +47270,11 @@ var TokenDocumentRepository = class TokenDocumentRepository2 {
47015
47270
  }
47016
47271
  };
47017
47272
  TokenDocumentRepository = __decorate2([
47018
- (0, import_tsyringe8.injectable)({ token: TOKENS.TOKEN_DOCUMENT_REPOSITORY })
47273
+ (0, import_tsyringe7.injectable)({ token: TOKENS.TOKEN_DOCUMENT_REPOSITORY })
47019
47274
  ], TokenDocumentRepository);
47020
47275
 
47021
47276
  // packages/zaku/dist/lib/repositories/recipe-repository.js
47022
- var import_tsyringe9 = __toESM(require_cjs(), 1);
47277
+ var import_tsyringe8 = __toESM(require_cjs(), 1);
47023
47278
  var RecipeRepository = class RecipeRepository2 {
47024
47279
  async save(root, doc) {
47025
47280
  const path2 = designPaths(root).recipe;
@@ -47028,11 +47283,11 @@ var RecipeRepository = class RecipeRepository2 {
47028
47283
  }
47029
47284
  };
47030
47285
  RecipeRepository = __decorate2([
47031
- (0, import_tsyringe9.injectable)({ token: TOKENS.RECIPE_REPOSITORY })
47286
+ (0, import_tsyringe8.injectable)({ token: TOKENS.RECIPE_REPOSITORY })
47032
47287
  ], RecipeRepository);
47033
47288
 
47034
47289
  // packages/zaku/dist/lib/repositories/json-schema-files.js
47035
- var import_tsyringe10 = __toESM(require_cjs(), 1);
47290
+ var import_tsyringe9 = __toESM(require_cjs(), 1);
47036
47291
  import { mkdir as mkdir3 } from "node:fs/promises";
47037
47292
  import { join as join7 } from "node:path";
47038
47293
  var JsonSchemaFiles = class JsonSchemaFiles2 {
@@ -47048,11 +47303,11 @@ var JsonSchemaFiles = class JsonSchemaFiles2 {
47048
47303
  }
47049
47304
  };
47050
47305
  JsonSchemaFiles = __decorate2([
47051
- (0, import_tsyringe10.injectable)({ token: TOKENS.JSON_SCHEMA_FILES })
47306
+ (0, import_tsyringe9.injectable)({ token: TOKENS.JSON_SCHEMA_FILES })
47052
47307
  ], JsonSchemaFiles);
47053
47308
 
47054
47309
  // packages/zaku/dist/lib/repositories/source-files.js
47055
- var import_tsyringe11 = __toESM(require_cjs(), 1);
47310
+ var import_tsyringe10 = __toESM(require_cjs(), 1);
47056
47311
  import { readFile as readFile4 } from "node:fs/promises";
47057
47312
  var SourceFiles = class SourceFiles2 {
47058
47313
  readText(path2) {
@@ -47063,7 +47318,7 @@ var SourceFiles = class SourceFiles2 {
47063
47318
  }
47064
47319
  };
47065
47320
  SourceFiles = __decorate2([
47066
- (0, import_tsyringe11.injectable)({ token: TOKENS.SOURCE_FILES })
47321
+ (0, import_tsyringe10.injectable)({ token: TOKENS.SOURCE_FILES })
47067
47322
  ], SourceFiles);
47068
47323
 
47069
47324
  // packages/zaku/dist/lib/repositories/budget-ledger.js
@@ -47165,81 +47420,6 @@ function printGraph(config2, snapshots) {
47165
47420
  return lines;
47166
47421
  }
47167
47422
 
47168
- // packages/zaku/dist/lib/adapters/figma-rest.js
47169
- var FigmaRateLimited = class extends Error {
47170
- url;
47171
- constructor(url2) {
47172
- super(`Figma answered 429 twice for ${url2}; wait and run again`);
47173
- this.url = url2;
47174
- this.name = "FigmaRateLimited";
47175
- }
47176
- };
47177
- var FigmaRequestFailed = class extends Error {
47178
- url;
47179
- status;
47180
- constructor(url2, status, body) {
47181
- super(`Figma answered ${status} for ${url2}: ${body.slice(0, 200)}`);
47182
- this.url = url2;
47183
- this.status = status;
47184
- this.name = "FigmaRequestFailed";
47185
- }
47186
- };
47187
- var BASE = "https://api.figma.com/v1";
47188
- var BATCH = 50;
47189
- var MAX_WAIT_MS = 12e4;
47190
- function createFigmaRest(options) {
47191
- const send = options.fetch ?? globalThis.fetch;
47192
- const sleep = options.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
47193
- const onCall = options.onCall ?? (() => void 0);
47194
- async function call(url2) {
47195
- for (let attempt = 0; attempt < 2; attempt += 1) {
47196
- const res = await send(url2, { headers: { "X-Figma-Token": options.token } });
47197
- onCall();
47198
- if (res.status === 429) {
47199
- if (attempt === 1)
47200
- break;
47201
- const seconds = Number(res.headers.get("Retry-After") ?? "60");
47202
- await sleep(Math.min(Number.isFinite(seconds) ? seconds * 1e3 : 6e4, MAX_WAIT_MS));
47203
- continue;
47204
- }
47205
- if (!res.ok)
47206
- throw new FigmaRequestFailed(url2, res.status, await res.text());
47207
- return await res.json();
47208
- }
47209
- throw new FigmaRateLimited(url2);
47210
- }
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
- }
47216
- return {
47217
- file: (fileKey3, depth) => call(`${BASE}/files/${fileKey3}?depth=${depth}`),
47218
- async nodes(fileKey3, ids) {
47219
- const merged = { version: "", nodes: {} };
47220
- for (const chunk of chunks(ids)) {
47221
- const part = await call(`${BASE}/files/${fileKey3}/nodes?ids=${encodeURIComponent(chunk.join(","))}`);
47222
- merged.version = part.version;
47223
- Object.assign(merged.nodes, part.nodes);
47224
- }
47225
- return merged;
47226
- },
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) };
47230
- },
47231
- components,
47232
- async images(fileKey3, ids) {
47233
- const merged = {};
47234
- for (const chunk of chunks(ids)) {
47235
- const part = await call(`${BASE}/images/${fileKey3}?ids=${encodeURIComponent(chunk.join(","))}&format=png&scale=1`);
47236
- Object.assign(merged, part.images);
47237
- }
47238
- return merged;
47239
- }
47240
- };
47241
- }
47242
-
47243
47423
  // packages/zaku/dist/lib/adapters/figma-variables-script.js
47244
47424
  async function exportVariables(figma, options) {
47245
47425
  const collections = [...await figma.variables.getLocalVariableCollectionsAsync()];
@@ -47944,9 +48124,9 @@ async function startPluginBridge(options = {}) {
47944
48124
  if (!socket)
47945
48125
  return Promise.resolve({ ok: false, error: `file ${fileKey3} is not open in Figma with the zaku plugin` });
47946
48126
  const id2 = `c${++next}`;
47947
- return new Promise((resolve3) => {
48127
+ return new Promise((resolve4) => {
47948
48128
  const timer = setTimeout(() => settle2(id2, { ok: false, error: `no answer from the zaku panel in ${timeoutMs / 1e3} s; ${PARTIAL}` }), timeoutMs);
47949
- pending.set(id2, { socket, resolve: resolve3, timer });
48129
+ pending.set(id2, { socket, resolve: resolve4, timer });
47950
48130
  send(socket, { ...message, id: id2 });
47951
48131
  });
47952
48132
  }
@@ -48265,6 +48445,71 @@ var copy = ({ config: config2, maps, outlines }) => {
48265
48445
  return { findings };
48266
48446
  };
48267
48447
 
48448
+ // packages/zaku/dist/lib/domain/file-labels.js
48449
+ var FAMILY_LABELS = {
48450
+ web: "Web",
48451
+ ios: "iOS",
48452
+ android: "Android",
48453
+ macos: "macOS",
48454
+ windows: "Windows",
48455
+ gnome: "Linux",
48456
+ kde: "Linux KDE"
48457
+ };
48458
+ var BRANCH_LABELS = { ui: "UI", native: "Native" };
48459
+ function branchLabel(branches) {
48460
+ const [only2] = branches;
48461
+ return branches.length === 1 && only2 ? BRANCH_LABELS[only2] : null;
48462
+ }
48463
+ function expectedFileName(product, role) {
48464
+ if (role.kind !== "library") {
48465
+ const family = `${product} - ${FAMILY_LABELS[role.family]}`;
48466
+ return role.kind === "prototype" ? `${family} Prototype` : family;
48467
+ }
48468
+ if (role.layer === "foundations")
48469
+ return null;
48470
+ const branch = branchLabel(role.branches);
48471
+ return branch ? `${product} - ${branch} Components` : `${product} - Components`;
48472
+ }
48473
+ function fileRoleLabel(role) {
48474
+ if (role.kind !== "library")
48475
+ return `${FAMILY_LABELS[role.family]} ${role.kind} file`;
48476
+ const branch = branchLabel(role.branches);
48477
+ return branch ? `${branch} ${role.layer} library` : `${role.layer} library`;
48478
+ }
48479
+
48480
+ // packages/zaku/dist/lib/domain/checks/file-names.js
48481
+ var fileNames = (input2) => {
48482
+ if (input2.fileNames === null)
48483
+ return { findings: [] };
48484
+ const graph = designGraph(input2.config);
48485
+ if (graph.legacy)
48486
+ return { findings: [] };
48487
+ const roles = [
48488
+ ...graph.products.map((file2) => [file2.key, { kind: "product", family: file2.family }]),
48489
+ ...graph.prototypes.map((file2) => [file2.key, { kind: "prototype", family: file2.family }]),
48490
+ ...graph.libraries.map((file2) => [
48491
+ file2.key,
48492
+ { kind: "library", layer: file2.layer, branches: file2.branches }
48493
+ ])
48494
+ ];
48495
+ const names = new Map(input2.fileNames.map((file2) => [file2.key, file2.name]));
48496
+ const { product } = input2.config;
48497
+ const findings = [];
48498
+ for (const [key, role] of roles) {
48499
+ const actual = names.get(key);
48500
+ const expected = expectedFileName(product, role);
48501
+ if (actual === void 0 || expected === null || actual === expected)
48502
+ continue;
48503
+ const label2 = fileRoleLabel(role);
48504
+ const article = /^(iOS|Android) /.test(label2) ? "an" : "a";
48505
+ findings.push({
48506
+ check: "file-names",
48507
+ message: `${key} is named ${actual}; ${article} ${label2} of ${product} is named ${expected}; rename it in Figma, then run zaku outline`
48508
+ });
48509
+ }
48510
+ return { findings };
48511
+ };
48512
+
48268
48513
  // packages/zaku/dist/lib/domain/checks/coverage.js
48269
48514
  function frameKey(feature, name) {
48270
48515
  return `${feature}
@@ -49462,6 +49707,7 @@ var ALL_CHECKS = {
49462
49707
  "system-bars": systemBars,
49463
49708
  placement,
49464
49709
  naming,
49710
+ "file-names": fileNames,
49465
49711
  component,
49466
49712
  prototype
49467
49713
  };
@@ -49502,6 +49748,7 @@ var CHECK_IDS = [
49502
49748
  "empty",
49503
49749
  "documentation",
49504
49750
  "naming",
49751
+ "file-names",
49505
49752
  "component",
49506
49753
  "prototype"
49507
49754
  ];
@@ -49619,17 +49866,10 @@ async function optional3(read, findings) {
49619
49866
  throw error62;
49620
49867
  }
49621
49868
  }
49622
- async function loadDesign(root) {
49623
- const paths = designPaths(root);
49869
+ async function loadDesign(product) {
49870
+ const paths = designPaths(product.root);
49624
49871
  const findings = [];
49625
- let config2;
49626
- try {
49627
- config2 = await readDesignFile(paths.config, zakuConfigSchema);
49628
- } catch (error62) {
49629
- if (error62 instanceof DesignFileInvalid)
49630
- return { input: null, findings: [schemaFinding(error62)] };
49631
- throw error62;
49632
- }
49872
+ const { config: config2 } = product;
49633
49873
  const { maps, errors: mapErrors } = await loadMaps(paths.maps);
49634
49874
  findings.push(...mapErrors.map(schemaFinding));
49635
49875
  for (const key of indexTitles(maps).duplicates) {
@@ -49640,7 +49880,7 @@ async function loadDesign(root) {
49640
49880
  message: `two screens share the title ${title}; a frame name could not tell them apart`
49641
49881
  });
49642
49882
  }
49643
- const { outlines, flows, unmapped, errors: outlineErrors } = await loadOutlines(paths.outline);
49883
+ const { outlines, flows, unmapped, files: files2, errors: outlineErrors } = await loadOutlines(paths.outline);
49644
49884
  findings.push(...outlineErrors.map(schemaFinding));
49645
49885
  const { libraries, kits, errors: snapshotErrors } = await readDesignSnapshots(paths, config2);
49646
49886
  findings.push(...snapshotErrors.map(schemaFinding));
@@ -49657,15 +49897,80 @@ async function loadDesign(root) {
49657
49897
  libraries,
49658
49898
  kits,
49659
49899
  tokens,
49660
- recipe: recipe2
49900
+ recipe: recipe2,
49901
+ fileNames: files2?.files ?? null
49661
49902
  },
49662
49903
  findings
49663
49904
  };
49664
49905
  }
49665
49906
 
49907
+ // packages/zaku/dist/lib/repositories/workspace.js
49908
+ import { readFile as readFile6 } from "node:fs/promises";
49909
+ import { dirname as dirname4, join as join9, resolve as resolve2 } from "node:path";
49910
+ var WORKSPACE_CONFIG = "zaku.config.yaml";
49911
+ async function readOptionalText(path2) {
49912
+ try {
49913
+ return await readFile6(path2, "utf8");
49914
+ } catch (error62) {
49915
+ if (error62.code === "ENOENT")
49916
+ return null;
49917
+ throw error62;
49918
+ }
49919
+ }
49920
+ function only(products, name) {
49921
+ if (name === void 0)
49922
+ return products;
49923
+ const named = products.filter((product) => product.name === name);
49924
+ if (named.length === 0)
49925
+ throw new ProductUnknown(name, products.map((product) => product.name));
49926
+ return named;
49927
+ }
49928
+ async function readWorkspace(cwd, options) {
49929
+ const configPath = resolve2(cwd, options.config ?? WORKSPACE_CONFIG);
49930
+ const text = await readOptionalText(configPath);
49931
+ if (text === null) {
49932
+ if (options.config !== void 0)
49933
+ throw new WorkspaceConfigMissing(configPath, null);
49934
+ const root = resolve2(cwd, "docs", "design");
49935
+ const legacyPath = designPaths(root).config;
49936
+ const config2 = await readOptionalDesignFile(legacyPath, zakuConfigSchema);
49937
+ if (config2 === null)
49938
+ throw new WorkspaceConfigMissing(configPath, legacyPath);
49939
+ const products2 = only([{ name: config2.product, root, config: config2 }], options.product);
49940
+ return { configPath: null, dir: resolve2(cwd), products: products2, legacy: true, budget: config2.budget };
49941
+ }
49942
+ const dir = dirname4(configPath);
49943
+ const workspace = parseDesignFile(configPath, text, workspaceConfigSchema);
49944
+ if (workspace.products === void 0) {
49945
+ const config2 = parseDesignFile(configPath, text, zakuConfigSchema);
49946
+ const products2 = only([{ name: config2.product, root: join9(dir, "design"), config: config2 }], options.product);
49947
+ return { configPath, dir, products: products2, legacy: false, budget: config2.budget };
49948
+ }
49949
+ const listed = workspace.products.map((product) => ({ name: product.name, root: resolve2(dir, product.root) }));
49950
+ const products = [];
49951
+ for (const { name, root } of only(listed, options.product)) {
49952
+ const path2 = designPaths(root).config;
49953
+ try {
49954
+ const own2 = await readOptionalDesignFile(path2, external_exports.unknown());
49955
+ if (own2 === null)
49956
+ throw new ProductConfigMissing(path2, name);
49957
+ products.push({ name, root, config: mergeProductConfig(workspace, name, own2) });
49958
+ } catch (error62) {
49959
+ if (error62 instanceof ProductConfigMissing)
49960
+ products.push({ name, root, refusal: error62 });
49961
+ else if (error62 instanceof DesignFileInvalid) {
49962
+ const refusal = error62.path === path2 ? error62 : new DesignFileInvalid(path2, error62.issues);
49963
+ products.push({ name, root, refusal });
49964
+ } else
49965
+ throw error62;
49966
+ }
49967
+ }
49968
+ return { configPath, dir, products, legacy: false, budget: workspace.budget ?? budgetSchema.parse({}) };
49969
+ }
49970
+
49666
49971
  // apps/zaku-cli/src/dependencies.ts
49667
49972
  function compose(seams) {
49668
- const scope = import_tsyringe12.container.createChildContainer();
49973
+ const scope = import_tsyringe11.container.createChildContainer();
49669
49974
  scope.register(TOKENS.CLOCK, { useValue: { now: seams.now } });
49670
49975
  scope.register(TOKENS.RUN_COMMAND, { useValue: seams.run });
49671
49976
  scope.register(TOKENS.BUDGET_LEDGER, {
@@ -49694,6 +49999,9 @@ function refusalOf(error62) {
49694
49999
  if (error62 instanceof FigmaFileKeyMissing) return { code: 3, line: error62.message };
49695
50000
  if (error62 instanceof FigmaTokenMissing) return { code: 3, line: error62.message };
49696
50001
  if (error62 instanceof LibraryKeyMissing) return { code: 3, line: error62.message };
50002
+ if (error62 instanceof WorkspaceConfigMissing) return { code: 3, line: error62.message };
50003
+ if (error62 instanceof ProductConfigMissing) return { code: 3, line: error62.message };
50004
+ if (error62 instanceof ProductUnknown || error62 instanceof ProductRequired) return { code: 3, line: error62.message };
49697
50005
  if (error62 instanceof RecipePageFailed) return { code: 2, line: `zaku recipe: ${error62.message}` };
49698
50006
  return null;
49699
50007
  }
@@ -57203,7 +57511,7 @@ var Protocol = class {
57203
57511
  return;
57204
57512
  }
57205
57513
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
57206
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
57514
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
57207
57515
  options?.signal?.throwIfAborted();
57208
57516
  }
57209
57517
  } catch (error62) {
@@ -57220,7 +57528,7 @@ var Protocol = class {
57220
57528
  */
57221
57529
  request(request, resultSchema, options) {
57222
57530
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
57223
- return new Promise((resolve3, reject) => {
57531
+ return new Promise((resolve4, reject) => {
57224
57532
  const earlyReject = (error62) => {
57225
57533
  reject(error62);
57226
57534
  };
@@ -57298,7 +57606,7 @@ var Protocol = class {
57298
57606
  if (!parseResult.success) {
57299
57607
  reject(parseResult.error);
57300
57608
  } else {
57301
- resolve3(parseResult.data);
57609
+ resolve4(parseResult.data);
57302
57610
  }
57303
57611
  } catch (error62) {
57304
57612
  reject(error62);
@@ -57560,12 +57868,12 @@ var Protocol = class {
57560
57868
  }
57561
57869
  } catch {
57562
57870
  }
57563
- return new Promise((resolve3, reject) => {
57871
+ return new Promise((resolve4, reject) => {
57564
57872
  if (signal.aborted) {
57565
57873
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
57566
57874
  return;
57567
57875
  }
57568
- const timeoutId = setTimeout(resolve3, interval);
57876
+ const timeoutId = setTimeout(resolve4, interval);
57569
57877
  signal.addEventListener("abort", () => {
57570
57878
  clearTimeout(timeoutId);
57571
57879
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -58705,7 +59013,7 @@ var McpServer = class {
58705
59013
  let task = createTaskResult.task;
58706
59014
  const pollInterval = task.pollInterval ?? 5e3;
58707
59015
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
58708
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
59016
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
58709
59017
  const updatedTask = await extra.taskStore.getTask(taskId);
58710
59018
  if (!updatedTask) {
58711
59019
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -59369,12 +59677,12 @@ var StdioServerTransport = class {
59369
59677
  this.onclose?.();
59370
59678
  }
59371
59679
  send(message) {
59372
- return new Promise((resolve3) => {
59680
+ return new Promise((resolve4) => {
59373
59681
  const json2 = serializeMessage(message);
59374
59682
  if (this._stdout.write(json2)) {
59375
- resolve3();
59683
+ resolve4();
59376
59684
  } else {
59377
- this._stdout.once("drain", resolve3);
59685
+ this._stdout.once("drain", resolve4);
59378
59686
  }
59379
59687
  });
59380
59688
  }
@@ -59383,11 +59691,11 @@ var StdioServerTransport = class {
59383
59691
  // apps/zaku-cli/src/mcp/server.ts
59384
59692
  import { mkdir as mkdir5 } from "node:fs/promises";
59385
59693
  import { tmpdir } from "node:os";
59386
- import { join as join11 } from "node:path";
59694
+ import { join as join12 } from "node:path";
59387
59695
 
59388
59696
  // apps/zaku-cli/src/mcp/screenshots.ts
59389
59697
  import { readdir as readdir4, rm as rm3, stat } from "node:fs/promises";
59390
- import { join as join9 } from "node:path";
59698
+ import { join as join10 } from "node:path";
59391
59699
  var KEPT_MS = 24 * 60 * 60 * 1e3;
59392
59700
  async function pruneScreenshots(dir, now) {
59393
59701
  let names;
@@ -59397,7 +59705,7 @@ async function pruneScreenshots(dir, now) {
59397
59705
  return;
59398
59706
  }
59399
59707
  for (const name of names.filter((entry) => entry.endsWith(".png"))) {
59400
- const path2 = join9(dir, name);
59708
+ const path2 = join10(dir, name);
59401
59709
  const written = await stat(path2).then(
59402
59710
  (stats) => stats.mtimeMs,
59403
59711
  () => now
@@ -59408,7 +59716,7 @@ async function pruneScreenshots(dir, now) {
59408
59716
 
59409
59717
  // apps/zaku-cli/src/mcp/tools.ts
59410
59718
  import { writeFile as writeFile5 } from "node:fs/promises";
59411
- import { join as join10 } from "node:path";
59719
+ import { join as join11 } from "node:path";
59412
59720
  var fileKey2 = external_exports.string().min(1).regex(/^[0-9a-zA-Z]{22,128}$/).describe(
59413
59721
  "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."
59414
59722
  );
@@ -59452,7 +59760,7 @@ function registerFigmaTools(server, bridge, options) {
59452
59760
  options.timeoutMs
59453
59761
  );
59454
59762
  if (!answer.ok) return { isError: true, content: [{ type: "text", text: answer.error }] };
59455
- const path2 = join10(options.imageDir, `${id2.replace(":", "-")}-${Date.now()}.png`);
59763
+ const path2 = join11(options.imageDir, `${id2.replace(":", "-")}-${Date.now()}.png`);
59456
59764
  await writeFile5(path2, Buffer.from(answer.png, "base64"));
59457
59765
  const text = JSON.stringify({
59458
59766
  path: path2,
@@ -59478,7 +59786,7 @@ async function serveMcp(log) {
59478
59786
  log(
59479
59787
  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}`
59480
59788
  );
59481
- const imageDir = join11(tmpdir(), "zaku-screenshots");
59789
+ const imageDir = join12(tmpdir(), "zaku-screenshots");
59482
59790
  await mkdir5(imageDir, { recursive: true });
59483
59791
  await pruneScreenshots(imageDir, Date.now());
59484
59792
  const server = new McpServer({ name: "zaku", version: "1" });
@@ -59495,59 +59803,175 @@ async function serveMcp(log) {
59495
59803
 
59496
59804
  // apps/zaku-cli/src/cli.ts
59497
59805
  var USE_FIGMA_CODE_LIMIT = 5e4;
59498
- var USAGE = "usage: zaku <check|outline|tokens|recipe|library|libraries|rules|budget|schema|mcp> [--root docs/design]";
59499
- async function sendOrRefuse(cli, io, finish, command) {
59500
- try {
59501
- return await cli.bus.send(command);
59502
- } catch (error62) {
59503
- const refusal = refusalOf(error62);
59504
- if (!refusal) throw error62;
59505
- io.err(refusal.line);
59506
- finish(refusal.code);
59507
- return null;
59508
- }
59509
- }
59806
+ var USAGE = "usage: zaku <check|outline|tokens|recipe|library|libraries|products|rules|budget|schema|mcp> [--config zaku.config.yaml] [--product <name>]";
59807
+ var LEGACY_WARNING = "zaku: read docs/design/zaku.yaml; move it to zaku.config.yaml at the repository root and the design to design/";
59510
59808
  async function recordRestCalls(cli, io) {
59511
59809
  const count = cli.restCalls();
59512
59810
  if (count === 0) return;
59513
59811
  const { seat, run } = seatAndRun(io.env);
59514
59812
  await cli.bus.send(new RecordBudgetCalls({ day: today(clock.now()), seat, run, kind: "rest", count }));
59515
59813
  }
59814
+ var CONFIG_OPTION = ["--config <path>", "the workspace config, when it is not zaku.config.yaml here"];
59815
+ var PRODUCT_OPTION = ["--product <name>", "only this product, by the name the workspace config gives it"];
59816
+ function warnLegacy(io, workspace) {
59817
+ if (workspace.legacy) io.err(LEGACY_WARNING);
59818
+ }
59819
+ async function workspaceOf(io, options) {
59820
+ const workspace = await readWorkspace(io.cwd, options);
59821
+ warnLegacy(io, workspace);
59822
+ return workspace;
59823
+ }
59824
+ function oneProduct(workspace, command) {
59825
+ const [product, ...rest] = workspace.products;
59826
+ if (product === void 0 || rest.length > 0) {
59827
+ throw new ProductRequired(
59828
+ command,
59829
+ workspace.products.map((each) => each.name)
59830
+ );
59831
+ }
59832
+ if ("refusal" in product) throw product.refusal;
59833
+ return product;
59834
+ }
59835
+ function onlyWithOne(workspace, command, option, given) {
59836
+ if (given !== void 0 && workspace.products.length > 1) {
59837
+ throw new ProductRequired(
59838
+ `${command} ${option}`,
59839
+ workspace.products.map((product) => product.name)
59840
+ );
59841
+ }
59842
+ }
59843
+ function productFailure(error62) {
59844
+ const refusal = refusalOf(error62);
59845
+ if (refusal) return refusal;
59846
+ if (error62 instanceof DesignFileInvalid || error62 instanceof FigmaRequestFailed) {
59847
+ return { code: 1, line: `zaku: ${error62.message}` };
59848
+ }
59849
+ throw error62;
59850
+ }
59851
+ function reportRefusal(io, error62) {
59852
+ const failure2 = productFailure(error62);
59853
+ io.err(failure2.line);
59854
+ return failure2.code;
59855
+ }
59856
+ async function forEachProduct(io, workspace, run) {
59857
+ let code = 0;
59858
+ for (const product of workspace.products) {
59859
+ if (workspace.products.length > 1) io.out(`== ${product.name}`);
59860
+ let exit;
59861
+ try {
59862
+ if ("refusal" in product) throw product.refusal;
59863
+ exit = await run(product);
59864
+ } catch (error62) {
59865
+ exit = reportRefusal(io, error62);
59866
+ }
59867
+ code = Math.max(code, exit);
59868
+ }
59869
+ return code;
59870
+ }
59516
59871
  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);
59872
+ program2.command("libraries").description("print the design graph: what each target draws from, and what each file extends and is extended by").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).action(async (options) => {
59873
+ const workspace = await workspaceOf(io, options);
59874
+ finish(
59875
+ await forEachProduct(io, workspace, async ({ root, config: config2 }) => {
59876
+ for (const line of printGraph(config2, await readDesignSnapshots(designPaths(root), config2))) io.out(line);
59877
+ return 0;
59878
+ })
59879
+ );
59880
+ });
59881
+ };
59882
+ var productsCommand = (program2, io, finish) => {
59883
+ program2.command("products").description("print each product the workspace config lists, with its design directory").option(...CONFIG_OPTION).option("--json", "print them as JSON").action(async (options) => {
59884
+ const workspace = await workspaceOf(io, options.config === void 0 ? {} : { config: options.config });
59885
+ const rows = workspace.products.map((product) => ({
59886
+ name: product.name,
59887
+ root: relative3(io.cwd, product.root) || "."
59888
+ }));
59889
+ if (options.json) io.out(JSON.stringify(rows, null, 2));
59890
+ else for (const row of rows) io.out(`${row.name} ${row.root}`);
59521
59891
  finish(0);
59522
59892
  });
59523
59893
  };
59524
59894
  var schemaCommand = (program2, io, finish, cli) => {
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) => {
59526
- const written = await cli.bus.send(new WriteSchemas({ out: resolve2(io.cwd, options.out) }));
59895
+ program2.command("schema").description("write the JSON Schemas an editor reads for the design files").option("--out <dir>", "where to write them; the workspace config's directory when left out").option(...CONFIG_OPTION).action(async (options) => {
59896
+ const out = options.out !== void 0 ? resolve3(io.cwd, options.out) : options.config !== void 0 ? dirname5(resolve3(io.cwd, options.config)) : resolve3(io.cwd);
59897
+ const written = await cli.bus.send(new WriteSchemas({ out }));
59527
59898
  for (const path2 of written) io.out(`wrote ${path2}`);
59528
59899
  finish(0);
59529
59900
  });
59530
59901
  };
59902
+ function checkExit(codes) {
59903
+ const rank = (code) => code === 1 ? 2 : code === 2 ? 1 : code;
59904
+ return codes.reduce((worst, code) => rank(code) > rank(worst) ? code : worst, 0);
59905
+ }
59906
+ async function checkProduct(product, ran) {
59907
+ let loaded;
59908
+ if ("refusal" in product) {
59909
+ if (!(product.refusal instanceof DesignFileInvalid)) return { refusal: product.refusal };
59910
+ loaded = { input: null, findings: [{ check: "schema", message: product.refusal.message }] };
59911
+ } else {
59912
+ try {
59913
+ loaded = await loadDesign(product);
59914
+ } catch (error62) {
59915
+ if (!(error62 instanceof DesignFileInvalid)) throw error62;
59916
+ loaded = { input: null, findings: [{ check: "schema", message: error62.message }] };
59917
+ }
59918
+ }
59919
+ const report = loaded.input ? runChecks(loaded.input, ALL_CHECKS) : { findings: [], notRun: [], passed: [] };
59920
+ report.findings.unshift(...loaded.findings);
59921
+ if (ran) report.findings.push(...ran.findings);
59922
+ const left = ran ? leftOut(ran) : [];
59923
+ return {
59924
+ report: { ...report, ...left.length > 0 ? { leftOut: left } : {} },
59925
+ code: loaded.input ? exitCode(report) : 1
59926
+ };
59927
+ }
59531
59928
  var checkCommand = (program2, io, finish) => {
59532
- program2.command("check").description("run every check over the committed design files").option("--root <dir>", "the design directory", "docs/design").option("--json", "print the report as JSON").option("--input <rules.json>", "what use_figma returned for zaku rules script, with or without --after").action(async (options) => {
59533
- const loaded = await loadDesign(resolve2(io.cwd, options.root));
59534
- const report = loaded.input ? runChecks(loaded.input, ALL_CHECKS) : { findings: [], notRun: [], passed: [] };
59535
- report.findings.unshift(...loaded.findings);
59536
- const ran = options.input ? parseDesignFile(options.input, await readFile6(resolve2(io.cwd, options.input), "utf8"), rulesAnswerSchema) : null;
59537
- if (ran && "rulesError" in ran) {
59538
- io.err(`zaku check: the rules did not run after the write: ${ran.rulesError}`);
59929
+ program2.command("check").description("run every check over each product's committed design files").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).option("--json", "print the report as JSON, keyed by product when there are several").option("--input <rules.json>", "what use_figma returned for zaku rules script, with or without --after").action(async (options) => {
59930
+ let workspace;
59931
+ try {
59932
+ workspace = await workspaceOf(io, options);
59933
+ } catch (error62) {
59934
+ if (!(error62 instanceof DesignFileInvalid)) throw error62;
59935
+ const report = { findings: [{ check: "schema", message: error62.message }], notRun: [], passed: [] };
59936
+ if (options.json) io.out(JSON.stringify(report, null, 2));
59937
+ else for (const line of formatReport(report)) io.out(line);
59539
59938
  finish(1);
59540
59939
  return;
59541
59940
  }
59542
- if (ran) report.findings.push(...ran.findings);
59543
- const left = ran ? leftOut(ran) : [];
59544
- if (options.json) io.out(JSON.stringify({ ...report, ...left.length > 0 ? { leftOut: left } : {} }, null, 2));
59545
- else for (const line of [...left, ...formatReport(report)]) io.out(line);
59546
- finish(loaded.input ? exitCode(report) : 1);
59941
+ onlyWithOne(workspace, "zaku check", "--input", options.input);
59942
+ const answer = options.input ? parseDesignFile(options.input, await readFile7(resolve3(io.cwd, options.input), "utf8"), rulesAnswerSchema) : null;
59943
+ if (answer && "rulesError" in answer) {
59944
+ io.err(`zaku check: the rules did not run after the write: ${answer.rulesError}`);
59945
+ finish(1);
59946
+ return;
59947
+ }
59948
+ const several = workspace.products.length > 1;
59949
+ const reports = {};
59950
+ const codes = [];
59951
+ for (const product of workspace.products) {
59952
+ if (several && !options.json) io.out(`== ${product.name}`);
59953
+ const checked = await checkProduct(product, answer);
59954
+ if ("refusal" in checked) {
59955
+ const failure2 = productFailure(checked.refusal);
59956
+ io.err(failure2.line);
59957
+ codes.push(failure2.code);
59958
+ if (options.json) reports[product.name] = { refusal: failure2.line };
59959
+ continue;
59960
+ }
59961
+ codes.push(checked.code);
59962
+ if (options.json) reports[product.name] = checked.report;
59963
+ else for (const line of [...checked.report.leftOut ?? [], ...formatReport(checked.report)]) io.out(line);
59964
+ }
59965
+ if (options.json) {
59966
+ const [only2] = Object.values(reports);
59967
+ if (several) io.out(JSON.stringify(reports, null, 2));
59968
+ else if (only2) io.out(JSON.stringify(only2, null, 2));
59969
+ }
59970
+ finish(checkExit(codes));
59547
59971
  });
59548
59972
  };
59549
59973
  async function productPlaywright(cwd) {
59550
- const require2 = createRequire(join12(cwd, "package.json"));
59974
+ const require2 = createRequire(join13(cwd, "package.json"));
59551
59975
  let resolved;
59552
59976
  try {
59553
59977
  resolved = require2.resolve("playwright");
@@ -59558,15 +59982,15 @@ async function productPlaywright(cwd) {
59558
59982
  return require2("playwright");
59559
59983
  }
59560
59984
  function fromProductTree(cwd, resolved) {
59561
- for (let dir = resolve2(cwd); ; dir = dirname4(dir)) {
59562
- if (resolved.startsWith(join12(dir, "node_modules") + sep)) return true;
59563
- if (dirname4(dir) === dir) return false;
59985
+ for (let dir = resolve3(cwd); ; dir = dirname5(dir)) {
59986
+ if (resolved.startsWith(join13(dir, "node_modules") + sep)) return true;
59987
+ if (dirname5(dir) === dir) return false;
59564
59988
  }
59565
59989
  }
59566
59990
  var runners = {
59567
59991
  run: execRun,
59568
59992
  loadPlaywright: productPlaywright,
59569
- rulesLibrary: () => readFile6(fileURLToPath(new URL("./rules.js", import.meta.url)), "utf8")
59993
+ rulesLibrary: () => readFile7(fileURLToPath(new URL("./rules.js", import.meta.url)), "utf8")
59570
59994
  };
59571
59995
  var mcpCommand = (program2, io, finish) => {
59572
59996
  program2.command("mcp").description(
@@ -59577,30 +60001,35 @@ var mcpCommand = (program2, io, finish) => {
59577
60001
  });
59578
60002
  };
59579
60003
  var tokensCommand = (program2, io, finish, cli) => {
59580
- program2.command("tokens").description("write the code's tokens to tokens.json, from the design system zaku.yaml names").option("--css <path>", "the stylesheet that declares :root and .dark (shadcn)").option("--root <dir>", "the design directory", "docs/design").action(async (options) => {
59581
- const command = new WriteTokens({
59582
- root: resolve2(io.cwd, options.root),
59583
- cwd: io.cwd,
59584
- css: options.css ? resolve2(io.cwd, options.css) : null
59585
- });
59586
- const written = await sendOrRefuse(cli, io, finish, command);
59587
- if (!written) return;
59588
- io.out(`wrote ${written.path}: ${written.colours} colours, ${written.from}`);
59589
- finish(0);
60004
+ program2.command("tokens").description("write the code's tokens to tokens.json, from the design system zaku.yaml names").option("--css <path>", "the stylesheet that declares :root and .dark (shadcn), over designSystem.shadcn.css").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).action(async (options) => {
60005
+ const workspace = await workspaceOf(io, options);
60006
+ onlyWithOne(workspace, "zaku tokens", "--css", options.css);
60007
+ finish(
60008
+ await forEachProduct(io, workspace, async ({ root, config: config2 }) => {
60009
+ const named = "shadcn" in config2.designSystem ? config2.designSystem.shadcn.css : void 0;
60010
+ const css = options.css ? resolve3(io.cwd, options.css) : named !== void 0 ? resolve3(workspace.dir, named) : null;
60011
+ const command = new WriteTokens({ root, config: config2, cwd: workspace.dir, css });
60012
+ const written = await cli.bus.send(command);
60013
+ io.out(`wrote ${written.path}: ${written.colours} colours, ${written.from}`);
60014
+ return 0;
60015
+ })
60016
+ );
59590
60017
  });
59591
60018
  };
59592
60019
  var recipeCommand = (program2, io, finish, cli) => {
59593
- program2.command("recipe").description("write what the code computes for every variant to recipe.json, from the product's recipe page").option("--url <url>", "the recipe page, when it is not zaku.yaml's recipe.url").option("--root <dir>", "the design directory", "docs/design").action(async (options) => {
59594
- const command = new WriteRecipe({
59595
- root: resolve2(io.cwd, options.root),
59596
- url: options.url ?? null
59597
- });
59598
- const written = await sendOrRefuse(cli, io, finish, command);
59599
- if (!written) return;
59600
- io.out(
59601
- `wrote ${written.path}: ${written.components} components, ${written.variants} variants, in light and dark`
60020
+ program2.command("recipe").description("write what the code computes for every variant to recipe.json, from the product's recipe page").option("--url <url>", "the recipe page, when it is not zaku.yaml's recipe.url").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).action(async (options) => {
60021
+ const workspace = await workspaceOf(io, options);
60022
+ onlyWithOne(workspace, "zaku recipe", "--url", options.url);
60023
+ finish(
60024
+ await forEachProduct(io, workspace, async ({ root, config: config2 }) => {
60025
+ const command = new WriteRecipe({ root, config: config2, url: options.url ?? null });
60026
+ const written = await cli.bus.send(command);
60027
+ io.out(
60028
+ `wrote ${written.path}: ${written.components} components, ${written.variants} variants, in light and dark`
60029
+ );
60030
+ return 0;
60031
+ })
59602
60032
  );
59603
- finish(0);
59604
60033
  });
59605
60034
  };
59606
60035
  var clock = { now: () => /* @__PURE__ */ new Date() };
@@ -59621,10 +60050,21 @@ var budgetCommand = (program2, io, finish, cli) => {
59621
60050
  await cli.bus.send(new RecordBudgetCalls({ day, seat, run, kind: options.kind, count: options.count }));
59622
60051
  finish(0);
59623
60052
  });
59624
- budget.command("status").description("exit 4 when the budget refuses another MCP call").option("--root <dir>", "the design directory", "docs/design").action(async (options) => {
60053
+ budget.command("status").description("exit 4 when the budget refuses another MCP call").option(...CONFIG_OPTION).action(async (options) => {
59625
60054
  const { path: path2, seat, run, day } = ledger();
59626
- const config2 = await readOptionalDesignFile(designPaths(resolve2(io.cwd, options.root)).config, zakuConfigSchema);
59627
- const allowance = mcpAllowance(await readLedger(path2, day), seat, run, config2?.budget ?? budgetSchema.parse({}));
60055
+ let budgets;
60056
+ try {
60057
+ const workspace = await workspaceOf(io, options.config === void 0 ? {} : { config: options.config });
60058
+ budgets = workspace.products.map(
60059
+ (product) => "refusal" in product ? workspace.budget : product.config.budget
60060
+ );
60061
+ } catch (error62) {
60062
+ if (!(error62 instanceof WorkspaceConfigMissing) || options.config !== void 0) throw error62;
60063
+ budgets = [budgetSchema.parse({})];
60064
+ }
60065
+ const read = await readLedger(path2, day);
60066
+ const allowances = budgets.map((budget2) => mcpAllowance(read, seat, run, budget2));
60067
+ const allowance = allowances.reduce((chosen, each) => chosen.ok && !each.ok ? each : chosen);
59628
60068
  io.out(
59629
60069
  `mcp ${allowance.usedToday} today, ${allowance.usedThisRun} this run${allowance.reason ? `: ${allowance.reason}` : ""}`
59630
60070
  );
@@ -59632,8 +60072,7 @@ var budgetCommand = (program2, io, finish, cli) => {
59632
60072
  });
59633
60073
  };
59634
60074
  var outlineCommand = (program2, io, finish, cli) => {
59635
- program2.command("outline").description("read the product file's frames into docs/design/outline").option("--root <dir>", "the design directory", "docs/design").option("--frames <ids>", "only these frame ids, comma separated").action(async (options) => {
59636
- const root = resolve2(io.cwd, options.root);
60075
+ program2.command("outline").description("read each product file's frames into its design's outline/").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).option("--frames <ids>", "only these frame ids, comma separated").action(async (options) => {
59637
60076
  const report = (summary) => io.out(
59638
60077
  summary.skipped ? "outline is current; nothing read" : `read ${summary.read} frames, wrote ${summary.written.length} outlines, ${summary.unmapped} unmapped`
59639
60078
  );
@@ -59643,9 +60082,15 @@ var outlineCommand = (program2, io, finish, cli) => {
59643
60082
  return;
59644
60083
  }
59645
60084
  try {
60085
+ const workspace = await workspaceOf(io, options);
60086
+ onlyWithOne(workspace, "zaku outline", "--frames", options.frames);
59646
60087
  const frames = options.frames ? options.frames.split(",") : null;
59647
- report(await cli.bus.send(new WriteOutline({ root, frames })));
59648
- finish(0);
60088
+ finish(
60089
+ await forEachProduct(io, workspace, async ({ root, config: config2 }) => {
60090
+ report(await cli.bus.send(new WriteOutline({ root, config: config2, frames })));
60091
+ return 0;
60092
+ })
60093
+ );
59649
60094
  } finally {
59650
60095
  await recordRestCalls(cli, io);
59651
60096
  }
@@ -59653,8 +60098,8 @@ var outlineCommand = (program2, io, finish, cli) => {
59653
60098
  };
59654
60099
  var libraryCommand = (program2, io, finish, cli) => {
59655
60100
  const library = program2.command("library").description("snapshot the library's components and tokens");
59656
- library.command("script").description("print the Plugin API code that exports the library variables, for use_figma").option("--root <dir>", "the design directory", "docs/design").option("--local", "also list the file's own components, for a library in the product file").option("--page <name>", "with --local, list only this page; one use_figma call per page").action(async (options) => {
59657
- const config2 = await readDesignFile(designPaths(resolve2(io.cwd, options.root)).config, zakuConfigSchema);
60101
+ library.command("script").description("print the Plugin API code that exports the library variables, for use_figma").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).option("--local", "also list the file's own components, for a library in the product file").option("--page <name>", "with --local, list only this page; one use_figma call per page").action(async (options) => {
60102
+ const { config: config2 } = oneProduct(await workspaceOf(io, options), "zaku library script");
59658
60103
  io.out(
59659
60104
  libraryExportScript({
59660
60105
  combos: [config2.modes],
@@ -59686,23 +60131,13 @@ var libraryCommand = (program2, io, finish, cli) => {
59686
60131
  );
59687
60132
  library.command("save").description(
59688
60133
  "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(
60134
+ ).option(...CONFIG_OPTION).option(...PRODUCT_OPTION).option("--key <key>", "the library or kit to save, as zaku.yaml lists it; needed when it lists several").option(
59690
60135
  "--input <variables.json>",
59691
60136
  "what use_figma returned for zaku library script; once per page export for a library in the product file",
59692
60137
  (value, previous = []) => [...previous, value]
59693
60138
  ).action(async (options) => {
59694
- const root = resolve2(io.cwd, options.root);
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
- }
60139
+ const { root, config: config2 } = oneProduct(await workspaceOf(io, options), "zaku library save");
60140
+ const target = librarySaveTarget(config2, options.key);
59706
60141
  if (target.kind === "library" && (!options.input || options.input.length === 0)) {
59707
60142
  io.err("zaku library save needs --input, the variables use_figma returned for zaku library script");
59708
60143
  finish(3);
@@ -59716,7 +60151,7 @@ var libraryCommand = (program2, io, finish, cli) => {
59716
60151
  const part = target.kind === "library" ? mergeParts(
59717
60152
  await Promise.all(
59718
60153
  (options.input ?? []).map(
59719
- async (input2) => parseDesignFile(input2, await readFile6(resolve2(io.cwd, input2), "utf8"), variablePartSchema)
60154
+ async (input2) => parseDesignFile(input2, await readFile7(resolve3(io.cwd, input2), "utf8"), variablePartSchema)
59720
60155
  )
59721
60156
  )
59722
60157
  ) : void 0;
@@ -59724,6 +60159,7 @@ var libraryCommand = (program2, io, finish, cli) => {
59724
60159
  const saved = await cli.bus.send(
59725
60160
  new SaveLibrary({
59726
60161
  root,
60162
+ config: config2,
59727
60163
  ...options.key !== void 0 ? { key: options.key } : {},
59728
60164
  ...part ? { part } : {}
59729
60165
  })
@@ -59752,13 +60188,13 @@ var libraryCommand = (program2, io, finish, cli) => {
59752
60188
  };
59753
60189
  var rulesCommand = (program2, io, finish) => {
59754
60190
  const rules = program2.command("rules").description("zaku's rule checks, for a run that reaches Figma through use_figma");
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) => {
60191
+ rules.command("script").description("print the Plugin API code that runs the rules over a page or a node, for use_figma").option(...CONFIG_OPTION).option(...PRODUCT_OPTION).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) => {
59756
60192
  if (options.page === void 0 === (options.node === void 0)) {
59757
60193
  io.err("zaku rules script needs --page or --node, one of them");
59758
60194
  finish(3);
59759
60195
  return;
59760
60196
  }
59761
- const config2 = await readDesignFile(designPaths(resolve2(io.cwd, options.root)).config, zakuConfigSchema);
60197
+ const { config: config2 } = oneProduct(await workspaceOf(io, options), "zaku rules script");
59762
60198
  const scope = options.node !== void 0 ? { nodeId: options.node } : { page: options.page };
59763
60199
  const graph = designGraph(config2);
59764
60200
  const file2 = options.key === void 0 ? graph.libraries.length === 1 ? graph.libraries[0] : void 0 : graph.libraries.find((library2) => library2.key === options.key);
@@ -59778,7 +60214,7 @@ return ${check2};`);
59778
60214
  finish(0);
59779
60215
  return;
59780
60216
  }
59781
- const write2 = await readFile6(resolve2(io.cwd, options.after), "utf8");
60217
+ const write2 = await readFile7(resolve3(io.cwd, options.after), "utf8");
59782
60218
  const body = [
59783
60219
  library,
59784
60220
  `const zakuWrite = async () => {
@@ -59810,6 +60246,7 @@ var COMMANDS = [
59810
60246
  budgetCommand,
59811
60247
  schemaCommand,
59812
60248
  librariesCommand,
60249
+ productsCommand,
59813
60250
  mcpCommand
59814
60251
  ];
59815
60252
  async function main(argv, io) {
@@ -59846,6 +60283,11 @@ async function main(argv, io) {
59846
60283
  io.err(USAGE);
59847
60284
  return 3;
59848
60285
  }
60286
+ const refusal = refusalOf(error62);
60287
+ if (refusal) {
60288
+ io.err(refusal.line);
60289
+ return refusal.code;
60290
+ }
59849
60291
  io.err(`zaku: ${error62 instanceof Error ? error62.message : String(error62)}`);
59850
60292
  return 1;
59851
60293
  }