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