kintone-migrator 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -321,6 +321,18 @@ function isSystemError(error) {
321
321
  return error instanceof SystemError;
322
322
  }
323
323
 
324
+ //#endregion
325
+ //#region src/lib/typeGuards.ts
326
+ /**
327
+ * Narrows `unknown` to a plain `Record<string, unknown>`.
328
+ * Returns true when the value is a non-null, non-array plain object.
329
+ * Excludes built-in types (Date, RegExp, Map, Set) that are technically
330
+ * objects but should not be treated as string-keyed records.
331
+ */
332
+ function isRecord$1(value) {
333
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp) && !(value instanceof Map) && !(value instanceof Set);
334
+ }
335
+
324
336
  //#endregion
325
337
  //#region src/core/domain/typeGuards.ts
326
338
  /**
@@ -328,13 +340,6 @@ function isSystemError(error) {
328
340
  * Use these functions instead of `as` casts when working with `unknown` values.
329
341
  */
330
342
  /**
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
343
  * Narrows `unknown` to `{ code: string }`.
339
344
  */
340
345
  function hasCode(value) {
@@ -1964,42 +1969,73 @@ function colorizeDiffEntry(type) {
1964
1969
  prefix: type === "added" ? "+" : type === "deleted" ? "-" : "~"
1965
1970
  };
1966
1971
  }
1967
- function printDiffResult(result) {
1972
+ function printGenericDiffResult(result, title, formatEntry) {
1973
+ for (const w of result.warnings) p.log.warn(w);
1968
1974
  if (result.isEmpty) {
1969
1975
  p.log.info("No changes detected.");
1970
1976
  return;
1971
1977
  }
1972
1978
  p.log.info(`Changes: ${formatDiffSummary(result.summary)}`);
1973
- if (result.hasLayoutChanges) p.log.info("Layout changes detected.");
1974
1979
  const lines = result.entries.map((entry) => {
1975
1980
  const { colorize, prefix } = colorizeDiffEntry(entry.type);
1976
- return `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]")} ${entry.fieldLabel}${pc.dim(":")} ${entry.details}`;
1981
+ return formatEntry(entry, colorize, prefix);
1977
1982
  });
1978
- p.note(lines.join("\n"), "Diff Details", { format: (v) => v });
1983
+ p.note(lines.join("\n"), title, { format: (v) => v });
1979
1984
  }
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}`;
1985
+ function printActionDiffResult(result) {
1986
+ printGenericDiffResult(result, "Action Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.actionName)}${pc.dim(":")} ${entry.details}`);
1987
+ }
1988
+ function printAdminNotesDiffResult(result) {
1989
+ printFieldDiffResult(result, "Admin Notes Diff Details");
1990
+ }
1991
+ function printAppPermissionDiffResult(result) {
1992
+ printGenericDiffResult(result, "App Permission Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.entityKey)}${pc.dim(":")} ${entry.details}`);
1993
+ }
1994
+ function printCustomizationDiffResult(result) {
1995
+ printGenericDiffResult(result, "Customization Diff Details", (entry, colorize, prefix) => {
1996
+ const location = entry.platform === "config" ? entry.category : `${entry.platform}.${entry.category}`;
1997
+ return `${colorize(prefix)} ${pc.dim("[")}${colorize(location)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`;
1989
1998
  });
1990
- p.note(lines.join("\n"), "View Diff Details", { format: (v) => v });
1991
1999
  }
1992
- function printProcessDiffResult(result) {
2000
+ function printDiffResult(result) {
1993
2001
  if (result.isEmpty) {
1994
2002
  p.log.info("No changes detected.");
1995
2003
  return;
1996
2004
  }
1997
2005
  p.log.info(`Changes: ${formatDiffSummary(result.summary)}`);
2006
+ if (result.hasLayoutChanges) p.log.info("Layout changes detected.");
1998
2007
  const lines = result.entries.map((entry) => {
1999
2008
  const { colorize, prefix } = colorizeDiffEntry(entry.type);
2000
- return `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.category)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`;
2009
+ return `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]")} ${entry.fieldLabel}${pc.dim(":")} ${entry.details}`;
2001
2010
  });
2002
- p.note(lines.join("\n"), "Process Management Diff Details", { format: (v) => v });
2011
+ p.note(lines.join("\n"), "Diff Details", { format: (v) => v });
2012
+ }
2013
+ function printFieldPermissionDiffResult(result) {
2014
+ printGenericDiffResult(result, "Field Permission Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]:")} ${entry.details}`);
2015
+ }
2016
+ function printGeneralSettingsDiffResult(result) {
2017
+ printFieldDiffResult(result, "General Settings Diff Details");
2018
+ }
2019
+ function printNotificationDiffResult(result) {
2020
+ printGenericDiffResult(result, "Notification Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.section)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`);
2021
+ }
2022
+ function printPluginDiffResult(result) {
2023
+ printGenericDiffResult(result, "Plugin Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.pluginId)}${pc.dim(":")} ${entry.details}`);
2024
+ }
2025
+ function printProcessDiffResult(result) {
2026
+ printGenericDiffResult(result, "Process Management Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.category)}${pc.dim("]")} ${entry.name}${pc.dim(":")} ${entry.details}`);
2027
+ }
2028
+ function printRecordPermissionDiffResult(result) {
2029
+ printGenericDiffResult(result, "Record Permission Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${pc.dim("[")}${colorize(entry.filterCond === "" ? "(all records)" : entry.filterCond)}${pc.dim("]:")} ${entry.details}`);
2030
+ }
2031
+ function printReportDiffResult(result) {
2032
+ printGenericDiffResult(result, "Report Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.reportName)}${pc.dim(":")} ${entry.details}`);
2033
+ }
2034
+ function printViewDiffResult(result) {
2035
+ printGenericDiffResult(result, "View Diff Details", (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.viewName)}${pc.dim(":")} ${entry.details}`);
2036
+ }
2037
+ function printFieldDiffResult(result, title) {
2038
+ printGenericDiffResult(result, title, (entry, colorize, prefix) => `${colorize(prefix)} ${colorize(entry.field)}${pc.dim(":")} ${entry.details}`);
2003
2039
  }
2004
2040
  function printAppHeader(appName, appId) {
2005
2041
  p.log.step(`\n=== [${pc.bold(appName)}] (app: ${appId}) ===`);
@@ -2051,8 +2087,13 @@ async function promptDeploy(container, skipConfirm) {
2051
2087
  }
2052
2088
  const ds = p.spinner();
2053
2089
  ds.start("Deploying to production...");
2054
- await deployApp({ container });
2055
- ds.stop("Deployment complete.");
2090
+ try {
2091
+ await deployApp({ container });
2092
+ ds.stop("Deployment complete.");
2093
+ } catch (error) {
2094
+ ds.stop("Deployment failed.");
2095
+ throw error;
2096
+ }
2056
2097
  p.log.success("Deployed to production.");
2057
2098
  }
2058
2099
 
@@ -2321,7 +2362,7 @@ function serializeMapping(mapping) {
2321
2362
  if (mapping.srcField !== void 0) result.srcField = mapping.srcField;
2322
2363
  return result;
2323
2364
  }
2324
- function serializeEntity$1(entity) {
2365
+ function serializeEntity$2(entity) {
2325
2366
  return {
2326
2367
  type: entity.type,
2327
2368
  code: entity.code
@@ -2332,7 +2373,7 @@ function serializeActionConfig(config) {
2332
2373
  index: config.index,
2333
2374
  destApp: serializeDestApp(config.destApp),
2334
2375
  mappings: config.mappings.map(serializeMapping),
2335
- entities: config.entities.map(serializeEntity$1),
2376
+ entities: config.entities.map(serializeEntity$2),
2336
2377
  filterCond: config.filterCond
2337
2378
  };
2338
2379
  }
@@ -2407,6 +2448,238 @@ var capture_default$13 = define({
2407
2448
  }
2408
2449
  });
2409
2450
 
2451
+ //#endregion
2452
+ //#region src/lib/deepEqual.ts
2453
+ function isArrayEqual(a, b, seen) {
2454
+ if (!Array.isArray(b)) return false;
2455
+ if (a.length !== b.length) return false;
2456
+ for (let i = 0; i < a.length; i++) if (!deepEqualInner(a[i], b[i], seen)) return false;
2457
+ return true;
2458
+ }
2459
+ function isRecordEqual(a, b, seen) {
2460
+ const keysA = Object.keys(a);
2461
+ const keysB = Object.keys(b);
2462
+ if (keysA.length !== keysB.length) return false;
2463
+ for (const key of keysA) {
2464
+ if (!Object.hasOwn(b, key)) return false;
2465
+ if (!deepEqualInner(a[key], b[key], seen)) return false;
2466
+ }
2467
+ return true;
2468
+ }
2469
+ function isMapEqual$1(a, b, seen) {
2470
+ if (!(b instanceof Map)) return false;
2471
+ if (a.size !== b.size) return false;
2472
+ for (const [key, valA] of a) {
2473
+ if (!b.has(key)) return false;
2474
+ if (!deepEqualInner(valA, b.get(key), seen)) return false;
2475
+ }
2476
+ return true;
2477
+ }
2478
+ function isSetEqual(a, b, _seen) {
2479
+ if (!(b instanceof Set)) return false;
2480
+ if (a.size !== b.size) return false;
2481
+ const remaining = [...b];
2482
+ for (const valA of a) {
2483
+ const idx = remaining.findIndex((valB) => deepEqualInner(valA, valB, /* @__PURE__ */ new WeakSet()));
2484
+ if (idx === -1) return false;
2485
+ remaining.splice(idx, 1);
2486
+ }
2487
+ return true;
2488
+ }
2489
+ function deepEqualInner(a, b, seen) {
2490
+ if (a === b) return true;
2491
+ if (a === null || b === null) return a === b;
2492
+ if (typeof a !== typeof b) return false;
2493
+ if (typeof a !== "object") return false;
2494
+ const objA = a;
2495
+ const objB = b;
2496
+ if (seen.has(objA) || seen.has(objB)) return false;
2497
+ seen.add(objA);
2498
+ seen.add(objB);
2499
+ if (Array.isArray(objA)) return isArrayEqual(objA, objB, seen);
2500
+ if (objA instanceof Date && objB instanceof Date) return objA.getTime() === objB.getTime();
2501
+ if (objA instanceof Date || objB instanceof Date) return false;
2502
+ if (objA instanceof RegExp && objB instanceof RegExp) return String(objA) === String(objB);
2503
+ if (objA instanceof RegExp || objB instanceof RegExp) return false;
2504
+ if (objA instanceof Map) return isMapEqual$1(objA, objB, seen);
2505
+ if (objB instanceof Map) return false;
2506
+ if (objA instanceof Set) return isSetEqual(objA, objB, seen);
2507
+ if (objB instanceof Set) return false;
2508
+ if (isRecord$1(objA) && isRecord$1(objB)) return isRecordEqual(objA, objB, seen);
2509
+ return false;
2510
+ }
2511
+ /**
2512
+ * Deep equality comparison for structured data.
2513
+ * Supports primitives, plain objects, arrays, Date, RegExp, Map, and Set.
2514
+ * Has circular reference protection via a WeakSet-based seen check (tracks both sides).
2515
+ *
2516
+ * Note: `{ a: undefined }` and `{}` are NOT considered equal. This differs from
2517
+ * JSON.stringify behavior but is intentional — explicit undefined properties are
2518
+ * semantically distinct from absent properties.
2519
+ *
2520
+ * Set comparison is order-independent: each element in one set is matched to an
2521
+ * element in the other using deep equality (O(n²)).
2522
+ *
2523
+ * NaN handling: `deepEqual(NaN, NaN)` returns `false` because the comparison
2524
+ * uses strict equality (`===`) for primitives, and `NaN !== NaN` in JavaScript.
2525
+ *
2526
+ * Circular reference limitation: objects already visited by the traversal are
2527
+ * treated as non-equal. This means structurally identical circular references
2528
+ * (e.g. `a.self = a` vs `b.self = b`) return `false`. This is a conservative
2529
+ * approach that prevents infinite recursion but may produce false negatives.
2530
+ */
2531
+ function deepEqual$1(a, b) {
2532
+ return deepEqualInner(a, b, /* @__PURE__ */ new WeakSet());
2533
+ }
2534
+
2535
+ //#endregion
2536
+ //#region src/core/domain/diff.ts
2537
+ const typeOrder = {
2538
+ added: 0,
2539
+ modified: 1,
2540
+ deleted: 2
2541
+ };
2542
+ function buildDiffResult(entries, warnings = []) {
2543
+ const sorted = [...entries].sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);
2544
+ let added = 0;
2545
+ let modified = 0;
2546
+ let deleted = 0;
2547
+ for (const e of sorted) if (e.type === "added") added++;
2548
+ else if (e.type === "modified") modified++;
2549
+ else deleted++;
2550
+ return {
2551
+ entries: sorted,
2552
+ summary: {
2553
+ added,
2554
+ modified,
2555
+ deleted,
2556
+ total: sorted.length
2557
+ },
2558
+ isEmpty: sorted.length === 0,
2559
+ warnings
2560
+ };
2561
+ }
2562
+
2563
+ //#endregion
2564
+ //#region src/core/domain/action/services/diffDetector.ts
2565
+ function compareActions$1(local, remote) {
2566
+ const diffs = [];
2567
+ if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
2568
+ if (local.name !== remote.name) diffs.push(`name: "${remote.name}" -> "${local.name}"`);
2569
+ if (!deepEqual$1(local.destApp, remote.destApp)) diffs.push("destApp changed");
2570
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
2571
+ if (!deepEqual$1(local.mappings, remote.mappings)) if (local.mappings.length !== remote.mappings.length) diffs.push(`mappings: ${remote.mappings.length} -> ${local.mappings.length}`);
2572
+ else diffs.push("mappings changed");
2573
+ if (!deepEqual$1(local.entities, remote.entities)) diffs.push("entities changed");
2574
+ return diffs;
2575
+ }
2576
+ const ActionDiffDetector = { detect: (local, remote) => {
2577
+ const entries = [];
2578
+ for (const [name, localAction] of Object.entries(local.actions)) {
2579
+ const remoteAction = remote.actions[name];
2580
+ if (!remoteAction) entries.push({
2581
+ type: "added",
2582
+ actionName: name,
2583
+ details: `dest: ${localAction.destApp.app ?? localAction.destApp.code ?? "(unspecified)"}`
2584
+ });
2585
+ else {
2586
+ const diffs = compareActions$1(localAction, remoteAction);
2587
+ if (diffs.length > 0) entries.push({
2588
+ type: "modified",
2589
+ actionName: name,
2590
+ details: diffs.join(", ")
2591
+ });
2592
+ }
2593
+ }
2594
+ for (const [name, remoteAction] of Object.entries(remote.actions)) if (!local.actions[name]) entries.push({
2595
+ type: "deleted",
2596
+ actionName: name,
2597
+ details: `dest: ${remoteAction.destApp.app ?? remoteAction.destApp.code ?? "(unspecified)"}`
2598
+ });
2599
+ return buildDiffResult(entries);
2600
+ } };
2601
+
2602
+ //#endregion
2603
+ //#region src/core/application/detectDiffBase.ts
2604
+ async function detectDiffFromConfig(config) {
2605
+ const [storageResult, remote] = await Promise.all([config.getStorage(), config.fetchRemote()]);
2606
+ if (!storageResult.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, config.notFoundMessage);
2607
+ const local = config.parseConfig(storageResult.content);
2608
+ return config.detect(local, remote);
2609
+ }
2610
+
2611
+ //#endregion
2612
+ //#region src/core/application/action/detectActionDiff.ts
2613
+ async function detectActionDiff({ container }) {
2614
+ return detectDiffFromConfig({
2615
+ getStorage: () => container.actionStorage.get(),
2616
+ fetchRemote: () => container.actionConfigurator.getActions(),
2617
+ parseConfig: parseActionConfigText,
2618
+ detect: (local, remote) => ActionDiffDetector.detect(local, { actions: remote.actions }),
2619
+ notFoundMessage: "Action config file not found"
2620
+ });
2621
+ }
2622
+
2623
+ //#endregion
2624
+ //#region src/cli/commands/diffCommandFactory.ts
2625
+ function createDiffCommand(config) {
2626
+ async function runDiff(containerConfig) {
2627
+ const container = config.createContainer(containerConfig);
2628
+ const s = p.spinner();
2629
+ s.start(config.spinnerMessage);
2630
+ let result;
2631
+ try {
2632
+ result = await config.detectDiff({ container });
2633
+ } catch (error) {
2634
+ s.stop("Comparison failed.");
2635
+ throw error;
2636
+ }
2637
+ s.stop("Comparison complete.");
2638
+ config.printResult(result);
2639
+ }
2640
+ return define({
2641
+ name: "diff",
2642
+ description: config.description,
2643
+ args: config.args,
2644
+ run: async (ctx) => {
2645
+ try {
2646
+ const values = ctx.values;
2647
+ await routeMultiApp(values, {
2648
+ singleLegacy: async () => {
2649
+ await runDiff(config.resolveContainerConfig(values));
2650
+ },
2651
+ singleApp: async (app, projectConfig) => {
2652
+ await runDiff(config.resolveAppContainerConfig(app, projectConfig, values));
2653
+ },
2654
+ multiApp: async (plan, projectConfig) => {
2655
+ await runMultiAppWithFailCheck(plan, async (app) => {
2656
+ const containerConfig = config.resolveAppContainerConfig(app, projectConfig, values);
2657
+ printAppHeader(app.name, app.appId);
2658
+ await runDiff(containerConfig);
2659
+ }, config.multiAppSuccessMessage);
2660
+ }
2661
+ });
2662
+ } catch (error) {
2663
+ handleCliError(error);
2664
+ }
2665
+ }
2666
+ });
2667
+ }
2668
+
2669
+ //#endregion
2670
+ //#region src/cli/commands/action/diff.ts
2671
+ var diff_default$12 = createDiffCommand({
2672
+ description: "Compare local action config with remote kintone app",
2673
+ args: actionArgs,
2674
+ spinnerMessage: "Comparing action settings...",
2675
+ multiAppSuccessMessage: "All action diffs completed successfully.",
2676
+ createContainer: createActionCliContainer,
2677
+ detectDiff: detectActionDiff,
2678
+ printResult: printActionDiffResult,
2679
+ resolveContainerConfig: resolveActionContainerConfig,
2680
+ resolveAppContainerConfig: resolveActionAppContainerConfig
2681
+ });
2682
+
2410
2683
  //#endregion
2411
2684
  //#region src/cli/commands/action/index.ts
2412
2685
  var action_default = define({
@@ -2414,7 +2687,8 @@ var action_default = define({
2414
2687
  description: "Manage kintone action settings",
2415
2688
  subCommands: {
2416
2689
  apply: apply_default$12,
2417
- capture: capture_default$13
2690
+ capture: capture_default$13,
2691
+ diff: diff_default$12
2418
2692
  },
2419
2693
  run: () => {}
2420
2694
  });
@@ -2662,6 +2936,52 @@ var capture_default$12 = define({
2662
2936
  }
2663
2937
  });
2664
2938
 
2939
+ //#endregion
2940
+ //#region src/core/domain/adminNotes/services/diffDetector.ts
2941
+ function compareConfigs$2(local, remote) {
2942
+ const entries = [];
2943
+ if (local.content !== remote.content) entries.push({
2944
+ type: "modified",
2945
+ field: "content",
2946
+ details: "content changed"
2947
+ });
2948
+ if (local.includeInTemplateAndDuplicates !== remote.includeInTemplateAndDuplicates) entries.push({
2949
+ type: "modified",
2950
+ field: "includeInTemplateAndDuplicates",
2951
+ details: `${String(remote.includeInTemplateAndDuplicates)} -> ${String(local.includeInTemplateAndDuplicates)}`
2952
+ });
2953
+ return entries;
2954
+ }
2955
+ const AdminNotesDiffDetector = { detect: (local, remote) => {
2956
+ return buildDiffResult(compareConfigs$2(local, remote));
2957
+ } };
2958
+
2959
+ //#endregion
2960
+ //#region src/core/application/adminNotes/detectAdminNotesDiff.ts
2961
+ async function detectAdminNotesDiff({ container }) {
2962
+ return detectDiffFromConfig({
2963
+ getStorage: () => container.adminNotesStorage.get(),
2964
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
2965
+ parseConfig: parseAdminNotesConfigText,
2966
+ detect: (local, remote) => AdminNotesDiffDetector.detect(local, remote.config),
2967
+ notFoundMessage: "Admin notes config file not found"
2968
+ });
2969
+ }
2970
+
2971
+ //#endregion
2972
+ //#region src/cli/commands/admin-notes/diff.ts
2973
+ var diff_default$11 = createDiffCommand({
2974
+ description: "Compare local admin notes config with remote kintone app",
2975
+ args: adminNotesArgs,
2976
+ spinnerMessage: "Comparing admin notes...",
2977
+ multiAppSuccessMessage: "All admin notes diffs completed successfully.",
2978
+ createContainer: createAdminNotesCliContainer,
2979
+ detectDiff: detectAdminNotesDiff,
2980
+ printResult: printAdminNotesDiffResult,
2981
+ resolveContainerConfig: resolveAdminNotesContainerConfig,
2982
+ resolveAppContainerConfig: resolveAdminNotesAppContainerConfig
2983
+ });
2984
+
2665
2985
  //#endregion
2666
2986
  //#region src/cli/commands/admin-notes/index.ts
2667
2987
  var admin_notes_default = define({
@@ -2669,7 +2989,8 @@ var admin_notes_default = define({
2669
2989
  description: "Manage kintone admin notes",
2670
2990
  subCommands: {
2671
2991
  apply: apply_default$11,
2672
- capture: capture_default$12
2992
+ capture: capture_default$12,
2993
+ diff: diff_default$11
2673
2994
  },
2674
2995
  run: () => {}
2675
2996
  });
@@ -3016,6 +3337,84 @@ var capture_default$11 = define({
3016
3337
  }
3017
3338
  });
3018
3339
 
3340
+ //#endregion
3341
+ //#region src/core/domain/appPermission/services/diffDetector.ts
3342
+ const BOOLEAN_FLAGS = [
3343
+ "includeSubs",
3344
+ "appEditable",
3345
+ "recordViewable",
3346
+ "recordAddable",
3347
+ "recordEditable",
3348
+ "recordDeletable",
3349
+ "recordImportable",
3350
+ "recordExportable"
3351
+ ];
3352
+ function entityKey(right) {
3353
+ return `${right.entity.type}:${right.entity.code}`;
3354
+ }
3355
+ function describeRight$1(right) {
3356
+ const flags = BOOLEAN_FLAGS.filter((f) => f !== "includeSubs" && right[f]);
3357
+ return flags.length > 0 ? flags.join(", ") : "no permissions";
3358
+ }
3359
+ function compareRights(local, remote) {
3360
+ const diffs = [];
3361
+ for (const flag of BOOLEAN_FLAGS) if (local[flag] !== remote[flag]) diffs.push(`${flag}: ${String(remote[flag])} -> ${String(local[flag])}`);
3362
+ return diffs;
3363
+ }
3364
+ const AppPermissionDiffDetector = { detect: (local, remote) => {
3365
+ const entries = [];
3366
+ const localMap = new Map(local.rights.map((r) => [entityKey(r), r]));
3367
+ const remoteMap = new Map(remote.rights.map((r) => [entityKey(r), r]));
3368
+ for (const [key, localRight] of localMap) {
3369
+ const remoteRight = remoteMap.get(key);
3370
+ if (!remoteRight) entries.push({
3371
+ type: "added",
3372
+ entityKey: key,
3373
+ details: describeRight$1(localRight)
3374
+ });
3375
+ else {
3376
+ const diffs = compareRights(localRight, remoteRight);
3377
+ if (diffs.length > 0) entries.push({
3378
+ type: "modified",
3379
+ entityKey: key,
3380
+ details: diffs.join(", ")
3381
+ });
3382
+ }
3383
+ }
3384
+ for (const [key, remoteRight] of remoteMap) if (!localMap.has(key)) entries.push({
3385
+ type: "deleted",
3386
+ entityKey: key,
3387
+ details: describeRight$1(remoteRight)
3388
+ });
3389
+ return buildDiffResult(entries);
3390
+ } };
3391
+
3392
+ //#endregion
3393
+ //#region src/core/application/appPermission/detectAppPermissionDiff.ts
3394
+ async function detectAppPermissionDiff({ container }) {
3395
+ return detectDiffFromConfig({
3396
+ getStorage: () => container.appPermissionStorage.get(),
3397
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3398
+ parseConfig: parseAppPermissionConfigText,
3399
+ detect: (local, remote) => AppPermissionDiffDetector.detect(local, { rights: remote.rights }),
3400
+ notFoundMessage: "App permission config file not found"
3401
+ });
3402
+ }
3403
+
3404
+ //#endregion
3405
+ //#region src/cli/commands/app-acl/diff.ts
3406
+ var diff_default$10 = createDiffCommand({
3407
+ description: "Compare local app permission config with remote kintone app",
3408
+ args: appAclArgs,
3409
+ spinnerMessage: "Comparing app permissions...",
3410
+ multiAppSuccessMessage: "All app permission diffs completed successfully.",
3411
+ createContainer: createAppPermissionCliContainer,
3412
+ detectDiff: detectAppPermissionDiff,
3413
+ printResult: printAppPermissionDiffResult,
3414
+ resolveContainerConfig: resolveAppAclContainerConfig,
3415
+ resolveAppContainerConfig: resolveAppAclAppContainerConfig
3416
+ });
3417
+
3019
3418
  //#endregion
3020
3419
  //#region src/cli/commands/app-acl/index.ts
3021
3420
  var app_acl_default = define({
@@ -3023,7 +3422,8 @@ var app_acl_default = define({
3023
3422
  description: "Manage kintone app access permissions",
3024
3423
  subCommands: {
3025
3424
  apply: apply_default$10,
3026
- capture: capture_default$11
3425
+ capture: capture_default$11,
3426
+ diff: diff_default$10
3027
3427
  },
3028
3428
  run: () => {}
3029
3429
  });
@@ -3499,6 +3899,105 @@ var apply_default$9 = define({
3499
3899
  }
3500
3900
  });
3501
3901
 
3902
+ //#endregion
3903
+ //#region src/core/domain/customization/valueObject.ts
3904
+ const DEFAULT_CUSTOMIZATION_SCOPE = "ALL";
3905
+
3906
+ //#endregion
3907
+ //#region src/core/domain/customization/services/diffDetector.ts
3908
+ function resourceName(resource) {
3909
+ if (resource.type === "URL") return resource.url;
3910
+ const parts = resource.path.replace(/\\/g, "/").split("/");
3911
+ return parts[parts.length - 1];
3912
+ }
3913
+ function remoteResourceName(resource) {
3914
+ if (resource.type === "URL") return resource.url;
3915
+ return resource.file.name;
3916
+ }
3917
+ function compareResourceLists(localResources, remoteResources, platform, resourceType, warnings) {
3918
+ const entries = [];
3919
+ const localNames = localResources.map(resourceName);
3920
+ const remoteNames = remoteResources.map(remoteResourceName);
3921
+ const localNameSet = new Set(localNames);
3922
+ const remoteNameSet = new Set(remoteNames);
3923
+ const hasDuplicates = localNames.length !== localNameSet.size || remoteNames.length !== remoteNameSet.size;
3924
+ if (hasDuplicates) warnings.push(`[${platform}.${resourceType}] duplicate basenames detected; diff results may be inaccurate for FILE resources`);
3925
+ for (const name of localNameSet) if (!remoteNameSet.has(name)) entries.push({
3926
+ type: "added",
3927
+ platform,
3928
+ category: resourceType,
3929
+ name,
3930
+ details: "new resource"
3931
+ });
3932
+ for (const name of remoteNameSet) if (!localNameSet.has(name)) entries.push({
3933
+ type: "deleted",
3934
+ platform,
3935
+ category: resourceType,
3936
+ name,
3937
+ details: "removed"
3938
+ });
3939
+ const matchedFiles = [...localNameSet].filter((n) => remoteNameSet.has(n));
3940
+ const hasLocalFiles = localResources.some((r) => r.type === "FILE");
3941
+ const hasRemoteFiles = remoteResources.some((r) => r.type === "FILE");
3942
+ if (matchedFiles.length > 0 && hasLocalFiles && hasRemoteFiles) warnings.push(`[${platform}.${resourceType}] FILE resources are compared by name only; content changes are not detected`);
3943
+ if (!hasDuplicates) {
3944
+ const localShared = localNames.filter((n) => remoteNameSet.has(n));
3945
+ const remoteShared = remoteNames.filter((n) => localNameSet.has(n));
3946
+ if (localShared.length > 1 && localShared.length === remoteShared.length && localShared.some((n, i) => n !== remoteShared[i])) entries.push({
3947
+ type: "modified",
3948
+ platform,
3949
+ category: resourceType,
3950
+ name: "(order)",
3951
+ details: "resource load order changed"
3952
+ });
3953
+ }
3954
+ return entries;
3955
+ }
3956
+ function comparePlatform(localJs, localCss, remote, platform, warnings) {
3957
+ return [...compareResourceLists(localJs, remote.js, platform, "js", warnings), ...compareResourceLists(localCss, remote.css, platform, "css", warnings)];
3958
+ }
3959
+ const CustomizationDiffDetector = { detect: (local, remote) => {
3960
+ const entries = [];
3961
+ const warnings = [];
3962
+ const localScope = local.scope ?? DEFAULT_CUSTOMIZATION_SCOPE;
3963
+ if (localScope !== remote.scope) entries.push({
3964
+ type: "modified",
3965
+ platform: "config",
3966
+ category: "scope",
3967
+ name: "scope",
3968
+ details: `${remote.scope} -> ${localScope}`
3969
+ });
3970
+ entries.push(...comparePlatform(local.desktop.js, local.desktop.css, remote.desktop, "desktop", warnings));
3971
+ entries.push(...comparePlatform(local.mobile.js, local.mobile.css, remote.mobile, "mobile", warnings));
3972
+ return buildDiffResult(entries, warnings);
3973
+ } };
3974
+
3975
+ //#endregion
3976
+ //#region src/core/application/customization/detectCustomizationDiff.ts
3977
+ async function detectCustomizationDiff({ container }) {
3978
+ return detectDiffFromConfig({
3979
+ getStorage: () => container.customizationStorage.get(),
3980
+ fetchRemote: () => container.customizationConfigurator.getCustomization(),
3981
+ parseConfig: (content) => parseConfigText(content),
3982
+ detect: (local, remote) => CustomizationDiffDetector.detect(local, remote),
3983
+ notFoundMessage: "Customization config file not found"
3984
+ });
3985
+ }
3986
+
3987
+ //#endregion
3988
+ //#region src/cli/commands/customize/diff.ts
3989
+ var diff_default$9 = createDiffCommand({
3990
+ description: "Compare local customization config with remote kintone app",
3991
+ args: customizeArgs,
3992
+ spinnerMessage: "Comparing customization settings...",
3993
+ multiAppSuccessMessage: "All customization diffs completed successfully.",
3994
+ createContainer: createCustomizationCliContainer,
3995
+ detectDiff: detectCustomizationDiff,
3996
+ printResult: printCustomizationDiffResult,
3997
+ resolveContainerConfig: resolveCustomizeConfig,
3998
+ resolveAppContainerConfig: resolveCustomizeAppConfig
3999
+ });
4000
+
3502
4001
  //#endregion
3503
4002
  //#region src/cli/commands/customize/index.ts
3504
4003
  var customize_default = define({
@@ -3506,7 +4005,8 @@ var customize_default = define({
3506
4005
  description: "Manage kintone JS/CSS customizations",
3507
4006
  subCommands: {
3508
4007
  apply: apply_default$9,
3509
- capture: capture_default$10
4008
+ capture: capture_default$10,
4009
+ diff: diff_default$9
3510
4010
  },
3511
4011
  run: () => {}
3512
4012
  });
@@ -3860,6 +4360,72 @@ var capture_default$9 = define({
3860
4360
  }
3861
4361
  });
3862
4362
 
4363
+ //#endregion
4364
+ //#region src/core/domain/fieldPermission/services/diffDetector.ts
4365
+ function areEntitiesEqual(a, b) {
4366
+ return deepEqual$1(a.entities.map((e) => ({
4367
+ accessibility: e.accessibility,
4368
+ type: e.entity.type,
4369
+ code: e.entity.code,
4370
+ includeSubs: e.includeSubs ?? false
4371
+ })), b.entities.map((e) => ({
4372
+ accessibility: e.accessibility,
4373
+ type: e.entity.type,
4374
+ code: e.entity.code,
4375
+ includeSubs: e.includeSubs ?? false
4376
+ })));
4377
+ }
4378
+ const FieldPermissionDiffDetector = { detect: (local, remote) => {
4379
+ const entries = [];
4380
+ const localMap = new Map(local.rights.map((r) => [r.code, r]));
4381
+ const remoteMap = new Map(remote.rights.map((r) => [r.code, r]));
4382
+ for (const [code, localRight] of localMap) {
4383
+ const remoteRight = remoteMap.get(code);
4384
+ if (!remoteRight) entries.push({
4385
+ type: "added",
4386
+ fieldCode: code,
4387
+ details: `${localRight.entities.length} entities`
4388
+ });
4389
+ else if (!areEntitiesEqual(localRight, remoteRight)) entries.push({
4390
+ type: "modified",
4391
+ fieldCode: code,
4392
+ details: "entities changed"
4393
+ });
4394
+ }
4395
+ for (const code of remoteMap.keys()) if (!localMap.has(code)) entries.push({
4396
+ type: "deleted",
4397
+ fieldCode: code,
4398
+ details: "removed"
4399
+ });
4400
+ return buildDiffResult(entries);
4401
+ } };
4402
+
4403
+ //#endregion
4404
+ //#region src/core/application/fieldPermission/detectFieldPermissionDiff.ts
4405
+ async function detectFieldPermissionDiff({ container }) {
4406
+ return detectDiffFromConfig({
4407
+ getStorage: () => container.fieldPermissionStorage.get(),
4408
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4409
+ parseConfig: parseFieldPermissionConfigText,
4410
+ detect: (local, remote) => FieldPermissionDiffDetector.detect(local, { rights: remote.rights }),
4411
+ notFoundMessage: "Field permission config file not found"
4412
+ });
4413
+ }
4414
+
4415
+ //#endregion
4416
+ //#region src/cli/commands/field-acl/diff.ts
4417
+ var diff_default$8 = createDiffCommand({
4418
+ description: "Compare local field permission config with remote kintone app",
4419
+ args: fieldAclArgs,
4420
+ spinnerMessage: "Comparing field permissions...",
4421
+ multiAppSuccessMessage: "All field permission diffs completed successfully.",
4422
+ createContainer: createFieldPermissionCliContainer,
4423
+ detectDiff: detectFieldPermissionDiff,
4424
+ printResult: printFieldPermissionDiffResult,
4425
+ resolveContainerConfig: resolveFieldAclContainerConfig,
4426
+ resolveAppContainerConfig: resolveFieldAclAppContainerConfig
4427
+ });
4428
+
3863
4429
  //#endregion
3864
4430
  //#region src/cli/commands/field-acl/index.ts
3865
4431
  var field_acl_default = define({
@@ -3867,7 +4433,8 @@ var field_acl_default = define({
3867
4433
  description: "Manage kintone field access permissions",
3868
4434
  subCommands: {
3869
4435
  apply: apply_default$8,
3870
- capture: capture_default$9
4436
+ capture: capture_default$9,
4437
+ diff: diff_default$8
3871
4438
  },
3872
4439
  run: () => {}
3873
4440
  });
@@ -5322,7 +5889,7 @@ async function saveGeneralSettings({ container, input }) {
5322
5889
 
5323
5890
  //#endregion
5324
5891
  //#region src/core/domain/notification/services/configSerializer.ts
5325
- function serializeEntity(entity) {
5892
+ function serializeEntity$1(entity) {
5326
5893
  return {
5327
5894
  type: entity.type,
5328
5895
  code: entity.code
@@ -5330,7 +5897,7 @@ function serializeEntity(entity) {
5330
5897
  }
5331
5898
  function serializeGeneralNotification(notification) {
5332
5899
  const result = {
5333
- entity: serializeEntity(notification.entity),
5900
+ entity: serializeEntity$1(notification.entity),
5334
5901
  recordAdded: notification.recordAdded,
5335
5902
  recordEdited: notification.recordEdited,
5336
5903
  commentAdded: notification.commentAdded,
@@ -5341,7 +5908,7 @@ function serializeGeneralNotification(notification) {
5341
5908
  return result;
5342
5909
  }
5343
5910
  function serializePerRecordTarget(target) {
5344
- const result = { entity: serializeEntity(target.entity) };
5911
+ const result = { entity: serializeEntity$1(target.entity) };
5345
5912
  if (target.includeSubs !== void 0) result.includeSubs = target.includeSubs;
5346
5913
  return result;
5347
5914
  }
@@ -5353,7 +5920,7 @@ function serializePerRecordNotification(notification) {
5353
5920
  };
5354
5921
  }
5355
5922
  function serializeReminderTarget(target) {
5356
- const result = { entity: serializeEntity(target.entity) };
5923
+ const result = { entity: serializeEntity$1(target.entity) };
5357
5924
  if (target.includeSubs !== void 0) result.includeSubs = target.includeSubs;
5358
5925
  return result;
5359
5926
  }
@@ -6328,26 +6895,273 @@ var capture_default$8 = define({
6328
6895
  });
6329
6896
 
6330
6897
  //#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: {
6336
- apply: apply_default$7,
6337
- capture: capture_default$8
6338
- },
6339
- run: () => {}
6340
- });
6898
+ //#region src/lib/groupByKey.ts
6899
+ /**
6900
+ * Groups items by a key function into a Map where each key maps to an array of items.
6901
+ * Multiple items can share the same key, producing a multimap structure.
6902
+ */
6903
+ function groupByKey(items, keyFn) {
6904
+ const map = /* @__PURE__ */ new Map();
6905
+ for (const item of items) {
6906
+ const key = keyFn(item);
6907
+ const existing = map.get(key);
6908
+ if (existing) existing.push(item);
6909
+ else map.set(key, [item]);
6910
+ }
6911
+ return map;
6912
+ }
6341
6913
 
6342
6914
  //#endregion
6343
- //#region src/core/domain/plugin/services/configParser.ts
6344
- function parsePluginEntry(raw, index) {
6345
- if (!isRecord$1(raw)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, `Plugin at index ${index} must be an object`);
6346
- const obj = raw;
6347
- 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
- return {
6349
- id: obj.id,
6350
- name: typeof obj.name === "string" ? obj.name : "",
6915
+ //#region src/core/domain/notification/services/diffDetector.ts
6916
+ function serializeEntity(entity) {
6917
+ return `${entity.type}:${entity.code}`;
6918
+ }
6919
+ const GENERAL_BOOLEAN_FLAGS = [
6920
+ "recordAdded",
6921
+ "recordEdited",
6922
+ "commentAdded",
6923
+ "statusChanged",
6924
+ "fileImported"
6925
+ ];
6926
+ function compareGeneralNotification(local, remote) {
6927
+ const diffs = [];
6928
+ if ((local.includeSubs ?? false) !== (remote.includeSubs ?? false)) diffs.push(`includeSubs: ${String(remote.includeSubs ?? false)} -> ${String(local.includeSubs ?? false)}`);
6929
+ for (const flag of GENERAL_BOOLEAN_FLAGS) if (local[flag] !== remote[flag]) diffs.push(`${flag}: ${String(remote[flag])} -> ${String(local[flag])}`);
6930
+ return diffs;
6931
+ }
6932
+ function compareGeneralSection(local, remote) {
6933
+ const entries = [];
6934
+ if (local.notifyToCommenter !== remote.notifyToCommenter) entries.push({
6935
+ type: "modified",
6936
+ section: "general",
6937
+ name: "notifyToCommenter",
6938
+ details: `${String(remote.notifyToCommenter)} -> ${String(local.notifyToCommenter)}`
6939
+ });
6940
+ const localMap = new Map(local.notifications.map((n) => [serializeEntity(n.entity), n]));
6941
+ const remoteMap = new Map(remote.notifications.map((n) => [serializeEntity(n.entity), n]));
6942
+ for (const [key, localNotif] of localMap) {
6943
+ const remoteNotif = remoteMap.get(key);
6944
+ if (!remoteNotif) entries.push({
6945
+ type: "added",
6946
+ section: "general",
6947
+ name: key,
6948
+ details: "new notification"
6949
+ });
6950
+ else {
6951
+ const diffs = compareGeneralNotification(localNotif, remoteNotif);
6952
+ if (diffs.length > 0) entries.push({
6953
+ type: "modified",
6954
+ section: "general",
6955
+ name: key,
6956
+ details: diffs.join(", ")
6957
+ });
6958
+ }
6959
+ }
6960
+ for (const key of remoteMap.keys()) if (!localMap.has(key)) entries.push({
6961
+ type: "deleted",
6962
+ section: "general",
6963
+ name: key,
6964
+ details: "removed"
6965
+ });
6966
+ return entries;
6967
+ }
6968
+ /**
6969
+ * Groups per-record notifications by filterCond. Multiple notifications can share
6970
+ * the same filterCond, so notifications within a group are compared by position
6971
+ * (index-based matching).
6972
+ */
6973
+ function buildPerRecordMultiMap(notifications) {
6974
+ return groupByKey(notifications, (n) => n.filterCond);
6975
+ }
6976
+ function perRecordLabel(notif) {
6977
+ return notif.title || notif.filterCond || "(empty filter)";
6978
+ }
6979
+ function describePerRecordChanges(local, remote) {
6980
+ const diffs = [];
6981
+ if (local.title !== remote.title) diffs.push("title changed");
6982
+ if (!deepEqual$1(local.targets, remote.targets)) diffs.push("targets changed");
6983
+ return diffs.length > 0 ? diffs.join(", ") : "changed";
6984
+ }
6985
+ function describeReminderChanges(local, remote) {
6986
+ const diffs = [];
6987
+ if (local.title !== remote.title) diffs.push("title changed");
6988
+ if (local.daysLater !== remote.daysLater) diffs.push("daysLater changed");
6989
+ if ((local.hoursLater ?? 0) !== (remote.hoursLater ?? 0)) diffs.push("hoursLater changed");
6990
+ if ((local.time ?? "") !== (remote.time ?? "")) diffs.push("time changed");
6991
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
6992
+ if (!deepEqual$1(local.targets, remote.targets)) diffs.push("targets changed");
6993
+ return diffs.length > 0 ? diffs.join(", ") : "changed";
6994
+ }
6995
+ function comparePerRecordSection(local, remote) {
6996
+ const entries = [];
6997
+ const localMulti = buildPerRecordMultiMap(local);
6998
+ const remoteMulti = buildPerRecordMultiMap(remote);
6999
+ for (const [key, localNotifs] of localMulti) {
7000
+ const remoteNotifs = remoteMulti.get(key) ?? [];
7001
+ const maxLen = Math.max(localNotifs.length, remoteNotifs.length);
7002
+ for (let i = 0; i < maxLen; i++) {
7003
+ const localNotif = localNotifs[i];
7004
+ const remoteNotif = remoteNotifs[i];
7005
+ if (localNotif && !remoteNotif) entries.push({
7006
+ type: "added",
7007
+ section: "perRecord",
7008
+ name: perRecordLabel(localNotif),
7009
+ details: "new notification"
7010
+ });
7011
+ else if (!localNotif && remoteNotif) entries.push({
7012
+ type: "deleted",
7013
+ section: "perRecord",
7014
+ name: perRecordLabel(remoteNotif),
7015
+ details: "removed"
7016
+ });
7017
+ else if (localNotif && remoteNotif && !deepEqual$1(localNotif, remoteNotif)) {
7018
+ const diffs = describePerRecordChanges(localNotif, remoteNotif);
7019
+ entries.push({
7020
+ type: "modified",
7021
+ section: "perRecord",
7022
+ name: perRecordLabel(localNotif),
7023
+ details: diffs
7024
+ });
7025
+ }
7026
+ }
7027
+ }
7028
+ for (const [key, remoteNotifs] of remoteMulti) if (!localMulti.has(key)) for (const remoteNotif of remoteNotifs) entries.push({
7029
+ type: "deleted",
7030
+ section: "perRecord",
7031
+ name: perRecordLabel(remoteNotif),
7032
+ details: "removed"
7033
+ });
7034
+ return entries;
7035
+ }
7036
+ function compareReminderSection(local, remote) {
7037
+ const entries = [];
7038
+ const localMap = new Map(local.map((n) => [n.code, n]));
7039
+ const remoteMap = new Map(remote.map((n) => [n.code, n]));
7040
+ for (const [code, localNotif] of localMap) {
7041
+ const remoteNotif = remoteMap.get(code);
7042
+ if (!remoteNotif) entries.push({
7043
+ type: "added",
7044
+ section: "reminder",
7045
+ name: code,
7046
+ details: `"${localNotif.title}"`
7047
+ });
7048
+ else if (!deepEqual$1(localNotif, remoteNotif)) entries.push({
7049
+ type: "modified",
7050
+ section: "reminder",
7051
+ name: code,
7052
+ details: describeReminderChanges(localNotif, remoteNotif)
7053
+ });
7054
+ }
7055
+ for (const code of remoteMap.keys()) if (!localMap.has(code)) entries.push({
7056
+ type: "deleted",
7057
+ section: "reminder",
7058
+ name: code,
7059
+ details: "removed"
7060
+ });
7061
+ return entries;
7062
+ }
7063
+ const NotificationDiffDetector = { detect: (local, remote) => {
7064
+ const entries = [];
7065
+ if (local.general && remote.general) entries.push(...compareGeneralSection(local.general, remote.general));
7066
+ else if (local.general && !remote.general) entries.push({
7067
+ type: "added",
7068
+ section: "general",
7069
+ name: "general",
7070
+ details: "added general notifications section"
7071
+ });
7072
+ else if (!local.general && remote.general) entries.push({
7073
+ type: "deleted",
7074
+ section: "general",
7075
+ name: "general",
7076
+ details: "removed general notifications section"
7077
+ });
7078
+ const localPerRecord = local.perRecord ?? [];
7079
+ const remotePerRecord = remote.perRecord ?? [];
7080
+ entries.push(...comparePerRecordSection(localPerRecord, remotePerRecord));
7081
+ if (local.reminder && remote.reminder) {
7082
+ entries.push(...compareReminderSection(local.reminder.notifications, remote.reminder.notifications));
7083
+ if (local.reminder.timezone !== remote.reminder.timezone) entries.push({
7084
+ type: "modified",
7085
+ section: "reminder",
7086
+ name: "timezone",
7087
+ details: `${remote.reminder.timezone} -> ${local.reminder.timezone}`
7088
+ });
7089
+ } else if (local.reminder && !remote.reminder) entries.push({
7090
+ type: "added",
7091
+ section: "reminder",
7092
+ name: "reminder",
7093
+ details: "added reminder notifications section"
7094
+ });
7095
+ else if (!local.reminder && remote.reminder) entries.push({
7096
+ type: "deleted",
7097
+ section: "reminder",
7098
+ name: "reminder",
7099
+ details: "removed reminder notifications section"
7100
+ });
7101
+ return buildDiffResult(entries);
7102
+ } };
7103
+
7104
+ //#endregion
7105
+ //#region src/core/application/notification/detectNotificationDiff.ts
7106
+ async function detectNotificationDiff({ container }) {
7107
+ const [storageResult, generalResult, perRecordResult, reminderResult] = await Promise.all([
7108
+ container.notificationStorage.get(),
7109
+ container.notificationConfigurator.getGeneralNotifications(),
7110
+ container.notificationConfigurator.getPerRecordNotifications(),
7111
+ container.notificationConfigurator.getReminderNotifications()
7112
+ ]);
7113
+ if (!storageResult.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Notification config file not found");
7114
+ const localConfig = parseNotificationConfigText(storageResult.content);
7115
+ const remoteConfig = {
7116
+ general: {
7117
+ notifyToCommenter: generalResult.notifyToCommenter,
7118
+ notifications: generalResult.notifications
7119
+ },
7120
+ perRecord: perRecordResult.notifications,
7121
+ reminder: {
7122
+ timezone: reminderResult.timezone,
7123
+ notifications: reminderResult.notifications
7124
+ }
7125
+ };
7126
+ return NotificationDiffDetector.detect(localConfig, remoteConfig);
7127
+ }
7128
+
7129
+ //#endregion
7130
+ //#region src/cli/commands/notification/diff.ts
7131
+ var diff_default$7 = createDiffCommand({
7132
+ description: "Compare local notification config with remote kintone app",
7133
+ args: notificationArgs,
7134
+ spinnerMessage: "Comparing notification settings...",
7135
+ multiAppSuccessMessage: "All notification diffs completed successfully.",
7136
+ createContainer: createNotificationCliContainer,
7137
+ detectDiff: detectNotificationDiff,
7138
+ printResult: printNotificationDiffResult,
7139
+ resolveContainerConfig: resolveNotificationContainerConfig,
7140
+ resolveAppContainerConfig: resolveNotificationAppContainerConfig
7141
+ });
7142
+
7143
+ //#endregion
7144
+ //#region src/cli/commands/notification/index.ts
7145
+ var notification_default = define({
7146
+ name: "notification",
7147
+ description: "Manage kintone notification settings",
7148
+ subCommands: {
7149
+ apply: apply_default$7,
7150
+ capture: capture_default$8,
7151
+ diff: diff_default$7
7152
+ },
7153
+ run: () => {}
7154
+ });
7155
+
7156
+ //#endregion
7157
+ //#region src/core/domain/plugin/services/configParser.ts
7158
+ function parsePluginEntry(raw, index) {
7159
+ if (!isRecord$1(raw)) throw new BusinessRuleError(PluginErrorCode.PlInvalidConfigStructure, `Plugin at index ${index} must be an object`);
7160
+ const obj = raw;
7161
+ 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`);
7162
+ return {
7163
+ id: obj.id,
7164
+ name: typeof obj.name === "string" ? obj.name : "",
6351
7165
  enabled: typeof obj.enabled === "boolean" ? obj.enabled : true
6352
7166
  };
6353
7167
  }
@@ -6497,6 +7311,64 @@ var capture_default$7 = define({
6497
7311
  }
6498
7312
  });
6499
7313
 
7314
+ //#endregion
7315
+ //#region src/core/domain/plugin/services/diffDetector.ts
7316
+ const PluginDiffDetector = { detect: (local, remote) => {
7317
+ const entries = [];
7318
+ const localMap = new Map(local.plugins.map((p) => [p.id, p]));
7319
+ const remoteMap = new Map(remote.plugins.map((p) => [p.id, p]));
7320
+ for (const [id, localPlugin] of localMap) {
7321
+ const remotePlugin = remoteMap.get(id);
7322
+ if (!remotePlugin) entries.push({
7323
+ type: "added",
7324
+ pluginId: id,
7325
+ details: `"${localPlugin.name}"`
7326
+ });
7327
+ else {
7328
+ const diffs = [];
7329
+ if (localPlugin.name !== remotePlugin.name) diffs.push(`name: "${remotePlugin.name}" -> "${localPlugin.name}"`);
7330
+ if (localPlugin.enabled !== remotePlugin.enabled) diffs.push(`enabled: ${String(remotePlugin.enabled)} -> ${String(localPlugin.enabled)}`);
7331
+ if (diffs.length > 0) entries.push({
7332
+ type: "modified",
7333
+ pluginId: id,
7334
+ details: diffs.join(", ")
7335
+ });
7336
+ }
7337
+ }
7338
+ for (const [id, remotePlugin] of remoteMap) if (!localMap.has(id)) entries.push({
7339
+ type: "deleted",
7340
+ pluginId: id,
7341
+ details: `"${remotePlugin.name}"`
7342
+ });
7343
+ return buildDiffResult(entries);
7344
+ } };
7345
+
7346
+ //#endregion
7347
+ //#region src/core/application/plugin/detectPluginDiff.ts
7348
+ async function detectPluginDiff({ container }) {
7349
+ return detectDiffFromConfig({
7350
+ getStorage: () => container.pluginStorage.get(),
7351
+ fetchRemote: () => container.pluginConfigurator.getPlugins(),
7352
+ parseConfig: parsePluginConfigText,
7353
+ detect: (local, remote) => PluginDiffDetector.detect(local, { plugins: remote.plugins }),
7354
+ notFoundMessage: "Plugin config file not found"
7355
+ });
7356
+ }
7357
+
7358
+ //#endregion
7359
+ //#region src/cli/commands/plugin/diff.ts
7360
+ var diff_default$6 = createDiffCommand({
7361
+ description: "Compare local plugin config with remote kintone app",
7362
+ args: pluginArgs,
7363
+ spinnerMessage: "Comparing plugin settings...",
7364
+ multiAppSuccessMessage: "All plugin diffs completed successfully.",
7365
+ createContainer: createPluginCliContainer,
7366
+ detectDiff: detectPluginDiff,
7367
+ printResult: printPluginDiffResult,
7368
+ resolveContainerConfig: resolvePluginContainerConfig,
7369
+ resolveAppContainerConfig: resolvePluginAppContainerConfig
7370
+ });
7371
+
6500
7372
  //#endregion
6501
7373
  //#region src/cli/commands/plugin/index.ts
6502
7374
  var plugin_default = define({
@@ -6504,7 +7376,8 @@ var plugin_default = define({
6504
7376
  description: "Manage kintone plugins",
6505
7377
  subCommands: {
6506
7378
  apply: apply_default$6,
6507
- capture: capture_default$7
7379
+ capture: capture_default$7,
7380
+ diff: diff_default$6
6508
7381
  },
6509
7382
  run: () => {}
6510
7383
  });
@@ -6781,7 +7654,7 @@ function compareActions(localAction, remoteAction) {
6781
7654
  if (!isExecutableUserEqual(localAction.executableUser, remoteAction.executableUser)) diffs.push("executableUser changed");
6782
7655
  return diffs;
6783
7656
  }
6784
- function compareConfigs(local, remote) {
7657
+ function compareConfigs$1(local, remote) {
6785
7658
  const entries = [];
6786
7659
  if (local.enable !== remote.enable) entries.push({
6787
7660
  type: "modified",
@@ -6846,68 +7719,33 @@ function compareConfigs(local, remote) {
6846
7719
  return entries;
6847
7720
  }
6848
7721
  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
- };
7722
+ return buildDiffResult(compareConfigs$1(local, remote));
6863
7723
  } };
6864
7724
 
6865
7725
  //#endregion
6866
7726
  //#region src/core/application/processManagement/detectProcessManagementDiff.ts
6867
7727
  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);
7728
+ return detectDiffFromConfig({
7729
+ getStorage: () => container.processManagementStorage.get(),
7730
+ fetchRemote: () => container.processManagementConfigurator.getProcessManagement(),
7731
+ parseConfig: parseProcessManagementConfigText,
7732
+ detect: (local, remote) => ProcessManagementDiffDetector.detect(local, remote.config),
7733
+ notFoundMessage: "Process management config file not found"
7734
+ });
6873
7735
  }
6874
7736
 
6875
7737
  //#endregion
6876
7738
  //#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",
7739
+ var diff_default$5 = createDiffCommand({
6887
7740
  description: "Compare local process management settings with remote kintone app",
6888
7741
  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
- }
7742
+ spinnerMessage: "Comparing process management settings...",
7743
+ multiAppSuccessMessage: "All process management diffs completed successfully.",
7744
+ createContainer: createProcessManagementCliContainer,
7745
+ detectDiff: detectProcessManagementDiff,
7746
+ printResult: printProcessDiffResult,
7747
+ resolveContainerConfig: resolveProcessContainerConfig,
7748
+ resolveAppContainerConfig: resolveProcessAppContainerConfig
6911
7749
  });
6912
7750
 
6913
7751
  //#endregion
@@ -6918,7 +7756,7 @@ var process_default = define({
6918
7756
  subCommands: {
6919
7757
  apply: apply_default$5,
6920
7758
  capture: capture_default$6,
6921
- diff: diff_default$2
7759
+ diff: diff_default$5
6922
7760
  },
6923
7761
  run: () => {}
6924
7762
  });
@@ -7113,6 +7951,104 @@ var capture_default$5 = define({
7113
7951
  }
7114
7952
  });
7115
7953
 
7954
+ //#endregion
7955
+ //#region src/core/domain/recordPermission/services/diffDetector.ts
7956
+ function areRightsEqual(a, b) {
7957
+ return deepEqual$1(a.entities.map((e) => ({
7958
+ type: e.entity.type,
7959
+ code: e.entity.code,
7960
+ viewable: e.viewable,
7961
+ editable: e.editable,
7962
+ deletable: e.deletable,
7963
+ includeSubs: e.includeSubs
7964
+ })), b.entities.map((e) => ({
7965
+ type: e.entity.type,
7966
+ code: e.entity.code,
7967
+ viewable: e.viewable,
7968
+ editable: e.editable,
7969
+ deletable: e.deletable,
7970
+ includeSubs: e.includeSubs
7971
+ })));
7972
+ }
7973
+ function describeRight(right) {
7974
+ const perEntity = right.entities.map((e) => {
7975
+ const perms = [
7976
+ e.viewable ? "view" : null,
7977
+ e.editable ? "edit" : null,
7978
+ e.deletable ? "delete" : null
7979
+ ].filter(Boolean).join("/");
7980
+ return `${e.entity.type}:${e.entity.code}(${perms || "none"})`;
7981
+ });
7982
+ return perEntity.length > 0 ? perEntity.join(", ") : "no entities";
7983
+ }
7984
+ const RecordPermissionDiffDetector = { detect: (local, remote) => {
7985
+ const entries = [];
7986
+ const localMulti = groupByKey(local.rights, (r) => r.filterCond);
7987
+ const remoteMulti = groupByKey(remote.rights, (r) => r.filterCond);
7988
+ for (const [filterCond, localRights] of localMulti) {
7989
+ const remoteRights = remoteMulti.get(filterCond) ?? [];
7990
+ const maxLen = Math.max(localRights.length, remoteRights.length);
7991
+ for (let i = 0; i < maxLen; i++) {
7992
+ const localRight = localRights[i];
7993
+ const remoteRight = remoteRights[i];
7994
+ if (localRight && !remoteRight) entries.push({
7995
+ type: "added",
7996
+ filterCond,
7997
+ details: describeRight(localRight)
7998
+ });
7999
+ else if (!localRight && remoteRight) entries.push({
8000
+ type: "deleted",
8001
+ filterCond,
8002
+ details: describeRight(remoteRight)
8003
+ });
8004
+ else if (localRight && remoteRight) {
8005
+ if (!areRightsEqual(localRight, remoteRight)) {
8006
+ const diffs = [];
8007
+ if (localRight.entities.length !== remoteRight.entities.length) diffs.push(`entities: ${remoteRight.entities.length} -> ${localRight.entities.length}`);
8008
+ else diffs.push("entities changed");
8009
+ entries.push({
8010
+ type: "modified",
8011
+ filterCond,
8012
+ details: diffs.join(", ")
8013
+ });
8014
+ }
8015
+ }
8016
+ }
8017
+ }
8018
+ for (const [filterCond, remoteRights] of remoteMulti) if (!localMulti.has(filterCond)) for (const remoteRight of remoteRights) entries.push({
8019
+ type: "deleted",
8020
+ filterCond,
8021
+ details: describeRight(remoteRight)
8022
+ });
8023
+ return buildDiffResult(entries);
8024
+ } };
8025
+
8026
+ //#endregion
8027
+ //#region src/core/application/recordPermission/detectRecordPermissionDiff.ts
8028
+ async function detectRecordPermissionDiff({ container }) {
8029
+ return detectDiffFromConfig({
8030
+ getStorage: () => container.recordPermissionStorage.get(),
8031
+ fetchRemote: () => container.recordPermissionConfigurator.getRecordPermissions(),
8032
+ parseConfig: parseRecordPermissionConfigText,
8033
+ detect: (local, remote) => RecordPermissionDiffDetector.detect(local, { rights: remote.rights }),
8034
+ notFoundMessage: "Record permission config file not found"
8035
+ });
8036
+ }
8037
+
8038
+ //#endregion
8039
+ //#region src/cli/commands/record-acl/diff.ts
8040
+ var diff_default$4 = createDiffCommand({
8041
+ description: "Compare local record permission config with remote kintone app",
8042
+ args: recordAclArgs,
8043
+ spinnerMessage: "Comparing record permissions...",
8044
+ multiAppSuccessMessage: "All record permission diffs completed successfully.",
8045
+ createContainer: createRecordPermissionCliContainer,
8046
+ detectDiff: detectRecordPermissionDiff,
8047
+ printResult: printRecordPermissionDiffResult,
8048
+ resolveContainerConfig: resolveRecordAclContainerConfig,
8049
+ resolveAppContainerConfig: resolveRecordAclAppContainerConfig
8050
+ });
8051
+
7116
8052
  //#endregion
7117
8053
  //#region src/cli/commands/record-acl/index.ts
7118
8054
  var record_acl_default = define({
@@ -7120,7 +8056,8 @@ var record_acl_default = define({
7120
8056
  description: "Manage kintone record access permissions",
7121
8057
  subCommands: {
7122
8058
  apply: apply_default$4,
7123
- capture: capture_default$5
8059
+ capture: capture_default$5,
8060
+ diff: diff_default$4
7124
8061
  },
7125
8062
  run: () => {}
7126
8063
  });
@@ -7462,6 +8399,73 @@ var capture_default$4 = define({
7462
8399
  }
7463
8400
  });
7464
8401
 
8402
+ //#endregion
8403
+ //#region src/core/domain/report/services/diffDetector.ts
8404
+ function compareReports(local, remote) {
8405
+ const diffs = [];
8406
+ if (local.name !== remote.name) diffs.push(`name: "${remote.name}" -> "${local.name}"`);
8407
+ if (local.chartType !== remote.chartType) diffs.push(`chartType: ${remote.chartType} -> ${local.chartType}`);
8408
+ if ((local.chartMode ?? "") !== (remote.chartMode ?? "")) diffs.push(`chartMode: ${remote.chartMode ?? "(unset)"} -> ${local.chartMode ?? "(unset)"}`);
8409
+ if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
8410
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
8411
+ if (!deepEqual$1(local.groups, remote.groups)) diffs.push("groups changed");
8412
+ if (!deepEqual$1(local.aggregations, remote.aggregations)) diffs.push("aggregations changed");
8413
+ if (!deepEqual$1(local.sorts, remote.sorts)) diffs.push("sorts changed");
8414
+ if (!deepEqual$1(local.periodicReport ?? null, remote.periodicReport ?? null)) diffs.push("periodicReport changed");
8415
+ return diffs;
8416
+ }
8417
+ const ReportDiffDetector = { detect: (local, remote) => {
8418
+ const entries = [];
8419
+ for (const [name, localReport] of Object.entries(local.reports)) {
8420
+ const remoteReport = remote.reports[name];
8421
+ if (!remoteReport) entries.push({
8422
+ type: "added",
8423
+ reportName: name,
8424
+ details: `chartType: ${localReport.chartType}`
8425
+ });
8426
+ else {
8427
+ const diffs = compareReports(localReport, remoteReport);
8428
+ if (diffs.length > 0) entries.push({
8429
+ type: "modified",
8430
+ reportName: name,
8431
+ details: diffs.join(", ")
8432
+ });
8433
+ }
8434
+ }
8435
+ for (const [name, remoteReport] of Object.entries(remote.reports)) if (!local.reports[name]) entries.push({
8436
+ type: "deleted",
8437
+ reportName: name,
8438
+ details: `chartType: ${remoteReport.chartType}`
8439
+ });
8440
+ return buildDiffResult(entries);
8441
+ } };
8442
+
8443
+ //#endregion
8444
+ //#region src/core/application/report/detectReportDiff.ts
8445
+ async function detectReportDiff({ container }) {
8446
+ return detectDiffFromConfig({
8447
+ getStorage: () => container.reportStorage.get(),
8448
+ fetchRemote: () => container.reportConfigurator.getReports(),
8449
+ parseConfig: parseReportConfigText,
8450
+ detect: (local, remote) => ReportDiffDetector.detect(local, { reports: remote.reports }),
8451
+ notFoundMessage: "Report config file not found"
8452
+ });
8453
+ }
8454
+
8455
+ //#endregion
8456
+ //#region src/cli/commands/report/diff.ts
8457
+ var diff_default$3 = createDiffCommand({
8458
+ description: "Compare local report config with remote kintone app",
8459
+ args: reportArgs,
8460
+ spinnerMessage: "Comparing report settings...",
8461
+ multiAppSuccessMessage: "All report diffs completed successfully.",
8462
+ createContainer: createReportCliContainer,
8463
+ detectDiff: detectReportDiff,
8464
+ printResult: printReportDiffResult,
8465
+ resolveContainerConfig: resolveReportContainerConfig,
8466
+ resolveAppContainerConfig: resolveReportAppContainerConfig
8467
+ });
8468
+
7465
8469
  //#endregion
7466
8470
  //#region src/cli/commands/report/index.ts
7467
8471
  var report_default = define({
@@ -7469,7 +8473,8 @@ var report_default = define({
7469
8473
  description: "Manage kintone report settings",
7470
8474
  subCommands: {
7471
8475
  apply: apply_default$3,
7472
- capture: capture_default$4
8476
+ capture: capture_default$4,
8477
+ diff: diff_default$3
7473
8478
  },
7474
8479
  run: () => {}
7475
8480
  });
@@ -7521,53 +8526,8 @@ var capture_default$3 = define({
7521
8526
  }
7522
8527
  });
7523
8528
 
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
8529
  //#endregion
7546
8530
  //#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
8531
  function isMapEqual(a, b) {
7572
8532
  if (a.size !== b.size) return false;
7573
8533
  for (const [key, valA] of a) {
@@ -7579,18 +8539,8 @@ function isMapEqual(a, b) {
7579
8539
  }
7580
8540
  function isPropertiesEqual(a, b) {
7581
8541
  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);
8542
+ if (a.type === "REFERENCE_TABLE" && b.type === "REFERENCE_TABLE") return deepEqual$1(a.properties.referenceTable, b.properties.referenceTable);
8543
+ return deepEqual$1(a.properties, b.properties);
7594
8544
  }
7595
8545
  function isFieldEqual(a, b) {
7596
8546
  if (a.type !== b.type) return false;
@@ -7599,31 +8549,16 @@ function isFieldEqual(a, b) {
7599
8549
  if (Boolean(a.noLabel) !== Boolean(b.noLabel)) return false;
7600
8550
  return isPropertiesEqual(a, b);
7601
8551
  }
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
8552
  function describeChanges$1(before, after) {
7618
8553
  const changes = [];
7619
8554
  if (before.type !== after.type) changes.push(`type: ${before.type} -> ${after.type}`);
7620
8555
  if (before.label !== after.label) changes.push(`label: ${before.label} -> ${after.label}`);
7621
8556
  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");
8557
+ if (!isPropertiesEqual(before, after)) changes.push("properties changed");
7623
8558
  return changes.length > 0 ? changes.join(", ") : "no visible changes";
7624
8559
  }
7625
8560
  function isLayoutEqual(a, b) {
7626
- return isValueEqual(a, b);
8561
+ return deepEqual$1(a, b);
7627
8562
  }
7628
8563
  const DiffDetector = {
7629
8564
  detectLayoutChanges: (schemaLayout, currentLayout) => {
@@ -7653,10 +8588,10 @@ const DiffDetector = {
7653
8588
  type: "deleted",
7654
8589
  fieldCode,
7655
8590
  fieldLabel: currentDef.label,
7656
- details: "deleted",
8591
+ details: "removed",
7657
8592
  before: currentDef
7658
8593
  });
7659
- return FormDiff.create(entries);
8594
+ return buildDiffResult(entries);
7660
8595
  }
7661
8596
  };
7662
8597
 
@@ -8153,14 +9088,20 @@ async function detectDiff({ container }) {
8153
9088
 
8154
9089
  //#endregion
8155
9090
  //#region src/cli/commands/schema/diff.ts
8156
- async function runDiff$1(container) {
9091
+ async function runDiff(container) {
8157
9092
  const s = p.spinner();
8158
9093
  s.start("Fetching form schema...");
8159
- const result = await detectDiff({ container });
9094
+ let result;
9095
+ try {
9096
+ result = await detectDiff({ container });
9097
+ } catch (error) {
9098
+ s.stop("Comparison failed.");
9099
+ throw error;
9100
+ }
8160
9101
  s.stop("Form schema fetched.");
8161
9102
  printDiffResult(result);
8162
9103
  }
8163
- var diff_default$1 = define({
9104
+ var diff_default$2 = define({
8164
9105
  name: "diff",
8165
9106
  description: "Detect differences between schema file and current kintone form",
8166
9107
  args: {
@@ -8171,17 +9112,17 @@ var diff_default$1 = define({
8171
9112
  try {
8172
9113
  await routeMultiApp(ctx.values, {
8173
9114
  singleLegacy: async () => {
8174
- await runDiff$1(createCliContainer(resolveConfig(ctx.values)));
9115
+ await runDiff(createCliContainer(resolveConfig(ctx.values)));
8175
9116
  },
8176
9117
  singleApp: async (app, projectConfig) => {
8177
- await runDiff$1(createCliContainer(resolveAppCliConfig(app, projectConfig, ctx.values)));
9118
+ await runDiff(createCliContainer(resolveAppCliConfig(app, projectConfig, ctx.values)));
8178
9119
  },
8179
9120
  multiApp: async (plan, projectConfig) => {
8180
9121
  await runMultiAppWithFailCheck(plan, async (app) => {
8181
9122
  const container = createCliContainer(resolveAppCliConfig(app, projectConfig, ctx.values));
8182
9123
  printAppHeader(app.name, app.appId);
8183
- await runDiff$1(container);
8184
- });
9124
+ await runDiff(container);
9125
+ }, "All schema diffs completed successfully.");
8185
9126
  }
8186
9127
  });
8187
9128
  } catch (error) {
@@ -8899,7 +9840,7 @@ var schema_default = define({
8899
9840
  name: "schema",
8900
9841
  description: "Manage kintone form schemas",
8901
9842
  subCommands: {
8902
- diff: diff_default$1,
9843
+ diff: diff_default$2,
8903
9844
  migrate: migrate_default,
8904
9845
  override: override_default,
8905
9846
  capture: capture_default$3,
@@ -9497,6 +10438,91 @@ var capture_default$1 = define({
9497
10438
  }
9498
10439
  });
9499
10440
 
10441
+ //#endregion
10442
+ //#region src/core/domain/generalSettings/services/diffDetector.ts
10443
+ const DEFAULT_STRING = "";
10444
+ const DEFAULT_BOOLEAN = false;
10445
+ const DEFAULT_FIRST_MONTH = 1;
10446
+ function compareConfigs(local, remote) {
10447
+ const entries = [];
10448
+ function compareString(field, l, r, defaultValue, suppressValues = false) {
10449
+ const lv = l ?? defaultValue;
10450
+ const rv = r ?? defaultValue;
10451
+ if (lv !== rv) entries.push({
10452
+ type: "modified",
10453
+ field,
10454
+ details: suppressValues ? `${field} changed` : `"${rv}" -> "${lv}"`
10455
+ });
10456
+ }
10457
+ function compareBoolean(field, l, r, defaultValue) {
10458
+ const lv = l ?? defaultValue;
10459
+ const rv = r ?? defaultValue;
10460
+ if (lv !== rv) entries.push({
10461
+ type: "modified",
10462
+ field,
10463
+ details: `${String(rv)} -> ${String(lv)}`
10464
+ });
10465
+ }
10466
+ function compareNumber(field, l, r, defaultValue) {
10467
+ const lv = l ?? defaultValue;
10468
+ const rv = r ?? defaultValue;
10469
+ if (lv !== rv) entries.push({
10470
+ type: "modified",
10471
+ field,
10472
+ details: `${rv} -> ${lv}`
10473
+ });
10474
+ }
10475
+ function compareDeepEqual(field, l, r) {
10476
+ if (!deepEqual$1(l, r)) entries.push({
10477
+ type: "modified",
10478
+ field,
10479
+ details: `${field} changed`
10480
+ });
10481
+ }
10482
+ compareString("name", local.name, remote.name, DEFAULT_STRING);
10483
+ compareString("description", local.description, remote.description, DEFAULT_STRING, true);
10484
+ compareDeepEqual("icon", local.icon, remote.icon);
10485
+ compareString("theme", local.theme, remote.theme, DEFAULT_STRING);
10486
+ compareDeepEqual("titleField", local.titleField, remote.titleField);
10487
+ compareBoolean("enableThumbnails", local.enableThumbnails, remote.enableThumbnails, DEFAULT_BOOLEAN);
10488
+ compareBoolean("enableBulkDeletion", local.enableBulkDeletion, remote.enableBulkDeletion, DEFAULT_BOOLEAN);
10489
+ compareBoolean("enableComments", local.enableComments, remote.enableComments, DEFAULT_BOOLEAN);
10490
+ compareBoolean("enableDuplicateRecord", local.enableDuplicateRecord, remote.enableDuplicateRecord, DEFAULT_BOOLEAN);
10491
+ compareBoolean("enableInlineRecordEditing", local.enableInlineRecordEditing, remote.enableInlineRecordEditing, DEFAULT_BOOLEAN);
10492
+ compareDeepEqual("numberPrecision", local.numberPrecision, remote.numberPrecision);
10493
+ compareNumber("firstMonthOfFiscalYear", local.firstMonthOfFiscalYear, remote.firstMonthOfFiscalYear, DEFAULT_FIRST_MONTH);
10494
+ return entries;
10495
+ }
10496
+ const GeneralSettingsDiffDetector = { detect: (local, remote) => {
10497
+ return buildDiffResult(compareConfigs(local, remote));
10498
+ } };
10499
+
10500
+ //#endregion
10501
+ //#region src/core/application/generalSettings/detectGeneralSettingsDiff.ts
10502
+ async function detectGeneralSettingsDiff({ container }) {
10503
+ return detectDiffFromConfig({
10504
+ getStorage: () => container.generalSettingsStorage.get(),
10505
+ fetchRemote: () => container.generalSettingsConfigurator.getGeneralSettings(),
10506
+ parseConfig: parseGeneralSettingsConfigText,
10507
+ detect: (local, remote) => GeneralSettingsDiffDetector.detect(local, remote.config),
10508
+ notFoundMessage: "General settings config file not found"
10509
+ });
10510
+ }
10511
+
10512
+ //#endregion
10513
+ //#region src/cli/commands/settings/diff.ts
10514
+ var diff_default$1 = createDiffCommand({
10515
+ description: "Compare local general settings config with remote kintone app",
10516
+ args: settingsArgs,
10517
+ spinnerMessage: "Comparing general settings...",
10518
+ multiAppSuccessMessage: "All general settings diffs completed successfully.",
10519
+ createContainer: createGeneralSettingsCliContainer,
10520
+ detectDiff: detectGeneralSettingsDiff,
10521
+ printResult: printGeneralSettingsDiffResult,
10522
+ resolveContainerConfig: resolveSettingsContainerConfig,
10523
+ resolveAppContainerConfig: resolveSettingsAppContainerConfig
10524
+ });
10525
+
9500
10526
  //#endregion
9501
10527
  //#region src/cli/commands/settings/index.ts
9502
10528
  var settings_default = define({
@@ -9504,7 +10530,8 @@ var settings_default = define({
9504
10530
  description: "Manage kintone general settings",
9505
10531
  subCommands: {
9506
10532
  apply: apply_default$1,
9507
- capture: capture_default$1
10533
+ capture: capture_default$1,
10534
+ diff: diff_default$1
9508
10535
  },
9509
10536
  run: () => {}
9510
10537
  });
@@ -9693,37 +10720,21 @@ var capture_default = define({
9693
10720
 
9694
10721
  //#endregion
9695
10722
  //#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
10723
  function describeChanges(local, remote) {
9702
10724
  const changes = [];
10725
+ if (local.name !== remote.name) changes.push(`name: "${remote.name}" -> "${local.name}"`);
9703
10726
  if (local.type !== remote.type) changes.push(`type: ${remote.type} -> ${local.type}`);
10727
+ if ((local.builtinType ?? "") !== (remote.builtinType ?? "")) changes.push("builtinType changed");
9704
10728
  if (local.index !== remote.index) changes.push(`index: ${remote.index} -> ${local.index}`);
9705
10729
  if ((local.filterCond ?? "") !== (remote.filterCond ?? "")) changes.push("filterCond changed");
9706
10730
  if ((local.sort ?? "") !== (remote.sort ?? "")) changes.push("sort changed");
9707
10731
  if ((local.date ?? "") !== (remote.date ?? "")) changes.push("date changed");
9708
10732
  if ((local.title ?? "") !== (remote.title ?? "")) changes.push("title changed");
9709
10733
  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")}`);
10734
+ if ((local.pager ?? false) !== (remote.pager ?? false)) changes.push(`pager: ${String(remote.pager ?? false)} -> ${String(local.pager ?? false)}`);
9711
10735
  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;
10736
+ if (!deepEqual$1(local.fields ?? [], remote.fields ?? [])) changes.push("fields changed");
10737
+ return changes;
9727
10738
  }
9728
10739
  const ViewDiffDetector = { detect: (localViews, remoteViews) => {
9729
10740
  const entries = [];
@@ -9734,83 +10745,47 @@ const ViewDiffDetector = { detect: (localViews, remoteViews) => {
9734
10745
  viewName: name,
9735
10746
  details: "new view"
9736
10747
  });
9737
- else if (!isViewEqual(localView, remoteView)) entries.push({
9738
- type: "modified",
9739
- viewName: name,
9740
- details: describeChanges(localView, remoteView)
9741
- });
10748
+ else {
10749
+ const changes = describeChanges(localView, remoteView);
10750
+ if (changes.length > 0) entries.push({
10751
+ type: "modified",
10752
+ viewName: name,
10753
+ details: changes.join(", ")
10754
+ });
10755
+ }
9742
10756
  }
9743
10757
  for (const name of Object.keys(remoteViews)) if (localViews[name] === void 0) entries.push({
9744
10758
  type: "deleted",
9745
10759
  viewName: name,
9746
- details: "deleted"
10760
+ details: "removed"
9747
10761
  });
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
- };
10762
+ return buildDiffResult(entries);
9761
10763
  } };
9762
10764
 
9763
10765
  //#endregion
9764
10766
  //#region src/core/application/view/detectViewDiff.ts
9765
10767
  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
- };
10768
+ return detectDiffFromConfig({
10769
+ getStorage: () => container.viewStorage.get(),
10770
+ fetchRemote: () => container.viewConfigurator.getViews(),
10771
+ parseConfig: (content) => parseViewConfigText(content).views,
10772
+ detect: (views, remote) => ViewDiffDetector.detect(views, remote.views),
10773
+ notFoundMessage: "View config file not found"
10774
+ });
9776
10775
  }
9777
10776
 
9778
10777
  //#endregion
9779
10778
  //#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",
10779
+ var diff_default = createDiffCommand({
10780
+ description: "Compare local view config with remote kintone app",
9791
10781
  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
- }
10782
+ spinnerMessage: "Comparing view settings...",
10783
+ multiAppSuccessMessage: "All view diffs completed successfully.",
10784
+ createContainer: createViewCliContainer,
10785
+ detectDiff: detectViewDiff,
10786
+ printResult: printViewDiffResult,
10787
+ resolveContainerConfig: resolveViewContainerConfig,
10788
+ resolveAppContainerConfig: resolveViewAppContainerConfig
9814
10789
  });
9815
10790
 
9816
10791
  //#endregion