kintone-migrator 0.35.4 → 0.35.5
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 +128 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -795,7 +795,7 @@ function compareActions$1(local, remote) {
|
|
|
795
795
|
const diffs = [];
|
|
796
796
|
if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
|
|
797
797
|
if (!deepEqual(local.destApp, remote.destApp)) diffs.push(`destApp: ${formatValue(remote.destApp)} -> ${formatValue(local.destApp)}`);
|
|
798
|
-
if (local.filterCond !== remote.filterCond) diffs.push(`filterCond:
|
|
798
|
+
if (local.filterCond !== remote.filterCond) diffs.push(`filterCond: ${formatValue(remote.filterCond)} -> ${formatValue(local.filterCond)}`);
|
|
799
799
|
if (!deepEqual(local.mappings, remote.mappings)) diffs.push(`mappings: ${formatValue(remote.mappings)} -> ${formatValue(local.mappings)}`);
|
|
800
800
|
if (!deepEqual(local.entities, remote.entities)) diffs.push(`entities: ${formatValue(remote.entities)} -> ${formatValue(local.entities)}`);
|
|
801
801
|
return diffs;
|
|
@@ -1354,6 +1354,17 @@ const SKIP_REVISION_CHECK = Symbol("skip-revision-check");
|
|
|
1354
1354
|
/** The generation read when a caller does not specify a target. */
|
|
1355
1355
|
const DEFAULT_FORM_READ_TARGET = "preview";
|
|
1356
1356
|
//#endregion
|
|
1357
|
+
//#region src/core/domain/formSchema/services/lookupListDecoder.ts
|
|
1358
|
+
/**
|
|
1359
|
+
* Reads a lookup list (`fieldMappings`, `lookupPickerFields`) from an external
|
|
1360
|
+
* value. kintone represents an unset list as `""`, which reads as the empty
|
|
1361
|
+
* list. Returns `undefined` when the value is not a list.
|
|
1362
|
+
*/
|
|
1363
|
+
function decodeLookupList(value) {
|
|
1364
|
+
if (value === "") return [];
|
|
1365
|
+
return Array.isArray(value) ? value : void 0;
|
|
1366
|
+
}
|
|
1367
|
+
//#endregion
|
|
1357
1368
|
//#region src/lib/charValidation.ts
|
|
1358
1369
|
/** Returns true if the string contains any control characters (0x00–0x1f or 0x7f). */
|
|
1359
1370
|
function hasControlChars(s) {
|
|
@@ -1375,6 +1386,16 @@ function sanitizeForDisplay$1(s) {
|
|
|
1375
1386
|
}
|
|
1376
1387
|
//#endregion
|
|
1377
1388
|
//#region src/core/domain/formSchema/valueObject.ts
|
|
1389
|
+
const LookupRelatedApp = {
|
|
1390
|
+
/**
|
|
1391
|
+
* kintone returns `code: ""` for a related app without an app code, while a
|
|
1392
|
+
* schema leaves `code` out for the same app. Both build a related app
|
|
1393
|
+
* without `code`, so only a non-empty `code` is kept.
|
|
1394
|
+
*/
|
|
1395
|
+
create: (app, code) => code === void 0 || code === "" ? { app } : {
|
|
1396
|
+
app,
|
|
1397
|
+
code
|
|
1398
|
+
} };
|
|
1378
1399
|
function hasInvalidFieldCodeChars(code) {
|
|
1379
1400
|
if (hasControlChars(code)) return true;
|
|
1380
1401
|
for (let i = 0; i < code.length; i++) {
|
|
@@ -1468,6 +1489,42 @@ function normalizeDefaultValue(type, rest) {
|
|
|
1468
1489
|
if (rest.defaultValue !== void 0 && typeof rest.defaultValue === "string") rest.defaultValue = rest.defaultValue === "" ? [] : [rest.defaultValue];
|
|
1469
1490
|
}
|
|
1470
1491
|
}
|
|
1492
|
+
function readKintoneLookupList(value, fieldPath) {
|
|
1493
|
+
const list = decodeLookupList(value);
|
|
1494
|
+
if (list === void 0) throw new SystemError(SystemErrorCode.ExternalApiError, `Expected array at ${fieldPath}, got ${typeof value}`);
|
|
1495
|
+
return list;
|
|
1496
|
+
}
|
|
1497
|
+
function fromKintoneLookup(value, fieldPath) {
|
|
1498
|
+
assertRecord(value, fieldPath);
|
|
1499
|
+
assertRecord(value.relatedApp, `${fieldPath}.relatedApp`);
|
|
1500
|
+
const { app, code } = value.relatedApp;
|
|
1501
|
+
assertString(app, `${fieldPath}.relatedApp.app`);
|
|
1502
|
+
if (code !== void 0) assertString(code, `${fieldPath}.relatedApp.code`);
|
|
1503
|
+
assertString(value.relatedKeyField, `${fieldPath}.relatedKeyField`);
|
|
1504
|
+
const fieldMappings = readKintoneLookupList(value.fieldMappings, `${fieldPath}.fieldMappings`).map((mapping, i) => {
|
|
1505
|
+
const mappingPath = `${fieldPath}.fieldMappings[${i}]`;
|
|
1506
|
+
assertRecord(mapping, mappingPath);
|
|
1507
|
+
assertString(mapping.field, `${mappingPath}.field`);
|
|
1508
|
+
assertString(mapping.relatedField, `${mappingPath}.relatedField`);
|
|
1509
|
+
return {
|
|
1510
|
+
field: mapping.field,
|
|
1511
|
+
relatedField: mapping.relatedField
|
|
1512
|
+
};
|
|
1513
|
+
});
|
|
1514
|
+
const lookupPickerFields = readKintoneLookupList(value.lookupPickerFields, `${fieldPath}.lookupPickerFields`);
|
|
1515
|
+
assertStringArray(lookupPickerFields, `${fieldPath}.lookupPickerFields`);
|
|
1516
|
+
const { filterCond, sort } = value;
|
|
1517
|
+
if (filterCond !== void 0) assertString(filterCond, `${fieldPath}.filterCond`);
|
|
1518
|
+
if (sort !== void 0) assertString(sort, `${fieldPath}.sort`);
|
|
1519
|
+
return {
|
|
1520
|
+
relatedApp: LookupRelatedApp.create(app, code),
|
|
1521
|
+
relatedKeyField: value.relatedKeyField,
|
|
1522
|
+
fieldMappings,
|
|
1523
|
+
lookupPickerFields,
|
|
1524
|
+
...filterCond !== void 0 ? { filterCond } : {},
|
|
1525
|
+
...sort !== void 0 ? { sort } : {}
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1471
1528
|
function fromKintoneProperty(prop) {
|
|
1472
1529
|
const { type, code, label, noLabel, ...rest } = prop;
|
|
1473
1530
|
const base = {
|
|
@@ -1518,10 +1575,15 @@ function fromKintoneProperty(prop) {
|
|
|
1518
1575
|
}
|
|
1519
1576
|
if (!KNOWN_FIELD_TYPES.has(type)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unknown field type: ${type}`);
|
|
1520
1577
|
normalizeDefaultValue(type, rest);
|
|
1578
|
+
const { lookup, ...others } = rest;
|
|
1579
|
+
const properties = lookup === void 0 ? others : {
|
|
1580
|
+
...others,
|
|
1581
|
+
lookup: fromKintoneLookup(lookup, `${type} "${code}".lookup`)
|
|
1582
|
+
};
|
|
1521
1583
|
return {
|
|
1522
1584
|
...base,
|
|
1523
1585
|
type,
|
|
1524
|
-
properties
|
|
1586
|
+
properties
|
|
1525
1587
|
};
|
|
1526
1588
|
}
|
|
1527
1589
|
function toKintoneProperty(field) {
|
|
@@ -4217,16 +4279,17 @@ async function applyAdminNotes({ container }) {
|
|
|
4217
4279
|
//#endregion
|
|
4218
4280
|
//#region src/core/domain/adminNotes/services/diffDetector.ts
|
|
4219
4281
|
const CONTENT_TRUNCATE_LENGTH = 30;
|
|
4220
|
-
function
|
|
4221
|
-
|
|
4222
|
-
|
|
4282
|
+
function formatContentPreview(text, maxLength) {
|
|
4283
|
+
const codePoints = Array.from(text);
|
|
4284
|
+
if (codePoints.length <= maxLength) return formatValue(text);
|
|
4285
|
+
return `${formatValue(codePoints.slice(0, maxLength).join(""))}...`;
|
|
4223
4286
|
}
|
|
4224
4287
|
function compareConfigs$2(local, remote) {
|
|
4225
4288
|
const entries = [];
|
|
4226
4289
|
if (local.content !== remote.content) entries.push({
|
|
4227
4290
|
type: "modified",
|
|
4228
4291
|
field: "content",
|
|
4229
|
-
details:
|
|
4292
|
+
details: `${formatContentPreview(remote.content, CONTENT_TRUNCATE_LENGTH)} -> ${formatContentPreview(local.content, CONTENT_TRUNCATE_LENGTH)}`
|
|
4230
4293
|
});
|
|
4231
4294
|
if (local.includeInTemplateAndDuplicates !== remote.includeInTemplateAndDuplicates) entries.push({
|
|
4232
4295
|
type: "modified",
|
|
@@ -5923,6 +5986,9 @@ function validateLookupStructure(field) {
|
|
|
5923
5986
|
const issues = [];
|
|
5924
5987
|
if (lookup.relatedApp.app.trim().length === 0) issues.push(issue("error", field.code, field.type, "INVALID_LOOKUP", `Field "${field.code}" lookup must have a non-empty "relatedApp.app"`));
|
|
5925
5988
|
if (lookup.relatedKeyField.trim().length === 0) issues.push(issue("error", field.code, field.type, "INVALID_LOOKUP", `Field "${field.code}" lookup must have a non-empty "relatedKeyField"`));
|
|
5989
|
+
lookup.fieldMappings.forEach((mapping, i) => {
|
|
5990
|
+
for (const key of ["field", "relatedField"]) if (mapping[key].trim().length === 0) issues.push(issue("error", field.code, field.type, "INVALID_LOOKUP", `Field "${field.code}" lookup must have a non-empty "fieldMappings[${i}].${key}"`));
|
|
5991
|
+
});
|
|
5926
5992
|
return issues;
|
|
5927
5993
|
}
|
|
5928
5994
|
function validateReferenceTableRelatedApp(field) {
|
|
@@ -6205,6 +6271,42 @@ function buildFieldDefinition(base, fieldType, properties) {
|
|
|
6205
6271
|
};
|
|
6206
6272
|
}
|
|
6207
6273
|
}
|
|
6274
|
+
function invalidLookup(code, detail) {
|
|
6275
|
+
return new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" lookup ${detail}`);
|
|
6276
|
+
}
|
|
6277
|
+
function optionalString(value) {
|
|
6278
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
6279
|
+
}
|
|
6280
|
+
/** Reads a lookup list; an omitted or valueless list is the empty list. */
|
|
6281
|
+
function optionalLookupList(code, key, value) {
|
|
6282
|
+
if (value === void 0 || value === null) return [];
|
|
6283
|
+
const list = decodeLookupList(value);
|
|
6284
|
+
if (list === void 0) throw invalidLookup(code, `must have a "${key}" array`);
|
|
6285
|
+
return list;
|
|
6286
|
+
}
|
|
6287
|
+
function parseLookup(code, raw) {
|
|
6288
|
+
if (!isRecord(raw)) throw invalidLookup(code, "must be an object");
|
|
6289
|
+
if (!isRecord(raw.relatedApp)) throw invalidLookup(code, "must have a \"relatedApp\" object");
|
|
6290
|
+
const rawFieldMappings = optionalLookupList(code, "fieldMappings", raw.fieldMappings);
|
|
6291
|
+
const lookupPickerFields = optionalLookupList(code, "lookupPickerFields", raw.lookupPickerFields).map((f) => String(f));
|
|
6292
|
+
const fieldMappings = rawFieldMappings.map((mapping) => {
|
|
6293
|
+
if (!isRecord(mapping)) throw invalidLookup(code, "must have objects in \"fieldMappings\"");
|
|
6294
|
+
return {
|
|
6295
|
+
field: String(mapping.field ?? ""),
|
|
6296
|
+
relatedField: String(mapping.relatedField ?? "")
|
|
6297
|
+
};
|
|
6298
|
+
});
|
|
6299
|
+
const filterCond = optionalString(raw.filterCond);
|
|
6300
|
+
const sort = optionalString(raw.sort);
|
|
6301
|
+
return {
|
|
6302
|
+
relatedApp: LookupRelatedApp.create(String(raw.relatedApp.app ?? ""), optionalString(raw.relatedApp.code)),
|
|
6303
|
+
relatedKeyField: String(raw.relatedKeyField ?? ""),
|
|
6304
|
+
fieldMappings,
|
|
6305
|
+
lookupPickerFields,
|
|
6306
|
+
...filterCond !== void 0 ? { filterCond } : {},
|
|
6307
|
+
...sort !== void 0 ? { sort } : {}
|
|
6308
|
+
};
|
|
6309
|
+
}
|
|
6208
6310
|
function parseReferenceTableField(raw, code, base) {
|
|
6209
6311
|
if (!isRecord(raw.referenceTable)) throw new BusinessRuleError(FormSchemaErrorCode.FsInvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have a "referenceTable" property`);
|
|
6210
6312
|
const refTable = raw.referenceTable;
|
|
@@ -6259,7 +6361,11 @@ function parseFieldDefinitionFromFlat(raw) {
|
|
|
6259
6361
|
};
|
|
6260
6362
|
}
|
|
6261
6363
|
if (fieldType === "REFERENCE_TABLE") return parseReferenceTableField(raw, code, base);
|
|
6262
|
-
const
|
|
6364
|
+
const { lookup, ...others } = extractProperties(raw);
|
|
6365
|
+
const properties = lookup === void 0 ? others : {
|
|
6366
|
+
...others,
|
|
6367
|
+
lookup: parseLookup(code, lookup)
|
|
6368
|
+
};
|
|
6263
6369
|
validateFieldProperties(code, fieldType, properties);
|
|
6264
6370
|
return buildFieldDefinition(base, fieldType, properties);
|
|
6265
6371
|
}
|
|
@@ -9306,7 +9412,7 @@ function compareConfigs$1(local, remote) {
|
|
|
9306
9412
|
if (lv !== rv) entries.push({
|
|
9307
9413
|
type: "modified",
|
|
9308
9414
|
field,
|
|
9309
|
-
details: suppressValues ? `${field} changed` :
|
|
9415
|
+
details: suppressValues ? `${field} changed` : `${formatValue(rv)} -> ${formatValue(lv)}`
|
|
9310
9416
|
});
|
|
9311
9417
|
}
|
|
9312
9418
|
function compareBoolean(field, l, r, defaultValue) {
|
|
@@ -9487,17 +9593,17 @@ function perRecordLabel(notif) {
|
|
|
9487
9593
|
}
|
|
9488
9594
|
function describePerRecordChanges(local, remote) {
|
|
9489
9595
|
const diffs = [];
|
|
9490
|
-
if (local.title !== remote.title) diffs.push(`title:
|
|
9596
|
+
if (local.title !== remote.title) diffs.push(`title: ${formatValue(remote.title)} -> ${formatValue(local.title)}`);
|
|
9491
9597
|
if (!deepEqual(local.targets, remote.targets)) diffs.push(`targets: ${formatValue(remote.targets)} -> ${formatValue(local.targets)}`);
|
|
9492
9598
|
return diffs.length > 0 ? diffs.join("\n") : "changed";
|
|
9493
9599
|
}
|
|
9494
9600
|
function describeReminderChanges(local, remote) {
|
|
9495
9601
|
const diffs = [];
|
|
9496
|
-
if (local.title !== remote.title) diffs.push(`title:
|
|
9602
|
+
if (local.title !== remote.title) diffs.push(`title: ${formatValue(remote.title)} -> ${formatValue(local.title)}`);
|
|
9497
9603
|
if (local.daysLater !== remote.daysLater) diffs.push(`daysLater: ${remote.daysLater} -> ${local.daysLater}`);
|
|
9498
9604
|
if ((local.hoursLater ?? 0) !== (remote.hoursLater ?? 0)) diffs.push(`hoursLater: ${remote.hoursLater ?? 0} -> ${local.hoursLater ?? 0}`);
|
|
9499
|
-
if ((local.time ?? "") !== (remote.time ?? "")) diffs.push(`time:
|
|
9500
|
-
if (local.filterCond !== remote.filterCond) diffs.push(`filterCond:
|
|
9605
|
+
if ((local.time ?? "") !== (remote.time ?? "")) diffs.push(`time: ${formatValue(remote.time ?? "")} -> ${formatValue(local.time ?? "")}`);
|
|
9606
|
+
if (local.filterCond !== remote.filterCond) diffs.push(`filterCond: ${formatValue(remote.filterCond)} -> ${formatValue(local.filterCond)}`);
|
|
9501
9607
|
if (!deepEqual(local.targets, remote.targets)) diffs.push(`targets: ${formatValue(remote.targets)} -> ${formatValue(local.targets)}`);
|
|
9502
9608
|
return diffs.length > 0 ? diffs.join("\n") : "changed";
|
|
9503
9609
|
}
|
|
@@ -9539,7 +9645,7 @@ function compareReminderSection(local, remote) {
|
|
|
9539
9645
|
type: "added",
|
|
9540
9646
|
section: "reminder",
|
|
9541
9647
|
name: code,
|
|
9542
|
-
details:
|
|
9648
|
+
details: formatValue(localNotif.title)
|
|
9543
9649
|
});
|
|
9544
9650
|
else if (!deepEqual(localNotif, remoteNotif)) entries.push({
|
|
9545
9651
|
type: "modified",
|
|
@@ -9649,11 +9755,11 @@ const PluginDiffDetector = { detect: (local, remote) => {
|
|
|
9649
9755
|
if (!remotePlugin) entries.push({
|
|
9650
9756
|
type: "added",
|
|
9651
9757
|
pluginId: id,
|
|
9652
|
-
details:
|
|
9758
|
+
details: formatValue(localPlugin.name)
|
|
9653
9759
|
});
|
|
9654
9760
|
else {
|
|
9655
9761
|
const diffs = [];
|
|
9656
|
-
if (localPlugin.name !== remotePlugin.name) diffs.push(`name:
|
|
9762
|
+
if (localPlugin.name !== remotePlugin.name) diffs.push(`name: ${formatValue(remotePlugin.name)} -> ${formatValue(localPlugin.name)}`);
|
|
9657
9763
|
if (localPlugin.enabled !== remotePlugin.enabled) diffs.push(`enabled: ${String(remotePlugin.enabled)} -> ${String(localPlugin.enabled)}`);
|
|
9658
9764
|
if (diffs.length > 0) entries.push({
|
|
9659
9765
|
type: "modified",
|
|
@@ -9665,7 +9771,7 @@ const PluginDiffDetector = { detect: (local, remote) => {
|
|
|
9665
9771
|
for (const [id, remotePlugin] of remoteMap) if (!localMap.has(id)) entries.push({
|
|
9666
9772
|
type: "deleted",
|
|
9667
9773
|
pluginId: id,
|
|
9668
|
-
details:
|
|
9774
|
+
details: formatValue(remotePlugin.name)
|
|
9669
9775
|
});
|
|
9670
9776
|
return buildDiffResult(entries);
|
|
9671
9777
|
} };
|
|
@@ -9700,7 +9806,7 @@ function isExecutableUserEqual(a, b) {
|
|
|
9700
9806
|
}
|
|
9701
9807
|
function compareActions(localAction, remoteAction) {
|
|
9702
9808
|
const diffs = [];
|
|
9703
|
-
if (localAction.filterCond !== remoteAction.filterCond) diffs.push(`filterCond:
|
|
9809
|
+
if (localAction.filterCond !== remoteAction.filterCond) diffs.push(`filterCond: ${formatValue(remoteAction.filterCond)} -> ${formatValue(localAction.filterCond)}`);
|
|
9704
9810
|
if (localAction.type !== remoteAction.type) diffs.push(`type: ${remoteAction.type} -> ${localAction.type}`);
|
|
9705
9811
|
if (!isExecutableUserEqual(localAction.executableUser, remoteAction.executableUser)) diffs.push(`executableUser: ${formatValue(remoteAction.executableUser)} -> ${formatValue(localAction.executableUser)}`);
|
|
9706
9812
|
return diffs;
|
|
@@ -9759,7 +9865,7 @@ function compareActionEntries(local, remote, entries) {
|
|
|
9759
9865
|
type: "added",
|
|
9760
9866
|
category: "action",
|
|
9761
9867
|
name: actionLabel(pair.local, pair.index),
|
|
9762
|
-
details: `filterCond:
|
|
9868
|
+
details: `filterCond: ${formatValue(pair.local.filterCond)}`
|
|
9763
9869
|
});
|
|
9764
9870
|
break;
|
|
9765
9871
|
case "remoteOnly":
|
|
@@ -9767,7 +9873,7 @@ function compareActionEntries(local, remote, entries) {
|
|
|
9767
9873
|
type: "deleted",
|
|
9768
9874
|
category: "action",
|
|
9769
9875
|
name: actionLabel(pair.remote, pair.index),
|
|
9770
|
-
details: `filterCond:
|
|
9876
|
+
details: `filterCond: ${formatValue(pair.remote.filterCond)}`
|
|
9771
9877
|
});
|
|
9772
9878
|
break;
|
|
9773
9879
|
case "both": {
|
|
@@ -9884,11 +9990,11 @@ async function detectRecordPermissionDiff({ container }) {
|
|
|
9884
9990
|
//#region src/core/domain/report/services/diffDetector.ts
|
|
9885
9991
|
function compareReports(local, remote) {
|
|
9886
9992
|
const diffs = [];
|
|
9887
|
-
if (local.name !== remote.name) diffs.push(`name:
|
|
9993
|
+
if (local.name !== remote.name) diffs.push(`name: ${formatValue(remote.name)} -> ${formatValue(local.name)}`);
|
|
9888
9994
|
if (local.chartType !== remote.chartType) diffs.push(`chartType: ${remote.chartType} -> ${local.chartType}`);
|
|
9889
9995
|
if ((local.chartMode ?? "") !== (remote.chartMode ?? "")) diffs.push(`chartMode: ${remote.chartMode ?? "(unset)"} -> ${local.chartMode ?? "(unset)"}`);
|
|
9890
9996
|
if (local.index !== remote.index) diffs.push(`index: ${remote.index} -> ${local.index}`);
|
|
9891
|
-
if (local.filterCond !== remote.filterCond) diffs.push(`filterCond:
|
|
9997
|
+
if (local.filterCond !== remote.filterCond) diffs.push(`filterCond: ${formatValue(remote.filterCond)} -> ${formatValue(local.filterCond)}`);
|
|
9892
9998
|
if (!deepEqual(local.groups, remote.groups)) diffs.push(`groups: ${formatValue(remote.groups)} -> ${formatValue(local.groups)}`);
|
|
9893
9999
|
if (!deepEqual(local.aggregations, remote.aggregations)) diffs.push(`aggregations: ${formatValue(remote.aggregations)} -> ${formatValue(local.aggregations)}`);
|
|
9894
10000
|
if (!deepEqual(local.sorts, remote.sorts)) diffs.push(`sorts: ${formatValue(remote.sorts)} -> ${formatValue(local.sorts)}`);
|
|
@@ -9931,7 +10037,7 @@ async function detectReportDiff({ container }) {
|
|
|
9931
10037
|
//#endregion
|
|
9932
10038
|
//#region src/core/domain/view/services/diffDetector.ts
|
|
9933
10039
|
function checkOptionalStringChange(changes, field, localVal, remoteVal) {
|
|
9934
|
-
if ((localVal ?? "") !== (remoteVal ?? "")) changes.push(`${field}:
|
|
10040
|
+
if ((localVal ?? "") !== (remoteVal ?? "")) changes.push(`${field}: ${formatValue(remoteVal ?? "")} -> ${formatValue(localVal ?? "")}`);
|
|
9935
10041
|
}
|
|
9936
10042
|
function describeChanges(local, remote) {
|
|
9937
10043
|
const changes = [];
|