kintone-migrator 0.24.9 → 0.26.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
@@ -497,6 +497,236 @@ async function applyAction({ container }) {
497
497
  });
498
498
  }
499
499
  //#endregion
500
+ //#region src/lib/deepEqual.ts
501
+ function isArrayEqual(a, b, stack) {
502
+ if (!Array.isArray(b)) return false;
503
+ if (a.length !== b.length) return false;
504
+ for (let i = 0; i < a.length; i++) if (!deepEqualInner(a[i], b[i], stack)) return false;
505
+ return true;
506
+ }
507
+ /**
508
+ * Compare two objects by own enumerable string keys (via `Object.keys`).
509
+ *
510
+ * Note: Custom class instances (e.g. `new Error()`) that pass `isRecord` are
511
+ * accepted. Because `Error.prototype.message` is non-enumerable, two Error
512
+ * objects with different messages will compare as equal. Callers should not
513
+ * rely on this function for types with non-enumerable significant state.
514
+ */
515
+ function isRecordEqual(a, b, stack) {
516
+ if (!isRecord(b)) return false;
517
+ const keysA = Object.keys(a);
518
+ const keysB = Object.keys(b);
519
+ if (keysA.length !== keysB.length) return false;
520
+ for (const key of keysA) {
521
+ if (!Object.hasOwn(b, key)) return false;
522
+ if (!deepEqualInner(a[key], b[key], stack)) return false;
523
+ }
524
+ return true;
525
+ }
526
+ /**
527
+ * Map keys are compared by reference (SameValueZero), not by deep equality.
528
+ * Two Maps with structurally equal but referentially different object keys
529
+ * will be considered unequal.
530
+ */
531
+ function isMapEqual$1(a, b, stack) {
532
+ if (!(b instanceof Map)) return false;
533
+ if (a.size !== b.size) return false;
534
+ for (const [key, valA] of a) {
535
+ if (!b.has(key)) return false;
536
+ if (!deepEqualInner(valA, b.get(key), stack)) return false;
537
+ }
538
+ return true;
539
+ }
540
+ function isPrimitiveSet(s) {
541
+ for (const val of s) {
542
+ if (val !== null && typeof val === "object") return false;
543
+ if (typeof val === "function") return false;
544
+ }
545
+ return true;
546
+ }
547
+ function isSetEqual(a, b, stack) {
548
+ if (!(b instanceof Set)) return false;
549
+ if (a.size !== b.size) return false;
550
+ if (isPrimitiveSet(a) && isPrimitiveSet(b)) {
551
+ for (const val of a) if (!b.has(val)) return false;
552
+ return true;
553
+ }
554
+ const remaining = [...b];
555
+ for (const valA of a) {
556
+ const stackLen = stack.length;
557
+ const idx = remaining.findIndex((valB) => {
558
+ const result = deepEqualInner(valA, valB, stack);
559
+ if (!result) stack.length = stackLen;
560
+ return result;
561
+ });
562
+ if (idx === -1) return false;
563
+ remaining.splice(idx, 1);
564
+ }
565
+ return true;
566
+ }
567
+ function compareDateOrRegExp(objA, objB) {
568
+ if (objA instanceof Date && objB instanceof Date) {
569
+ const ta = objA.getTime();
570
+ const tb = objB.getTime();
571
+ return ta === tb || Number.isNaN(ta) && Number.isNaN(tb);
572
+ }
573
+ if (objA instanceof Date || objB instanceof Date) return false;
574
+ if (objA instanceof RegExp && objB instanceof RegExp) return String(objA) === String(objB);
575
+ if (objA instanceof RegExp || objB instanceof RegExp) return false;
576
+ }
577
+ function compareCollectionOrRecord(objA, objB, stack) {
578
+ if (Array.isArray(objA)) return isArrayEqual(objA, objB, stack);
579
+ if (objA instanceof Map) return isMapEqual$1(objA, objB, stack);
580
+ if (objB instanceof Map) return false;
581
+ if (objA instanceof Set) return isSetEqual(objA, objB, stack);
582
+ if (objB instanceof Set) return false;
583
+ if (isRecord(objA)) return isRecordEqual(objA, objB, stack);
584
+ return false;
585
+ }
586
+ function deepEqualInner(a, b, stack) {
587
+ if (a === b) return true;
588
+ if (typeof a === "number" && typeof b === "number" && Number.isNaN(a) && Number.isNaN(b)) return true;
589
+ if (a === null || b === null) return a === b;
590
+ if (typeof a !== typeof b) return false;
591
+ if (typeof a !== "object") return false;
592
+ const objA = a;
593
+ const objB = b;
594
+ const dateRegExpResult = compareDateOrRegExp(objA, objB);
595
+ if (dateRegExpResult !== void 0) return dateRegExpResult;
596
+ for (const [sa, sb] of stack) if (sa === objA && sb === objB) return true;
597
+ stack.push([objA, objB]);
598
+ try {
599
+ return compareCollectionOrRecord(objA, objB, stack);
600
+ } finally {
601
+ stack.pop();
602
+ }
603
+ }
604
+ /**
605
+ * Deep equality comparison for structured data.
606
+ * Supports primitives, plain objects, arrays, Date, RegExp, Map, and Set.
607
+ *
608
+ * Note: `{ a: undefined }` and `{}` are NOT considered equal. This differs from
609
+ * JSON.stringify behavior but is intentional — explicit undefined properties are
610
+ * semantically distinct from absent properties.
611
+ *
612
+ * Set comparison is order-independent: each element in one set is matched to an
613
+ * element in the other using deep equality (O(n²) for non-primitive elements,
614
+ * O(n) fast path for primitive-only sets).
615
+ *
616
+ * Map keys are compared by reference (SameValueZero), not by deep equality.
617
+ * Two Maps with structurally equal but referentially different object keys
618
+ * will be considered unequal.
619
+ *
620
+ * NaN handling: `deepEqual(NaN, NaN)` returns `true`. Invalid Date objects
621
+ * (whose `getTime()` returns NaN) are also considered equal to each other.
622
+ *
623
+ * Signed zero: `-0` and `0` are considered equal (`-0 === 0` is `true`).
624
+ *
625
+ * Circular reference handling: uses a stack-based pair tracker. When the same
626
+ * `(objA, objB)` pair is encountered again on the current comparison path,
627
+ * structural equality is assumed to break the cycle. Shared references (DAG
628
+ * structures) are correctly handled — the same sub-object appearing in multiple
629
+ * properties does not cause false negatives.
630
+ */
631
+ function deepEqual(a, b) {
632
+ return deepEqualInner(a, b, []);
633
+ }
634
+ //#endregion
635
+ //#region src/core/domain/diff.ts
636
+ const typeOrder = {
637
+ added: 0,
638
+ modified: 1,
639
+ deleted: 2
640
+ };
641
+ function buildDiffResult(entries, warnings = []) {
642
+ const sorted = [...entries].sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);
643
+ let added = 0;
644
+ let modified = 0;
645
+ let deleted = 0;
646
+ for (const e of sorted) if (e.type === "added") added++;
647
+ else if (e.type === "modified") modified++;
648
+ else deleted++;
649
+ return {
650
+ entries: sorted,
651
+ summary: {
652
+ added,
653
+ modified,
654
+ deleted,
655
+ total: sorted.length
656
+ },
657
+ isEmpty: sorted.length === 0,
658
+ warnings
659
+ };
660
+ }
661
+ //#endregion
662
+ //#region src/core/domain/services/recordDiffDetector.ts
663
+ function detectRecordDiff(localRecord, remoteRecord, callbacks) {
664
+ const entries = [];
665
+ for (const [key, localValue] of Object.entries(localRecord)) if (!Object.hasOwn(remoteRecord, key)) entries.push(callbacks.onAdded(key, localValue));
666
+ else {
667
+ const entry = callbacks.onModified(key, localValue, remoteRecord[key]);
668
+ if (entry !== void 0) entries.push(entry);
669
+ }
670
+ for (const [key, remoteValue] of Object.entries(remoteRecord)) if (!Object.hasOwn(localRecord, key)) entries.push(callbacks.onDeleted(key, remoteValue));
671
+ return entries;
672
+ }
673
+ //#endregion
674
+ //#region src/core/domain/action/services/diffDetector.ts
675
+ function compareActions$1(local, remote) {
676
+ const diffs = [];
677
+ if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
678
+ if (!deepEqual(local.destApp, remote.destApp)) diffs.push("destApp changed");
679
+ if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
680
+ if (!deepEqual(local.mappings, remote.mappings)) if (local.mappings.length !== remote.mappings.length) diffs.push(`mappings: ${remote.mappings.length} -> ${local.mappings.length}`);
681
+ else diffs.push("mappings changed");
682
+ if (!deepEqual(local.entities, remote.entities)) diffs.push("entities changed");
683
+ return diffs;
684
+ }
685
+ function destAppLabel(action) {
686
+ return `dest: ${action.destApp.app ?? action.destApp.code ?? "(unspecified)"}`;
687
+ }
688
+ const ActionDiffDetector = { detect: (local, remote) => {
689
+ return buildDiffResult(detectRecordDiff(local.actions, remote.actions, {
690
+ onAdded: (name, localAction) => ({
691
+ type: "added",
692
+ actionName: name,
693
+ details: destAppLabel(localAction)
694
+ }),
695
+ onModified: (name, localAction, remoteAction) => {
696
+ const diffs = compareActions$1(localAction, remoteAction);
697
+ if (diffs.length > 0) return {
698
+ type: "modified",
699
+ actionName: name,
700
+ details: diffs.join(", ")
701
+ };
702
+ },
703
+ onDeleted: (name, remoteAction) => ({
704
+ type: "deleted",
705
+ actionName: name,
706
+ details: destAppLabel(remoteAction)
707
+ })
708
+ }));
709
+ } };
710
+ //#endregion
711
+ //#region src/core/application/detectDiffBase.ts
712
+ async function detectDiffFromConfig(config) {
713
+ const [storageResult, remote] = await Promise.all([config.getStorage(), config.fetchRemote()]);
714
+ if (!storageResult.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, config.notFoundMessage);
715
+ const local = config.parseConfig(storageResult.content);
716
+ return config.detect(local, remote);
717
+ }
718
+ //#endregion
719
+ //#region src/core/application/action/detectActionDiff.ts
720
+ async function detectActionDiff({ container }) {
721
+ return detectDiffFromConfig({
722
+ getStorage: () => container.actionStorage.get(),
723
+ fetchRemote: () => container.actionConfigurator.getActions(),
724
+ parseConfig: (content) => parseActionConfigText(container.configCodec, content),
725
+ detect: (local, remote) => ActionDiffDetector.detect(local, { actions: remote.actions }),
726
+ notFoundMessage: "Action config file not found"
727
+ });
728
+ }
729
+ //#endregion
500
730
  //#region src/core/adapters/kintone/wrapKintoneError.ts
501
731
  const KINTONE_REVISION_CONFLICT_CODE = "GAIA_CO02";
502
732
  const KINTONE_MAINTENANCE_MODE_CODE = "GAIA_NO02";
@@ -2508,9 +2738,27 @@ const { resolveFilePath: resolveActionFilePath, resolveContainerConfig: resolveA
2508
2738
  //#endregion
2509
2739
  //#region src/cli/commands/applyCommandFactory.ts
2510
2740
  function createApplyCommand(config) {
2511
- async function runApply(containerConfig) {
2741
+ async function runDiffPreview(containerConfig, diffPreview) {
2512
2742
  const container = config.createContainer(containerConfig);
2513
2743
  const s = p.spinner();
2744
+ s.start("Detecting changes...");
2745
+ let result;
2746
+ try {
2747
+ result = await diffPreview.detectDiff({ container });
2748
+ } catch (error) {
2749
+ s.stop("Comparison failed.");
2750
+ throw error;
2751
+ }
2752
+ s.stop("Comparison complete.");
2753
+ diffPreview.printResult(result);
2754
+ return {
2755
+ container,
2756
+ hasChanges: !result.isEmpty
2757
+ };
2758
+ }
2759
+ async function runApply(containerConfig, existingContainer) {
2760
+ const container = existingContainer ?? config.createContainer(containerConfig);
2761
+ const s = p.spinner();
2514
2762
  s.start(config.spinnerMessage);
2515
2763
  let result;
2516
2764
  try {
@@ -2537,21 +2785,91 @@ function createApplyCommand(config) {
2537
2785
  const skipConfirm = ctx.values.yes === true;
2538
2786
  await routeMultiApp(values, {
2539
2787
  singleLegacy: async () => {
2540
- await confirmAndDeploy([await runApply(config.resolveContainerConfig(values))], skipConfirm);
2788
+ const containerConfig = config.resolveContainerConfig(values);
2789
+ if (config.diffPreview) {
2790
+ const { container, hasChanges } = await runDiffPreview(containerConfig, config.diffPreview);
2791
+ if (!hasChanges) {
2792
+ p.log.success("No changes detected.");
2793
+ return;
2794
+ }
2795
+ if (!skipConfirm) {
2796
+ const shouldContinue = await p.confirm({ message: "Apply these changes?" });
2797
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
2798
+ p.cancel("Apply cancelled.");
2799
+ return;
2800
+ }
2801
+ }
2802
+ await confirmAndDeploy([await runApply(containerConfig, container)], skipConfirm);
2803
+ } else await confirmAndDeploy([await runApply(containerConfig)], skipConfirm);
2541
2804
  },
2542
2805
  singleApp: async (app, projectConfig) => {
2543
- await confirmAndDeploy([await runApply(config.resolveAppContainerConfig(app, projectConfig, values))], skipConfirm);
2806
+ const containerConfig = config.resolveAppContainerConfig(app, projectConfig, values);
2807
+ if (config.diffPreview) {
2808
+ const { container, hasChanges } = await runDiffPreview(containerConfig, config.diffPreview);
2809
+ if (!hasChanges) {
2810
+ p.log.success("No changes detected.");
2811
+ return;
2812
+ }
2813
+ if (!skipConfirm) {
2814
+ const shouldContinue = await p.confirm({ message: "Apply these changes?" });
2815
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
2816
+ p.cancel("Apply cancelled.");
2817
+ return;
2818
+ }
2819
+ }
2820
+ await confirmAndDeploy([await runApply(containerConfig, container)], skipConfirm);
2821
+ } else await confirmAndDeploy([await runApply(containerConfig)], skipConfirm);
2544
2822
  },
2545
2823
  multiApp: async (plan, projectConfig) => {
2546
- const containers = [];
2547
- await runMultiAppWithHeaders(plan, async (app) => {
2548
- const container = await runApply(config.resolveAppContainerConfig(app, projectConfig, values));
2549
- containers.push({
2550
- appDeployer: container.appDeployer,
2551
- appName: app.name
2824
+ if (config.diffPreview) {
2825
+ const appDiffResults = [];
2826
+ for (const app of plan.orderedApps) {
2827
+ const containerConfig = config.resolveAppContainerConfig(app, projectConfig, values);
2828
+ printAppHeader(app.name, app.appId);
2829
+ const { container, hasChanges } = await runDiffPreview(containerConfig, config.diffPreview);
2830
+ appDiffResults.push({
2831
+ app,
2832
+ container,
2833
+ hasChanges
2834
+ });
2835
+ }
2836
+ if (!appDiffResults.some((a) => a.hasChanges)) {
2837
+ p.log.success("No changes detected in any app.");
2838
+ return;
2839
+ }
2840
+ if (!skipConfirm) {
2841
+ const shouldContinue = await p.confirm({ message: "Apply these changes to all apps?" });
2842
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
2843
+ p.cancel("Apply cancelled.");
2844
+ return;
2845
+ }
2846
+ }
2847
+ const containers = [];
2848
+ await runMultiAppWithHeaders(plan, async (app) => {
2849
+ const entry = appDiffResults.find((a) => a.app.name === app.name);
2850
+ if (!entry) throw new SystemError(SystemErrorCode.InternalServerError, `App container not found for "${app.name}"`);
2851
+ if (!entry.hasChanges) {
2852
+ p.log.info("No changes. Skipping.");
2853
+ return;
2854
+ }
2855
+ await runApply(config.resolveAppContainerConfig(app, projectConfig, values), entry.container);
2856
+ containers.push({
2857
+ appDeployer: entry.container.appDeployer,
2858
+ appName: app.name
2859
+ });
2552
2860
  });
2553
- });
2554
- await confirmAndDeploy(containers, skipConfirm);
2861
+ await confirmAndDeploy(containers, skipConfirm);
2862
+ } else {
2863
+ const containers = [];
2864
+ await runMultiAppWithHeaders(plan, async (app) => {
2865
+ const container = await runApply(config.resolveAppContainerConfig(app, projectConfig, values));
2866
+ containers.push({
2867
+ appDeployer: container.appDeployer,
2868
+ appName: app.name
2869
+ });
2870
+ });
2871
+ await confirmAndDeploy(containers, skipConfirm);
2872
+ }
2555
2873
  }
2556
2874
  });
2557
2875
  } catch (error) {
@@ -2570,6 +2888,10 @@ var apply_default$12 = createApplyCommand({
2570
2888
  successMessage: "Action settings applied successfully.",
2571
2889
  createContainer: createActionCliContainer,
2572
2890
  applyFn: applyAction,
2891
+ diffPreview: {
2892
+ detectDiff: detectActionDiff,
2893
+ printResult: printActionDiffResult
2894
+ },
2573
2895
  resolveContainerConfig: resolveActionContainerConfig,
2574
2896
  resolveAppContainerConfig: resolveActionAppContainerConfig
2575
2897
  });
@@ -2710,236 +3032,6 @@ var capture_default$13 = createCaptureCommand({
2710
3032
  resolveAppContainerConfig: resolveActionAppContainerConfig
2711
3033
  });
2712
3034
  //#endregion
2713
- //#region src/lib/deepEqual.ts
2714
- function isArrayEqual(a, b, stack) {
2715
- if (!Array.isArray(b)) return false;
2716
- if (a.length !== b.length) return false;
2717
- for (let i = 0; i < a.length; i++) if (!deepEqualInner(a[i], b[i], stack)) return false;
2718
- return true;
2719
- }
2720
- /**
2721
- * Compare two objects by own enumerable string keys (via `Object.keys`).
2722
- *
2723
- * Note: Custom class instances (e.g. `new Error()`) that pass `isRecord` are
2724
- * accepted. Because `Error.prototype.message` is non-enumerable, two Error
2725
- * objects with different messages will compare as equal. Callers should not
2726
- * rely on this function for types with non-enumerable significant state.
2727
- */
2728
- function isRecordEqual(a, b, stack) {
2729
- if (!isRecord(b)) return false;
2730
- const keysA = Object.keys(a);
2731
- const keysB = Object.keys(b);
2732
- if (keysA.length !== keysB.length) return false;
2733
- for (const key of keysA) {
2734
- if (!Object.hasOwn(b, key)) return false;
2735
- if (!deepEqualInner(a[key], b[key], stack)) return false;
2736
- }
2737
- return true;
2738
- }
2739
- /**
2740
- * Map keys are compared by reference (SameValueZero), not by deep equality.
2741
- * Two Maps with structurally equal but referentially different object keys
2742
- * will be considered unequal.
2743
- */
2744
- function isMapEqual$1(a, b, stack) {
2745
- if (!(b instanceof Map)) return false;
2746
- if (a.size !== b.size) return false;
2747
- for (const [key, valA] of a) {
2748
- if (!b.has(key)) return false;
2749
- if (!deepEqualInner(valA, b.get(key), stack)) return false;
2750
- }
2751
- return true;
2752
- }
2753
- function isPrimitiveSet(s) {
2754
- for (const val of s) {
2755
- if (val !== null && typeof val === "object") return false;
2756
- if (typeof val === "function") return false;
2757
- }
2758
- return true;
2759
- }
2760
- function isSetEqual(a, b, stack) {
2761
- if (!(b instanceof Set)) return false;
2762
- if (a.size !== b.size) return false;
2763
- if (isPrimitiveSet(a) && isPrimitiveSet(b)) {
2764
- for (const val of a) if (!b.has(val)) return false;
2765
- return true;
2766
- }
2767
- const remaining = [...b];
2768
- for (const valA of a) {
2769
- const stackLen = stack.length;
2770
- const idx = remaining.findIndex((valB) => {
2771
- const result = deepEqualInner(valA, valB, stack);
2772
- if (!result) stack.length = stackLen;
2773
- return result;
2774
- });
2775
- if (idx === -1) return false;
2776
- remaining.splice(idx, 1);
2777
- }
2778
- return true;
2779
- }
2780
- function compareDateOrRegExp(objA, objB) {
2781
- if (objA instanceof Date && objB instanceof Date) {
2782
- const ta = objA.getTime();
2783
- const tb = objB.getTime();
2784
- return ta === tb || Number.isNaN(ta) && Number.isNaN(tb);
2785
- }
2786
- if (objA instanceof Date || objB instanceof Date) return false;
2787
- if (objA instanceof RegExp && objB instanceof RegExp) return String(objA) === String(objB);
2788
- if (objA instanceof RegExp || objB instanceof RegExp) return false;
2789
- }
2790
- function compareCollectionOrRecord(objA, objB, stack) {
2791
- if (Array.isArray(objA)) return isArrayEqual(objA, objB, stack);
2792
- if (objA instanceof Map) return isMapEqual$1(objA, objB, stack);
2793
- if (objB instanceof Map) return false;
2794
- if (objA instanceof Set) return isSetEqual(objA, objB, stack);
2795
- if (objB instanceof Set) return false;
2796
- if (isRecord(objA)) return isRecordEqual(objA, objB, stack);
2797
- return false;
2798
- }
2799
- function deepEqualInner(a, b, stack) {
2800
- if (a === b) return true;
2801
- if (typeof a === "number" && typeof b === "number" && Number.isNaN(a) && Number.isNaN(b)) return true;
2802
- if (a === null || b === null) return a === b;
2803
- if (typeof a !== typeof b) return false;
2804
- if (typeof a !== "object") return false;
2805
- const objA = a;
2806
- const objB = b;
2807
- const dateRegExpResult = compareDateOrRegExp(objA, objB);
2808
- if (dateRegExpResult !== void 0) return dateRegExpResult;
2809
- for (const [sa, sb] of stack) if (sa === objA && sb === objB) return true;
2810
- stack.push([objA, objB]);
2811
- try {
2812
- return compareCollectionOrRecord(objA, objB, stack);
2813
- } finally {
2814
- stack.pop();
2815
- }
2816
- }
2817
- /**
2818
- * Deep equality comparison for structured data.
2819
- * Supports primitives, plain objects, arrays, Date, RegExp, Map, and Set.
2820
- *
2821
- * Note: `{ a: undefined }` and `{}` are NOT considered equal. This differs from
2822
- * JSON.stringify behavior but is intentional — explicit undefined properties are
2823
- * semantically distinct from absent properties.
2824
- *
2825
- * Set comparison is order-independent: each element in one set is matched to an
2826
- * element in the other using deep equality (O(n²) for non-primitive elements,
2827
- * O(n) fast path for primitive-only sets).
2828
- *
2829
- * Map keys are compared by reference (SameValueZero), not by deep equality.
2830
- * Two Maps with structurally equal but referentially different object keys
2831
- * will be considered unequal.
2832
- *
2833
- * NaN handling: `deepEqual(NaN, NaN)` returns `true`. Invalid Date objects
2834
- * (whose `getTime()` returns NaN) are also considered equal to each other.
2835
- *
2836
- * Signed zero: `-0` and `0` are considered equal (`-0 === 0` is `true`).
2837
- *
2838
- * Circular reference handling: uses a stack-based pair tracker. When the same
2839
- * `(objA, objB)` pair is encountered again on the current comparison path,
2840
- * structural equality is assumed to break the cycle. Shared references (DAG
2841
- * structures) are correctly handled — the same sub-object appearing in multiple
2842
- * properties does not cause false negatives.
2843
- */
2844
- function deepEqual(a, b) {
2845
- return deepEqualInner(a, b, []);
2846
- }
2847
- //#endregion
2848
- //#region src/core/domain/diff.ts
2849
- const typeOrder = {
2850
- added: 0,
2851
- modified: 1,
2852
- deleted: 2
2853
- };
2854
- function buildDiffResult(entries, warnings = []) {
2855
- const sorted = [...entries].sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);
2856
- let added = 0;
2857
- let modified = 0;
2858
- let deleted = 0;
2859
- for (const e of sorted) if (e.type === "added") added++;
2860
- else if (e.type === "modified") modified++;
2861
- else deleted++;
2862
- return {
2863
- entries: sorted,
2864
- summary: {
2865
- added,
2866
- modified,
2867
- deleted,
2868
- total: sorted.length
2869
- },
2870
- isEmpty: sorted.length === 0,
2871
- warnings
2872
- };
2873
- }
2874
- //#endregion
2875
- //#region src/core/domain/services/recordDiffDetector.ts
2876
- function detectRecordDiff(localRecord, remoteRecord, callbacks) {
2877
- const entries = [];
2878
- for (const [key, localValue] of Object.entries(localRecord)) if (!Object.hasOwn(remoteRecord, key)) entries.push(callbacks.onAdded(key, localValue));
2879
- else {
2880
- const entry = callbacks.onModified(key, localValue, remoteRecord[key]);
2881
- if (entry !== void 0) entries.push(entry);
2882
- }
2883
- for (const [key, remoteValue] of Object.entries(remoteRecord)) if (!Object.hasOwn(localRecord, key)) entries.push(callbacks.onDeleted(key, remoteValue));
2884
- return entries;
2885
- }
2886
- //#endregion
2887
- //#region src/core/domain/action/services/diffDetector.ts
2888
- function compareActions$1(local, remote) {
2889
- const diffs = [];
2890
- if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
2891
- if (!deepEqual(local.destApp, remote.destApp)) diffs.push("destApp changed");
2892
- if (local.filterCond !== remote.filterCond) diffs.push("filterCond changed");
2893
- if (!deepEqual(local.mappings, remote.mappings)) if (local.mappings.length !== remote.mappings.length) diffs.push(`mappings: ${remote.mappings.length} -> ${local.mappings.length}`);
2894
- else diffs.push("mappings changed");
2895
- if (!deepEqual(local.entities, remote.entities)) diffs.push("entities changed");
2896
- return diffs;
2897
- }
2898
- function destAppLabel(action) {
2899
- return `dest: ${action.destApp.app ?? action.destApp.code ?? "(unspecified)"}`;
2900
- }
2901
- const ActionDiffDetector = { detect: (local, remote) => {
2902
- return buildDiffResult(detectRecordDiff(local.actions, remote.actions, {
2903
- onAdded: (name, localAction) => ({
2904
- type: "added",
2905
- actionName: name,
2906
- details: destAppLabel(localAction)
2907
- }),
2908
- onModified: (name, localAction, remoteAction) => {
2909
- const diffs = compareActions$1(localAction, remoteAction);
2910
- if (diffs.length > 0) return {
2911
- type: "modified",
2912
- actionName: name,
2913
- details: diffs.join(", ")
2914
- };
2915
- },
2916
- onDeleted: (name, remoteAction) => ({
2917
- type: "deleted",
2918
- actionName: name,
2919
- details: destAppLabel(remoteAction)
2920
- })
2921
- }));
2922
- } };
2923
- //#endregion
2924
- //#region src/core/application/detectDiffBase.ts
2925
- async function detectDiffFromConfig(config) {
2926
- const [storageResult, remote] = await Promise.all([config.getStorage(), config.fetchRemote()]);
2927
- if (!storageResult.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, config.notFoundMessage);
2928
- const local = config.parseConfig(storageResult.content);
2929
- return config.detect(local, remote);
2930
- }
2931
- //#endregion
2932
- //#region src/core/application/action/detectActionDiff.ts
2933
- async function detectActionDiff({ container }) {
2934
- return detectDiffFromConfig({
2935
- getStorage: () => container.actionStorage.get(),
2936
- fetchRemote: () => container.actionConfigurator.getActions(),
2937
- parseConfig: (content) => parseActionConfigText(container.configCodec, content),
2938
- detect: (local, remote) => ActionDiffDetector.detect(local, { actions: remote.actions }),
2939
- notFoundMessage: "Action config file not found"
2940
- });
2941
- }
2942
- //#endregion
2943
3035
  //#region src/cli/commands/diffCommandFactory.ts
2944
3036
  function createDiffCommand(config) {
2945
3037
  async function runDiff(containerConfig) {
@@ -3040,6 +3132,41 @@ async function applyAdminNotes({ container }) {
3040
3132
  });
3041
3133
  }
3042
3134
  //#endregion
3135
+ //#region src/core/domain/adminNotes/services/diffDetector.ts
3136
+ const CONTENT_TRUNCATE_LENGTH = 30;
3137
+ function truncate(text, maxLength) {
3138
+ if (text.length <= maxLength) return text;
3139
+ return `${text.slice(0, maxLength)}...`;
3140
+ }
3141
+ function compareConfigs$2(local, remote) {
3142
+ const entries = [];
3143
+ if (local.content !== remote.content) entries.push({
3144
+ type: "modified",
3145
+ field: "content",
3146
+ details: `"${truncate(remote.content, CONTENT_TRUNCATE_LENGTH)}" -> "${truncate(local.content, CONTENT_TRUNCATE_LENGTH)}"`
3147
+ });
3148
+ if (local.includeInTemplateAndDuplicates !== remote.includeInTemplateAndDuplicates) entries.push({
3149
+ type: "modified",
3150
+ field: "includeInTemplateAndDuplicates",
3151
+ details: `${String(remote.includeInTemplateAndDuplicates)} -> ${String(local.includeInTemplateAndDuplicates)}`
3152
+ });
3153
+ return entries;
3154
+ }
3155
+ const AdminNotesDiffDetector = { detect: (local, remote) => {
3156
+ return buildDiffResult(compareConfigs$2(local, remote));
3157
+ } };
3158
+ //#endregion
3159
+ //#region src/core/application/adminNotes/detectAdminNotesDiff.ts
3160
+ async function detectAdminNotesDiff({ container }) {
3161
+ return detectDiffFromConfig({
3162
+ getStorage: () => container.adminNotesStorage.get(),
3163
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
3164
+ parseConfig: (content) => parseAdminNotesConfigText(container.configCodec, content),
3165
+ detect: (local, remote) => AdminNotesDiffDetector.detect(local, remote.config),
3166
+ notFoundMessage: "Admin notes config file not found"
3167
+ });
3168
+ }
3169
+ //#endregion
3043
3170
  //#region src/core/adapters/kintone/adminNotesConfigurator.ts
3044
3171
  var KintoneAdminNotesConfigurator = class {
3045
3172
  constructor(client, appId) {
@@ -3124,6 +3251,10 @@ var apply_default$11 = createApplyCommand({
3124
3251
  successMessage: "Admin notes applied successfully.",
3125
3252
  createContainer: createAdminNotesCliContainer,
3126
3253
  applyFn: applyAdminNotes,
3254
+ diffPreview: {
3255
+ detectDiff: detectAdminNotesDiff,
3256
+ printResult: printAdminNotesDiffResult
3257
+ },
3127
3258
  resolveContainerConfig: resolveAdminNotesContainerConfig,
3128
3259
  resolveAppContainerConfig: resolveAdminNotesAppContainerConfig
3129
3260
  });
@@ -3150,59 +3281,26 @@ async function saveAdminNotes({ container, input }) {
3150
3281
  await container.adminNotesStorage.update(input.configText);
3151
3282
  }
3152
3283
  //#endregion
3153
- //#region src/cli/commands/admin-notes/capture.ts
3154
- var capture_default$12 = createCaptureCommand({
3155
- description: "Capture current admin notes from kintone app to file",
3156
- args: adminNotesArgs,
3157
- spinnerMessage: "Capturing admin notes...",
3158
- spinnerStopMessage: "Admin notes captured.",
3159
- domainLabel: "Admin notes",
3160
- multiAppSuccessMessage: "All admin notes captures completed successfully.",
3161
- createContainer: createAdminNotesCliContainer,
3162
- captureFn: captureAdminNotes,
3163
- saveFn: saveAdminNotes,
3164
- getConfigFilePath: (config) => config.adminNotesFilePath,
3165
- resolveContainerConfig: resolveAdminNotesContainerConfig,
3166
- resolveAppContainerConfig: resolveAdminNotesAppContainerConfig
3167
- });
3168
- //#endregion
3169
- //#region src/core/domain/adminNotes/services/diffDetector.ts
3170
- function compareConfigs$2(local, remote) {
3171
- const entries = [];
3172
- if (local.content !== remote.content) entries.push({
3173
- type: "modified",
3174
- field: "content",
3175
- details: "content changed"
3176
- });
3177
- if (local.includeInTemplateAndDuplicates !== remote.includeInTemplateAndDuplicates) entries.push({
3178
- type: "modified",
3179
- field: "includeInTemplateAndDuplicates",
3180
- details: `${String(remote.includeInTemplateAndDuplicates)} -> ${String(local.includeInTemplateAndDuplicates)}`
3181
- });
3182
- return entries;
3183
- }
3184
- const AdminNotesDiffDetector = { detect: (local, remote) => {
3185
- return buildDiffResult(compareConfigs$2(local, remote));
3186
- } };
3187
- //#endregion
3188
- //#region src/core/application/adminNotes/detectAdminNotesDiff.ts
3189
- async function detectAdminNotesDiff({ container }) {
3190
- return detectDiffFromConfig({
3191
- getStorage: () => container.adminNotesStorage.get(),
3192
- fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
3193
- parseConfig: (content) => parseAdminNotesConfigText(container.configCodec, content),
3194
- detect: (local, remote) => AdminNotesDiffDetector.detect(local, remote.config),
3195
- notFoundMessage: "Admin notes config file not found"
3196
- });
3197
- }
3198
- //#endregion
3199
3284
  //#region src/cli/commands/admin-notes/index.ts
3200
3285
  var admin_notes_default = define({
3201
3286
  name: "admin-notes",
3202
3287
  description: "Manage kintone admin notes",
3203
3288
  subCommands: {
3204
3289
  apply: apply_default$11,
3205
- capture: capture_default$12,
3290
+ capture: createCaptureCommand({
3291
+ description: "Capture current admin notes from kintone app to file",
3292
+ args: adminNotesArgs,
3293
+ spinnerMessage: "Capturing admin notes...",
3294
+ spinnerStopMessage: "Admin notes captured.",
3295
+ domainLabel: "Admin notes",
3296
+ multiAppSuccessMessage: "All admin notes captures completed successfully.",
3297
+ createContainer: createAdminNotesCliContainer,
3298
+ captureFn: captureAdminNotes,
3299
+ saveFn: saveAdminNotes,
3300
+ getConfigFilePath: (config) => config.adminNotesFilePath,
3301
+ resolveContainerConfig: resolveAdminNotesContainerConfig,
3302
+ resolveAppContainerConfig: resolveAdminNotesAppContainerConfig
3303
+ }),
3206
3304
  diff: createDiffCommand({
3207
3305
  description: "Compare local admin notes config with remote kintone app",
3208
3306
  args: adminNotesArgs,
@@ -3281,6 +3379,68 @@ async function applyAppPermission({ container }) {
3281
3379
  });
3282
3380
  }
3283
3381
  //#endregion
3382
+ //#region src/core/domain/appPermission/services/diffDetector.ts
3383
+ const BOOLEAN_FLAGS = [
3384
+ "includeSubs",
3385
+ "appEditable",
3386
+ "recordViewable",
3387
+ "recordAddable",
3388
+ "recordEditable",
3389
+ "recordDeletable",
3390
+ "recordImportable",
3391
+ "recordExportable"
3392
+ ];
3393
+ function entityKey(right) {
3394
+ return `${right.entity.type}:${right.entity.code}`;
3395
+ }
3396
+ function describeRight$1(right) {
3397
+ const flags = BOOLEAN_FLAGS.filter((f) => f !== "includeSubs" && right[f]);
3398
+ return flags.length > 0 ? flags.join(", ") : "no permissions";
3399
+ }
3400
+ function compareRights(local, remote) {
3401
+ const diffs = [];
3402
+ for (const flag of BOOLEAN_FLAGS) if (local[flag] !== remote[flag]) diffs.push(`${flag}: ${String(remote[flag])} -> ${String(local[flag])}`);
3403
+ return diffs;
3404
+ }
3405
+ const AppPermissionDiffDetector = { detect: (local, remote) => {
3406
+ const entries = [];
3407
+ const localMap = new Map(local.rights.map((r) => [entityKey(r), r]));
3408
+ const remoteMap = new Map(remote.rights.map((r) => [entityKey(r), r]));
3409
+ for (const [key, localRight] of localMap) {
3410
+ const remoteRight = remoteMap.get(key);
3411
+ if (!remoteRight) entries.push({
3412
+ type: "added",
3413
+ entityKey: key,
3414
+ details: describeRight$1(localRight)
3415
+ });
3416
+ else {
3417
+ const diffs = compareRights(localRight, remoteRight);
3418
+ if (diffs.length > 0) entries.push({
3419
+ type: "modified",
3420
+ entityKey: key,
3421
+ details: diffs.join(", ")
3422
+ });
3423
+ }
3424
+ }
3425
+ for (const [key, remoteRight] of remoteMap) if (!localMap.has(key)) entries.push({
3426
+ type: "deleted",
3427
+ entityKey: key,
3428
+ details: describeRight$1(remoteRight)
3429
+ });
3430
+ return buildDiffResult(entries);
3431
+ } };
3432
+ //#endregion
3433
+ //#region src/core/application/appPermission/detectAppPermissionDiff.ts
3434
+ async function detectAppPermissionDiff({ container }) {
3435
+ return detectDiffFromConfig({
3436
+ getStorage: () => container.appPermissionStorage.get(),
3437
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3438
+ parseConfig: (content) => parseAppPermissionConfigText(container.configCodec, content),
3439
+ detect: (local, remote) => AppPermissionDiffDetector.detect(local, { rights: remote.rights }),
3440
+ notFoundMessage: "App permission config file not found"
3441
+ });
3442
+ }
3443
+ //#endregion
3284
3444
  //#region src/core/adapters/kintone/appPermissionConfigurator.ts
3285
3445
  const VALID_ENTITY_TYPES$7 = new Set([
3286
3446
  "USER",
@@ -3407,6 +3567,10 @@ var apply_default$10 = createApplyCommand({
3407
3567
  successMessage: "App access permissions applied successfully.",
3408
3568
  createContainer: createAppPermissionCliContainer,
3409
3569
  applyFn: applyAppPermission,
3570
+ diffPreview: {
3571
+ detectDiff: detectAppPermissionDiff,
3572
+ printResult: printAppPermissionDiffResult
3573
+ },
3410
3574
  resolveContainerConfig: resolveAppAclContainerConfig,
3411
3575
  resolveAppContainerConfig: resolveAppAclAppContainerConfig
3412
3576
  });
@@ -3446,91 +3610,26 @@ async function saveAppPermission({ container, input }) {
3446
3610
  await container.appPermissionStorage.update(input.configText);
3447
3611
  }
3448
3612
  //#endregion
3449
- //#region src/cli/commands/app-acl/capture.ts
3450
- var capture_default$11 = createCaptureCommand({
3451
- description: "Capture current app access permissions from kintone app to file",
3452
- args: appAclArgs,
3453
- spinnerMessage: "Capturing app access permissions...",
3454
- spinnerStopMessage: "App access permissions captured.",
3455
- domainLabel: "App ACL",
3456
- multiAppSuccessMessage: "All app ACL captures completed successfully.",
3457
- createContainer: createAppPermissionCliContainer,
3458
- captureFn: captureAppPermission,
3459
- saveFn: saveAppPermission,
3460
- getConfigFilePath: (config) => config.appAclFilePath,
3461
- resolveContainerConfig: resolveAppAclContainerConfig,
3462
- resolveAppContainerConfig: resolveAppAclAppContainerConfig
3463
- });
3464
- //#endregion
3465
- //#region src/core/domain/appPermission/services/diffDetector.ts
3466
- const BOOLEAN_FLAGS = [
3467
- "includeSubs",
3468
- "appEditable",
3469
- "recordViewable",
3470
- "recordAddable",
3471
- "recordEditable",
3472
- "recordDeletable",
3473
- "recordImportable",
3474
- "recordExportable"
3475
- ];
3476
- function entityKey(right) {
3477
- return `${right.entity.type}:${right.entity.code}`;
3478
- }
3479
- function describeRight$1(right) {
3480
- const flags = BOOLEAN_FLAGS.filter((f) => f !== "includeSubs" && right[f]);
3481
- return flags.length > 0 ? flags.join(", ") : "no permissions";
3482
- }
3483
- function compareRights(local, remote) {
3484
- const diffs = [];
3485
- for (const flag of BOOLEAN_FLAGS) if (local[flag] !== remote[flag]) diffs.push(`${flag}: ${String(remote[flag])} -> ${String(local[flag])}`);
3486
- return diffs;
3487
- }
3488
- const AppPermissionDiffDetector = { detect: (local, remote) => {
3489
- const entries = [];
3490
- const localMap = new Map(local.rights.map((r) => [entityKey(r), r]));
3491
- const remoteMap = new Map(remote.rights.map((r) => [entityKey(r), r]));
3492
- for (const [key, localRight] of localMap) {
3493
- const remoteRight = remoteMap.get(key);
3494
- if (!remoteRight) entries.push({
3495
- type: "added",
3496
- entityKey: key,
3497
- details: describeRight$1(localRight)
3498
- });
3499
- else {
3500
- const diffs = compareRights(localRight, remoteRight);
3501
- if (diffs.length > 0) entries.push({
3502
- type: "modified",
3503
- entityKey: key,
3504
- details: diffs.join(", ")
3505
- });
3506
- }
3507
- }
3508
- for (const [key, remoteRight] of remoteMap) if (!localMap.has(key)) entries.push({
3509
- type: "deleted",
3510
- entityKey: key,
3511
- details: describeRight$1(remoteRight)
3512
- });
3513
- return buildDiffResult(entries);
3514
- } };
3515
- //#endregion
3516
- //#region src/core/application/appPermission/detectAppPermissionDiff.ts
3517
- async function detectAppPermissionDiff({ container }) {
3518
- return detectDiffFromConfig({
3519
- getStorage: () => container.appPermissionStorage.get(),
3520
- fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3521
- parseConfig: (content) => parseAppPermissionConfigText(container.configCodec, content),
3522
- detect: (local, remote) => AppPermissionDiffDetector.detect(local, { rights: remote.rights }),
3523
- notFoundMessage: "App permission config file not found"
3524
- });
3525
- }
3526
- //#endregion
3527
3613
  //#region src/cli/commands/app-acl/index.ts
3528
3614
  var app_acl_default = define({
3529
3615
  name: "app-acl",
3530
3616
  description: "Manage kintone app access permissions",
3531
3617
  subCommands: {
3532
3618
  apply: apply_default$10,
3533
- capture: capture_default$11,
3619
+ capture: createCaptureCommand({
3620
+ description: "Capture current app access permissions from kintone app to file",
3621
+ args: appAclArgs,
3622
+ spinnerMessage: "Capturing app access permissions...",
3623
+ spinnerStopMessage: "App access permissions captured.",
3624
+ domainLabel: "App ACL",
3625
+ multiAppSuccessMessage: "All app ACL captures completed successfully.",
3626
+ createContainer: createAppPermissionCliContainer,
3627
+ captureFn: captureAppPermission,
3628
+ saveFn: saveAppPermission,
3629
+ getConfigFilePath: (config) => config.appAclFilePath,
3630
+ resolveContainerConfig: resolveAppAclContainerConfig,
3631
+ resolveAppContainerConfig: resolveAppAclAppContainerConfig
3632
+ }),
3534
3633
  diff: createDiffCommand({
3535
3634
  description: "Compare local app permission config with remote kintone app",
3536
3635
  args: appAclArgs,
@@ -3650,35 +3749,124 @@ async function resolveResources(platform, basePath, fileUploader) {
3650
3749
  css
3651
3750
  };
3652
3751
  }
3653
- function mergePlatform(current, incoming) {
3654
- return {
3655
- js: ResourceMerger.mergeResources(current.js, incoming.js),
3656
- css: ResourceMerger.mergeResources(current.css, incoming.css)
3657
- };
3752
+ function mergePlatform(current, incoming) {
3753
+ return {
3754
+ js: ResourceMerger.mergeResources(current.js, incoming.js),
3755
+ css: ResourceMerger.mergeResources(current.css, incoming.css)
3756
+ };
3757
+ }
3758
+ async function applyCustomization({ container, input }) {
3759
+ const result = await container.customizationStorage.get();
3760
+ if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Customization config file not found");
3761
+ const config = parseCustomizationConfigText(container.configCodec, result.content);
3762
+ const currentCustomization = await container.customizationConfigurator.getCustomization();
3763
+ const [resolvedDesktop, resolvedMobile] = await Promise.all([resolveResources(config.desktop, input.basePath, container.fileUploader), resolveResources(config.mobile, input.basePath, container.fileUploader)]);
3764
+ const mergedDesktop = mergePlatform(currentCustomization.desktop, resolvedDesktop);
3765
+ const mergedMobile = mergePlatform(currentCustomization.mobile, resolvedMobile);
3766
+ ResourceMerger.assertResourceCount("desktop.js", mergedDesktop.js);
3767
+ ResourceMerger.assertResourceCount("desktop.css", mergedDesktop.css);
3768
+ ResourceMerger.assertResourceCount("mobile.js", mergedMobile.js);
3769
+ ResourceMerger.assertResourceCount("mobile.css", mergedMobile.css);
3770
+ await container.customizationConfigurator.updateCustomization({
3771
+ scope: config.scope,
3772
+ desktop: {
3773
+ js: mergedDesktop.js,
3774
+ css: mergedDesktop.css
3775
+ },
3776
+ mobile: {
3777
+ js: mergedMobile.js,
3778
+ css: mergedMobile.css
3779
+ },
3780
+ revision: currentCustomization.revision
3781
+ });
3782
+ }
3783
+ //#endregion
3784
+ //#region src/core/domain/customization/services/diffDetector.ts
3785
+ function resourceName(resource) {
3786
+ if (resource.type === "URL") return resource.url;
3787
+ const parts = resource.path.replace(/\\/g, "/").split("/").filter(Boolean);
3788
+ return parts[parts.length - 1];
3789
+ }
3790
+ function remoteResourceName(resource) {
3791
+ if (resource.type === "URL") return resource.url;
3792
+ return resource.file.name;
3793
+ }
3794
+ function compareResourceLists(localResources, remoteResources, platform, resourceType, warnings) {
3795
+ const entries = [];
3796
+ const localNames = localResources.map(resourceName);
3797
+ const remoteNames = remoteResources.map(remoteResourceName);
3798
+ const localNameSet = new Set(localNames);
3799
+ const remoteNameSet = new Set(remoteNames);
3800
+ const hasDuplicates = localNames.length !== localNameSet.size || remoteNames.length !== remoteNameSet.size;
3801
+ if (hasDuplicates) warnings.push(`[${platform}.${resourceType}] duplicate basenames detected; diff results may be inaccurate for FILE resources`);
3802
+ const localTypeMap = /* @__PURE__ */ new Map();
3803
+ for (const r of localResources) localTypeMap.set(resourceName(r), r.type);
3804
+ const remoteTypeMap = /* @__PURE__ */ new Map();
3805
+ for (const r of remoteResources) remoteTypeMap.set(remoteResourceName(r), r.type);
3806
+ for (const name of localNameSet) if (!remoteNameSet.has(name)) {
3807
+ const resType = localTypeMap.get(name) ?? "FILE";
3808
+ entries.push({
3809
+ type: "added",
3810
+ platform,
3811
+ category: resourceType,
3812
+ name,
3813
+ details: `new ${resType} resource`
3814
+ });
3815
+ }
3816
+ for (const name of remoteNameSet) if (!localNameSet.has(name)) {
3817
+ const resType = remoteTypeMap.get(name) ?? "FILE";
3818
+ entries.push({
3819
+ type: "deleted",
3820
+ platform,
3821
+ category: resourceType,
3822
+ name,
3823
+ details: `removed ${resType} resource`
3824
+ });
3825
+ }
3826
+ const matchedFiles = [...localNameSet].filter((n) => remoteNameSet.has(n));
3827
+ const hasLocalFiles = localResources.some((r) => r.type === "FILE");
3828
+ const hasRemoteFiles = remoteResources.some((r) => r.type === "FILE");
3829
+ if (matchedFiles.length > 0 && hasLocalFiles && hasRemoteFiles) warnings.push(`[${platform}.${resourceType}] FILE resources are compared by name only; content changes are not detected`);
3830
+ if (!hasDuplicates) {
3831
+ const localShared = localNames.filter((n) => remoteNameSet.has(n));
3832
+ const remoteShared = remoteNames.filter((n) => localNameSet.has(n));
3833
+ if (localShared.length > 1 && localShared.length === remoteShared.length && localShared.some((n, i) => n !== remoteShared[i])) entries.push({
3834
+ type: "modified",
3835
+ platform,
3836
+ category: resourceType,
3837
+ name: "(order)",
3838
+ details: "resource load order changed"
3839
+ });
3840
+ }
3841
+ return entries;
3842
+ }
3843
+ function comparePlatform(localJs, localCss, remote, platform, warnings) {
3844
+ return [...compareResourceLists(localJs, remote.js, platform, "js", warnings), ...compareResourceLists(localCss, remote.css, platform, "css", warnings)];
3658
3845
  }
3659
- async function applyCustomization({ container, input }) {
3660
- const result = await container.customizationStorage.get();
3661
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Customization config file not found");
3662
- const config = parseCustomizationConfigText(container.configCodec, result.content);
3663
- const currentCustomization = await container.customizationConfigurator.getCustomization();
3664
- const [resolvedDesktop, resolvedMobile] = await Promise.all([resolveResources(config.desktop, input.basePath, container.fileUploader), resolveResources(config.mobile, input.basePath, container.fileUploader)]);
3665
- const mergedDesktop = mergePlatform(currentCustomization.desktop, resolvedDesktop);
3666
- const mergedMobile = mergePlatform(currentCustomization.mobile, resolvedMobile);
3667
- ResourceMerger.assertResourceCount("desktop.js", mergedDesktop.js);
3668
- ResourceMerger.assertResourceCount("desktop.css", mergedDesktop.css);
3669
- ResourceMerger.assertResourceCount("mobile.js", mergedMobile.js);
3670
- ResourceMerger.assertResourceCount("mobile.css", mergedMobile.css);
3671
- await container.customizationConfigurator.updateCustomization({
3672
- scope: config.scope,
3673
- desktop: {
3674
- js: mergedDesktop.js,
3675
- css: mergedDesktop.css
3676
- },
3677
- mobile: {
3678
- js: mergedMobile.js,
3679
- css: mergedMobile.css
3680
- },
3681
- revision: currentCustomization.revision
3846
+ const CustomizationDiffDetector = { detect: (local, remote) => {
3847
+ const entries = [];
3848
+ const warnings = [];
3849
+ const localScope = local.scope ?? "ALL";
3850
+ if (localScope !== remote.scope) entries.push({
3851
+ type: "modified",
3852
+ platform: "config",
3853
+ category: "scope",
3854
+ name: "scope",
3855
+ details: `${remote.scope} -> ${localScope}`
3856
+ });
3857
+ entries.push(...comparePlatform(local.desktop.js, local.desktop.css, remote.desktop, "desktop", warnings));
3858
+ entries.push(...comparePlatform(local.mobile.js, local.mobile.css, remote.mobile, "mobile", warnings));
3859
+ return buildDiffResult(entries, warnings);
3860
+ } };
3861
+ //#endregion
3862
+ //#region src/core/application/customization/detectCustomizationDiff.ts
3863
+ async function detectCustomizationDiff({ container }) {
3864
+ return detectDiffFromConfig({
3865
+ getStorage: () => container.customizationStorage.get(),
3866
+ fetchRemote: () => container.customizationConfigurator.getCustomization(),
3867
+ parseConfig: (content) => parseCustomizationConfigText(container.configCodec, content),
3868
+ detect: (local, remote) => CustomizationDiffDetector.detect(local, remote),
3869
+ notFoundMessage: "Customization config file not found"
3682
3870
  });
3683
3871
  }
3684
3872
  //#endregion
@@ -3945,132 +4133,46 @@ var capture_default$10 = define({
3945
4133
  });
3946
4134
  //#endregion
3947
4135
  //#region src/cli/commands/customize/apply.ts
3948
- async function applyCustomizationForApp(config) {
4136
+ function createCustomizationContainer(config) {
3949
4137
  const container = createCustomizationCliContainer(config);
3950
4138
  const filePrefix = deriveFilePrefix(config.customizeFilePath);
3951
- const basePath = join(dirname(resolve(config.customizeFilePath)), filePrefix);
3952
- const s = p.spinner();
3953
- s.start("Applying customization...");
3954
- await applyCustomization({
4139
+ return {
3955
4140
  container,
3956
- input: { basePath }
3957
- });
3958
- s.stop("Customization applied.");
3959
- return container;
3960
- }
3961
- var apply_default$9 = define({
3962
- name: "apply",
3963
- description: "Apply JS/CSS customization to kintone app",
3964
- args: {
3965
- ...customizeArgs,
3966
- ...confirmArgs
3967
- },
3968
- run: async (ctx) => {
3969
- try {
3970
- const values = ctx.values;
3971
- const skipConfirm = values.yes === true;
3972
- await routeMultiApp(values, {
3973
- singleLegacy: async () => {
3974
- await confirmAndDeploy([await applyCustomizationForApp(resolveCustomizeConfig(values))], skipConfirm, "Customization applied and deployed successfully.");
3975
- },
3976
- singleApp: async (app, projectConfig) => {
3977
- await confirmAndDeploy([await applyCustomizationForApp(resolveCustomizeAppConfig(app, projectConfig, values))], skipConfirm, "Customization applied and deployed successfully.");
3978
- },
3979
- multiApp: async (plan, projectConfig) => {
3980
- const containers = [];
3981
- await runMultiAppWithHeaders(plan, async (app) => {
3982
- const container = await applyCustomizationForApp(resolveCustomizeAppConfig(app, projectConfig, values));
3983
- containers.push({
3984
- appDeployer: container.appDeployer,
3985
- appName: app.name
3986
- });
3987
- });
3988
- await confirmAndDeploy(containers, skipConfirm, "Customization applied and deployed successfully.");
3989
- }
3990
- });
3991
- } catch (error) {
3992
- handleCliError(error);
3993
- }
3994
- }
3995
- });
3996
- //#endregion
3997
- //#region src/core/domain/customization/services/diffDetector.ts
3998
- function resourceName(resource) {
3999
- if (resource.type === "URL") return resource.url;
4000
- const parts = resource.path.replace(/\\/g, "/").split("/").filter(Boolean);
4001
- return parts[parts.length - 1];
4002
- }
4003
- function remoteResourceName(resource) {
4004
- if (resource.type === "URL") return resource.url;
4005
- return resource.file.name;
4141
+ basePath: join(dirname(resolve(config.customizeFilePath)), filePrefix)
4142
+ };
4006
4143
  }
4007
- function compareResourceLists(localResources, remoteResources, platform, resourceType, warnings) {
4008
- const entries = [];
4009
- const localNames = localResources.map(resourceName);
4010
- const remoteNames = remoteResources.map(remoteResourceName);
4011
- const localNameSet = new Set(localNames);
4012
- const remoteNameSet = new Set(remoteNames);
4013
- const hasDuplicates = localNames.length !== localNameSet.size || remoteNames.length !== remoteNameSet.size;
4014
- if (hasDuplicates) warnings.push(`[${platform}.${resourceType}] duplicate basenames detected; diff results may be inaccurate for FILE resources`);
4015
- for (const name of localNameSet) if (!remoteNameSet.has(name)) entries.push({
4016
- type: "added",
4017
- platform,
4018
- category: resourceType,
4019
- name,
4020
- details: "new resource"
4021
- });
4022
- for (const name of remoteNameSet) if (!localNameSet.has(name)) entries.push({
4023
- type: "deleted",
4024
- platform,
4025
- category: resourceType,
4026
- name,
4027
- details: "removed"
4028
- });
4029
- const matchedFiles = [...localNameSet].filter((n) => remoteNameSet.has(n));
4030
- const hasLocalFiles = localResources.some((r) => r.type === "FILE");
4031
- const hasRemoteFiles = remoteResources.some((r) => r.type === "FILE");
4032
- if (matchedFiles.length > 0 && hasLocalFiles && hasRemoteFiles) warnings.push(`[${platform}.${resourceType}] FILE resources are compared by name only; content changes are not detected`);
4033
- if (!hasDuplicates) {
4034
- const localShared = localNames.filter((n) => remoteNameSet.has(n));
4035
- const remoteShared = remoteNames.filter((n) => localNameSet.has(n));
4036
- if (localShared.length > 1 && localShared.length === remoteShared.length && localShared.some((n, i) => n !== remoteShared[i])) entries.push({
4037
- type: "modified",
4038
- platform,
4039
- category: resourceType,
4040
- name: "(order)",
4041
- details: "resource load order changed"
4144
+ async function runCustomizationApply(container, basePath) {
4145
+ const s = p.spinner();
4146
+ s.start("Applying customization...");
4147
+ try {
4148
+ await applyCustomization({
4149
+ container,
4150
+ input: { basePath }
4042
4151
  });
4152
+ } catch (error) {
4153
+ s.stop("Apply failed.");
4154
+ throw error;
4043
4155
  }
4044
- return entries;
4045
- }
4046
- function comparePlatform(localJs, localCss, remote, platform, warnings) {
4047
- return [...compareResourceLists(localJs, remote.js, platform, "js", warnings), ...compareResourceLists(localCss, remote.css, platform, "css", warnings)];
4156
+ s.stop("Customization applied.");
4048
4157
  }
4049
- const CustomizationDiffDetector = { detect: (local, remote) => {
4050
- const entries = [];
4051
- const warnings = [];
4052
- const localScope = local.scope ?? "ALL";
4053
- if (localScope !== remote.scope) entries.push({
4054
- type: "modified",
4055
- platform: "config",
4056
- category: "scope",
4057
- name: "scope",
4058
- details: `${remote.scope} -> ${localScope}`
4059
- });
4060
- entries.push(...comparePlatform(local.desktop.js, local.desktop.css, remote.desktop, "desktop", warnings));
4061
- entries.push(...comparePlatform(local.mobile.js, local.mobile.css, remote.mobile, "mobile", warnings));
4062
- return buildDiffResult(entries, warnings);
4063
- } };
4064
- //#endregion
4065
- //#region src/core/application/customization/detectCustomizationDiff.ts
4066
- async function detectCustomizationDiff({ container }) {
4067
- return detectDiffFromConfig({
4068
- getStorage: () => container.customizationStorage.get(),
4069
- fetchRemote: () => container.customizationConfigurator.getCustomization(),
4070
- parseConfig: (content) => parseCustomizationConfigText(container.configCodec, content),
4071
- detect: (local, remote) => CustomizationDiffDetector.detect(local, remote),
4072
- notFoundMessage: "Customization config file not found"
4073
- });
4158
+ async function runDiffPreview(config) {
4159
+ const { container, basePath } = createCustomizationContainer(config);
4160
+ const s = p.spinner();
4161
+ s.start("Detecting changes...");
4162
+ let result;
4163
+ try {
4164
+ result = await detectCustomizationDiff({ container });
4165
+ } catch (error) {
4166
+ s.stop("Comparison failed.");
4167
+ throw error;
4168
+ }
4169
+ s.stop("Comparison complete.");
4170
+ printCustomizationDiffResult(result);
4171
+ return {
4172
+ container,
4173
+ basePath,
4174
+ hasChanges: !result.isEmpty
4175
+ };
4074
4176
  }
4075
4177
  //#endregion
4076
4178
  //#region src/cli/commands/customize/index.ts
@@ -4078,7 +4180,96 @@ var customize_default = define({
4078
4180
  name: "customize",
4079
4181
  description: "Manage kintone JS/CSS customizations",
4080
4182
  subCommands: {
4081
- apply: apply_default$9,
4183
+ apply: define({
4184
+ name: "apply",
4185
+ description: "Apply JS/CSS customization to kintone app",
4186
+ args: {
4187
+ ...customizeArgs,
4188
+ ...confirmArgs
4189
+ },
4190
+ run: async (ctx) => {
4191
+ try {
4192
+ const values = ctx.values;
4193
+ const skipConfirm = values.yes === true;
4194
+ await routeMultiApp(values, {
4195
+ singleLegacy: async () => {
4196
+ const { container, basePath, hasChanges } = await runDiffPreview(resolveCustomizeConfig(values));
4197
+ if (!hasChanges) {
4198
+ p.log.success("No changes detected.");
4199
+ return;
4200
+ }
4201
+ if (!skipConfirm) {
4202
+ const shouldContinue = await p.confirm({ message: "Apply these changes?" });
4203
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
4204
+ p.cancel("Apply cancelled.");
4205
+ return;
4206
+ }
4207
+ }
4208
+ await runCustomizationApply(container, basePath);
4209
+ await confirmAndDeploy([container], skipConfirm, "Customization applied and deployed successfully.");
4210
+ },
4211
+ singleApp: async (app, projectConfig) => {
4212
+ const { container, basePath, hasChanges } = await runDiffPreview(resolveCustomizeAppConfig(app, projectConfig, values));
4213
+ if (!hasChanges) {
4214
+ p.log.success("No changes detected.");
4215
+ return;
4216
+ }
4217
+ if (!skipConfirm) {
4218
+ const shouldContinue = await p.confirm({ message: "Apply these changes?" });
4219
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
4220
+ p.cancel("Apply cancelled.");
4221
+ return;
4222
+ }
4223
+ }
4224
+ await runCustomizationApply(container, basePath);
4225
+ await confirmAndDeploy([container], skipConfirm, "Customization applied and deployed successfully.");
4226
+ },
4227
+ multiApp: async (plan, projectConfig) => {
4228
+ const appDiffResults = [];
4229
+ for (const app of plan.orderedApps) {
4230
+ const config = resolveCustomizeAppConfig(app, projectConfig, values);
4231
+ printAppHeader(app.name, app.appId);
4232
+ const { container, basePath, hasChanges } = await runDiffPreview(config);
4233
+ appDiffResults.push({
4234
+ app,
4235
+ container,
4236
+ basePath,
4237
+ hasChanges
4238
+ });
4239
+ }
4240
+ if (!appDiffResults.some((a) => a.hasChanges)) {
4241
+ p.log.success("No changes detected in any app.");
4242
+ return;
4243
+ }
4244
+ if (!skipConfirm) {
4245
+ const shouldContinue = await p.confirm({ message: "Apply these changes to all apps?" });
4246
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
4247
+ p.cancel("Apply cancelled.");
4248
+ return;
4249
+ }
4250
+ }
4251
+ const containers = [];
4252
+ await runMultiAppWithHeaders(plan, async (app) => {
4253
+ const entry = appDiffResults.find((a) => a.app.name === app.name);
4254
+ if (!entry) throw new SystemError(SystemErrorCode.InternalServerError, `App container not found for "${app.name}"`);
4255
+ if (!entry.hasChanges) {
4256
+ p.log.info("No changes. Skipping.");
4257
+ return;
4258
+ }
4259
+ await runCustomizationApply(entry.container, entry.basePath);
4260
+ containers.push({
4261
+ appDeployer: entry.container.appDeployer,
4262
+ appName: app.name
4263
+ });
4264
+ });
4265
+ await confirmAndDeploy(containers, skipConfirm, "Customization applied and deployed successfully.");
4266
+ }
4267
+ });
4268
+ } catch (error) {
4269
+ handleCliError(error);
4270
+ }
4271
+ }
4272
+ }),
4082
4273
  capture: capture_default$10,
4083
4274
  diff: createDiffCommand({
4084
4275
  description: "Compare local customization config with remote kintone app",
@@ -4258,14 +4449,71 @@ function parseFieldPermissionConfigText(codec, rawText) {
4258
4449
  async function applyFieldPermission({ container }) {
4259
4450
  await applyFromConfig({
4260
4451
  getStorage: () => container.fieldPermissionStorage.get(),
4261
- parseConfig: (content) => parseFieldPermissionConfigText(container.configCodec, content),
4452
+ parseConfig: (content) => parseFieldPermissionConfigText(container.configCodec, content),
4453
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4454
+ update: async (config, current) => {
4455
+ await container.fieldPermissionConfigurator.updateFieldPermissions({
4456
+ rights: config.rights,
4457
+ revision: current.revision
4458
+ });
4459
+ },
4460
+ notFoundMessage: "Field permission config file not found"
4461
+ });
4462
+ }
4463
+ //#endregion
4464
+ //#region src/core/domain/fieldPermission/services/diffDetector.ts
4465
+ function isEntitiesEqual$1(a, b) {
4466
+ return deepEqual(a.entities.map((e) => ({
4467
+ accessibility: e.accessibility,
4468
+ type: e.entity.type,
4469
+ code: e.entity.code,
4470
+ includeSubs: e.includeSubs ?? false
4471
+ })), b.entities.map((e) => ({
4472
+ accessibility: e.accessibility,
4473
+ type: e.entity.type,
4474
+ code: e.entity.code,
4475
+ includeSubs: e.includeSubs ?? false
4476
+ })));
4477
+ }
4478
+ function describeEntities(entities) {
4479
+ if (entities.length === 0) return "no entities";
4480
+ return entities.map((e) => {
4481
+ const access = e.accessibility.toLowerCase();
4482
+ return `${e.entity.type}:${e.entity.code}(${access})`;
4483
+ }).join(", ");
4484
+ }
4485
+ const FieldPermissionDiffDetector = { detect: (local, remote) => {
4486
+ const entries = [];
4487
+ const localMap = new Map(local.rights.map((r) => [r.code, r]));
4488
+ const remoteMap = new Map(remote.rights.map((r) => [r.code, r]));
4489
+ for (const [code, localRight] of localMap) {
4490
+ const remoteRight = remoteMap.get(code);
4491
+ if (!remoteRight) entries.push({
4492
+ type: "added",
4493
+ fieldCode: code,
4494
+ details: `entities: ${describeEntities(localRight.entities)}`
4495
+ });
4496
+ else if (!isEntitiesEqual$1(localRight, remoteRight)) entries.push({
4497
+ type: "modified",
4498
+ fieldCode: code,
4499
+ details: `entities: ${describeEntities(localRight.entities)}`
4500
+ });
4501
+ }
4502
+ for (const [code, remoteRight] of remoteMap) if (!localMap.has(code)) entries.push({
4503
+ type: "deleted",
4504
+ fieldCode: code,
4505
+ details: `entities: ${describeEntities(remoteRight.entities)}`
4506
+ });
4507
+ return buildDiffResult(entries);
4508
+ } };
4509
+ //#endregion
4510
+ //#region src/core/application/fieldPermission/detectFieldPermissionDiff.ts
4511
+ async function detectFieldPermissionDiff({ container }) {
4512
+ return detectDiffFromConfig({
4513
+ getStorage: () => container.fieldPermissionStorage.get(),
4262
4514
  fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4263
- update: async (config, current) => {
4264
- await container.fieldPermissionConfigurator.updateFieldPermissions({
4265
- rights: config.rights,
4266
- revision: current.revision
4267
- });
4268
- },
4515
+ parseConfig: (content) => parseFieldPermissionConfigText(container.configCodec, content),
4516
+ detect: (local, remote) => FieldPermissionDiffDetector.detect(local, { rights: remote.rights }),
4269
4517
  notFoundMessage: "Field permission config file not found"
4270
4518
  });
4271
4519
  }
@@ -4300,6 +4548,10 @@ var apply_default$8 = createApplyCommand({
4300
4548
  successMessage: "Field access permissions applied successfully.",
4301
4549
  createContainer: createFieldPermissionCliContainer,
4302
4550
  applyFn: applyFieldPermission,
4551
+ diffPreview: {
4552
+ detectDiff: detectFieldPermissionDiff,
4553
+ printResult: printFieldPermissionDiffResult
4554
+ },
4303
4555
  resolveContainerConfig: resolveFieldAclContainerConfig,
4304
4556
  resolveAppContainerConfig: resolveFieldAclAppContainerConfig
4305
4557
  });
@@ -4337,79 +4589,26 @@ async function saveFieldPermission({ container, input }) {
4337
4589
  await container.fieldPermissionStorage.update(input.configText);
4338
4590
  }
4339
4591
  //#endregion
4340
- //#region src/cli/commands/field-acl/capture.ts
4341
- var capture_default$9 = createCaptureCommand({
4342
- description: "Capture current field access permissions from kintone app to file",
4343
- args: fieldAclArgs,
4344
- spinnerMessage: "Capturing field access permissions...",
4345
- spinnerStopMessage: "Field access permissions captured.",
4346
- domainLabel: "Field ACL",
4347
- multiAppSuccessMessage: "All field ACL captures completed successfully.",
4348
- createContainer: createFieldPermissionCliContainer,
4349
- captureFn: captureFieldPermission,
4350
- saveFn: saveFieldPermission,
4351
- getConfigFilePath: (config) => config.fieldAclFilePath,
4352
- resolveContainerConfig: resolveFieldAclContainerConfig,
4353
- resolveAppContainerConfig: resolveFieldAclAppContainerConfig
4354
- });
4355
- //#endregion
4356
- //#region src/core/domain/fieldPermission/services/diffDetector.ts
4357
- function isEntitiesEqual$1(a, b) {
4358
- return deepEqual(a.entities.map((e) => ({
4359
- accessibility: e.accessibility,
4360
- type: e.entity.type,
4361
- code: e.entity.code,
4362
- includeSubs: e.includeSubs ?? false
4363
- })), b.entities.map((e) => ({
4364
- accessibility: e.accessibility,
4365
- type: e.entity.type,
4366
- code: e.entity.code,
4367
- includeSubs: e.includeSubs ?? false
4368
- })));
4369
- }
4370
- const FieldPermissionDiffDetector = { detect: (local, remote) => {
4371
- const entries = [];
4372
- const localMap = new Map(local.rights.map((r) => [r.code, r]));
4373
- const remoteMap = new Map(remote.rights.map((r) => [r.code, r]));
4374
- for (const [code, localRight] of localMap) {
4375
- const remoteRight = remoteMap.get(code);
4376
- if (!remoteRight) entries.push({
4377
- type: "added",
4378
- fieldCode: code,
4379
- details: `${localRight.entities.length} entities`
4380
- });
4381
- else if (!isEntitiesEqual$1(localRight, remoteRight)) entries.push({
4382
- type: "modified",
4383
- fieldCode: code,
4384
- details: "entities changed"
4385
- });
4386
- }
4387
- for (const code of remoteMap.keys()) if (!localMap.has(code)) entries.push({
4388
- type: "deleted",
4389
- fieldCode: code,
4390
- details: "removed"
4391
- });
4392
- return buildDiffResult(entries);
4393
- } };
4394
- //#endregion
4395
- //#region src/core/application/fieldPermission/detectFieldPermissionDiff.ts
4396
- async function detectFieldPermissionDiff({ container }) {
4397
- return detectDiffFromConfig({
4398
- getStorage: () => container.fieldPermissionStorage.get(),
4399
- fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4400
- parseConfig: (content) => parseFieldPermissionConfigText(container.configCodec, content),
4401
- detect: (local, remote) => FieldPermissionDiffDetector.detect(local, { rights: remote.rights }),
4402
- notFoundMessage: "Field permission config file not found"
4403
- });
4404
- }
4405
- //#endregion
4406
4592
  //#region src/cli/commands/field-acl/index.ts
4407
4593
  var field_acl_default = define({
4408
4594
  name: "field-acl",
4409
4595
  description: "Manage kintone field access permissions",
4410
4596
  subCommands: {
4411
4597
  apply: apply_default$8,
4412
- capture: capture_default$9,
4598
+ capture: createCaptureCommand({
4599
+ description: "Capture current field access permissions from kintone app to file",
4600
+ args: fieldAclArgs,
4601
+ spinnerMessage: "Capturing field access permissions...",
4602
+ spinnerStopMessage: "Field access permissions captured.",
4603
+ domainLabel: "Field ACL",
4604
+ multiAppSuccessMessage: "All field ACL captures completed successfully.",
4605
+ createContainer: createFieldPermissionCliContainer,
4606
+ captureFn: captureFieldPermission,
4607
+ saveFn: saveFieldPermission,
4608
+ getConfigFilePath: (config) => config.fieldAclFilePath,
4609
+ resolveContainerConfig: resolveFieldAclContainerConfig,
4610
+ resolveAppContainerConfig: resolveFieldAclAppContainerConfig
4611
+ }),
4413
4612
  diff: createDiffCommand({
4414
4613
  description: "Compare local field permission config with remote kintone app",
4415
4614
  args: fieldAclArgs,
@@ -5548,6 +5747,42 @@ function createCliCaptureContainers(input) {
5548
5747
  };
5549
5748
  }
5550
5749
  //#endregion
5750
+ //#region src/core/adapters/kintone/appLister.ts
5751
+ const PAGE_LIMIT = 100;
5752
+ const MAX_PAGES = 1e3;
5753
+ var KintoneAppLister = class {
5754
+ constructor(client) {
5755
+ this.client = client;
5756
+ }
5757
+ async getAllApps() {
5758
+ const result = [];
5759
+ try {
5760
+ let offset = 0;
5761
+ let completed = false;
5762
+ for (let page = 0; page < MAX_PAGES; page++) {
5763
+ const { apps } = await this.client.app.getApps({
5764
+ limit: PAGE_LIMIT,
5765
+ offset
5766
+ });
5767
+ for (const app of apps) result.push({
5768
+ appId: app.appId,
5769
+ code: app.code,
5770
+ name: app.name
5771
+ });
5772
+ if (apps.length < PAGE_LIMIT) {
5773
+ completed = true;
5774
+ break;
5775
+ }
5776
+ offset += PAGE_LIMIT;
5777
+ }
5778
+ if (!completed) throw new SystemError(SystemErrorCode.ExternalApiError, `Pagination did not complete within ${MAX_PAGES} pages (fetched ${result.length} apps). This may indicate an API issue.`);
5779
+ return result;
5780
+ } catch (error) {
5781
+ throw wrapKintoneError(error, `Failed to get apps (fetched ${result.length} so far)`);
5782
+ }
5783
+ }
5784
+ };
5785
+ //#endregion
5551
5786
  //#region src/core/adapters/kintone/spaceReader.ts
5552
5787
  /**
5553
5788
  * Reads space information from the kintone REST API.
@@ -5597,8 +5832,10 @@ function createLocalFileProjectConfigStorage(filePath) {
5597
5832
  //#endregion
5598
5833
  //#region src/core/application/container/initCli.ts
5599
5834
  function createInitCliContainer(config) {
5835
+ const client = config.client ?? createKintoneClient(config);
5600
5836
  return {
5601
- spaceReader: new KintoneSpaceReader(config.client ?? createKintoneClient(config)),
5837
+ spaceReader: new KintoneSpaceReader(client),
5838
+ appLister: new KintoneAppLister(client),
5602
5839
  projectConfigStorage: createLocalFileProjectConfigStorage(config.configFilePath)
5603
5840
  };
5604
5841
  }
@@ -6300,6 +6537,13 @@ async function captureAllForApp(args) {
6300
6537
  return results;
6301
6538
  }
6302
6539
  //#endregion
6540
+ //#region src/core/application/init/fetchAllApps.ts
6541
+ async function fetchAllApps(args) {
6542
+ const apps = await args.container.appLister.getAllApps();
6543
+ if (apps.length === 0) throw new NotFoundError(NotFoundErrorCode.NotFound, "No apps found. Please check your API token permissions.");
6544
+ return apps;
6545
+ }
6546
+ //#endregion
6303
6547
  //#region src/core/application/init/fetchSpaceApps.ts
6304
6548
  async function fetchSpaceApps(args) {
6305
6549
  const apps = await args.container.spaceReader.getSpaceApps(args.input.spaceId);
@@ -6307,7 +6551,7 @@ async function fetchSpaceApps(args) {
6307
6551
  return apps;
6308
6552
  }
6309
6553
  //#endregion
6310
- //#region src/core/domain/space/entity.ts
6554
+ //#region src/core/domain/app/entity.ts
6311
6555
  const UNSAFE_PATH_CHARS = /[<>:"/\\|?*\u0000-\u001f]/g;
6312
6556
  function sanitizeForFileSystem(name) {
6313
6557
  const sanitized = name.replace(UNSAFE_PATH_CHARS, "_").replace(/\.+$/, "");
@@ -6347,8 +6591,7 @@ const initArgs = {
6347
6591
  "space-id": {
6348
6592
  type: "string",
6349
6593
  short: "s",
6350
- description: "kintone space ID",
6351
- required: true
6594
+ description: "kintone space ID"
6352
6595
  },
6353
6596
  domain: kintoneArgs.domain,
6354
6597
  username: kintoneArgs.username,
@@ -6378,15 +6621,52 @@ function printCaptureResults(results) {
6378
6621
  logError(result.error);
6379
6622
  }
6380
6623
  }
6624
+ function printDryRunPreview(apps, configPath, configText, output) {
6625
+ p.log.info(pc.dim("(dry-run mode - no files will be written)"));
6626
+ p.log.step(`\nConfig file: ${pc.cyan(configPath)}`);
6627
+ p.log.message(configText);
6628
+ for (const app of apps) {
6629
+ const appName = resolveAppName(app);
6630
+ const paths = buildAppFilePaths(appName, output);
6631
+ p.log.step(`\n=== [${pc.bold(appName)}] (appId: ${app.appId}) ===`);
6632
+ p.log.message(" Files that would be created:");
6633
+ for (const [domain, filePath] of Object.entries(paths)) p.log.message(` ${domain}: ${pc.dim(filePath)}`);
6634
+ }
6635
+ p.log.success("\nDry run complete. No files were written.");
6636
+ }
6637
+ async function captureApps(apps, config) {
6638
+ for (const app of apps) {
6639
+ const appName = resolveAppName(app);
6640
+ p.log.step(`\n=== [${pc.bold(appName)}] (appId: ${app.appId}) ===`);
6641
+ const { containers, paths } = createCliCaptureContainers({
6642
+ baseUrl: config.baseUrl,
6643
+ auth: config.auth,
6644
+ appId: app.appId,
6645
+ guestSpaceId: config.guestSpaceId,
6646
+ appName,
6647
+ baseDir: config.output
6648
+ });
6649
+ const cs = p.spinner();
6650
+ cs.start(`Capturing all domains for ${appName}...`);
6651
+ const results = await captureAllForApp({
6652
+ container: containers,
6653
+ input: { customizeBasePath: dirname(resolve(paths.customize)) }
6654
+ });
6655
+ const successCount = results.filter((r) => r.success).length;
6656
+ const failCount = results.length - successCount;
6657
+ cs.stop(`Captured ${successCount}/${results.length} domains.` + (failCount > 0 ? pc.red(` (${failCount} failed)`) : ""));
6658
+ printCaptureResults(results);
6659
+ }
6660
+ }
6381
6661
  var init_default = define({
6382
6662
  name: "init",
6383
- description: "Initialize project from a kintone space",
6663
+ description: "Initialize project from kintone",
6384
6664
  args: initArgs,
6385
6665
  run: async (ctx) => {
6386
6666
  try {
6387
6667
  const values = ctx.values;
6388
6668
  const spaceId = values["space-id"];
6389
- if (!/^[1-9]\d*$/.test(spaceId)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Invalid space ID: "${spaceId}" (must be a positive integer, e.g. 1, 42, 100)`);
6669
+ if (spaceId && !/^[1-9]\d*$/.test(spaceId)) throw new ValidationError(ValidationErrorCode.InvalidInput, `Invalid space ID: "${spaceId}" (must be a positive integer, e.g. 1, 42, 100)`);
6390
6670
  const kintoneDomain = values.domain ?? process.env.KINTONE_DOMAIN;
6391
6671
  if (!kintoneDomain) throw new ValidationError(ValidationErrorCode.InvalidInput, "Missing required configuration:\n KINTONE_DOMAIN is required");
6392
6672
  const apiToken = values["api-token"] ?? process.env.KINTONE_API_TOKEN;
@@ -6399,19 +6679,26 @@ var init_default = define({
6399
6679
  const configPath = DEFAULT_CONFIG_PATH;
6400
6680
  const skipConfirm = values.yes ?? false;
6401
6681
  const dryRun = values["dry-run"] ?? false;
6402
- const { spaceReader, projectConfigStorage } = createInitCliContainer({
6682
+ const { spaceReader, appLister, projectConfigStorage } = createInitCliContainer({
6403
6683
  baseUrl,
6404
6684
  auth,
6405
6685
  guestSpaceId,
6406
6686
  configFilePath: configPath
6407
6687
  });
6408
6688
  const s = p.spinner();
6409
- s.start("Fetching space info...");
6410
- const apps = await fetchSpaceApps({
6411
- container: { spaceReader },
6412
- input: { spaceId }
6413
- });
6414
- s.stop(`Found ${apps.length} app(s) in the space.`);
6689
+ let apps;
6690
+ if (spaceId) {
6691
+ s.start("Fetching space info...");
6692
+ apps = await fetchSpaceApps({
6693
+ container: { spaceReader },
6694
+ input: { spaceId }
6695
+ });
6696
+ s.stop(`Found ${apps.length} app(s) in the space.`);
6697
+ } else {
6698
+ s.start("Fetching all apps...");
6699
+ apps = await fetchAllApps({ container: { appLister } });
6700
+ s.stop(`Found ${apps.length} app(s).`);
6701
+ }
6415
6702
  p.log.info("Apps:");
6416
6703
  for (const app of apps) {
6417
6704
  const name = resolveAppName(app);
@@ -6424,17 +6711,7 @@ var init_default = define({
6424
6711
  baseDir: output
6425
6712
  }, configCodec);
6426
6713
  if (dryRun) {
6427
- p.log.info(pc.dim("(dry-run mode - no files will be written)"));
6428
- p.log.step(`\nConfig file: ${pc.cyan(configPath)}`);
6429
- p.log.message(configText);
6430
- for (const app of apps) {
6431
- const appName = resolveAppName(app);
6432
- const paths = buildAppFilePaths(appName, output);
6433
- p.log.step(`\n=== [${pc.bold(appName)}] (appId: ${app.appId}) ===`);
6434
- p.log.message(" Files that would be created:");
6435
- for (const [domain, filePath] of Object.entries(paths)) p.log.message(` ${domain}: ${pc.dim(filePath)}`);
6436
- }
6437
- p.log.success("\nDry run complete. No files were written.");
6714
+ printDryRunPreview(apps, configPath, configText, output);
6438
6715
  return;
6439
6716
  }
6440
6717
  if ((await projectConfigStorage.get()).exists && !skipConfirm) {
@@ -6446,28 +6723,12 @@ var init_default = define({
6446
6723
  }
6447
6724
  await projectConfigStorage.update(configText);
6448
6725
  p.log.success(`Config written to: ${pc.cyan(configPath)}`);
6449
- for (const app of apps) {
6450
- const appName = resolveAppName(app);
6451
- p.log.step(`\n=== [${pc.bold(appName)}] (appId: ${app.appId}) ===`);
6452
- const { containers, paths } = createCliCaptureContainers({
6453
- baseUrl,
6454
- auth,
6455
- appId: app.appId,
6456
- guestSpaceId,
6457
- appName,
6458
- baseDir: output
6459
- });
6460
- const cs = p.spinner();
6461
- cs.start(`Capturing all domains for ${appName}...`);
6462
- const results = await captureAllForApp({
6463
- container: containers,
6464
- input: { customizeBasePath: dirname(resolve(paths.customize)) }
6465
- });
6466
- const successCount = results.filter((r) => r.success).length;
6467
- const failCount = results.length - successCount;
6468
- cs.stop(`Captured ${successCount}/${results.length} domains.` + (failCount > 0 ? pc.red(` (${failCount} failed)`) : ""));
6469
- printCaptureResults(results);
6470
- }
6726
+ await captureApps(apps, {
6727
+ baseUrl,
6728
+ auth,
6729
+ guestSpaceId,
6730
+ output
6731
+ });
6471
6732
  p.log.info(`Add authentication settings (auth) to ${pc.cyan(configPath)} or set KINTONE_API_TOKEN / KINTONE_USERNAME + KINTONE_PASSWORD environment variables.`);
6472
6733
  p.log.success("\nProject initialization complete.");
6473
6734
  } catch (error) {
@@ -6627,56 +6888,6 @@ async function applyNotification({ container }) {
6627
6888
  }
6628
6889
  }
6629
6890
  //#endregion
6630
- //#region src/cli/notificationConfig.ts
6631
- const notificationArgs = {
6632
- ...kintoneArgs,
6633
- ...multiAppArgs,
6634
- "notification-file": {
6635
- type: "string",
6636
- description: "Notification file path (default: notification.yaml)"
6637
- }
6638
- };
6639
- const { resolveFilePath: resolveNotificationFilePath, resolveContainerConfig: resolveNotificationContainerConfig, resolveAppContainerConfig: resolveNotificationAppContainerConfig } = createDomainConfigResolver({
6640
- fileArgKey: "notification-file",
6641
- envVar: () => process.env.NOTIFICATION_FILE_PATH,
6642
- appFileField: (a) => a.notificationFile,
6643
- defaultDir: "notification",
6644
- defaultFileName: "notification.yaml",
6645
- buildConfig: (base, filePath) => ({
6646
- ...base,
6647
- notificationFilePath: filePath
6648
- })
6649
- });
6650
- //#endregion
6651
- //#region src/cli/commands/notification/apply.ts
6652
- var apply_default$7 = createApplyCommand({
6653
- description: "Apply notification settings from YAML to kintone app",
6654
- args: notificationArgs,
6655
- spinnerMessage: "Applying notification settings...",
6656
- spinnerStopMessage: "Notification settings applied.",
6657
- successMessage: "Notification settings applied successfully.",
6658
- createContainer: createNotificationCliContainer,
6659
- applyFn: applyNotification,
6660
- resolveContainerConfig: resolveNotificationContainerConfig,
6661
- resolveAppContainerConfig: resolveNotificationAppContainerConfig
6662
- });
6663
- //#endregion
6664
- //#region src/cli/commands/notification/capture.ts
6665
- var capture_default$8 = createCaptureCommand({
6666
- description: "Capture current notification settings from kintone app to file",
6667
- args: notificationArgs,
6668
- spinnerMessage: "Capturing notification settings...",
6669
- spinnerStopMessage: "Notification settings captured.",
6670
- domainLabel: "Notification settings",
6671
- multiAppSuccessMessage: "All notification captures completed successfully.",
6672
- createContainer: createNotificationCliContainer,
6673
- captureFn: captureNotification,
6674
- saveFn: saveNotification,
6675
- getConfigFilePath: (config) => config.notificationFilePath,
6676
- resolveContainerConfig: resolveNotificationContainerConfig,
6677
- resolveAppContainerConfig: resolveNotificationAppContainerConfig
6678
- });
6679
- //#endregion
6680
6891
  //#region src/lib/groupByKey.ts
6681
6892
  /**
6682
6893
  * Groups items by a key function into a Map where each key maps to an array of items.
@@ -6906,13 +7117,61 @@ async function detectNotificationDiff({ container }) {
6906
7117
  return NotificationDiffDetector.detect(localConfig, remoteConfig);
6907
7118
  }
6908
7119
  //#endregion
7120
+ //#region src/cli/notificationConfig.ts
7121
+ const notificationArgs = {
7122
+ ...kintoneArgs,
7123
+ ...multiAppArgs,
7124
+ "notification-file": {
7125
+ type: "string",
7126
+ description: "Notification file path (default: notification.yaml)"
7127
+ }
7128
+ };
7129
+ const { resolveFilePath: resolveNotificationFilePath, resolveContainerConfig: resolveNotificationContainerConfig, resolveAppContainerConfig: resolveNotificationAppContainerConfig } = createDomainConfigResolver({
7130
+ fileArgKey: "notification-file",
7131
+ envVar: () => process.env.NOTIFICATION_FILE_PATH,
7132
+ appFileField: (a) => a.notificationFile,
7133
+ defaultDir: "notification",
7134
+ defaultFileName: "notification.yaml",
7135
+ buildConfig: (base, filePath) => ({
7136
+ ...base,
7137
+ notificationFilePath: filePath
7138
+ })
7139
+ });
7140
+ //#endregion
6909
7141
  //#region src/cli/commands/notification/index.ts
6910
7142
  var notification_default = define({
6911
7143
  name: "notification",
6912
7144
  description: "Manage kintone notification settings",
6913
7145
  subCommands: {
6914
- apply: apply_default$7,
6915
- capture: capture_default$8,
7146
+ apply: createApplyCommand({
7147
+ description: "Apply notification settings from YAML to kintone app",
7148
+ args: notificationArgs,
7149
+ spinnerMessage: "Applying notification settings...",
7150
+ spinnerStopMessage: "Notification settings applied.",
7151
+ successMessage: "Notification settings applied successfully.",
7152
+ createContainer: createNotificationCliContainer,
7153
+ applyFn: applyNotification,
7154
+ diffPreview: {
7155
+ detectDiff: detectNotificationDiff,
7156
+ printResult: printNotificationDiffResult
7157
+ },
7158
+ resolveContainerConfig: resolveNotificationContainerConfig,
7159
+ resolveAppContainerConfig: resolveNotificationAppContainerConfig
7160
+ }),
7161
+ capture: createCaptureCommand({
7162
+ description: "Capture current notification settings from kintone app to file",
7163
+ args: notificationArgs,
7164
+ spinnerMessage: "Capturing notification settings...",
7165
+ spinnerStopMessage: "Notification settings captured.",
7166
+ domainLabel: "Notification settings",
7167
+ multiAppSuccessMessage: "All notification captures completed successfully.",
7168
+ createContainer: createNotificationCliContainer,
7169
+ captureFn: captureNotification,
7170
+ saveFn: saveNotification,
7171
+ getConfigFilePath: (config) => config.notificationFilePath,
7172
+ resolveContainerConfig: resolveNotificationContainerConfig,
7173
+ resolveAppContainerConfig: resolveNotificationAppContainerConfig
7174
+ }),
6916
7175
  diff: createDiffCommand({
6917
7176
  description: "Compare local notification config with remote kintone app",
6918
7177
  args: notificationArgs,
@@ -6971,56 +7230,6 @@ async function applyPlugin({ container }) {
6971
7230
  });
6972
7231
  }
6973
7232
  //#endregion
6974
- //#region src/cli/pluginConfig.ts
6975
- const pluginArgs = {
6976
- ...kintoneArgs,
6977
- ...multiAppArgs,
6978
- "plugin-file": {
6979
- type: "string",
6980
- description: "Plugin file path (default: plugins.yaml)"
6981
- }
6982
- };
6983
- const { resolveFilePath: resolvePluginFilePath, resolveContainerConfig: resolvePluginContainerConfig, resolveAppContainerConfig: resolvePluginAppContainerConfig } = createDomainConfigResolver({
6984
- fileArgKey: "plugin-file",
6985
- envVar: () => process.env.PLUGIN_FILE_PATH,
6986
- appFileField: (a) => a.pluginFile,
6987
- defaultDir: "plugin",
6988
- defaultFileName: "plugins.yaml",
6989
- buildConfig: (base, filePath) => ({
6990
- ...base,
6991
- pluginFilePath: filePath
6992
- })
6993
- });
6994
- //#endregion
6995
- //#region src/cli/commands/plugin/apply.ts
6996
- var apply_default$6 = createApplyCommand({
6997
- description: "Apply plugins from YAML to kintone app",
6998
- args: pluginArgs,
6999
- spinnerMessage: "Applying plugins...",
7000
- spinnerStopMessage: "Plugins applied.",
7001
- successMessage: "Plugins applied successfully.",
7002
- createContainer: createPluginCliContainer,
7003
- applyFn: applyPlugin,
7004
- resolveContainerConfig: resolvePluginContainerConfig,
7005
- resolveAppContainerConfig: resolvePluginAppContainerConfig
7006
- });
7007
- //#endregion
7008
- //#region src/cli/commands/plugin/capture.ts
7009
- var capture_default$7 = createCaptureCommand({
7010
- description: "Capture current plugins from kintone app to file",
7011
- args: pluginArgs,
7012
- spinnerMessage: "Capturing plugins...",
7013
- spinnerStopMessage: "Plugins captured.",
7014
- domainLabel: "Plugins",
7015
- multiAppSuccessMessage: "All plugin captures completed successfully.",
7016
- createContainer: createPluginCliContainer,
7017
- captureFn: capturePlugin,
7018
- saveFn: savePlugin,
7019
- getConfigFilePath: (config) => config.pluginFilePath,
7020
- resolveContainerConfig: resolvePluginContainerConfig,
7021
- resolveAppContainerConfig: resolvePluginAppContainerConfig
7022
- });
7023
- //#endregion
7024
7233
  //#region src/core/domain/plugin/services/diffDetector.ts
7025
7234
  const PluginDiffDetector = { detect: (local, remote) => {
7026
7235
  const entries = [];
@@ -7063,13 +7272,61 @@ async function detectPluginDiff({ container }) {
7063
7272
  });
7064
7273
  }
7065
7274
  //#endregion
7275
+ //#region src/cli/pluginConfig.ts
7276
+ const pluginArgs = {
7277
+ ...kintoneArgs,
7278
+ ...multiAppArgs,
7279
+ "plugin-file": {
7280
+ type: "string",
7281
+ description: "Plugin file path (default: plugins.yaml)"
7282
+ }
7283
+ };
7284
+ const { resolveFilePath: resolvePluginFilePath, resolveContainerConfig: resolvePluginContainerConfig, resolveAppContainerConfig: resolvePluginAppContainerConfig } = createDomainConfigResolver({
7285
+ fileArgKey: "plugin-file",
7286
+ envVar: () => process.env.PLUGIN_FILE_PATH,
7287
+ appFileField: (a) => a.pluginFile,
7288
+ defaultDir: "plugin",
7289
+ defaultFileName: "plugins.yaml",
7290
+ buildConfig: (base, filePath) => ({
7291
+ ...base,
7292
+ pluginFilePath: filePath
7293
+ })
7294
+ });
7295
+ //#endregion
7066
7296
  //#region src/cli/commands/plugin/index.ts
7067
7297
  var plugin_default = define({
7068
7298
  name: "plugin",
7069
7299
  description: "Manage kintone plugins",
7070
7300
  subCommands: {
7071
- apply: apply_default$6,
7072
- capture: capture_default$7,
7301
+ apply: createApplyCommand({
7302
+ description: "Apply plugins from YAML to kintone app",
7303
+ args: pluginArgs,
7304
+ spinnerMessage: "Applying plugins...",
7305
+ spinnerStopMessage: "Plugins applied.",
7306
+ successMessage: "Plugins applied successfully.",
7307
+ createContainer: createPluginCliContainer,
7308
+ applyFn: applyPlugin,
7309
+ diffPreview: {
7310
+ detectDiff: detectPluginDiff,
7311
+ printResult: printPluginDiffResult
7312
+ },
7313
+ resolveContainerConfig: resolvePluginContainerConfig,
7314
+ resolveAppContainerConfig: resolvePluginAppContainerConfig
7315
+ }),
7316
+ capture: createCaptureCommand({
7317
+ description: "Capture current plugins from kintone app to file",
7318
+ args: pluginArgs,
7319
+ spinnerMessage: "Capturing plugins...",
7320
+ spinnerStopMessage: "Plugins captured.",
7321
+ domainLabel: "Plugins",
7322
+ multiAppSuccessMessage: "All plugin captures completed successfully.",
7323
+ createContainer: createPluginCliContainer,
7324
+ captureFn: capturePlugin,
7325
+ saveFn: savePlugin,
7326
+ getConfigFilePath: (config) => config.pluginFilePath,
7327
+ resolveContainerConfig: resolvePluginContainerConfig,
7328
+ resolveAppContainerConfig: resolvePluginAppContainerConfig
7329
+ }),
7073
7330
  diff: createDiffCommand({
7074
7331
  description: "Compare local plugin config with remote kintone app",
7075
7332
  args: pluginArgs,
@@ -7204,59 +7461,6 @@ async function applyProcessManagement({ container }) {
7204
7461
  };
7205
7462
  }
7206
7463
  //#endregion
7207
- //#region src/cli/processConfig.ts
7208
- const processArgs = {
7209
- ...kintoneArgs,
7210
- ...multiAppArgs,
7211
- "process-file": {
7212
- type: "string",
7213
- description: "Process management file path (default: process.yaml)"
7214
- }
7215
- };
7216
- const { resolveFilePath: resolveProcessFilePath, resolveContainerConfig: resolveProcessContainerConfig, resolveAppContainerConfig: resolveProcessAppContainerConfig } = createDomainConfigResolver({
7217
- fileArgKey: "process-file",
7218
- envVar: () => process.env.PROCESS_FILE_PATH,
7219
- appFileField: (a) => a.processFile,
7220
- defaultDir: "process",
7221
- defaultFileName: "process.yaml",
7222
- buildConfig: (base, filePath) => ({
7223
- ...base,
7224
- processFilePath: filePath
7225
- })
7226
- });
7227
- //#endregion
7228
- //#region src/cli/commands/process/apply.ts
7229
- var apply_default$5 = createApplyCommand({
7230
- description: "Apply process management settings from YAML to kintone app",
7231
- args: processArgs,
7232
- spinnerMessage: "Applying process management settings...",
7233
- spinnerStopMessage: "Process management settings applied.",
7234
- successMessage: "Process management settings applied successfully.",
7235
- createContainer: createProcessManagementCliContainer,
7236
- applyFn: applyProcessManagement,
7237
- onResult: (result) => {
7238
- if (result.enableChanged) p.log.warn(result.newEnable ? "Process management will be ENABLED. This activates workflow processing for this app." : "Process management will be DISABLED. This deactivates workflow processing for this app.");
7239
- },
7240
- resolveContainerConfig: resolveProcessContainerConfig,
7241
- resolveAppContainerConfig: resolveProcessAppContainerConfig
7242
- });
7243
- //#endregion
7244
- //#region src/cli/commands/process/capture.ts
7245
- var capture_default$6 = createCaptureCommand({
7246
- description: "Capture current process management settings from kintone app to file",
7247
- args: processArgs,
7248
- spinnerMessage: "Capturing process management settings...",
7249
- spinnerStopMessage: "Process management settings captured.",
7250
- domainLabel: "Process management settings",
7251
- multiAppSuccessMessage: "All process management captures completed successfully.",
7252
- createContainer: createProcessManagementCliContainer,
7253
- captureFn: captureProcessManagement,
7254
- saveFn: saveProcessManagement,
7255
- getConfigFilePath: (config) => config.processFilePath,
7256
- resolveContainerConfig: resolveProcessContainerConfig,
7257
- resolveAppContainerConfig: resolveProcessAppContainerConfig
7258
- });
7259
- //#endregion
7260
7464
  //#region src/core/domain/processManagement/services/diffDetector.ts
7261
7465
  function isEntityEqual(a, b) {
7262
7466
  if (a.type !== b.type) return false;
@@ -7368,13 +7572,64 @@ async function detectProcessManagementDiff({ container }) {
7368
7572
  });
7369
7573
  }
7370
7574
  //#endregion
7575
+ //#region src/cli/processConfig.ts
7576
+ const processArgs = {
7577
+ ...kintoneArgs,
7578
+ ...multiAppArgs,
7579
+ "process-file": {
7580
+ type: "string",
7581
+ description: "Process management file path (default: process.yaml)"
7582
+ }
7583
+ };
7584
+ const { resolveFilePath: resolveProcessFilePath, resolveContainerConfig: resolveProcessContainerConfig, resolveAppContainerConfig: resolveProcessAppContainerConfig } = createDomainConfigResolver({
7585
+ fileArgKey: "process-file",
7586
+ envVar: () => process.env.PROCESS_FILE_PATH,
7587
+ appFileField: (a) => a.processFile,
7588
+ defaultDir: "process",
7589
+ defaultFileName: "process.yaml",
7590
+ buildConfig: (base, filePath) => ({
7591
+ ...base,
7592
+ processFilePath: filePath
7593
+ })
7594
+ });
7595
+ //#endregion
7371
7596
  //#region src/cli/commands/process/index.ts
7372
7597
  var process_default = define({
7373
7598
  name: "process",
7374
7599
  description: "Manage kintone process management settings",
7375
7600
  subCommands: {
7376
- apply: apply_default$5,
7377
- capture: capture_default$6,
7601
+ apply: createApplyCommand({
7602
+ description: "Apply process management settings from YAML to kintone app",
7603
+ args: processArgs,
7604
+ spinnerMessage: "Applying process management settings...",
7605
+ spinnerStopMessage: "Process management settings applied.",
7606
+ successMessage: "Process management settings applied successfully.",
7607
+ createContainer: createProcessManagementCliContainer,
7608
+ applyFn: applyProcessManagement,
7609
+ onResult: (result) => {
7610
+ if (result.enableChanged) p.log.warn(result.newEnable ? "Process management will be ENABLED. This activates workflow processing for this app." : "Process management will be DISABLED. This deactivates workflow processing for this app.");
7611
+ },
7612
+ diffPreview: {
7613
+ detectDiff: detectProcessManagementDiff,
7614
+ printResult: printProcessDiffResult
7615
+ },
7616
+ resolveContainerConfig: resolveProcessContainerConfig,
7617
+ resolveAppContainerConfig: resolveProcessAppContainerConfig
7618
+ }),
7619
+ capture: createCaptureCommand({
7620
+ description: "Capture current process management settings from kintone app to file",
7621
+ args: processArgs,
7622
+ spinnerMessage: "Capturing process management settings...",
7623
+ spinnerStopMessage: "Process management settings captured.",
7624
+ domainLabel: "Process management settings",
7625
+ multiAppSuccessMessage: "All process management captures completed successfully.",
7626
+ createContainer: createProcessManagementCliContainer,
7627
+ captureFn: captureProcessManagement,
7628
+ saveFn: saveProcessManagement,
7629
+ getConfigFilePath: (config) => config.processFilePath,
7630
+ resolveContainerConfig: resolveProcessContainerConfig,
7631
+ resolveAppContainerConfig: resolveProcessAppContainerConfig
7632
+ }),
7378
7633
  diff: createDiffCommand({
7379
7634
  description: "Compare local process management settings with remote kintone app",
7380
7635
  args: processArgs,
@@ -7462,56 +7717,6 @@ async function applyRecordPermission({ container }) {
7462
7717
  });
7463
7718
  }
7464
7719
  //#endregion
7465
- //#region src/cli/recordAclConfig.ts
7466
- const recordAclArgs = {
7467
- ...kintoneArgs,
7468
- ...multiAppArgs,
7469
- "record-acl-file": {
7470
- type: "string",
7471
- description: "Record ACL file path (default: record-acl.yaml)"
7472
- }
7473
- };
7474
- const { resolveFilePath: resolveRecordAclFilePath, resolveContainerConfig: resolveRecordAclContainerConfig, resolveAppContainerConfig: resolveRecordAclAppContainerConfig } = createDomainConfigResolver({
7475
- fileArgKey: "record-acl-file",
7476
- envVar: () => process.env.RECORD_ACL_FILE_PATH,
7477
- appFileField: (a) => a.recordAclFile,
7478
- defaultDir: "record-acl",
7479
- defaultFileName: "record-acl.yaml",
7480
- buildConfig: (base, filePath) => ({
7481
- ...base,
7482
- recordAclFilePath: filePath
7483
- })
7484
- });
7485
- //#endregion
7486
- //#region src/cli/commands/record-acl/apply.ts
7487
- var apply_default$4 = createApplyCommand({
7488
- description: "Apply record access permissions from YAML to kintone app",
7489
- args: recordAclArgs,
7490
- spinnerMessage: "Applying record access permissions...",
7491
- spinnerStopMessage: "Record access permissions applied.",
7492
- successMessage: "Record access permissions applied successfully.",
7493
- createContainer: createRecordPermissionCliContainer,
7494
- applyFn: applyRecordPermission,
7495
- resolveContainerConfig: resolveRecordAclContainerConfig,
7496
- resolveAppContainerConfig: resolveRecordAclAppContainerConfig
7497
- });
7498
- //#endregion
7499
- //#region src/cli/commands/record-acl/capture.ts
7500
- var capture_default$5 = createCaptureCommand({
7501
- description: "Capture current record access permissions from kintone app to file",
7502
- args: recordAclArgs,
7503
- spinnerMessage: "Capturing record access permissions...",
7504
- spinnerStopMessage: "Record access permissions captured.",
7505
- domainLabel: "Record ACL",
7506
- multiAppSuccessMessage: "All record ACL captures completed successfully.",
7507
- createContainer: createRecordPermissionCliContainer,
7508
- captureFn: captureRecordPermission,
7509
- saveFn: saveRecordPermission,
7510
- getConfigFilePath: (config) => config.recordAclFilePath,
7511
- resolveContainerConfig: resolveRecordAclContainerConfig,
7512
- resolveAppContainerConfig: resolveRecordAclAppContainerConfig
7513
- });
7514
- //#endregion
7515
7720
  //#region src/core/domain/recordPermission/services/diffDetector.ts
7516
7721
  function isRightEqual(a, b) {
7517
7722
  return deepEqual(a.entities.map((e) => ({
@@ -7558,7 +7763,7 @@ function compareRightsForFilter(filterCond, localRights, remoteRights, entries)
7558
7763
  });
7559
7764
  else if (localRight && remoteRight) {
7560
7765
  if (!isRightEqual(localRight, remoteRight)) {
7561
- const detail = localRight.entities.length !== remoteRight.entities.length ? `entities: ${remoteRight.entities.length} -> ${localRight.entities.length}` : "entities changed";
7766
+ const detail = localRight.entities.length !== remoteRight.entities.length ? `entities: ${remoteRight.entities.length} -> ${localRight.entities.length}` : `entities: ${describeRight(localRight)}`;
7562
7767
  entries.push({
7563
7768
  type: "modified",
7564
7769
  filterCond,
@@ -7592,13 +7797,61 @@ async function detectRecordPermissionDiff({ container }) {
7592
7797
  });
7593
7798
  }
7594
7799
  //#endregion
7800
+ //#region src/cli/recordAclConfig.ts
7801
+ const recordAclArgs = {
7802
+ ...kintoneArgs,
7803
+ ...multiAppArgs,
7804
+ "record-acl-file": {
7805
+ type: "string",
7806
+ description: "Record ACL file path (default: record-acl.yaml)"
7807
+ }
7808
+ };
7809
+ const { resolveFilePath: resolveRecordAclFilePath, resolveContainerConfig: resolveRecordAclContainerConfig, resolveAppContainerConfig: resolveRecordAclAppContainerConfig } = createDomainConfigResolver({
7810
+ fileArgKey: "record-acl-file",
7811
+ envVar: () => process.env.RECORD_ACL_FILE_PATH,
7812
+ appFileField: (a) => a.recordAclFile,
7813
+ defaultDir: "record-acl",
7814
+ defaultFileName: "record-acl.yaml",
7815
+ buildConfig: (base, filePath) => ({
7816
+ ...base,
7817
+ recordAclFilePath: filePath
7818
+ })
7819
+ });
7820
+ //#endregion
7595
7821
  //#region src/cli/commands/record-acl/index.ts
7596
7822
  var record_acl_default = define({
7597
7823
  name: "record-acl",
7598
7824
  description: "Manage kintone record access permissions",
7599
7825
  subCommands: {
7600
- apply: apply_default$4,
7601
- capture: capture_default$5,
7826
+ apply: createApplyCommand({
7827
+ description: "Apply record access permissions from YAML to kintone app",
7828
+ args: recordAclArgs,
7829
+ spinnerMessage: "Applying record access permissions...",
7830
+ spinnerStopMessage: "Record access permissions applied.",
7831
+ successMessage: "Record access permissions applied successfully.",
7832
+ createContainer: createRecordPermissionCliContainer,
7833
+ applyFn: applyRecordPermission,
7834
+ diffPreview: {
7835
+ detectDiff: detectRecordPermissionDiff,
7836
+ printResult: printRecordPermissionDiffResult
7837
+ },
7838
+ resolveContainerConfig: resolveRecordAclContainerConfig,
7839
+ resolveAppContainerConfig: resolveRecordAclAppContainerConfig
7840
+ }),
7841
+ capture: createCaptureCommand({
7842
+ description: "Capture current record access permissions from kintone app to file",
7843
+ args: recordAclArgs,
7844
+ spinnerMessage: "Capturing record access permissions...",
7845
+ spinnerStopMessage: "Record access permissions captured.",
7846
+ domainLabel: "Record ACL",
7847
+ multiAppSuccessMessage: "All record ACL captures completed successfully.",
7848
+ createContainer: createRecordPermissionCliContainer,
7849
+ captureFn: captureRecordPermission,
7850
+ saveFn: saveRecordPermission,
7851
+ getConfigFilePath: (config) => config.recordAclFilePath,
7852
+ resolveContainerConfig: resolveRecordAclContainerConfig,
7853
+ resolveAppContainerConfig: resolveRecordAclAppContainerConfig
7854
+ }),
7602
7855
  diff: createDiffCommand({
7603
7856
  description: "Compare local record permission config with remote kintone app",
7604
7857
  args: recordAclArgs,
@@ -7777,56 +8030,6 @@ async function applyReport({ container }) {
7777
8030
  });
7778
8031
  }
7779
8032
  //#endregion
7780
- //#region src/cli/reportConfig.ts
7781
- const reportArgs = {
7782
- ...kintoneArgs,
7783
- ...multiAppArgs,
7784
- "report-file": {
7785
- type: "string",
7786
- description: "Report file path (default: reports.yaml)"
7787
- }
7788
- };
7789
- const { resolveFilePath: resolveReportFilePath, resolveContainerConfig: resolveReportContainerConfig, resolveAppContainerConfig: resolveReportAppContainerConfig } = createDomainConfigResolver({
7790
- fileArgKey: "report-file",
7791
- envVar: () => process.env.REPORT_FILE_PATH,
7792
- appFileField: (a) => a.reportFile,
7793
- defaultDir: "report",
7794
- defaultFileName: "reports.yaml",
7795
- buildConfig: (base, filePath) => ({
7796
- ...base,
7797
- reportFilePath: filePath
7798
- })
7799
- });
7800
- //#endregion
7801
- //#region src/cli/commands/report/apply.ts
7802
- var apply_default$3 = createApplyCommand({
7803
- description: "Apply report settings from YAML to kintone app",
7804
- args: reportArgs,
7805
- spinnerMessage: "Applying report settings...",
7806
- spinnerStopMessage: "Report settings applied.",
7807
- successMessage: "Report settings applied successfully.",
7808
- createContainer: createReportCliContainer,
7809
- applyFn: applyReport,
7810
- resolveContainerConfig: resolveReportContainerConfig,
7811
- resolveAppContainerConfig: resolveReportAppContainerConfig
7812
- });
7813
- //#endregion
7814
- //#region src/cli/commands/report/capture.ts
7815
- var capture_default$4 = createCaptureCommand({
7816
- description: "Capture current report settings from kintone app to file",
7817
- args: reportArgs,
7818
- spinnerMessage: "Capturing report settings...",
7819
- spinnerStopMessage: "Report settings captured.",
7820
- domainLabel: "Reports",
7821
- multiAppSuccessMessage: "All report captures completed successfully.",
7822
- createContainer: createReportCliContainer,
7823
- captureFn: captureReport,
7824
- saveFn: saveReport,
7825
- getConfigFilePath: (config) => config.reportFilePath,
7826
- resolveContainerConfig: resolveReportContainerConfig,
7827
- resolveAppContainerConfig: resolveReportAppContainerConfig
7828
- });
7829
- //#endregion
7830
8033
  //#region src/core/domain/report/services/diffDetector.ts
7831
8034
  function compareReports(local, remote) {
7832
8035
  const diffs = [];
@@ -7875,13 +8078,61 @@ async function detectReportDiff({ container }) {
7875
8078
  });
7876
8079
  }
7877
8080
  //#endregion
8081
+ //#region src/cli/reportConfig.ts
8082
+ const reportArgs = {
8083
+ ...kintoneArgs,
8084
+ ...multiAppArgs,
8085
+ "report-file": {
8086
+ type: "string",
8087
+ description: "Report file path (default: reports.yaml)"
8088
+ }
8089
+ };
8090
+ const { resolveFilePath: resolveReportFilePath, resolveContainerConfig: resolveReportContainerConfig, resolveAppContainerConfig: resolveReportAppContainerConfig } = createDomainConfigResolver({
8091
+ fileArgKey: "report-file",
8092
+ envVar: () => process.env.REPORT_FILE_PATH,
8093
+ appFileField: (a) => a.reportFile,
8094
+ defaultDir: "report",
8095
+ defaultFileName: "reports.yaml",
8096
+ buildConfig: (base, filePath) => ({
8097
+ ...base,
8098
+ reportFilePath: filePath
8099
+ })
8100
+ });
8101
+ //#endregion
7878
8102
  //#region src/cli/commands/report/index.ts
7879
8103
  var report_default = define({
7880
8104
  name: "report",
7881
8105
  description: "Manage kintone report settings",
7882
8106
  subCommands: {
7883
- apply: apply_default$3,
7884
- capture: capture_default$4,
8107
+ apply: createApplyCommand({
8108
+ description: "Apply report settings from YAML to kintone app",
8109
+ args: reportArgs,
8110
+ spinnerMessage: "Applying report settings...",
8111
+ spinnerStopMessage: "Report settings applied.",
8112
+ successMessage: "Report settings applied successfully.",
8113
+ createContainer: createReportCliContainer,
8114
+ applyFn: applyReport,
8115
+ diffPreview: {
8116
+ detectDiff: detectReportDiff,
8117
+ printResult: printReportDiffResult
8118
+ },
8119
+ resolveContainerConfig: resolveReportContainerConfig,
8120
+ resolveAppContainerConfig: resolveReportAppContainerConfig
8121
+ }),
8122
+ capture: createCaptureCommand({
8123
+ description: "Capture current report settings from kintone app to file",
8124
+ args: reportArgs,
8125
+ spinnerMessage: "Capturing report settings...",
8126
+ spinnerStopMessage: "Report settings captured.",
8127
+ domainLabel: "Reports",
8128
+ multiAppSuccessMessage: "All report captures completed successfully.",
8129
+ createContainer: createReportCliContainer,
8130
+ captureFn: captureReport,
8131
+ saveFn: saveReport,
8132
+ getConfigFilePath: (config) => config.reportFilePath,
8133
+ resolveContainerConfig: resolveReportContainerConfig,
8134
+ resolveAppContainerConfig: resolveReportAppContainerConfig
8135
+ }),
7885
8136
  diff: createDiffCommand({
7886
8137
  description: "Compare local report config with remote kintone app",
7887
8138
  args: reportArgs,
@@ -9762,56 +10013,6 @@ async function applyGeneralSettings({ container }) {
9762
10013
  });
9763
10014
  }
9764
10015
  //#endregion
9765
- //#region src/cli/settingsConfig.ts
9766
- const settingsArgs = {
9767
- ...kintoneArgs,
9768
- ...multiAppArgs,
9769
- "settings-file": {
9770
- type: "string",
9771
- description: "General settings file path (default: settings.yaml)"
9772
- }
9773
- };
9774
- const { resolveFilePath: resolveSettingsFilePath, resolveContainerConfig: resolveSettingsContainerConfig, resolveAppContainerConfig: resolveSettingsAppContainerConfig } = createDomainConfigResolver({
9775
- fileArgKey: "settings-file",
9776
- envVar: () => process.env.SETTINGS_FILE_PATH,
9777
- appFileField: (a) => a.settingsFile,
9778
- defaultDir: "settings",
9779
- defaultFileName: "settings.yaml",
9780
- buildConfig: (base, filePath) => ({
9781
- ...base,
9782
- settingsFilePath: filePath
9783
- })
9784
- });
9785
- //#endregion
9786
- //#region src/cli/commands/settings/apply.ts
9787
- var apply_default$1 = createApplyCommand({
9788
- description: "Apply general settings from YAML to kintone app",
9789
- args: settingsArgs,
9790
- spinnerMessage: "Applying general settings...",
9791
- spinnerStopMessage: "General settings applied.",
9792
- successMessage: "General settings applied successfully.",
9793
- createContainer: createGeneralSettingsCliContainer,
9794
- applyFn: applyGeneralSettings,
9795
- resolveContainerConfig: resolveSettingsContainerConfig,
9796
- resolveAppContainerConfig: resolveSettingsAppContainerConfig
9797
- });
9798
- //#endregion
9799
- //#region src/cli/commands/settings/capture.ts
9800
- var capture_default$1 = createCaptureCommand({
9801
- description: "Capture current general settings from kintone app to file",
9802
- args: settingsArgs,
9803
- spinnerMessage: "Capturing general settings...",
9804
- spinnerStopMessage: "General settings captured.",
9805
- domainLabel: "General settings",
9806
- multiAppSuccessMessage: "All general settings captures completed successfully.",
9807
- createContainer: createGeneralSettingsCliContainer,
9808
- captureFn: captureGeneralSettings,
9809
- saveFn: saveGeneralSettings,
9810
- getConfigFilePath: (config) => config.settingsFilePath,
9811
- resolveContainerConfig: resolveSettingsContainerConfig,
9812
- resolveAppContainerConfig: resolveSettingsAppContainerConfig
9813
- });
9814
- //#endregion
9815
10016
  //#region src/core/domain/generalSettings/services/diffDetector.ts
9816
10017
  const DEFAULT_STRING = "";
9817
10018
  const DEFAULT_BOOLEAN = false;
@@ -9884,13 +10085,61 @@ async function detectGeneralSettingsDiff({ container }) {
9884
10085
  });
9885
10086
  }
9886
10087
  //#endregion
10088
+ //#region src/cli/settingsConfig.ts
10089
+ const settingsArgs = {
10090
+ ...kintoneArgs,
10091
+ ...multiAppArgs,
10092
+ "settings-file": {
10093
+ type: "string",
10094
+ description: "General settings file path (default: settings.yaml)"
10095
+ }
10096
+ };
10097
+ const { resolveFilePath: resolveSettingsFilePath, resolveContainerConfig: resolveSettingsContainerConfig, resolveAppContainerConfig: resolveSettingsAppContainerConfig } = createDomainConfigResolver({
10098
+ fileArgKey: "settings-file",
10099
+ envVar: () => process.env.SETTINGS_FILE_PATH,
10100
+ appFileField: (a) => a.settingsFile,
10101
+ defaultDir: "settings",
10102
+ defaultFileName: "settings.yaml",
10103
+ buildConfig: (base, filePath) => ({
10104
+ ...base,
10105
+ settingsFilePath: filePath
10106
+ })
10107
+ });
10108
+ //#endregion
9887
10109
  //#region src/cli/commands/settings/index.ts
9888
10110
  var settings_default = define({
9889
10111
  name: "settings",
9890
10112
  description: "Manage kintone general settings",
9891
10113
  subCommands: {
9892
- apply: apply_default$1,
9893
- capture: capture_default$1,
10114
+ apply: createApplyCommand({
10115
+ description: "Apply general settings from YAML to kintone app",
10116
+ args: settingsArgs,
10117
+ spinnerMessage: "Applying general settings...",
10118
+ spinnerStopMessage: "General settings applied.",
10119
+ successMessage: "General settings applied successfully.",
10120
+ createContainer: createGeneralSettingsCliContainer,
10121
+ applyFn: applyGeneralSettings,
10122
+ diffPreview: {
10123
+ detectDiff: detectGeneralSettingsDiff,
10124
+ printResult: printGeneralSettingsDiffResult
10125
+ },
10126
+ resolveContainerConfig: resolveSettingsContainerConfig,
10127
+ resolveAppContainerConfig: resolveSettingsAppContainerConfig
10128
+ }),
10129
+ capture: createCaptureCommand({
10130
+ description: "Capture current general settings from kintone app to file",
10131
+ args: settingsArgs,
10132
+ spinnerMessage: "Capturing general settings...",
10133
+ spinnerStopMessage: "General settings captured.",
10134
+ domainLabel: "General settings",
10135
+ multiAppSuccessMessage: "All general settings captures completed successfully.",
10136
+ createContainer: createGeneralSettingsCliContainer,
10137
+ captureFn: captureGeneralSettings,
10138
+ saveFn: saveGeneralSettings,
10139
+ getConfigFilePath: (config) => config.settingsFilePath,
10140
+ resolveContainerConfig: resolveSettingsContainerConfig,
10141
+ resolveAppContainerConfig: resolveSettingsAppContainerConfig
10142
+ }),
9894
10143
  diff: createDiffCommand({
9895
10144
  description: "Compare local general settings config with remote kintone app",
9896
10145
  args: settingsArgs,
@@ -9974,59 +10223,6 @@ async function applyView({ container }) {
9974
10223
  return { skippedBuiltinViews };
9975
10224
  }
9976
10225
  //#endregion
9977
- //#region src/cli/viewConfig.ts
9978
- const viewArgs = {
9979
- ...kintoneArgs,
9980
- ...multiAppArgs,
9981
- "view-file": {
9982
- type: "string",
9983
- description: "View file path (default: views.yaml)"
9984
- }
9985
- };
9986
- const { resolveFilePath: resolveViewFilePath, resolveContainerConfig: resolveViewContainerConfig, resolveAppContainerConfig: resolveViewAppContainerConfig } = createDomainConfigResolver({
9987
- fileArgKey: "view-file",
9988
- envVar: () => process.env.VIEW_FILE_PATH,
9989
- appFileField: (a) => a.viewFile,
9990
- defaultDir: "view",
9991
- defaultFileName: "views.yaml",
9992
- buildConfig: (base, filePath) => ({
9993
- ...base,
9994
- viewFilePath: filePath
9995
- })
9996
- });
9997
- //#endregion
9998
- //#region src/cli/commands/view/apply.ts
9999
- var apply_default = createApplyCommand({
10000
- description: "Apply view settings from YAML to kintone app",
10001
- args: viewArgs,
10002
- spinnerMessage: "Applying views...",
10003
- spinnerStopMessage: "Views applied.",
10004
- successMessage: "Views applied successfully.",
10005
- createContainer: createViewCliContainer,
10006
- applyFn: applyView,
10007
- onResult: (result) => {
10008
- if (result.skippedBuiltinViews.length > 0) p.log.warn(`Skipped built-in views: ${result.skippedBuiltinViews.join(", ")}`);
10009
- },
10010
- resolveContainerConfig: resolveViewContainerConfig,
10011
- resolveAppContainerConfig: resolveViewAppContainerConfig
10012
- });
10013
- //#endregion
10014
- //#region src/cli/commands/view/capture.ts
10015
- var capture_default = createCaptureCommand({
10016
- description: "Capture current view settings from kintone app to file",
10017
- args: viewArgs,
10018
- spinnerMessage: "Capturing views...",
10019
- spinnerStopMessage: "Views captured.",
10020
- domainLabel: "Views",
10021
- multiAppSuccessMessage: "All view captures completed successfully.",
10022
- createContainer: createViewCliContainer,
10023
- captureFn: captureView,
10024
- saveFn: saveView,
10025
- getConfigFilePath: (config) => config.viewFilePath,
10026
- resolveContainerConfig: resolveViewContainerConfig,
10027
- resolveAppContainerConfig: resolveViewAppContainerConfig
10028
- });
10029
- //#endregion
10030
10226
  //#region src/core/domain/view/services/diffDetector.ts
10031
10227
  function checkOptionalStringChange(changes, field, localVal, remoteVal) {
10032
10228
  if ((localVal ?? "") !== (remoteVal ?? "")) changes.push(`${field} changed`);
@@ -10048,10 +10244,10 @@ function describeChanges(local, remote) {
10048
10244
  }
10049
10245
  const ViewDiffDetector = { detect: (localViews, remoteViews) => {
10050
10246
  return buildDiffResult(detectRecordDiff(localViews, remoteViews, {
10051
- onAdded: (name) => ({
10247
+ onAdded: (name, localView) => ({
10052
10248
  type: "added",
10053
10249
  viewName: name,
10054
- details: "new view"
10250
+ details: `new ${localView.type} view`
10055
10251
  }),
10056
10252
  onModified: (name, localView, remoteView) => {
10057
10253
  const changes = describeChanges(localView, remoteView);
@@ -10061,10 +10257,10 @@ const ViewDiffDetector = { detect: (localViews, remoteViews) => {
10061
10257
  details: changes.join(", ")
10062
10258
  };
10063
10259
  },
10064
- onDeleted: (name) => ({
10260
+ onDeleted: (name, remoteView) => ({
10065
10261
  type: "deleted",
10066
10262
  viewName: name,
10067
- details: "removed"
10263
+ details: `removed ${remoteView.type} view`
10068
10264
  })
10069
10265
  }));
10070
10266
  } };
@@ -10080,13 +10276,64 @@ async function detectViewDiff({ container }) {
10080
10276
  });
10081
10277
  }
10082
10278
  //#endregion
10279
+ //#region src/cli/viewConfig.ts
10280
+ const viewArgs = {
10281
+ ...kintoneArgs,
10282
+ ...multiAppArgs,
10283
+ "view-file": {
10284
+ type: "string",
10285
+ description: "View file path (default: views.yaml)"
10286
+ }
10287
+ };
10288
+ const { resolveFilePath: resolveViewFilePath, resolveContainerConfig: resolveViewContainerConfig, resolveAppContainerConfig: resolveViewAppContainerConfig } = createDomainConfigResolver({
10289
+ fileArgKey: "view-file",
10290
+ envVar: () => process.env.VIEW_FILE_PATH,
10291
+ appFileField: (a) => a.viewFile,
10292
+ defaultDir: "view",
10293
+ defaultFileName: "views.yaml",
10294
+ buildConfig: (base, filePath) => ({
10295
+ ...base,
10296
+ viewFilePath: filePath
10297
+ })
10298
+ });
10299
+ //#endregion
10083
10300
  //#region src/cli/commands/view/index.ts
10084
10301
  var view_default = define({
10085
10302
  name: "view",
10086
10303
  description: "Manage kintone view settings",
10087
10304
  subCommands: {
10088
- apply: apply_default,
10089
- capture: capture_default,
10305
+ apply: createApplyCommand({
10306
+ description: "Apply view settings from YAML to kintone app",
10307
+ args: viewArgs,
10308
+ spinnerMessage: "Applying views...",
10309
+ spinnerStopMessage: "Views applied.",
10310
+ successMessage: "Views applied successfully.",
10311
+ createContainer: createViewCliContainer,
10312
+ applyFn: applyView,
10313
+ onResult: (result) => {
10314
+ if (result.skippedBuiltinViews.length > 0) p.log.warn(`Skipped built-in views: ${result.skippedBuiltinViews.join(", ")}`);
10315
+ },
10316
+ diffPreview: {
10317
+ detectDiff: detectViewDiff,
10318
+ printResult: printViewDiffResult
10319
+ },
10320
+ resolveContainerConfig: resolveViewContainerConfig,
10321
+ resolveAppContainerConfig: resolveViewAppContainerConfig
10322
+ }),
10323
+ capture: createCaptureCommand({
10324
+ description: "Capture current view settings from kintone app to file",
10325
+ args: viewArgs,
10326
+ spinnerMessage: "Capturing views...",
10327
+ spinnerStopMessage: "Views captured.",
10328
+ domainLabel: "Views",
10329
+ multiAppSuccessMessage: "All view captures completed successfully.",
10330
+ createContainer: createViewCliContainer,
10331
+ captureFn: captureView,
10332
+ saveFn: saveView,
10333
+ getConfigFilePath: (config) => config.viewFilePath,
10334
+ resolveContainerConfig: resolveViewContainerConfig,
10335
+ resolveAppContainerConfig: resolveViewAppContainerConfig
10336
+ }),
10090
10337
  diff: createDiffCommand({
10091
10338
  description: "Compare local view config with remote kintone app",
10092
10339
  args: viewArgs,