kintone-migrator 0.23.0 → 0.24.1

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
@@ -76,13 +76,15 @@ const FieldPermissionErrorCode = {
76
76
  //#region src/core/domain/formSchema/errorCode.ts
77
77
  const FormSchemaErrorCode = {
78
78
  FsEmptyFieldCode: "FS_EMPTY_FIELD_CODE",
79
+ FsInvalidFieldCode: "FS_INVALID_FIELD_CODE",
79
80
  FsEmptySchemaText: "FS_EMPTY_SCHEMA_TEXT",
80
81
  FsInvalidSchemaFormat: "FS_INVALID_SCHEMA_FORMAT",
81
82
  FsInvalidSchemaStructure: "FS_INVALID_SCHEMA_STRUCTURE",
82
83
  FsDuplicateFieldCode: "FS_DUPLICATE_FIELD_CODE",
83
84
  FsInvalidFieldType: "FS_INVALID_FIELD_TYPE",
84
85
  FsInvalidLayoutStructure: "FS_INVALID_LAYOUT_STRUCTURE",
85
- FsInvalidDecorationElement: "FS_INVALID_DECORATION_ELEMENT"
86
+ FsInvalidDecorationElement: "FS_INVALID_DECORATION_ELEMENT",
87
+ FsEmptyFields: "FS_EMPTY_FIELDS"
86
88
  };
87
89
 
88
90
  //#endregion
@@ -135,6 +137,7 @@ const ProjectConfigErrorCode = {
135
137
  PcEmptyApps: "PC_EMPTY_APPS",
136
138
  PcEmptyAppId: "PC_EMPTY_APP_ID",
137
139
  PcEmptyAppName: "PC_EMPTY_APP_NAME",
140
+ PcInvalidAppName: "PC_INVALID_APP_NAME",
138
141
  PcInvalidConfigStructure: "PC_INVALID_CONFIG_STRUCTURE",
139
142
  PcInvalidAuthConfig: "PC_INVALID_AUTH_CONFIG"
140
143
  };
@@ -321,6 +324,18 @@ function isSystemError(error) {
321
324
  return error instanceof SystemError;
322
325
  }
323
326
 
327
+ //#endregion
328
+ //#region src/lib/typeGuards.ts
329
+ /**
330
+ * Narrows `unknown` to a plain `Record<string, unknown>`.
331
+ * Returns true when the value is a non-null, non-array plain object.
332
+ * Excludes built-in types (Date, RegExp, Map, Set) that are technically
333
+ * objects but should not be treated as string-keyed records.
334
+ */
335
+ function isRecord(value) {
336
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp) && !(value instanceof Map) && !(value instanceof Set);
337
+ }
338
+
324
339
  //#endregion
325
340
  //#region src/core/domain/typeGuards.ts
326
341
  /**
@@ -328,32 +343,25 @@ function isSystemError(error) {
328
343
  * Use these functions instead of `as` casts when working with `unknown` values.
329
344
  */
330
345
  /**
331
- * Narrows `unknown` to `Record<string, unknown>`.
332
- * Returns true when the value is a non-null object (and not an array).
333
- */
334
- function isRecord$1(value) {
335
- return typeof value === "object" && value !== null && !Array.isArray(value);
336
- }
337
- /**
338
346
  * Narrows `unknown` to `{ code: string }`.
339
347
  */
340
348
  function hasCode(value) {
341
- return isRecord$1(value) && typeof value.code === "string";
349
+ return isRecord(value) && typeof value.code === "string";
342
350
  }
343
351
  /**
344
352
  * Narrows `unknown` to `{ value: Record<string, { value: unknown }> }`.
345
353
  * Used when processing kintone subtable rows.
346
354
  */
347
355
  function isKintoneSubtableRow(value) {
348
- if (!isRecord$1(value)) return false;
349
- if (!isRecord$1(value.value)) return false;
350
- return Object.values(value.value).every((cell) => isRecord$1(cell) && "value" in cell);
356
+ if (!isRecord(value)) return false;
357
+ if (!isRecord(value.value)) return false;
358
+ return Object.values(value.value).every((cell) => isRecord(cell) && "value" in cell);
351
359
  }
352
360
  /**
353
361
  * Narrows `unknown` to `{ type?: string }`.
354
362
  */
355
363
  function hasOptionalType(value) {
356
- if (!isRecord$1(value)) return false;
364
+ if (!isRecord(value)) return false;
357
365
  return value.type === void 0 || typeof value.type === "string";
358
366
  }
359
367
 
@@ -369,7 +377,7 @@ const VALID_ENTITY_TYPES$10 = new Set([
369
377
  //#endregion
370
378
  //#region src/core/domain/action/services/configParser.ts
371
379
  function parseDestApp(raw, actionName) {
372
- if (!isRecord$1(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" destApp must be an object`);
380
+ if (!isRecord(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" destApp must be an object`);
373
381
  const obj = raw;
374
382
  const result = {};
375
383
  if (obj.app !== void 0 && obj.app !== null) result.app = String(obj.app);
@@ -377,7 +385,7 @@ function parseDestApp(raw, actionName) {
377
385
  return result;
378
386
  }
379
387
  function parseMapping(raw, index, actionName) {
380
- if (!isRecord$1(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" mapping at index ${index} must be an object`);
388
+ if (!isRecord(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" mapping at index ${index} must be an object`);
381
389
  const obj = raw;
382
390
  if (typeof obj.srcType !== "string" || !VALID_SRC_TYPES.has(obj.srcType)) throw new BusinessRuleError(ActionErrorCode.AcInvalidSrcType, `Action "${actionName}" mapping at index ${index} has invalid srcType: ${String(obj.srcType)}. Must be FIELD or RECORD_URL`);
383
391
  if (typeof obj.destField !== "string" || obj.destField.length === 0) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" mapping at index ${index} must have a non-empty "destField" property`);
@@ -388,7 +396,7 @@ function parseMapping(raw, index, actionName) {
388
396
  };
389
397
  }
390
398
  function parseEntity$4(raw, index, actionName) {
391
- if (!isRecord$1(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" entity at index ${index} must be an object`);
399
+ if (!isRecord(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" entity at index ${index} must be an object`);
392
400
  const obj = raw;
393
401
  if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$10.has(obj.type)) throw new BusinessRuleError(ActionErrorCode.AcInvalidEntityType, `Action "${actionName}" entity at index ${index} has invalid type: ${String(obj.type)}. Must be USER, GROUP, or ORGANIZATION`);
394
402
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" entity at index ${index} must have a non-empty "code" property`);
@@ -398,11 +406,11 @@ function parseEntity$4(raw, index, actionName) {
398
406
  };
399
407
  }
400
408
  function parseActionConfig(raw, actionName) {
401
- if (!isRecord$1(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" must be an object`);
409
+ if (!isRecord(raw)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" must be an object`);
402
410
  const obj = raw;
403
411
  if (actionName.length === 0) throw new BusinessRuleError(ActionErrorCode.AcEmptyActionName, "Action name (key) must not be empty");
404
412
  if (typeof obj.index !== "number") throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" must have a numeric "index" property`);
405
- if (!isRecord$1(obj.destApp)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" must have a "destApp" object`);
413
+ if (!isRecord(obj.destApp)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" must have a "destApp" object`);
406
414
  const destApp = parseDestApp(obj.destApp, actionName);
407
415
  if (!Array.isArray(obj.mappings)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, `Action "${actionName}" must have a "mappings" array`);
408
416
  const mappings = obj.mappings.map((item, i) => parseMapping(item, i, actionName));
@@ -426,9 +434,9 @@ const ActionConfigParser = { parse: (rawText) => {
426
434
  } catch (error) {
427
435
  throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
428
436
  }
429
- if (!isRecord$1(parsed)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, "Config must be a YAML object");
437
+ if (!isRecord(parsed)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, "Config must be a YAML object");
430
438
  const obj = parsed;
431
- if (!isRecord$1(obj.actions)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, "Config must have an \"actions\" object");
439
+ if (!isRecord(obj.actions)) throw new BusinessRuleError(ActionErrorCode.AcInvalidConfigStructure, "Config must have an \"actions\" object");
432
440
  const rawActions = obj.actions;
433
441
  const actions = {};
434
442
  for (const [key, value] of Object.entries(rawActions)) actions[key] = parseActionConfig(value, key);
@@ -786,10 +794,40 @@ var KintoneFileUploader = class {
786
794
  }
787
795
  };
788
796
 
797
+ //#endregion
798
+ //#region src/lib/charValidation.ts
799
+ /** Returns true if the string contains any control characters (0x00–0x1f or 0x7f). */
800
+ function hasControlChars(s) {
801
+ for (let i = 0; i < s.length; i++) {
802
+ const ch = s.charCodeAt(i);
803
+ if (ch <= 31 || ch === 127) return true;
804
+ }
805
+ return false;
806
+ }
807
+ /** Replaces control characters (0x00–0x1f, 0x7f) with escaped `\\xNN` representation for safe display. */
808
+ function sanitizeForDisplay(s) {
809
+ let result = "";
810
+ for (let i = 0; i < s.length; i++) {
811
+ const ch = s.charCodeAt(i);
812
+ if (ch <= 31 || ch === 127) result += `\\x${ch.toString(16).padStart(2, "0")}`;
813
+ else result += s[i];
814
+ }
815
+ return result;
816
+ }
817
+
789
818
  //#endregion
790
819
  //#region src/core/domain/formSchema/valueObject.ts
820
+ function hasInvalidFieldCodeChars(code) {
821
+ if (hasControlChars(code)) return true;
822
+ for (let i = 0; i < code.length; i++) {
823
+ const c = code[i];
824
+ if (c === "/" || c === "\\") return true;
825
+ }
826
+ return false;
827
+ }
791
828
  const FieldCode = { create: (code) => {
792
829
  if (code.length === 0) throw new BusinessRuleError(FormSchemaErrorCode.FsEmptyFieldCode, "Field code cannot be empty");
830
+ if (hasInvalidFieldCodeChars(code)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidFieldCode, `Field code "${sanitizeForDisplay(code)}" contains invalid characters`);
793
831
  return code;
794
832
  } };
795
833
 
@@ -1643,16 +1681,31 @@ async function executeMultiApp(plan, executor) {
1643
1681
 
1644
1682
  //#endregion
1645
1683
  //#region src/core/domain/projectConfig/valueObject.ts
1684
+ const INVALID_APP_NAME_CHARS = new Set([
1685
+ "/",
1686
+ "\\",
1687
+ ":",
1688
+ "*",
1689
+ "?",
1690
+ "\"",
1691
+ "<",
1692
+ ">",
1693
+ "|"
1694
+ ]);
1695
+ function hasInvalidAppNameChars(name) {
1696
+ if (hasControlChars(name)) return true;
1697
+ for (let i = 0; i < name.length; i++) if (INVALID_APP_NAME_CHARS.has(name[i])) return true;
1698
+ return false;
1699
+ }
1646
1700
  const AppName = { create: (name) => {
1647
1701
  if (name.length === 0) throw new BusinessRuleError(ProjectConfigErrorCode.PcEmptyAppName, "App name cannot be empty");
1702
+ if (hasInvalidAppNameChars(name)) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAppName, `App name "${sanitizeForDisplay(name)}" contains invalid characters (path separators or control characters are not allowed)`);
1703
+ if (name === "." || name === "..") throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAppName, `App name "${name}" is not allowed (reserved path component)`);
1648
1704
  return name;
1649
1705
  } };
1650
1706
 
1651
1707
  //#endregion
1652
1708
  //#region src/core/domain/projectConfig/services/configParser.ts
1653
- function isRecord(value) {
1654
- return typeof value === "object" && value !== null && !Array.isArray(value);
1655
- }
1656
1709
  function asOptionalString(value) {
1657
1710
  return typeof value === "string" ? value : void 0;
1658
1711
  }
@@ -1726,19 +1779,21 @@ function parseAuth(raw) {
1726
1779
  if (!raw) return void 0;
1727
1780
  const apiToken = asOptionalString(raw.apiToken);
1728
1781
  if (apiToken !== void 0) {
1729
- if (apiToken.trim().length === 0) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAuthConfig, "apiToken must not be empty");
1782
+ const trimmed = apiToken.trim();
1783
+ if (trimmed.length === 0) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAuthConfig, "apiToken must not be empty");
1730
1784
  return {
1731
1785
  type: "apiToken",
1732
- apiToken
1786
+ apiToken: trimmed
1733
1787
  };
1734
1788
  }
1735
1789
  const username = asOptionalString(raw.username);
1736
1790
  const password = asOptionalString(raw.password);
1737
1791
  if (username !== void 0 && password !== void 0) {
1738
- if (username.trim().length === 0 || password.trim().length === 0) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAuthConfig, "username and password must not be empty");
1792
+ const trimmedUsername = username.trim();
1793
+ if (trimmedUsername.length === 0 || password.length === 0) throw new BusinessRuleError(ProjectConfigErrorCode.PcInvalidAuthConfig, "username and password must not be empty");
1739
1794
  return {
1740
1795
  type: "password",
1741
- username,
1796
+ username: trimmedUsername,
1742
1797
  password
1743
1798
  };
1744
1799
  }
@@ -1799,7 +1854,8 @@ function resolveExecutionOrder(apps) {
1799
1854
  queue.push(...nextBatch);
1800
1855
  }
1801
1856
  if (orderedNames.length !== apps.size) {
1802
- const cycleNodes = [...apps.keys()].filter((name) => !orderedNames.includes(name));
1857
+ const orderedSet = new Set(orderedNames);
1858
+ const cycleNodes = [...apps.keys()].filter((name) => !orderedSet.has(name));
1803
1859
  throw new BusinessRuleError(ProjectConfigErrorCode.PcCircularDependency, `Circular dependency detected among: ${cycleNodes.join(", ")}`);
1804
1860
  }
1805
1861
  return { orderedApps: orderedNames.flatMap((name) => {
@@ -1964,42 +2020,73 @@ function colorizeDiffEntry(type) {
1964
2020
  prefix: type === "added" ? "+" : type === "deleted" ? "-" : "~"
1965
2021
  };
1966
2022
  }
1967
- function printDiffResult(result) {
2023
+ function printGenericDiffResult(result, title, formatEntry) {
2024
+ for (const w of result.warnings) p.log.warn(w);
1968
2025
  if (result.isEmpty) {
1969
2026
  p.log.info("No changes detected.");
1970
2027
  return;
1971
2028
  }
1972
2029
  p.log.info(`Changes: ${formatDiffSummary(result.summary)}`);
1973
- if (result.hasLayoutChanges) p.log.info("Layout changes detected.");
1974
2030
  const lines = result.entries.map((entry) => {
1975
2031
  const { colorize, prefix } = colorizeDiffEntry(entry.type);
1976
- return `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]")} ${entry.fieldLabel}${pc.dim(":")} ${entry.details}`;
2032
+ return formatEntry(entry, colorize, prefix);
1977
2033
  });
1978
- p.note(lines.join("\n"), "Diff Details", { format: (v) => v });
2034
+ p.note(lines.join("\n"), title, { format: (v) => v });
1979
2035
  }
1980
- function printViewDiffResult(result) {
1981
- if (result.isEmpty) {
1982
- p.log.info("No changes detected.");
1983
- return;
1984
- }
1985
- p.log.info(`Changes: ${formatDiffSummary(result.summary)}`);
1986
- const lines = result.entries.map((entry) => {
1987
- const { colorize, prefix } = colorizeDiffEntry(entry.type);
1988
- return `${colorize(prefix)} ${colorize(entry.viewName)}${pc.dim(":")} ${entry.details}`;
2036
+ function printActionDiffResult(result) {
2037
+ printGenericDiffResult(result, "Action Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.actionName)}${pc.dim(":")} ${entry.details}`);
2038
+ }
2039
+ function printAdminNotesDiffResult(result) {
2040
+ printFieldDiffResult(result, "Admin Notes Diff Details");
2041
+ }
2042
+ function printAppPermissionDiffResult(result) {
2043
+ printGenericDiffResult(result, "App Permission Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.entityKey)}${pc.dim(":")} ${entry.details}`);
2044
+ }
2045
+ function printCustomizationDiffResult(result) {
2046
+ printGenericDiffResult(result, "Customization Diff Details", (entry, colorize, prefix) => {
2047
+ const location = entry.platform === "config" ? entry.category : `${entry.platform}.${entry.category}`;
2048
+ return `${colorize(prefix)} ${pc.dim("[")}${colorize(location)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`;
1989
2049
  });
1990
- p.note(lines.join("\n"), "View Diff Details", { format: (v) => v });
1991
2050
  }
1992
- function printProcessDiffResult(result) {
2051
+ function printDiffResult(result) {
1993
2052
  if (result.isEmpty) {
1994
2053
  p.log.info("No changes detected.");
1995
2054
  return;
1996
2055
  }
1997
2056
  p.log.info(`Changes: ${formatDiffSummary(result.summary)}`);
2057
+ if (result.hasLayoutChanges) p.log.info("Layout changes detected.");
1998
2058
  const lines = result.entries.map((entry) => {
1999
2059
  const { colorize, prefix } = colorizeDiffEntry(entry.type);
2000
- return `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.category)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`;
2060
+ return `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]")} ${entry.fieldLabel}${pc.dim(":")} ${entry.details}`;
2001
2061
  });
2002
- p.note(lines.join("\n"), "Process Management Diff Details", { format: (v) => v });
2062
+ p.note(lines.join("\n"), "Diff Details", { format: (v) => v });
2063
+ }
2064
+ function printFieldPermissionDiffResult(result) {
2065
+ printGenericDiffResult(result, "Field Permission Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]:")} ${entry.details}`);
2066
+ }
2067
+ function printGeneralSettingsDiffResult(result) {
2068
+ printFieldDiffResult(result, "General Settings Diff Details");
2069
+ }
2070
+ function printNotificationDiffResult(result) {
2071
+ printGenericDiffResult(result, "Notification Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.section)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`);
2072
+ }
2073
+ function printPluginDiffResult(result) {
2074
+ printGenericDiffResult(result, "Plugin Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.pluginId)}${pc.dim(":")} ${entry.details}`);
2075
+ }
2076
+ function printProcessDiffResult(result) {
2077
+ printGenericDiffResult(result, "Process Management Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.category)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`);
2078
+ }
2079
+ function printRecordPermissionDiffResult(result) {
2080
+ printGenericDiffResult(result, "Record Permission Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.filterCond === "" ? "(all records)" : entry.filterCond)}${pc.dim("]:")} ${entry.details}`);
2081
+ }
2082
+ function printReportDiffResult(result) {
2083
+ printGenericDiffResult(result, "Report Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.reportName)}${pc.dim(":")} ${entry.details}`);
2084
+ }
2085
+ function printViewDiffResult(result) {
2086
+ printGenericDiffResult(result, "View Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.viewName)}${pc.dim(":")} ${entry.details}`);
2087
+ }
2088
+ function printFieldDiffResult(result, title) {
2089
+ printGenericDiffResult(result, title, (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.field)}${pc.dim(":")} ${entry.details}`);
2003
2090
  }
2004
2091
  function printAppHeader(appName, appId) {
2005
2092
  p.log.step(`\n=== [${pc.bold(appName)}] (app: ${appId}) ===`);
@@ -2051,8 +2138,13 @@ async function promptDeploy(container, skipConfirm) {
2051
2138
  }
2052
2139
  const ds = p.spinner();
2053
2140
  ds.start("Deploying to production...");
2054
- await deployApp({ container });
2055
- ds.stop("Deployment complete.");
2141
+ try {
2142
+ await deployApp({ container });
2143
+ ds.stop("Deployment complete.");
2144
+ } catch (error) {
2145
+ ds.stop("Deployment failed.");
2146
+ throw error;
2147
+ }
2056
2148
  p.log.success("Deployed to production.");
2057
2149
  }
2058
2150
 
@@ -2321,7 +2413,7 @@ function serializeMapping(mapping) {
2321
2413
  if (mapping.srcField !== void 0) result.srcField = mapping.srcField;
2322
2414
  return result;
2323
2415
  }
2324
- function serializeEntity$1(entity) {
2416
+ function serializeEntity$2(entity) {
2325
2417
  return {
2326
2418
  type: entity.type,
2327
2419
  code: entity.code
@@ -2332,7 +2424,7 @@ function serializeActionConfig(config) {
2332
2424
  index: config.index,
2333
2425
  destApp: serializeDestApp(config.destApp),
2334
2426
  mappings: config.mappings.map(serializeMapping),
2335
- entities: config.entities.map(serializeEntity$1),
2427
+ entities: config.entities.map(serializeEntity$2),
2336
2428
  filterCond: config.filterCond
2337
2429
  };
2338
2430
  }
@@ -2407,6 +2499,238 @@ var capture_default$13 = define({
2407
2499
  }
2408
2500
  });
2409
2501
 
2502
+ //#endregion
2503
+ //#region src/lib/deepEqual.ts
2504
+ function isArrayEqual(a, b, seen) {
2505
+ if (!Array.isArray(b)) return false;
2506
+ if (a.length !== b.length) return false;
2507
+ for (let i = 0; i < a.length; i++) if (!deepEqualInner(a[i], b[i], seen)) return false;
2508
+ return true;
2509
+ }
2510
+ function isRecordEqual(a, b, seen) {
2511
+ const keysA = Object.keys(a);
2512
+ const keysB = Object.keys(b);
2513
+ if (keysA.length !== keysB.length) return false;
2514
+ for (const key of keysA) {
2515
+ if (!Object.hasOwn(b, key)) return false;
2516
+ if (!deepEqualInner(a[key], b[key], seen)) return false;
2517
+ }
2518
+ return true;
2519
+ }
2520
+ function isMapEqual$1(a, b, seen) {
2521
+ if (!(b instanceof Map)) return false;
2522
+ if (a.size !== b.size) return false;
2523
+ for (const [key, valA] of a) {
2524
+ if (!b.has(key)) return false;
2525
+ if (!deepEqualInner(valA, b.get(key), seen)) return false;
2526
+ }
2527
+ return true;
2528
+ }
2529
+ function isSetEqual(a, b, _seen) {
2530
+ if (!(b instanceof Set)) return false;
2531
+ if (a.size !== b.size) return false;
2532
+ const remaining = [...b];
2533
+ for (const valA of a) {
2534
+ const idx = remaining.findIndex((valB) => deepEqualInner(valA, valB, /* @__PURE__ */ new WeakSet()));
2535
+ if (idx === -1) return false;
2536
+ remaining.splice(idx, 1);
2537
+ }
2538
+ return true;
2539
+ }
2540
+ function deepEqualInner(a, b, seen) {
2541
+ if (a === b) return true;
2542
+ if (a === null || b === null) return a === b;
2543
+ if (typeof a !== typeof b) return false;
2544
+ if (typeof a !== "object") return false;
2545
+ const objA = a;
2546
+ const objB = b;
2547
+ if (seen.has(objA) || seen.has(objB)) return false;
2548
+ seen.add(objA);
2549
+ seen.add(objB);
2550
+ if (Array.isArray(objA)) return isArrayEqual(objA, objB, seen);
2551
+ if (objA instanceof Date && objB instanceof Date) return objA.getTime() === objB.getTime();
2552
+ if (objA instanceof Date || objB instanceof Date) return false;
2553
+ if (objA instanceof RegExp && objB instanceof RegExp) return String(objA) === String(objB);
2554
+ if (objA instanceof RegExp || objB instanceof RegExp) return false;
2555
+ if (objA instanceof Map) return isMapEqual$1(objA, objB, seen);
2556
+ if (objB instanceof Map) return false;
2557
+ if (objA instanceof Set) return isSetEqual(objA, objB, seen);
2558
+ if (objB instanceof Set) return false;
2559
+ if (isRecord(objA) && isRecord(objB)) return isRecordEqual(objA, objB, seen);
2560
+ return false;
2561
+ }
2562
+ /**
2563
+ * Deep equality comparison for structured data.
2564
+ * Supports primitives, plain objects, arrays, Date, RegExp, Map, and Set.
2565
+ * Has circular reference protection via a WeakSet-based seen check (tracks both sides).
2566
+ *
2567
+ * Note: `{ a: undefined }` and `{}` are NOT considered equal. This differs from
2568
+ * JSON.stringify behavior but is intentional — explicit undefined properties are
2569
+ * semantically distinct from absent properties.
2570
+ *
2571
+ * Set comparison is order-independent: each element in one set is matched to an
2572
+ * element in the other using deep equality (O(n²)).
2573
+ *
2574
+ * NaN handling: `deepEqual(NaN, NaN)` returns `false` because the comparison
2575
+ * uses strict equality (`===`) for primitives, and `NaN !== NaN` in JavaScript.
2576
+ *
2577
+ * Circular reference limitation: objects already visited by the traversal are
2578
+ * treated as non-equal. This means structurally identical circular references
2579
+ * (e.g. `a.self = a` vs `b.self = b`) return `false`. This is a conservative
2580
+ * approach that prevents infinite recursion but may produce false negatives.
2581
+ */
2582
+ function deepEqual(a, b) {
2583
+ return deepEqualInner(a, b, /* @__PURE__ */ new WeakSet());
2584
+ }
2585
+
2586
+ //#endregion
2587
+ //#region src/core/domain/diff.ts
2588
+ const typeOrder = {
2589
+ added: 0,
2590
+ modified: 1,
2591
+ deleted: 2
2592
+ };
2593
+ function buildDiffResult(entries, warnings = []) {
2594
+ const sorted = [...entries].sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);
2595
+ let added = 0;
2596
+ let modified = 0;
2597
+ let deleted = 0;
2598
+ for (const e of sorted) if (e.type === "added") added++;
2599
+ else if (e.type === "modified") modified++;
2600
+ else deleted++;
2601
+ return {
2602
+ entries: sorted,
2603
+ summary: {
2604
+ added,
2605
+ modified,
2606
+ deleted,
2607
+ total: sorted.length
2608
+ },
2609
+ isEmpty: sorted.length === 0,
2610
+ warnings
2611
+ };
2612
+ }
2613
+
2614
+ //#endregion
2615
+ //#region src/core/domain/action/services/diffDetector.ts
2616
+ function compareActions$1(local, remote) {
2617
+ const diffs = [];
2618
+ if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
2619
+ if (local.name !== remote.name) diffs.push(`name: "${remote.name}" -> "${local.name}"`);
2620
+ if (!deepEqual(local.destApp, remote.destApp)) diffs.push("destApp changed");
2621
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
2622
+ if (!deepEqual(local.mappings, remote.mappings)) if (local.mappings.length !== remote.mappings.length) diffs.push(`mappings: ${remote.mappings.length} -> ${local.mappings.length}`);
2623
+ else diffs.push("mappings changed");
2624
+ if (!deepEqual(local.entities, remote.entities)) diffs.push("entities changed");
2625
+ return diffs;
2626
+ }
2627
+ const ActionDiffDetector = { detect: (local, remote) => {
2628
+ const entries = [];
2629
+ for (const [name, localAction] of Object.entries(local.actions)) {
2630
+ const remoteAction = remote.actions[name];
2631
+ if (!remoteAction) entries.push({
2632
+ type: "added",
2633
+ actionName: name,
2634
+ details: `dest: ${localAction.destApp.app ?? localAction.destApp.code ?? "(unspecified)"}`
2635
+ });
2636
+ else {
2637
+ const diffs = compareActions$1(localAction, remoteAction);
2638
+ if (diffs.length > 0) entries.push({
2639
+ type: "modified",
2640
+ actionName: name,
2641
+ details: diffs.join(", ")
2642
+ });
2643
+ }
2644
+ }
2645
+ for (const [name, remoteAction] of Object.entries(remote.actions)) if (!local.actions[name]) entries.push({
2646
+ type: "deleted",
2647
+ actionName: name,
2648
+ details: `dest: ${remoteAction.destApp.app ?? remoteAction.destApp.code ?? "(unspecified)"}`
2649
+ });
2650
+ return buildDiffResult(entries);
2651
+ } };
2652
+
2653
+ //#endregion
2654
+ //#region src/core/application/detectDiffBase.ts
2655
+ async function detectDiffFromConfig(config) {
2656
+ const [storageResult, remote] = await Promise.all([config.getStorage(), config.fetchRemote()]);
2657
+ if (!storageResult.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, config.notFoundMessage);
2658
+ const local = config.parseConfig(storageResult.content);
2659
+ return config.detect(local, remote);
2660
+ }
2661
+
2662
+ //#endregion
2663
+ //#region src/core/application/action/detectActionDiff.ts
2664
+ async function detectActionDiff({ container }) {
2665
+ return detectDiffFromConfig({
2666
+ getStorage: () => container.actionStorage.get(),
2667
+ fetchRemote: () => container.actionConfigurator.getActions(),
2668
+ parseConfig: parseActionConfigText,
2669
+ detect: (local, remote) => ActionDiffDetector.detect(local, { actions: remote.actions }),
2670
+ notFoundMessage: "Action config file not found"
2671
+ });
2672
+ }
2673
+
2674
+ //#endregion
2675
+ //#region src/cli/commands/diffCommandFactory.ts
2676
+ function createDiffCommand(config) {
2677
+ async function runDiff(containerConfig) {
2678
+ const container = config.createContainer(containerConfig);
2679
+ const s = p.spinner();
2680
+ s.start(config.spinnerMessage);
2681
+ let result;
2682
+ try {
2683
+ result = await config.detectDiff({ container });
2684
+ } catch (error) {
2685
+ s.stop("Comparison failed.");
2686
+ throw error;
2687
+ }
2688
+ s.stop("Comparison complete.");
2689
+ config.printResult(result);
2690
+ }
2691
+ return define({
2692
+ name: "diff",
2693
+ description: config.description,
2694
+ args: config.args,
2695
+ run: async (ctx) => {
2696
+ try {
2697
+ const values = ctx.values;
2698
+ await routeMultiApp(values, {
2699
+ singleLegacy: async () => {
2700
+ await runDiff(config.resolveContainerConfig(values));
2701
+ },
2702
+ singleApp: async (app, projectConfig) => {
2703
+ await runDiff(config.resolveAppContainerConfig(app, projectConfig, values));
2704
+ },
2705
+ multiApp: async (plan, projectConfig) => {
2706
+ await runMultiAppWithFailCheck(plan, async (app) => {
2707
+ const containerConfig = config.resolveAppContainerConfig(app, projectConfig, values);
2708
+ printAppHeader(app.name, app.appId);
2709
+ await runDiff(containerConfig);
2710
+ }, config.multiAppSuccessMessage);
2711
+ }
2712
+ });
2713
+ } catch (error) {
2714
+ handleCliError(error);
2715
+ }
2716
+ }
2717
+ });
2718
+ }
2719
+
2720
+ //#endregion
2721
+ //#region src/cli/commands/action/diff.ts
2722
+ var diff_default$12 = createDiffCommand({
2723
+ description: "Compare local action config with remote kintone app",
2724
+ args: actionArgs,
2725
+ spinnerMessage: "Comparing action settings...",
2726
+ multiAppSuccessMessage: "All action diffs completed successfully.",
2727
+ createContainer: createActionCliContainer,
2728
+ detectDiff: detectActionDiff,
2729
+ printResult: printActionDiffResult,
2730
+ resolveContainerConfig: resolveActionContainerConfig,
2731
+ resolveAppContainerConfig: resolveActionAppContainerConfig
2732
+ });
2733
+
2410
2734
  //#endregion
2411
2735
  //#region src/cli/commands/action/index.ts
2412
2736
  var action_default = define({
@@ -2414,7 +2738,8 @@ var action_default = define({
2414
2738
  description: "Manage kintone action settings",
2415
2739
  subCommands: {
2416
2740
  apply: apply_default$12,
2417
- capture: capture_default$13
2741
+ capture: capture_default$13,
2742
+ diff: diff_default$12
2418
2743
  },
2419
2744
  run: () => {}
2420
2745
  });
@@ -2429,7 +2754,7 @@ const AdminNotesConfigParser = { parse: (rawText) => {
2429
2754
  } catch (error) {
2430
2755
  throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
2431
2756
  }
2432
- if (!isRecord$1(parsed)) throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must be a YAML object");
2757
+ if (!isRecord(parsed)) throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must be a YAML object");
2433
2758
  const obj = parsed;
2434
2759
  if (typeof obj.content !== "string") throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must have a \"content\" string property");
2435
2760
  if (typeof obj.includeInTemplateAndDuplicates !== "boolean") throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must have an \"includeInTemplateAndDuplicates\" boolean property");
@@ -2662,6 +2987,52 @@ var capture_default$12 = define({
2662
2987
  }
2663
2988
  });
2664
2989
 
2990
+ //#endregion
2991
+ //#region src/core/domain/adminNotes/services/diffDetector.ts
2992
+ function compareConfigs$2(local, remote) {
2993
+ const entries = [];
2994
+ if (local.content !== remote.content) entries.push({
2995
+ type: "modified",
2996
+ field: "content",
2997
+ details: "content changed"
2998
+ });
2999
+ if (local.includeInTemplateAndDuplicates !== remote.includeInTemplateAndDuplicates) entries.push({
3000
+ type: "modified",
3001
+ field: "includeInTemplateAndDuplicates",
3002
+ details: `${String(remote.includeInTemplateAndDuplicates)} -> ${String(local.includeInTemplateAndDuplicates)}`
3003
+ });
3004
+ return entries;
3005
+ }
3006
+ const AdminNotesDiffDetector = { detect: (local, remote) => {
3007
+ return buildDiffResult(compareConfigs$2(local, remote));
3008
+ } };
3009
+
3010
+ //#endregion
3011
+ //#region src/core/application/adminNotes/detectAdminNotesDiff.ts
3012
+ async function detectAdminNotesDiff({ container }) {
3013
+ return detectDiffFromConfig({
3014
+ getStorage: () => container.adminNotesStorage.get(),
3015
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
3016
+ parseConfig: parseAdminNotesConfigText,
3017
+ detect: (local, remote) => AdminNotesDiffDetector.detect(local, remote.config),
3018
+ notFoundMessage: "Admin notes config file not found"
3019
+ });
3020
+ }
3021
+
3022
+ //#endregion
3023
+ //#region src/cli/commands/admin-notes/diff.ts
3024
+ var diff_default$11 = createDiffCommand({
3025
+ description: "Compare local admin notes config with remote kintone app",
3026
+ args: adminNotesArgs,
3027
+ spinnerMessage: "Comparing admin notes...",
3028
+ multiAppSuccessMessage: "All admin notes diffs completed successfully.",
3029
+ createContainer: createAdminNotesCliContainer,
3030
+ detectDiff: detectAdminNotesDiff,
3031
+ printResult: printAdminNotesDiffResult,
3032
+ resolveContainerConfig: resolveAdminNotesContainerConfig,
3033
+ resolveAppContainerConfig: resolveAdminNotesAppContainerConfig
3034
+ });
3035
+
2665
3036
  //#endregion
2666
3037
  //#region src/cli/commands/admin-notes/index.ts
2667
3038
  var admin_notes_default = define({
@@ -2669,7 +3040,8 @@ var admin_notes_default = define({
2669
3040
  description: "Manage kintone admin notes",
2670
3041
  subCommands: {
2671
3042
  apply: apply_default$11,
2672
- capture: capture_default$12
3043
+ capture: capture_default$12,
3044
+ diff: diff_default$11
2673
3045
  },
2674
3046
  run: () => {}
2675
3047
  });
@@ -2683,7 +3055,7 @@ const VALID_ENTITY_TYPES$9 = new Set([
2683
3055
  "CREATOR"
2684
3056
  ]);
2685
3057
  function parseEntity$3(raw, index) {
2686
- if (!isRecord$1(raw)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, `Entity at index ${index} must be an object`);
3058
+ if (!isRecord(raw)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, `Entity at index ${index} must be an object`);
2687
3059
  const obj = raw;
2688
3060
  if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$9.has(obj.type)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidEntityType, `Entity at index ${index} has invalid type: ${String(obj.type)}. Must be USER, GROUP, ORGANIZATION, or CREATOR`);
2689
3061
  const type = obj.type;
@@ -2703,7 +3075,7 @@ function parseBooleanField$1(obj, field, index) {
2703
3075
  return value;
2704
3076
  }
2705
3077
  function parseAppRight(raw, index) {
2706
- if (!isRecord$1(raw)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, `App right at index ${index} must be an object`);
3078
+ if (!isRecord(raw)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, `App right at index ${index} must be an object`);
2707
3079
  const obj = raw;
2708
3080
  return {
2709
3081
  entity: parseEntity$3(obj.entity, index),
@@ -2725,7 +3097,7 @@ const AppPermissionConfigParser = { parse: (rawText) => {
2725
3097
  } catch (error) {
2726
3098
  throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
2727
3099
  }
2728
- if (!isRecord$1(parsed)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, "Config must be a YAML object");
3100
+ if (!isRecord(parsed)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, "Config must be a YAML object");
2729
3101
  const obj = parsed;
2730
3102
  if (!Array.isArray(obj.rights)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, "Config must have a \"rights\" array");
2731
3103
  const rights = obj.rights.map((item, i) => parseAppRight(item, i));
@@ -3016,6 +3388,84 @@ var capture_default$11 = define({
3016
3388
  }
3017
3389
  });
3018
3390
 
3391
+ //#endregion
3392
+ //#region src/core/domain/appPermission/services/diffDetector.ts
3393
+ const BOOLEAN_FLAGS = [
3394
+ "includeSubs",
3395
+ "appEditable",
3396
+ "recordViewable",
3397
+ "recordAddable",
3398
+ "recordEditable",
3399
+ "recordDeletable",
3400
+ "recordImportable",
3401
+ "recordExportable"
3402
+ ];
3403
+ function entityKey(right) {
3404
+ return `${right.entity.type}:${right.entity.code}`;
3405
+ }
3406
+ function describeRight$1(right) {
3407
+ const flags = BOOLEAN_FLAGS.filter((f) => f !== "includeSubs" && right[f]);
3408
+ return flags.length > 0 ? flags.join(", ") : "no permissions";
3409
+ }
3410
+ function compareRights(local, remote) {
3411
+ const diffs = [];
3412
+ for (const flag of BOOLEAN_FLAGS) if (local[flag] !== remote[flag]) diffs.push(`${flag}: ${String(remote[flag])} -> ${String(local[flag])}`);
3413
+ return diffs;
3414
+ }
3415
+ const AppPermissionDiffDetector = { detect: (local, remote) => {
3416
+ const entries = [];
3417
+ const localMap = new Map(local.rights.map((r) => [entityKey(r), r]));
3418
+ const remoteMap = new Map(remote.rights.map((r) => [entityKey(r), r]));
3419
+ for (const [key, localRight] of localMap) {
3420
+ const remoteRight = remoteMap.get(key);
3421
+ if (!remoteRight) entries.push({
3422
+ type: "added",
3423
+ entityKey: key,
3424
+ details: describeRight$1(localRight)
3425
+ });
3426
+ else {
3427
+ const diffs = compareRights(localRight, remoteRight);
3428
+ if (diffs.length > 0) entries.push({
3429
+ type: "modified",
3430
+ entityKey: key,
3431
+ details: diffs.join(", ")
3432
+ });
3433
+ }
3434
+ }
3435
+ for (const [key, remoteRight] of remoteMap) if (!localMap.has(key)) entries.push({
3436
+ type: "deleted",
3437
+ entityKey: key,
3438
+ details: describeRight$1(remoteRight)
3439
+ });
3440
+ return buildDiffResult(entries);
3441
+ } };
3442
+
3443
+ //#endregion
3444
+ //#region src/core/application/appPermission/detectAppPermissionDiff.ts
3445
+ async function detectAppPermissionDiff({ container }) {
3446
+ return detectDiffFromConfig({
3447
+ getStorage: () => container.appPermissionStorage.get(),
3448
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3449
+ parseConfig: parseAppPermissionConfigText,
3450
+ detect: (local, remote) => AppPermissionDiffDetector.detect(local, { rights: remote.rights }),
3451
+ notFoundMessage: "App permission config file not found"
3452
+ });
3453
+ }
3454
+
3455
+ //#endregion
3456
+ //#region src/cli/commands/app-acl/diff.ts
3457
+ var diff_default$10 = createDiffCommand({
3458
+ description: "Compare local app permission config with remote kintone app",
3459
+ args: appAclArgs,
3460
+ spinnerMessage: "Comparing app permissions...",
3461
+ multiAppSuccessMessage: "All app permission diffs completed successfully.",
3462
+ createContainer: createAppPermissionCliContainer,
3463
+ detectDiff: detectAppPermissionDiff,
3464
+ printResult: printAppPermissionDiffResult,
3465
+ resolveContainerConfig: resolveAppAclContainerConfig,
3466
+ resolveAppContainerConfig: resolveAppAclAppContainerConfig
3467
+ });
3468
+
3019
3469
  //#endregion
3020
3470
  //#region src/cli/commands/app-acl/index.ts
3021
3471
  var app_acl_default = define({
@@ -3023,7 +3473,8 @@ var app_acl_default = define({
3023
3473
  description: "Manage kintone app access permissions",
3024
3474
  subCommands: {
3025
3475
  apply: apply_default$10,
3026
- capture: capture_default$11
3476
+ capture: capture_default$11,
3477
+ diff: diff_default$10
3027
3478
  },
3028
3479
  run: () => {}
3029
3480
  });
@@ -3065,7 +3516,7 @@ const VALID_SCOPES = new Set([
3065
3516
  ]);
3066
3517
  const VALID_RESOURCE_TYPES = new Set(["FILE", "URL"]);
3067
3518
  function parseResource(raw, index) {
3068
- if (!isRecord$1(raw)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigStructure, `Resource at index ${index} must be an object`);
3519
+ if (!isRecord(raw)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigStructure, `Resource at index ${index} must be an object`);
3069
3520
  const obj = raw;
3070
3521
  const type = obj.type;
3071
3522
  if (typeof type !== "string" || !VALID_RESOURCE_TYPES.has(type)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidResourceType, `Resource at index ${index} has invalid type: ${String(type)}. Must be FILE or URL`);
@@ -3087,7 +3538,7 @@ function parseResourceList(raw) {
3087
3538
  return raw.map((item, index) => parseResource(item, index));
3088
3539
  }
3089
3540
  function parsePlatform(raw) {
3090
- if (!isRecord$1(raw)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigStructure, "Platform configuration must be an object");
3541
+ if (!isRecord(raw)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigStructure, "Platform configuration must be an object");
3091
3542
  const obj = raw;
3092
3543
  return {
3093
3544
  js: obj.js === void 0 || obj.js === null ? [] : parseResourceList(obj.js),
@@ -3102,7 +3553,7 @@ const ConfigParser = { parse: (rawText) => {
3102
3553
  } catch (error) {
3103
3554
  throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
3104
3555
  }
3105
- if (!isRecord$1(parsed)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigStructure, "Config must be a YAML object");
3556
+ if (!isRecord(parsed)) throw new BusinessRuleError(CustomizationErrorCode.CzInvalidConfigStructure, "Config must be a YAML object");
3106
3557
  const obj = parsed;
3107
3558
  let scope;
3108
3559
  if (obj.scope !== void 0 && obj.scope !== null) {
@@ -3499,6 +3950,105 @@ var apply_default$9 = define({
3499
3950
  }
3500
3951
  });
3501
3952
 
3953
+ //#endregion
3954
+ //#region src/core/domain/customization/valueObject.ts
3955
+ const DEFAULT_CUSTOMIZATION_SCOPE = "ALL";
3956
+
3957
+ //#endregion
3958
+ //#region src/core/domain/customization/services/diffDetector.ts
3959
+ function resourceName(resource) {
3960
+ if (resource.type === "URL") return resource.url;
3961
+ const parts = resource.path.replace(/\\/g, "/").split("/");
3962
+ return parts[parts.length - 1];
3963
+ }
3964
+ function remoteResourceName(resource) {
3965
+ if (resource.type === "URL") return resource.url;
3966
+ return resource.file.name;
3967
+ }
3968
+ function compareResourceLists(localResources, remoteResources, platform, resourceType, warnings) {
3969
+ const entries = [];
3970
+ const localNames = localResources.map(resourceName);
3971
+ const remoteNames = remoteResources.map(remoteResourceName);
3972
+ const localNameSet = new Set(localNames);
3973
+ const remoteNameSet = new Set(remoteNames);
3974
+ const hasDuplicates = localNames.length !== localNameSet.size || remoteNames.length !== remoteNameSet.size;
3975
+ if (hasDuplicates) warnings.push(`[${platform}.${resourceType}] duplicate basenames detected; diff results may be inaccurate for FILE resources`);
3976
+ for (const name of localNameSet) if (!remoteNameSet.has(name)) entries.push({
3977
+ type: "added",
3978
+ platform,
3979
+ category: resourceType,
3980
+ name,
3981
+ details: "new resource"
3982
+ });
3983
+ for (const name of remoteNameSet) if (!localNameSet.has(name)) entries.push({
3984
+ type: "deleted",
3985
+ platform,
3986
+ category: resourceType,
3987
+ name,
3988
+ details: "removed"
3989
+ });
3990
+ const matchedFiles = [...localNameSet].filter((n) => remoteNameSet.has(n));
3991
+ const hasLocalFiles = localResources.some((r) => r.type === "FILE");
3992
+ const hasRemoteFiles = remoteResources.some((r) => r.type === "FILE");
3993
+ if (matchedFiles.length > 0 && hasLocalFiles && hasRemoteFiles) warnings.push(`[${platform}.${resourceType}] FILE resources are compared by name only; content changes are not detected`);
3994
+ if (!hasDuplicates) {
3995
+ const localShared = localNames.filter((n) => remoteNameSet.has(n));
3996
+ const remoteShared = remoteNames.filter((n) => localNameSet.has(n));
3997
+ if (localShared.length > 1 && localShared.length === remoteShared.length && localShared.some((n, i) => n !== remoteShared[i])) entries.push({
3998
+ type: "modified",
3999
+ platform,
4000
+ category: resourceType,
4001
+ name: "(order)",
4002
+ details: "resource load order changed"
4003
+ });
4004
+ }
4005
+ return entries;
4006
+ }
4007
+ function comparePlatform(localJs, localCss, remote, platform, warnings) {
4008
+ return [...compareResourceLists(localJs, remote.js, platform, "js", warnings), ...compareResourceLists(localCss, remote.css, platform, "css", warnings)];
4009
+ }
4010
+ const CustomizationDiffDetector = { detect: (local, remote) => {
4011
+ const entries = [];
4012
+ const warnings = [];
4013
+ const localScope = local.scope ?? DEFAULT_CUSTOMIZATION_SCOPE;
4014
+ if (localScope !== remote.scope) entries.push({
4015
+ type: "modified",
4016
+ platform: "config",
4017
+ category: "scope",
4018
+ name: "scope",
4019
+ details: `${remote.scope} -> ${localScope}`
4020
+ });
4021
+ entries.push(...comparePlatform(local.desktop.js, local.desktop.css, remote.desktop, "desktop", warnings));
4022
+ entries.push(...comparePlatform(local.mobile.js, local.mobile.css, remote.mobile, "mobile", warnings));
4023
+ return buildDiffResult(entries, warnings);
4024
+ } };
4025
+
4026
+ //#endregion
4027
+ //#region src/core/application/customization/detectCustomizationDiff.ts
4028
+ async function detectCustomizationDiff({ container }) {
4029
+ return detectDiffFromConfig({
4030
+ getStorage: () => container.customizationStorage.get(),
4031
+ fetchRemote: () => container.customizationConfigurator.getCustomization(),
4032
+ parseConfig: (content) => parseConfigText(content),
4033
+ detect: (local, remote) => CustomizationDiffDetector.detect(local, remote),
4034
+ notFoundMessage: "Customization config file not found"
4035
+ });
4036
+ }
4037
+
4038
+ //#endregion
4039
+ //#region src/cli/commands/customize/diff.ts
4040
+ var diff_default$9 = createDiffCommand({
4041
+ description: "Compare local customization config with remote kintone app",
4042
+ args: customizeArgs,
4043
+ spinnerMessage: "Comparing customization settings...",
4044
+ multiAppSuccessMessage: "All customization diffs completed successfully.",
4045
+ createContainer: createCustomizationCliContainer,
4046
+ detectDiff: detectCustomizationDiff,
4047
+ printResult: printCustomizationDiffResult,
4048
+ resolveContainerConfig: resolveCustomizeConfig,
4049
+ resolveAppContainerConfig: resolveCustomizeAppConfig
4050
+ });
4051
+
3502
4052
  //#endregion
3503
4053
  //#region src/cli/commands/customize/index.ts
3504
4054
  var customize_default = define({
@@ -3506,7 +4056,8 @@ var customize_default = define({
3506
4056
  description: "Manage kintone JS/CSS customizations",
3507
4057
  subCommands: {
3508
4058
  apply: apply_default$9,
3509
- capture: capture_default$10
4059
+ capture: capture_default$10,
4060
+ diff: diff_default$9
3510
4061
  },
3511
4062
  run: () => {}
3512
4063
  });
@@ -3635,7 +4186,7 @@ const VALID_ENTITY_TYPES$6 = new Set([
3635
4186
  "FIELD_ENTITY"
3636
4187
  ]);
3637
4188
  function parseEntity$2(raw, index) {
3638
- if (!isRecord$1(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Entity at index ${index} must be an object`);
4189
+ if (!isRecord(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Entity at index ${index} must be an object`);
3639
4190
  const obj = raw;
3640
4191
  if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$6.has(obj.type)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidEntityType, `Entity at index ${index} has invalid type: ${String(obj.type)}. Must be USER, GROUP, ORGANIZATION, or FIELD_ENTITY`);
3641
4192
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(FieldPermissionErrorCode.FpEmptyEntityCode, `Entity at index ${index} must have a non-empty "code" property`);
@@ -3645,7 +4196,7 @@ function parseEntity$2(raw, index) {
3645
4196
  };
3646
4197
  }
3647
4198
  function parseFieldRightEntity(raw, index) {
3648
- if (!isRecord$1(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right entity at index ${index} must be an object`);
4199
+ if (!isRecord(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right entity at index ${index} must be an object`);
3649
4200
  const obj = raw;
3650
4201
  if (typeof obj.accessibility !== "string" || !VALID_ACCESSIBILITIES.has(obj.accessibility)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidAccessibility, `Field right entity at index ${index} has invalid accessibility: ${String(obj.accessibility)}. Must be READ, WRITE, or NONE`);
3651
4202
  const entity = parseEntity$2(obj.entity, index);
@@ -3660,7 +4211,7 @@ function parseFieldRightEntity(raw, index) {
3660
4211
  return result;
3661
4212
  }
3662
4213
  function parseFieldRight(raw, index) {
3663
- if (!isRecord$1(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right at index ${index} must be an object`);
4214
+ if (!isRecord(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right at index ${index} must be an object`);
3664
4215
  const obj = raw;
3665
4216
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(FieldPermissionErrorCode.FpEmptyFieldCode, `Field right at index ${index} must have a non-empty "code" property`);
3666
4217
  if (!Array.isArray(obj.entities)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right at index ${index} must have an "entities" array`);
@@ -3678,7 +4229,7 @@ const FieldPermissionConfigParser = { parse: (rawText) => {
3678
4229
  } catch (error) {
3679
4230
  throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
3680
4231
  }
3681
- if (!isRecord$1(parsed)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, "Config must be a YAML object");
4232
+ if (!isRecord(parsed)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, "Config must be a YAML object");
3682
4233
  const obj = parsed;
3683
4234
  if (!Array.isArray(obj.rights)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, "Config must have a \"rights\" array");
3684
4235
  const rights = obj.rights.map((item, i) => parseFieldRight(item, i));
@@ -3860,6 +4411,72 @@ var capture_default$9 = define({
3860
4411
  }
3861
4412
  });
3862
4413
 
4414
+ //#endregion
4415
+ //#region src/core/domain/fieldPermission/services/diffDetector.ts
4416
+ function areEntitiesEqual(a, b) {
4417
+ return deepEqual(a.entities.map((e) => ({
4418
+ accessibility: e.accessibility,
4419
+ type: e.entity.type,
4420
+ code: e.entity.code,
4421
+ includeSubs: e.includeSubs ?? false
4422
+ })), b.entities.map((e) => ({
4423
+ accessibility: e.accessibility,
4424
+ type: e.entity.type,
4425
+ code: e.entity.code,
4426
+ includeSubs: e.includeSubs ?? false
4427
+ })));
4428
+ }
4429
+ const FieldPermissionDiffDetector = { detect: (local, remote) => {
4430
+ const entries = [];
4431
+ const localMap = new Map(local.rights.map((r) => [r.code, r]));
4432
+ const remoteMap = new Map(remote.rights.map((r) => [r.code, r]));
4433
+ for (const [code, localRight] of localMap) {
4434
+ const remoteRight = remoteMap.get(code);
4435
+ if (!remoteRight) entries.push({
4436
+ type: "added",
4437
+ fieldCode: code,
4438
+ details: `${localRight.entities.length} entities`
4439
+ });
4440
+ else if (!areEntitiesEqual(localRight, remoteRight)) entries.push({
4441
+ type: "modified",
4442
+ fieldCode: code,
4443
+ details: "entities changed"
4444
+ });
4445
+ }
4446
+ for (const code of remoteMap.keys()) if (!localMap.has(code)) entries.push({
4447
+ type: "deleted",
4448
+ fieldCode: code,
4449
+ details: "removed"
4450
+ });
4451
+ return buildDiffResult(entries);
4452
+ } };
4453
+
4454
+ //#endregion
4455
+ //#region src/core/application/fieldPermission/detectFieldPermissionDiff.ts
4456
+ async function detectFieldPermissionDiff({ container }) {
4457
+ return detectDiffFromConfig({
4458
+ getStorage: () => container.fieldPermissionStorage.get(),
4459
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4460
+ parseConfig: parseFieldPermissionConfigText,
4461
+ detect: (local, remote) => FieldPermissionDiffDetector.detect(local, { rights: remote.rights }),
4462
+ notFoundMessage: "Field permission config file not found"
4463
+ });
4464
+ }
4465
+
4466
+ //#endregion
4467
+ //#region src/cli/commands/field-acl/diff.ts
4468
+ var diff_default$8 = createDiffCommand({
4469
+ description: "Compare local field permission config with remote kintone app",
4470
+ args: fieldAclArgs,
4471
+ spinnerMessage: "Comparing field permissions...",
4472
+ multiAppSuccessMessage: "All field permission diffs completed successfully.",
4473
+ createContainer: createFieldPermissionCliContainer,
4474
+ detectDiff: detectFieldPermissionDiff,
4475
+ printResult: printFieldPermissionDiffResult,
4476
+ resolveContainerConfig: resolveFieldAclContainerConfig,
4477
+ resolveAppContainerConfig: resolveFieldAclAppContainerConfig
4478
+ });
4479
+
3863
4480
  //#endregion
3864
4481
  //#region src/cli/commands/field-acl/index.ts
3865
4482
  var field_acl_default = define({
@@ -3867,7 +4484,8 @@ var field_acl_default = define({
3867
4484
  description: "Manage kintone field access permissions",
3868
4485
  subCommands: {
3869
4486
  apply: apply_default$8,
3870
- capture: capture_default$9
4487
+ capture: capture_default$9,
4488
+ diff: diff_default$8
3871
4489
  },
3872
4490
  run: () => {}
3873
4491
  });
@@ -5322,7 +5940,7 @@ async function saveGeneralSettings({ container, input }) {
5322
5940
 
5323
5941
  //#endregion
5324
5942
  //#region src/core/domain/notification/services/configSerializer.ts
5325
- function serializeEntity(entity) {
5943
+ function serializeEntity$1(entity) {
5326
5944
  return {
5327
5945
  type: entity.type,
5328
5946
  code: entity.code
@@ -5330,7 +5948,7 @@ function serializeEntity(entity) {
5330
5948
  }
5331
5949
  function serializeGeneralNotification(notification) {
5332
5950
  const result = {
5333
- entity: serializeEntity(notification.entity),
5951
+ entity: serializeEntity$1(notification.entity),
5334
5952
  recordAdded: notification.recordAdded,
5335
5953
  recordEdited: notification.recordEdited,
5336
5954
  commentAdded: notification.commentAdded,
@@ -5341,7 +5959,7 @@ function serializeGeneralNotification(notification) {
5341
5959
  return result;
5342
5960
  }
5343
5961
  function serializePerRecordTarget(target) {
5344
- const result = { entity: serializeEntity(target.entity) };
5962
+ const result = { entity: serializeEntity$1(target.entity) };
5345
5963
  if (target.includeSubs !== void 0) result.includeSubs = target.includeSubs;
5346
5964
  return result;
5347
5965
  }
@@ -5353,7 +5971,7 @@ function serializePerRecordNotification(notification) {
5353
5971
  };
5354
5972
  }
5355
5973
  function serializeReminderTarget(target) {
5356
- const result = { entity: serializeEntity(target.entity) };
5974
+ const result = { entity: serializeEntity$1(target.entity) };
5357
5975
  if (target.includeSubs !== void 0) result.includeSubs = target.includeSubs;
5358
5976
  return result;
5359
5977
  }
@@ -5619,11 +6237,7 @@ async function saveReport({ container, input }) {
5619
6237
  //#endregion
5620
6238
  //#region src/core/domain/seedData/services/seedSerializer.ts
5621
6239
  const SeedSerializer = { serialize: (seedData) => {
5622
- const records = seedData.records.map((record) => {
5623
- const plain = {};
5624
- for (const [key, value] of Object.entries(record)) plain[key] = value;
5625
- return plain;
5626
- });
6240
+ const records = seedData.records.map((record) => ({ ...record }));
5627
6241
  return stringify(seedData.key !== null ? {
5628
6242
  key: seedData.key,
5629
6243
  records
@@ -6053,7 +6667,7 @@ const VALID_ENTITY_TYPES$2 = new Set([
6053
6667
  "FIELD_ENTITY"
6054
6668
  ]);
6055
6669
  function parseEntity$1(raw, context) {
6056
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `${context}: entity must be an object`);
6670
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `${context}: entity must be an object`);
6057
6671
  const obj = raw;
6058
6672
  if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$2.has(obj.type)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidEntityType, `${context}: entity has invalid type: ${String(obj.type)}. Must be USER, GROUP, ORGANIZATION, or FIELD_ENTITY`);
6059
6673
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(NotificationErrorCode.NtEmptyEntityCode, `${context}: entity must have a non-empty "code" property`);
@@ -6063,7 +6677,7 @@ function parseEntity$1(raw, context) {
6063
6677
  };
6064
6678
  }
6065
6679
  function parseGeneralNotification(raw, index) {
6066
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `General notification at index ${index} must be an object`);
6680
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `General notification at index ${index} must be an object`);
6067
6681
  const obj = raw;
6068
6682
  const result = {
6069
6683
  entity: parseEntity$1(obj.entity, `General notification at index ${index}`),
@@ -6080,7 +6694,7 @@ function parseGeneralNotification(raw, index) {
6080
6694
  return result;
6081
6695
  }
6082
6696
  function parseGeneralConfig(raw) {
6083
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "\"general\" must be an object");
6697
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "\"general\" must be an object");
6084
6698
  const obj = raw;
6085
6699
  if (!Array.isArray(obj.notifications)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "\"general\" must have a \"notifications\" array");
6086
6700
  const notifications = obj.notifications.map((item, i) => parseGeneralNotification(item, i));
@@ -6090,7 +6704,7 @@ function parseGeneralConfig(raw) {
6090
6704
  };
6091
6705
  }
6092
6706
  function parsePerRecordTarget(raw, index) {
6093
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Per-record target at index ${index} must be an object`);
6707
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Per-record target at index ${index} must be an object`);
6094
6708
  const obj = raw;
6095
6709
  const result = { entity: parseEntity$1(obj.entity, `Per-record target at index ${index}`) };
6096
6710
  if (obj.includeSubs !== void 0 && obj.includeSubs !== null) return {
@@ -6100,7 +6714,7 @@ function parsePerRecordTarget(raw, index) {
6100
6714
  return result;
6101
6715
  }
6102
6716
  function parsePerRecordNotification(raw, index) {
6103
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Per-record notification at index ${index} must be an object`);
6717
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Per-record notification at index ${index} must be an object`);
6104
6718
  const obj = raw;
6105
6719
  if (!Array.isArray(obj.targets)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Per-record notification at index ${index} must have a "targets" array`);
6106
6720
  const targets = obj.targets.map((item, i) => parsePerRecordTarget(item, i));
@@ -6116,7 +6730,7 @@ function parsePerRecordConfig(raw) {
6116
6730
  return raw.map((item, i) => parsePerRecordNotification(item, i));
6117
6731
  }
6118
6732
  function parseReminderTarget(raw, index) {
6119
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Reminder target at index ${index} must be an object`);
6733
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Reminder target at index ${index} must be an object`);
6120
6734
  const obj = raw;
6121
6735
  const result = { entity: parseEntity$1(obj.entity, `Reminder target at index ${index}`) };
6122
6736
  if (obj.includeSubs !== void 0 && obj.includeSubs !== null) return {
@@ -6126,7 +6740,7 @@ function parseReminderTarget(raw, index) {
6126
6740
  return result;
6127
6741
  }
6128
6742
  function parseReminderNotification(raw, index) {
6129
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Reminder notification at index ${index} must be an object`);
6743
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Reminder notification at index ${index} must be an object`);
6130
6744
  const obj = raw;
6131
6745
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Reminder notification at index ${index} must have a non-empty "code" property`);
6132
6746
  if (!Array.isArray(obj.targets)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, `Reminder notification at index ${index} must have a "targets" array`);
@@ -6153,7 +6767,7 @@ function parseReminderNotification(raw, index) {
6153
6767
  return result;
6154
6768
  }
6155
6769
  function parseReminderConfig(raw) {
6156
- if (!isRecord$1(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "\"reminder\" must be an object");
6770
+ if (!isRecord(raw)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "\"reminder\" must be an object");
6157
6771
  const obj = raw;
6158
6772
  if (!Array.isArray(obj.notifications)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "\"reminder\" must have a \"notifications\" array");
6159
6773
  const notifications = obj.notifications.map((item, i) => parseReminderNotification(item, i));
@@ -6170,7 +6784,7 @@ const NotificationConfigParser = { parse: (rawText) => {
6170
6784
  } catch (error) {
6171
6785
  throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
6172
6786
  }
6173
- if (!isRecord$1(parsed)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "Config must be a YAML object");
6787
+ if (!isRecord(parsed)) throw new BusinessRuleError(NotificationErrorCode.NtInvalidConfigStructure, "Config must be a YAML object");
6174
6788
  const obj = parsed;
6175
6789
  const config = {};
6176
6790
  if (obj.general !== void 0) config.general = parseGeneralConfig(obj.general);
@@ -6328,13 +6942,260 @@ var capture_default$8 = define({
6328
6942
  });
6329
6943
 
6330
6944
  //#endregion
6331
- //#region src/cli/commands/notification/index.ts
6332
- var notification_default = define({
6333
- name: "notification",
6334
- description: "Manage kintone notification settings",
6335
- subCommands: {
6945
+ //#region src/lib/groupByKey.ts
6946
+ /**
6947
+ * Groups items by a key function into a Map where each key maps to an array of items.
6948
+ * Multiple items can share the same key, producing a multimap structure.
6949
+ */
6950
+ function groupByKey(items, keyFn) {
6951
+ const map = /* @__PURE__ */ new Map();
6952
+ for (const item of items) {
6953
+ const key = keyFn(item);
6954
+ const existing = map.get(key);
6955
+ if (existing) existing.push(item);
6956
+ else map.set(key, [item]);
6957
+ }
6958
+ return map;
6959
+ }
6960
+
6961
+ //#endregion
6962
+ //#region src/core/domain/notification/services/diffDetector.ts
6963
+ function serializeEntity(entity) {
6964
+ return `${entity.type}:${entity.code}`;
6965
+ }
6966
+ const GENERAL_BOOLEAN_FLAGS = [
6967
+ "recordAdded",
6968
+ "recordEdited",
6969
+ "commentAdded",
6970
+ "statusChanged",
6971
+ "fileImported"
6972
+ ];
6973
+ function compareGeneralNotification(local, remote) {
6974
+ const diffs = [];
6975
+ if ((local.includeSubs ?? false) !== (remote.includeSubs ?? false)) diffs.push(`includeSubs: ${String(remote.includeSubs ?? false)} -> ${String(local.includeSubs ?? false)}`);
6976
+ for (const flag of GENERAL_BOOLEAN_FLAGS) if (local[flag] !== remote[flag]) diffs.push(`${flag}: ${String(remote[flag])} -> ${String(local[flag])}`);
6977
+ return diffs;
6978
+ }
6979
+ function compareGeneralSection(local, remote) {
6980
+ const entries = [];
6981
+ if (local.notifyToCommenter !== remote.notifyToCommenter) entries.push({
6982
+ type: "modified",
6983
+ section: "general",
6984
+ name: "notifyToCommenter",
6985
+ details: `${String(remote.notifyToCommenter)} -> ${String(local.notifyToCommenter)}`
6986
+ });
6987
+ const localMap = new Map(local.notifications.map((n) => [serializeEntity(n.entity), n]));
6988
+ const remoteMap = new Map(remote.notifications.map((n) => [serializeEntity(n.entity), n]));
6989
+ for (const [key, localNotif] of localMap) {
6990
+ const remoteNotif = remoteMap.get(key);
6991
+ if (!remoteNotif) entries.push({
6992
+ type: "added",
6993
+ section: "general",
6994
+ name: key,
6995
+ details: "new notification"
6996
+ });
6997
+ else {
6998
+ const diffs = compareGeneralNotification(localNotif, remoteNotif);
6999
+ if (diffs.length > 0) entries.push({
7000
+ type: "modified",
7001
+ section: "general",
7002
+ name: key,
7003
+ details: diffs.join(", ")
7004
+ });
7005
+ }
7006
+ }
7007
+ for (const key of remoteMap.keys()) if (!localMap.has(key)) entries.push({
7008
+ type: "deleted",
7009
+ section: "general",
7010
+ name: key,
7011
+ details: "removed"
7012
+ });
7013
+ return entries;
7014
+ }
7015
+ /**
7016
+ * Groups per-record notifications by filterCond. Multiple notifications can share
7017
+ * the same filterCond, so notifications within a group are compared by position
7018
+ * (index-based matching).
7019
+ */
7020
+ function buildPerRecordMultiMap(notifications) {
7021
+ return groupByKey(notifications, (n) => n.filterCond);
7022
+ }
7023
+ function perRecordLabel(notif) {
7024
+ return notif.title || notif.filterCond || "(empty filter)";
7025
+ }
7026
+ function describePerRecordChanges(local, remote) {
7027
+ const diffs = [];
7028
+ if (local.title !== remote.title) diffs.push("title changed");
7029
+ if (!deepEqual(local.targets, remote.targets)) diffs.push("targets changed");
7030
+ return diffs.length > 0 ? diffs.join(", ") : "changed";
7031
+ }
7032
+ function describeReminderChanges(local, remote) {
7033
+ const diffs = [];
7034
+ if (local.title !== remote.title) diffs.push("title changed");
7035
+ if (local.daysLater !== remote.daysLater) diffs.push("daysLater changed");
7036
+ if ((local.hoursLater ?? 0) !== (remote.hoursLater ?? 0)) diffs.push("hoursLater changed");
7037
+ if ((local.time ?? "") !== (remote.time ?? "")) diffs.push("time changed");
7038
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
7039
+ if (!deepEqual(local.targets, remote.targets)) diffs.push("targets changed");
7040
+ return diffs.length > 0 ? diffs.join(", ") : "changed";
7041
+ }
7042
+ function comparePerRecordSection(local, remote) {
7043
+ const entries = [];
7044
+ const localMulti = buildPerRecordMultiMap(local);
7045
+ const remoteMulti = buildPerRecordMultiMap(remote);
7046
+ for (const [key, localNotifs] of localMulti) {
7047
+ const remoteNotifs = remoteMulti.get(key) ?? [];
7048
+ const maxLen = Math.max(localNotifs.length, remoteNotifs.length);
7049
+ for (let i = 0; i < maxLen; i++) {
7050
+ const localNotif = localNotifs[i];
7051
+ const remoteNotif = remoteNotifs[i];
7052
+ if (localNotif && !remoteNotif) entries.push({
7053
+ type: "added",
7054
+ section: "perRecord",
7055
+ name: perRecordLabel(localNotif),
7056
+ details: "new notification"
7057
+ });
7058
+ else if (!localNotif && remoteNotif) entries.push({
7059
+ type: "deleted",
7060
+ section: "perRecord",
7061
+ name: perRecordLabel(remoteNotif),
7062
+ details: "removed"
7063
+ });
7064
+ else if (localNotif && remoteNotif && !deepEqual(localNotif, remoteNotif)) {
7065
+ const diffs = describePerRecordChanges(localNotif, remoteNotif);
7066
+ entries.push({
7067
+ type: "modified",
7068
+ section: "perRecord",
7069
+ name: perRecordLabel(localNotif),
7070
+ details: diffs
7071
+ });
7072
+ }
7073
+ }
7074
+ }
7075
+ for (const [key, remoteNotifs] of remoteMulti) if (!localMulti.has(key)) for (const remoteNotif of remoteNotifs) entries.push({
7076
+ type: "deleted",
7077
+ section: "perRecord",
7078
+ name: perRecordLabel(remoteNotif),
7079
+ details: "removed"
7080
+ });
7081
+ return entries;
7082
+ }
7083
+ function compareReminderSection(local, remote) {
7084
+ const entries = [];
7085
+ const localMap = new Map(local.map((n) => [n.code, n]));
7086
+ const remoteMap = new Map(remote.map((n) => [n.code, n]));
7087
+ for (const [code, localNotif] of localMap) {
7088
+ const remoteNotif = remoteMap.get(code);
7089
+ if (!remoteNotif) entries.push({
7090
+ type: "added",
7091
+ section: "reminder",
7092
+ name: code,
7093
+ details: `"${localNotif.title}"`
7094
+ });
7095
+ else if (!deepEqual(localNotif, remoteNotif)) entries.push({
7096
+ type: "modified",
7097
+ section: "reminder",
7098
+ name: code,
7099
+ details: describeReminderChanges(localNotif, remoteNotif)
7100
+ });
7101
+ }
7102
+ for (const code of remoteMap.keys()) if (!localMap.has(code)) entries.push({
7103
+ type: "deleted",
7104
+ section: "reminder",
7105
+ name: code,
7106
+ details: "removed"
7107
+ });
7108
+ return entries;
7109
+ }
7110
+ const NotificationDiffDetector = { detect: (local, remote) => {
7111
+ const entries = [];
7112
+ if (local.general && remote.general) entries.push(...compareGeneralSection(local.general, remote.general));
7113
+ else if (local.general && !remote.general) entries.push({
7114
+ type: "added",
7115
+ section: "general",
7116
+ name: "general",
7117
+ details: "added general notifications section"
7118
+ });
7119
+ else if (!local.general && remote.general) entries.push({
7120
+ type: "deleted",
7121
+ section: "general",
7122
+ name: "general",
7123
+ details: "removed general notifications section"
7124
+ });
7125
+ const localPerRecord = local.perRecord ?? [];
7126
+ const remotePerRecord = remote.perRecord ?? [];
7127
+ entries.push(...comparePerRecordSection(localPerRecord, remotePerRecord));
7128
+ if (local.reminder && remote.reminder) {
7129
+ entries.push(...compareReminderSection(local.reminder.notifications, remote.reminder.notifications));
7130
+ if (local.reminder.timezone !== remote.reminder.timezone) entries.push({
7131
+ type: "modified",
7132
+ section: "reminder",
7133
+ name: "timezone",
7134
+ details: `${remote.reminder.timezone} -> ${local.reminder.timezone}`
7135
+ });
7136
+ } else if (local.reminder && !remote.reminder) entries.push({
7137
+ type: "added",
7138
+ section: "reminder",
7139
+ name: "reminder",
7140
+ details: "added reminder notifications section"
7141
+ });
7142
+ else if (!local.reminder && remote.reminder) entries.push({
7143
+ type: "deleted",
7144
+ section: "reminder",
7145
+ name: "reminder",
7146
+ details: "removed reminder notifications section"
7147
+ });
7148
+ return buildDiffResult(entries);
7149
+ } };
7150
+
7151
+ //#endregion
7152
+ //#region src/core/application/notification/detectNotificationDiff.ts
7153
+ async function detectNotificationDiff({ container }) {
7154
+ const [storageResult, generalResult, perRecordResult, reminderResult] = await Promise.all([
7155
+ container.notificationStorage.get(),
7156
+ container.notificationConfigurator.getGeneralNotifications(),
7157
+ container.notificationConfigurator.getPerRecordNotifications(),
7158
+ container.notificationConfigurator.getReminderNotifications()
7159
+ ]);
7160
+ if (!storageResult.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Notification config file not found");
7161
+ const localConfig = parseNotificationConfigText(storageResult.content);
7162
+ const remoteConfig = {
7163
+ general: {
7164
+ notifyToCommenter: generalResult.notifyToCommenter,
7165
+ notifications: generalResult.notifications
7166
+ },
7167
+ perRecord: perRecordResult.notifications,
7168
+ reminder: {
7169
+ timezone: reminderResult.timezone,
7170
+ notifications: reminderResult.notifications
7171
+ }
7172
+ };
7173
+ return NotificationDiffDetector.detect(localConfig, remoteConfig);
7174
+ }
7175
+
7176
+ //#endregion
7177
+ //#region src/cli/commands/notification/diff.ts
7178
+ var diff_default$7 = createDiffCommand({
7179
+ description: "Compare local notification config with remote kintone app",
7180
+ args: notificationArgs,
7181
+ spinnerMessage: "Comparing notification settings...",
7182
+ multiAppSuccessMessage: "All notification diffs completed successfully.",
7183
+ createContainer: createNotificationCliContainer,
7184
+ detectDiff: detectNotificationDiff,
7185
+ printResult: printNotificationDiffResult,
7186
+ resolveContainerConfig: resolveNotificationContainerConfig,
7187
+ resolveAppContainerConfig: resolveNotificationAppContainerConfig
7188
+ });
7189
+
7190
+ //#endregion
7191
+ //#region src/cli/commands/notification/index.ts
7192
+ var notification_default = define({
7193
+ name: "notification",
7194
+ description: "Manage kintone notification settings",
7195
+ subCommands: {
6336
7196
  apply: apply_default$7,
6337
- capture: capture_default$8
7197
+ capture: capture_default$8,
7198
+ diff: diff_default$7
6338
7199
  },
6339
7200
  run: () => {}
6340
7201
  });
@@ -6342,7 +7203,7 @@ var notification_default = define({
6342
7203
  //#endregion
6343
7204
  //#region src/core/domain/plugin/services/configParser.ts
6344
7205
  function parsePluginEntry(raw, index) {
6345
- if (!isRecord$1(raw)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, `Plugin at index ${index} must be an object`);
7206
+ if (!isRecord(raw)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, `Plugin at index ${index} must be an object`);
6346
7207
  const obj = raw;
6347
7208
  if (typeof obj.id !== "string" || obj.id.length === 0) throw new BusinessRuleError(PluginErrorCode.PlEmptyPluginId, `Plugin at index ${index} must have a non-empty "id" property`);
6348
7209
  return {
@@ -6359,7 +7220,7 @@ const PluginConfigParser = { parse: (rawText) => {
6359
7220
  } catch (error) {
6360
7221
  throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
6361
7222
  }
6362
- if (!isRecord$1(parsed)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, "Config must be a YAML object");
7223
+ if (!isRecord(parsed)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, "Config must be a YAML object");
6363
7224
  const obj = parsed;
6364
7225
  if (!Array.isArray(obj.plugins)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, "Config must have a \"plugins\" array");
6365
7226
  return { plugins: obj.plugins.map((item, i) => parsePluginEntry(item, i)) };
@@ -6497,6 +7358,64 @@ var capture_default$7 = define({
6497
7358
  }
6498
7359
  });
6499
7360
 
7361
+ //#endregion
7362
+ //#region src/core/domain/plugin/services/diffDetector.ts
7363
+ const PluginDiffDetector = { detect: (local, remote) => {
7364
+ const entries = [];
7365
+ const localMap = new Map(local.plugins.map((p) => [p.id, p]));
7366
+ const remoteMap = new Map(remote.plugins.map((p) => [p.id, p]));
7367
+ for (const [id, localPlugin] of localMap) {
7368
+ const remotePlugin = remoteMap.get(id);
7369
+ if (!remotePlugin) entries.push({
7370
+ type: "added",
7371
+ pluginId: id,
7372
+ details: `"${localPlugin.name}"`
7373
+ });
7374
+ else {
7375
+ const diffs = [];
7376
+ if (localPlugin.name !== remotePlugin.name) diffs.push(`name: "${remotePlugin.name}" -> "${localPlugin.name}"`);
7377
+ if (localPlugin.enabled !== remotePlugin.enabled) diffs.push(`enabled: ${String(remotePlugin.enabled)} -> ${String(localPlugin.enabled)}`);
7378
+ if (diffs.length > 0) entries.push({
7379
+ type: "modified",
7380
+ pluginId: id,
7381
+ details: diffs.join(", ")
7382
+ });
7383
+ }
7384
+ }
7385
+ for (const [id, remotePlugin] of remoteMap) if (!localMap.has(id)) entries.push({
7386
+ type: "deleted",
7387
+ pluginId: id,
7388
+ details: `"${remotePlugin.name}"`
7389
+ });
7390
+ return buildDiffResult(entries);
7391
+ } };
7392
+
7393
+ //#endregion
7394
+ //#region src/core/application/plugin/detectPluginDiff.ts
7395
+ async function detectPluginDiff({ container }) {
7396
+ return detectDiffFromConfig({
7397
+ getStorage: () => container.pluginStorage.get(),
7398
+ fetchRemote: () => container.pluginConfigurator.getPlugins(),
7399
+ parseConfig: parsePluginConfigText,
7400
+ detect: (local, remote) => PluginDiffDetector.detect(local, { plugins: remote.plugins }),
7401
+ notFoundMessage: "Plugin config file not found"
7402
+ });
7403
+ }
7404
+
7405
+ //#endregion
7406
+ //#region src/cli/commands/plugin/diff.ts
7407
+ var diff_default$6 = createDiffCommand({
7408
+ description: "Compare local plugin config with remote kintone app",
7409
+ args: pluginArgs,
7410
+ spinnerMessage: "Comparing plugin settings...",
7411
+ multiAppSuccessMessage: "All plugin diffs completed successfully.",
7412
+ createContainer: createPluginCliContainer,
7413
+ detectDiff: detectPluginDiff,
7414
+ printResult: printPluginDiffResult,
7415
+ resolveContainerConfig: resolvePluginContainerConfig,
7416
+ resolveAppContainerConfig: resolvePluginAppContainerConfig
7417
+ });
7418
+
6500
7419
  //#endregion
6501
7420
  //#region src/cli/commands/plugin/index.ts
6502
7421
  var plugin_default = define({
@@ -6504,7 +7423,8 @@ var plugin_default = define({
6504
7423
  description: "Manage kintone plugins",
6505
7424
  subCommands: {
6506
7425
  apply: apply_default$6,
6507
- capture: capture_default$7
7426
+ capture: capture_default$7,
7427
+ diff: diff_default$6
6508
7428
  },
6509
7429
  run: () => {}
6510
7430
  });
@@ -6526,7 +7446,7 @@ const VALID_ENTITY_TYPES$1 = new Set([
6526
7446
  ]);
6527
7447
  const VALID_ACTION_TYPES = new Set(["PRIMARY", "SECONDARY"]);
6528
7448
  function parseProcessEntity(raw, index) {
6529
- if (!isRecord$1(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Entity at index ${index} must be an object`);
7449
+ if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Entity at index ${index} must be an object`);
6530
7450
  const obj = raw;
6531
7451
  if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$1.has(obj.type)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidEntityType, `Entity at index ${index} has invalid type: ${String(obj.type)}. Must be USER, GROUP, ORGANIZATION, FIELD_ENTITY, CREATOR, or CUSTOM_FIELD`);
6532
7452
  const result = { type: obj.type };
@@ -6541,7 +7461,7 @@ function parseProcessEntity(raw, index) {
6541
7461
  return withCode;
6542
7462
  }
6543
7463
  function parseAssignee(raw, stateName) {
6544
- if (!isRecord$1(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Assignee for state "${stateName}" must be an object`);
7464
+ if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Assignee for state "${stateName}" must be an object`);
6545
7465
  const obj = raw;
6546
7466
  if (typeof obj.type !== "string" || !VALID_ASSIGNEE_TYPES.has(obj.type)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidAssigneeType, `Assignee for state "${stateName}" has invalid type: ${String(obj.type)}. Must be ONE, ALL, or ANY`);
6547
7467
  if (!Array.isArray(obj.entities)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Assignee for state "${stateName}" must have an "entities" array`);
@@ -6552,7 +7472,7 @@ function parseAssignee(raw, stateName) {
6552
7472
  };
6553
7473
  }
6554
7474
  function parseState(raw, stateName) {
6555
- if (!isRecord$1(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must be an object`);
7475
+ if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must be an object`);
6556
7476
  const obj = raw;
6557
7477
  if (typeof obj.index !== "number") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must have a numeric "index" property`);
6558
7478
  if (obj.assignee === void 0 || obj.assignee === null) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must have an "assignee" property`);
@@ -6563,13 +7483,13 @@ function parseState(raw, stateName) {
6563
7483
  };
6564
7484
  }
6565
7485
  function parseExecutableUser(raw, actionIndex) {
6566
- if (!isRecord$1(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${actionIndex}: executableUser must be an object`);
7486
+ if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${actionIndex}: executableUser must be an object`);
6567
7487
  const obj = raw;
6568
7488
  if (!Array.isArray(obj.entities)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${actionIndex}: executableUser must have an "entities" array`);
6569
7489
  return { entities: obj.entities.map((item, i) => parseProcessEntity(item, i)) };
6570
7490
  }
6571
7491
  function parseAction(raw, index) {
6572
- if (!isRecord$1(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must be an object`);
7492
+ if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must be an object`);
6573
7493
  const obj = raw;
6574
7494
  if (typeof obj.name !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "name" string property`);
6575
7495
  if (typeof obj.from !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "from" string property`);
@@ -6597,11 +7517,11 @@ const ProcessManagementConfigParser = { parse: (rawText) => {
6597
7517
  } catch (error) {
6598
7518
  throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
6599
7519
  }
6600
- if (!isRecord$1(parsed)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config must be a YAML object");
7520
+ if (!isRecord(parsed)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config must be a YAML object");
6601
7521
  const obj = parsed;
6602
7522
  const enable = obj.enable !== void 0 && obj.enable !== null ? Boolean(obj.enable) : false;
6603
- if (obj.states !== void 0 && obj.states !== null && !isRecord$1(obj.states)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config \"states\" must be an object (map of state name to state definition)");
6604
- const rawStates = isRecord$1(obj.states) ? obj.states : {};
7523
+ if (obj.states !== void 0 && obj.states !== null && !isRecord(obj.states)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config \"states\" must be an object (map of state name to state definition)");
7524
+ const rawStates = isRecord(obj.states) ? obj.states : {};
6605
7525
  const states = {};
6606
7526
  for (const [name, value] of Object.entries(rawStates)) states[name] = parseState(value, name);
6607
7527
  if (!Array.isArray(obj.actions) && obj.actions !== void 0) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config \"actions\" must be an array");
@@ -6781,7 +7701,7 @@ function compareActions(localAction, remoteAction) {
6781
7701
  if (!isExecutableUserEqual(localAction.executableUser, remoteAction.executableUser)) diffs.push("executableUser changed");
6782
7702
  return diffs;
6783
7703
  }
6784
- function compareConfigs(local, remote) {
7704
+ function compareConfigs$1(local, remote) {
6785
7705
  const entries = [];
6786
7706
  if (local.enable !== remote.enable) entries.push({
6787
7707
  type: "modified",
@@ -6846,68 +7766,33 @@ function compareConfigs(local, remote) {
6846
7766
  return entries;
6847
7767
  }
6848
7768
  const ProcessManagementDiffDetector = { detect: (local, remote) => {
6849
- const entries = compareConfigs(local, remote);
6850
- const added = entries.filter((e) => e.type === "added").length;
6851
- const modified = entries.filter((e) => e.type === "modified").length;
6852
- const deleted = entries.filter((e) => e.type === "deleted").length;
6853
- return {
6854
- entries,
6855
- summary: {
6856
- added,
6857
- modified,
6858
- deleted,
6859
- total: added + modified + deleted
6860
- },
6861
- isEmpty: entries.length === 0
6862
- };
7769
+ return buildDiffResult(compareConfigs$1(local, remote));
6863
7770
  } };
6864
7771
 
6865
7772
  //#endregion
6866
7773
  //#region src/core/application/processManagement/detectProcessManagementDiff.ts
6867
7774
  async function detectProcessManagementDiff({ container }) {
6868
- const result = await container.processManagementStorage.get();
6869
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Process management config file not found");
6870
- const localConfig = parseProcessManagementConfigText(result.content);
6871
- const { config: remoteConfig } = await container.processManagementConfigurator.getProcessManagement();
6872
- return ProcessManagementDiffDetector.detect(localConfig, remoteConfig);
7775
+ return detectDiffFromConfig({
7776
+ getStorage: () => container.processManagementStorage.get(),
7777
+ fetchRemote: () => container.processManagementConfigurator.getProcessManagement(),
7778
+ parseConfig: parseProcessManagementConfigText,
7779
+ detect: (local, remote) => ProcessManagementDiffDetector.detect(local, remote.config),
7780
+ notFoundMessage: "Process management config file not found"
7781
+ });
6873
7782
  }
6874
7783
 
6875
7784
  //#endregion
6876
7785
  //#region src/cli/commands/process/diff.ts
6877
- async function runDiffProcess(config) {
6878
- const container = createProcessManagementCliContainer(config);
6879
- const s = p.spinner();
6880
- s.start("Comparing process management settings...");
6881
- const result = await detectProcessManagementDiff({ container });
6882
- s.stop("Comparison complete.");
6883
- printProcessDiffResult(result);
6884
- }
6885
- var diff_default$2 = define({
6886
- name: "diff",
7786
+ var diff_default$5 = createDiffCommand({
6887
7787
  description: "Compare local process management settings with remote kintone app",
6888
7788
  args: processArgs,
6889
- run: async (ctx) => {
6890
- try {
6891
- const values = ctx.values;
6892
- await routeMultiApp(values, {
6893
- singleLegacy: async () => {
6894
- await runDiffProcess(resolveProcessContainerConfig(values));
6895
- },
6896
- singleApp: async (app, projectConfig) => {
6897
- await runDiffProcess(resolveProcessAppContainerConfig(app, projectConfig, values));
6898
- },
6899
- multiApp: async (plan, projectConfig) => {
6900
- await runMultiAppWithFailCheck(plan, async (app) => {
6901
- const config = resolveProcessAppContainerConfig(app, projectConfig, values);
6902
- printAppHeader(app.name, app.appId);
6903
- await runDiffProcess(config);
6904
- }, "All process management diffs completed successfully.");
6905
- }
6906
- });
6907
- } catch (error) {
6908
- handleCliError(error);
6909
- }
6910
- }
7789
+ spinnerMessage: "Comparing process management settings...",
7790
+ multiAppSuccessMessage: "All process management diffs completed successfully.",
7791
+ createContainer: createProcessManagementCliContainer,
7792
+ detectDiff: detectProcessManagementDiff,
7793
+ printResult: printProcessDiffResult,
7794
+ resolveContainerConfig: resolveProcessContainerConfig,
7795
+ resolveAppContainerConfig: resolveProcessAppContainerConfig
6911
7796
  });
6912
7797
 
6913
7798
  //#endregion
@@ -6918,7 +7803,7 @@ var process_default = define({
6918
7803
  subCommands: {
6919
7804
  apply: apply_default$5,
6920
7805
  capture: capture_default$6,
6921
- diff: diff_default$2
7806
+ diff: diff_default$5
6922
7807
  },
6923
7808
  run: () => {}
6924
7809
  });
@@ -6937,7 +7822,7 @@ function parseBooleanField(value, fieldName, context) {
6937
7822
  throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidPermissionValue, `${context} has invalid "${fieldName}" value: ${String(value)}. Must be a boolean`);
6938
7823
  }
6939
7824
  function parseEntity(raw, index) {
6940
- if (!isRecord$1(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Entity at index ${index} must be an object`);
7825
+ if (!isRecord(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Entity at index ${index} must be an object`);
6941
7826
  const obj = raw;
6942
7827
  if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES.has(obj.type)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidEntityType, `Entity at index ${index} has invalid type: ${String(obj.type)}. Must be USER, GROUP, ORGANIZATION, or FIELD_ENTITY`);
6943
7828
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(RecordPermissionErrorCode.RpEmptyEntityCode, `Entity at index ${index} must have a non-empty "code" property`);
@@ -6947,7 +7832,7 @@ function parseEntity(raw, index) {
6947
7832
  };
6948
7833
  }
6949
7834
  function parseRecordRightEntity(raw, index) {
6950
- if (!isRecord$1(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right entity at index ${index} must be an object`);
7835
+ if (!isRecord(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right entity at index ${index} must be an object`);
6951
7836
  const obj = raw;
6952
7837
  const entity = parseEntity(obj.entity, index);
6953
7838
  const context = `Record right entity at index ${index}`;
@@ -6960,7 +7845,7 @@ function parseRecordRightEntity(raw, index) {
6960
7845
  };
6961
7846
  }
6962
7847
  function parseRecordRight(raw, index) {
6963
- if (!isRecord$1(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right at index ${index} must be an object`);
7848
+ if (!isRecord(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right at index ${index} must be an object`);
6964
7849
  const obj = raw;
6965
7850
  if (!Array.isArray(obj.entities)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right at index ${index} must have an "entities" array`);
6966
7851
  const entities = obj.entities.map((item, i) => parseRecordRightEntity(item, i));
@@ -6977,7 +7862,7 @@ const RecordPermissionConfigParser = { parse: (rawText) => {
6977
7862
  } catch (error) {
6978
7863
  throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
6979
7864
  }
6980
- if (!isRecord$1(parsed)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, "Config must be a YAML object");
7865
+ if (!isRecord(parsed)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, "Config must be a YAML object");
6981
7866
  const obj = parsed;
6982
7867
  if (!Array.isArray(obj.rights)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, "Config must have a \"rights\" array");
6983
7868
  return { rights: obj.rights.map((item, i) => parseRecordRight(item, i)) };
@@ -7113,6 +7998,104 @@ var capture_default$5 = define({
7113
7998
  }
7114
7999
  });
7115
8000
 
8001
+ //#endregion
8002
+ //#region src/core/domain/recordPermission/services/diffDetector.ts
8003
+ function areRightsEqual(a, b) {
8004
+ return deepEqual(a.entities.map((e) => ({
8005
+ type: e.entity.type,
8006
+ code: e.entity.code,
8007
+ viewable: e.viewable,
8008
+ editable: e.editable,
8009
+ deletable: e.deletable,
8010
+ includeSubs: e.includeSubs
8011
+ })), b.entities.map((e) => ({
8012
+ type: e.entity.type,
8013
+ code: e.entity.code,
8014
+ viewable: e.viewable,
8015
+ editable: e.editable,
8016
+ deletable: e.deletable,
8017
+ includeSubs: e.includeSubs
8018
+ })));
8019
+ }
8020
+ function describeRight(right) {
8021
+ const perEntity = right.entities.map((e) => {
8022
+ const perms = [
8023
+ e.viewable ? "view" : null,
8024
+ e.editable ? "edit" : null,
8025
+ e.deletable ? "delete" : null
8026
+ ].filter(Boolean).join("/");
8027
+ return `${e.entity.type}:${e.entity.code}(${perms || "none"})`;
8028
+ });
8029
+ return perEntity.length > 0 ? perEntity.join(", ") : "no entities";
8030
+ }
8031
+ const RecordPermissionDiffDetector = { detect: (local, remote) => {
8032
+ const entries = [];
8033
+ const localMulti = groupByKey(local.rights, (r) => r.filterCond);
8034
+ const remoteMulti = groupByKey(remote.rights, (r) => r.filterCond);
8035
+ for (const [filterCond, localRights] of localMulti) {
8036
+ const remoteRights = remoteMulti.get(filterCond) ?? [];
8037
+ const maxLen = Math.max(localRights.length, remoteRights.length);
8038
+ for (let i = 0; i < maxLen; i++) {
8039
+ const localRight = localRights[i];
8040
+ const remoteRight = remoteRights[i];
8041
+ if (localRight && !remoteRight) entries.push({
8042
+ type: "added",
8043
+ filterCond,
8044
+ details: describeRight(localRight)
8045
+ });
8046
+ else if (!localRight && remoteRight) entries.push({
8047
+ type: "deleted",
8048
+ filterCond,
8049
+ details: describeRight(remoteRight)
8050
+ });
8051
+ else if (localRight && remoteRight) {
8052
+ if (!areRightsEqual(localRight, remoteRight)) {
8053
+ const diffs = [];
8054
+ if (localRight.entities.length !== remoteRight.entities.length) diffs.push(`entities: ${remoteRight.entities.length} -> ${localRight.entities.length}`);
8055
+ else diffs.push("entities changed");
8056
+ entries.push({
8057
+ type: "modified",
8058
+ filterCond,
8059
+ details: diffs.join(", ")
8060
+ });
8061
+ }
8062
+ }
8063
+ }
8064
+ }
8065
+ for (const [filterCond, remoteRights] of remoteMulti) if (!localMulti.has(filterCond)) for (const remoteRight of remoteRights) entries.push({
8066
+ type: "deleted",
8067
+ filterCond,
8068
+ details: describeRight(remoteRight)
8069
+ });
8070
+ return buildDiffResult(entries);
8071
+ } };
8072
+
8073
+ //#endregion
8074
+ //#region src/core/application/recordPermission/detectRecordPermissionDiff.ts
8075
+ async function detectRecordPermissionDiff({ container }) {
8076
+ return detectDiffFromConfig({
8077
+ getStorage: () => container.recordPermissionStorage.get(),
8078
+ fetchRemote: () => container.recordPermissionConfigurator.getRecordPermissions(),
8079
+ parseConfig: parseRecordPermissionConfigText,
8080
+ detect: (local, remote) => RecordPermissionDiffDetector.detect(local, { rights: remote.rights }),
8081
+ notFoundMessage: "Record permission config file not found"
8082
+ });
8083
+ }
8084
+
8085
+ //#endregion
8086
+ //#region src/cli/commands/record-acl/diff.ts
8087
+ var diff_default$4 = createDiffCommand({
8088
+ description: "Compare local record permission config with remote kintone app",
8089
+ args: recordAclArgs,
8090
+ spinnerMessage: "Comparing record permissions...",
8091
+ multiAppSuccessMessage: "All record permission diffs completed successfully.",
8092
+ createContainer: createRecordPermissionCliContainer,
8093
+ detectDiff: detectRecordPermissionDiff,
8094
+ printResult: printRecordPermissionDiffResult,
8095
+ resolveContainerConfig: resolveRecordAclContainerConfig,
8096
+ resolveAppContainerConfig: resolveRecordAclAppContainerConfig
8097
+ });
8098
+
7116
8099
  //#endregion
7117
8100
  //#region src/cli/commands/record-acl/index.ts
7118
8101
  var record_acl_default = define({
@@ -7120,7 +8103,8 @@ var record_acl_default = define({
7120
8103
  description: "Manage kintone record access permissions",
7121
8104
  subCommands: {
7122
8105
  apply: apply_default$4,
7123
- capture: capture_default$5
8106
+ capture: capture_default$5,
8107
+ diff: diff_default$4
7124
8108
  },
7125
8109
  run: () => {}
7126
8110
  });
@@ -7189,7 +8173,7 @@ const VALID_PERIODIC_EVERY = new Set([
7189
8173
  "HOUR"
7190
8174
  ]);
7191
8175
  function parseGroup(raw, index) {
7192
- if (!isRecord$1(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Group at index ${index} must be an object`);
8176
+ if (!isRecord(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Group at index ${index} must be an object`);
7193
8177
  const obj = raw;
7194
8178
  if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Group at index ${index} must have a non-empty "code" property`);
7195
8179
  const result = { code: obj.code };
@@ -7203,7 +8187,7 @@ function parseGroup(raw, index) {
7203
8187
  return result;
7204
8188
  }
7205
8189
  function parseAggregation(raw, index) {
7206
- if (!isRecord$1(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Aggregation at index ${index} must be an object`);
8190
+ if (!isRecord(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Aggregation at index ${index} must be an object`);
7207
8191
  const obj = raw;
7208
8192
  if (typeof obj.type !== "string" || !VALID_AGGREGATION_TYPES.has(obj.type)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Aggregation at index ${index} has invalid type: ${String(obj.type)}. Must be COUNT, SUM, AVERAGE, MAX, or MIN`);
7209
8193
  const result = { type: obj.type };
@@ -7217,7 +8201,7 @@ function parseAggregation(raw, index) {
7217
8201
  return result;
7218
8202
  }
7219
8203
  function parseSort(raw, index) {
7220
- if (!isRecord$1(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Sort at index ${index} must be an object`);
8204
+ if (!isRecord(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Sort at index ${index} must be an object`);
7221
8205
  const obj = raw;
7222
8206
  if (typeof obj.by !== "string" || !VALID_SORT_BY.has(obj.by)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Sort at index ${index} has invalid by: ${String(obj.by)}. Must be TOTAL, GROUP1, GROUP2, or GROUP3`);
7223
8207
  if (typeof obj.order !== "string" || !VALID_SORT_ORDER.has(obj.order)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Sort at index ${index} has invalid order: ${String(obj.order)}. Must be ASC or DESC`);
@@ -7227,7 +8211,7 @@ function parseSort(raw, index) {
7227
8211
  };
7228
8212
  }
7229
8213
  function parsePeriodicReportPeriod(raw) {
7230
- if (!isRecord$1(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "periodicReport.period must be an object");
8214
+ if (!isRecord(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "periodicReport.period must be an object");
7231
8215
  const obj = raw;
7232
8216
  if (typeof obj.every !== "string" || !VALID_PERIODIC_EVERY.has(obj.every)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `periodicReport.period has invalid every: ${String(obj.every)}. Must be YEAR, QUARTER, MONTH, WEEK, DAY, or HOUR`);
7233
8217
  const every = obj.every;
@@ -7274,7 +8258,7 @@ function parsePeriodicReportPeriod(raw) {
7274
8258
  };
7275
8259
  }
7276
8260
  function parsePeriodicReport(raw) {
7277
- if (!isRecord$1(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "periodicReport must be an object");
8261
+ if (!isRecord(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "periodicReport must be an object");
7278
8262
  const obj = raw;
7279
8263
  if (typeof obj.active !== "boolean") throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "periodicReport.active must be a boolean");
7280
8264
  const period = parsePeriodicReportPeriod(obj.period);
@@ -7284,7 +8268,7 @@ function parsePeriodicReport(raw) {
7284
8268
  };
7285
8269
  }
7286
8270
  function parseReportConfig(raw, reportName) {
7287
- if (!isRecord$1(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Report "${reportName}" must be an object`);
8271
+ if (!isRecord(raw)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, `Report "${reportName}" must be an object`);
7288
8272
  const obj = raw;
7289
8273
  if (typeof obj.chartType !== "string" || !VALID_CHART_TYPES.has(obj.chartType)) throw new BusinessRuleError(ReportErrorCode.RtInvalidChartType, `Report "${reportName}" has invalid chartType: ${String(obj.chartType)}. Must be BAR, COLUMN, PIE, LINE, PIVOT_TABLE, TABLE, AREA, SPLINE, or SPLINE_AREA`);
7290
8274
  let chartMode;
@@ -7323,9 +8307,9 @@ const ReportConfigParser = { parse: (rawText) => {
7323
8307
  } catch (error) {
7324
8308
  throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
7325
8309
  }
7326
- if (!isRecord$1(parsed)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "Config must be a YAML object");
8310
+ if (!isRecord(parsed)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "Config must be a YAML object");
7327
8311
  const obj = parsed;
7328
- if (!isRecord$1(obj.reports)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "Config must have a \"reports\" object");
8312
+ if (!isRecord(obj.reports)) throw new BusinessRuleError(ReportErrorCode.RtInvalidConfigStructure, "Config must have a \"reports\" object");
7329
8313
  const rawReports = obj.reports;
7330
8314
  const reports = {};
7331
8315
  for (const [name, value] of Object.entries(rawReports)) reports[name] = parseReportConfig(value, name);
@@ -7462,6 +8446,73 @@ var capture_default$4 = define({
7462
8446
  }
7463
8447
  });
7464
8448
 
8449
+ //#endregion
8450
+ //#region src/core/domain/report/services/diffDetector.ts
8451
+ function compareReports(local, remote) {
8452
+ const diffs = [];
8453
+ if (local.name !== remote.name) diffs.push(`name: "${remote.name}" -> "${local.name}"`);
8454
+ if (local.chartType !== remote.chartType) diffs.push(`chartType: ${remote.chartType} -> ${local.chartType}`);
8455
+ if ((local.chartMode ?? "") !== (remote.chartMode ?? "")) diffs.push(`chartMode: ${remote.chartMode ?? "(unset)"} -> ${local.chartMode ?? "(unset)"}`);
8456
+ if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
8457
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
8458
+ if (!deepEqual(local.groups, remote.groups)) diffs.push("groups changed");
8459
+ if (!deepEqual(local.aggregations, remote.aggregations)) diffs.push("aggregations changed");
8460
+ if (!deepEqual(local.sorts, remote.sorts)) diffs.push("sorts changed");
8461
+ if (!deepEqual(local.periodicReport ?? null, remote.periodicReport ?? null)) diffs.push("periodicReport changed");
8462
+ return diffs;
8463
+ }
8464
+ const ReportDiffDetector = { detect: (local, remote) => {
8465
+ const entries = [];
8466
+ for (const [name, localReport] of Object.entries(local.reports)) {
8467
+ const remoteReport = remote.reports[name];
8468
+ if (!remoteReport) entries.push({
8469
+ type: "added",
8470
+ reportName: name,
8471
+ details: `chartType: ${localReport.chartType}`
8472
+ });
8473
+ else {
8474
+ const diffs = compareReports(localReport, remoteReport);
8475
+ if (diffs.length > 0) entries.push({
8476
+ type: "modified",
8477
+ reportName: name,
8478
+ details: diffs.join(", ")
8479
+ });
8480
+ }
8481
+ }
8482
+ for (const [name, remoteReport] of Object.entries(remote.reports)) if (!local.reports[name]) entries.push({
8483
+ type: "deleted",
8484
+ reportName: name,
8485
+ details: `chartType: ${remoteReport.chartType}`
8486
+ });
8487
+ return buildDiffResult(entries);
8488
+ } };
8489
+
8490
+ //#endregion
8491
+ //#region src/core/application/report/detectReportDiff.ts
8492
+ async function detectReportDiff({ container }) {
8493
+ return detectDiffFromConfig({
8494
+ getStorage: () => container.reportStorage.get(),
8495
+ fetchRemote: () => container.reportConfigurator.getReports(),
8496
+ parseConfig: parseReportConfigText,
8497
+ detect: (local, remote) => ReportDiffDetector.detect(local, { reports: remote.reports }),
8498
+ notFoundMessage: "Report config file not found"
8499
+ });
8500
+ }
8501
+
8502
+ //#endregion
8503
+ //#region src/cli/commands/report/diff.ts
8504
+ var diff_default$3 = createDiffCommand({
8505
+ description: "Compare local report config with remote kintone app",
8506
+ args: reportArgs,
8507
+ spinnerMessage: "Comparing report settings...",
8508
+ multiAppSuccessMessage: "All report diffs completed successfully.",
8509
+ createContainer: createReportCliContainer,
8510
+ detectDiff: detectReportDiff,
8511
+ printResult: printReportDiffResult,
8512
+ resolveContainerConfig: resolveReportContainerConfig,
8513
+ resolveAppContainerConfig: resolveReportAppContainerConfig
8514
+ });
8515
+
7465
8516
  //#endregion
7466
8517
  //#region src/cli/commands/report/index.ts
7467
8518
  var report_default = define({
@@ -7469,7 +8520,8 @@ var report_default = define({
7469
8520
  description: "Manage kintone report settings",
7470
8521
  subCommands: {
7471
8522
  apply: apply_default$3,
7472
- capture: capture_default$4
8523
+ capture: capture_default$4,
8524
+ diff: diff_default$3
7473
8525
  },
7474
8526
  run: () => {}
7475
8527
  });
@@ -7521,53 +8573,8 @@ var capture_default$3 = define({
7521
8573
  }
7522
8574
  });
7523
8575
 
7524
- //#endregion
7525
- //#region src/core/domain/formSchema/entity.ts
7526
- const FormDiff = { create: (entries) => {
7527
- const summary = {
7528
- added: entries.filter((e) => e.type === "added").length,
7529
- modified: entries.filter((e) => e.type === "modified").length,
7530
- deleted: entries.filter((e) => e.type === "deleted").length,
7531
- total: entries.length
7532
- };
7533
- const sortOrder = {
7534
- added: 0,
7535
- modified: 1,
7536
- deleted: 2
7537
- };
7538
- return {
7539
- entries: [...entries].sort((a, b) => sortOrder[a.type] - sortOrder[b.type]),
7540
- summary,
7541
- isEmpty: entries.length === 0
7542
- };
7543
- } };
7544
-
7545
8576
  //#endregion
7546
8577
  //#region src/core/domain/formSchema/services/diffDetector.ts
7547
- function isArrayEqual$1(a, b) {
7548
- if (!Array.isArray(a) || !Array.isArray(b)) return false;
7549
- if (a.length !== b.length) return false;
7550
- for (let i = 0; i < a.length; i++) if (!isValueEqual(a[i], b[i])) return false;
7551
- return true;
7552
- }
7553
- function isRecordEqual(a, b) {
7554
- const keysA = Object.keys(a);
7555
- const keysB = Object.keys(b);
7556
- if (keysA.length !== keysB.length) return false;
7557
- for (const key of keysA) {
7558
- if (!Object.hasOwn(b, key)) return false;
7559
- if (!isValueEqual(a[key], b[key])) return false;
7560
- }
7561
- return true;
7562
- }
7563
- function isValueEqual(a, b) {
7564
- if (a === b) return true;
7565
- if (a === null || b === null) return a === b;
7566
- if (typeof a !== typeof b) return false;
7567
- if (Array.isArray(a)) return isArrayEqual$1(a, b);
7568
- if (isRecord$1(a) && isRecord$1(b)) return isRecordEqual(a, b);
7569
- return false;
7570
- }
7571
8578
  function isMapEqual(a, b) {
7572
8579
  if (a.size !== b.size) return false;
7573
8580
  for (const [key, valA] of a) {
@@ -7579,51 +8586,26 @@ function isMapEqual(a, b) {
7579
8586
  }
7580
8587
  function isPropertiesEqual(a, b) {
7581
8588
  if (a.type === "SUBTABLE" && b.type === "SUBTABLE") return isMapEqual(a.properties.fields, b.properties.fields);
7582
- if (a.type === "REFERENCE_TABLE" && b.type === "REFERENCE_TABLE") {
7583
- const refA = a.properties.referenceTable;
7584
- const refB = b.properties.referenceTable;
7585
- return isValueEqual({
7586
- ...refA,
7587
- displayFields: [...refA.displayFields]
7588
- }, {
7589
- ...refB,
7590
- displayFields: [...refB.displayFields]
7591
- });
7592
- }
7593
- return isValueEqual(a.properties, b.properties);
8589
+ if (a.type === "REFERENCE_TABLE" && b.type === "REFERENCE_TABLE") return deepEqual(a.properties.referenceTable, b.properties.referenceTable);
8590
+ return deepEqual(a.properties, b.properties);
7594
8591
  }
7595
8592
  function isFieldEqual(a, b) {
7596
8593
  if (a.type !== b.type) return false;
7597
8594
  if (a.label !== b.label) return false;
7598
8595
  if (a.code !== b.code) return false;
7599
- if (Boolean(a.noLabel) !== Boolean(b.noLabel)) return false;
8596
+ if ((a.noLabel ?? false) !== (b.noLabel ?? false)) return false;
7600
8597
  return isPropertiesEqual(a, b);
7601
8598
  }
7602
- function hasPropertiesChanged(before, after) {
7603
- if (before.type === "SUBTABLE" && after.type === "SUBTABLE") return !isMapEqual(before.properties.fields, after.properties.fields);
7604
- if (before.type === "REFERENCE_TABLE" && after.type === "REFERENCE_TABLE") {
7605
- const refB = before.properties.referenceTable;
7606
- const refA = after.properties.referenceTable;
7607
- return !isValueEqual({
7608
- ...refB,
7609
- displayFields: [...refB.displayFields]
7610
- }, {
7611
- ...refA,
7612
- displayFields: [...refA.displayFields]
7613
- });
7614
- }
7615
- return !isValueEqual(before.properties, after.properties);
7616
- }
7617
8599
  function describeChanges$1(before, after) {
7618
8600
  const changes = [];
7619
8601
  if (before.type !== after.type) changes.push(`type: ${before.type} -> ${after.type}`);
7620
8602
  if (before.label !== after.label) changes.push(`label: ${before.label} -> ${after.label}`);
7621
- if (Boolean(before.noLabel) !== Boolean(after.noLabel)) changes.push(`noLabel: ${before.noLabel ?? false} -> ${after.noLabel ?? false}`);
7622
- if (hasPropertiesChanged(before, after)) changes.push("properties changed");
8603
+ if ((before.noLabel ?? false) !== (after.noLabel ?? false)) changes.push(`noLabel: ${before.noLabel ?? false} -> ${after.noLabel ?? false}`);
8604
+ if (!isPropertiesEqual(before, after)) changes.push("properties changed");
7623
8605
  return changes.length > 0 ? changes.join(", ") : "no visible changes";
7624
8606
  }
7625
8607
  function isLayoutEqual(a, b) {
7626
- return isValueEqual(a, b);
8608
+ return deepEqual(a, b);
7627
8609
  }
7628
8610
  const DiffDetector = {
7629
8611
  detectLayoutChanges: (schemaLayout, currentLayout) => {
@@ -7653,13 +8635,23 @@ const DiffDetector = {
7653
8635
  type: "deleted",
7654
8636
  fieldCode,
7655
8637
  fieldLabel: currentDef.label,
7656
- details: "deleted",
8638
+ details: "removed",
7657
8639
  before: currentDef
7658
8640
  });
7659
- return FormDiff.create(entries);
8641
+ return buildDiffResult(entries);
7660
8642
  }
7661
8643
  };
7662
8644
 
8645
+ //#endregion
8646
+ //#region src/core/domain/formSchema/entity.ts
8647
+ const Schema = { create: (fields, layout) => {
8648
+ if (fields.size === 0) throw new BusinessRuleError(FormSchemaErrorCode.FsEmptyFields, "Schema must have at least one field");
8649
+ return {
8650
+ fields,
8651
+ layout
8652
+ };
8653
+ } };
8654
+
7663
8655
  //#endregion
7664
8656
  //#region src/core/domain/formSchema/services/schemaParser.ts
7665
8657
  const VALID_UNIT_POSITIONS = new Set(["BEFORE", "AFTER"]);
@@ -7679,7 +8671,7 @@ const VALID_LINK_PROTOCOLS = new Set([
7679
8671
  "MAIL"
7680
8672
  ]);
7681
8673
  function validateEnumProperty(fieldCode, propName, value, validValues) {
7682
- if (value !== void 0 && !validValues.has(value)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Invalid ${propName} "${String(value)}" for field "${fieldCode}". Expected one of: ${[...validValues].join(", ")}`);
8674
+ if (value !== void 0 && (typeof value !== "string" || !validValues.has(value))) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Invalid ${propName} "${String(value)}" for field "${fieldCode}". Expected one of: ${[...validValues].join(", ")}`);
7683
8675
  }
7684
8676
  function validateFieldProperties(code, fieldType, properties) {
7685
8677
  switch (fieldType) {
@@ -7748,17 +8740,38 @@ const LAYOUT_ATTRIBUTES = new Set(["size"]);
7748
8740
  const DECORATION_ATTRIBUTES = new Set(["elementId"]);
7749
8741
  const GROUP_ATTRIBUTES = new Set(["openGroup", "layout"]);
7750
8742
  const SUBTABLE_ATTRIBUTES = new Set(["fields"]);
8743
+ /**
8744
+ * Parses a raw size object into an ElementSize.
8745
+ * Returns undefined when no size properties are present, treating an empty
8746
+ * object the same as absent — layout elements without explicit sizing should
8747
+ * not carry a redundant empty size object.
8748
+ */
7751
8749
  function parseSize(raw) {
7752
- if (!isRecord$1(raw)) return void 0;
8750
+ if (!isRecord(raw)) return void 0;
7753
8751
  const obj = raw;
8752
+ if (obj.width === void 0 && obj.height === void 0 && obj.innerHeight === void 0) return;
7754
8753
  return {
7755
8754
  ...obj.width !== void 0 ? { width: String(obj.width) } : {},
7756
8755
  ...obj.height !== void 0 ? { height: String(obj.height) } : {},
7757
8756
  ...obj.innerHeight !== void 0 ? { innerHeight: String(obj.innerHeight) } : {}
7758
8757
  };
7759
8758
  }
7760
- function normalizePropertyValue(value) {
8759
+ const BOOLEAN_PROPERTIES = new Set([
8760
+ "required",
8761
+ "unique",
8762
+ "digit",
8763
+ "hideExpression",
8764
+ "defaultNowValue",
8765
+ "openGroup"
8766
+ ]);
8767
+ function normalizePropertyValue(key, value) {
8768
+ if (typeof value === "boolean") return value;
7761
8769
  if (typeof value === "number") return String(value);
8770
+ if (BOOLEAN_PROPERTIES.has(key) && typeof value === "string") {
8771
+ if (value === "true") return true;
8772
+ if (value === "false") return false;
8773
+ throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Invalid boolean string "${value}" for property "${key}". Expected "true" or "false"`);
8774
+ }
7762
8775
  return value;
7763
8776
  }
7764
8777
  function extractProperties(raw) {
@@ -7769,7 +8782,7 @@ function extractProperties(raw) {
7769
8782
  if (DECORATION_ATTRIBUTES.has(key)) continue;
7770
8783
  if (GROUP_ATTRIBUTES.has(key)) continue;
7771
8784
  if (SUBTABLE_ATTRIBUTES.has(key)) continue;
7772
- properties[key] = normalizePropertyValue(value);
8785
+ properties[key] = normalizePropertyValue(key, value);
7773
8786
  }
7774
8787
  return properties;
7775
8788
  }
@@ -7887,10 +8900,10 @@ function parseFieldDefinitionFromFlat(raw) {
7887
8900
  };
7888
8901
  }
7889
8902
  if (fieldType === "REFERENCE_TABLE") {
7890
- if (!isRecord$1(raw.referenceTable)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have a "referenceTable" property`);
8903
+ if (!isRecord(raw.referenceTable)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have a "referenceTable" property`);
7891
8904
  const refTable = raw.referenceTable;
7892
- if (!isRecord$1(refTable.relatedApp)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.relatedApp"`);
7893
- if (!isRecord$1(refTable.condition)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.condition"`);
8905
+ if (!isRecord(refTable.relatedApp)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.relatedApp"`);
8906
+ if (!isRecord(refTable.condition)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.condition"`);
7894
8907
  if (!Array.isArray(refTable.displayFields)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.displayFields" array`);
7895
8908
  const condition = refTable.condition;
7896
8909
  const displayFields = refTable.displayFields.map((f) => FieldCode.create(f));
@@ -7965,6 +8978,7 @@ function parseLayoutElement(raw) {
7965
8978
  }
7966
8979
  function parseLayoutRow(raw) {
7967
8980
  if (raw.type !== "ROW") throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidLayoutStructure, `Expected layout row type "ROW", got "${String(raw.type)}"`);
8981
+ if (raw.fields !== void 0 && !Array.isArray(raw.fields)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidLayoutStructure, `ROW "fields" must be an array, got ${typeof raw.fields}`);
7968
8982
  return {
7969
8983
  type: "ROW",
7970
8984
  fields: (Array.isArray(raw.fields) ? raw.fields : []).map(parseLayoutElement)
@@ -8004,7 +9018,7 @@ function parseLayoutItem(raw) {
8004
9018
  const label = String(raw.label ?? "");
8005
9019
  const noLabel = typeof raw.noLabel === "boolean" ? raw.noLabel : void 0;
8006
9020
  const openGroup = typeof raw.openGroup === "boolean" ? raw.openGroup : void 0;
8007
- const rawLayout = Array.isArray(raw.layout) ? raw.layout.filter(isRecord$1) : [];
9021
+ const rawLayout = Array.isArray(raw.layout) ? raw.layout.filter(isRecord) : [];
8008
9022
  let groupFields = /* @__PURE__ */ new Map();
8009
9023
  const layout = [];
8010
9024
  for (const r of rawLayout) {
@@ -8089,22 +9103,21 @@ const SchemaParser = { parse: (rawText) => {
8089
9103
  } catch {
8090
9104
  throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaFormat, "Schema text is not valid YAML/JSON");
8091
9105
  }
8092
- if (!isRecord$1(parsed)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, "Schema must be an object");
9106
+ if (!isRecord(parsed)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, "Schema must be an object");
8093
9107
  const obj = parsed;
8094
9108
  if ("fields" in obj && !("layout" in obj)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, "\"fields\" key detected. Schema format has changed. Please use \"capture\" to generate a new format schema.");
8095
9109
  if (!("layout" in obj) || !Array.isArray(obj.layout)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidLayoutStructure, "Schema must have a \"layout\" array");
8096
- const rawLayout = obj.layout.filter(isRecord$1);
9110
+ const rawLayout = obj.layout;
8097
9111
  let fieldMap = /* @__PURE__ */ new Map();
8098
9112
  const layout = [];
8099
- for (const rawItem of rawLayout) {
9113
+ for (let i = 0; i < rawLayout.length; i++) {
9114
+ const rawItem = rawLayout[i];
9115
+ if (!isRecord(rawItem)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidLayoutStructure, `Layout item at index ${i} must be an object`);
8100
9116
  const result = parseLayoutItem(rawItem);
8101
9117
  layout.push(result.item);
8102
9118
  fieldMap = mergeFieldMaps(fieldMap, result.fields);
8103
9119
  }
8104
- return {
8105
- fields: fieldMap,
8106
- layout
8107
- };
9120
+ return Schema.create(fieldMap, layout);
8108
9121
  } };
8109
9122
 
8110
9123
  //#endregion
@@ -8153,14 +9166,20 @@ async function detectDiff({ container }) {
8153
9166
 
8154
9167
  //#endregion
8155
9168
  //#region src/cli/commands/schema/diff.ts
8156
- async function runDiff$1(container) {
9169
+ async function runDiff(container) {
8157
9170
  const s = p.spinner();
8158
9171
  s.start("Fetching form schema...");
8159
- const result = await detectDiff({ container });
9172
+ let result;
9173
+ try {
9174
+ result = await detectDiff({ container });
9175
+ } catch (error) {
9176
+ s.stop("Comparison failed.");
9177
+ throw error;
9178
+ }
8160
9179
  s.stop("Form schema fetched.");
8161
9180
  printDiffResult(result);
8162
9181
  }
8163
- var diff_default$1 = define({
9182
+ var diff_default$2 = define({
8164
9183
  name: "diff",
8165
9184
  description: "Detect differences between schema file and current kintone form",
8166
9185
  args: {
@@ -8171,17 +9190,17 @@ var diff_default$1 = define({
8171
9190
  try {
8172
9191
  await routeMultiApp(ctx.values, {
8173
9192
  singleLegacy: async () => {
8174
- await runDiff$1(createCliContainer(resolveConfig(ctx.values)));
9193
+ await runDiff(createCliContainer(resolveConfig(ctx.values)));
8175
9194
  },
8176
9195
  singleApp: async (app, projectConfig) => {
8177
- await runDiff$1(createCliContainer(resolveAppCliConfig(app, projectConfig, ctx.values)));
9196
+ await runDiff(createCliContainer(resolveAppCliConfig(app, projectConfig, ctx.values)));
8178
9197
  },
8179
9198
  multiApp: async (plan, projectConfig) => {
8180
9199
  await runMultiAppWithFailCheck(plan, async (app) => {
8181
9200
  const container = createCliContainer(resolveAppCliConfig(app, projectConfig, ctx.values));
8182
9201
  printAppHeader(app.name, app.appId);
8183
- await runDiff$1(container);
8184
- });
9202
+ await runDiff(container);
9203
+ }, "All schema diffs completed successfully.");
8185
9204
  }
8186
9205
  });
8187
9206
  } catch (error) {
@@ -8899,7 +9918,7 @@ var schema_default = define({
8899
9918
  name: "schema",
8900
9919
  description: "Manage kintone form schemas",
8901
9920
  subCommands: {
8902
- diff: diff_default$1,
9921
+ diff: diff_default$2,
8903
9922
  migrate: migrate_default,
8904
9923
  override: override_default,
8905
9924
  capture: capture_default$3,
@@ -8922,17 +9941,17 @@ function normalizeValue(value) {
8922
9941
  if (typeof first === "string") return value;
8923
9942
  if (typeof first === "number") return value.map(String);
8924
9943
  if (typeof first === "object" && first !== null && "code" in first && Object.keys(first).length === 1) return value;
8925
- return value.filter(isRecord$1).map((row) => {
9944
+ return value.filter(isRecord).map((row) => {
8926
9945
  const normalized = {};
8927
9946
  for (const [k, v] of Object.entries(row)) if (Array.isArray(v)) normalized[k] = v.map(String);
8928
9947
  else normalized[k] = v === null || v === void 0 ? "" : String(v);
8929
9948
  return normalized;
8930
9949
  });
8931
9950
  }
8932
- return String(value);
9951
+ throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, `Unsupported value type: ${typeof value}`);
8933
9952
  }
8934
9953
  function parseRecord(raw, index) {
8935
- if (!isRecord$1(raw)) throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, `Record at index ${index} must be an object`);
9954
+ if (!isRecord(raw)) throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, `Record at index ${index} must be an object`);
8936
9955
  const record = {};
8937
9956
  for (const [key, value] of Object.entries(raw)) record[key] = normalizeValue(value);
8938
9957
  return record;
@@ -8945,7 +9964,7 @@ const SeedParser = { parse: (rawText) => {
8945
9964
  } catch {
8946
9965
  throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedYaml, "Seed text is not valid YAML");
8947
9966
  }
8948
- if (!isRecord$1(parsed)) throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, "Seed data must be an object");
9967
+ if (!isRecord(parsed)) throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, "Seed data must be an object");
8949
9968
  const obj = parsed;
8950
9969
  const key = "key" in obj && typeof obj.key === "string" ? UpsertKey.create(obj.key) : null;
8951
9970
  if (!("records" in obj) || !Array.isArray(obj.records)) throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, "Seed data must have a \"records\" array");
@@ -8954,8 +9973,9 @@ const SeedParser = { parse: (rawText) => {
8954
9973
  for (let i = 0; i < obj.records.length; i++) {
8955
9974
  const record = parseRecord(obj.records[i], i);
8956
9975
  if (key !== null) {
8957
- if (!(key in record)) throw new BusinessRuleError(SeedDataErrorCode.SdMissingKeyField, `Record at index ${i} is missing key field "${key}"`);
8958
- const keyValue = record[key];
9976
+ const keyField = key;
9977
+ if (!(keyField in record)) throw new BusinessRuleError(SeedDataErrorCode.SdMissingKeyField, `Record at index ${i} is missing key field "${key}"`);
9978
+ const keyValue = record[keyField];
8959
9979
  if (typeof keyValue !== "string") throw new BusinessRuleError(SeedDataErrorCode.SdInvalidSeedStructure, `Key field "${key}" value at index ${i} must be a string`);
8960
9980
  if (seenKeys.has(keyValue)) throw new BusinessRuleError(SeedDataErrorCode.SdDuplicateKeyValue, `Duplicate key value "${keyValue}" at index ${i}`);
8961
9981
  seenKeys.add(keyValue);
@@ -8970,30 +9990,6 @@ const SeedParser = { parse: (rawText) => {
8970
9990
 
8971
9991
  //#endregion
8972
9992
  //#region src/core/domain/seedData/services/upsertPlanner.ts
8973
- function deepEqual(a, b) {
8974
- if (typeof a === "string" && typeof b === "string") return a === b;
8975
- if (Array.isArray(a) && Array.isArray(b)) {
8976
- if (a.length !== b.length) return false;
8977
- for (let i = 0; i < a.length; i++) {
8978
- const itemA = a[i];
8979
- const itemB = b[i];
8980
- if (typeof itemA === "string" && typeof itemB === "string") {
8981
- if (itemA !== itemB) return false;
8982
- continue;
8983
- }
8984
- if (isRecord$1(itemA) && isRecord$1(itemB)) {
8985
- const keysA = Object.keys(itemA);
8986
- const keysB = Object.keys(itemB);
8987
- if (keysA.length !== keysB.length) return false;
8988
- for (const key of keysA) if (String(itemA[key] ?? "") !== String(itemB[key] ?? "")) return false;
8989
- continue;
8990
- }
8991
- if (String(itemA) !== String(itemB)) return false;
8992
- }
8993
- return true;
8994
- }
8995
- return String(a) === String(b);
8996
- }
8997
9993
  function recordsEqual(seed, existing, keyField) {
8998
9994
  const seedKeys = Object.keys(seed).filter((k) => k !== keyField);
8999
9995
  for (const key of seedKeys) {
@@ -9269,7 +10265,7 @@ const VALID_ROUNDING_MODES = new Set([
9269
10265
  "DOWN"
9270
10266
  ]);
9271
10267
  function parseIcon(raw) {
9272
- if (!isRecord$1(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "icon must be an object with \"type\" and \"key\" properties");
10268
+ if (!isRecord(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "icon must be an object with \"type\" and \"key\" properties");
9273
10269
  const obj = raw;
9274
10270
  if (typeof obj.type !== "string" || !VALID_ICON_TYPES.has(obj.type)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidIconType, `icon.type must be PRESET or FILE, got: ${String(obj.type)}`);
9275
10271
  if (typeof obj.key !== "string" || obj.key.length === 0) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "icon must have a non-empty \"key\" property");
@@ -9279,7 +10275,7 @@ function parseIcon(raw) {
9279
10275
  };
9280
10276
  }
9281
10277
  function parseTitleField(raw) {
9282
- if (!isRecord$1(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "titleField must be an object with \"selectionMode\" property");
10278
+ if (!isRecord(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "titleField must be an object with \"selectionMode\" property");
9283
10279
  const obj = raw;
9284
10280
  if (typeof obj.selectionMode !== "string" || !VALID_SELECTION_MODES.has(obj.selectionMode)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, `titleField.selectionMode must be AUTO or MANUAL, got: ${String(obj.selectionMode)}`);
9285
10281
  const result = { selectionMode: obj.selectionMode };
@@ -9293,7 +10289,7 @@ function parseTitleField(raw) {
9293
10289
  return result;
9294
10290
  }
9295
10291
  function parseNumberPrecision(raw) {
9296
- if (!isRecord$1(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision must be an object");
10292
+ if (!isRecord(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision must be an object");
9297
10293
  const obj = raw;
9298
10294
  if (typeof obj.digits !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision.digits must be a number");
9299
10295
  if (typeof obj.decimalPlaces !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision.decimalPlaces must be a number");
@@ -9312,7 +10308,7 @@ const GeneralSettingsConfigParser = { parse: (rawText) => {
9312
10308
  } catch (error) {
9313
10309
  throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
9314
10310
  }
9315
- if (!isRecord$1(parsed)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "Config must be a YAML object");
10311
+ if (!isRecord(parsed)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "Config must be a YAML object");
9316
10312
  const obj = parsed;
9317
10313
  let name;
9318
10314
  if (obj.name !== void 0 && obj.name !== null) {
@@ -9497,6 +10493,91 @@ var capture_default$1 = define({
9497
10493
  }
9498
10494
  });
9499
10495
 
10496
+ //#endregion
10497
+ //#region src/core/domain/generalSettings/services/diffDetector.ts
10498
+ const DEFAULT_STRING = "";
10499
+ const DEFAULT_BOOLEAN = false;
10500
+ const DEFAULT_FIRST_MONTH = 1;
10501
+ function compareConfigs(local, remote) {
10502
+ const entries = [];
10503
+ function compareString(field, l, r, defaultValue, suppressValues = false) {
10504
+ const lv = l ?? defaultValue;
10505
+ const rv = r ?? defaultValue;
10506
+ if (lv !== rv) entries.push({
10507
+ type: "modified",
10508
+ field,
10509
+ details: suppressValues ? `${field} changed` : `"${rv}" -> "${lv}"`
10510
+ });
10511
+ }
10512
+ function compareBoolean(field, l, r, defaultValue) {
10513
+ const lv = l ?? defaultValue;
10514
+ const rv = r ?? defaultValue;
10515
+ if (lv !== rv) entries.push({
10516
+ type: "modified",
10517
+ field,
10518
+ details: `${String(rv)} -> ${String(lv)}`
10519
+ });
10520
+ }
10521
+ function compareNumber(field, l, r, defaultValue) {
10522
+ const lv = l ?? defaultValue;
10523
+ const rv = r ?? defaultValue;
10524
+ if (lv !== rv) entries.push({
10525
+ type: "modified",
10526
+ field,
10527
+ details: `${rv} -> ${lv}`
10528
+ });
10529
+ }
10530
+ function compareDeepEqual(field, l, r) {
10531
+ if (!deepEqual(l, r)) entries.push({
10532
+ type: "modified",
10533
+ field,
10534
+ details: `${field} changed`
10535
+ });
10536
+ }
10537
+ compareString("name", local.name, remote.name, DEFAULT_STRING);
10538
+ compareString("description", local.description, remote.description, DEFAULT_STRING, true);
10539
+ compareDeepEqual("icon", local.icon, remote.icon);
10540
+ compareString("theme", local.theme, remote.theme, DEFAULT_STRING);
10541
+ compareDeepEqual("titleField", local.titleField, remote.titleField);
10542
+ compareBoolean("enableThumbnails", local.enableThumbnails, remote.enableThumbnails, DEFAULT_BOOLEAN);
10543
+ compareBoolean("enableBulkDeletion", local.enableBulkDeletion, remote.enableBulkDeletion, DEFAULT_BOOLEAN);
10544
+ compareBoolean("enableComments", local.enableComments, remote.enableComments, DEFAULT_BOOLEAN);
10545
+ compareBoolean("enableDuplicateRecord", local.enableDuplicateRecord, remote.enableDuplicateRecord, DEFAULT_BOOLEAN);
10546
+ compareBoolean("enableInlineRecordEditing", local.enableInlineRecordEditing, remote.enableInlineRecordEditing, DEFAULT_BOOLEAN);
10547
+ compareDeepEqual("numberPrecision", local.numberPrecision, remote.numberPrecision);
10548
+ compareNumber("firstMonthOfFiscalYear", local.firstMonthOfFiscalYear, remote.firstMonthOfFiscalYear, DEFAULT_FIRST_MONTH);
10549
+ return entries;
10550
+ }
10551
+ const GeneralSettingsDiffDetector = { detect: (local, remote) => {
10552
+ return buildDiffResult(compareConfigs(local, remote));
10553
+ } };
10554
+
10555
+ //#endregion
10556
+ //#region src/core/application/generalSettings/detectGeneralSettingsDiff.ts
10557
+ async function detectGeneralSettingsDiff({ container }) {
10558
+ return detectDiffFromConfig({
10559
+ getStorage: () => container.generalSettingsStorage.get(),
10560
+ fetchRemote: () => container.generalSettingsConfigurator.getGeneralSettings(),
10561
+ parseConfig: parseGeneralSettingsConfigText,
10562
+ detect: (local, remote) => GeneralSettingsDiffDetector.detect(local, remote.config),
10563
+ notFoundMessage: "General settings config file not found"
10564
+ });
10565
+ }
10566
+
10567
+ //#endregion
10568
+ //#region src/cli/commands/settings/diff.ts
10569
+ var diff_default$1 = createDiffCommand({
10570
+ description: "Compare local general settings config with remote kintone app",
10571
+ args: settingsArgs,
10572
+ spinnerMessage: "Comparing general settings...",
10573
+ multiAppSuccessMessage: "All general settings diffs completed successfully.",
10574
+ createContainer: createGeneralSettingsCliContainer,
10575
+ detectDiff: detectGeneralSettingsDiff,
10576
+ printResult: printGeneralSettingsDiffResult,
10577
+ resolveContainerConfig: resolveSettingsContainerConfig,
10578
+ resolveAppContainerConfig: resolveSettingsAppContainerConfig
10579
+ });
10580
+
9500
10581
  //#endregion
9501
10582
  //#region src/cli/commands/settings/index.ts
9502
10583
  var settings_default = define({
@@ -9504,7 +10585,8 @@ var settings_default = define({
9504
10585
  description: "Manage kintone general settings",
9505
10586
  subCommands: {
9506
10587
  apply: apply_default$1,
9507
- capture: capture_default$1
10588
+ capture: capture_default$1,
10589
+ diff: diff_default$1
9508
10590
  },
9509
10591
  run: () => {}
9510
10592
  });
@@ -9512,7 +10594,7 @@ var settings_default = define({
9512
10594
  //#endregion
9513
10595
  //#region src/core/domain/view/services/configParser.ts
9514
10596
  function parseViewConfig(name, raw) {
9515
- if (!isRecord$1(raw)) throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigStructure, `View "${name}" must be an object`);
10597
+ if (!isRecord(raw)) throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigStructure, `View "${name}" must be an object`);
9516
10598
  const obj = raw;
9517
10599
  if (typeof obj.type !== "string" || !VALID_VIEW_TYPES.has(obj.type)) throw new BusinessRuleError(ViewErrorCode.VwInvalidViewType, `View "${name}" has invalid type: ${String(obj.type)}. Must be LIST, CALENDAR, or CUSTOM`);
9518
10600
  return {
@@ -9542,9 +10624,9 @@ const ViewConfigParser = { parse: (rawText) => {
9542
10624
  } catch (error) {
9543
10625
  throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
9544
10626
  }
9545
- if (!isRecord$1(parsed)) throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigStructure, "Config must be a YAML object");
10627
+ if (!isRecord(parsed)) throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigStructure, "Config must be a YAML object");
9546
10628
  const obj = parsed;
9547
- if (!isRecord$1(obj.views)) throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigStructure, "Config must have a \"views\" object");
10629
+ if (!isRecord(obj.views)) throw new BusinessRuleError(ViewErrorCode.VwInvalidConfigStructure, "Config must have a \"views\" object");
9548
10630
  const viewsObj = obj.views;
9549
10631
  const views = {};
9550
10632
  for (const [name, value] of Object.entries(viewsObj)) {
@@ -9693,37 +10775,21 @@ var capture_default = define({
9693
10775
 
9694
10776
  //#endregion
9695
10777
  //#region src/core/domain/view/services/diffDetector.ts
9696
- function isArrayEqual(a, b) {
9697
- if (a.length !== b.length) return false;
9698
- for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
9699
- return true;
9700
- }
9701
10778
  function describeChanges(local, remote) {
9702
10779
  const changes = [];
10780
+ if (local.name !== remote.name) changes.push(`name: "${remote.name}" -> "${local.name}"`);
9703
10781
  if (local.type !== remote.type) changes.push(`type: ${remote.type} -> ${local.type}`);
10782
+ if ((local.builtinType ?? "") !== (remote.builtinType ?? "")) changes.push("builtinType changed");
9704
10783
  if (local.index !== remote.index) changes.push(`index: ${remote.index} -> ${local.index}`);
9705
10784
  if ((local.filterCond ?? "") !== (remote.filterCond ?? "")) changes.push("filterCond changed");
9706
10785
  if ((local.sort ?? "") !== (remote.sort ?? "")) changes.push("sort changed");
9707
10786
  if ((local.date ?? "") !== (remote.date ?? "")) changes.push("date changed");
9708
10787
  if ((local.title ?? "") !== (remote.title ?? "")) changes.push("title changed");
9709
10788
  if ((local.html ?? "") !== (remote.html ?? "")) changes.push("html changed");
9710
- if (local.pager !== remote.pager) changes.push(`pager: ${String(remote.pager ?? "undefined")} -> ${String(local.pager ?? "undefined")}`);
10789
+ if ((local.pager ?? false) !== (remote.pager ?? false)) changes.push(`pager: ${String(remote.pager ?? false)} -> ${String(local.pager ?? false)}`);
9711
10790
  if ((local.device ?? "") !== (remote.device ?? "")) changes.push("device changed");
9712
- if (!isArrayEqual(local.fields ?? [], remote.fields ?? [])) changes.push("fields changed");
9713
- return changes.length > 0 ? changes.join(", ") : "no visible changes";
9714
- }
9715
- function isViewEqual(local, remote) {
9716
- if (local.type !== remote.type) return false;
9717
- if (local.index !== remote.index) return false;
9718
- if ((local.filterCond ?? "") !== (remote.filterCond ?? "")) return false;
9719
- if ((local.sort ?? "") !== (remote.sort ?? "")) return false;
9720
- if ((local.date ?? "") !== (remote.date ?? "")) return false;
9721
- if ((local.title ?? "") !== (remote.title ?? "")) return false;
9722
- if ((local.html ?? "") !== (remote.html ?? "")) return false;
9723
- if (local.pager !== remote.pager) return false;
9724
- if ((local.device ?? "") !== (remote.device ?? "")) return false;
9725
- if (!isArrayEqual(local.fields ?? [], remote.fields ?? [])) return false;
9726
- return true;
10791
+ if (!deepEqual(local.fields ?? [], remote.fields ?? [])) changes.push("fields changed");
10792
+ return changes;
9727
10793
  }
9728
10794
  const ViewDiffDetector = { detect: (localViews, remoteViews) => {
9729
10795
  const entries = [];
@@ -9734,83 +10800,47 @@ const ViewDiffDetector = { detect: (localViews, remoteViews) => {
9734
10800
  viewName: name,
9735
10801
  details: "new view"
9736
10802
  });
9737
- else if (!isViewEqual(localView, remoteView)) entries.push({
9738
- type: "modified",
9739
- viewName: name,
9740
- details: describeChanges(localView, remoteView)
9741
- });
10803
+ else {
10804
+ const changes = describeChanges(localView, remoteView);
10805
+ if (changes.length > 0) entries.push({
10806
+ type: "modified",
10807
+ viewName: name,
10808
+ details: changes.join(", ")
10809
+ });
10810
+ }
9742
10811
  }
9743
10812
  for (const name of Object.keys(remoteViews)) if (localViews[name] === void 0) entries.push({
9744
10813
  type: "deleted",
9745
10814
  viewName: name,
9746
- details: "deleted"
10815
+ details: "removed"
9747
10816
  });
9748
- const added = entries.filter((e) => e.type === "added").length;
9749
- const modified = entries.filter((e) => e.type === "modified").length;
9750
- const deleted = entries.filter((e) => e.type === "deleted").length;
9751
- return {
9752
- entries,
9753
- summary: {
9754
- added,
9755
- modified,
9756
- deleted,
9757
- total: added + modified + deleted
9758
- },
9759
- isEmpty: entries.length === 0
9760
- };
10817
+ return buildDiffResult(entries);
9761
10818
  } };
9762
10819
 
9763
10820
  //#endregion
9764
10821
  //#region src/core/application/view/detectViewDiff.ts
9765
10822
  async function detectViewDiff({ container }) {
9766
- const result = await container.viewStorage.get();
9767
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "View config file not found");
9768
- const config = parseViewConfigText(result.content);
9769
- const { views: remoteViews } = await container.viewConfigurator.getViews();
9770
- const diff = ViewDiffDetector.detect(config.views, remoteViews);
9771
- return {
9772
- entries: diff.entries,
9773
- summary: diff.summary,
9774
- isEmpty: diff.isEmpty
9775
- };
10823
+ return detectDiffFromConfig({
10824
+ getStorage: () => container.viewStorage.get(),
10825
+ fetchRemote: () => container.viewConfigurator.getViews(),
10826
+ parseConfig: (content) => parseViewConfigText(content).views,
10827
+ detect: (views, remote) => ViewDiffDetector.detect(views, remote.views),
10828
+ notFoundMessage: "View config file not found"
10829
+ });
9776
10830
  }
9777
10831
 
9778
10832
  //#endregion
9779
10833
  //#region src/cli/commands/view/diff.ts
9780
- async function runDiff(config) {
9781
- const container = createViewCliContainer(config);
9782
- const s = p.spinner();
9783
- s.start("Fetching views...");
9784
- const result = await detectViewDiff({ container });
9785
- s.stop("Views fetched.");
9786
- printViewDiffResult(result);
9787
- }
9788
- var diff_default = define({
9789
- name: "diff",
9790
- description: "Detect differences between view config file and current kintone views",
10834
+ var diff_default = createDiffCommand({
10835
+ description: "Compare local view config with remote kintone app",
9791
10836
  args: viewArgs,
9792
- run: async (ctx) => {
9793
- try {
9794
- const values = ctx.values;
9795
- await routeMultiApp(values, {
9796
- singleLegacy: async () => {
9797
- await runDiff(resolveViewContainerConfig(values));
9798
- },
9799
- singleApp: async (app, projectConfig) => {
9800
- await runDiff(resolveViewAppContainerConfig(app, projectConfig, values));
9801
- },
9802
- multiApp: async (plan, projectConfig) => {
9803
- await runMultiAppWithFailCheck(plan, async (app) => {
9804
- const config = resolveViewAppContainerConfig(app, projectConfig, values);
9805
- printAppHeader(app.name, app.appId);
9806
- await runDiff(config);
9807
- });
9808
- }
9809
- });
9810
- } catch (error) {
9811
- handleCliError(error);
9812
- }
9813
- }
10837
+ spinnerMessage: "Comparing view settings...",
10838
+ multiAppSuccessMessage: "All view diffs completed successfully.",
10839
+ createContainer: createViewCliContainer,
10840
+ detectDiff: detectViewDiff,
10841
+ printResult: printViewDiffResult,
10842
+ resolveContainerConfig: resolveViewContainerConfig,
10843
+ resolveAppContainerConfig: resolveViewAppContainerConfig
9814
10844
  });
9815
10845
 
9816
10846
  //#endregion