fimo 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-02T16:18:34.112Z",
5
- "cliVersion": "0.3.1",
4
+ "bundledAt": "2026-07-02T20:33:21.698Z",
5
+ "cliVersion": "0.3.2",
6
6
  "external": [
7
7
  "oxc-parser",
8
8
  "fsevents"
package/dist/cli/index.js CHANGED
@@ -17313,20 +17313,20 @@ var require_parse_async = __commonJS({
17313
17313
  const index2 = 0;
17314
17314
  const blocksize = opts.blocksize || 40960;
17315
17315
  const parser = new TOMLParser();
17316
- return new Promise((resolve7, reject) => {
17317
- setImmediate(parseAsyncNext, index2, blocksize, resolve7, reject);
17316
+ return new Promise((resolve8, reject) => {
17317
+ setImmediate(parseAsyncNext, index2, blocksize, resolve8, reject);
17318
17318
  });
17319
- function parseAsyncNext(index3, blocksize2, resolve7, reject) {
17319
+ function parseAsyncNext(index3, blocksize2, resolve8, reject) {
17320
17320
  if (index3 >= str.length) {
17321
17321
  try {
17322
- return resolve7(parser.finish());
17322
+ return resolve8(parser.finish());
17323
17323
  } catch (err) {
17324
17324
  return reject(prettyError(err, str));
17325
17325
  }
17326
17326
  }
17327
17327
  try {
17328
17328
  parser.parse(str.slice(index3, index3 + blocksize2));
17329
- setImmediate(parseAsyncNext, index3 + blocksize2, blocksize2, resolve7, reject);
17329
+ setImmediate(parseAsyncNext, index3 + blocksize2, blocksize2, resolve8, reject);
17330
17330
  } catch (err) {
17331
17331
  reject(prettyError(err, str));
17332
17332
  }
@@ -17355,7 +17355,7 @@ var require_parse_stream = __commonJS({
17355
17355
  function parseReadable(stm) {
17356
17356
  const parser = new TOMLParser();
17357
17357
  stm.setEncoding("utf8");
17358
- return new Promise((resolve7, reject) => {
17358
+ return new Promise((resolve8, reject) => {
17359
17359
  let readable;
17360
17360
  let ended = false;
17361
17361
  let errored = false;
@@ -17363,7 +17363,7 @@ var require_parse_stream = __commonJS({
17363
17363
  ended = true;
17364
17364
  if (readable) return;
17365
17365
  try {
17366
- resolve7(parser.finish());
17366
+ resolve8(parser.finish());
17367
17367
  } catch (err) {
17368
17368
  reject(err);
17369
17369
  }
@@ -41851,7 +41851,7 @@ var init_pagination = __esm({
41851
41851
  });
41852
41852
 
41853
41853
  // ../../packages/api-contracts/dist/esm/types/common.js
41854
- var CommonSchemas, BaseEntitySchema, ProjectRoleKeySchema, ProjectRoleKeyWithSystemSchema, ProjectPrincipalTypeSchema, ProjectVisibilitySchema, ProjectStatusSchema, ProjectInviteStatusSchema, PublicationStatusSchema, OrganizationRoleSchema, AssetTypeSchema, TemplateCodeSchema, FieldTypeSchema;
41854
+ var CommonSchemas, BaseEntitySchema, ProjectRoleKeySchema, ProjectRoleKeyWithSystemSchema, ProjectPrincipalTypeSchema, ProjectVisibilitySchema, ProjectStatusSchema, ProjectInviteStatusSchema, PublicationStatusSchema, OrganizationRoleSchema, AssetTypeSchema, TemplateCodeSchema, FieldTypeSchema, RESERVED_CONTENT_FIELD_NAMES;
41855
41855
  var init_common = __esm({
41856
41856
  "../../packages/api-contracts/dist/esm/types/common.js"() {
41857
41857
  "use strict";
@@ -41932,6 +41932,15 @@ var init_common = __esm({
41932
41932
  "date",
41933
41933
  "json"
41934
41934
  ]);
41935
+ RESERVED_CONTENT_FIELD_NAMES = [
41936
+ "id",
41937
+ "documentId",
41938
+ "slug",
41939
+ "locale",
41940
+ "createdAt",
41941
+ "updatedAt",
41942
+ "__fimo"
41943
+ ];
41935
41944
  }
41936
41945
  });
41937
41946
 
@@ -69995,9 +70004,9 @@ var require_dispatcher_base = __commonJS({
69995
70004
  }
69996
70005
  close(callback) {
69997
70006
  if (callback === void 0) {
69998
- return new Promise((resolve7, reject) => {
70007
+ return new Promise((resolve8, reject) => {
69999
70008
  this.close((err, data2) => {
70000
- return err ? reject(err) : resolve7(data2);
70009
+ return err ? reject(err) : resolve8(data2);
70001
70010
  });
70002
70011
  });
70003
70012
  }
@@ -70035,9 +70044,9 @@ var require_dispatcher_base = __commonJS({
70035
70044
  err = null;
70036
70045
  }
70037
70046
  if (callback === void 0) {
70038
- return new Promise((resolve7, reject) => {
70047
+ return new Promise((resolve8, reject) => {
70039
70048
  this.destroy(err, (err2, data2) => {
70040
- return err2 ? reject(err2) : resolve7(data2);
70049
+ return err2 ? reject(err2) : resolve8(data2);
70041
70050
  });
70042
70051
  });
70043
70052
  }
@@ -73627,8 +73636,8 @@ var require_promise = __commonJS({
73627
73636
  function createDeferredPromise() {
73628
73637
  let res;
73629
73638
  let rej;
73630
- const promise2 = new Promise((resolve7, reject) => {
73631
- res = resolve7;
73639
+ const promise2 = new Promise((resolve8, reject) => {
73640
+ res = resolve8;
73632
73641
  rej = reject;
73633
73642
  });
73634
73643
  return { promise: promise2, resolve: res, reject: rej };
@@ -74957,12 +74966,12 @@ upgrade: ${upgrade}\r
74957
74966
  }
74958
74967
  }
74959
74968
  __name(onDrain, "onDrain");
74960
- const waitForDrain = /* @__PURE__ */ __name(() => new Promise((resolve7, reject) => {
74969
+ const waitForDrain = /* @__PURE__ */ __name(() => new Promise((resolve8, reject) => {
74961
74970
  assert2(callback === null);
74962
74971
  if (socket[kError]) {
74963
74972
  reject(socket[kError]);
74964
74973
  } else {
74965
- callback = resolve7;
74974
+ callback = resolve8;
74966
74975
  }
74967
74976
  }), "waitForDrain");
74968
74977
  socket.on("close", onDrain).on("drain", onDrain);
@@ -75835,12 +75844,12 @@ var require_client_h2 = __commonJS({
75835
75844
  }
75836
75845
  }
75837
75846
  __name(onDrain, "onDrain");
75838
- const waitForDrain = /* @__PURE__ */ __name(() => new Promise((resolve7, reject) => {
75847
+ const waitForDrain = /* @__PURE__ */ __name(() => new Promise((resolve8, reject) => {
75839
75848
  assert2(callback === null);
75840
75849
  if (socket[kError]) {
75841
75850
  reject(socket[kError]);
75842
75851
  } else {
75843
- callback = resolve7;
75852
+ callback = resolve8;
75844
75853
  }
75845
75854
  }), "waitForDrain");
75846
75855
  h2stream.on("close", onDrain).on("drain", onDrain);
@@ -76156,16 +76165,16 @@ var require_client = __commonJS({
76156
76165
  return this[kNeedDrain] < 2;
76157
76166
  }
76158
76167
  [kClose]() {
76159
- return new Promise((resolve7) => {
76168
+ return new Promise((resolve8) => {
76160
76169
  if (this[kSize]) {
76161
- this[kClosedResolve] = resolve7;
76170
+ this[kClosedResolve] = resolve8;
76162
76171
  } else {
76163
- resolve7(null);
76172
+ resolve8(null);
76164
76173
  }
76165
76174
  });
76166
76175
  }
76167
76176
  [kDestroy](err) {
76168
- return new Promise((resolve7) => {
76177
+ return new Promise((resolve8) => {
76169
76178
  const requests = this[kQueue].splice(this[kPendingIdx]);
76170
76179
  for (let i = 0; i < requests.length; i++) {
76171
76180
  const request = requests[i];
@@ -76176,7 +76185,7 @@ var require_client = __commonJS({
76176
76185
  this[kClosedResolve]();
76177
76186
  this[kClosedResolve] = null;
76178
76187
  }
76179
- resolve7(null);
76188
+ resolve8(null);
76180
76189
  }, "callback");
76181
76190
  if (this[kHTTPContext]) {
76182
76191
  this[kHTTPContext].destroy(err, callback);
@@ -76596,8 +76605,8 @@ var require_pool_base = __commonJS({
76596
76605
  }
76597
76606
  return Promise.all(closeAll);
76598
76607
  } else {
76599
- return new Promise((resolve7) => {
76600
- this[kClosedResolve] = resolve7;
76608
+ return new Promise((resolve8) => {
76609
+ this[kClosedResolve] = resolve8;
76601
76610
  });
76602
76611
  }
76603
76612
  }
@@ -77717,10 +77726,10 @@ var require_socks5_proxy_agent = __commonJS({
77717
77726
  const proxyHost = this[kProxyUrl].hostname;
77718
77727
  const proxyPort = parseInt(this[kProxyUrl].port) || 1080;
77719
77728
  debug("creating SOCKS5 connection to", proxyHost, proxyPort);
77720
- const socket = await new Promise((resolve7, reject) => {
77729
+ const socket = await new Promise((resolve8, reject) => {
77721
77730
  const onConnect = /* @__PURE__ */ __name(() => {
77722
77731
  socket2.removeListener("error", onError);
77723
- resolve7(socket2);
77732
+ resolve8(socket2);
77724
77733
  }, "onConnect");
77725
77734
  const onError = /* @__PURE__ */ __name((err) => {
77726
77735
  socket2.removeListener("connect", onConnect);
@@ -77739,14 +77748,14 @@ var require_socks5_proxy_agent = __commonJS({
77739
77748
  socket.destroy();
77740
77749
  });
77741
77750
  await socks5Client.handshake();
77742
- await new Promise((resolve7, reject) => {
77751
+ await new Promise((resolve8, reject) => {
77743
77752
  const timeout = setTimeout(() => {
77744
77753
  reject(new Error("SOCKS5 authentication timeout"));
77745
77754
  }, 5e3);
77746
77755
  const onAuthenticated = /* @__PURE__ */ __name(() => {
77747
77756
  clearTimeout(timeout);
77748
77757
  socks5Client.removeListener("error", onError);
77749
- resolve7();
77758
+ resolve8();
77750
77759
  }, "onAuthenticated");
77751
77760
  const onError = /* @__PURE__ */ __name((err) => {
77752
77761
  clearTimeout(timeout);
@@ -77755,14 +77764,14 @@ var require_socks5_proxy_agent = __commonJS({
77755
77764
  }, "onError");
77756
77765
  if (socks5Client.state === "authenticated") {
77757
77766
  clearTimeout(timeout);
77758
- resolve7();
77767
+ resolve8();
77759
77768
  } else {
77760
77769
  socks5Client.once("authenticated", onAuthenticated);
77761
77770
  socks5Client.once("error", onError);
77762
77771
  }
77763
77772
  });
77764
77773
  await socks5Client.connect(targetHost, targetPort);
77765
- await new Promise((resolve7, reject) => {
77774
+ await new Promise((resolve8, reject) => {
77766
77775
  const timeout = setTimeout(() => {
77767
77776
  reject(new Error("SOCKS5 connection timeout"));
77768
77777
  }, 5e3);
@@ -77770,7 +77779,7 @@ var require_socks5_proxy_agent = __commonJS({
77770
77779
  debug("SOCKS5 tunnel established to", targetHost, targetPort, "via", info);
77771
77780
  clearTimeout(timeout);
77772
77781
  socks5Client.removeListener("error", onError);
77773
- resolve7();
77782
+ resolve8();
77774
77783
  }, "onConnected");
77775
77784
  const onError = /* @__PURE__ */ __name((err) => {
77776
77785
  clearTimeout(timeout);
@@ -77811,8 +77820,8 @@ var require_socks5_proxy_agent = __commonJS({
77811
77820
  servername: targetHost,
77812
77821
  ...connectOpts.tls || {}
77813
77822
  });
77814
- await new Promise((resolve7, reject) => {
77815
- finalSocket.once("secureConnect", resolve7);
77823
+ await new Promise((resolve8, reject) => {
77824
+ finalSocket.once("secureConnect", resolve8);
77816
77825
  finalSocket.once("error", reject);
77817
77826
  });
77818
77827
  }
@@ -78866,7 +78875,7 @@ var require_readable = __commonJS({
78866
78875
  if (this._readableState.closeEmitted) {
78867
78876
  return Promise.resolve(null);
78868
78877
  }
78869
- return new Promise((resolve7, reject) => {
78878
+ return new Promise((resolve8, reject) => {
78870
78879
  if (this[kContentLength] && this[kContentLength] > limit || this[kBytesRead] > limit) {
78871
78880
  this.destroy(new AbortError());
78872
78881
  }
@@ -78880,11 +78889,11 @@ var require_readable = __commonJS({
78880
78889
  if (signal.aborted) {
78881
78890
  reject(signal.reason ?? new AbortError());
78882
78891
  } else {
78883
- resolve7(null);
78892
+ resolve8(null);
78884
78893
  }
78885
78894
  });
78886
78895
  } else {
78887
- this.on("close", resolve7);
78896
+ this.on("close", resolve8);
78888
78897
  }
78889
78898
  this.on("error", noop).on("data", () => {
78890
78899
  if (this[kBytesRead] > limit) {
@@ -78914,7 +78923,7 @@ var require_readable = __commonJS({
78914
78923
  __name(isUnusable, "isUnusable");
78915
78924
  function consume(stream, type) {
78916
78925
  assert2(!stream[kConsume]);
78917
- return new Promise((resolve7, reject) => {
78926
+ return new Promise((resolve8, reject) => {
78918
78927
  if (isUnusable(stream)) {
78919
78928
  const rState = stream._readableState;
78920
78929
  if (rState.destroyed && rState.closeEmitted === false) {
@@ -78929,7 +78938,7 @@ var require_readable = __commonJS({
78929
78938
  stream[kConsume] = {
78930
78939
  type,
78931
78940
  stream,
78932
- resolve: resolve7,
78941
+ resolve: resolve8,
78933
78942
  reject,
78934
78943
  length: 0,
78935
78944
  body: []
@@ -79007,18 +79016,18 @@ var require_readable = __commonJS({
79007
79016
  }
79008
79017
  __name(chunksConcat, "chunksConcat");
79009
79018
  function consumeEnd(consume2, encoding) {
79010
- const { type, body, resolve: resolve7, stream, length } = consume2;
79019
+ const { type, body, resolve: resolve8, stream, length } = consume2;
79011
79020
  try {
79012
79021
  if (type === "text") {
79013
- resolve7(chunksDecode(body, length, encoding));
79022
+ resolve8(chunksDecode(body, length, encoding));
79014
79023
  } else if (type === "json") {
79015
- resolve7(JSON.parse(chunksDecode(body, length, encoding)));
79024
+ resolve8(JSON.parse(chunksDecode(body, length, encoding)));
79016
79025
  } else if (type === "arrayBuffer") {
79017
- resolve7(chunksConcat(body, length).buffer);
79026
+ resolve8(chunksConcat(body, length).buffer);
79018
79027
  } else if (type === "blob") {
79019
- resolve7(new Blob(body, { type: stream[kContentType] }));
79028
+ resolve8(new Blob(body, { type: stream[kContentType] }));
79020
79029
  } else if (type === "bytes") {
79021
- resolve7(chunksConcat(body, length));
79030
+ resolve8(chunksConcat(body, length));
79022
79031
  }
79023
79032
  consumeFinish(consume2);
79024
79033
  } catch (err) {
@@ -79215,9 +79224,9 @@ var require_api_request = __commonJS({
79215
79224
  };
79216
79225
  function request(opts, callback) {
79217
79226
  if (callback === void 0) {
79218
- return new Promise((resolve7, reject) => {
79227
+ return new Promise((resolve8, reject) => {
79219
79228
  request.call(this, opts, (err, data2) => {
79220
- return err ? reject(err) : resolve7(data2);
79229
+ return err ? reject(err) : resolve8(data2);
79221
79230
  });
79222
79231
  });
79223
79232
  }
@@ -79437,9 +79446,9 @@ var require_api_stream = __commonJS({
79437
79446
  };
79438
79447
  function stream(opts, factory, callback) {
79439
79448
  if (callback === void 0) {
79440
- return new Promise((resolve7, reject) => {
79449
+ return new Promise((resolve8, reject) => {
79441
79450
  stream.call(this, opts, factory, (err, data2) => {
79442
- return err ? reject(err) : resolve7(data2);
79451
+ return err ? reject(err) : resolve8(data2);
79443
79452
  });
79444
79453
  });
79445
79454
  }
@@ -79742,9 +79751,9 @@ var require_api_upgrade = __commonJS({
79742
79751
  };
79743
79752
  function upgrade(opts, callback) {
79744
79753
  if (callback === void 0) {
79745
- return new Promise((resolve7, reject) => {
79754
+ return new Promise((resolve8, reject) => {
79746
79755
  upgrade.call(this, opts, (err, data2) => {
79747
- return err ? reject(err) : resolve7(data2);
79756
+ return err ? reject(err) : resolve8(data2);
79748
79757
  });
79749
79758
  });
79750
79759
  }
@@ -79841,9 +79850,9 @@ var require_api_connect = __commonJS({
79841
79850
  };
79842
79851
  function connect(opts, callback) {
79843
79852
  if (callback === void 0) {
79844
- return new Promise((resolve7, reject) => {
79853
+ return new Promise((resolve8, reject) => {
79845
79854
  connect.call(this, opts, (err, data2) => {
79846
- return err ? reject(err) : resolve7(data2);
79855
+ return err ? reject(err) : resolve8(data2);
79847
79856
  });
79848
79857
  });
79849
79858
  }
@@ -81179,7 +81188,7 @@ var require_snapshot_recorder = __commonJS({
81179
81188
  "../../node_modules/.pnpm/undici@7.24.8/node_modules/undici/lib/mock/snapshot-recorder.js"(exports2, module2) {
81180
81189
  "use strict";
81181
81190
  var { writeFile: writeFile4, readFile: readFile4, mkdir: mkdir2 } = __require("node:fs/promises");
81182
- var { dirname: dirname8, resolve: resolve7 } = __require("node:path");
81191
+ var { dirname: dirname8, resolve: resolve8 } = __require("node:path");
81183
81192
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
81184
81193
  var { InvalidArgumentError: InvalidArgumentError2, UndiciError } = require_errors3();
81185
81194
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -81387,7 +81396,7 @@ var require_snapshot_recorder = __commonJS({
81387
81396
  throw new InvalidArgumentError2("Snapshot path is required");
81388
81397
  }
81389
81398
  try {
81390
- const data2 = await readFile4(resolve7(path46), "utf8");
81399
+ const data2 = await readFile4(resolve8(path46), "utf8");
81391
81400
  const parsed = JSON.parse(data2);
81392
81401
  if (Array.isArray(parsed)) {
81393
81402
  this.#snapshots.clear();
@@ -81416,7 +81425,7 @@ var require_snapshot_recorder = __commonJS({
81416
81425
  if (!path46) {
81417
81426
  throw new InvalidArgumentError2("Snapshot path is required");
81418
81427
  }
81419
- const resolvedPath = resolve7(path46);
81428
+ const resolvedPath = resolve8(path46);
81420
81429
  await mkdir2(dirname8(resolvedPath), { recursive: true });
81421
81430
  const data2 = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
81422
81431
  hash: hash2,
@@ -88396,7 +88405,7 @@ var require_fetch = __commonJS({
88396
88405
  const agent = fetchParams.controller.dispatcher;
88397
88406
  const path46 = url2.pathname + url2.search;
88398
88407
  const hasTrailingQuestionMark = url2.search.length === 0 && url2.href[url2.href.length - url2.hash.length - 1] === "?";
88399
- return new Promise((resolve7, reject) => agent.dispatch(
88408
+ return new Promise((resolve8, reject) => agent.dispatch(
88400
88409
  {
88401
88410
  path: hasTrailingQuestionMark ? `${path46}?` : path46,
88402
88411
  origin: url2.origin,
@@ -88484,7 +88493,7 @@ var require_fetch = __commonJS({
88484
88493
  }
88485
88494
  }
88486
88495
  const onError = this.onError.bind(this);
88487
- resolve7({
88496
+ resolve8({
88488
88497
  status,
88489
88498
  statusText,
88490
88499
  headersList,
@@ -88537,7 +88546,7 @@ var require_fetch = __commonJS({
88537
88546
  headersList.append(headerName, String(value), true);
88538
88547
  }
88539
88548
  }
88540
- resolve7({
88549
+ resolve8({
88541
88550
  status,
88542
88551
  statusText: STATUS_CODES[status],
88543
88552
  headersList,
@@ -88561,7 +88570,7 @@ var require_fetch = __commonJS({
88561
88570
  headersList.append(nameStr, value.toString("latin1"), true);
88562
88571
  }
88563
88572
  }
88564
- resolve7({
88573
+ resolve8({
88565
88574
  status,
88566
88575
  statusText: STATUS_CODES[status],
88567
88576
  headersList,
@@ -96276,16 +96285,16 @@ function pickSurfaces(opts) {
96276
96285
  return selected.length > 0 ? selected : void 0;
96277
96286
  }
96278
96287
  function withSoftTimeout(p2, ms) {
96279
- return new Promise((resolve7) => {
96280
- const t2 = setTimeout(() => resolve7({ kind: "timeout" }), ms);
96288
+ return new Promise((resolve8) => {
96289
+ const t2 = setTimeout(() => resolve8({ kind: "timeout" }), ms);
96281
96290
  p2.then(
96282
96291
  (value) => {
96283
96292
  clearTimeout(t2);
96284
- resolve7({ kind: "ok", value });
96293
+ resolve8({ kind: "ok", value });
96285
96294
  },
96286
96295
  (error45) => {
96287
96296
  clearTimeout(t2);
96288
- resolve7({ kind: "error", error: error45 });
96297
+ resolve8({ kind: "error", error: error45 });
96289
96298
  }
96290
96299
  );
96291
96300
  });
@@ -98169,11 +98178,11 @@ function defaultConsentIO() {
98169
98178
  __name(defaultConsentIO, "defaultConsentIO");
98170
98179
  async function readLine(prompt) {
98171
98180
  const readline = await import("node:readline");
98172
- return new Promise((resolve7) => {
98181
+ return new Promise((resolve8) => {
98173
98182
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
98174
98183
  rl.question(prompt, (answer) => {
98175
98184
  rl.close();
98176
- resolve7(answer);
98185
+ resolve8(answer);
98177
98186
  });
98178
98187
  });
98179
98188
  }
@@ -101170,6 +101179,112 @@ function normalizeFimoLocale(locale) {
101170
101179
  }
101171
101180
  __name(normalizeFimoLocale, "normalizeFimoLocale");
101172
101181
 
101182
+ // src/scripts/validate-schemas.ts
101183
+ init_esm();
101184
+ import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
101185
+ import { basename as basename2, join as join9, resolve as resolve6 } from "node:path";
101186
+ var SCHEMAS_REL2 = "src/schemas";
101187
+ var FIELD_TYPES = new Set(FieldTypeSchema.options);
101188
+ var RESERVED_FIELD_NAMES = new Set(RESERVED_CONTENT_FIELD_NAMES);
101189
+ function validateSchemas(options = {}) {
101190
+ const cwd = resolve6(options.cwd ?? process.cwd());
101191
+ const schemasDir = join9(cwd, SCHEMAS_REL2);
101192
+ if (!existsSync7(schemasDir)) {
101193
+ return { errors: [], schemasChecked: 0 };
101194
+ }
101195
+ const files = readdirSync4(schemasDir).filter((f) => f.endsWith(".json")).sort();
101196
+ const errors2 = [];
101197
+ for (const fileName of files) {
101198
+ const file2 = join9(SCHEMAS_REL2, fileName);
101199
+ errors2.push(...validateSchemaFile(join9(schemasDir, fileName), file2));
101200
+ }
101201
+ return { errors: errors2, schemasChecked: files.length };
101202
+ }
101203
+ __name(validateSchemas, "validateSchemas");
101204
+ function validateSchemaFile(absolutePath, file2) {
101205
+ let schema;
101206
+ try {
101207
+ schema = JSON.parse(readFileSync7(absolutePath, "utf-8"));
101208
+ } catch (error45) {
101209
+ return [
101210
+ {
101211
+ rule: "schemas/parse",
101212
+ message: `Invalid JSON: ${error45 instanceof Error ? error45.message : String(error45)}`,
101213
+ file: file2
101214
+ }
101215
+ ];
101216
+ }
101217
+ if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
101218
+ return [{ rule: "schemas/shape", message: "Schema must be a JSON object.", file: file2 }];
101219
+ }
101220
+ const errors2 = [];
101221
+ const { uid, name, fields } = schema;
101222
+ const expectedUid = basename2(file2, ".json");
101223
+ if (typeof uid !== "string" || uid.length === 0) {
101224
+ errors2.push({
101225
+ rule: "schemas/uid",
101226
+ message: `Missing "uid". Set it to the file name: "${expectedUid}".`,
101227
+ file: file2
101228
+ });
101229
+ } else if (uid !== expectedUid) {
101230
+ errors2.push({
101231
+ rule: "schemas/uid",
101232
+ message: `"uid" is "${uid}" but the file is ${expectedUid}.json \u2014 they must match.`,
101233
+ file: file2
101234
+ });
101235
+ }
101236
+ if (typeof name !== "string" || name.length === 0) {
101237
+ errors2.push({
101238
+ rule: "schemas/name",
101239
+ message: 'Missing "name" (the human-readable label).',
101240
+ file: file2
101241
+ });
101242
+ }
101243
+ if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
101244
+ errors2.push({
101245
+ rule: "schemas/fields",
101246
+ message: 'Missing "fields" object.',
101247
+ file: file2
101248
+ });
101249
+ return errors2;
101250
+ }
101251
+ for (const [fieldName, definition] of Object.entries(fields)) {
101252
+ if (RESERVED_FIELD_NAMES.has(fieldName)) {
101253
+ errors2.push({
101254
+ rule: "schemas/reserved-field",
101255
+ message: `Field "${fieldName}" is reserved entry metadata \u2014 the platform manages it automatically. Remove it from the schema${fieldName === "slug" ? " (set slug inside entry data on create instead)" : ""}.`,
101256
+ file: file2
101257
+ });
101258
+ continue;
101259
+ }
101260
+ if (typeof definition !== "object" || definition === null || Array.isArray(definition)) {
101261
+ errors2.push({
101262
+ rule: "schemas/field-shape",
101263
+ message: `Field "${fieldName}" must be an object like { "type": "string" }.`,
101264
+ file: file2
101265
+ });
101266
+ continue;
101267
+ }
101268
+ const { type } = definition;
101269
+ if (typeof type !== "string" || !FIELD_TYPES.has(type)) {
101270
+ errors2.push({
101271
+ rule: "schemas/field-type",
101272
+ message: `Field "${fieldName}" has unsupported type "${String(type)}". Allowed: ${[...FIELD_TYPES].join(", ")}.`,
101273
+ file: file2
101274
+ });
101275
+ }
101276
+ if ("i18n" in definition) {
101277
+ errors2.push({
101278
+ rule: "schemas/field-i18n",
101279
+ message: `Field "${fieldName}": field-level locale settings ("i18n") are not supported.`,
101280
+ file: file2
101281
+ });
101282
+ }
101283
+ }
101284
+ return errors2;
101285
+ }
101286
+ __name(validateSchemaFile, "validateSchemaFile");
101287
+
101173
101288
  // src/cli/commands/validate.ts
101174
101289
  init_api();
101175
101290
  init_config2();
@@ -101314,8 +101429,8 @@ init_project();
101314
101429
 
101315
101430
  // src/cli/commands/scripts.ts
101316
101431
  init_dist();
101317
- import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
101318
- import { join as join10, resolve as resolve6 } from "node:path";
101432
+ import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
101433
+ import { join as join11, resolve as resolve7 } from "node:path";
101319
101434
 
101320
101435
  // src/scripts/export-static.ts
101321
101436
  import fs23 from "node:fs/promises";
@@ -101357,8 +101472,8 @@ async function exportStatic(options = {}) {
101357
101472
  __name(exportStatic, "exportStatic");
101358
101473
 
101359
101474
  // src/scripts/lint-translation-keys.ts
101360
- import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
101361
- import { join as join9 } from "path";
101475
+ import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
101476
+ import { join as join10 } from "path";
101362
101477
  import { parseSync as parseSync2 } from "oxc-parser";
101363
101478
  var SRC_DIR = "src";
101364
101479
  function getLineAndColumn(code2, offset) {
@@ -101452,9 +101567,9 @@ function lintTranslationKeys(filePath, code2) {
101452
101567
  __name(lintTranslationKeys, "lintTranslationKeys");
101453
101568
  function findReactFiles2(dir) {
101454
101569
  const files = [];
101455
- if (!existsSync7(dir)) return files;
101456
- for (const entry of readdirSync4(dir)) {
101457
- const fullPath = join9(dir, entry);
101570
+ if (!existsSync8(dir)) return files;
101571
+ for (const entry of readdirSync5(dir)) {
101572
+ const fullPath = join10(dir, entry);
101458
101573
  const stat2 = statSync2(fullPath);
101459
101574
  if (stat2.isDirectory()) {
101460
101575
  if (entry === "ui" || entry === "node_modules" || entry === "fimo") continue;
@@ -101468,10 +101583,10 @@ function findReactFiles2(dir) {
101468
101583
  __name(findReactFiles2, "findReactFiles");
101469
101584
  var isStandalone = process.argv[1]?.includes("lint-translation-keys");
101470
101585
  if (isStandalone) {
101471
- const reactFiles = [...findReactFiles2(join9(SRC_DIR, "components")), ...findReactFiles2(join9(SRC_DIR, "pages"))];
101586
+ const reactFiles = [...findReactFiles2(join10(SRC_DIR, "components")), ...findReactFiles2(join10(SRC_DIR, "pages"))];
101472
101587
  const errors2 = [];
101473
101588
  for (const file2 of reactFiles) {
101474
- const code2 = readFileSync7(file2, "utf-8");
101589
+ const code2 = readFileSync8(file2, "utf-8");
101475
101590
  errors2.push(...lintTranslationKeys(file2, code2));
101476
101591
  }
101477
101592
  if (errors2.length > 0) {
@@ -101696,7 +101811,7 @@ __name(resolveRoutes, "resolveRoutes");
101696
101811
  init_sync_forms();
101697
101812
  init_sync_schemas();
101698
101813
  function resolveCwd(options) {
101699
- return options.cwd ? resolve6(options.cwd) : process.cwd();
101814
+ return options.cwd ? resolve7(options.cwd) : process.cwd();
101700
101815
  }
101701
101816
  __name(resolveCwd, "resolveCwd");
101702
101817
  function renderPrettyJson4(data2) {
@@ -101767,7 +101882,7 @@ function resolveRoutesCommand(changedFiles, options) {
101767
101882
  }
101768
101883
  __name(resolveRoutesCommand, "resolveRoutesCommand");
101769
101884
  function lintProject(opts) {
101770
- const files = opts.targetPath ? [resolve6(opts.cwd, opts.targetPath)] : collectFiles2(resolve6(opts.cwd, "src"));
101885
+ const files = opts.targetPath ? [resolve7(opts.cwd, opts.targetPath)] : collectFiles2(resolve7(opts.cwd, "src"));
101771
101886
  const errors2 = [];
101772
101887
  let filesChecked = 0;
101773
101888
  for (const file2 of files) {
@@ -101775,7 +101890,7 @@ function lintProject(opts) {
101775
101890
  continue;
101776
101891
  }
101777
101892
  try {
101778
- const code2 = readFileSync8(file2, "utf-8");
101893
+ const code2 = readFileSync9(file2, "utf-8");
101779
101894
  errors2.push(...lintTranslationKeys(file2, code2));
101780
101895
  filesChecked++;
101781
101896
  } catch {
@@ -101794,15 +101909,15 @@ function lintCommand(targetPath, options) {
101794
101909
  }
101795
101910
  __name(lintCommand, "lintCommand");
101796
101911
  function collectFiles2(root2) {
101797
- if (!existsSync8(root2)) {
101912
+ if (!existsSync9(root2)) {
101798
101913
  return [];
101799
101914
  }
101800
101915
  const results = [];
101801
- for (const name of readdirSync5(root2)) {
101916
+ for (const name of readdirSync6(root2)) {
101802
101917
  if (name === "node_modules" || name === "dist" || name === ".git") {
101803
101918
  continue;
101804
101919
  }
101805
- const full = join10(root2, name);
101920
+ const full = join11(root2, name);
101806
101921
  if (statSync3(full).isDirectory()) {
101807
101922
  results.push(...collectFiles2(full));
101808
101923
  } else {
@@ -101855,10 +101970,11 @@ async function runValidate(opts) {
101855
101970
  __name(runValidate, "runValidate");
101856
101971
  async function runChecksPhase(opts) {
101857
101972
  const { cwd } = opts;
101858
- const [lintResult, routeResult, agentsResult] = await Promise.allSettled([
101973
+ const [lintResult, routeResult, agentsResult, schemasResult] = await Promise.allSettled([
101859
101974
  Promise.resolve().then(() => lintProject({ cwd })),
101860
101975
  Promise.resolve().then(() => runRouteMetadataCheck({ cwd })),
101861
- Promise.resolve().then(() => runAgentsCheck({ cwd }))
101976
+ Promise.resolve().then(() => runAgentsCheck({ cwd })),
101977
+ Promise.resolve().then(() => validateSchemas({ cwd }))
101862
101978
  ]);
101863
101979
  const errors2 = [];
101864
101980
  const stats = {};
@@ -101894,6 +102010,14 @@ async function runChecksPhase(opts) {
101894
102010
  } else {
101895
102011
  errors2.push(infraErrorFromException("agents", agentsResult.reason));
101896
102012
  }
102013
+ if (schemasResult.status === "fulfilled") {
102014
+ stats.schemasChecked = schemasResult.value.schemasChecked;
102015
+ for (const err of schemasResult.value.errors) {
102016
+ errors2.push(authorError({ rule: err.rule, message: err.message, file: err.file }));
102017
+ }
102018
+ } else {
102019
+ errors2.push(infraErrorFromException("schemas", schemasResult.reason));
102020
+ }
101897
102021
  return {
101898
102022
  name: "Checks",
101899
102023
  ok: errors2.length === 0,
@@ -103886,14 +104010,14 @@ var baseOpen = /* @__PURE__ */ __name(async (options) => {
103886
104010
  }
103887
104011
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
103888
104012
  if (options.wait) {
103889
- return new Promise((resolve7, reject) => {
104013
+ return new Promise((resolve8, reject) => {
103890
104014
  subprocess.once("error", reject);
103891
104015
  subprocess.once("close", (exitCode) => {
103892
104016
  if (!options.allowNonzeroExitCode && exitCode > 0) {
103893
104017
  reject(new Error(`Exited with code ${exitCode}`));
103894
104018
  return;
103895
104019
  }
103896
- resolve7(subprocess);
104020
+ resolve8(subprocess);
103897
104021
  });
103898
104022
  });
103899
104023
  }
@@ -104110,7 +104234,7 @@ async function pollUntilVerified(api, projectId, initial, timeoutMs, organizatio
104110
104234
  sp.fail(`SSL validation failed for ${current.hostname}.`);
104111
104235
  return current;
104112
104236
  }
104113
- await new Promise((resolve7) => setTimeout(resolve7, 5e3));
104237
+ await new Promise((resolve8) => setTimeout(resolve8, 5e3));
104114
104238
  try {
104115
104239
  current = await api.refreshProjectDomain(projectId, initial.id);
104116
104240
  sp.update(`Waiting for ${current.hostname} (SSL: ${current.ssl.status})`);
@@ -104715,7 +104839,7 @@ async function pollPurchasePayment(api, organizationId, initial, timeoutMs) {
104715
104839
  sp.fail(`${current.status === "payment_failed" ? "Payment" : "Registration"} failed for ${current.domain}.`);
104716
104840
  return current;
104717
104841
  }
104718
- await new Promise((resolve7) => setTimeout(resolve7, 5e3));
104842
+ await new Promise((resolve8) => setTimeout(resolve8, 5e3));
104719
104843
  try {
104720
104844
  current = await api.refreshRegisteredDomainPayment(organizationId, current.id);
104721
104845
  sp.update(`Waiting (status: ${current.status})`);
@@ -106953,7 +107077,7 @@ async function loginCommand() {
106953
107077
  }
106954
107078
  __name(loginCommand, "loginCommand");
106955
107079
  async function startCallbackServer() {
106956
- return new Promise((resolve7, reject) => {
107080
+ return new Promise((resolve8, reject) => {
106957
107081
  const server = createHttpServer();
106958
107082
  server.on("error", reject);
106959
107083
  server.listen(0, "127.0.0.1", () => {
@@ -106962,13 +107086,13 @@ async function startCallbackServer() {
106962
107086
  reject(new Error("Failed to bind callback server"));
106963
107087
  return;
106964
107088
  }
106965
- resolve7({ server, port: address.port });
107089
+ resolve8({ server, port: address.port });
106966
107090
  });
106967
107091
  });
106968
107092
  }
106969
107093
  __name(startCallbackServer, "startCallbackServer");
106970
107094
  function waitForCallback(server, timeoutMs) {
106971
- return new Promise((resolve7, reject) => {
107095
+ return new Promise((resolve8, reject) => {
106972
107096
  const timeout = setTimeout(() => {
106973
107097
  cleanup();
106974
107098
  reject(new Error("Timed out waiting for browser callback"));
@@ -106991,7 +107115,7 @@ function waitForCallback(server, timeoutMs) {
106991
107115
  }
106992
107116
  respond(res, 200, renderSignedInPage(), "text/html");
106993
107117
  cleanup();
106994
- resolve7({ token, state });
107118
+ resolve8({ token, state });
106995
107119
  }, "handler");
106996
107120
  server.on("request", handler);
106997
107121
  function cleanup() {
@@ -107149,7 +107273,7 @@ init_api();
107149
107273
  init_auth_errors();
107150
107274
  init_color();
107151
107275
  import { execSync, spawnSync as spawnSync4 } from "node:child_process";
107152
- import { mkdtempSync, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
107276
+ import { mkdtempSync, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
107153
107277
  import { tmpdir as tmpdir3 } from "node:os";
107154
107278
  import path37 from "node:path";
107155
107279
 
@@ -107554,7 +107678,7 @@ function openEditorForPick(conflict) {
107554
107678
  const seed = conflict.childValue !== void 0 ? JSON.stringify(conflict.childValue, null, 2) : '// Replace this comment with the resolved value as JSON\n""';
107555
107679
  writeFileSync6(file2, seed, "utf8");
107556
107680
  spawnSync4(editor, [file2], { stdio: "inherit" });
107557
- const raw = readFileSync9(file2, "utf8").trim();
107681
+ const raw = readFileSync10(file2, "utf8").trim();
107558
107682
  if (raw === "" || raw === seed.trim()) {
107559
107683
  return null;
107560
107684
  }
@@ -108630,6 +108754,7 @@ __name(deleteCommand, "deleteCommand");
108630
108754
  init_dist();
108631
108755
  import fs41 from "node:fs";
108632
108756
  import path44 from "node:path";
108757
+ init_api();
108633
108758
  init_project_context();
108634
108759
  init_project();
108635
108760
  function toApiShape(schema) {
@@ -108671,13 +108796,17 @@ async function schemaPushCommand(typeName, options, cmd) {
108671
108796
  if (!fs41.existsSync(filePath)) {
108672
108797
  throw new Error(`Schema file not found: ${filePath}`);
108673
108798
  }
108799
+ const issues = validateSchemaFile(filePath, path44.join("src/schemas", file2));
108800
+ if (issues.length > 0) {
108801
+ throw new Error(issues.map((issue2) => `${issue2.file}: ${issue2.message}`).join("\n"));
108802
+ }
108674
108803
  const schema = JSON.parse(fs41.readFileSync(filePath, "utf-8"));
108675
108804
  const contentType = toApiShape(schema);
108676
108805
  try {
108677
108806
  const result = await api.createSchema(ctx.projectId, contentType);
108678
108807
  results.push(result);
108679
108808
  } catch (error45) {
108680
- if (error45 instanceof Error && error45.message.includes("400")) {
108809
+ if (error45 instanceof ApiRequestError && error45.status === 400 && error45.detail === "Content type already exists") {
108681
108810
  const result = await api.updateSchema(ctx.projectId, contentType.name, contentType);
108682
108811
  results.push(result);
108683
108812
  } else {
@@ -0,0 +1,13 @@
1
+ export interface SchemaFileError {
2
+ rule: string;
3
+ message: string;
4
+ file: string;
5
+ }
6
+ export interface ValidateSchemasResult {
7
+ errors: SchemaFileError[];
8
+ schemasChecked: number;
9
+ }
10
+ export declare function validateSchemas(options?: {
11
+ cwd?: string;
12
+ }): ValidateSchemasResult;
13
+ export declare function validateSchemaFile(absolutePath: string, file: string): SchemaFileError[];
@@ -0,0 +1,113 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { basename, join, resolve } from 'node:path';
3
+ import { FieldTypeSchema, RESERVED_CONTENT_FIELD_NAMES } from '@fimo/api-contracts';
4
+ /**
5
+ * Validate `src/schemas/*.json` locally against the same rules the API's
6
+ * schema validator enforces (field types, reserved field names, required
7
+ * uid/name). Catching these in `fimo validate` surfaces the real problem
8
+ * before a `schemas push` can fail with a less direct server error.
9
+ */
10
+ const SCHEMAS_REL = 'src/schemas';
11
+ const FIELD_TYPES = new Set(FieldTypeSchema.options);
12
+ const RESERVED_FIELD_NAMES = new Set(RESERVED_CONTENT_FIELD_NAMES);
13
+ export function validateSchemas(options = {}) {
14
+ const cwd = resolve(options.cwd ?? process.cwd());
15
+ const schemasDir = join(cwd, SCHEMAS_REL);
16
+ if (!existsSync(schemasDir)) {
17
+ return { errors: [], schemasChecked: 0 };
18
+ }
19
+ const files = readdirSync(schemasDir)
20
+ .filter((f) => f.endsWith('.json'))
21
+ .sort();
22
+ const errors = [];
23
+ for (const fileName of files) {
24
+ const file = join(SCHEMAS_REL, fileName);
25
+ errors.push(...validateSchemaFile(join(schemasDir, fileName), file));
26
+ }
27
+ return { errors, schemasChecked: files.length };
28
+ }
29
+ export function validateSchemaFile(absolutePath, file) {
30
+ let schema;
31
+ try {
32
+ schema = JSON.parse(readFileSync(absolutePath, 'utf-8'));
33
+ }
34
+ catch (error) {
35
+ return [
36
+ {
37
+ rule: 'schemas/parse',
38
+ message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
39
+ file,
40
+ },
41
+ ];
42
+ }
43
+ if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {
44
+ return [{ rule: 'schemas/shape', message: 'Schema must be a JSON object.', file }];
45
+ }
46
+ const errors = [];
47
+ const { uid, name, fields } = schema;
48
+ const expectedUid = basename(file, '.json');
49
+ if (typeof uid !== 'string' || uid.length === 0) {
50
+ errors.push({
51
+ rule: 'schemas/uid',
52
+ message: `Missing "uid". Set it to the file name: "${expectedUid}".`,
53
+ file,
54
+ });
55
+ }
56
+ else if (uid !== expectedUid) {
57
+ errors.push({
58
+ rule: 'schemas/uid',
59
+ message: `"uid" is "${uid}" but the file is ${expectedUid}.json — they must match.`,
60
+ file,
61
+ });
62
+ }
63
+ if (typeof name !== 'string' || name.length === 0) {
64
+ errors.push({
65
+ rule: 'schemas/name',
66
+ message: 'Missing "name" (the human-readable label).',
67
+ file,
68
+ });
69
+ }
70
+ if (typeof fields !== 'object' || fields === null || Array.isArray(fields)) {
71
+ errors.push({
72
+ rule: 'schemas/fields',
73
+ message: 'Missing "fields" object.',
74
+ file,
75
+ });
76
+ return errors;
77
+ }
78
+ for (const [fieldName, definition] of Object.entries(fields)) {
79
+ if (RESERVED_FIELD_NAMES.has(fieldName)) {
80
+ errors.push({
81
+ rule: 'schemas/reserved-field',
82
+ message: `Field "${fieldName}" is reserved entry metadata — the platform manages it automatically. ` +
83
+ `Remove it from the schema${fieldName === 'slug' ? ' (set slug inside entry data on create instead)' : ''}.`,
84
+ file,
85
+ });
86
+ continue;
87
+ }
88
+ if (typeof definition !== 'object' || definition === null || Array.isArray(definition)) {
89
+ errors.push({
90
+ rule: 'schemas/field-shape',
91
+ message: `Field "${fieldName}" must be an object like { "type": "string" }.`,
92
+ file,
93
+ });
94
+ continue;
95
+ }
96
+ const { type } = definition;
97
+ if (typeof type !== 'string' || !FIELD_TYPES.has(type)) {
98
+ errors.push({
99
+ rule: 'schemas/field-type',
100
+ message: `Field "${fieldName}" has unsupported type "${String(type)}". Allowed: ${[...FIELD_TYPES].join(', ')}.`,
101
+ file,
102
+ });
103
+ }
104
+ if ('i18n' in definition) {
105
+ errors.push({
106
+ rule: 'schemas/field-i18n',
107
+ message: `Field "${fieldName}": field-level locale settings ("i18n") are not supported.`,
108
+ file,
109
+ });
110
+ }
111
+ }
112
+ return errors;
113
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,132 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { validateSchemas } from './validate-schemas';
6
+ describe('validateSchemas', () => {
7
+ let scratch = null;
8
+ afterEach(() => {
9
+ if (scratch) {
10
+ rmSync(scratch, { recursive: true, force: true });
11
+ scratch = null;
12
+ }
13
+ });
14
+ function writeProject(schemas) {
15
+ scratch = mkdtempSync(join(tmpdir(), 'validate-schemas-'));
16
+ mkdirSync(join(scratch, 'src/schemas'), { recursive: true });
17
+ for (const [fileName, schema] of Object.entries(schemas)) {
18
+ writeFileSync(join(scratch, 'src/schemas', fileName), typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2));
19
+ }
20
+ return scratch;
21
+ }
22
+ it('accepts a valid schema', () => {
23
+ const cwd = writeProject({
24
+ 'BlogPost.json': {
25
+ uid: 'BlogPost',
26
+ name: 'Blog Posts',
27
+ fields: {
28
+ title: { type: 'string' },
29
+ body: { type: 'richtext' },
30
+ coverImage: { type: 'media' },
31
+ featured: { type: 'boolean' },
32
+ },
33
+ },
34
+ });
35
+ expect(validateSchemas({ cwd })).toEqual({ errors: [], schemasChecked: 1 });
36
+ });
37
+ it('returns no errors when the schemas directory does not exist', () => {
38
+ scratch = mkdtempSync(join(tmpdir(), 'validate-schemas-'));
39
+ expect(validateSchemas({ cwd: scratch })).toEqual({ errors: [], schemasChecked: 0 });
40
+ });
41
+ it('rejects reserved field names like slug', () => {
42
+ const cwd = writeProject({
43
+ 'TeamMember.json': {
44
+ uid: 'TeamMember',
45
+ name: 'Team Members',
46
+ fields: {
47
+ name: { type: 'string' },
48
+ slug: { type: 'string' },
49
+ },
50
+ },
51
+ });
52
+ const { errors } = validateSchemas({ cwd });
53
+ expect(errors).toHaveLength(1);
54
+ expect(errors[0]).toMatchObject({ rule: 'schemas/reserved-field', file: 'src/schemas/TeamMember.json' });
55
+ expect(errors[0].message).toContain('"slug" is reserved');
56
+ expect(errors[0].message).toContain('entry data');
57
+ });
58
+ it('rejects every reserved metadata name', () => {
59
+ const cwd = writeProject({
60
+ 'Bad.json': {
61
+ uid: 'Bad',
62
+ name: 'Bad',
63
+ fields: Object.fromEntries(['id', 'documentId', 'slug', 'locale', 'createdAt', 'updatedAt', '__fimo'].map((name) => [
64
+ name,
65
+ { type: 'string' },
66
+ ])),
67
+ },
68
+ });
69
+ const { errors } = validateSchemas({ cwd });
70
+ expect(errors).toHaveLength(7);
71
+ expect(errors.every((err) => err.rule === 'schemas/reserved-field')).toBe(true);
72
+ });
73
+ it('rejects unsupported field types', () => {
74
+ const cwd = writeProject({
75
+ 'Product.json': {
76
+ uid: 'Product',
77
+ name: 'Products',
78
+ fields: { price: { type: 'decimal' } },
79
+ },
80
+ });
81
+ const { errors } = validateSchemas({ cwd });
82
+ expect(errors).toHaveLength(1);
83
+ expect(errors[0]).toMatchObject({ rule: 'schemas/field-type' });
84
+ expect(errors[0].message).toContain('"decimal"');
85
+ expect(errors[0].message).toContain('string, richtext, media');
86
+ });
87
+ it('rejects field-level i18n settings', () => {
88
+ const cwd = writeProject({
89
+ 'Page.json': {
90
+ uid: 'Page',
91
+ name: 'Pages',
92
+ fields: { title: { type: 'string', i18n: true } },
93
+ },
94
+ });
95
+ const { errors } = validateSchemas({ cwd });
96
+ expect(errors).toHaveLength(1);
97
+ expect(errors[0]).toMatchObject({ rule: 'schemas/field-i18n' });
98
+ });
99
+ it('rejects a uid that does not match the file name', () => {
100
+ const cwd = writeProject({
101
+ 'TeamMember.json': {
102
+ uid: 'Member',
103
+ name: 'Team Members',
104
+ fields: { name: { type: 'string' } },
105
+ },
106
+ });
107
+ const { errors } = validateSchemas({ cwd });
108
+ expect(errors).toHaveLength(1);
109
+ expect(errors[0]).toMatchObject({ rule: 'schemas/uid' });
110
+ });
111
+ it('rejects missing uid, name, and fields', () => {
112
+ const cwd = writeProject({ 'Empty.json': {} });
113
+ const { errors } = validateSchemas({ cwd });
114
+ expect(errors.map((err) => err.rule)).toEqual(['schemas/uid', 'schemas/name', 'schemas/fields']);
115
+ });
116
+ it('reports invalid JSON', () => {
117
+ const cwd = writeProject({ 'Broken.json': '{ not json' });
118
+ const { errors } = validateSchemas({ cwd });
119
+ expect(errors).toHaveLength(1);
120
+ expect(errors[0]).toMatchObject({ rule: 'schemas/parse' });
121
+ });
122
+ it('validates multiple files and counts them', () => {
123
+ const cwd = writeProject({
124
+ 'Good.json': { uid: 'Good', name: 'Good', fields: { title: { type: 'string' } } },
125
+ 'Bad.json': { uid: 'Bad', name: 'Bad', fields: { slug: { type: 'string' } } },
126
+ });
127
+ const result = validateSchemas({ cwd });
128
+ expect(result.schemasChecked).toBe(2);
129
+ expect(result.errors).toHaveLength(1);
130
+ expect(result.errors[0].file).toBe('src/schemas/Bad.json');
131
+ });
132
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fimo",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Fimo CLI - create, deploy, and manage Fimo projects",
5
5
  "bin": {
6
6
  "fimo": "dist/cli/index.js"
@@ -21,7 +21,7 @@
21
21
  "cmdk": "^1.1.1",
22
22
  "date-fns": "^4.1.0",
23
23
  "embla-carousel-react": "^8.6.0",
24
- "fimo": "0.3.1",
24
+ "fimo": "0.3.2",
25
25
  "input-otp": "^1.4.2",
26
26
  "isbot": "^5",
27
27
  "lucide-react": "^0.577.0",