kintone-migrator 0.24.3 → 0.24.5

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.
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ import { parse, stringify } from "yaml";
7
7
  import { KintoneRestAPIClient, KintoneRestAPIError } from "@kintone/rest-api-client";
8
8
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
9
9
  import { basename, dirname, extname, join, resolve } from "node:path";
10
+ import { realpathSync } from "node:fs";
10
11
  import * as v from "valibot";
11
12
  import pc from "picocolors";
12
13
 
@@ -290,6 +291,10 @@ var ConflictError = class extends ApplicationError {
290
291
  function isConflictError(error) {
291
292
  return error instanceof ConflictError;
292
293
  }
294
+ const UnauthenticatedErrorCode = {
295
+ AuthenticationRequired: "AUTHENTICATION_REQUIRED",
296
+ InvalidCredentials: "INVALID_CREDENTIALS"
297
+ };
293
298
  var UnauthenticatedError = class extends ApplicationError {
294
299
  name = "UnauthenticatedError";
295
300
  constructor(code, message, cause) {
@@ -300,6 +305,7 @@ var UnauthenticatedError = class extends ApplicationError {
300
305
  function isUnauthenticatedError(error) {
301
306
  return error instanceof UnauthenticatedError;
302
307
  }
308
+ const ForbiddenErrorCode = { InsufficientPermissions: "INSUFFICIENT_PERMISSIONS" };
303
309
  var ForbiddenError = class extends ApplicationError {
304
310
  name = "ForbiddenError";
305
311
  constructor(code, message, cause) {
@@ -340,6 +346,16 @@ function isSystemError(error) {
340
346
  return error instanceof SystemError;
341
347
  }
342
348
 
349
+ //#endregion
350
+ //#region src/core/application/applyFromConfigBase.ts
351
+ async function applyFromConfig(config) {
352
+ const result = await config.getStorage();
353
+ if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, config.notFoundMessage);
354
+ const parsed = config.parseConfig(result.content);
355
+ const remote = await config.fetchRemote();
356
+ await config.update(parsed, remote);
357
+ }
358
+
343
359
  //#endregion
344
360
  //#region src/lib/typeGuards.ts
345
361
  /**
@@ -525,16 +541,74 @@ function parseActionConfigText(rawText) {
525
541
  //#endregion
526
542
  //#region src/core/application/action/applyAction.ts
527
543
  async function applyAction({ container }) {
528
- const result = await container.actionStorage.get();
529
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Action config file not found");
530
- const config = parseActionConfigText(result.content);
531
- const current = await container.actionConfigurator.getActions();
532
- await container.actionConfigurator.updateActions({
533
- actions: config.actions,
534
- revision: current.revision
544
+ await applyFromConfig({
545
+ getStorage: () => container.actionStorage.get(),
546
+ parseConfig: parseActionConfigText,
547
+ fetchRemote: () => container.actionConfigurator.getActions(),
548
+ update: async (config, current) => {
549
+ await container.actionConfigurator.updateActions({
550
+ actions: config.actions,
551
+ revision: current.revision
552
+ });
553
+ },
554
+ notFoundMessage: "Action config file not found"
535
555
  });
536
556
  }
537
557
 
558
+ //#endregion
559
+ //#region src/core/adapters/kintone/wrapKintoneError.ts
560
+ const KINTONE_REVISION_CONFLICT_CODE = "GAIA_CO02";
561
+ const KINTONE_MAINTENANCE_MODE_CODE = "GAIA_NO02";
562
+ function formatMessage(message, error) {
563
+ if (error instanceof KintoneRestAPIError && error.message) return `${message}: ${error.message}`;
564
+ if (error instanceof Error && error.message) return `${message}: ${error.message}`;
565
+ return message;
566
+ }
567
+ /**
568
+ * Converts an unknown error thrown during a kintone API call into the
569
+ * appropriate application-layer error.
570
+ *
571
+ * - {@link BusinessRuleError} and {@link ApplicationError} (including all
572
+ * subclasses such as {@link SystemError}, {@link ValidationError}, etc.)
573
+ * are re-thrown as-is so that errors raised inside a try block are never
574
+ * silently converted.
575
+ * - {@link KintoneRestAPIError} is mapped by HTTP status (401/403/404/409/400)
576
+ * and kintone-specific codes (`GAIA_CO02` revision conflict, `GAIA_NO02`
577
+ * maintenance mode).
578
+ * - Everything else becomes a {@link SystemError} with code `ExternalApiError`.
579
+ *
580
+ * **Note on 400 mapping**: All 400 errors (except `GAIA_CO02`) are mapped to
581
+ * {@link ValidationError}. This is intentionally simplified — some 400 codes
582
+ * (e.g. `GAIA_RE18` record limit, `GAIA_AP01` app unavailable) may have
583
+ * different semantics, but for this CLI tool a single `ValidationError`
584
+ * with the original kintone message in the error detail is sufficient.
585
+ *
586
+ * @throws {UnauthenticatedError} when kintone returns 401
587
+ * @throws {ForbiddenError} when kintone returns 403 (non-GAIA_NO02)
588
+ * @throws {SystemError} when kintone returns 403 with GAIA_NO02 (maintenance mode)
589
+ * @throws {NotFoundError} when kintone returns 404
590
+ * @throws {ConflictError} when kintone returns 409 or GAIA_CO02
591
+ * @throws {ValidationError} when kintone returns 400 (non-GAIA_CO02)
592
+ * @throws {SystemError} for all other kintone errors and unknown errors
593
+ */
594
+ function wrapKintoneError(error, message) {
595
+ if (isBusinessRuleError(error)) throw error;
596
+ if (error instanceof ApplicationError) throw error;
597
+ const detail = formatMessage(message, error);
598
+ if (error instanceof KintoneRestAPIError) {
599
+ if (error.status === 401) throw new UnauthenticatedError(UnauthenticatedErrorCode.InvalidCredentials, detail, error);
600
+ if (error.status === 403) {
601
+ if (error.code === KINTONE_MAINTENANCE_MODE_CODE) throw new SystemError(SystemErrorCode.ExternalApiError, `${detail} (app is in maintenance mode — GAIA_NO02)`, error);
602
+ throw new ForbiddenError(ForbiddenErrorCode.InsufficientPermissions, detail, error);
603
+ }
604
+ if (error.status === 404) throw new NotFoundError(NotFoundErrorCode.NotFound, detail, error);
605
+ if (error.code === KINTONE_REVISION_CONFLICT_CODE) throw new ConflictError(ConflictErrorCode.Conflict, `${detail} (revision conflict — GAIA_CO02). Please retry the operation.`, error);
606
+ if (error.status === 409) throw new ConflictError(ConflictErrorCode.Conflict, detail, error);
607
+ if (error.status === 400) throw new ValidationError(ValidationErrorCode.InvalidInput, detail, error);
608
+ }
609
+ throw new SystemError(SystemErrorCode.ExternalApiError, detail, error);
610
+ }
611
+
538
612
  //#endregion
539
613
  //#region src/core/adapters/kintone/actionConfigurator.ts
540
614
  function fromKintoneDestApp(raw) {
@@ -620,9 +694,7 @@ var KintoneActionConfigurator = class {
620
694
  revision: response.revision
621
695
  };
622
696
  } catch (error) {
623
- if (isBusinessRuleError(error)) throw error;
624
- if (error instanceof SystemError) throw error;
625
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get app actions", error);
697
+ throw wrapKintoneError(error, "Failed to get app actions");
626
698
  }
627
699
  }
628
700
  async updateActions(params) {
@@ -636,15 +708,22 @@ var KintoneActionConfigurator = class {
636
708
  if (params.revision !== void 0) requestParams.revision = params.revision;
637
709
  return { revision: (await this.client.app.updateAppActions(requestParams)).revision };
638
710
  } catch (error) {
639
- if (isBusinessRuleError(error)) throw error;
640
- if (error instanceof SystemError) throw error;
641
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update app actions", error);
711
+ throw wrapKintoneError(error, "Failed to update app actions");
642
712
  }
643
713
  }
644
714
  };
645
715
 
646
716
  //#endregion
647
717
  //#region src/core/adapters/kintone/appDeployer.ts
718
+ const VALID_DEPLOY_STATUSES = new Set([
719
+ "PROCESSING",
720
+ "SUCCESS",
721
+ "FAIL",
722
+ "CANCEL"
723
+ ]);
724
+ function isDeployStatus(value) {
725
+ return VALID_DEPLOY_STATUSES.has(value);
726
+ }
648
727
  const DEFAULT_POLL_INTERVAL_MS = 1e3;
649
728
  const DEFAULT_MAX_RETRIES = 180;
650
729
  var KintoneAppDeployer = class {
@@ -659,12 +738,10 @@ var KintoneAppDeployer = class {
659
738
  async deploy() {
660
739
  try {
661
740
  await this.client.app.deployApp({ apps: [{ app: this.appId }] });
662
- await this.waitForDeployment();
663
741
  } catch (error) {
664
- if (isBusinessRuleError(error)) throw error;
665
- if (error instanceof SystemError) throw error;
666
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to deploy app", error);
742
+ throw wrapKintoneError(error, "Failed to deploy app");
667
743
  }
744
+ await this.waitForDeployment();
668
745
  }
669
746
  async waitForDeployment() {
670
747
  let lastPollError;
@@ -678,20 +755,21 @@ var KintoneAppDeployer = class {
678
755
  consecutivePollFailures = 0;
679
756
  } catch (error) {
680
757
  if (isBusinessRuleError(error)) throw error;
681
- if (error instanceof SystemError) throw error;
758
+ if (error instanceof ApplicationError) throw error;
682
759
  lastPollError = error;
683
760
  consecutivePollFailures++;
684
761
  if (consecutivePollFailures >= maxConsecutivePollFailures) throw new SystemError(SystemErrorCode.ExternalApiError, `Deploy status polling failed ${maxConsecutivePollFailures} consecutive times`, lastPollError);
685
762
  continue;
686
763
  }
687
- const status = apps[0]?.status;
688
- switch (status) {
764
+ if (apps.length === 0) throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status response contained no apps");
765
+ const rawStatus = apps[0].status;
766
+ if (rawStatus === void 0) throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status unavailable");
767
+ if (!isDeployStatus(rawStatus)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected deploy status: ${rawStatus}`);
768
+ switch (rawStatus) {
689
769
  case "SUCCESS": return;
690
770
  case "FAIL": throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment failed");
691
771
  case "CANCEL": throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment was cancelled");
692
772
  case "PROCESSING": continue;
693
- case void 0: throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status unavailable");
694
- default: throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected deploy status: ${status}`);
695
773
  }
696
774
  }
697
775
  throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment timed out", lastPollError);
@@ -820,9 +898,7 @@ var KintoneCustomizationConfigurator = class {
820
898
  revision: response.revision
821
899
  };
822
900
  } catch (error) {
823
- if (isBusinessRuleError(error)) throw error;
824
- if (error instanceof SystemError) throw error;
825
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get app customization", error);
901
+ throw wrapKintoneError(error, "Failed to get app customization");
826
902
  }
827
903
  }
828
904
  async updateCustomization(params) {
@@ -843,9 +919,7 @@ var KintoneCustomizationConfigurator = class {
843
919
  const response = await this.client.app.updateAppCustomize(requestParams);
844
920
  return { revision: String(response.revision) };
845
921
  } catch (error) {
846
- if (isBusinessRuleError(error)) throw error;
847
- if (error instanceof SystemError) throw error;
848
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update app customization", error);
922
+ throw wrapKintoneError(error, "Failed to update app customization");
849
923
  }
850
924
  }
851
925
  };
@@ -857,31 +931,77 @@ var KintoneFileDownloader = class {
857
931
  this.client = client;
858
932
  }
859
933
  async download(fileKey) {
860
- if (!fileKey) throw new SystemError(SystemErrorCode.ExternalApiError, "fileKey must not be empty");
934
+ if (!fileKey) throw new ValidationError(ValidationErrorCode.InvalidInput, "fileKey must not be empty");
861
935
  try {
862
936
  return await this.client.file.downloadFile({ fileKey });
863
937
  } catch (error) {
864
- if (isBusinessRuleError(error)) throw error;
865
- if (error instanceof SystemError) throw error;
866
- throw new SystemError(SystemErrorCode.ExternalApiError, `Failed to download file: ${fileKey}`, error);
938
+ throw wrapKintoneError(error, `Failed to download file: ${fileKey}`);
867
939
  }
868
940
  }
869
941
  };
870
942
 
943
+ //#endregion
944
+ //#region src/lib/safePath.ts
945
+ /**
946
+ * Returns `true` if {@link targetPath} resolves to a location within
947
+ * {@link baseDir}. Follows symlinks via `realpathSync` for both the base
948
+ * directory and the target path when they exist on disk, falling back to
949
+ * textual `resolve` otherwise (e.g. in tests with virtual paths or when
950
+ * the target does not yet exist).
951
+ *
952
+ * {@link targetPath} may be either a relative path (resolved against
953
+ * {@link baseDir}) or an absolute path (used as-is by `path.resolve`).
954
+ * When callers have already resolved the path via `path.resolve(baseDir, x)`,
955
+ * passing the result as an absolute path is safe — `resolve(base, absPath)`
956
+ * returns `absPath` unchanged.
957
+ *
958
+ * **Limitation — symlinks**: If the target path does not exist on disk,
959
+ * symlink components within it cannot be resolved. The check falls back
960
+ * to a textual prefix comparison, which still catches `..` traversal but
961
+ * cannot detect symlink-based escapes for non-existent paths.
962
+ *
963
+ * **Limitation — TOCTOU**: This check is inherently subject to a
964
+ * time-of-check-to-time-of-use race: the filesystem may change between
965
+ * this call and the subsequent file operation. This is acceptable for a
966
+ * CLI tool where the user controls the local environment.
967
+ *
968
+ * **Note**: This function performs synchronous I/O (`realpathSync`).
969
+ *
970
+ * Callers decide how to handle the result.
971
+ */
972
+ function isSafePath(targetPath, baseDir) {
973
+ if (targetPath.includes("\0") || baseDir.includes("\0")) return false;
974
+ let resolvedBase;
975
+ try {
976
+ resolvedBase = realpathSync(baseDir);
977
+ } catch {
978
+ resolvedBase = resolve(baseDir);
979
+ }
980
+ const textualTarget = resolve(resolvedBase, targetPath);
981
+ let resolvedTarget;
982
+ try {
983
+ resolvedTarget = realpathSync(textualTarget);
984
+ } catch {
985
+ resolvedTarget = textualTarget;
986
+ }
987
+ return resolvedTarget.startsWith(`${resolvedBase}/`) || resolvedTarget === resolvedBase;
988
+ }
989
+
871
990
  //#endregion
872
991
  //#region src/core/adapters/kintone/fileUploader.ts
873
992
  var KintoneFileUploader = class {
874
- constructor(client) {
993
+ constructor(client, baseDir) {
875
994
  this.client = client;
995
+ this.baseDir = baseDir;
876
996
  }
877
997
  async upload(filePath) {
878
- if (!filePath) throw new SystemError(SystemErrorCode.ExternalApiError, "filePath must not be empty");
998
+ if (!filePath) throw new ValidationError(ValidationErrorCode.InvalidInput, "filePath must not be empty");
999
+ const resolvedPath = resolve(this.baseDir, filePath);
1000
+ if (!isSafePath(resolvedPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${resolvedPath}" escapes base directory "${this.baseDir}"`);
879
1001
  try {
880
- return { fileKey: (await this.client.file.uploadFile({ file: { path: filePath } })).fileKey };
1002
+ return { fileKey: (await this.client.file.uploadFile({ file: { path: resolvedPath } })).fileKey };
881
1003
  } catch (error) {
882
- if (isBusinessRuleError(error)) throw error;
883
- if (error instanceof SystemError) throw error;
884
- throw new SystemError(SystemErrorCode.ExternalApiError, `Failed to upload file: ${filePath}`, error);
1004
+ throw wrapKintoneError(error, `Failed to upload file: ${filePath}`);
885
1005
  }
886
1006
  }
887
1007
  };
@@ -962,10 +1082,6 @@ const DECORATION_TYPES$1 = new Set([
962
1082
  "SPACER",
963
1083
  "HR"
964
1084
  ]);
965
- const KINTONE_REVISION_CONFLICT_CODE = "GAIA_CO02";
966
- function isRevisionConflict(error) {
967
- return error instanceof KintoneRestAPIError && error.code === KINTONE_REVISION_CONFLICT_CODE;
968
- }
969
1085
  /**
970
1086
  * Tracks the latest known kintone form revision for optimistic concurrency control.
971
1087
  *
@@ -978,10 +1094,13 @@ function isRevisionConflict(error) {
978
1094
  var RevisionTracker = class {
979
1095
  revision = void 0;
980
1096
  track(revision) {
981
- if (this.revision === void 0 || Number(revision) > Number(this.revision)) this.revision = revision;
1097
+ if (revision === "") throw new SystemError(SystemErrorCode.ExternalApiError, "Unexpected empty revision from kintone API");
1098
+ const revisionNum = Number(revision);
1099
+ if (!Number.isFinite(revisionNum)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric revision from kintone API: "${revision}"`);
1100
+ if (this.revision === void 0 || revisionNum > this.revision) this.revision = revisionNum;
982
1101
  }
983
1102
  get current() {
984
- return this.revision;
1103
+ return this.revision !== void 0 ? String(this.revision) : void 0;
985
1104
  }
986
1105
  };
987
1106
  function assertRecord(value, fieldPath) {
@@ -1253,7 +1372,7 @@ var KintoneFormConfigurator = class {
1253
1372
  app: this.appId,
1254
1373
  preview: true
1255
1374
  });
1256
- this.revisionTracker.track(revision);
1375
+ if (revision) this.revisionTracker.track(revision);
1257
1376
  const fields = /* @__PURE__ */ new Map();
1258
1377
  for (const [code, prop] of Object.entries(properties)) {
1259
1378
  const kintoneProp = prop;
@@ -1264,9 +1383,7 @@ var KintoneFormConfigurator = class {
1264
1383
  }
1265
1384
  return fields;
1266
1385
  } catch (error) {
1267
- if (isBusinessRuleError(error)) throw error;
1268
- if (error instanceof SystemError) throw error;
1269
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get form fields", error);
1386
+ throw wrapKintoneError(error, "Failed to get form fields");
1270
1387
  }
1271
1388
  }
1272
1389
  async addFields(fields) {
@@ -1280,10 +1397,7 @@ var KintoneFormConfigurator = class {
1280
1397
  });
1281
1398
  if (response.revision) this.revisionTracker.track(response.revision);
1282
1399
  } catch (error) {
1283
- if (isBusinessRuleError(error)) throw error;
1284
- if (error instanceof SystemError) throw error;
1285
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1286
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add form fields", error);
1400
+ throw wrapKintoneError(error, "Failed to add form fields");
1287
1401
  }
1288
1402
  }
1289
1403
  async updateFields(fields) {
@@ -1297,10 +1411,7 @@ var KintoneFormConfigurator = class {
1297
1411
  });
1298
1412
  if (response.revision) this.revisionTracker.track(response.revision);
1299
1413
  } catch (error) {
1300
- if (isBusinessRuleError(error)) throw error;
1301
- if (error instanceof SystemError) throw error;
1302
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1303
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update form fields", error);
1414
+ throw wrapKintoneError(error, "Failed to update form fields");
1304
1415
  }
1305
1416
  }
1306
1417
  async deleteFields(fieldCodes) {
@@ -1312,10 +1423,7 @@ var KintoneFormConfigurator = class {
1312
1423
  });
1313
1424
  if (response.revision) this.revisionTracker.track(response.revision);
1314
1425
  } catch (error) {
1315
- if (isBusinessRuleError(error)) throw error;
1316
- if (error instanceof SystemError) throw error;
1317
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1318
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to delete form fields", error);
1426
+ throw wrapKintoneError(error, "Failed to delete form fields");
1319
1427
  }
1320
1428
  }
1321
1429
  async getLayout() {
@@ -1324,12 +1432,10 @@ var KintoneFormConfigurator = class {
1324
1432
  app: this.appId,
1325
1433
  preview: true
1326
1434
  });
1327
- this.revisionTracker.track(response.revision);
1435
+ if (response.revision) this.revisionTracker.track(response.revision);
1328
1436
  return response.layout.map(fromKintoneLayoutItem);
1329
1437
  } catch (error) {
1330
- if (isBusinessRuleError(error)) throw error;
1331
- if (error instanceof SystemError) throw error;
1332
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get form layout", error);
1438
+ throw wrapKintoneError(error, "Failed to get form layout");
1333
1439
  }
1334
1440
  }
1335
1441
  async updateLayout(layout) {
@@ -1342,10 +1448,7 @@ var KintoneFormConfigurator = class {
1342
1448
  });
1343
1449
  if (response.revision) this.revisionTracker.track(response.revision);
1344
1450
  } catch (error) {
1345
- if (isBusinessRuleError(error)) throw error;
1346
- if (error instanceof SystemError) throw error;
1347
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1348
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update form layout", error);
1451
+ throw wrapKintoneError(error, "Failed to update form layout");
1349
1452
  }
1350
1453
  }
1351
1454
  };
@@ -1466,9 +1569,7 @@ var KintoneRecordManager = class {
1466
1569
  };
1467
1570
  });
1468
1571
  } catch (error) {
1469
- if (isBusinessRuleError(error)) throw error;
1470
- if (error instanceof SystemError) throw error;
1471
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get records", error);
1572
+ throw wrapKintoneError(error, "Failed to get records");
1472
1573
  }
1473
1574
  }
1474
1575
  async addRecords(records) {
@@ -1479,9 +1580,7 @@ var KintoneRecordManager = class {
1479
1580
  records: records.map(toKintoneRecord)
1480
1581
  });
1481
1582
  } catch (error) {
1482
- if (isBusinessRuleError(error)) throw error;
1483
- if (error instanceof SystemError) throw error;
1484
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add records", error);
1583
+ throw wrapKintoneError(error, "Failed to add records");
1485
1584
  }
1486
1585
  }
1487
1586
  async updateRecords(records) {
@@ -1495,9 +1594,7 @@ var KintoneRecordManager = class {
1495
1594
  }))
1496
1595
  });
1497
1596
  } catch (error) {
1498
- if (isBusinessRuleError(error)) throw error;
1499
- if (error instanceof SystemError) throw error;
1500
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update records", error);
1597
+ throw wrapKintoneError(error, "Failed to update records");
1501
1598
  }
1502
1599
  }
1503
1600
  async deleteAllRecords() {
@@ -1514,9 +1611,7 @@ var KintoneRecordManager = class {
1514
1611
  });
1515
1612
  return { deletedCount: records.length };
1516
1613
  } catch (error) {
1517
- if (isBusinessRuleError(error)) throw error;
1518
- if (error instanceof SystemError) throw error;
1519
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to delete all records", error);
1614
+ throw wrapKintoneError(error, "Failed to delete all records");
1520
1615
  }
1521
1616
  }
1522
1617
  };
@@ -1530,10 +1625,15 @@ function createLocalFileCustomizationStorage(filePath) {
1530
1625
  //#endregion
1531
1626
  //#region src/core/adapters/local/fileWriter.ts
1532
1627
  var LocalFileWriter = class {
1628
+ constructor(baseDir = process.cwd()) {
1629
+ this.baseDir = baseDir;
1630
+ }
1533
1631
  async write(filePath, data) {
1632
+ const resolvedPath = resolve(this.baseDir, filePath);
1633
+ if (!isSafePath(resolvedPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${resolvedPath}" escapes base directory "${this.baseDir}"`);
1534
1634
  try {
1535
- await mkdir(dirname(filePath), { recursive: true });
1536
- await writeFile(filePath, Buffer.from(new Uint8Array(data)));
1635
+ await mkdir(dirname(resolvedPath), { recursive: true });
1636
+ await writeFile(resolvedPath, Buffer.from(new Uint8Array(data)));
1537
1637
  } catch (error) {
1538
1638
  throw new SystemError(SystemErrorCode.StorageError, `Failed to write file: ${filePath}`, error);
1539
1639
  }
@@ -1562,11 +1662,7 @@ function buildKintoneAuth(auth) {
1562
1662
  };
1563
1663
  }
1564
1664
  function createCliContainer(config) {
1565
- const client = new KintoneRestAPIClient({
1566
- baseUrl: config.baseUrl,
1567
- auth: buildKintoneAuth(config.auth),
1568
- guestSpaceId: config.guestSpaceId
1569
- });
1665
+ const client = config.client ?? createKintoneClient(config);
1570
1666
  return {
1571
1667
  formConfigurator: new KintoneFormConfigurator(client, config.appId),
1572
1668
  schemaStorage: createLocalFileSchemaStorage(config.schemaFilePath),
@@ -1575,38 +1671,36 @@ function createCliContainer(config) {
1575
1671
  }
1576
1672
  function createSeedCliContainer(config) {
1577
1673
  return {
1578
- recordManager: new KintoneRecordManager(new KintoneRestAPIClient({
1579
- baseUrl: config.baseUrl,
1580
- auth: buildKintoneAuth(config.auth),
1581
- guestSpaceId: config.guestSpaceId
1582
- }), config.appId),
1674
+ recordManager: new KintoneRecordManager(config.client ?? createKintoneClient(config), config.appId),
1583
1675
  seedStorage: createLocalFileSeedStorage(config.seedFilePath)
1584
1676
  };
1585
1677
  }
1586
1678
  function createCustomizationCliContainer(config) {
1587
- const client = new KintoneRestAPIClient({
1588
- baseUrl: config.baseUrl,
1589
- auth: buildKintoneAuth(config.auth),
1590
- guestSpaceId: config.guestSpaceId
1591
- });
1679
+ const client = config.client ?? createKintoneClient(config);
1592
1680
  return {
1593
1681
  customizationConfigurator: new KintoneCustomizationConfigurator(client, config.appId),
1594
1682
  customizationStorage: createLocalFileCustomizationStorage(config.customizeFilePath),
1595
- fileUploader: new KintoneFileUploader(client),
1683
+ fileUploader: new KintoneFileUploader(client, process.cwd()),
1596
1684
  fileDownloader: new KintoneFileDownloader(client),
1597
- fileWriter: new LocalFileWriter(),
1685
+ fileWriter: new LocalFileWriter(process.cwd()),
1598
1686
  appDeployer: new KintoneAppDeployer(client, config.appId)
1599
1687
  };
1600
1688
  }
1601
1689
 
1602
1690
  //#endregion
1603
- //#region src/core/application/container/actionCli.ts
1604
- function createActionCliContainer(config) {
1605
- const client = new KintoneRestAPIClient({
1691
+ //#region src/core/application/container/kintoneClient.ts
1692
+ function createKintoneClient(config) {
1693
+ return new KintoneRestAPIClient({
1606
1694
  baseUrl: config.baseUrl,
1607
1695
  auth: buildKintoneAuth(config.auth),
1608
1696
  guestSpaceId: config.guestSpaceId
1609
1697
  });
1698
+ }
1699
+
1700
+ //#endregion
1701
+ //#region src/core/application/container/actionCli.ts
1702
+ function createActionCliContainer(config) {
1703
+ const client = config.client ?? createKintoneClient(config);
1610
1704
  return {
1611
1705
  actionConfigurator: new KintoneActionConfigurator(client, config.appId),
1612
1706
  actionStorage: createLocalFileActionStorage(config.actionFilePath),
@@ -2543,15 +2637,25 @@ const ActionConfigSerializer = { serialize: (config) => {
2543
2637
  } };
2544
2638
 
2545
2639
  //#endregion
2546
- //#region src/core/application/action/captureAction.ts
2547
- async function captureAction({ container }) {
2548
- const { actions } = await container.actionConfigurator.getActions();
2640
+ //#region src/core/application/captureFromConfigBase.ts
2641
+ async function captureFromConfig(config) {
2642
+ const remote = await config.fetchRemote();
2549
2643
  return {
2550
- configText: ActionConfigSerializer.serialize({ actions }),
2551
- hasExistingConfig: (await container.actionStorage.get()).exists
2644
+ configText: config.serialize(remote),
2645
+ hasExistingConfig: (await config.getStorage()).exists
2552
2646
  };
2553
2647
  }
2554
2648
 
2649
+ //#endregion
2650
+ //#region src/core/application/action/captureAction.ts
2651
+ async function captureAction({ container }) {
2652
+ return captureFromConfig({
2653
+ fetchRemote: () => container.actionConfigurator.getActions(),
2654
+ serialize: ({ actions }) => ActionConfigSerializer.serialize({ actions }),
2655
+ getStorage: () => container.actionStorage.get()
2656
+ });
2657
+ }
2658
+
2555
2659
  //#endregion
2556
2660
  //#region src/core/application/action/saveAction.ts
2557
2661
  async function saveAction({ container, input }) {
@@ -2883,13 +2987,17 @@ function parseAdminNotesConfigText(rawText) {
2883
2987
  //#endregion
2884
2988
  //#region src/core/application/adminNotes/applyAdminNotes.ts
2885
2989
  async function applyAdminNotes({ container }) {
2886
- const result = await container.adminNotesStorage.get();
2887
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Admin notes config file not found");
2888
- const config = parseAdminNotesConfigText(result.content);
2889
- const current = await container.adminNotesConfigurator.getAdminNotes();
2890
- await container.adminNotesConfigurator.updateAdminNotes({
2891
- config,
2892
- revision: current.revision
2990
+ await applyFromConfig({
2991
+ getStorage: () => container.adminNotesStorage.get(),
2992
+ parseConfig: parseAdminNotesConfigText,
2993
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
2994
+ update: async (config, current) => {
2995
+ await container.adminNotesConfigurator.updateAdminNotes({
2996
+ config,
2997
+ revision: current.revision
2998
+ });
2999
+ },
3000
+ notFoundMessage: "Admin notes config file not found"
2893
3001
  });
2894
3002
  }
2895
3003
 
@@ -2914,9 +3022,7 @@ var KintoneAdminNotesConfigurator = class {
2914
3022
  revision: response.revision
2915
3023
  };
2916
3024
  } catch (error) {
2917
- if (isBusinessRuleError(error)) throw error;
2918
- if (error instanceof SystemError) throw error;
2919
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get admin notes", error);
3025
+ throw wrapKintoneError(error, "Failed to get admin notes");
2920
3026
  }
2921
3027
  }
2922
3028
  async updateAdminNotes(params) {
@@ -2929,9 +3035,7 @@ var KintoneAdminNotesConfigurator = class {
2929
3035
  if (params.revision !== void 0) requestParams.revision = params.revision;
2930
3036
  return { revision: (await this.client.app.updateAdminNotes(requestParams)).revision };
2931
3037
  } catch (error) {
2932
- if (isBusinessRuleError(error)) throw error;
2933
- if (error instanceof SystemError) throw error;
2934
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update admin notes", error);
3038
+ throw wrapKintoneError(error, "Failed to update admin notes");
2935
3039
  }
2936
3040
  }
2937
3041
  };
@@ -2945,11 +3049,7 @@ function createLocalFileAdminNotesStorage(filePath) {
2945
3049
  //#endregion
2946
3050
  //#region src/core/application/container/adminNotesCli.ts
2947
3051
  function createAdminNotesCliContainer(config) {
2948
- const client = new KintoneRestAPIClient({
2949
- baseUrl: config.baseUrl,
2950
- auth: buildKintoneAuth(config.auth),
2951
- guestSpaceId: config.guestSpaceId
2952
- });
3052
+ const client = config.client ?? createKintoneClient(config);
2953
3053
  return {
2954
3054
  adminNotesConfigurator: new KintoneAdminNotesConfigurator(client, config.appId),
2955
3055
  adminNotesStorage: createLocalFileAdminNotesStorage(config.adminNotesFilePath),
@@ -3037,11 +3137,11 @@ const AdminNotesConfigSerializer = { serialize: (config) => {
3037
3137
  //#endregion
3038
3138
  //#region src/core/application/adminNotes/captureAdminNotes.ts
3039
3139
  async function captureAdminNotes({ container }) {
3040
- const { config } = await container.adminNotesConfigurator.getAdminNotes();
3041
- return {
3042
- configText: AdminNotesConfigSerializer.serialize(config),
3043
- hasExistingConfig: (await container.adminNotesStorage.get()).exists
3044
- };
3140
+ return captureFromConfig({
3141
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
3142
+ serialize: ({ config }) => AdminNotesConfigSerializer.serialize(config),
3143
+ getStorage: () => container.adminNotesStorage.get()
3144
+ });
3045
3145
  }
3046
3146
 
3047
3147
  //#endregion
@@ -3207,13 +3307,17 @@ function parseAppPermissionConfigText(rawText) {
3207
3307
  //#endregion
3208
3308
  //#region src/core/application/appPermission/applyAppPermission.ts
3209
3309
  async function applyAppPermission({ container }) {
3210
- const result = await container.appPermissionStorage.get();
3211
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "App permission config file not found");
3212
- const config = parseAppPermissionConfigText(result.content);
3213
- const current = await container.appPermissionConfigurator.getAppPermissions();
3214
- await container.appPermissionConfigurator.updateAppPermissions({
3215
- rights: config.rights,
3216
- revision: current.revision
3310
+ await applyFromConfig({
3311
+ getStorage: () => container.appPermissionStorage.get(),
3312
+ parseConfig: parseAppPermissionConfigText,
3313
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3314
+ update: async (config, current) => {
3315
+ await container.appPermissionConfigurator.updateAppPermissions({
3316
+ rights: config.rights,
3317
+ revision: current.revision
3318
+ });
3319
+ },
3320
+ notFoundMessage: "App permission config file not found"
3217
3321
  });
3218
3322
  }
3219
3323
 
@@ -3281,9 +3385,7 @@ var KintoneAppPermissionConfigurator = class {
3281
3385
  revision: response.revision
3282
3386
  };
3283
3387
  } catch (error) {
3284
- if (isBusinessRuleError(error)) throw error;
3285
- if (error instanceof SystemError) throw error;
3286
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get app ACL", error);
3388
+ throw wrapKintoneError(error, "Failed to get app ACL");
3287
3389
  }
3288
3390
  }
3289
3391
  async updateAppPermissions(params) {
@@ -3295,9 +3397,7 @@ var KintoneAppPermissionConfigurator = class {
3295
3397
  if (params.revision !== void 0) requestParams.revision = params.revision;
3296
3398
  return { revision: (await this.client.app.updateAppAcl(requestParams)).revision };
3297
3399
  } catch (error) {
3298
- if (isBusinessRuleError(error)) throw error;
3299
- if (error instanceof SystemError) throw error;
3300
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update app ACL", error);
3400
+ throw wrapKintoneError(error, "Failed to update app ACL");
3301
3401
  }
3302
3402
  }
3303
3403
  };
@@ -3311,11 +3411,7 @@ function createLocalFileAppPermissionStorage(filePath) {
3311
3411
  //#endregion
3312
3412
  //#region src/core/application/container/appPermissionCli.ts
3313
3413
  function createAppPermissionCliContainer(config) {
3314
- const client = new KintoneRestAPIClient({
3315
- baseUrl: config.baseUrl,
3316
- auth: buildKintoneAuth(config.auth),
3317
- guestSpaceId: config.guestSpaceId
3318
- });
3414
+ const client = config.client ?? createKintoneClient(config);
3319
3415
  return {
3320
3416
  appPermissionConfigurator: new KintoneAppPermissionConfigurator(client, config.appId),
3321
3417
  appPermissionStorage: createLocalFileAppPermissionStorage(config.appAclFilePath),
@@ -3416,11 +3512,11 @@ const AppPermissionConfigSerializer = { serialize: (config) => {
3416
3512
  //#endregion
3417
3513
  //#region src/core/application/appPermission/captureAppPermission.ts
3418
3514
  async function captureAppPermission({ container }) {
3419
- const { rights } = await container.appPermissionConfigurator.getAppPermissions();
3420
- return {
3421
- configText: AppPermissionConfigSerializer.serialize({ rights }),
3422
- hasExistingConfig: (await container.appPermissionStorage.get()).exists
3423
- };
3515
+ return captureFromConfig({
3516
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3517
+ serialize: ({ rights }) => AppPermissionConfigSerializer.serialize({ rights }),
3518
+ getStorage: () => container.appPermissionStorage.get()
3519
+ });
3424
3520
  }
3425
3521
 
3426
3522
  //#endregion
@@ -3856,8 +3952,8 @@ function planResources(resources, platformDir, resourceType, relativeBaseDir) {
3856
3952
  filesToDownload
3857
3953
  };
3858
3954
  }
3859
- function planPlatform(remotePlatform, platformName, args) {
3860
- const platformDir = join(args.input.basePath, args.input.filePrefix, platformName);
3955
+ function planPlatform(remotePlatform, platformName, basePath, filePrefix) {
3956
+ const platformDir = join(basePath, filePrefix, platformName);
3861
3957
  const platformPrefix = platformName;
3862
3958
  const jsPlan = planResources(remotePlatform.js, platformDir, "js", platformPrefix);
3863
3959
  const cssPlan = planResources(remotePlatform.css, platformDir, "css", platformPrefix);
@@ -3885,18 +3981,18 @@ async function downloadFiles(files, container) {
3885
3981
  await container.fileWriter.write(file.absolutePath, data);
3886
3982
  }));
3887
3983
  }
3888
- async function captureCustomization(args) {
3889
- const existing = await args.container.customizationStorage.get();
3890
- const { scope, desktop, mobile } = await args.container.customizationConfigurator.getCustomization();
3891
- const desktopPlan = planPlatform(desktop, "desktop", args);
3892
- const mobilePlan = planPlatform(mobile, "mobile", args);
3984
+ async function captureCustomization({ container, input }) {
3985
+ const existing = await container.customizationStorage.get();
3986
+ const { scope, desktop, mobile } = await container.customizationConfigurator.getCustomization();
3987
+ const desktopPlan = planPlatform(desktop, "desktop", input.basePath, input.filePrefix);
3988
+ const mobilePlan = planPlatform(mobile, "mobile", input.basePath, input.filePrefix);
3893
3989
  const config = {
3894
3990
  scope,
3895
3991
  desktop: desktopPlan.platform,
3896
3992
  mobile: mobilePlan.platform
3897
3993
  };
3898
3994
  const configText = CustomizationConfigSerializer.serialize(config);
3899
- await downloadFiles([...desktopPlan.filesToDownload, ...mobilePlan.filesToDownload], args.container);
3995
+ await downloadFiles([...desktopPlan.filesToDownload, ...mobilePlan.filesToDownload], container);
3900
3996
  return {
3901
3997
  configText,
3902
3998
  hasExistingConfig: existing.exists,
@@ -4194,9 +4290,7 @@ var KintoneFieldPermissionConfigurator = class {
4194
4290
  revision: response.revision
4195
4291
  };
4196
4292
  } catch (error) {
4197
- if (isBusinessRuleError(error)) throw error;
4198
- if (error instanceof SystemError) throw error;
4199
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get field ACL", error);
4293
+ throw wrapKintoneError(error, "Failed to get field ACL");
4200
4294
  }
4201
4295
  }
4202
4296
  async updateFieldPermissions(params) {
@@ -4208,9 +4302,7 @@ var KintoneFieldPermissionConfigurator = class {
4208
4302
  if (params.revision !== void 0) requestParams.revision = params.revision;
4209
4303
  return { revision: (await this.client.app.updateFieldAcl(requestParams)).revision };
4210
4304
  } catch (error) {
4211
- if (isBusinessRuleError(error)) throw error;
4212
- if (error instanceof SystemError) throw error;
4213
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update field ACL", error);
4305
+ throw wrapKintoneError(error, "Failed to update field ACL");
4214
4306
  }
4215
4307
  }
4216
4308
  };
@@ -4224,11 +4316,7 @@ function createLocalFileFieldPermissionStorage(filePath) {
4224
4316
  //#endregion
4225
4317
  //#region src/core/application/container/fieldPermissionCli.ts
4226
4318
  function createFieldPermissionCliContainer(config) {
4227
- const client = new KintoneRestAPIClient({
4228
- baseUrl: config.baseUrl,
4229
- auth: buildKintoneAuth(config.auth),
4230
- guestSpaceId: config.guestSpaceId
4231
- });
4319
+ const client = config.client ?? createKintoneClient(config);
4232
4320
  return {
4233
4321
  fieldPermissionConfigurator: new KintoneFieldPermissionConfigurator(client, config.appId),
4234
4322
  fieldPermissionStorage: createLocalFileFieldPermissionStorage(config.fieldAclFilePath),
@@ -4303,13 +4391,17 @@ function parseFieldPermissionConfigText(rawText) {
4303
4391
  //#endregion
4304
4392
  //#region src/core/application/fieldPermission/applyFieldPermission.ts
4305
4393
  async function applyFieldPermission({ container }) {
4306
- const result = await container.fieldPermissionStorage.get();
4307
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Field permission config file not found");
4308
- const config = parseFieldPermissionConfigText(result.content);
4309
- const current = await container.fieldPermissionConfigurator.getFieldPermissions();
4310
- await container.fieldPermissionConfigurator.updateFieldPermissions({
4311
- rights: config.rights,
4312
- revision: current.revision
4394
+ await applyFromConfig({
4395
+ getStorage: () => container.fieldPermissionStorage.get(),
4396
+ parseConfig: parseFieldPermissionConfigText,
4397
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4398
+ update: async (config, current) => {
4399
+ await container.fieldPermissionConfigurator.updateFieldPermissions({
4400
+ rights: config.rights,
4401
+ revision: current.revision
4402
+ });
4403
+ },
4404
+ notFoundMessage: "Field permission config file not found"
4313
4405
  });
4314
4406
  }
4315
4407
 
@@ -4404,11 +4496,11 @@ const FieldPermissionConfigSerializer = { serialize: (config) => {
4404
4496
  //#endregion
4405
4497
  //#region src/core/application/fieldPermission/captureFieldPermission.ts
4406
4498
  async function captureFieldPermission({ container }) {
4407
- const { rights } = await container.fieldPermissionConfigurator.getFieldPermissions();
4408
- return {
4409
- configText: FieldPermissionConfigSerializer.serialize({ rights }),
4410
- hasExistingConfig: (await container.fieldPermissionStorage.get()).exists
4411
- };
4499
+ return captureFromConfig({
4500
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4501
+ serialize: ({ rights }) => FieldPermissionConfigSerializer.serialize({ rights }),
4502
+ getStorage: () => container.fieldPermissionStorage.get()
4503
+ });
4412
4504
  }
4413
4505
 
4414
4506
  //#endregion
@@ -4539,6 +4631,14 @@ var field_acl_default = define({
4539
4631
  run: () => {}
4540
4632
  });
4541
4633
 
4634
+ //#endregion
4635
+ //#region src/core/adapters/kintone/parseKintoneIntegerField.ts
4636
+ function parseKintoneIntegerField(raw, fieldName) {
4637
+ const n = Number(raw);
4638
+ if (!Number.isFinite(n) || !Number.isInteger(n)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-integer ${fieldName} from kintone API: ${raw}`);
4639
+ return n;
4640
+ }
4641
+
4542
4642
  //#endregion
4543
4643
  //#region src/core/adapters/kintone/generalSettingsConfigurator.ts
4544
4644
  const VALID_THEMES$1 = new Set([
@@ -4583,8 +4683,8 @@ function fromKintoneTitleField(raw) {
4583
4683
  function fromKintoneNumberPrecision(raw) {
4584
4684
  if (!VALID_ROUNDING_MODES$1.has(raw.roundingMode)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected roundingMode from kintone API: ${raw.roundingMode}`);
4585
4685
  return {
4586
- digits: Number(raw.digits),
4587
- decimalPlaces: Number(raw.decimalPlaces),
4686
+ digits: parseKintoneIntegerField(raw.digits, "digits"),
4687
+ decimalPlaces: parseKintoneIntegerField(raw.decimalPlaces, "decimalPlaces"),
4588
4688
  roundingMode: raw.roundingMode
4589
4689
  };
4590
4690
  }
@@ -4602,7 +4702,7 @@ function fromKintoneSettings(raw) {
4602
4702
  ...raw.enableDuplicateRecord !== void 0 ? { enableDuplicateRecord: raw.enableDuplicateRecord } : {},
4603
4703
  ...raw.enableInlineRecordEditing !== void 0 ? { enableInlineRecordEditing: raw.enableInlineRecordEditing } : {},
4604
4704
  ...raw.numberPrecision !== void 0 ? { numberPrecision: fromKintoneNumberPrecision(raw.numberPrecision) } : {},
4605
- ...raw.firstMonthOfFiscalYear !== void 0 ? { firstMonthOfFiscalYear: Number(raw.firstMonthOfFiscalYear) } : {}
4705
+ ...raw.firstMonthOfFiscalYear !== void 0 ? { firstMonthOfFiscalYear: parseKintoneIntegerField(raw.firstMonthOfFiscalYear, "firstMonthOfFiscalYear") } : {}
4606
4706
  };
4607
4707
  }
4608
4708
  function toKintoneIcon(icon) {
@@ -4655,9 +4755,7 @@ var KintoneGeneralSettingsConfigurator = class {
4655
4755
  revision: raw.revision ?? "-1"
4656
4756
  };
4657
4757
  } catch (error) {
4658
- if (isBusinessRuleError(error)) throw error;
4659
- if (error instanceof SystemError) throw error;
4660
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get general settings", error);
4758
+ throw wrapKintoneError(error, "Failed to get general settings");
4661
4759
  }
4662
4760
  }
4663
4761
  async updateGeneralSettings(params) {
@@ -4669,9 +4767,7 @@ var KintoneGeneralSettingsConfigurator = class {
4669
4767
  if (params.revision !== void 0) requestParams.revision = params.revision;
4670
4768
  return { revision: (await this.client.app.updateAppSettings(requestParams)).revision };
4671
4769
  } catch (error) {
4672
- if (isBusinessRuleError(error)) throw error;
4673
- if (error instanceof SystemError) throw error;
4674
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update general settings", error);
4770
+ throw wrapKintoneError(error, "Failed to update general settings");
4675
4771
  }
4676
4772
  }
4677
4773
  };
@@ -4685,11 +4781,7 @@ function createLocalFileGeneralSettingsStorage(filePath) {
4685
4781
  //#endregion
4686
4782
  //#region src/core/application/container/generalSettingsCli.ts
4687
4783
  function createGeneralSettingsCliContainer(config) {
4688
- const client = new KintoneRestAPIClient({
4689
- baseUrl: config.baseUrl,
4690
- auth: buildKintoneAuth(config.auth),
4691
- guestSpaceId: config.guestSpaceId
4692
- });
4784
+ const client = config.client ?? createKintoneClient(config);
4693
4785
  return {
4694
4786
  generalSettingsConfigurator: new KintoneGeneralSettingsConfigurator(client, config.appId),
4695
4787
  generalSettingsStorage: createLocalFileGeneralSettingsStorage(config.settingsFilePath),
@@ -4750,8 +4842,7 @@ function fromKintonePerRecordNotification(raw) {
4750
4842
  };
4751
4843
  }
4752
4844
  function fromKintoneReminderNotification(raw) {
4753
- const daysLater = Number(raw.timing.daysLater);
4754
- if (!Number.isFinite(daysLater)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric daysLater from kintone API: ${raw.timing.daysLater}`);
4845
+ const daysLater = parseKintoneIntegerField(raw.timing.daysLater, "daysLater");
4755
4846
  const result = {
4756
4847
  code: raw.timing.code,
4757
4848
  daysLater,
@@ -4760,8 +4851,7 @@ function fromKintoneReminderNotification(raw) {
4760
4851
  targets: raw.targets.map(fromKintoneTarget)
4761
4852
  };
4762
4853
  if ("hoursLater" in raw.timing) {
4763
- const hoursLater = Number(raw.timing.hoursLater);
4764
- if (!Number.isFinite(hoursLater)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric hoursLater from kintone API: ${raw.timing.hoursLater}`);
4854
+ const hoursLater = parseKintoneIntegerField(raw.timing.hoursLater, "hoursLater");
4765
4855
  return {
4766
4856
  ...result,
4767
4857
  hoursLater
@@ -4836,9 +4926,7 @@ var KintoneNotificationConfigurator = class {
4836
4926
  revision: response.revision
4837
4927
  };
4838
4928
  } catch (error) {
4839
- if (isBusinessRuleError(error)) throw error;
4840
- if (error instanceof SystemError) throw error;
4841
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get general notifications", error);
4929
+ throw wrapKintoneError(error, "Failed to get general notifications");
4842
4930
  }
4843
4931
  }
4844
4932
  async updateGeneralNotifications(params) {
@@ -4851,9 +4939,7 @@ var KintoneNotificationConfigurator = class {
4851
4939
  if (params.revision !== void 0) requestParams.revision = params.revision;
4852
4940
  return { revision: (await this.client.app.updateGeneralNotifications(requestParams)).revision };
4853
4941
  } catch (error) {
4854
- if (isBusinessRuleError(error)) throw error;
4855
- if (error instanceof SystemError) throw error;
4856
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update general notifications", error);
4942
+ throw wrapKintoneError(error, "Failed to update general notifications");
4857
4943
  }
4858
4944
  }
4859
4945
  async getPerRecordNotifications() {
@@ -4867,9 +4953,7 @@ var KintoneNotificationConfigurator = class {
4867
4953
  revision: response.revision
4868
4954
  };
4869
4955
  } catch (error) {
4870
- if (isBusinessRuleError(error)) throw error;
4871
- if (error instanceof SystemError) throw error;
4872
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get per-record notifications", error);
4956
+ throw wrapKintoneError(error, "Failed to get per-record notifications");
4873
4957
  }
4874
4958
  }
4875
4959
  async updatePerRecordNotifications(params) {
@@ -4881,9 +4965,7 @@ var KintoneNotificationConfigurator = class {
4881
4965
  if (params.revision !== void 0) requestParams.revision = params.revision;
4882
4966
  return { revision: (await this.client.app.updatePerRecordNotifications(requestParams)).revision };
4883
4967
  } catch (error) {
4884
- if (isBusinessRuleError(error)) throw error;
4885
- if (error instanceof SystemError) throw error;
4886
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update per-record notifications", error);
4968
+ throw wrapKintoneError(error, "Failed to update per-record notifications");
4887
4969
  }
4888
4970
  }
4889
4971
  async getReminderNotifications() {
@@ -4899,9 +4981,7 @@ var KintoneNotificationConfigurator = class {
4899
4981
  revision: response.revision
4900
4982
  };
4901
4983
  } catch (error) {
4902
- if (isBusinessRuleError(error)) throw error;
4903
- if (error instanceof SystemError) throw error;
4904
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get reminder notifications", error);
4984
+ throw wrapKintoneError(error, "Failed to get reminder notifications");
4905
4985
  }
4906
4986
  }
4907
4987
  async updateReminderNotifications(params) {
@@ -4914,9 +4994,7 @@ var KintoneNotificationConfigurator = class {
4914
4994
  if (params.revision !== void 0) requestParams.revision = params.revision;
4915
4995
  return { revision: (await this.client.app.updateReminderNotifications(requestParams)).revision };
4916
4996
  } catch (error) {
4917
- if (isBusinessRuleError(error)) throw error;
4918
- if (error instanceof SystemError) throw error;
4919
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update reminder notifications", error);
4997
+ throw wrapKintoneError(error, "Failed to update reminder notifications");
4920
4998
  }
4921
4999
  }
4922
5000
  };
@@ -4930,11 +5008,7 @@ function createLocalFileNotificationStorage(filePath) {
4930
5008
  //#endregion
4931
5009
  //#region src/core/application/container/notificationCli.ts
4932
5010
  function createNotificationCliContainer(config) {
4933
- const client = new KintoneRestAPIClient({
4934
- baseUrl: config.baseUrl,
4935
- auth: buildKintoneAuth(config.auth),
4936
- guestSpaceId: config.guestSpaceId
4937
- });
5011
+ const client = config.client ?? createKintoneClient(config);
4938
5012
  return {
4939
5013
  notificationConfigurator: new KintoneNotificationConfigurator(client, config.appId),
4940
5014
  notificationStorage: createLocalFileNotificationStorage(config.notificationFilePath),
@@ -4964,22 +5038,18 @@ var KintonePluginConfigurator = class {
4964
5038
  revision: response.revision
4965
5039
  };
4966
5040
  } catch (error) {
4967
- if (isBusinessRuleError(error)) throw error;
4968
- if (error instanceof SystemError) throw error;
4969
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get plugins", error);
5041
+ throw wrapKintoneError(error, "Failed to get plugins");
4970
5042
  }
4971
5043
  }
4972
5044
  async addPlugins(params) {
4973
5045
  try {
4974
5046
  return { revision: (await this.client.app.addPlugins({
4975
5047
  app: this.appId,
4976
- ids: params.ids,
5048
+ ids: [...params.ids],
4977
5049
  revision: params.revision
4978
5050
  })).revision };
4979
5051
  } catch (error) {
4980
- if (isBusinessRuleError(error)) throw error;
4981
- if (error instanceof SystemError) throw error;
4982
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add plugins", error);
5052
+ throw wrapKintoneError(error, "Failed to add plugins");
4983
5053
  }
4984
5054
  }
4985
5055
  };
@@ -4993,11 +5063,7 @@ function createLocalFilePluginStorage(filePath) {
4993
5063
  //#endregion
4994
5064
  //#region src/core/application/container/pluginCli.ts
4995
5065
  function createPluginCliContainer(config) {
4996
- const client = new KintoneRestAPIClient({
4997
- baseUrl: config.baseUrl,
4998
- auth: buildKintoneAuth(config.auth),
4999
- guestSpaceId: config.guestSpaceId
5000
- });
5066
+ const client = config.client ?? createKintoneClient(config);
5001
5067
  return {
5002
5068
  pluginConfigurator: new KintonePluginConfigurator(client, config.appId),
5003
5069
  pluginStorage: createLocalFilePluginStorage(config.pluginFilePath),
@@ -5116,9 +5182,7 @@ var KintoneProcessManagementConfigurator = class {
5116
5182
  revision: response.revision
5117
5183
  };
5118
5184
  } catch (error) {
5119
- if (isBusinessRuleError(error)) throw error;
5120
- if (error instanceof SystemError) throw error;
5121
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get process management settings", error);
5185
+ throw wrapKintoneError(error, "Failed to get process management settings");
5122
5186
  }
5123
5187
  }
5124
5188
  async updateProcessManagement(params) {
@@ -5134,9 +5198,7 @@ var KintoneProcessManagementConfigurator = class {
5134
5198
  if (params.revision !== void 0) requestParams.revision = params.revision;
5135
5199
  return { revision: (await this.client.app.updateProcessManagement(requestParams)).revision };
5136
5200
  } catch (error) {
5137
- if (isBusinessRuleError(error)) throw error;
5138
- if (error instanceof SystemError) throw error;
5139
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update process management settings", error);
5201
+ throw wrapKintoneError(error, "Failed to update process management settings");
5140
5202
  }
5141
5203
  }
5142
5204
  };
@@ -5150,11 +5212,7 @@ function createLocalFileProcessManagementStorage(filePath) {
5150
5212
  //#endregion
5151
5213
  //#region src/core/application/container/processManagementCli.ts
5152
5214
  function createProcessManagementCliContainer(config) {
5153
- const client = new KintoneRestAPIClient({
5154
- baseUrl: config.baseUrl,
5155
- auth: buildKintoneAuth(config.auth),
5156
- guestSpaceId: config.guestSpaceId
5157
- });
5215
+ const client = config.client ?? createKintoneClient(config);
5158
5216
  return {
5159
5217
  processManagementConfigurator: new KintoneProcessManagementConfigurator(client, config.appId),
5160
5218
  processManagementStorage: createLocalFileProcessManagementStorage(config.processFilePath),
@@ -5223,9 +5281,7 @@ var KintoneRecordPermissionConfigurator = class {
5223
5281
  revision: response.revision
5224
5282
  };
5225
5283
  } catch (error) {
5226
- if (isBusinessRuleError(error)) throw error;
5227
- if (error instanceof SystemError) throw error;
5228
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get record ACL", error);
5284
+ throw wrapKintoneError(error, "Failed to get record ACL");
5229
5285
  }
5230
5286
  }
5231
5287
  async updateRecordPermissions(params) {
@@ -5237,9 +5293,7 @@ var KintoneRecordPermissionConfigurator = class {
5237
5293
  if (params.revision !== void 0) requestParams.revision = params.revision;
5238
5294
  return { revision: (await this.client.app.updateRecordAcl(requestParams)).revision };
5239
5295
  } catch (error) {
5240
- if (isBusinessRuleError(error)) throw error;
5241
- if (error instanceof SystemError) throw error;
5242
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update record ACL", error);
5296
+ throw wrapKintoneError(error, "Failed to update record ACL");
5243
5297
  }
5244
5298
  }
5245
5299
  };
@@ -5253,11 +5307,7 @@ function createLocalFileRecordPermissionStorage(filePath) {
5253
5307
  //#endregion
5254
5308
  //#region src/core/application/container/recordPermissionCli.ts
5255
5309
  function createRecordPermissionCliContainer(config) {
5256
- const client = new KintoneRestAPIClient({
5257
- baseUrl: config.baseUrl,
5258
- auth: buildKintoneAuth(config.auth),
5259
- guestSpaceId: config.guestSpaceId
5260
- });
5310
+ const client = config.client ?? createKintoneClient(config);
5261
5311
  return {
5262
5312
  recordPermissionConfigurator: new KintoneRecordPermissionConfigurator(client, config.appId),
5263
5313
  recordPermissionStorage: createLocalFileRecordPermissionStorage(config.recordAclFilePath),
@@ -5516,9 +5566,7 @@ var KintoneReportConfigurator = class {
5516
5566
  revision: response.revision
5517
5567
  };
5518
5568
  } catch (error) {
5519
- if (isBusinessRuleError(error)) throw error;
5520
- if (error instanceof SystemError) throw error;
5521
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get reports", error);
5569
+ throw wrapKintoneError(error, "Failed to get reports");
5522
5570
  }
5523
5571
  }
5524
5572
  async updateReports(params) {
@@ -5532,9 +5580,7 @@ var KintoneReportConfigurator = class {
5532
5580
  if (params.revision !== void 0) requestParams.revision = params.revision;
5533
5581
  return { revision: (await this.client.app.updateReports(requestParams)).revision };
5534
5582
  } catch (error) {
5535
- if (isBusinessRuleError(error)) throw error;
5536
- if (error instanceof SystemError) throw error;
5537
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update reports", error);
5583
+ throw wrapKintoneError(error, "Failed to update reports");
5538
5584
  }
5539
5585
  }
5540
5586
  };
@@ -5548,11 +5594,7 @@ function createLocalFileReportStorage(filePath) {
5548
5594
  //#endregion
5549
5595
  //#region src/core/application/container/reportCli.ts
5550
5596
  function createReportCliContainer(config) {
5551
- const client = new KintoneRestAPIClient({
5552
- baseUrl: config.baseUrl,
5553
- auth: buildKintoneAuth(config.auth),
5554
- guestSpaceId: config.guestSpaceId
5555
- });
5597
+ const client = config.client ?? createKintoneClient(config);
5556
5598
  return {
5557
5599
  reportConfigurator: new KintoneReportConfigurator(client, config.appId),
5558
5600
  reportStorage: createLocalFileReportStorage(config.reportFilePath),
@@ -5588,21 +5630,17 @@ function fromKintoneView(name, raw) {
5588
5630
  }
5589
5631
  return {
5590
5632
  type: raw.type,
5591
- index: (() => {
5592
- const idx = typeof raw.index === "string" ? Number(raw.index) : raw.index;
5593
- if (!Number.isFinite(idx)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric index from kintone API: ${raw.index}`);
5594
- return idx;
5595
- })(),
5633
+ index: parseKintoneIntegerField(raw.index, "index"),
5596
5634
  name,
5597
- ...raw.builtinType !== void 0 && { builtinType: raw.builtinType },
5598
- ...raw.fields !== void 0 && { fields: raw.fields },
5599
- ...raw.date !== void 0 && { date: raw.date },
5600
- ...raw.title !== void 0 && { title: raw.title },
5601
- ...raw.html !== void 0 && { html: raw.html },
5602
- ...raw.pager !== void 0 && { pager: raw.pager },
5603
- ...device !== void 0 && { device },
5604
- ...raw.filterCond !== void 0 && { filterCond: raw.filterCond },
5605
- ...raw.sort !== void 0 && { sort: raw.sort }
5635
+ ...raw.builtinType !== void 0 ? { builtinType: raw.builtinType } : {},
5636
+ ...raw.fields !== void 0 ? { fields: raw.fields } : {},
5637
+ ...raw.date !== void 0 ? { date: raw.date } : {},
5638
+ ...raw.title !== void 0 ? { title: raw.title } : {},
5639
+ ...raw.html !== void 0 ? { html: raw.html } : {},
5640
+ ...raw.pager !== void 0 ? { pager: raw.pager } : {},
5641
+ ...device !== void 0 ? { device } : {},
5642
+ ...raw.filterCond !== void 0 ? { filterCond: raw.filterCond } : {},
5643
+ ...raw.sort !== void 0 ? { sort: raw.sort } : {}
5606
5644
  };
5607
5645
  }
5608
5646
  function toKintoneView(config) {
@@ -5642,9 +5680,7 @@ var KintoneViewConfigurator = class {
5642
5680
  revision: response.revision
5643
5681
  };
5644
5682
  } catch (error) {
5645
- if (isBusinessRuleError(error)) throw error;
5646
- if (error instanceof SystemError) throw error;
5647
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get views", error);
5683
+ throw wrapKintoneError(error, "Failed to get views");
5648
5684
  }
5649
5685
  }
5650
5686
  async updateViews(params) {
@@ -5658,9 +5694,7 @@ var KintoneViewConfigurator = class {
5658
5694
  if (params.revision !== void 0) requestParams.revision = params.revision;
5659
5695
  return { revision: (await this.client.app.updateViews(requestParams)).revision };
5660
5696
  } catch (error) {
5661
- if (isBusinessRuleError(error)) throw error;
5662
- if (error instanceof SystemError) throw error;
5663
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update views", error);
5697
+ throw wrapKintoneError(error, "Failed to update views");
5664
5698
  }
5665
5699
  }
5666
5700
  };
@@ -5674,11 +5708,7 @@ function createLocalFileViewStorage(filePath) {
5674
5708
  //#endregion
5675
5709
  //#region src/core/application/container/viewCli.ts
5676
5710
  function createViewCliContainer(config) {
5677
- const client = new KintoneRestAPIClient({
5678
- baseUrl: config.baseUrl,
5679
- auth: buildKintoneAuth(config.auth),
5680
- guestSpaceId: config.guestSpaceId
5681
- });
5711
+ const client = config.client ?? createKintoneClient(config);
5682
5712
  return {
5683
5713
  viewConfigurator: new KintoneViewConfigurator(client, config.appId),
5684
5714
  viewStorage: createLocalFileViewStorage(config.viewFilePath),
@@ -5689,11 +5719,13 @@ function createViewCliContainer(config) {
5689
5719
  //#endregion
5690
5720
  //#region src/core/application/container/captureAllCli.ts
5691
5721
  function createCliCaptureContainers(input) {
5722
+ const client = createKintoneClient(input);
5692
5723
  const base = {
5693
5724
  baseUrl: input.baseUrl,
5694
5725
  auth: input.auth,
5695
5726
  appId: input.appId,
5696
- guestSpaceId: input.guestSpaceId
5727
+ guestSpaceId: input.guestSpaceId,
5728
+ client
5697
5729
  };
5698
5730
  const paths = buildAppFilePaths(input.appName, input.baseDir);
5699
5731
  return {
@@ -5778,7 +5810,7 @@ var KintoneSpaceReader = class {
5778
5810
  this.client = client;
5779
5811
  }
5780
5812
  async getSpaceApps(spaceId) {
5781
- if (!spaceId) throw new SystemError(SystemErrorCode.ExternalApiError, "spaceId must not be empty");
5813
+ if (!spaceId) throw new ValidationError(ValidationErrorCode.InvalidInput, "spaceId must not be empty");
5782
5814
  try {
5783
5815
  const rawApps = (await this.client.space.getSpace({ id: spaceId })).attachedApps;
5784
5816
  if (rawApps === void 0) throw new SystemError(SystemErrorCode.ExternalApiError, `Space API response for space ID ${spaceId} does not contain attachedApps. The kintone API response format may have changed.`);
@@ -5797,9 +5829,7 @@ var KintoneSpaceReader = class {
5797
5829
  }
5798
5830
  return result;
5799
5831
  } catch (error) {
5800
- if (isBusinessRuleError(error)) throw error;
5801
- if (error instanceof SystemError) throw error;
5802
- throw new SystemError(SystemErrorCode.ExternalApiError, `Failed to get space info for space ID: ${spaceId}`, error);
5832
+ throw wrapKintoneError(error, `Failed to get space info for space ID: ${spaceId}`);
5803
5833
  }
5804
5834
  }
5805
5835
  };
@@ -5814,11 +5844,7 @@ function createLocalFileProjectConfigStorage(filePath) {
5814
5844
  //#region src/core/application/container/initCli.ts
5815
5845
  function createInitCliContainer(config) {
5816
5846
  return {
5817
- spaceReader: new KintoneSpaceReader(new KintoneRestAPIClient({
5818
- baseUrl: config.baseUrl,
5819
- auth: buildKintoneAuth(config.auth),
5820
- guestSpaceId: config.guestSpaceId
5821
- })),
5847
+ spaceReader: new KintoneSpaceReader(config.client ?? createKintoneClient(config)),
5822
5848
  projectConfigStorage: createLocalFileProjectConfigStorage(config.configFilePath)
5823
5849
  };
5824
5850
  }
@@ -6038,11 +6064,11 @@ const GeneralSettingsConfigSerializer = { serialize: (config) => {
6038
6064
  //#endregion
6039
6065
  //#region src/core/application/generalSettings/captureGeneralSettings.ts
6040
6066
  async function captureGeneralSettings({ container }) {
6041
- const { config } = await container.generalSettingsConfigurator.getGeneralSettings();
6042
- return {
6043
- configText: GeneralSettingsConfigSerializer.serialize(config),
6044
- hasExistingConfig: (await container.generalSettingsStorage.get()).exists
6045
- };
6067
+ return captureFromConfig({
6068
+ fetchRemote: () => container.generalSettingsConfigurator.getGeneralSettings(),
6069
+ serialize: ({ config }) => GeneralSettingsConfigSerializer.serialize(config),
6070
+ getStorage: () => container.generalSettingsStorage.get()
6071
+ });
6046
6072
  }
6047
6073
 
6048
6074
  //#endregion
@@ -6112,9 +6138,11 @@ const NotificationConfigSerializer = { serialize: (config) => {
6112
6138
  //#endregion
6113
6139
  //#region src/core/application/notification/captureNotification.ts
6114
6140
  async function captureNotification({ container }) {
6115
- const general = await container.notificationConfigurator.getGeneralNotifications();
6116
- const perRecord = await container.notificationConfigurator.getPerRecordNotifications();
6117
- const reminder = await container.notificationConfigurator.getReminderNotifications();
6141
+ const [general, perRecord, reminder] = await Promise.all([
6142
+ container.notificationConfigurator.getGeneralNotifications(),
6143
+ container.notificationConfigurator.getPerRecordNotifications(),
6144
+ container.notificationConfigurator.getReminderNotifications()
6145
+ ]);
6118
6146
  const config = {
6119
6147
  general: {
6120
6148
  notifyToCommenter: general.notifyToCommenter,
@@ -6151,11 +6179,11 @@ const PluginConfigSerializer = { serialize: (config) => {
6151
6179
  //#endregion
6152
6180
  //#region src/core/application/plugin/capturePlugin.ts
6153
6181
  async function capturePlugin({ container }) {
6154
- const { plugins } = await container.pluginConfigurator.getPlugins();
6155
- return {
6156
- configText: PluginConfigSerializer.serialize({ plugins }),
6157
- hasExistingConfig: (await container.pluginStorage.get()).exists
6158
- };
6182
+ return captureFromConfig({
6183
+ fetchRemote: () => container.pluginConfigurator.getPlugins(),
6184
+ serialize: ({ plugins }) => PluginConfigSerializer.serialize({ plugins }),
6185
+ getStorage: () => container.pluginStorage.get()
6186
+ });
6159
6187
  }
6160
6188
 
6161
6189
  //#endregion
@@ -6201,11 +6229,11 @@ const ProcessManagementConfigSerializer = { serialize: (config) => {
6201
6229
  //#endregion
6202
6230
  //#region src/core/application/processManagement/captureProcessManagement.ts
6203
6231
  async function captureProcessManagement({ container }) {
6204
- const { config } = await container.processManagementConfigurator.getProcessManagement();
6205
- return {
6206
- configText: ProcessManagementConfigSerializer.serialize(config),
6207
- hasExistingConfig: (await container.processManagementStorage.get()).exists
6208
- };
6232
+ return captureFromConfig({
6233
+ fetchRemote: () => container.processManagementConfigurator.getProcessManagement(),
6234
+ serialize: ({ config }) => ProcessManagementConfigSerializer.serialize(config),
6235
+ getStorage: () => container.processManagementStorage.get()
6236
+ });
6209
6237
  }
6210
6238
 
6211
6239
  //#endregion
@@ -6238,11 +6266,11 @@ const RecordPermissionConfigSerializer = { serialize: (config) => {
6238
6266
  //#endregion
6239
6267
  //#region src/core/application/recordPermission/captureRecordPermission.ts
6240
6268
  async function captureRecordPermission({ container }) {
6241
- const { rights } = await container.recordPermissionConfigurator.getRecordPermissions();
6242
- return {
6243
- configText: RecordPermissionConfigSerializer.serialize({ rights }),
6244
- hasExistingConfig: (await container.recordPermissionStorage.get()).exists
6245
- };
6269
+ return captureFromConfig({
6270
+ fetchRemote: () => container.recordPermissionConfigurator.getRecordPermissions(),
6271
+ serialize: ({ rights }) => RecordPermissionConfigSerializer.serialize({ rights }),
6272
+ getStorage: () => container.recordPermissionStorage.get()
6273
+ });
6246
6274
  }
6247
6275
 
6248
6276
  //#endregion
@@ -6309,11 +6337,11 @@ const ReportConfigSerializer = { serialize: (config) => {
6309
6337
  //#endregion
6310
6338
  //#region src/core/application/report/captureReport.ts
6311
6339
  async function captureReport({ container }) {
6312
- const { reports } = await container.reportConfigurator.getReports();
6313
- return {
6314
- configText: ReportConfigSerializer.serialize({ reports }),
6315
- hasExistingConfig: (await container.reportStorage.get()).exists
6316
- };
6340
+ return captureFromConfig({
6341
+ fetchRemote: () => container.reportConfigurator.getReports(),
6342
+ serialize: ({ reports }) => ReportConfigSerializer.serialize({ reports }),
6343
+ getStorage: () => container.reportStorage.get()
6344
+ });
6317
6345
  }
6318
6346
 
6319
6347
  //#endregion
@@ -6393,11 +6421,11 @@ const ViewConfigSerializer = { serialize: (config) => {
6393
6421
  //#endregion
6394
6422
  //#region src/core/application/view/captureView.ts
6395
6423
  async function captureView({ container }) {
6396
- const { views } = await container.viewConfigurator.getViews();
6397
- return {
6398
- configText: ViewConfigSerializer.serialize({ views }),
6399
- hasExistingConfig: (await container.viewStorage.get()).exists
6400
- };
6424
+ return captureFromConfig({
6425
+ fetchRemote: () => container.viewConfigurator.getViews(),
6426
+ serialize: ({ views }) => ViewConfigSerializer.serialize({ views }),
6427
+ getStorage: () => container.viewStorage.get()
6428
+ });
6401
6429
  }
6402
6430
 
6403
6431
  //#endregion
@@ -6596,12 +6624,11 @@ function generateProjectConfig(input) {
6596
6624
  files: buildAppFilePaths(name, input.baseDir)
6597
6625
  };
6598
6626
  }
6599
- const config = {
6627
+ return stringify({
6600
6628
  domain: input.domain,
6629
+ ...input.guestSpaceId !== void 0 ? { guestSpaceId: input.guestSpaceId } : {},
6601
6630
  apps
6602
- };
6603
- if (input.guestSpaceId !== void 0) config.guestSpaceId = input.guestSpaceId;
6604
- return stringify(config, { lineWidth: 0 });
6631
+ }, { lineWidth: 0 });
6605
6632
  }
6606
6633
 
6607
6634
  //#endregion
@@ -6861,6 +6888,15 @@ function parseNotificationConfigText(rawText) {
6861
6888
 
6862
6889
  //#endregion
6863
6890
  //#region src/core/application/notification/applyNotification.ts
6891
+ /**
6892
+ * Apply notification settings to kintone.
6893
+ *
6894
+ * Each notification section (general, perRecord, reminder) is updated
6895
+ * independently via separate API calls. If one section fails after others
6896
+ * have already been applied, the app will be in a partially-updated state.
6897
+ * This is a kintone API limitation — there is no transactional update
6898
+ * across notification types. Re-running the command is safe and idempotent.
6899
+ */
6864
6900
  async function applyNotification({ container }) {
6865
6901
  const result = await container.notificationStorage.get();
6866
6902
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Notification config file not found");
@@ -7926,13 +7962,17 @@ function parseRecordPermissionConfigText(rawText) {
7926
7962
  //#endregion
7927
7963
  //#region src/core/application/recordPermission/applyRecordPermission.ts
7928
7964
  async function applyRecordPermission({ container }) {
7929
- const result = await container.recordPermissionStorage.get();
7930
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Record permission config file not found");
7931
- const config = parseRecordPermissionConfigText(result.content);
7932
- const current = await container.recordPermissionConfigurator.getRecordPermissions();
7933
- await container.recordPermissionConfigurator.updateRecordPermissions({
7934
- rights: config.rights,
7935
- revision: current.revision
7965
+ await applyFromConfig({
7966
+ getStorage: () => container.recordPermissionStorage.get(),
7967
+ parseConfig: parseRecordPermissionConfigText,
7968
+ fetchRemote: () => container.recordPermissionConfigurator.getRecordPermissions(),
7969
+ update: async (config, current) => {
7970
+ await container.recordPermissionConfigurator.updateRecordPermissions({
7971
+ rights: config.rights,
7972
+ revision: current.revision
7973
+ });
7974
+ },
7975
+ notFoundMessage: "Record permission config file not found"
7936
7976
  });
7937
7977
  }
7938
7978
 
@@ -8308,13 +8348,17 @@ function parseReportConfigText(rawText) {
8308
8348
  //#endregion
8309
8349
  //#region src/core/application/report/applyReport.ts
8310
8350
  async function applyReport({ container }) {
8311
- const result = await container.reportStorage.get();
8312
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Report config file not found");
8313
- const config = parseReportConfigText(result.content);
8314
- const current = await container.reportConfigurator.getReports();
8315
- await container.reportConfigurator.updateReports({
8316
- reports: config.reports,
8317
- revision: current.revision
8351
+ await applyFromConfig({
8352
+ getStorage: () => container.reportStorage.get(),
8353
+ parseConfig: parseReportConfigText,
8354
+ fetchRemote: () => container.reportConfigurator.getReports(),
8355
+ update: async (config, current) => {
8356
+ await container.reportConfigurator.updateReports({
8357
+ reports: config.reports,
8358
+ revision: current.revision
8359
+ });
8360
+ },
8361
+ notFoundMessage: "Report config file not found"
8318
8362
  });
8319
8363
  }
8320
8364
 
@@ -9108,12 +9152,25 @@ function parseSchemaText(rawText) {
9108
9152
 
9109
9153
  //#endregion
9110
9154
  //#region src/core/application/formSchema/detectDiff.ts
9155
+ function fieldPropertiesToDto(field) {
9156
+ if (field.type === "SUBTABLE") {
9157
+ const innerFields = {};
9158
+ for (const [code, def] of field.properties.fields) innerFields[code] = {
9159
+ code: def.code,
9160
+ type: def.type,
9161
+ label: def.label,
9162
+ properties: def.properties
9163
+ };
9164
+ return { fields: innerFields };
9165
+ }
9166
+ return { ...field.properties };
9167
+ }
9111
9168
  function toFieldDto(field) {
9112
9169
  return {
9113
9170
  code: field.code,
9114
9171
  type: field.type,
9115
9172
  label: field.label,
9116
- properties: field.properties
9173
+ properties: fieldPropertiesToDto(field)
9117
9174
  };
9118
9175
  }
9119
9176
  async function detectDiff({ container }) {
@@ -9198,15 +9255,19 @@ var KintoneFormDumpReader = class {
9198
9255
  }
9199
9256
  async getRawFormData() {
9200
9257
  try {
9201
- const [fields, layout] = await Promise.all([this.client.app.getFormFields({ app: this.appId }), this.client.app.getFormLayout({ app: this.appId })]);
9258
+ const [fields, layout] = await Promise.all([this.client.app.getFormFields({
9259
+ app: this.appId,
9260
+ preview: true
9261
+ }), this.client.app.getFormLayout({
9262
+ app: this.appId,
9263
+ preview: true
9264
+ })]);
9202
9265
  return {
9203
9266
  fields,
9204
9267
  layout
9205
9268
  };
9206
9269
  } catch (error) {
9207
- if (isBusinessRuleError(error)) throw error;
9208
- if (error instanceof SystemError) throw error;
9209
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to fetch raw form data for dump", error);
9270
+ throw wrapKintoneError(error, "Failed to fetch raw form data for dump");
9210
9271
  }
9211
9272
  }
9212
9273
  };
@@ -9214,11 +9275,16 @@ var KintoneFormDumpReader = class {
9214
9275
  //#endregion
9215
9276
  //#region src/core/adapters/local/dumpStorage.ts
9216
9277
  var LocalFileDumpStorage = class {
9217
- constructor(filePrefix) {
9278
+ constructor(filePrefix, baseDir = process.cwd()) {
9218
9279
  this.filePrefix = filePrefix;
9280
+ this.baseDir = baseDir;
9281
+ const fieldsPath = resolve(this.baseDir, `${this.filePrefix}fields.json`);
9282
+ if (!isSafePath(fieldsPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${fieldsPath}" escapes base directory "${this.baseDir}"`);
9283
+ const layoutPath = resolve(this.baseDir, `${this.filePrefix}layout.json`);
9284
+ if (!isSafePath(layoutPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${layoutPath}" escapes base directory "${this.baseDir}"`);
9219
9285
  }
9220
9286
  async saveFields(content) {
9221
- const filePath = `${this.filePrefix}fields.json`;
9287
+ const filePath = resolve(this.baseDir, `${this.filePrefix}fields.json`);
9222
9288
  try {
9223
9289
  await mkdir(dirname(filePath), { recursive: true });
9224
9290
  await writeFile(filePath, content, "utf-8");
@@ -9227,7 +9293,7 @@ var LocalFileDumpStorage = class {
9227
9293
  }
9228
9294
  }
9229
9295
  async saveLayout(content) {
9230
- const filePath = `${this.filePrefix}layout.json`;
9296
+ const filePath = resolve(this.baseDir, `${this.filePrefix}layout.json`);
9231
9297
  try {
9232
9298
  await mkdir(dirname(filePath), { recursive: true });
9233
9299
  await writeFile(filePath, content, "utf-8");
@@ -9241,12 +9307,8 @@ var LocalFileDumpStorage = class {
9241
9307
  //#region src/core/application/container/dumpCli.ts
9242
9308
  function createDumpCliContainer(config) {
9243
9309
  return {
9244
- formDumpReader: new KintoneFormDumpReader(new KintoneRestAPIClient({
9245
- baseUrl: config.baseUrl,
9246
- auth: buildKintoneAuth(config.auth),
9247
- guestSpaceId: config.guestSpaceId
9248
- }), config.appId),
9249
- dumpStorage: new LocalFileDumpStorage(config.filePrefix)
9310
+ formDumpReader: new KintoneFormDumpReader(config.client ?? createKintoneClient(config), config.appId),
9311
+ dumpStorage: new LocalFileDumpStorage(config.filePrefix, process.cwd())
9250
9312
  };
9251
9313
  }
9252
9314
 
@@ -9317,6 +9379,22 @@ var dump_default = define({
9317
9379
  }
9318
9380
  });
9319
9381
 
9382
+ //#endregion
9383
+ //#region src/core/domain/formSchema/services/subtableFieldSplitter.ts
9384
+ function splitSubtableInnerFields(desired, current) {
9385
+ const newInnerFields = /* @__PURE__ */ new Map();
9386
+ const existingInnerFields = /* @__PURE__ */ new Map();
9387
+ const deletedInnerFieldCodes = [];
9388
+ for (const [code, def] of desired.properties.fields) if (current.properties.fields.has(code)) existingInnerFields.set(code, def);
9389
+ else newInnerFields.set(code, def);
9390
+ for (const code of current.properties.fields.keys()) if (!desired.properties.fields.has(code)) deletedInnerFieldCodes.push(code);
9391
+ return {
9392
+ newInnerFields,
9393
+ existingInnerFields,
9394
+ deletedInnerFieldCodes
9395
+ };
9396
+ }
9397
+
9320
9398
  //#endregion
9321
9399
  //#region src/core/domain/formSchema/services/schemaValidator.ts
9322
9400
  const SELECTION_TYPES = new Set([
@@ -9420,6 +9498,11 @@ function validateReferenceTableRelatedApp(field) {
9420
9498
  if (app.trim().length === 0) return [issue("error", field.code, field.type, "EMPTY_RELATED_APP", `Field "${field.code}" referenceTable.relatedApp.app must be non-empty`)];
9421
9499
  return [];
9422
9500
  }
9501
+ function validateSubtableHasInnerFields(field) {
9502
+ if (field.type !== "SUBTABLE") return [];
9503
+ if (field.properties.fields.size === 0) return [issue("error", field.code, field.type, "EMPTY_SUBTABLE", `Subtable "${field.code}" must have at least one inner field`)];
9504
+ return [];
9505
+ }
9423
9506
  const FIELD_VALIDATORS = [
9424
9507
  validateLabelNonEmpty,
9425
9508
  validateSelectionOptions,
@@ -9429,7 +9512,8 @@ const FIELD_VALIDATORS = [
9429
9512
  validateFileThumbnailSize,
9430
9513
  validateReferenceTableSize,
9431
9514
  validateLookupStructure,
9432
- validateReferenceTableRelatedApp
9515
+ validateReferenceTableRelatedApp,
9516
+ validateSubtableHasInnerFields
9433
9517
  ];
9434
9518
  function validateField(field) {
9435
9519
  return FIELD_VALIDATORS.flatMap((validator) => validator(field));
@@ -9478,6 +9562,7 @@ async function executeMigration({ container }) {
9478
9562
  const deleted = diff.entries.filter((e) => e.type === "deleted");
9479
9563
  const fieldsToAdd = [];
9480
9564
  const fieldsToUpdate = [];
9565
+ const innerFieldsToDelete = [];
9481
9566
  for (const entry of added) {
9482
9567
  if (entry.after === void 0) continue;
9483
9568
  if (subtableInnerCodes.has(entry.fieldCode)) continue;
@@ -9489,25 +9574,21 @@ async function executeMigration({ container }) {
9489
9574
  const after = entry.after;
9490
9575
  const before = entry.before;
9491
9576
  if (after.type === "SUBTABLE" && before !== void 0 && before.type === "SUBTABLE") {
9492
- const newInnerFields = /* @__PURE__ */ new Map();
9493
- const existingInnerFields = /* @__PURE__ */ new Map();
9494
- for (const [code, def] of after.properties.fields) if (before.properties.fields.has(code)) existingInnerFields.set(code, def);
9495
- else newInnerFields.set(code, def);
9496
- if (newInnerFields.size > 0) fieldsToAdd.push({
9497
- ...after,
9498
- properties: { fields: newInnerFields }
9499
- });
9577
+ const { newInnerFields, existingInnerFields, deletedInnerFieldCodes } = splitSubtableInnerFields(after, before);
9578
+ if (newInnerFields.size > 0) throw new ValidationError(ValidationErrorCode.InvalidInput, `kintone REST API does not support adding fields to an existing subtable. Use the schema override command instead. Subtable: ${after.code}`);
9500
9579
  if (existingInnerFields.size > 0) fieldsToUpdate.push({
9501
9580
  ...after,
9502
9581
  properties: { fields: existingInnerFields }
9503
9582
  });
9504
- } else fieldsToUpdate.push(after);
9583
+ for (const code of deletedInnerFieldCodes) innerFieldsToDelete.push(code);
9584
+ } else if (before !== void 0 && before.type !== after.type) throw new ValidationError(ValidationErrorCode.InvalidInput, `Field type change detected for "${after.code}" (${before.type} → ${after.type}). Use the schema override command instead.`);
9585
+ else fieldsToUpdate.push(after);
9505
9586
  }
9506
9587
  if (fieldsToAdd.length > 0) await container.formConfigurator.addFields(fieldsToAdd);
9507
9588
  if (fieldsToUpdate.length > 0) await container.formConfigurator.updateFields(fieldsToUpdate);
9508
- if (deleted.length > 0) {
9589
+ if (deleted.length > 0 || innerFieldsToDelete.length > 0) {
9509
9590
  const currentSubtableInnerCodes = collectSubtableInnerFieldCodes(currentFields);
9510
- const fieldCodes = deleted.filter((e) => !currentSubtableInnerCodes.has(e.fieldCode)).map((e) => e.fieldCode);
9591
+ const fieldCodes = [...deleted.filter((e) => !currentSubtableInnerCodes.has(e.fieldCode)).map((e) => e.fieldCode), ...innerFieldsToDelete];
9511
9592
  if (fieldCodes.length > 0) await container.formConfigurator.deleteFields(fieldCodes);
9512
9593
  }
9513
9594
  if (hasLayoutChanges) await container.formConfigurator.updateLayout(schema.layout);
@@ -9622,25 +9703,35 @@ async function forceOverrideForm({ container }) {
9622
9703
  const toAdd = [];
9623
9704
  const toUpdate = [];
9624
9705
  const toDelete = [];
9706
+ const innerFieldsToDelete = [];
9625
9707
  for (const [fieldCode, schemaDef] of schema.fields) {
9626
9708
  if (subtableInnerCodes.has(fieldCode)) continue;
9627
9709
  if (currentFields.has(fieldCode)) if (schemaDef.type === "SUBTABLE") {
9628
9710
  const currentDef = currentFields.get(fieldCode);
9629
9711
  if (currentDef !== void 0 && currentDef.type === "SUBTABLE") {
9630
- const newInnerFields = /* @__PURE__ */ new Map();
9631
- const existingInnerFields = /* @__PURE__ */ new Map();
9632
- for (const [code, def] of schemaDef.properties.fields) if (currentDef.properties.fields.has(code)) existingInnerFields.set(code, def);
9633
- else newInnerFields.set(code, def);
9634
- if (newInnerFields.size > 0) toAdd.push({
9635
- ...schemaDef,
9636
- properties: { fields: newInnerFields }
9637
- });
9638
- if (existingInnerFields.size > 0) toUpdate.push({
9639
- ...schemaDef,
9640
- properties: { fields: existingInnerFields }
9641
- });
9712
+ const { newInnerFields, existingInnerFields, deletedInnerFieldCodes } = splitSubtableInnerFields(schemaDef, currentDef);
9713
+ const allInnerFieldsRemoved = existingInnerFields.size === 0 && deletedInnerFieldCodes.length > 0;
9714
+ if (newInnerFields.size > 0 || allInnerFieldsRemoved) {
9715
+ toDelete.push(fieldCode);
9716
+ toAdd.push(schemaDef);
9717
+ } else {
9718
+ toUpdate.push({
9719
+ ...schemaDef,
9720
+ properties: { fields: existingInnerFields }
9721
+ });
9722
+ for (const code of deletedInnerFieldCodes) innerFieldsToDelete.push(code);
9723
+ }
9724
+ } else {
9725
+ toDelete.push(fieldCode);
9726
+ toAdd.push(schemaDef);
9727
+ }
9728
+ } else {
9729
+ const currentDef = currentFields.get(fieldCode);
9730
+ if (currentDef !== void 0 && currentDef.type !== schemaDef.type) {
9731
+ toDelete.push(fieldCode);
9732
+ toAdd.push(schemaDef);
9642
9733
  } else toUpdate.push(schemaDef);
9643
- } else toUpdate.push(schemaDef);
9734
+ }
9644
9735
  else toAdd.push(schemaDef);
9645
9736
  }
9646
9737
  const currentSubtableInnerCodes = collectSubtableInnerFieldCodes(currentFields);
@@ -9648,9 +9739,9 @@ async function forceOverrideForm({ container }) {
9648
9739
  if (currentSubtableInnerCodes.has(fieldCode)) continue;
9649
9740
  if (!schema.fields.has(fieldCode)) toDelete.push(fieldCode);
9650
9741
  }
9742
+ if (toDelete.length > 0 || innerFieldsToDelete.length > 0) await container.formConfigurator.deleteFields([...toDelete, ...innerFieldsToDelete]);
9651
9743
  if (toAdd.length > 0) await container.formConfigurator.addFields(toAdd);
9652
9744
  if (toUpdate.length > 0) await container.formConfigurator.updateFields(toUpdate);
9653
- if (toDelete.length > 0) await container.formConfigurator.deleteFields(toDelete);
9654
9745
  await container.formConfigurator.updateLayout(schema.layout);
9655
9746
  }
9656
9747
 
@@ -9797,9 +9888,9 @@ async function validateSchema({ container }) {
9797
9888
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Schema file not found");
9798
9889
  let schema;
9799
9890
  try {
9800
- schema = SchemaParser.parse(result.content);
9891
+ schema = parseSchemaText(result.content);
9801
9892
  } catch (error) {
9802
- if (isBusinessRuleError(error)) return {
9893
+ if (isValidationError(error)) return {
9803
9894
  parseError: error.message,
9804
9895
  fieldCount: 0
9805
9896
  };
@@ -9908,6 +9999,50 @@ var schema_default = define({
9908
9999
  run: () => {}
9909
10000
  });
9910
10001
 
10002
+ //#endregion
10003
+ //#region src/core/domain/seedData/services/upsertPlanner.ts
10004
+ function recordsEqual(seed, existing, keyField) {
10005
+ const seedKeys = Object.keys(seed).filter((k) => k !== keyField);
10006
+ for (const key of seedKeys) {
10007
+ const seedValue = seed[key];
10008
+ const existingValue = existing[key];
10009
+ if (seedValue === void 0 && existingValue === void 0) continue;
10010
+ if (seedValue === void 0 || existingValue === void 0) return false;
10011
+ if (!deepEqual(seedValue, existingValue)) return false;
10012
+ }
10013
+ return true;
10014
+ }
10015
+ const UpsertPlanner = { plan: (key, seedRecords, existingRecords) => {
10016
+ const keyField = key;
10017
+ const existingMap = /* @__PURE__ */ new Map();
10018
+ for (const { id, record } of existingRecords) {
10019
+ const keyValue = record[keyField];
10020
+ if (typeof keyValue === "string") existingMap.set(keyValue, {
10021
+ id,
10022
+ record
10023
+ });
10024
+ }
10025
+ const toAdd = [];
10026
+ const toUpdate = [];
10027
+ let unchanged = 0;
10028
+ for (const seedRecord of seedRecords) {
10029
+ const keyValue = seedRecord[keyField];
10030
+ if (typeof keyValue !== "string") throw new BusinessRuleError(SeedDataErrorCode.SdInvalidKeyFieldValue, `Key field "${keyField}" value must be a string, got ${typeof keyValue}`);
10031
+ const existing = existingMap.get(keyValue);
10032
+ if (existing === void 0) toAdd.push(seedRecord);
10033
+ else if (recordsEqual(seedRecord, existing.record, keyField)) unchanged++;
10034
+ else toUpdate.push({
10035
+ id: existing.id,
10036
+ record: seedRecord
10037
+ });
10038
+ }
10039
+ return {
10040
+ toAdd,
10041
+ toUpdate,
10042
+ unchanged
10043
+ };
10044
+ } };
10045
+
9911
10046
  //#endregion
9912
10047
  //#region src/core/domain/seedData/services/seedParser.ts
9913
10048
  function normalizeValue(value) {
@@ -9969,55 +10104,17 @@ const SeedParser = { parse: (rawText) => {
9969
10104
  } };
9970
10105
 
9971
10106
  //#endregion
9972
- //#region src/core/domain/seedData/services/upsertPlanner.ts
9973
- function recordsEqual(seed, existing, keyField) {
9974
- const seedKeys = Object.keys(seed).filter((k) => k !== keyField);
9975
- for (const key of seedKeys) {
9976
- const seedValue = seed[key];
9977
- const existingValue = existing[key];
9978
- if (seedValue === void 0 && existingValue === void 0) continue;
9979
- if (seedValue === void 0 || existingValue === void 0) return false;
9980
- if (!deepEqual(seedValue, existingValue)) return false;
9981
- }
9982
- return true;
10107
+ //#region src/core/application/seedData/parseConfig.ts
10108
+ function parseSeedText(rawText) {
10109
+ return wrapBusinessRuleError(() => SeedParser.parse(rawText));
9983
10110
  }
9984
- const UpsertPlanner = { plan: (key, seedRecords, existingRecords) => {
9985
- const keyField = key;
9986
- const existingMap = /* @__PURE__ */ new Map();
9987
- for (const { id, record } of existingRecords) {
9988
- const keyValue = record[keyField];
9989
- if (typeof keyValue === "string") existingMap.set(keyValue, {
9990
- id,
9991
- record
9992
- });
9993
- }
9994
- const toAdd = [];
9995
- const toUpdate = [];
9996
- let unchanged = 0;
9997
- for (const seedRecord of seedRecords) {
9998
- const keyValue = seedRecord[keyField];
9999
- if (typeof keyValue !== "string") throw new BusinessRuleError(SeedDataErrorCode.SdInvalidKeyFieldValue, `Key field "${keyField}" value must be a string, got ${typeof keyValue}`);
10000
- const existing = existingMap.get(keyValue);
10001
- if (existing === void 0) toAdd.push(seedRecord);
10002
- else if (recordsEqual(seedRecord, existing.record, keyField)) unchanged++;
10003
- else toUpdate.push({
10004
- id: existing.id,
10005
- record: seedRecord
10006
- });
10007
- }
10008
- return {
10009
- toAdd,
10010
- toUpdate,
10011
- unchanged
10012
- };
10013
- } };
10014
10111
 
10015
10112
  //#endregion
10016
10113
  //#region src/core/application/seedData/upsertSeed.ts
10017
10114
  async function upsertSeed({ container, input }) {
10018
10115
  const result = await container.seedStorage.get();
10019
10116
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Seed file not found");
10020
- const seedData = SeedParser.parse(result.content);
10117
+ const seedData = parseSeedText(result.content);
10021
10118
  if (input.clean) {
10022
10119
  const { deletedCount } = await container.recordManager.deleteAllRecords();
10023
10120
  if (seedData.records.length > 0) await container.recordManager.addRecords(seedData.records);
@@ -10341,13 +10438,17 @@ function parseGeneralSettingsConfigText(rawText) {
10341
10438
  //#endregion
10342
10439
  //#region src/core/application/generalSettings/applyGeneralSettings.ts
10343
10440
  async function applyGeneralSettings({ container }) {
10344
- const result = await container.generalSettingsStorage.get();
10345
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "General settings config file not found");
10346
- const config = parseGeneralSettingsConfigText(result.content);
10347
- const current = await container.generalSettingsConfigurator.getGeneralSettings();
10348
- await container.generalSettingsConfigurator.updateGeneralSettings({
10349
- config,
10350
- revision: current.revision
10441
+ await applyFromConfig({
10442
+ getStorage: () => container.generalSettingsStorage.get(),
10443
+ parseConfig: parseGeneralSettingsConfigText,
10444
+ fetchRemote: () => container.generalSettingsConfigurator.getGeneralSettings(),
10445
+ update: async (config, current) => {
10446
+ await container.generalSettingsConfigurator.updateGeneralSettings({
10447
+ config,
10448
+ revision: current.revision
10449
+ });
10450
+ },
10451
+ notFoundMessage: "General settings config file not found"
10351
10452
  });
10352
10453
  }
10353
10454