kintone-migrator 0.24.4 → 0.24.6

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
@@ -4,9 +4,10 @@ import "dotenv/config";
4
4
  import { cli, define } from "gunshi";
5
5
  import * as p from "@clack/prompts";
6
6
  import { parse, stringify } from "yaml";
7
+ import { KintoneRestAPIClient, KintoneRestAPIError } from "@kintone/rest-api-client";
7
8
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
8
9
  import { basename, dirname, extname, join, resolve } from "node:path";
9
- import { KintoneRestAPIClient, KintoneRestAPIError } from "@kintone/rest-api-client";
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) {
@@ -327,7 +333,8 @@ const SystemErrorCode = {
327
333
  NetworkError: "NETWORK_ERROR",
328
334
  StorageError: "STORAGE_ERROR",
329
335
  DocumentGenerationError: "DOCUMENT_GENERATION_ERROR",
330
- ExternalApiError: "EXTERNAL_API_ERROR"
336
+ ExternalApiError: "EXTERNAL_API_ERROR",
337
+ ExecutionError: "EXECUTION_ERROR"
331
338
  };
332
339
  var SystemError = class extends ApplicationError {
333
340
  name = "SystemError";
@@ -549,6 +556,60 @@ async function applyAction({ container }) {
549
556
  });
550
557
  }
551
558
 
559
+ //#endregion
560
+ //#region src/core/adapters/kintone/wrapKintoneError.ts
561
+ const KINTONE_REVISION_CONFLICT_CODE = "GAIA_CO02";
562
+ const KINTONE_MAINTENANCE_MODE_CODE = "GAIA_NO02";
563
+ function formatMessage(message, error) {
564
+ if (error instanceof KintoneRestAPIError && error.message) return `${message}: ${error.message}`;
565
+ if (error instanceof Error && error.message) return `${message}: ${error.message}`;
566
+ return message;
567
+ }
568
+ /**
569
+ * Converts an unknown error thrown during a kintone API call into the
570
+ * appropriate application-layer error.
571
+ *
572
+ * - {@link BusinessRuleError} and {@link ApplicationError} (including all
573
+ * subclasses such as {@link SystemError}, {@link ValidationError}, etc.)
574
+ * are re-thrown as-is so that errors raised inside a try block are never
575
+ * silently converted.
576
+ * - {@link KintoneRestAPIError} is mapped by HTTP status (401/403/404/409/400)
577
+ * and kintone-specific codes (`GAIA_CO02` revision conflict, `GAIA_NO02`
578
+ * maintenance mode).
579
+ * - Everything else becomes a {@link SystemError} with code `ExternalApiError`.
580
+ *
581
+ * **Note on 400 mapping**: All 400 errors (except `GAIA_CO02`) are mapped to
582
+ * {@link ValidationError}. This is intentionally simplified — some 400 codes
583
+ * (e.g. `GAIA_RE18` record limit, `GAIA_AP01` app unavailable) may have
584
+ * different semantics, but for this CLI tool a single `ValidationError`
585
+ * with the original kintone message in the error detail is sufficient.
586
+ *
587
+ * @throws {UnauthenticatedError} when kintone returns 401
588
+ * @throws {ForbiddenError} when kintone returns 403 (non-GAIA_NO02)
589
+ * @throws {SystemError} when kintone returns 403 with GAIA_NO02 (maintenance mode)
590
+ * @throws {NotFoundError} when kintone returns 404
591
+ * @throws {ConflictError} when kintone returns 409 or GAIA_CO02
592
+ * @throws {ValidationError} when kintone returns 400 (non-GAIA_CO02)
593
+ * @throws {SystemError} for all other kintone errors and unknown errors
594
+ */
595
+ function wrapKintoneError(error, message) {
596
+ if (isBusinessRuleError(error)) throw error;
597
+ if (error instanceof ApplicationError) throw error;
598
+ const detail = formatMessage(message, error);
599
+ if (error instanceof KintoneRestAPIError) {
600
+ if (error.status === 401) throw new UnauthenticatedError(UnauthenticatedErrorCode.InvalidCredentials, detail, error);
601
+ if (error.status === 403) {
602
+ if (error.code === KINTONE_MAINTENANCE_MODE_CODE) throw new SystemError(SystemErrorCode.ExternalApiError, `${detail} (app is in maintenance mode — GAIA_NO02)`, error);
603
+ throw new ForbiddenError(ForbiddenErrorCode.InsufficientPermissions, detail, error);
604
+ }
605
+ if (error.status === 404) throw new NotFoundError(NotFoundErrorCode.NotFound, detail, error);
606
+ if (error.code === KINTONE_REVISION_CONFLICT_CODE) throw new ConflictError(ConflictErrorCode.Conflict, `${detail} (revision conflict — GAIA_CO02). Please retry the operation.`, error);
607
+ if (error.status === 409) throw new ConflictError(ConflictErrorCode.Conflict, detail, error);
608
+ if (error.status === 400) throw new ValidationError(ValidationErrorCode.InvalidInput, detail, error);
609
+ }
610
+ throw new SystemError(SystemErrorCode.ExternalApiError, detail, error);
611
+ }
612
+
552
613
  //#endregion
553
614
  //#region src/core/adapters/kintone/actionConfigurator.ts
554
615
  function fromKintoneDestApp(raw) {
@@ -634,9 +695,7 @@ var KintoneActionConfigurator = class {
634
695
  revision: response.revision
635
696
  };
636
697
  } catch (error) {
637
- if (isBusinessRuleError(error)) throw error;
638
- if (error instanceof SystemError) throw error;
639
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get app actions", error);
698
+ throw wrapKintoneError(error, "Failed to get app actions");
640
699
  }
641
700
  }
642
701
  async updateActions(params) {
@@ -650,15 +709,22 @@ var KintoneActionConfigurator = class {
650
709
  if (params.revision !== void 0) requestParams.revision = params.revision;
651
710
  return { revision: (await this.client.app.updateAppActions(requestParams)).revision };
652
711
  } catch (error) {
653
- if (isBusinessRuleError(error)) throw error;
654
- if (error instanceof SystemError) throw error;
655
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update app actions", error);
712
+ throw wrapKintoneError(error, "Failed to update app actions");
656
713
  }
657
714
  }
658
715
  };
659
716
 
660
717
  //#endregion
661
718
  //#region src/core/adapters/kintone/appDeployer.ts
719
+ const VALID_DEPLOY_STATUSES = new Set([
720
+ "PROCESSING",
721
+ "SUCCESS",
722
+ "FAIL",
723
+ "CANCEL"
724
+ ]);
725
+ function isDeployStatus(value) {
726
+ return VALID_DEPLOY_STATUSES.has(value);
727
+ }
662
728
  const DEFAULT_POLL_INTERVAL_MS = 1e3;
663
729
  const DEFAULT_MAX_RETRIES = 180;
664
730
  var KintoneAppDeployer = class {
@@ -673,12 +739,10 @@ var KintoneAppDeployer = class {
673
739
  async deploy() {
674
740
  try {
675
741
  await this.client.app.deployApp({ apps: [{ app: this.appId }] });
676
- await this.waitForDeployment();
677
742
  } catch (error) {
678
- if (isBusinessRuleError(error)) throw error;
679
- if (error instanceof SystemError) throw error;
680
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to deploy app", error);
743
+ throw wrapKintoneError(error, "Failed to deploy app");
681
744
  }
745
+ await this.waitForDeployment();
682
746
  }
683
747
  async waitForDeployment() {
684
748
  let lastPollError;
@@ -692,20 +756,21 @@ var KintoneAppDeployer = class {
692
756
  consecutivePollFailures = 0;
693
757
  } catch (error) {
694
758
  if (isBusinessRuleError(error)) throw error;
695
- if (error instanceof SystemError) throw error;
759
+ if (error instanceof ApplicationError) throw error;
696
760
  lastPollError = error;
697
761
  consecutivePollFailures++;
698
762
  if (consecutivePollFailures >= maxConsecutivePollFailures) throw new SystemError(SystemErrorCode.ExternalApiError, `Deploy status polling failed ${maxConsecutivePollFailures} consecutive times`, lastPollError);
699
763
  continue;
700
764
  }
701
- const status = apps[0]?.status;
702
- switch (status) {
765
+ if (apps.length === 0) throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status response contained no apps");
766
+ const rawStatus = apps[0].status;
767
+ if (rawStatus === void 0) throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status unavailable");
768
+ if (!isDeployStatus(rawStatus)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected deploy status: ${rawStatus}`);
769
+ switch (rawStatus) {
703
770
  case "SUCCESS": return;
704
771
  case "FAIL": throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment failed");
705
772
  case "CANCEL": throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment was cancelled");
706
773
  case "PROCESSING": continue;
707
- case void 0: throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status unavailable");
708
- default: throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected deploy status: ${status}`);
709
774
  }
710
775
  }
711
776
  throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment timed out", lastPollError);
@@ -834,9 +899,7 @@ var KintoneCustomizationConfigurator = class {
834
899
  revision: response.revision
835
900
  };
836
901
  } catch (error) {
837
- if (isBusinessRuleError(error)) throw error;
838
- if (error instanceof SystemError) throw error;
839
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get app customization", error);
902
+ throw wrapKintoneError(error, "Failed to get app customization");
840
903
  }
841
904
  }
842
905
  async updateCustomization(params) {
@@ -857,9 +920,7 @@ var KintoneCustomizationConfigurator = class {
857
920
  const response = await this.client.app.updateAppCustomize(requestParams);
858
921
  return { revision: String(response.revision) };
859
922
  } catch (error) {
860
- if (isBusinessRuleError(error)) throw error;
861
- if (error instanceof SystemError) throw error;
862
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update app customization", error);
923
+ throw wrapKintoneError(error, "Failed to update app customization");
863
924
  }
864
925
  }
865
926
  };
@@ -871,31 +932,77 @@ var KintoneFileDownloader = class {
871
932
  this.client = client;
872
933
  }
873
934
  async download(fileKey) {
874
- if (!fileKey) throw new SystemError(SystemErrorCode.ExternalApiError, "fileKey must not be empty");
935
+ if (!fileKey) throw new ValidationError(ValidationErrorCode.InvalidInput, "fileKey must not be empty");
875
936
  try {
876
937
  return await this.client.file.downloadFile({ fileKey });
877
938
  } catch (error) {
878
- if (isBusinessRuleError(error)) throw error;
879
- if (error instanceof SystemError) throw error;
880
- throw new SystemError(SystemErrorCode.ExternalApiError, `Failed to download file: ${fileKey}`, error);
939
+ throw wrapKintoneError(error, `Failed to download file: ${fileKey}`);
881
940
  }
882
941
  }
883
942
  };
884
943
 
944
+ //#endregion
945
+ //#region src/lib/safePath.ts
946
+ /**
947
+ * Returns `true` if {@link targetPath} resolves to a location within
948
+ * {@link baseDir}. Follows symlinks via `realpathSync` for both the base
949
+ * directory and the target path when they exist on disk, falling back to
950
+ * textual `resolve` otherwise (e.g. in tests with virtual paths or when
951
+ * the target does not yet exist).
952
+ *
953
+ * {@link targetPath} may be either a relative path (resolved against
954
+ * {@link baseDir}) or an absolute path (used as-is by `path.resolve`).
955
+ * When callers have already resolved the path via `path.resolve(baseDir, x)`,
956
+ * passing the result as an absolute path is safe — `resolve(base, absPath)`
957
+ * returns `absPath` unchanged.
958
+ *
959
+ * **Limitation — symlinks**: If the target path does not exist on disk,
960
+ * symlink components within it cannot be resolved. The check falls back
961
+ * to a textual prefix comparison, which still catches `..` traversal but
962
+ * cannot detect symlink-based escapes for non-existent paths.
963
+ *
964
+ * **Limitation — TOCTOU**: This check is inherently subject to a
965
+ * time-of-check-to-time-of-use race: the filesystem may change between
966
+ * this call and the subsequent file operation. This is acceptable for a
967
+ * CLI tool where the user controls the local environment.
968
+ *
969
+ * **Note**: This function performs synchronous I/O (`realpathSync`).
970
+ *
971
+ * Callers decide how to handle the result.
972
+ */
973
+ function isSafePath(targetPath, baseDir) {
974
+ if (targetPath.includes("\0") || baseDir.includes("\0")) return false;
975
+ let resolvedBase;
976
+ try {
977
+ resolvedBase = realpathSync(baseDir);
978
+ } catch {
979
+ resolvedBase = resolve(baseDir);
980
+ }
981
+ const textualTarget = resolve(resolvedBase, targetPath);
982
+ let resolvedTarget;
983
+ try {
984
+ resolvedTarget = realpathSync(textualTarget);
985
+ } catch {
986
+ resolvedTarget = textualTarget;
987
+ }
988
+ return resolvedTarget.startsWith(`${resolvedBase}/`) || resolvedTarget === resolvedBase;
989
+ }
990
+
885
991
  //#endregion
886
992
  //#region src/core/adapters/kintone/fileUploader.ts
887
993
  var KintoneFileUploader = class {
888
- constructor(client) {
994
+ constructor(client, baseDir) {
889
995
  this.client = client;
996
+ this.baseDir = baseDir;
890
997
  }
891
998
  async upload(filePath) {
892
- if (!filePath) throw new SystemError(SystemErrorCode.ExternalApiError, "filePath must not be empty");
999
+ if (!filePath) throw new ValidationError(ValidationErrorCode.InvalidInput, "filePath must not be empty");
1000
+ const resolvedPath = resolve(this.baseDir, filePath);
1001
+ if (!isSafePath(resolvedPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${resolvedPath}" escapes base directory "${this.baseDir}"`);
893
1002
  try {
894
- return { fileKey: (await this.client.file.uploadFile({ file: { path: filePath } })).fileKey };
1003
+ return { fileKey: (await this.client.file.uploadFile({ file: { path: resolvedPath } })).fileKey };
895
1004
  } catch (error) {
896
- if (isBusinessRuleError(error)) throw error;
897
- if (error instanceof SystemError) throw error;
898
- throw new SystemError(SystemErrorCode.ExternalApiError, `Failed to upload file: ${filePath}`, error);
1005
+ throw wrapKintoneError(error, `Failed to upload file: ${filePath}`);
899
1006
  }
900
1007
  }
901
1008
  };
@@ -911,7 +1018,7 @@ function hasControlChars(s) {
911
1018
  return false;
912
1019
  }
913
1020
  /** Replaces control characters (0x00–0x1f, 0x7f) with escaped `\\xNN` representation for safe display. */
914
- function sanitizeForDisplay(s) {
1021
+ function sanitizeForDisplay$1(s) {
915
1022
  let result = "";
916
1023
  for (let i = 0; i < s.length; i++) {
917
1024
  const ch = s.charCodeAt(i);
@@ -933,7 +1040,7 @@ function hasInvalidFieldCodeChars(code) {
933
1040
  }
934
1041
  const FieldCode = { create: (code) => {
935
1042
  if (code.length === 0) throw new BusinessRuleError(FormSchemaErrorCode.FsEmptyFieldCode, "Field code cannot be empty");
936
- if (hasInvalidFieldCodeChars(code)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidFieldCode, `Field code "${sanitizeForDisplay(code)}" contains invalid characters`);
1043
+ if (hasInvalidFieldCodeChars(code)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidFieldCode, `Field code "${sanitizeForDisplay$1(code)}" contains invalid characters`);
937
1044
  return code;
938
1045
  } };
939
1046
 
@@ -976,10 +1083,6 @@ const DECORATION_TYPES$1 = new Set([
976
1083
  "SPACER",
977
1084
  "HR"
978
1085
  ]);
979
- const KINTONE_REVISION_CONFLICT_CODE = "GAIA_CO02";
980
- function isRevisionConflict(error) {
981
- return error instanceof KintoneRestAPIError && error.code === KINTONE_REVISION_CONFLICT_CODE;
982
- }
983
1086
  /**
984
1087
  * Tracks the latest known kintone form revision for optimistic concurrency control.
985
1088
  *
@@ -992,10 +1095,13 @@ function isRevisionConflict(error) {
992
1095
  var RevisionTracker = class {
993
1096
  revision = void 0;
994
1097
  track(revision) {
995
- if (this.revision === void 0 || Number(revision) > Number(this.revision)) this.revision = revision;
1098
+ if (revision === "") throw new SystemError(SystemErrorCode.ExternalApiError, "Unexpected empty revision from kintone API");
1099
+ const revisionNum = Number(revision);
1100
+ if (!Number.isFinite(revisionNum)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric revision from kintone API: "${revision}"`);
1101
+ if (this.revision === void 0 || revisionNum > this.revision) this.revision = revisionNum;
996
1102
  }
997
1103
  get current() {
998
- return this.revision;
1104
+ return this.revision !== void 0 ? String(this.revision) : void 0;
999
1105
  }
1000
1106
  };
1001
1107
  function assertRecord(value, fieldPath) {
@@ -1267,7 +1373,7 @@ var KintoneFormConfigurator = class {
1267
1373
  app: this.appId,
1268
1374
  preview: true
1269
1375
  });
1270
- this.revisionTracker.track(revision);
1376
+ if (revision) this.revisionTracker.track(revision);
1271
1377
  const fields = /* @__PURE__ */ new Map();
1272
1378
  for (const [code, prop] of Object.entries(properties)) {
1273
1379
  const kintoneProp = prop;
@@ -1278,9 +1384,7 @@ var KintoneFormConfigurator = class {
1278
1384
  }
1279
1385
  return fields;
1280
1386
  } catch (error) {
1281
- if (isBusinessRuleError(error)) throw error;
1282
- if (error instanceof SystemError) throw error;
1283
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get form fields", error);
1387
+ throw wrapKintoneError(error, "Failed to get form fields");
1284
1388
  }
1285
1389
  }
1286
1390
  async addFields(fields) {
@@ -1294,10 +1398,7 @@ var KintoneFormConfigurator = class {
1294
1398
  });
1295
1399
  if (response.revision) this.revisionTracker.track(response.revision);
1296
1400
  } catch (error) {
1297
- if (isBusinessRuleError(error)) throw error;
1298
- if (error instanceof SystemError) throw error;
1299
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1300
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add form fields", error);
1401
+ throw wrapKintoneError(error, "Failed to add form fields");
1301
1402
  }
1302
1403
  }
1303
1404
  async updateFields(fields) {
@@ -1311,10 +1412,7 @@ var KintoneFormConfigurator = class {
1311
1412
  });
1312
1413
  if (response.revision) this.revisionTracker.track(response.revision);
1313
1414
  } catch (error) {
1314
- if (isBusinessRuleError(error)) throw error;
1315
- if (error instanceof SystemError) throw error;
1316
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1317
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update form fields", error);
1415
+ throw wrapKintoneError(error, "Failed to update form fields");
1318
1416
  }
1319
1417
  }
1320
1418
  async deleteFields(fieldCodes) {
@@ -1326,10 +1424,7 @@ var KintoneFormConfigurator = class {
1326
1424
  });
1327
1425
  if (response.revision) this.revisionTracker.track(response.revision);
1328
1426
  } catch (error) {
1329
- if (isBusinessRuleError(error)) throw error;
1330
- if (error instanceof SystemError) throw error;
1331
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1332
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to delete form fields", error);
1427
+ throw wrapKintoneError(error, "Failed to delete form fields");
1333
1428
  }
1334
1429
  }
1335
1430
  async getLayout() {
@@ -1338,12 +1433,10 @@ var KintoneFormConfigurator = class {
1338
1433
  app: this.appId,
1339
1434
  preview: true
1340
1435
  });
1341
- this.revisionTracker.track(response.revision);
1436
+ if (response.revision) this.revisionTracker.track(response.revision);
1342
1437
  return response.layout.map(fromKintoneLayoutItem);
1343
1438
  } catch (error) {
1344
- if (isBusinessRuleError(error)) throw error;
1345
- if (error instanceof SystemError) throw error;
1346
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get form layout", error);
1439
+ throw wrapKintoneError(error, "Failed to get form layout");
1347
1440
  }
1348
1441
  }
1349
1442
  async updateLayout(layout) {
@@ -1356,10 +1449,7 @@ var KintoneFormConfigurator = class {
1356
1449
  });
1357
1450
  if (response.revision) this.revisionTracker.track(response.revision);
1358
1451
  } catch (error) {
1359
- if (isBusinessRuleError(error)) throw error;
1360
- if (error instanceof SystemError) throw error;
1361
- if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
1362
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update form layout", error);
1452
+ throw wrapKintoneError(error, "Failed to update form layout");
1363
1453
  }
1364
1454
  }
1365
1455
  };
@@ -1480,9 +1570,7 @@ var KintoneRecordManager = class {
1480
1570
  };
1481
1571
  });
1482
1572
  } catch (error) {
1483
- if (isBusinessRuleError(error)) throw error;
1484
- if (error instanceof SystemError) throw error;
1485
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get records", error);
1573
+ throw wrapKintoneError(error, "Failed to get records");
1486
1574
  }
1487
1575
  }
1488
1576
  async addRecords(records) {
@@ -1493,9 +1581,7 @@ var KintoneRecordManager = class {
1493
1581
  records: records.map(toKintoneRecord)
1494
1582
  });
1495
1583
  } catch (error) {
1496
- if (isBusinessRuleError(error)) throw error;
1497
- if (error instanceof SystemError) throw error;
1498
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add records", error);
1584
+ throw wrapKintoneError(error, "Failed to add records");
1499
1585
  }
1500
1586
  }
1501
1587
  async updateRecords(records) {
@@ -1509,9 +1595,7 @@ var KintoneRecordManager = class {
1509
1595
  }))
1510
1596
  });
1511
1597
  } catch (error) {
1512
- if (isBusinessRuleError(error)) throw error;
1513
- if (error instanceof SystemError) throw error;
1514
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update records", error);
1598
+ throw wrapKintoneError(error, "Failed to update records");
1515
1599
  }
1516
1600
  }
1517
1601
  async deleteAllRecords() {
@@ -1528,9 +1612,7 @@ var KintoneRecordManager = class {
1528
1612
  });
1529
1613
  return { deletedCount: records.length };
1530
1614
  } catch (error) {
1531
- if (isBusinessRuleError(error)) throw error;
1532
- if (error instanceof SystemError) throw error;
1533
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to delete all records", error);
1615
+ throw wrapKintoneError(error, "Failed to delete all records");
1534
1616
  }
1535
1617
  }
1536
1618
  };
@@ -1544,10 +1626,15 @@ function createLocalFileCustomizationStorage(filePath) {
1544
1626
  //#endregion
1545
1627
  //#region src/core/adapters/local/fileWriter.ts
1546
1628
  var LocalFileWriter = class {
1629
+ constructor(baseDir = process.cwd()) {
1630
+ this.baseDir = baseDir;
1631
+ }
1547
1632
  async write(filePath, data) {
1633
+ const resolvedPath = resolve(this.baseDir, filePath);
1634
+ if (!isSafePath(resolvedPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${resolvedPath}" escapes base directory "${this.baseDir}"`);
1548
1635
  try {
1549
- await mkdir(dirname(filePath), { recursive: true });
1550
- await writeFile(filePath, Buffer.from(new Uint8Array(data)));
1636
+ await mkdir(dirname(resolvedPath), { recursive: true });
1637
+ await writeFile(resolvedPath, Buffer.from(new Uint8Array(data)));
1551
1638
  } catch (error) {
1552
1639
  throw new SystemError(SystemErrorCode.StorageError, `Failed to write file: ${filePath}`, error);
1553
1640
  }
@@ -1594,9 +1681,9 @@ function createCustomizationCliContainer(config) {
1594
1681
  return {
1595
1682
  customizationConfigurator: new KintoneCustomizationConfigurator(client, config.appId),
1596
1683
  customizationStorage: createLocalFileCustomizationStorage(config.customizeFilePath),
1597
- fileUploader: new KintoneFileUploader(client),
1684
+ fileUploader: new KintoneFileUploader(client, process.cwd()),
1598
1685
  fileDownloader: new KintoneFileDownloader(client),
1599
- fileWriter: new LocalFileWriter(),
1686
+ fileWriter: new LocalFileWriter(process.cwd()),
1600
1687
  appDeployer: new KintoneAppDeployer(client, config.appId)
1601
1688
  };
1602
1689
  }
@@ -1799,7 +1886,7 @@ function hasInvalidAppNameChars(name) {
1799
1886
  }
1800
1887
  const AppName = { create: (name) => {
1801
1888
  if (name.length === 0) throw new BusinessRuleError(ProjectConfigErrorCode.PcEmptyAppName, "App name cannot be empty");
1802
- if (hasInvalidAppNameChars(name)) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAppName, `App name "${sanitizeForDisplay(name)}" contains invalid characters (path separators or control characters are not allowed)`);
1889
+ if (hasInvalidAppNameChars(name)) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAppName, `App name "${sanitizeForDisplay$1(name)}" contains invalid characters (path separators or control characters are not allowed)`);
1803
1890
  if (name === "." || name === "..") throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAppName, `App name "${name}" is not allowed (reserved path component)`);
1804
1891
  return name;
1805
1892
  } };
@@ -2005,14 +2092,17 @@ function buildAppFilePaths(appName, baseDir) {
2005
2092
  };
2006
2093
  }
2007
2094
 
2008
- //#endregion
2009
- //#region src/core/application/formSchema/deployApp.ts
2010
- async function deployApp({ container }) {
2011
- await container.appDeployer.deploy();
2012
- }
2013
-
2014
2095
  //#endregion
2015
2096
  //#region src/cli/handleError.ts
2097
+ /**
2098
+ * Log error details without terminating the process.
2099
+ * Use this in contexts where multiple errors may be reported (e.g. multi-app results).
2100
+ *
2101
+ * Note: This function intentionally does NOT show the "Set VERBOSE=1 ..." hint.
2102
+ * The hint is only shown by `handleCliError`, which is the top-level CLI handler.
2103
+ * `logError` is used for per-app error reporting within multi-app runs where the
2104
+ * hint would be noisy if repeated for each failure.
2105
+ */
2016
2106
  function logError(error) {
2017
2107
  if (isBusinessRuleError(error)) {
2018
2108
  p.log.error(`[BusinessRuleError] ${error.code}: ${error.message}`);
@@ -2021,11 +2111,14 @@ function logError(error) {
2021
2111
  }
2022
2112
  if (isValidationError(error)) {
2023
2113
  p.log.error(`[ValidationError] ${error.code}: ${error.message}`);
2114
+ p.log.warn("Hint: Please check your input values and configuration.");
2024
2115
  logErrorDetails(error);
2025
2116
  return;
2026
2117
  }
2027
2118
  if (isSystemError(error)) {
2028
2119
  p.log.error(`[SystemError] ${error.code}: ${error.message}`);
2120
+ const hint = systemErrorHints[error.code];
2121
+ if (hint) p.log.warn(`Hint: ${hint}`);
2029
2122
  logErrorDetails(error);
2030
2123
  return;
2031
2124
  }
@@ -2065,42 +2158,83 @@ function logError(error) {
2065
2158
  }
2066
2159
  p.log.error(`[Error] Unexpected error occurred: ${String(error)}`);
2067
2160
  }
2161
+ /**
2162
+ * Log error details and terminate the process with exit code 1.
2163
+ * Use this as the top-level error handler in CLI command `run` functions.
2164
+ */
2068
2165
  function handleCliError(error) {
2069
2166
  logError(error);
2167
+ if (!isVerbose()) p.log.info("Set VERBOSE=1 for full stack traces.");
2070
2168
  p.outro("Failed.");
2071
2169
  process.exit(1);
2072
2170
  }
2073
2171
  function formatErrorForDisplay(error) {
2074
- if (error instanceof Error) return error.message;
2075
- if (typeof error === "object" && error !== null) return JSON.stringify(error, null, 2);
2076
- return String(error);
2172
+ if (error instanceof Error) return sanitizeString(error.message);
2173
+ if (typeof error === "object" && error !== null) return JSON.stringify(sanitizeForDisplay(error), null, 2);
2174
+ return sanitizeString(String(error));
2175
+ }
2176
+ const systemErrorHints = {
2177
+ NETWORK_ERROR: "Please check your network connection and kintone domain.",
2178
+ EXTERNAL_API_ERROR: "The kintone API returned an unexpected error. Please retry or check the API status.",
2179
+ STORAGE_ERROR: "Failed to read/write a local file. Please check file permissions.",
2180
+ EXECUTION_ERROR: "One or more apps failed during execution. Check the individual errors above."
2181
+ };
2182
+ const SENSITIVE_KEYS = /^(authorization|apitoken|api-token|api_token|apikey|api-key|api_key|password|secret|credentials|x-cybozu-authorization)$/i;
2183
+ const SENSITIVE_VALUE_PATTERNS = /(?<=(?:password|apiToken|api[_-]?token|api[_-]?key|secret|credentials)[=:]\s*)\S+/gi;
2184
+ const AUTHORIZATION_VALUE_PATTERN = /(?<=authorization[=:]\s*)\S+(?:\s+\S+)?/gi;
2185
+ function sanitizeString(value) {
2186
+ return value.replace(AUTHORIZATION_VALUE_PATTERN, "[REDACTED]").replace(SENSITIVE_VALUE_PATTERNS, "[REDACTED]");
2187
+ }
2188
+ function sanitizeForDisplay(obj, seen = /* @__PURE__ */ new WeakSet()) {
2189
+ if (typeof obj !== "object" || obj === null) return obj;
2190
+ if (seen.has(obj)) return "[Circular]";
2191
+ seen.add(obj);
2192
+ if (Array.isArray(obj)) return obj.map((item) => sanitizeForDisplay(item, seen));
2193
+ const result = {};
2194
+ if (obj instanceof Error) {
2195
+ result.message = sanitizeString(obj.message);
2196
+ if (obj.stack) result.stack = sanitizeString(obj.stack);
2197
+ }
2198
+ for (const [key, value] of Object.entries(obj)) if (SENSITIVE_KEYS.test(key)) result[key] = "[REDACTED]";
2199
+ else if (typeof value === "object" && value !== null) result[key] = sanitizeForDisplay(value, seen);
2200
+ else if (typeof value === "string") result[key] = sanitizeString(value);
2201
+ else result[key] = value;
2202
+ return result;
2203
+ }
2204
+ function isVerbose() {
2205
+ return process.env.VERBOSE === "1" || process.env.VERBOSE === "true" || process.env.VERBOSE === "yes";
2077
2206
  }
2078
2207
  function logErrorDetails(error) {
2079
2208
  if (error.cause) {
2209
+ const seen = /* @__PURE__ */ new WeakSet();
2210
+ seen.add(error);
2080
2211
  p.log.warn(`Cause: ${formatErrorForDisplay(error.cause)}`);
2081
- logNestedErrorProperties(error.cause);
2212
+ logNestedErrorProperties(error.cause, seen);
2082
2213
  }
2083
- if (error.stack) p.log.warn(`Stack: ${error.stack}`);
2214
+ if (isVerbose() && error.stack) p.log.warn(`Stack: ${error.stack}`);
2084
2215
  }
2085
- function logNestedErrorProperties(target) {
2216
+ function logNestedErrorProperties(target, seen = /* @__PURE__ */ new WeakSet()) {
2086
2217
  if (typeof target !== "object" || target === null) return;
2218
+ if (seen.has(target)) return;
2219
+ seen.add(target);
2087
2220
  const record = target;
2088
2221
  if (record.error instanceof Error) {
2089
- p.log.warn(` Error: ${record.error.message}`);
2090
- const innerRecord = record.error;
2091
- if (innerRecord.errors && typeof innerRecord.errors === "object") p.log.warn(` Details: ${JSON.stringify(innerRecord.errors, null, 2)}`);
2222
+ p.log.warn(` Error: ${sanitizeString(record.error.message)}`);
2223
+ if (hasObjectProperty(record.error, "errors")) p.log.warn(` Details: ${JSON.stringify(sanitizeForDisplay(record.error.errors), null, 2)}`);
2092
2224
  }
2093
2225
  if (Array.isArray(record.errors)) for (const e of record.errors) if (e instanceof Error) {
2094
- p.log.warn(` - ${e.message}`);
2095
- const inner = e;
2096
- if (inner.errors && typeof inner.errors === "object") p.log.warn(` ${JSON.stringify(inner.errors, null, 2)}`);
2097
- } else p.log.warn(` - ${JSON.stringify(e, null, 2)}`);
2098
- else if (record.errors && typeof record.errors === "object" && !("error" in record)) p.log.warn(`Details: ${JSON.stringify(record.errors, null, 2)}`);
2226
+ p.log.warn(` - ${sanitizeString(e.message)}`);
2227
+ if (hasObjectProperty(e, "errors")) p.log.warn(` ${JSON.stringify(sanitizeForDisplay(e.errors), null, 2)}`);
2228
+ } else p.log.warn(` - ${JSON.stringify(sanitizeForDisplay(e), null, 2)}`);
2229
+ else if (record.errors && typeof record.errors === "object" && !(record.error instanceof Error)) p.log.warn(`Details: ${JSON.stringify(sanitizeForDisplay(record.errors), null, 2)}`);
2099
2230
  if (target instanceof Error && target.cause) {
2100
2231
  p.log.warn(` Caused by: ${formatErrorForDisplay(target.cause)}`);
2101
- logNestedErrorProperties(target.cause);
2232
+ logNestedErrorProperties(target.cause, seen);
2102
2233
  }
2103
2234
  }
2235
+ function hasObjectProperty(obj, key) {
2236
+ return key in obj && typeof obj[key] === "object" && obj[key] !== null;
2237
+ }
2104
2238
  function isApplicationError(error) {
2105
2239
  return error instanceof ApplicationError;
2106
2240
  }
@@ -2213,40 +2347,30 @@ async function confirmAndDeploy(containers, skipConfirm, successMessage = "Deplo
2213
2347
  if (!skipConfirm) {
2214
2348
  const shouldDeploy = await p.confirm({ message: "Deploy to production?" });
2215
2349
  if (p.isCancel(shouldDeploy) || !shouldDeploy) {
2216
- p.log.warn("Applied to preview, but not deployed to production.");
2350
+ const appNames = containers.map((c) => c.appName).filter((n) => n != null);
2351
+ if (appNames.length > 0) p.log.warn(`Applied to preview, but not deployed to production: ${appNames.join(", ")}`);
2352
+ else p.log.warn("Applied to preview, but not deployed to production.");
2217
2353
  return;
2218
2354
  }
2219
2355
  }
2220
2356
  const ds = p.spinner();
2221
2357
  ds.start("Deploying to production...");
2358
+ const deployedNames = [];
2222
2359
  try {
2223
- for (const container of containers) await container.appDeployer.deploy();
2360
+ for (const container of containers) {
2361
+ await container.appDeployer.deploy();
2362
+ if (container.appName) deployedNames.push(container.appName);
2363
+ }
2224
2364
  ds.stop("Deployed to production.");
2225
2365
  } catch (error) {
2226
2366
  ds.stop("Deployment failed.");
2367
+ if (deployedNames.length > 0) p.log.warn(`${deployedNames.length}/${containers.length} app(s) were deployed before the failure: ${deployedNames.join(", ")}`);
2368
+ const notDeployed = containers.map((c) => c.appName).filter((n) => n != null).filter((n) => !deployedNames.includes(n));
2369
+ if (notDeployed.length > 0) p.log.warn(`Not deployed: ${notDeployed.join(", ")}`);
2227
2370
  throw error;
2228
2371
  }
2229
2372
  p.log.success(successMessage);
2230
2373
  }
2231
- async function promptDeploy(container, skipConfirm) {
2232
- if (!skipConfirm) {
2233
- const shouldDeploy = await p.confirm({ message: "Deploy to production?" });
2234
- if (p.isCancel(shouldDeploy) || !shouldDeploy) {
2235
- p.log.warn("Applied to preview, but not deployed to production.");
2236
- return;
2237
- }
2238
- }
2239
- const ds = p.spinner();
2240
- ds.start("Deploying to production...");
2241
- try {
2242
- await deployApp({ container });
2243
- ds.stop("Deployment complete.");
2244
- } catch (error) {
2245
- ds.stop("Deployment failed.");
2246
- throw error;
2247
- }
2248
- p.log.success("Deployed to production.");
2249
- }
2250
2374
 
2251
2375
  //#endregion
2252
2376
  //#region src/cli/projectConfig.ts
@@ -2352,10 +2476,30 @@ async function routeMultiApp(values, handlers) {
2352
2476
  }
2353
2477
  await handlers.multiApp(target.plan, target.config);
2354
2478
  }
2479
+ /**
2480
+ * High-level multi-app executor that prints app headers before each executor call.
2481
+ * Use this when the executor does not print its own headers (most apply commands).
2482
+ * Delegates to `runMultiAppWithFailCheck` for execution and failure handling.
2483
+ */
2484
+ async function runMultiAppWithHeaders(plan, executor, successMessage) {
2485
+ await runMultiAppWithFailCheck(plan, async (app) => {
2486
+ printAppHeader(app.name, app.appId);
2487
+ await executor(app);
2488
+ }, successMessage);
2489
+ }
2490
+ /**
2491
+ * Low-level multi-app executor that runs apps, prints results, and throws on failure.
2492
+ * Use this directly when the executor needs full control over output (e.g. schema migrate
2493
+ * prints its own headers and performs per-app deploy inside the executor callback).
2494
+ */
2355
2495
  async function runMultiAppWithFailCheck(plan, executor, successMessage) {
2356
2496
  const multiResult = await executeMultiApp(plan, executor);
2357
2497
  printMultiAppResult(multiResult);
2358
- if (multiResult.hasFailure) throw new SystemError(SystemErrorCode.ExternalApiError, "Execution stopped due to failure.");
2498
+ if (multiResult.hasFailure) {
2499
+ const succeededCount = multiResult.results.filter((r) => r.status === "succeeded").length;
2500
+ if (succeededCount > 0) p.log.warn(`${succeededCount} app(s) were applied to preview but may not have been deployed. Check their status in kintone.`);
2501
+ throw new SystemError(SystemErrorCode.ExecutionError, "Execution stopped due to failure.");
2502
+ }
2359
2503
  if (successMessage) p.log.success(successMessage);
2360
2504
  }
2361
2505
  async function configFileExists(configPath) {
@@ -2482,12 +2626,13 @@ var apply_default$12 = define({
2482
2626
  },
2483
2627
  multiApp: async (plan, projectConfig) => {
2484
2628
  const containers = [];
2485
- await runMultiAppWithFailCheck(plan, async (app) => {
2486
- const config = resolveActionAppContainerConfig(app, projectConfig, values);
2487
- printAppHeader(app.name, app.appId);
2488
- const container = await runAction(config);
2489
- containers.push(container);
2490
- }, void 0);
2629
+ await runMultiAppWithHeaders(plan, async (app) => {
2630
+ const container = await runAction(resolveActionAppContainerConfig(app, projectConfig, values));
2631
+ containers.push({
2632
+ appDeployer: container.appDeployer,
2633
+ appName: app.name
2634
+ });
2635
+ });
2491
2636
  await confirmAndDeploy(containers, skipConfirm);
2492
2637
  }
2493
2638
  });
@@ -2936,9 +3081,7 @@ var KintoneAdminNotesConfigurator = class {
2936
3081
  revision: response.revision
2937
3082
  };
2938
3083
  } catch (error) {
2939
- if (isBusinessRuleError(error)) throw error;
2940
- if (error instanceof SystemError) throw error;
2941
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get admin notes", error);
3084
+ throw wrapKintoneError(error, "Failed to get admin notes");
2942
3085
  }
2943
3086
  }
2944
3087
  async updateAdminNotes(params) {
@@ -2951,9 +3094,7 @@ var KintoneAdminNotesConfigurator = class {
2951
3094
  if (params.revision !== void 0) requestParams.revision = params.revision;
2952
3095
  return { revision: (await this.client.app.updateAdminNotes(requestParams)).revision };
2953
3096
  } catch (error) {
2954
- if (isBusinessRuleError(error)) throw error;
2955
- if (error instanceof SystemError) throw error;
2956
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update admin notes", error);
3097
+ throw wrapKintoneError(error, "Failed to update admin notes");
2957
3098
  }
2958
3099
  }
2959
3100
  };
@@ -3028,12 +3169,13 @@ var apply_default$11 = define({
3028
3169
  },
3029
3170
  multiApp: async (plan, projectConfig) => {
3030
3171
  const containers = [];
3031
- await runMultiAppWithFailCheck(plan, async (app) => {
3032
- const config = resolveAdminNotesAppContainerConfig(app, projectConfig, values);
3033
- printAppHeader(app.name, app.appId);
3034
- const container = await runAdminNotes(config);
3035
- containers.push(container);
3036
- }, void 0);
3172
+ await runMultiAppWithHeaders(plan, async (app) => {
3173
+ const container = await runAdminNotes(resolveAdminNotesAppContainerConfig(app, projectConfig, values));
3174
+ containers.push({
3175
+ appDeployer: container.appDeployer,
3176
+ appName: app.name
3177
+ });
3178
+ });
3037
3179
  await confirmAndDeploy(containers, skipConfirm);
3038
3180
  }
3039
3181
  });
@@ -3303,9 +3445,7 @@ var KintoneAppPermissionConfigurator = class {
3303
3445
  revision: response.revision
3304
3446
  };
3305
3447
  } catch (error) {
3306
- if (isBusinessRuleError(error)) throw error;
3307
- if (error instanceof SystemError) throw error;
3308
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get app ACL", error);
3448
+ throw wrapKintoneError(error, "Failed to get app ACL");
3309
3449
  }
3310
3450
  }
3311
3451
  async updateAppPermissions(params) {
@@ -3317,9 +3457,7 @@ var KintoneAppPermissionConfigurator = class {
3317
3457
  if (params.revision !== void 0) requestParams.revision = params.revision;
3318
3458
  return { revision: (await this.client.app.updateAppAcl(requestParams)).revision };
3319
3459
  } catch (error) {
3320
- if (isBusinessRuleError(error)) throw error;
3321
- if (error instanceof SystemError) throw error;
3322
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update app ACL", error);
3460
+ throw wrapKintoneError(error, "Failed to update app ACL");
3323
3461
  }
3324
3462
  }
3325
3463
  };
@@ -3394,12 +3532,13 @@ var apply_default$10 = define({
3394
3532
  },
3395
3533
  multiApp: async (plan, projectConfig) => {
3396
3534
  const containers = [];
3397
- await runMultiAppWithFailCheck(plan, async (app) => {
3398
- const config = resolveAppAclAppContainerConfig(app, projectConfig, values);
3399
- printAppHeader(app.name, app.appId);
3400
- const container = await runAppAcl(config);
3401
- containers.push(container);
3402
- }, void 0);
3535
+ await runMultiAppWithHeaders(plan, async (app) => {
3536
+ const container = await runAppAcl(resolveAppAclAppContainerConfig(app, projectConfig, values));
3537
+ containers.push({
3538
+ appDeployer: container.appDeployer,
3539
+ appName: app.name
3540
+ });
3541
+ });
3403
3542
  await confirmAndDeploy(containers, skipConfirm);
3404
3543
  }
3405
3544
  });
@@ -4021,12 +4160,13 @@ var apply_default$9 = define({
4021
4160
  },
4022
4161
  multiApp: async (plan, projectConfig) => {
4023
4162
  const containers = [];
4024
- await runMultiAppWithFailCheck(plan, async (app) => {
4025
- const config = resolveCustomizeAppConfig(app, projectConfig, values);
4026
- printAppHeader(app.name, app.appId);
4027
- const container = await applyCustomizationForApp(config);
4028
- containers.push(container);
4029
- }, void 0);
4163
+ await runMultiAppWithHeaders(plan, async (app) => {
4164
+ const container = await applyCustomizationForApp(resolveCustomizeAppConfig(app, projectConfig, values));
4165
+ containers.push({
4166
+ appDeployer: container.appDeployer,
4167
+ appName: app.name
4168
+ });
4169
+ });
4030
4170
  await confirmAndDeploy(containers, skipConfirm, "Customization applied and deployed successfully.");
4031
4171
  }
4032
4172
  });
@@ -4212,9 +4352,7 @@ var KintoneFieldPermissionConfigurator = class {
4212
4352
  revision: response.revision
4213
4353
  };
4214
4354
  } catch (error) {
4215
- if (isBusinessRuleError(error)) throw error;
4216
- if (error instanceof SystemError) throw error;
4217
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get field ACL", error);
4355
+ throw wrapKintoneError(error, "Failed to get field ACL");
4218
4356
  }
4219
4357
  }
4220
4358
  async updateFieldPermissions(params) {
@@ -4226,9 +4364,7 @@ var KintoneFieldPermissionConfigurator = class {
4226
4364
  if (params.revision !== void 0) requestParams.revision = params.revision;
4227
4365
  return { revision: (await this.client.app.updateFieldAcl(requestParams)).revision };
4228
4366
  } catch (error) {
4229
- if (isBusinessRuleError(error)) throw error;
4230
- if (error instanceof SystemError) throw error;
4231
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update field ACL", error);
4367
+ throw wrapKintoneError(error, "Failed to update field ACL");
4232
4368
  }
4233
4369
  }
4234
4370
  };
@@ -4384,12 +4520,13 @@ var apply_default$8 = define({
4384
4520
  },
4385
4521
  multiApp: async (plan, projectConfig) => {
4386
4522
  const containers = [];
4387
- await runMultiAppWithFailCheck(plan, async (app) => {
4388
- const config = resolveFieldAclAppContainerConfig(app, projectConfig, values);
4389
- printAppHeader(app.name, app.appId);
4390
- const container = await runFieldAcl(config);
4391
- containers.push(container);
4392
- }, void 0);
4523
+ await runMultiAppWithHeaders(plan, async (app) => {
4524
+ const container = await runFieldAcl(resolveFieldAclAppContainerConfig(app, projectConfig, values));
4525
+ containers.push({
4526
+ appDeployer: container.appDeployer,
4527
+ appName: app.name
4528
+ });
4529
+ });
4393
4530
  await confirmAndDeploy(containers, skipConfirm);
4394
4531
  }
4395
4532
  });
@@ -4557,6 +4694,14 @@ var field_acl_default = define({
4557
4694
  run: () => {}
4558
4695
  });
4559
4696
 
4697
+ //#endregion
4698
+ //#region src/core/adapters/kintone/parseKintoneIntegerField.ts
4699
+ function parseKintoneIntegerField(raw, fieldName) {
4700
+ const n = Number(raw);
4701
+ if (!Number.isFinite(n) || !Number.isInteger(n)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-integer ${fieldName} from kintone API: ${raw}`);
4702
+ return n;
4703
+ }
4704
+
4560
4705
  //#endregion
4561
4706
  //#region src/core/adapters/kintone/generalSettingsConfigurator.ts
4562
4707
  const VALID_THEMES$1 = new Set([
@@ -4601,8 +4746,8 @@ function fromKintoneTitleField(raw) {
4601
4746
  function fromKintoneNumberPrecision(raw) {
4602
4747
  if (!VALID_ROUNDING_MODES$1.has(raw.roundingMode)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected roundingMode from kintone API: ${raw.roundingMode}`);
4603
4748
  return {
4604
- digits: Number(raw.digits),
4605
- decimalPlaces: Number(raw.decimalPlaces),
4749
+ digits: parseKintoneIntegerField(raw.digits, "digits"),
4750
+ decimalPlaces: parseKintoneIntegerField(raw.decimalPlaces, "decimalPlaces"),
4606
4751
  roundingMode: raw.roundingMode
4607
4752
  };
4608
4753
  }
@@ -4620,7 +4765,7 @@ function fromKintoneSettings(raw) {
4620
4765
  ...raw.enableDuplicateRecord !== void 0 ? { enableDuplicateRecord: raw.enableDuplicateRecord } : {},
4621
4766
  ...raw.enableInlineRecordEditing !== void 0 ? { enableInlineRecordEditing: raw.enableInlineRecordEditing } : {},
4622
4767
  ...raw.numberPrecision !== void 0 ? { numberPrecision: fromKintoneNumberPrecision(raw.numberPrecision) } : {},
4623
- ...raw.firstMonthOfFiscalYear !== void 0 ? { firstMonthOfFiscalYear: Number(raw.firstMonthOfFiscalYear) } : {}
4768
+ ...raw.firstMonthOfFiscalYear !== void 0 ? { firstMonthOfFiscalYear: parseKintoneIntegerField(raw.firstMonthOfFiscalYear, "firstMonthOfFiscalYear") } : {}
4624
4769
  };
4625
4770
  }
4626
4771
  function toKintoneIcon(icon) {
@@ -4673,9 +4818,7 @@ var KintoneGeneralSettingsConfigurator = class {
4673
4818
  revision: raw.revision ?? "-1"
4674
4819
  };
4675
4820
  } catch (error) {
4676
- if (isBusinessRuleError(error)) throw error;
4677
- if (error instanceof SystemError) throw error;
4678
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get general settings", error);
4821
+ throw wrapKintoneError(error, "Failed to get general settings");
4679
4822
  }
4680
4823
  }
4681
4824
  async updateGeneralSettings(params) {
@@ -4687,9 +4830,7 @@ var KintoneGeneralSettingsConfigurator = class {
4687
4830
  if (params.revision !== void 0) requestParams.revision = params.revision;
4688
4831
  return { revision: (await this.client.app.updateAppSettings(requestParams)).revision };
4689
4832
  } catch (error) {
4690
- if (isBusinessRuleError(error)) throw error;
4691
- if (error instanceof SystemError) throw error;
4692
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update general settings", error);
4833
+ throw wrapKintoneError(error, "Failed to update general settings");
4693
4834
  }
4694
4835
  }
4695
4836
  };
@@ -4764,8 +4905,7 @@ function fromKintonePerRecordNotification(raw) {
4764
4905
  };
4765
4906
  }
4766
4907
  function fromKintoneReminderNotification(raw) {
4767
- const daysLater = Number(raw.timing.daysLater);
4768
- if (!Number.isFinite(daysLater)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric daysLater from kintone API: ${raw.timing.daysLater}`);
4908
+ const daysLater = parseKintoneIntegerField(raw.timing.daysLater, "daysLater");
4769
4909
  const result = {
4770
4910
  code: raw.timing.code,
4771
4911
  daysLater,
@@ -4774,8 +4914,7 @@ function fromKintoneReminderNotification(raw) {
4774
4914
  targets: raw.targets.map(fromKintoneTarget)
4775
4915
  };
4776
4916
  if ("hoursLater" in raw.timing) {
4777
- const hoursLater = Number(raw.timing.hoursLater);
4778
- if (!Number.isFinite(hoursLater)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric hoursLater from kintone API: ${raw.timing.hoursLater}`);
4917
+ const hoursLater = parseKintoneIntegerField(raw.timing.hoursLater, "hoursLater");
4779
4918
  return {
4780
4919
  ...result,
4781
4920
  hoursLater
@@ -4850,9 +4989,7 @@ var KintoneNotificationConfigurator = class {
4850
4989
  revision: response.revision
4851
4990
  };
4852
4991
  } catch (error) {
4853
- if (isBusinessRuleError(error)) throw error;
4854
- if (error instanceof SystemError) throw error;
4855
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get general notifications", error);
4992
+ throw wrapKintoneError(error, "Failed to get general notifications");
4856
4993
  }
4857
4994
  }
4858
4995
  async updateGeneralNotifications(params) {
@@ -4865,9 +5002,7 @@ var KintoneNotificationConfigurator = class {
4865
5002
  if (params.revision !== void 0) requestParams.revision = params.revision;
4866
5003
  return { revision: (await this.client.app.updateGeneralNotifications(requestParams)).revision };
4867
5004
  } catch (error) {
4868
- if (isBusinessRuleError(error)) throw error;
4869
- if (error instanceof SystemError) throw error;
4870
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update general notifications", error);
5005
+ throw wrapKintoneError(error, "Failed to update general notifications");
4871
5006
  }
4872
5007
  }
4873
5008
  async getPerRecordNotifications() {
@@ -4881,9 +5016,7 @@ var KintoneNotificationConfigurator = class {
4881
5016
  revision: response.revision
4882
5017
  };
4883
5018
  } catch (error) {
4884
- if (isBusinessRuleError(error)) throw error;
4885
- if (error instanceof SystemError) throw error;
4886
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get per-record notifications", error);
5019
+ throw wrapKintoneError(error, "Failed to get per-record notifications");
4887
5020
  }
4888
5021
  }
4889
5022
  async updatePerRecordNotifications(params) {
@@ -4895,9 +5028,7 @@ var KintoneNotificationConfigurator = class {
4895
5028
  if (params.revision !== void 0) requestParams.revision = params.revision;
4896
5029
  return { revision: (await this.client.app.updatePerRecordNotifications(requestParams)).revision };
4897
5030
  } catch (error) {
4898
- if (isBusinessRuleError(error)) throw error;
4899
- if (error instanceof SystemError) throw error;
4900
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update per-record notifications", error);
5031
+ throw wrapKintoneError(error, "Failed to update per-record notifications");
4901
5032
  }
4902
5033
  }
4903
5034
  async getReminderNotifications() {
@@ -4913,9 +5044,7 @@ var KintoneNotificationConfigurator = class {
4913
5044
  revision: response.revision
4914
5045
  };
4915
5046
  } catch (error) {
4916
- if (isBusinessRuleError(error)) throw error;
4917
- if (error instanceof SystemError) throw error;
4918
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get reminder notifications", error);
5047
+ throw wrapKintoneError(error, "Failed to get reminder notifications");
4919
5048
  }
4920
5049
  }
4921
5050
  async updateReminderNotifications(params) {
@@ -4928,9 +5057,7 @@ var KintoneNotificationConfigurator = class {
4928
5057
  if (params.revision !== void 0) requestParams.revision = params.revision;
4929
5058
  return { revision: (await this.client.app.updateReminderNotifications(requestParams)).revision };
4930
5059
  } catch (error) {
4931
- if (isBusinessRuleError(error)) throw error;
4932
- if (error instanceof SystemError) throw error;
4933
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update reminder notifications", error);
5060
+ throw wrapKintoneError(error, "Failed to update reminder notifications");
4934
5061
  }
4935
5062
  }
4936
5063
  };
@@ -4974,22 +5101,18 @@ var KintonePluginConfigurator = class {
4974
5101
  revision: response.revision
4975
5102
  };
4976
5103
  } catch (error) {
4977
- if (isBusinessRuleError(error)) throw error;
4978
- if (error instanceof SystemError) throw error;
4979
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get plugins", error);
5104
+ throw wrapKintoneError(error, "Failed to get plugins");
4980
5105
  }
4981
5106
  }
4982
5107
  async addPlugins(params) {
4983
5108
  try {
4984
5109
  return { revision: (await this.client.app.addPlugins({
4985
5110
  app: this.appId,
4986
- ids: params.ids,
5111
+ ids: [...params.ids],
4987
5112
  revision: params.revision
4988
5113
  })).revision };
4989
5114
  } catch (error) {
4990
- if (isBusinessRuleError(error)) throw error;
4991
- if (error instanceof SystemError) throw error;
4992
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add plugins", error);
5115
+ throw wrapKintoneError(error, "Failed to add plugins");
4993
5116
  }
4994
5117
  }
4995
5118
  };
@@ -5122,9 +5245,7 @@ var KintoneProcessManagementConfigurator = class {
5122
5245
  revision: response.revision
5123
5246
  };
5124
5247
  } catch (error) {
5125
- if (isBusinessRuleError(error)) throw error;
5126
- if (error instanceof SystemError) throw error;
5127
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get process management settings", error);
5248
+ throw wrapKintoneError(error, "Failed to get process management settings");
5128
5249
  }
5129
5250
  }
5130
5251
  async updateProcessManagement(params) {
@@ -5140,9 +5261,7 @@ var KintoneProcessManagementConfigurator = class {
5140
5261
  if (params.revision !== void 0) requestParams.revision = params.revision;
5141
5262
  return { revision: (await this.client.app.updateProcessManagement(requestParams)).revision };
5142
5263
  } catch (error) {
5143
- if (isBusinessRuleError(error)) throw error;
5144
- if (error instanceof SystemError) throw error;
5145
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update process management settings", error);
5264
+ throw wrapKintoneError(error, "Failed to update process management settings");
5146
5265
  }
5147
5266
  }
5148
5267
  };
@@ -5225,9 +5344,7 @@ var KintoneRecordPermissionConfigurator = class {
5225
5344
  revision: response.revision
5226
5345
  };
5227
5346
  } catch (error) {
5228
- if (isBusinessRuleError(error)) throw error;
5229
- if (error instanceof SystemError) throw error;
5230
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get record ACL", error);
5347
+ throw wrapKintoneError(error, "Failed to get record ACL");
5231
5348
  }
5232
5349
  }
5233
5350
  async updateRecordPermissions(params) {
@@ -5239,9 +5356,7 @@ var KintoneRecordPermissionConfigurator = class {
5239
5356
  if (params.revision !== void 0) requestParams.revision = params.revision;
5240
5357
  return { revision: (await this.client.app.updateRecordAcl(requestParams)).revision };
5241
5358
  } catch (error) {
5242
- if (isBusinessRuleError(error)) throw error;
5243
- if (error instanceof SystemError) throw error;
5244
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update record ACL", error);
5359
+ throw wrapKintoneError(error, "Failed to update record ACL");
5245
5360
  }
5246
5361
  }
5247
5362
  };
@@ -5514,9 +5629,7 @@ var KintoneReportConfigurator = class {
5514
5629
  revision: response.revision
5515
5630
  };
5516
5631
  } catch (error) {
5517
- if (isBusinessRuleError(error)) throw error;
5518
- if (error instanceof SystemError) throw error;
5519
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get reports", error);
5632
+ throw wrapKintoneError(error, "Failed to get reports");
5520
5633
  }
5521
5634
  }
5522
5635
  async updateReports(params) {
@@ -5530,9 +5643,7 @@ var KintoneReportConfigurator = class {
5530
5643
  if (params.revision !== void 0) requestParams.revision = params.revision;
5531
5644
  return { revision: (await this.client.app.updateReports(requestParams)).revision };
5532
5645
  } catch (error) {
5533
- if (isBusinessRuleError(error)) throw error;
5534
- if (error instanceof SystemError) throw error;
5535
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update reports", error);
5646
+ throw wrapKintoneError(error, "Failed to update reports");
5536
5647
  }
5537
5648
  }
5538
5649
  };
@@ -5582,21 +5693,17 @@ function fromKintoneView(name, raw) {
5582
5693
  }
5583
5694
  return {
5584
5695
  type: raw.type,
5585
- index: (() => {
5586
- const idx = typeof raw.index === "string" ? Number(raw.index) : raw.index;
5587
- if (!Number.isFinite(idx)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected non-numeric index from kintone API: ${raw.index}`);
5588
- return idx;
5589
- })(),
5696
+ index: parseKintoneIntegerField(raw.index, "index"),
5590
5697
  name,
5591
- ...raw.builtinType !== void 0 && { builtinType: raw.builtinType },
5592
- ...raw.fields !== void 0 && { fields: raw.fields },
5593
- ...raw.date !== void 0 && { date: raw.date },
5594
- ...raw.title !== void 0 && { title: raw.title },
5595
- ...raw.html !== void 0 && { html: raw.html },
5596
- ...raw.pager !== void 0 && { pager: raw.pager },
5597
- ...device !== void 0 && { device },
5598
- ...raw.filterCond !== void 0 && { filterCond: raw.filterCond },
5599
- ...raw.sort !== void 0 && { sort: raw.sort }
5698
+ ...raw.builtinType !== void 0 ? { builtinType: raw.builtinType } : {},
5699
+ ...raw.fields !== void 0 ? { fields: raw.fields } : {},
5700
+ ...raw.date !== void 0 ? { date: raw.date } : {},
5701
+ ...raw.title !== void 0 ? { title: raw.title } : {},
5702
+ ...raw.html !== void 0 ? { html: raw.html } : {},
5703
+ ...raw.pager !== void 0 ? { pager: raw.pager } : {},
5704
+ ...device !== void 0 ? { device } : {},
5705
+ ...raw.filterCond !== void 0 ? { filterCond: raw.filterCond } : {},
5706
+ ...raw.sort !== void 0 ? { sort: raw.sort } : {}
5600
5707
  };
5601
5708
  }
5602
5709
  function toKintoneView(config) {
@@ -5636,9 +5743,7 @@ var KintoneViewConfigurator = class {
5636
5743
  revision: response.revision
5637
5744
  };
5638
5745
  } catch (error) {
5639
- if (isBusinessRuleError(error)) throw error;
5640
- if (error instanceof SystemError) throw error;
5641
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get views", error);
5746
+ throw wrapKintoneError(error, "Failed to get views");
5642
5747
  }
5643
5748
  }
5644
5749
  async updateViews(params) {
@@ -5652,9 +5757,7 @@ var KintoneViewConfigurator = class {
5652
5757
  if (params.revision !== void 0) requestParams.revision = params.revision;
5653
5758
  return { revision: (await this.client.app.updateViews(requestParams)).revision };
5654
5759
  } catch (error) {
5655
- if (isBusinessRuleError(error)) throw error;
5656
- if (error instanceof SystemError) throw error;
5657
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update views", error);
5760
+ throw wrapKintoneError(error, "Failed to update views");
5658
5761
  }
5659
5762
  }
5660
5763
  };
@@ -5770,7 +5873,7 @@ var KintoneSpaceReader = class {
5770
5873
  this.client = client;
5771
5874
  }
5772
5875
  async getSpaceApps(spaceId) {
5773
- if (!spaceId) throw new SystemError(SystemErrorCode.ExternalApiError, "spaceId must not be empty");
5876
+ if (!spaceId) throw new ValidationError(ValidationErrorCode.InvalidInput, "spaceId must not be empty");
5774
5877
  try {
5775
5878
  const rawApps = (await this.client.space.getSpace({ id: spaceId })).attachedApps;
5776
5879
  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.`);
@@ -5789,9 +5892,7 @@ var KintoneSpaceReader = class {
5789
5892
  }
5790
5893
  return result;
5791
5894
  } catch (error) {
5792
- if (isBusinessRuleError(error)) throw error;
5793
- if (error instanceof SystemError) throw error;
5794
- throw new SystemError(SystemErrorCode.ExternalApiError, `Failed to get space info for space ID: ${spaceId}`, error);
5895
+ throw wrapKintoneError(error, `Failed to get space info for space ID: ${spaceId}`);
5795
5896
  }
5796
5897
  }
5797
5898
  };
@@ -6638,7 +6739,7 @@ var init_default = define({
6638
6739
  try {
6639
6740
  const values = ctx.values;
6640
6741
  const spaceId = values["space-id"];
6641
- if (!/^\d+$/.test(spaceId)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Invalid space ID: "${spaceId}" (must be a numeric value)`);
6742
+ if (!/^[1-9]\d*$/.test(spaceId)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Invalid space ID: "${spaceId}" (must be a positive integer, e.g. 1, 42, 100)`);
6642
6743
  const kintoneDomain = values.domain ?? process.env.KINTONE_DOMAIN;
6643
6744
  if (!kintoneDomain) throw new ValidationError(ValidationErrorCode.InvalidInput, "Missing required configuration:\n KINTONE_DOMAIN is required");
6644
6745
  const apiToken = values["api-token"] ?? process.env.KINTONE_API_TOKEN;
@@ -6941,12 +7042,13 @@ var apply_default$7 = define({
6941
7042
  },
6942
7043
  multiApp: async (plan, projectConfig) => {
6943
7044
  const containers = [];
6944
- await runMultiAppWithFailCheck(plan, async (app) => {
6945
- const config = resolveNotificationAppContainerConfig(app, projectConfig, values);
6946
- printAppHeader(app.name, app.appId);
6947
- const container = await runNotification(config);
6948
- containers.push(container);
6949
- }, void 0);
7045
+ await runMultiAppWithHeaders(plan, async (app) => {
7046
+ const container = await runNotification(resolveNotificationAppContainerConfig(app, projectConfig, values));
7047
+ containers.push({
7048
+ appDeployer: container.appDeployer,
7049
+ appName: app.name
7050
+ });
7051
+ });
6950
7052
  await confirmAndDeploy(containers, skipConfirm);
6951
7053
  }
6952
7054
  });
@@ -7360,12 +7462,13 @@ var apply_default$6 = define({
7360
7462
  },
7361
7463
  multiApp: async (plan, projectConfig) => {
7362
7464
  const containers = [];
7363
- await runMultiAppWithFailCheck(plan, async (app) => {
7364
- const config = resolvePluginAppContainerConfig(app, projectConfig, values);
7365
- printAppHeader(app.name, app.appId);
7366
- const container = await runPlugin(config);
7367
- containers.push(container);
7368
- }, void 0);
7465
+ await runMultiAppWithHeaders(plan, async (app) => {
7466
+ const container = await runPlugin(resolvePluginAppContainerConfig(app, projectConfig, values));
7467
+ containers.push({
7468
+ appDeployer: container.appDeployer,
7469
+ appName: app.name
7470
+ });
7471
+ });
7369
7472
  await confirmAndDeploy(containers, skipConfirm);
7370
7473
  }
7371
7474
  });
@@ -7668,12 +7771,13 @@ var apply_default$5 = define({
7668
7771
  },
7669
7772
  multiApp: async (plan, projectConfig) => {
7670
7773
  const containers = [];
7671
- await runMultiAppWithFailCheck(plan, async (app) => {
7672
- const config = resolveProcessAppContainerConfig(app, projectConfig, values);
7673
- printAppHeader(app.name, app.appId);
7674
- const container = await runProcessApply(config);
7675
- containers.push(container);
7676
- }, void 0);
7774
+ await runMultiAppWithHeaders(plan, async (app) => {
7775
+ const container = await runProcessApply(resolveProcessAppContainerConfig(app, projectConfig, values));
7776
+ containers.push({
7777
+ appDeployer: container.appDeployer,
7778
+ appName: app.name
7779
+ });
7780
+ });
7677
7781
  await confirmAndDeploy(containers, skipConfirm);
7678
7782
  }
7679
7783
  });
@@ -7991,12 +8095,13 @@ var apply_default$4 = define({
7991
8095
  },
7992
8096
  multiApp: async (plan, projectConfig) => {
7993
8097
  const containers = [];
7994
- await runMultiAppWithFailCheck(plan, async (app) => {
7995
- const config = resolveRecordAclAppContainerConfig(app, projectConfig, values);
7996
- printAppHeader(app.name, app.appId);
7997
- const container = await runRecordAcl(config);
7998
- containers.push(container);
7999
- }, void 0);
8098
+ await runMultiAppWithHeaders(plan, async (app) => {
8099
+ const container = await runRecordAcl(resolveRecordAclAppContainerConfig(app, projectConfig, values));
8100
+ containers.push({
8101
+ appDeployer: container.appDeployer,
8102
+ appName: app.name
8103
+ });
8104
+ });
8000
8105
  await confirmAndDeploy(containers, skipConfirm);
8001
8106
  }
8002
8107
  });
@@ -8377,12 +8482,13 @@ var apply_default$3 = define({
8377
8482
  },
8378
8483
  multiApp: async (plan, projectConfig) => {
8379
8484
  const containers = [];
8380
- await runMultiAppWithFailCheck(plan, async (app) => {
8381
- const config = resolveReportAppContainerConfig(app, projectConfig, values);
8382
- printAppHeader(app.name, app.appId);
8383
- const container = await runReport(config);
8384
- containers.push(container);
8385
- }, void 0);
8485
+ await runMultiAppWithHeaders(plan, async (app) => {
8486
+ const container = await runReport(resolveReportAppContainerConfig(app, projectConfig, values));
8487
+ containers.push({
8488
+ appDeployer: container.appDeployer,
8489
+ appName: app.name
8490
+ });
8491
+ });
8386
8492
  await confirmAndDeploy(containers, skipConfirm);
8387
8493
  }
8388
8494
  });
@@ -9167,7 +9273,7 @@ async function detectDiff({ container }) {
9167
9273
  //#region src/cli/commands/schema/diff.ts
9168
9274
  async function runDiff(container) {
9169
9275
  const s = p.spinner();
9170
- s.start("Fetching form schema...");
9276
+ s.start("Comparing schema...");
9171
9277
  let result;
9172
9278
  try {
9173
9279
  result = await detectDiff({ container });
@@ -9175,7 +9281,7 @@ async function runDiff(container) {
9175
9281
  s.stop("Comparison failed.");
9176
9282
  throw error;
9177
9283
  }
9178
- s.stop("Form schema fetched.");
9284
+ s.stop("Comparison complete.");
9179
9285
  printDiffResult(result);
9180
9286
  }
9181
9287
  var diff_default$2 = define({
@@ -9217,15 +9323,19 @@ var KintoneFormDumpReader = class {
9217
9323
  }
9218
9324
  async getRawFormData() {
9219
9325
  try {
9220
- const [fields, layout] = await Promise.all([this.client.app.getFormFields({ app: this.appId }), this.client.app.getFormLayout({ app: this.appId })]);
9326
+ const [fields, layout] = await Promise.all([this.client.app.getFormFields({
9327
+ app: this.appId,
9328
+ preview: true
9329
+ }), this.client.app.getFormLayout({
9330
+ app: this.appId,
9331
+ preview: true
9332
+ })]);
9221
9333
  return {
9222
9334
  fields,
9223
9335
  layout
9224
9336
  };
9225
9337
  } catch (error) {
9226
- if (isBusinessRuleError(error)) throw error;
9227
- if (error instanceof SystemError) throw error;
9228
- throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to fetch raw form data for dump", error);
9338
+ throw wrapKintoneError(error, "Failed to fetch raw form data for dump");
9229
9339
  }
9230
9340
  }
9231
9341
  };
@@ -9233,11 +9343,16 @@ var KintoneFormDumpReader = class {
9233
9343
  //#endregion
9234
9344
  //#region src/core/adapters/local/dumpStorage.ts
9235
9345
  var LocalFileDumpStorage = class {
9236
- constructor(filePrefix) {
9346
+ constructor(filePrefix, baseDir = process.cwd()) {
9237
9347
  this.filePrefix = filePrefix;
9348
+ this.baseDir = baseDir;
9349
+ const fieldsPath = resolve(this.baseDir, `${this.filePrefix}fields.json`);
9350
+ if (!isSafePath(fieldsPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${fieldsPath}" escapes base directory "${this.baseDir}"`);
9351
+ const layoutPath = resolve(this.baseDir, `${this.filePrefix}layout.json`);
9352
+ if (!isSafePath(layoutPath, this.baseDir)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Path traversal detected: "${layoutPath}" escapes base directory "${this.baseDir}"`);
9238
9353
  }
9239
9354
  async saveFields(content) {
9240
- const filePath = `${this.filePrefix}fields.json`;
9355
+ const filePath = resolve(this.baseDir, `${this.filePrefix}fields.json`);
9241
9356
  try {
9242
9357
  await mkdir(dirname(filePath), { recursive: true });
9243
9358
  await writeFile(filePath, content, "utf-8");
@@ -9246,7 +9361,7 @@ var LocalFileDumpStorage = class {
9246
9361
  }
9247
9362
  }
9248
9363
  async saveLayout(content) {
9249
- const filePath = `${this.filePrefix}layout.json`;
9364
+ const filePath = resolve(this.baseDir, `${this.filePrefix}layout.json`);
9250
9365
  try {
9251
9366
  await mkdir(dirname(filePath), { recursive: true });
9252
9367
  await writeFile(filePath, content, "utf-8");
@@ -9261,7 +9376,7 @@ var LocalFileDumpStorage = class {
9261
9376
  function createDumpCliContainer(config) {
9262
9377
  return {
9263
9378
  formDumpReader: new KintoneFormDumpReader(config.client ?? createKintoneClient(config), config.appId),
9264
- dumpStorage: new LocalFileDumpStorage(config.filePrefix)
9379
+ dumpStorage: new LocalFileDumpStorage(config.filePrefix, process.cwd())
9265
9380
  };
9266
9381
  }
9267
9382
 
@@ -9332,16 +9447,25 @@ var dump_default = define({
9332
9447
  }
9333
9448
  });
9334
9449
 
9450
+ //#endregion
9451
+ //#region src/core/application/formSchema/deployApp.ts
9452
+ async function deployApp({ container }) {
9453
+ await container.appDeployer.deploy();
9454
+ }
9455
+
9335
9456
  //#endregion
9336
9457
  //#region src/core/domain/formSchema/services/subtableFieldSplitter.ts
9337
9458
  function splitSubtableInnerFields(desired, current) {
9338
9459
  const newInnerFields = /* @__PURE__ */ new Map();
9339
9460
  const existingInnerFields = /* @__PURE__ */ new Map();
9461
+ const deletedInnerFieldCodes = [];
9340
9462
  for (const [code, def] of desired.properties.fields) if (current.properties.fields.has(code)) existingInnerFields.set(code, def);
9341
9463
  else newInnerFields.set(code, def);
9464
+ for (const code of current.properties.fields.keys()) if (!desired.properties.fields.has(code)) deletedInnerFieldCodes.push(code);
9342
9465
  return {
9343
9466
  newInnerFields,
9344
- existingInnerFields
9467
+ existingInnerFields,
9468
+ deletedInnerFieldCodes
9345
9469
  };
9346
9470
  }
9347
9471
 
@@ -9448,6 +9572,11 @@ function validateReferenceTableRelatedApp(field) {
9448
9572
  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`)];
9449
9573
  return [];
9450
9574
  }
9575
+ function validateSubtableHasInnerFields(field) {
9576
+ if (field.type !== "SUBTABLE") return [];
9577
+ 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`)];
9578
+ return [];
9579
+ }
9451
9580
  const FIELD_VALIDATORS = [
9452
9581
  validateLabelNonEmpty,
9453
9582
  validateSelectionOptions,
@@ -9457,7 +9586,8 @@ const FIELD_VALIDATORS = [
9457
9586
  validateFileThumbnailSize,
9458
9587
  validateReferenceTableSize,
9459
9588
  validateLookupStructure,
9460
- validateReferenceTableRelatedApp
9589
+ validateReferenceTableRelatedApp,
9590
+ validateSubtableHasInnerFields
9461
9591
  ];
9462
9592
  function validateField(field) {
9463
9593
  return FIELD_VALIDATORS.flatMap((validator) => validator(field));
@@ -9506,6 +9636,7 @@ async function executeMigration({ container }) {
9506
9636
  const deleted = diff.entries.filter((e) => e.type === "deleted");
9507
9637
  const fieldsToAdd = [];
9508
9638
  const fieldsToUpdate = [];
9639
+ const innerFieldsToDelete = [];
9509
9640
  for (const entry of added) {
9510
9641
  if (entry.after === void 0) continue;
9511
9642
  if (subtableInnerCodes.has(entry.fieldCode)) continue;
@@ -9517,22 +9648,21 @@ async function executeMigration({ container }) {
9517
9648
  const after = entry.after;
9518
9649
  const before = entry.before;
9519
9650
  if (after.type === "SUBTABLE" && before !== void 0 && before.type === "SUBTABLE") {
9520
- const { newInnerFields, existingInnerFields } = splitSubtableInnerFields(after, before);
9521
- if (newInnerFields.size > 0) fieldsToAdd.push({
9522
- ...after,
9523
- properties: { fields: newInnerFields }
9524
- });
9651
+ const { newInnerFields, existingInnerFields, deletedInnerFieldCodes } = splitSubtableInnerFields(after, before);
9652
+ 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}`);
9525
9653
  if (existingInnerFields.size > 0) fieldsToUpdate.push({
9526
9654
  ...after,
9527
9655
  properties: { fields: existingInnerFields }
9528
9656
  });
9529
- } else fieldsToUpdate.push(after);
9657
+ for (const code of deletedInnerFieldCodes) innerFieldsToDelete.push(code);
9658
+ } 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.`);
9659
+ else fieldsToUpdate.push(after);
9530
9660
  }
9531
9661
  if (fieldsToAdd.length > 0) await container.formConfigurator.addFields(fieldsToAdd);
9532
9662
  if (fieldsToUpdate.length > 0) await container.formConfigurator.updateFields(fieldsToUpdate);
9533
- if (deleted.length > 0) {
9663
+ if (deleted.length > 0 || innerFieldsToDelete.length > 0) {
9534
9664
  const currentSubtableInnerCodes = collectSubtableInnerFieldCodes(currentFields);
9535
- const fieldCodes = deleted.filter((e) => !currentSubtableInnerCodes.has(e.fieldCode)).map((e) => e.fieldCode);
9665
+ const fieldCodes = [...deleted.filter((e) => !currentSubtableInnerCodes.has(e.fieldCode)).map((e) => e.fieldCode), ...innerFieldsToDelete];
9536
9666
  if (fieldCodes.length > 0) await container.formConfigurator.deleteFields(fieldCodes);
9537
9667
  }
9538
9668
  if (hasLayoutChanges) await container.formConfigurator.updateLayout(schema.layout);
@@ -9562,7 +9692,7 @@ async function runSingleMigrate(container, skipConfirm) {
9562
9692
  await executeMigration({ container });
9563
9693
  ms.stop("Migration applied.");
9564
9694
  p.log.success("Migration completed successfully.");
9565
- await promptDeploy(container, skipConfirm);
9695
+ await confirmAndDeploy([container], skipConfirm);
9566
9696
  }
9567
9697
  var migrate_default = define({
9568
9698
  name: "migrate",
@@ -9611,7 +9741,7 @@ var migrate_default = define({
9611
9741
  }
9612
9742
  await runMultiAppWithFailCheck(plan, async (app) => {
9613
9743
  const entry = appContainers.find((a) => a.app.name === app.name);
9614
- if (!entry) return;
9744
+ if (!entry) throw new SystemError(SystemErrorCode.InternalServerError, `App container not found for "${app.name}"`);
9615
9745
  const { container, hasChanges } = entry;
9616
9746
  printAppHeader(app.name, app.appId);
9617
9747
  if (!hasChanges) {
@@ -9647,22 +9777,35 @@ async function forceOverrideForm({ container }) {
9647
9777
  const toAdd = [];
9648
9778
  const toUpdate = [];
9649
9779
  const toDelete = [];
9780
+ const innerFieldsToDelete = [];
9650
9781
  for (const [fieldCode, schemaDef] of schema.fields) {
9651
9782
  if (subtableInnerCodes.has(fieldCode)) continue;
9652
9783
  if (currentFields.has(fieldCode)) if (schemaDef.type === "SUBTABLE") {
9653
9784
  const currentDef = currentFields.get(fieldCode);
9654
9785
  if (currentDef !== void 0 && currentDef.type === "SUBTABLE") {
9655
- const { newInnerFields, existingInnerFields } = splitSubtableInnerFields(schemaDef, currentDef);
9656
- if (newInnerFields.size > 0) toAdd.push({
9657
- ...schemaDef,
9658
- properties: { fields: newInnerFields }
9659
- });
9660
- if (existingInnerFields.size > 0) toUpdate.push({
9661
- ...schemaDef,
9662
- properties: { fields: existingInnerFields }
9663
- });
9786
+ const { newInnerFields, existingInnerFields, deletedInnerFieldCodes } = splitSubtableInnerFields(schemaDef, currentDef);
9787
+ const allInnerFieldsRemoved = existingInnerFields.size === 0 && deletedInnerFieldCodes.length > 0;
9788
+ if (newInnerFields.size > 0 || allInnerFieldsRemoved) {
9789
+ toDelete.push(fieldCode);
9790
+ toAdd.push(schemaDef);
9791
+ } else {
9792
+ toUpdate.push({
9793
+ ...schemaDef,
9794
+ properties: { fields: existingInnerFields }
9795
+ });
9796
+ for (const code of deletedInnerFieldCodes) innerFieldsToDelete.push(code);
9797
+ }
9798
+ } else {
9799
+ toDelete.push(fieldCode);
9800
+ toAdd.push(schemaDef);
9801
+ }
9802
+ } else {
9803
+ const currentDef = currentFields.get(fieldCode);
9804
+ if (currentDef !== void 0 && currentDef.type !== schemaDef.type) {
9805
+ toDelete.push(fieldCode);
9806
+ toAdd.push(schemaDef);
9664
9807
  } else toUpdate.push(schemaDef);
9665
- } else toUpdate.push(schemaDef);
9808
+ }
9666
9809
  else toAdd.push(schemaDef);
9667
9810
  }
9668
9811
  const currentSubtableInnerCodes = collectSubtableInnerFieldCodes(currentFields);
@@ -9670,9 +9813,9 @@ async function forceOverrideForm({ container }) {
9670
9813
  if (currentSubtableInnerCodes.has(fieldCode)) continue;
9671
9814
  if (!schema.fields.has(fieldCode)) toDelete.push(fieldCode);
9672
9815
  }
9816
+ if (toDelete.length > 0 || innerFieldsToDelete.length > 0) await container.formConfigurator.deleteFields([...toDelete, ...innerFieldsToDelete]);
9673
9817
  if (toAdd.length > 0) await container.formConfigurator.addFields(toAdd);
9674
9818
  if (toUpdate.length > 0) await container.formConfigurator.updateFields(toUpdate);
9675
- if (toDelete.length > 0) await container.formConfigurator.deleteFields(toDelete);
9676
9819
  await container.formConfigurator.updateLayout(schema.layout);
9677
9820
  }
9678
9821
 
@@ -9707,7 +9850,7 @@ async function runSingleOverride(container, skipConfirm) {
9707
9850
  await forceOverrideForm({ container });
9708
9851
  s.stop("Force override applied.");
9709
9852
  p.log.success("Force override completed successfully.");
9710
- await promptDeploy(container, skipConfirm);
9853
+ await confirmAndDeploy([container], skipConfirm);
9711
9854
  }
9712
9855
  async function runSingleReset(container, skipConfirm) {
9713
9856
  p.log.warn(`${pc.bold(pc.red("WARNING:"))} This will delete ALL custom fields, resetting the form to empty.`);
@@ -9723,7 +9866,7 @@ async function runSingleReset(container, skipConfirm) {
9723
9866
  await resetForm({ container });
9724
9867
  s.stop("Form reset applied.");
9725
9868
  p.log.success("Reset completed successfully.");
9726
- await promptDeploy(container, skipConfirm);
9869
+ await confirmAndDeploy([container], skipConfirm);
9727
9870
  }
9728
9871
  var override_default = define({
9729
9872
  name: "override",
@@ -10436,12 +10579,13 @@ var apply_default$1 = define({
10436
10579
  },
10437
10580
  multiApp: async (plan, projectConfig) => {
10438
10581
  const containers = [];
10439
- await runMultiAppWithFailCheck(plan, async (app) => {
10440
- const config = resolveSettingsAppContainerConfig(app, projectConfig, values);
10441
- printAppHeader(app.name, app.appId);
10442
- const container = await runSettings(config);
10443
- containers.push(container);
10444
- }, "All general settings applied successfully.");
10582
+ await runMultiAppWithHeaders(plan, async (app) => {
10583
+ const container = await runSettings(resolveSettingsAppContainerConfig(app, projectConfig, values));
10584
+ containers.push({
10585
+ appDeployer: container.appDeployer,
10586
+ appName: app.name
10587
+ });
10588
+ });
10445
10589
  await confirmAndDeploy(containers, skipConfirm);
10446
10590
  }
10447
10591
  });
@@ -10723,12 +10867,13 @@ var apply_default = define({
10723
10867
  },
10724
10868
  multiApp: async (plan, projectConfig) => {
10725
10869
  const containers = [];
10726
- await runMultiAppWithFailCheck(plan, async (app) => {
10727
- const config = resolveViewAppContainerConfig(app, projectConfig, values);
10728
- printAppHeader(app.name, app.appId);
10729
- const container = await runView(config);
10730
- containers.push(container);
10731
- }, void 0);
10870
+ await runMultiAppWithHeaders(plan, async (app) => {
10871
+ const container = await runView(resolveViewAppContainerConfig(app, projectConfig, values));
10872
+ containers.push({
10873
+ appDeployer: container.appDeployer,
10874
+ appName: app.name
10875
+ });
10876
+ });
10732
10877
  await confirmAndDeploy(containers, skipConfirm);
10733
10878
  }
10734
10879
  });