kintone-migrator 0.24.2 → 0.24.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,9 +4,9 @@ import "dotenv/config";
4
4
  import { cli, define } from "gunshi";
5
5
  import * as p from "@clack/prompts";
6
6
  import { parse, stringify } from "yaml";
7
- import { KintoneRestAPIClient, KintoneRestAPIError } from "@kintone/rest-api-client";
8
7
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
9
8
  import { basename, dirname, extname, join, resolve } from "node:path";
9
+ import { KintoneRestAPIClient, KintoneRestAPIError } from "@kintone/rest-api-client";
10
10
  import * as v from "valibot";
11
11
  import pc from "picocolors";
12
12
 
@@ -68,6 +68,7 @@ const FieldPermissionErrorCode = {
68
68
  FpInvalidConfigStructure: "FP_INVALID_CONFIG_STRUCTURE",
69
69
  FpInvalidAccessibility: "FP_INVALID_ACCESSIBILITY",
70
70
  FpInvalidEntityType: "FP_INVALID_ENTITY_TYPE",
71
+ FpInvalidBooleanField: "FP_INVALID_BOOLEAN_FIELD",
71
72
  FpEmptyFieldCode: "FP_EMPTY_FIELD_CODE",
72
73
  FpEmptyEntityCode: "FP_EMPTY_ENTITY_CODE",
73
74
  FpDuplicateFieldCode: "FP_DUPLICATE_FIELD_CODE"
@@ -95,7 +96,9 @@ const GeneralSettingsErrorCode = {
95
96
  GsInvalidConfigYaml: "GS_INVALID_CONFIG_YAML",
96
97
  GsInvalidConfigStructure: "GS_INVALID_CONFIG_STRUCTURE",
97
98
  GsInvalidTheme: "GS_INVALID_THEME",
98
- GsInvalidIconType: "GS_INVALID_ICON_TYPE"
99
+ GsInvalidIconType: "GS_INVALID_ICON_TYPE",
100
+ GsInvalidBooleanField: "GS_INVALID_BOOLEAN_FIELD",
101
+ GsInvalidNumberPrecision: "GS_INVALID_NUMBER_PRECISION"
99
102
  };
100
103
 
101
104
  //#endregion
@@ -130,7 +133,9 @@ const ProcessManagementErrorCode = {
130
133
  PmInvalidConfigStructure: "PM_INVALID_CONFIG_STRUCTURE",
131
134
  PmInvalidAssigneeType: "PM_INVALID_ASSIGNEE_TYPE",
132
135
  PmInvalidEntityType: "PM_INVALID_ENTITY_TYPE",
133
- PmInvalidActionReference: "PM_INVALID_ACTION_REFERENCE"
136
+ PmInvalidBooleanField: "PM_INVALID_BOOLEAN_FIELD",
137
+ PmInvalidActionReference: "PM_INVALID_ACTION_REFERENCE",
138
+ PmDuplicateActionName: "PM_DUPLICATE_ACTION_NAME"
134
139
  };
135
140
 
136
141
  //#endregion
@@ -154,7 +159,8 @@ const RecordPermissionErrorCode = {
154
159
  RpInvalidConfigStructure: "RP_INVALID_CONFIG_STRUCTURE",
155
160
  RpInvalidEntityType: "RP_INVALID_ENTITY_TYPE",
156
161
  RpEmptyEntityCode: "RP_EMPTY_ENTITY_CODE",
157
- RpInvalidPermissionValue: "RP_INVALID_PERMISSION_VALUE"
162
+ RpInvalidPermissionValue: "RP_INVALID_PERMISSION_VALUE",
163
+ RpDuplicateEntity: "RP_DUPLICATE_ENTITY"
158
164
  };
159
165
 
160
166
  //#endregion
@@ -180,6 +186,10 @@ const SeedDataErrorCode = {
180
186
  SdInvalidKeyFieldValue: "SD_INVALID_KEY_FIELD_VALUE"
181
187
  };
182
188
 
189
+ //#endregion
190
+ //#region src/core/domain/services/errorCode.ts
191
+ const DomainServiceErrorCode = { YamlSerializationFailed: "DS_YAML_SERIALIZATION_FAILED" };
192
+
183
193
  //#endregion
184
194
  //#region src/core/domain/view/errorCode.ts
185
195
  const ViewErrorCode = {
@@ -209,6 +219,7 @@ const BusinessRuleErrorCode = {
209
219
  ...RecordPermissionErrorCode,
210
220
  ...ReportErrorCode,
211
221
  ...SeedDataErrorCode,
222
+ ...DomainServiceErrorCode,
212
223
  ...ViewErrorCode
213
224
  };
214
225
  /**
@@ -329,6 +340,16 @@ function isSystemError(error) {
329
340
  return error instanceof SystemError;
330
341
  }
331
342
 
343
+ //#endregion
344
+ //#region src/core/application/applyFromConfigBase.ts
345
+ async function applyFromConfig(config) {
346
+ const result = await config.getStorage();
347
+ if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, config.notFoundMessage);
348
+ const parsed = config.parseConfig(result.content);
349
+ const remote = await config.fetchRemote();
350
+ await config.update(parsed, remote);
351
+ }
352
+
332
353
  //#endregion
333
354
  //#region src/lib/typeGuards.ts
334
355
  /**
@@ -344,10 +365,6 @@ function isRecord(value) {
344
365
  //#endregion
345
366
  //#region src/core/domain/typeGuards.ts
346
367
  /**
347
- * Type guard utilities for safe runtime type narrowing.
348
- * Use these functions instead of `as` casts when working with `unknown` values.
349
- */
350
- /**
351
368
  * Narrows `unknown` to `{ code: string }`.
352
369
  */
353
370
  function hasCode(value) {
@@ -369,6 +386,45 @@ function hasOptionalType(value) {
369
386
  if (!isRecord(value)) return false;
370
387
  return value.type === void 0 || typeof value.type === "string";
371
388
  }
389
+ /**
390
+ * Strict boolean validation — rejects non-boolean values.
391
+ * Returns the boolean value if valid, or `defaultValue` when the value is undefined/null.
392
+ * Throws `BusinessRuleError` when the value is present but not a boolean.
393
+ */
394
+ function parseStrictBoolean(value, fieldName, context, errorCode, defaultValue) {
395
+ if (value === void 0 || value === null) {
396
+ if (defaultValue !== void 0) return defaultValue;
397
+ throw new BusinessRuleError(errorCode, `${context} must have a boolean "${fieldName}" property`);
398
+ }
399
+ if (typeof value !== "boolean") throw new BusinessRuleError(errorCode, `${context} has invalid "${fieldName}" value: ${String(value)}. Must be a boolean`);
400
+ return value;
401
+ }
402
+ /**
403
+ * Validates that an unknown value is a string within an allowed set and returns a typed value.
404
+ * Replaces manual `typeof` + `Set.has` + `as` cast patterns.
405
+ */
406
+ function parseEnum(value, validValues, errorCode, message) {
407
+ if (typeof value !== "string" || !validValues.has(value)) throw new BusinessRuleError(errorCode, message);
408
+ return value;
409
+ }
410
+ /**
411
+ * Shared entity parsing logic for domains that use `{ type, code }` entities.
412
+ * Handles common validation: isRecord check, type enum validation, and code non-empty check.
413
+ * Use `allowEmptyCode` to permit empty/missing code for specific entity types (e.g., CREATOR).
414
+ */
415
+ function parseEntityBase(raw, index, validTypes, errorCodes, options) {
416
+ if (!isRecord(raw)) throw new BusinessRuleError(errorCodes.invalidStructure, `Entity at index ${index} must be an object`);
417
+ const type = parseEnum(raw.type, validTypes, errorCodes.invalidType, `Entity at index ${index} has invalid type: ${String(raw.type)}. Must be ${[...validTypes].join(", ")}`);
418
+ if (options?.allowEmptyCode?.(type)) return {
419
+ type,
420
+ code: typeof raw.code === "string" ? raw.code : ""
421
+ };
422
+ if (typeof raw.code !== "string" || raw.code.length === 0) throw new BusinessRuleError(errorCodes.emptyCode, `Entity at index ${index} must have a non-empty "code" property`);
423
+ return {
424
+ type,
425
+ code: raw.code
426
+ };
427
+ }
372
428
 
373
429
  //#endregion
374
430
  //#region src/core/domain/services/yamlConfigParser.ts
@@ -479,13 +535,17 @@ function parseActionConfigText(rawText) {
479
535
  //#endregion
480
536
  //#region src/core/application/action/applyAction.ts
481
537
  async function applyAction({ container }) {
482
- const result = await container.actionStorage.get();
483
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Action config file not found");
484
- const config = parseActionConfigText(result.content);
485
- const current = await container.actionConfigurator.getActions();
486
- await container.actionConfigurator.updateActions({
487
- actions: config.actions,
488
- revision: current.revision
538
+ await applyFromConfig({
539
+ getStorage: () => container.actionStorage.get(),
540
+ parseConfig: parseActionConfigText,
541
+ fetchRemote: () => container.actionConfigurator.getActions(),
542
+ update: async (config, current) => {
543
+ await container.actionConfigurator.updateActions({
544
+ actions: config.actions,
545
+ revision: current.revision
546
+ });
547
+ },
548
+ notFoundMessage: "Action config file not found"
489
549
  });
490
550
  }
491
551
 
@@ -1516,11 +1576,7 @@ function buildKintoneAuth(auth) {
1516
1576
  };
1517
1577
  }
1518
1578
  function createCliContainer(config) {
1519
- const client = new KintoneRestAPIClient({
1520
- baseUrl: config.baseUrl,
1521
- auth: buildKintoneAuth(config.auth),
1522
- guestSpaceId: config.guestSpaceId
1523
- });
1579
+ const client = config.client ?? createKintoneClient(config);
1524
1580
  return {
1525
1581
  formConfigurator: new KintoneFormConfigurator(client, config.appId),
1526
1582
  schemaStorage: createLocalFileSchemaStorage(config.schemaFilePath),
@@ -1529,20 +1585,12 @@ function createCliContainer(config) {
1529
1585
  }
1530
1586
  function createSeedCliContainer(config) {
1531
1587
  return {
1532
- recordManager: new KintoneRecordManager(new KintoneRestAPIClient({
1533
- baseUrl: config.baseUrl,
1534
- auth: buildKintoneAuth(config.auth),
1535
- guestSpaceId: config.guestSpaceId
1536
- }), config.appId),
1588
+ recordManager: new KintoneRecordManager(config.client ?? createKintoneClient(config), config.appId),
1537
1589
  seedStorage: createLocalFileSeedStorage(config.seedFilePath)
1538
1590
  };
1539
1591
  }
1540
1592
  function createCustomizationCliContainer(config) {
1541
- const client = new KintoneRestAPIClient({
1542
- baseUrl: config.baseUrl,
1543
- auth: buildKintoneAuth(config.auth),
1544
- guestSpaceId: config.guestSpaceId
1545
- });
1593
+ const client = config.client ?? createKintoneClient(config);
1546
1594
  return {
1547
1595
  customizationConfigurator: new KintoneCustomizationConfigurator(client, config.appId),
1548
1596
  customizationStorage: createLocalFileCustomizationStorage(config.customizeFilePath),
@@ -1554,13 +1602,19 @@ function createCustomizationCliContainer(config) {
1554
1602
  }
1555
1603
 
1556
1604
  //#endregion
1557
- //#region src/core/application/container/actionCli.ts
1558
- function createActionCliContainer(config) {
1559
- const client = new KintoneRestAPIClient({
1605
+ //#region src/core/application/container/kintoneClient.ts
1606
+ function createKintoneClient(config) {
1607
+ return new KintoneRestAPIClient({
1560
1608
  baseUrl: config.baseUrl,
1561
1609
  auth: buildKintoneAuth(config.auth),
1562
1610
  guestSpaceId: config.guestSpaceId
1563
1611
  });
1612
+ }
1613
+
1614
+ //#endregion
1615
+ //#region src/core/application/container/actionCli.ts
1616
+ function createActionCliContainer(config) {
1617
+ const client = config.client ?? createKintoneClient(config);
1564
1618
  return {
1565
1619
  actionConfigurator: new KintoneActionConfigurator(client, config.appId),
1566
1620
  actionStorage: createLocalFileActionStorage(config.actionFilePath),
@@ -2453,7 +2507,7 @@ function serializeToYaml(data) {
2453
2507
  defaultStringType: "PLAIN"
2454
2508
  });
2455
2509
  } catch (error) {
2456
- throw new BusinessRuleError(BusinessRuleErrorCode.AcInvalidConfigStructure, `Failed to serialize config to YAML: ${error instanceof Error ? error.message : String(error)}`, error);
2510
+ throw new BusinessRuleError(DomainServiceErrorCode.YamlSerializationFailed, `Failed to serialize config to YAML: ${error instanceof Error ? error.message : String(error)}`, error);
2457
2511
  }
2458
2512
  }
2459
2513
 
@@ -2497,15 +2551,25 @@ const ActionConfigSerializer = { serialize: (config) => {
2497
2551
  } };
2498
2552
 
2499
2553
  //#endregion
2500
- //#region src/core/application/action/captureAction.ts
2501
- async function captureAction({ container }) {
2502
- const { actions } = await container.actionConfigurator.getActions();
2554
+ //#region src/core/application/captureFromConfigBase.ts
2555
+ async function captureFromConfig(config) {
2556
+ const remote = await config.fetchRemote();
2503
2557
  return {
2504
- configText: ActionConfigSerializer.serialize({ actions }),
2505
- hasExistingConfig: (await container.actionStorage.get()).exists
2558
+ configText: config.serialize(remote),
2559
+ hasExistingConfig: (await config.getStorage()).exists
2506
2560
  };
2507
2561
  }
2508
2562
 
2563
+ //#endregion
2564
+ //#region src/core/application/action/captureAction.ts
2565
+ async function captureAction({ container }) {
2566
+ return captureFromConfig({
2567
+ fetchRemote: () => container.actionConfigurator.getActions(),
2568
+ serialize: ({ actions }) => ActionConfigSerializer.serialize({ actions }),
2569
+ getStorage: () => container.actionStorage.get()
2570
+ });
2571
+ }
2572
+
2509
2573
  //#endregion
2510
2574
  //#region src/core/application/action/saveAction.ts
2511
2575
  async function saveAction({ container, input }) {
@@ -2815,20 +2879,16 @@ var action_default = define({
2815
2879
  //#endregion
2816
2880
  //#region src/core/domain/adminNotes/services/configParser.ts
2817
2881
  const AdminNotesConfigParser = { parse: (rawText) => {
2818
- if (rawText.trim().length === 0) throw new BusinessRuleError(AdminNotesErrorCode.AnEmptyConfigText, "Admin notes config text is empty");
2819
- let parsed;
2820
- try {
2821
- parsed = parse(rawText);
2822
- } catch (error) {
2823
- throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
2824
- }
2825
- if (!isRecord(parsed)) throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must be a YAML object");
2826
- const obj = parsed;
2827
- if (typeof obj.content !== "string") throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must have a \"content\" string property");
2828
- if (typeof obj.includeInTemplateAndDuplicates !== "boolean") throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must have an \"includeInTemplateAndDuplicates\" boolean property");
2882
+ const parsed = parseYamlConfig(rawText, {
2883
+ emptyConfigText: AdminNotesErrorCode.AnEmptyConfigText,
2884
+ invalidConfigYaml: AdminNotesErrorCode.AnInvalidConfigYaml,
2885
+ invalidConfigStructure: AdminNotesErrorCode.AnInvalidConfigStructure
2886
+ }, "Admin notes");
2887
+ if (typeof parsed.content !== "string") throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must have a \"content\" string property");
2888
+ if (typeof parsed.includeInTemplateAndDuplicates !== "boolean") throw new BusinessRuleError(AdminNotesErrorCode.AnInvalidConfigStructure, "Config must have an \"includeInTemplateAndDuplicates\" boolean property");
2829
2889
  return {
2830
- content: obj.content,
2831
- includeInTemplateAndDuplicates: obj.includeInTemplateAndDuplicates
2890
+ content: parsed.content,
2891
+ includeInTemplateAndDuplicates: parsed.includeInTemplateAndDuplicates
2832
2892
  };
2833
2893
  } };
2834
2894
 
@@ -2841,13 +2901,17 @@ function parseAdminNotesConfigText(rawText) {
2841
2901
  //#endregion
2842
2902
  //#region src/core/application/adminNotes/applyAdminNotes.ts
2843
2903
  async function applyAdminNotes({ container }) {
2844
- const result = await container.adminNotesStorage.get();
2845
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Admin notes config file not found");
2846
- const config = parseAdminNotesConfigText(result.content);
2847
- const current = await container.adminNotesConfigurator.getAdminNotes();
2848
- await container.adminNotesConfigurator.updateAdminNotes({
2849
- config,
2850
- revision: current.revision
2904
+ await applyFromConfig({
2905
+ getStorage: () => container.adminNotesStorage.get(),
2906
+ parseConfig: parseAdminNotesConfigText,
2907
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
2908
+ update: async (config, current) => {
2909
+ await container.adminNotesConfigurator.updateAdminNotes({
2910
+ config,
2911
+ revision: current.revision
2912
+ });
2913
+ },
2914
+ notFoundMessage: "Admin notes config file not found"
2851
2915
  });
2852
2916
  }
2853
2917
 
@@ -2903,11 +2967,7 @@ function createLocalFileAdminNotesStorage(filePath) {
2903
2967
  //#endregion
2904
2968
  //#region src/core/application/container/adminNotesCli.ts
2905
2969
  function createAdminNotesCliContainer(config) {
2906
- const client = new KintoneRestAPIClient({
2907
- baseUrl: config.baseUrl,
2908
- auth: buildKintoneAuth(config.auth),
2909
- guestSpaceId: config.guestSpaceId
2910
- });
2970
+ const client = config.client ?? createKintoneClient(config);
2911
2971
  return {
2912
2972
  adminNotesConfigurator: new KintoneAdminNotesConfigurator(client, config.appId),
2913
2973
  adminNotesStorage: createLocalFileAdminNotesStorage(config.adminNotesFilePath),
@@ -2986,24 +3046,20 @@ var apply_default$11 = define({
2986
3046
  //#endregion
2987
3047
  //#region src/core/domain/adminNotes/services/configSerializer.ts
2988
3048
  const AdminNotesConfigSerializer = { serialize: (config) => {
2989
- return stringify({
3049
+ return serializeToYaml({
2990
3050
  content: config.content,
2991
3051
  includeInTemplateAndDuplicates: config.includeInTemplateAndDuplicates
2992
- }, {
2993
- lineWidth: 0,
2994
- defaultKeyType: "PLAIN",
2995
- defaultStringType: "PLAIN"
2996
3052
  });
2997
3053
  } };
2998
3054
 
2999
3055
  //#endregion
3000
3056
  //#region src/core/application/adminNotes/captureAdminNotes.ts
3001
3057
  async function captureAdminNotes({ container }) {
3002
- const { config } = await container.adminNotesConfigurator.getAdminNotes();
3003
- return {
3004
- configText: AdminNotesConfigSerializer.serialize(config),
3005
- hasExistingConfig: (await container.adminNotesStorage.get()).exists
3006
- };
3058
+ return captureFromConfig({
3059
+ fetchRemote: () => container.adminNotesConfigurator.getAdminNotes(),
3060
+ serialize: ({ config }) => AdminNotesConfigSerializer.serialize(config),
3061
+ getStorage: () => container.adminNotesStorage.get()
3062
+ });
3007
3063
  }
3008
3064
 
3009
3065
  //#endregion
@@ -3123,52 +3179,34 @@ const VALID_ENTITY_TYPES$8 = new Set([
3123
3179
  "CREATOR"
3124
3180
  ]);
3125
3181
  function parseEntity$3(raw, index) {
3126
- if (!isRecord(raw)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, `Entity at index ${index} must be an object`);
3127
- const obj = raw;
3128
- if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$8.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`);
3129
- const type = obj.type;
3130
- if (type === "CREATOR") return {
3131
- type,
3132
- code: typeof obj.code === "string" ? obj.code : ""
3133
- };
3134
- if (typeof obj.code !== "string" || obj.code.length === 0) throw new BusinessRuleError(AppPermissionErrorCode.ApEmptyEntityCode, `Entity at index ${index} must have a non-empty "code" property`);
3135
- return {
3136
- type,
3137
- code: obj.code
3138
- };
3139
- }
3140
- function parseBooleanField$1(obj, field, index) {
3141
- const value = obj[field];
3142
- if (typeof value !== "boolean") throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidBooleanField, `App right at index ${index} must have a boolean "${field}" property`);
3143
- return value;
3182
+ return parseEntityBase(raw, index, VALID_ENTITY_TYPES$8, {
3183
+ invalidStructure: AppPermissionErrorCode.ApInvalidConfigStructure,
3184
+ invalidType: AppPermissionErrorCode.ApInvalidEntityType,
3185
+ emptyCode: AppPermissionErrorCode.ApEmptyEntityCode
3186
+ }, { allowEmptyCode: (type) => type === "CREATOR" });
3144
3187
  }
3145
3188
  function parseAppRight(raw, index) {
3146
3189
  if (!isRecord(raw)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, `App right at index ${index} must be an object`);
3147
- const obj = raw;
3148
3190
  return {
3149
- entity: parseEntity$3(obj.entity, index),
3150
- includeSubs: parseBooleanField$1(obj, "includeSubs", index),
3151
- appEditable: parseBooleanField$1(obj, "appEditable", index),
3152
- recordViewable: parseBooleanField$1(obj, "recordViewable", index),
3153
- recordAddable: parseBooleanField$1(obj, "recordAddable", index),
3154
- recordEditable: parseBooleanField$1(obj, "recordEditable", index),
3155
- recordDeletable: parseBooleanField$1(obj, "recordDeletable", index),
3156
- recordImportable: parseBooleanField$1(obj, "recordImportable", index),
3157
- recordExportable: parseBooleanField$1(obj, "recordExportable", index)
3191
+ entity: parseEntity$3(raw.entity, index),
3192
+ includeSubs: parseStrictBoolean(raw.includeSubs, "includeSubs", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3193
+ appEditable: parseStrictBoolean(raw.appEditable, "appEditable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3194
+ recordViewable: parseStrictBoolean(raw.recordViewable, "recordViewable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3195
+ recordAddable: parseStrictBoolean(raw.recordAddable, "recordAddable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3196
+ recordEditable: parseStrictBoolean(raw.recordEditable, "recordEditable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3197
+ recordDeletable: parseStrictBoolean(raw.recordDeletable, "recordDeletable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3198
+ recordImportable: parseStrictBoolean(raw.recordImportable, "recordImportable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField),
3199
+ recordExportable: parseStrictBoolean(raw.recordExportable, "recordExportable", `App right at index ${index}`, AppPermissionErrorCode.ApInvalidBooleanField)
3158
3200
  };
3159
3201
  }
3160
3202
  const AppPermissionConfigParser = { parse: (rawText) => {
3161
- if (rawText.trim().length === 0) throw new BusinessRuleError(AppPermissionErrorCode.ApEmptyConfigText, "App permission config text is empty");
3162
- let parsed;
3163
- try {
3164
- parsed = parse(rawText);
3165
- } catch (error) {
3166
- throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
3167
- }
3168
- if (!isRecord(parsed)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, "Config must be a YAML object");
3169
- const obj = parsed;
3170
- if (!Array.isArray(obj.rights)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, "Config must have a \"rights\" array");
3171
- const rights = obj.rights.map((item, i) => parseAppRight(item, i));
3203
+ const parsed = parseYamlConfig(rawText, {
3204
+ emptyConfigText: AppPermissionErrorCode.ApEmptyConfigText,
3205
+ invalidConfigYaml: AppPermissionErrorCode.ApInvalidConfigYaml,
3206
+ invalidConfigStructure: AppPermissionErrorCode.ApInvalidConfigStructure
3207
+ }, "App permission");
3208
+ if (!Array.isArray(parsed.rights)) throw new BusinessRuleError(AppPermissionErrorCode.ApInvalidConfigStructure, "Config must have a \"rights\" array");
3209
+ const rights = parsed.rights.map((item, i) => parseAppRight(item, i));
3172
3210
  const seenKeys = /* @__PURE__ */ new Set();
3173
3211
  for (const right of rights) {
3174
3212
  const key = `${right.entity.type}:${right.entity.code}`;
@@ -3187,13 +3225,17 @@ function parseAppPermissionConfigText(rawText) {
3187
3225
  //#endregion
3188
3226
  //#region src/core/application/appPermission/applyAppPermission.ts
3189
3227
  async function applyAppPermission({ container }) {
3190
- const result = await container.appPermissionStorage.get();
3191
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "App permission config file not found");
3192
- const config = parseAppPermissionConfigText(result.content);
3193
- const current = await container.appPermissionConfigurator.getAppPermissions();
3194
- await container.appPermissionConfigurator.updateAppPermissions({
3195
- rights: config.rights,
3196
- revision: current.revision
3228
+ await applyFromConfig({
3229
+ getStorage: () => container.appPermissionStorage.get(),
3230
+ parseConfig: parseAppPermissionConfigText,
3231
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3232
+ update: async (config, current) => {
3233
+ await container.appPermissionConfigurator.updateAppPermissions({
3234
+ rights: config.rights,
3235
+ revision: current.revision
3236
+ });
3237
+ },
3238
+ notFoundMessage: "App permission config file not found"
3197
3239
  });
3198
3240
  }
3199
3241
 
@@ -3291,11 +3333,7 @@ function createLocalFileAppPermissionStorage(filePath) {
3291
3333
  //#endregion
3292
3334
  //#region src/core/application/container/appPermissionCli.ts
3293
3335
  function createAppPermissionCliContainer(config) {
3294
- const client = new KintoneRestAPIClient({
3295
- baseUrl: config.baseUrl,
3296
- auth: buildKintoneAuth(config.auth),
3297
- guestSpaceId: config.guestSpaceId
3298
- });
3336
+ const client = config.client ?? createKintoneClient(config);
3299
3337
  return {
3300
3338
  appPermissionConfigurator: new KintoneAppPermissionConfigurator(client, config.appId),
3301
3339
  appPermissionStorage: createLocalFileAppPermissionStorage(config.appAclFilePath),
@@ -3390,21 +3428,17 @@ function serializeAppRight(right) {
3390
3428
  };
3391
3429
  }
3392
3430
  const AppPermissionConfigSerializer = { serialize: (config) => {
3393
- return stringify({ rights: config.rights.map(serializeAppRight) }, {
3394
- lineWidth: 0,
3395
- defaultKeyType: "PLAIN",
3396
- defaultStringType: "PLAIN"
3397
- });
3431
+ return serializeToYaml({ rights: config.rights.map(serializeAppRight) });
3398
3432
  } };
3399
3433
 
3400
3434
  //#endregion
3401
3435
  //#region src/core/application/appPermission/captureAppPermission.ts
3402
3436
  async function captureAppPermission({ container }) {
3403
- const { rights } = await container.appPermissionConfigurator.getAppPermissions();
3404
- return {
3405
- configText: AppPermissionConfigSerializer.serialize({ rights }),
3406
- hasExistingConfig: (await container.appPermissionStorage.get()).exists
3407
- };
3437
+ return captureFromConfig({
3438
+ fetchRemote: () => container.appPermissionConfigurator.getAppPermissions(),
3439
+ serialize: ({ rights }) => AppPermissionConfigSerializer.serialize({ rights }),
3440
+ getStorage: () => container.appPermissionStorage.get()
3441
+ });
3408
3442
  }
3409
3443
 
3410
3444
  //#endregion
@@ -3840,8 +3874,8 @@ function planResources(resources, platformDir, resourceType, relativeBaseDir) {
3840
3874
  filesToDownload
3841
3875
  };
3842
3876
  }
3843
- function planPlatform(remotePlatform, platformName, args) {
3844
- const platformDir = join(args.input.basePath, args.input.filePrefix, platformName);
3877
+ function planPlatform(remotePlatform, platformName, basePath, filePrefix) {
3878
+ const platformDir = join(basePath, filePrefix, platformName);
3845
3879
  const platformPrefix = platformName;
3846
3880
  const jsPlan = planResources(remotePlatform.js, platformDir, "js", platformPrefix);
3847
3881
  const cssPlan = planResources(remotePlatform.css, platformDir, "css", platformPrefix);
@@ -3869,18 +3903,18 @@ async function downloadFiles(files, container) {
3869
3903
  await container.fileWriter.write(file.absolutePath, data);
3870
3904
  }));
3871
3905
  }
3872
- async function captureCustomization(args) {
3873
- const existing = await args.container.customizationStorage.get();
3874
- const { scope, desktop, mobile } = await args.container.customizationConfigurator.getCustomization();
3875
- const desktopPlan = planPlatform(desktop, "desktop", args);
3876
- const mobilePlan = planPlatform(mobile, "mobile", args);
3906
+ async function captureCustomization({ container, input }) {
3907
+ const existing = await container.customizationStorage.get();
3908
+ const { scope, desktop, mobile } = await container.customizationConfigurator.getCustomization();
3909
+ const desktopPlan = planPlatform(desktop, "desktop", input.basePath, input.filePrefix);
3910
+ const mobilePlan = planPlatform(mobile, "mobile", input.basePath, input.filePrefix);
3877
3911
  const config = {
3878
3912
  scope,
3879
3913
  desktop: desktopPlan.platform,
3880
3914
  mobile: mobilePlan.platform
3881
3915
  };
3882
3916
  const configText = CustomizationConfigSerializer.serialize(config);
3883
- await downloadFiles([...desktopPlan.filesToDownload, ...mobilePlan.filesToDownload], args.container);
3917
+ await downloadFiles([...desktopPlan.filesToDownload, ...mobilePlan.filesToDownload], container);
3884
3918
  return {
3885
3919
  configText,
3886
3920
  hasExistingConfig: existing.exists,
@@ -4208,11 +4242,7 @@ function createLocalFileFieldPermissionStorage(filePath) {
4208
4242
  //#endregion
4209
4243
  //#region src/core/application/container/fieldPermissionCli.ts
4210
4244
  function createFieldPermissionCliContainer(config) {
4211
- const client = new KintoneRestAPIClient({
4212
- baseUrl: config.baseUrl,
4213
- auth: buildKintoneAuth(config.auth),
4214
- guestSpaceId: config.guestSpaceId
4215
- });
4245
+ const client = config.client ?? createKintoneClient(config);
4216
4246
  return {
4217
4247
  fieldPermissionConfigurator: new KintoneFieldPermissionConfigurator(client, config.appId),
4218
4248
  fieldPermissionStorage: createLocalFileFieldPermissionStorage(config.fieldAclFilePath),
@@ -4234,53 +4264,42 @@ const VALID_ENTITY_TYPES$5 = new Set([
4234
4264
  "FIELD_ENTITY"
4235
4265
  ]);
4236
4266
  function parseEntity$2(raw, index) {
4237
- if (!isRecord(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Entity at index ${index} must be an object`);
4238
- const obj = raw;
4239
- if (typeof obj.type !== "string" || !VALID_ENTITY_TYPES$5.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`);
4240
- 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`);
4241
- return {
4242
- type: obj.type,
4243
- code: obj.code
4244
- };
4267
+ return parseEntityBase(raw, index, VALID_ENTITY_TYPES$5, {
4268
+ invalidStructure: FieldPermissionErrorCode.FpInvalidConfigStructure,
4269
+ invalidType: FieldPermissionErrorCode.FpInvalidEntityType,
4270
+ emptyCode: FieldPermissionErrorCode.FpEmptyEntityCode
4271
+ });
4245
4272
  }
4246
4273
  function parseFieldRightEntity(raw, index) {
4247
4274
  if (!isRecord(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right entity at index ${index} must be an object`);
4248
- const obj = raw;
4249
- 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`);
4250
- const entity = parseEntity$2(obj.entity, index);
4251
4275
  const result = {
4252
- accessibility: obj.accessibility,
4253
- entity
4276
+ accessibility: parseEnum(raw.accessibility, VALID_ACCESSIBILITIES, FieldPermissionErrorCode.FpInvalidAccessibility, `Field right entity at index ${index} has invalid accessibility: ${String(raw.accessibility)}. Must be READ, WRITE, or NONE`),
4277
+ entity: parseEntity$2(raw.entity, index)
4254
4278
  };
4255
- if (obj.includeSubs !== void 0 && obj.includeSubs !== null) return {
4279
+ if (raw.includeSubs !== void 0 && raw.includeSubs !== null) return {
4256
4280
  ...result,
4257
- includeSubs: Boolean(obj.includeSubs)
4281
+ includeSubs: parseStrictBoolean(raw.includeSubs, "includeSubs", `Field right entity at index ${index}`, FieldPermissionErrorCode.FpInvalidBooleanField)
4258
4282
  };
4259
4283
  return result;
4260
4284
  }
4261
4285
  function parseFieldRight(raw, index) {
4262
4286
  if (!isRecord(raw)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right at index ${index} must be an object`);
4263
- const obj = raw;
4264
- 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`);
4265
- if (!Array.isArray(obj.entities)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right at index ${index} must have an "entities" array`);
4266
- const entities = obj.entities.map((item, i) => parseFieldRightEntity(item, i));
4287
+ if (typeof raw.code !== "string" || raw.code.length === 0) throw new BusinessRuleError(FieldPermissionErrorCode.FpEmptyFieldCode, `Field right at index ${index} must have a non-empty "code" property`);
4288
+ if (!Array.isArray(raw.entities)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, `Field right at index ${index} must have an "entities" array`);
4289
+ const entities = raw.entities.map((item, i) => parseFieldRightEntity(item, i));
4267
4290
  return {
4268
- code: obj.code,
4291
+ code: raw.code,
4269
4292
  entities
4270
4293
  };
4271
4294
  }
4272
4295
  const FieldPermissionConfigParser = { parse: (rawText) => {
4273
- if (rawText.trim().length === 0) throw new BusinessRuleError(FieldPermissionErrorCode.FpEmptyConfigText, "Field permission config text is empty");
4274
- let parsed;
4275
- try {
4276
- parsed = parse(rawText);
4277
- } catch (error) {
4278
- throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
4279
- }
4280
- if (!isRecord(parsed)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, "Config must be a YAML object");
4281
- const obj = parsed;
4282
- if (!Array.isArray(obj.rights)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, "Config must have a \"rights\" array");
4283
- const rights = obj.rights.map((item, i) => parseFieldRight(item, i));
4296
+ const parsed = parseYamlConfig(rawText, {
4297
+ emptyConfigText: FieldPermissionErrorCode.FpEmptyConfigText,
4298
+ invalidConfigYaml: FieldPermissionErrorCode.FpInvalidConfigYaml,
4299
+ invalidConfigStructure: FieldPermissionErrorCode.FpInvalidConfigStructure
4300
+ }, "Field permission");
4301
+ if (!Array.isArray(parsed.rights)) throw new BusinessRuleError(FieldPermissionErrorCode.FpInvalidConfigStructure, "Config must have a \"rights\" array");
4302
+ const rights = parsed.rights.map((item, i) => parseFieldRight(item, i));
4284
4303
  const seenCodes = /* @__PURE__ */ new Set();
4285
4304
  for (const right of rights) {
4286
4305
  if (seenCodes.has(right.code)) throw new BusinessRuleError(FieldPermissionErrorCode.FpDuplicateFieldCode, `Duplicate field code: ${right.code}`);
@@ -4298,13 +4317,17 @@ function parseFieldPermissionConfigText(rawText) {
4298
4317
  //#endregion
4299
4318
  //#region src/core/application/fieldPermission/applyFieldPermission.ts
4300
4319
  async function applyFieldPermission({ container }) {
4301
- const result = await container.fieldPermissionStorage.get();
4302
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Field permission config file not found");
4303
- const config = parseFieldPermissionConfigText(result.content);
4304
- const current = await container.fieldPermissionConfigurator.getFieldPermissions();
4305
- await container.fieldPermissionConfigurator.updateFieldPermissions({
4306
- rights: config.rights,
4307
- revision: current.revision
4320
+ await applyFromConfig({
4321
+ getStorage: () => container.fieldPermissionStorage.get(),
4322
+ parseConfig: parseFieldPermissionConfigText,
4323
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4324
+ update: async (config, current) => {
4325
+ await container.fieldPermissionConfigurator.updateFieldPermissions({
4326
+ rights: config.rights,
4327
+ revision: current.revision
4328
+ });
4329
+ },
4330
+ notFoundMessage: "Field permission config file not found"
4308
4331
  });
4309
4332
  }
4310
4333
 
@@ -4390,24 +4413,20 @@ function serializeFieldRightEntity(entity) {
4390
4413
  return result;
4391
4414
  }
4392
4415
  const FieldPermissionConfigSerializer = { serialize: (config) => {
4393
- return stringify({ rights: config.rights.map((right) => ({
4416
+ return serializeToYaml({ rights: config.rights.map((right) => ({
4394
4417
  code: right.code,
4395
4418
  entities: right.entities.map(serializeFieldRightEntity)
4396
- })) }, {
4397
- lineWidth: 0,
4398
- defaultKeyType: "PLAIN",
4399
- defaultStringType: "PLAIN"
4400
- });
4419
+ })) });
4401
4420
  } };
4402
4421
 
4403
4422
  //#endregion
4404
4423
  //#region src/core/application/fieldPermission/captureFieldPermission.ts
4405
4424
  async function captureFieldPermission({ container }) {
4406
- const { rights } = await container.fieldPermissionConfigurator.getFieldPermissions();
4407
- return {
4408
- configText: FieldPermissionConfigSerializer.serialize({ rights }),
4409
- hasExistingConfig: (await container.fieldPermissionStorage.get()).exists
4410
- };
4425
+ return captureFromConfig({
4426
+ fetchRemote: () => container.fieldPermissionConfigurator.getFieldPermissions(),
4427
+ serialize: ({ rights }) => FieldPermissionConfigSerializer.serialize({ rights }),
4428
+ getStorage: () => container.fieldPermissionStorage.get()
4429
+ });
4411
4430
  }
4412
4431
 
4413
4432
  //#endregion
@@ -4461,7 +4480,7 @@ var capture_default$9 = define({
4461
4480
 
4462
4481
  //#endregion
4463
4482
  //#region src/core/domain/fieldPermission/services/diffDetector.ts
4464
- function areEntitiesEqual(a, b) {
4483
+ function isEntitiesEqual$1(a, b) {
4465
4484
  return deepEqual(a.entities.map((e) => ({
4466
4485
  accessibility: e.accessibility,
4467
4486
  type: e.entity.type,
@@ -4485,7 +4504,7 @@ const FieldPermissionDiffDetector = { detect: (local, remote) => {
4485
4504
  fieldCode: code,
4486
4505
  details: `${localRight.entities.length} entities`
4487
4506
  });
4488
- else if (!areEntitiesEqual(localRight, remoteRight)) entries.push({
4507
+ else if (!isEntitiesEqual$1(localRight, remoteRight)) entries.push({
4489
4508
  type: "modified",
4490
4509
  fieldCode: code,
4491
4510
  details: "entities changed"
@@ -4684,11 +4703,7 @@ function createLocalFileGeneralSettingsStorage(filePath) {
4684
4703
  //#endregion
4685
4704
  //#region src/core/application/container/generalSettingsCli.ts
4686
4705
  function createGeneralSettingsCliContainer(config) {
4687
- const client = new KintoneRestAPIClient({
4688
- baseUrl: config.baseUrl,
4689
- auth: buildKintoneAuth(config.auth),
4690
- guestSpaceId: config.guestSpaceId
4691
- });
4706
+ const client = config.client ?? createKintoneClient(config);
4692
4707
  return {
4693
4708
  generalSettingsConfigurator: new KintoneGeneralSettingsConfigurator(client, config.appId),
4694
4709
  generalSettingsStorage: createLocalFileGeneralSettingsStorage(config.settingsFilePath),
@@ -4929,11 +4944,7 @@ function createLocalFileNotificationStorage(filePath) {
4929
4944
  //#endregion
4930
4945
  //#region src/core/application/container/notificationCli.ts
4931
4946
  function createNotificationCliContainer(config) {
4932
- const client = new KintoneRestAPIClient({
4933
- baseUrl: config.baseUrl,
4934
- auth: buildKintoneAuth(config.auth),
4935
- guestSpaceId: config.guestSpaceId
4936
- });
4947
+ const client = config.client ?? createKintoneClient(config);
4937
4948
  return {
4938
4949
  notificationConfigurator: new KintoneNotificationConfigurator(client, config.appId),
4939
4950
  notificationStorage: createLocalFileNotificationStorage(config.notificationFilePath),
@@ -4992,11 +5003,7 @@ function createLocalFilePluginStorage(filePath) {
4992
5003
  //#endregion
4993
5004
  //#region src/core/application/container/pluginCli.ts
4994
5005
  function createPluginCliContainer(config) {
4995
- const client = new KintoneRestAPIClient({
4996
- baseUrl: config.baseUrl,
4997
- auth: buildKintoneAuth(config.auth),
4998
- guestSpaceId: config.guestSpaceId
4999
- });
5006
+ const client = config.client ?? createKintoneClient(config);
5000
5007
  return {
5001
5008
  pluginConfigurator: new KintonePluginConfigurator(client, config.appId),
5002
5009
  pluginStorage: createLocalFilePluginStorage(config.pluginFilePath),
@@ -5149,11 +5156,7 @@ function createLocalFileProcessManagementStorage(filePath) {
5149
5156
  //#endregion
5150
5157
  //#region src/core/application/container/processManagementCli.ts
5151
5158
  function createProcessManagementCliContainer(config) {
5152
- const client = new KintoneRestAPIClient({
5153
- baseUrl: config.baseUrl,
5154
- auth: buildKintoneAuth(config.auth),
5155
- guestSpaceId: config.guestSpaceId
5156
- });
5159
+ const client = config.client ?? createKintoneClient(config);
5157
5160
  return {
5158
5161
  processManagementConfigurator: new KintoneProcessManagementConfigurator(client, config.appId),
5159
5162
  processManagementStorage: createLocalFileProcessManagementStorage(config.processFilePath),
@@ -5252,11 +5255,7 @@ function createLocalFileRecordPermissionStorage(filePath) {
5252
5255
  //#endregion
5253
5256
  //#region src/core/application/container/recordPermissionCli.ts
5254
5257
  function createRecordPermissionCliContainer(config) {
5255
- const client = new KintoneRestAPIClient({
5256
- baseUrl: config.baseUrl,
5257
- auth: buildKintoneAuth(config.auth),
5258
- guestSpaceId: config.guestSpaceId
5259
- });
5258
+ const client = config.client ?? createKintoneClient(config);
5260
5259
  return {
5261
5260
  recordPermissionConfigurator: new KintoneRecordPermissionConfigurator(client, config.appId),
5262
5261
  recordPermissionStorage: createLocalFileRecordPermissionStorage(config.recordAclFilePath),
@@ -5547,11 +5546,7 @@ function createLocalFileReportStorage(filePath) {
5547
5546
  //#endregion
5548
5547
  //#region src/core/application/container/reportCli.ts
5549
5548
  function createReportCliContainer(config) {
5550
- const client = new KintoneRestAPIClient({
5551
- baseUrl: config.baseUrl,
5552
- auth: buildKintoneAuth(config.auth),
5553
- guestSpaceId: config.guestSpaceId
5554
- });
5549
+ const client = config.client ?? createKintoneClient(config);
5555
5550
  return {
5556
5551
  reportConfigurator: new KintoneReportConfigurator(client, config.appId),
5557
5552
  reportStorage: createLocalFileReportStorage(config.reportFilePath),
@@ -5673,11 +5668,7 @@ function createLocalFileViewStorage(filePath) {
5673
5668
  //#endregion
5674
5669
  //#region src/core/application/container/viewCli.ts
5675
5670
  function createViewCliContainer(config) {
5676
- const client = new KintoneRestAPIClient({
5677
- baseUrl: config.baseUrl,
5678
- auth: buildKintoneAuth(config.auth),
5679
- guestSpaceId: config.guestSpaceId
5680
- });
5671
+ const client = config.client ?? createKintoneClient(config);
5681
5672
  return {
5682
5673
  viewConfigurator: new KintoneViewConfigurator(client, config.appId),
5683
5674
  viewStorage: createLocalFileViewStorage(config.viewFilePath),
@@ -5688,11 +5679,13 @@ function createViewCliContainer(config) {
5688
5679
  //#endregion
5689
5680
  //#region src/core/application/container/captureAllCli.ts
5690
5681
  function createCliCaptureContainers(input) {
5682
+ const client = createKintoneClient(input);
5691
5683
  const base = {
5692
5684
  baseUrl: input.baseUrl,
5693
5685
  auth: input.auth,
5694
5686
  appId: input.appId,
5695
- guestSpaceId: input.guestSpaceId
5687
+ guestSpaceId: input.guestSpaceId,
5688
+ client
5696
5689
  };
5697
5690
  const paths = buildAppFilePaths(input.appName, input.baseDir);
5698
5691
  return {
@@ -5813,11 +5806,7 @@ function createLocalFileProjectConfigStorage(filePath) {
5813
5806
  //#region src/core/application/container/initCli.ts
5814
5807
  function createInitCliContainer(config) {
5815
5808
  return {
5816
- spaceReader: new KintoneSpaceReader(new KintoneRestAPIClient({
5817
- baseUrl: config.baseUrl,
5818
- auth: buildKintoneAuth(config.auth),
5819
- guestSpaceId: config.guestSpaceId
5820
- })),
5809
+ spaceReader: new KintoneSpaceReader(config.client ?? createKintoneClient(config)),
5821
5810
  projectConfigStorage: createLocalFileProjectConfigStorage(config.configFilePath)
5822
5811
  };
5823
5812
  }
@@ -6031,21 +6020,17 @@ function serializeConfig(config) {
6031
6020
  return result;
6032
6021
  }
6033
6022
  const GeneralSettingsConfigSerializer = { serialize: (config) => {
6034
- return stringify(serializeConfig(config), {
6035
- lineWidth: 0,
6036
- defaultKeyType: "PLAIN",
6037
- defaultStringType: "PLAIN"
6038
- });
6023
+ return serializeToYaml(serializeConfig(config));
6039
6024
  } };
6040
6025
 
6041
6026
  //#endregion
6042
6027
  //#region src/core/application/generalSettings/captureGeneralSettings.ts
6043
6028
  async function captureGeneralSettings({ container }) {
6044
- const { config } = await container.generalSettingsConfigurator.getGeneralSettings();
6045
- return {
6046
- configText: GeneralSettingsConfigSerializer.serialize(config),
6047
- hasExistingConfig: (await container.generalSettingsStorage.get()).exists
6048
- };
6029
+ return captureFromConfig({
6030
+ fetchRemote: () => container.generalSettingsConfigurator.getGeneralSettings(),
6031
+ serialize: ({ config }) => GeneralSettingsConfigSerializer.serialize(config),
6032
+ getStorage: () => container.generalSettingsStorage.get()
6033
+ });
6049
6034
  }
6050
6035
 
6051
6036
  //#endregion
@@ -6115,9 +6100,11 @@ const NotificationConfigSerializer = { serialize: (config) => {
6115
6100
  //#endregion
6116
6101
  //#region src/core/application/notification/captureNotification.ts
6117
6102
  async function captureNotification({ container }) {
6118
- const general = await container.notificationConfigurator.getGeneralNotifications();
6119
- const perRecord = await container.notificationConfigurator.getPerRecordNotifications();
6120
- const reminder = await container.notificationConfigurator.getReminderNotifications();
6103
+ const [general, perRecord, reminder] = await Promise.all([
6104
+ container.notificationConfigurator.getGeneralNotifications(),
6105
+ container.notificationConfigurator.getPerRecordNotifications(),
6106
+ container.notificationConfigurator.getReminderNotifications()
6107
+ ]);
6121
6108
  const config = {
6122
6109
  general: {
6123
6110
  notifyToCommenter: general.notifyToCommenter,
@@ -6154,11 +6141,11 @@ const PluginConfigSerializer = { serialize: (config) => {
6154
6141
  //#endregion
6155
6142
  //#region src/core/application/plugin/capturePlugin.ts
6156
6143
  async function capturePlugin({ container }) {
6157
- const { plugins } = await container.pluginConfigurator.getPlugins();
6158
- return {
6159
- configText: PluginConfigSerializer.serialize({ plugins }),
6160
- hasExistingConfig: (await container.pluginStorage.get()).exists
6161
- };
6144
+ return captureFromConfig({
6145
+ fetchRemote: () => container.pluginConfigurator.getPlugins(),
6146
+ serialize: ({ plugins }) => PluginConfigSerializer.serialize({ plugins }),
6147
+ getStorage: () => container.pluginStorage.get()
6148
+ });
6162
6149
  }
6163
6150
 
6164
6151
  //#endregion
@@ -6184,7 +6171,7 @@ const ProcessManagementConfigSerializer = { serialize: (config) => {
6184
6171
  entities: state.assignee.entities.map(serializeProcessEntity)
6185
6172
  }
6186
6173
  };
6187
- return stringify({
6174
+ return serializeToYaml({
6188
6175
  enable: config.enable,
6189
6176
  states: serializedStates,
6190
6177
  actions: config.actions.map((action) => {
@@ -6198,21 +6185,17 @@ const ProcessManagementConfigSerializer = { serialize: (config) => {
6198
6185
  if (action.executableUser !== void 0) serializedAction.executableUser = { entities: action.executableUser.entities.map(serializeProcessEntity) };
6199
6186
  return serializedAction;
6200
6187
  })
6201
- }, {
6202
- lineWidth: 0,
6203
- defaultKeyType: "PLAIN",
6204
- defaultStringType: "PLAIN"
6205
6188
  });
6206
6189
  } };
6207
6190
 
6208
6191
  //#endregion
6209
6192
  //#region src/core/application/processManagement/captureProcessManagement.ts
6210
6193
  async function captureProcessManagement({ container }) {
6211
- const { config } = await container.processManagementConfigurator.getProcessManagement();
6212
- return {
6213
- configText: ProcessManagementConfigSerializer.serialize(config),
6214
- hasExistingConfig: (await container.processManagementStorage.get()).exists
6215
- };
6194
+ return captureFromConfig({
6195
+ fetchRemote: () => container.processManagementConfigurator.getProcessManagement(),
6196
+ serialize: ({ config }) => ProcessManagementConfigSerializer.serialize(config),
6197
+ getStorage: () => container.processManagementStorage.get()
6198
+ });
6216
6199
  }
6217
6200
 
6218
6201
  //#endregion
@@ -6236,24 +6219,20 @@ function serializeRecordRightEntity(entity) {
6236
6219
  };
6237
6220
  }
6238
6221
  const RecordPermissionConfigSerializer = { serialize: (config) => {
6239
- return stringify({ rights: config.rights.map((right) => ({
6222
+ return serializeToYaml({ rights: config.rights.map((right) => ({
6240
6223
  filterCond: right.filterCond,
6241
6224
  entities: right.entities.map(serializeRecordRightEntity)
6242
- })) }, {
6243
- lineWidth: 0,
6244
- defaultKeyType: "PLAIN",
6245
- defaultStringType: "PLAIN"
6246
- });
6225
+ })) });
6247
6226
  } };
6248
6227
 
6249
6228
  //#endregion
6250
6229
  //#region src/core/application/recordPermission/captureRecordPermission.ts
6251
6230
  async function captureRecordPermission({ container }) {
6252
- const { rights } = await container.recordPermissionConfigurator.getRecordPermissions();
6253
- return {
6254
- configText: RecordPermissionConfigSerializer.serialize({ rights }),
6255
- hasExistingConfig: (await container.recordPermissionStorage.get()).exists
6256
- };
6231
+ return captureFromConfig({
6232
+ fetchRemote: () => container.recordPermissionConfigurator.getRecordPermissions(),
6233
+ serialize: ({ rights }) => RecordPermissionConfigSerializer.serialize({ rights }),
6234
+ getStorage: () => container.recordPermissionStorage.get()
6235
+ });
6257
6236
  }
6258
6237
 
6259
6238
  //#endregion
@@ -6320,11 +6299,11 @@ const ReportConfigSerializer = { serialize: (config) => {
6320
6299
  //#endregion
6321
6300
  //#region src/core/application/report/captureReport.ts
6322
6301
  async function captureReport({ container }) {
6323
- const { reports } = await container.reportConfigurator.getReports();
6324
- return {
6325
- configText: ReportConfigSerializer.serialize({ reports }),
6326
- hasExistingConfig: (await container.reportStorage.get()).exists
6327
- };
6302
+ return captureFromConfig({
6303
+ fetchRemote: () => container.reportConfigurator.getReports(),
6304
+ serialize: ({ reports }) => ReportConfigSerializer.serialize({ reports }),
6305
+ getStorage: () => container.reportStorage.get()
6306
+ });
6328
6307
  }
6329
6308
 
6330
6309
  //#endregion
@@ -6404,11 +6383,11 @@ const ViewConfigSerializer = { serialize: (config) => {
6404
6383
  //#endregion
6405
6384
  //#region src/core/application/view/captureView.ts
6406
6385
  async function captureView({ container }) {
6407
- const { views } = await container.viewConfigurator.getViews();
6408
- return {
6409
- configText: ViewConfigSerializer.serialize({ views }),
6410
- hasExistingConfig: (await container.viewStorage.get()).exists
6411
- };
6386
+ return captureFromConfig({
6387
+ fetchRemote: () => container.viewConfigurator.getViews(),
6388
+ serialize: ({ views }) => ViewConfigSerializer.serialize({ views }),
6389
+ getStorage: () => container.viewStorage.get()
6390
+ });
6412
6391
  }
6413
6392
 
6414
6393
  //#endregion
@@ -6607,12 +6586,11 @@ function generateProjectConfig(input) {
6607
6586
  files: buildAppFilePaths(name, input.baseDir)
6608
6587
  };
6609
6588
  }
6610
- const config = {
6589
+ return stringify({
6611
6590
  domain: input.domain,
6591
+ ...input.guestSpaceId !== void 0 ? { guestSpaceId: input.guestSpaceId } : {},
6612
6592
  apps
6613
- };
6614
- if (input.guestSpaceId !== void 0) config.guestSpaceId = input.guestSpaceId;
6615
- return stringify(config, { lineWidth: 0 });
6593
+ }, { lineWidth: 0 });
6616
6594
  }
6617
6595
 
6618
6596
  //#endregion
@@ -6872,6 +6850,15 @@ function parseNotificationConfigText(rawText) {
6872
6850
 
6873
6851
  //#endregion
6874
6852
  //#region src/core/application/notification/applyNotification.ts
6853
+ /**
6854
+ * Apply notification settings to kintone.
6855
+ *
6856
+ * Each notification section (general, perRecord, reminder) is updated
6857
+ * independently via separate API calls. If one section fails after others
6858
+ * have already been applied, the app will be in a partially-updated state.
6859
+ * This is a kintone API limitation — there is no transactional update
6860
+ * across notification types. Re-running the command is safe and idempotent.
6861
+ */
6875
6862
  async function applyNotification({ container }) {
6876
6863
  const result = await container.notificationStorage.get();
6877
6864
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Notification config file not found");
@@ -7520,85 +7507,77 @@ const VALID_ENTITY_TYPES$1 = new Set([
7520
7507
  const VALID_ACTION_TYPES = new Set(["PRIMARY", "SECONDARY"]);
7521
7508
  function parseProcessEntity(raw, index) {
7522
7509
  if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Entity at index ${index} must be an object`);
7523
- const obj = raw;
7524
- 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`);
7525
- const result = { type: obj.type };
7526
- const withCode = obj.code !== void 0 && obj.code !== null ? {
7510
+ const result = { type: parseEnum(raw.type, VALID_ENTITY_TYPES$1, ProcessManagementErrorCode.PmInvalidEntityType, `Entity at index ${index} has invalid type: ${String(raw.type)}. Must be USER, GROUP, ORGANIZATION, FIELD_ENTITY, CREATOR, or CUSTOM_FIELD`) };
7511
+ const withCode = raw.code !== void 0 && raw.code !== null ? {
7527
7512
  ...result,
7528
- code: String(obj.code)
7513
+ code: String(raw.code)
7529
7514
  } : result;
7530
- if (obj.includeSubs !== void 0 && obj.includeSubs !== null) return {
7515
+ if (raw.includeSubs !== void 0 && raw.includeSubs !== null) return {
7531
7516
  ...withCode,
7532
- includeSubs: Boolean(obj.includeSubs)
7517
+ includeSubs: parseStrictBoolean(raw.includeSubs, "includeSubs", `Entity at index ${index}`, ProcessManagementErrorCode.PmInvalidBooleanField)
7533
7518
  };
7534
7519
  return withCode;
7535
7520
  }
7536
7521
  function parseAssignee(raw, stateName) {
7537
7522
  if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Assignee for state "${stateName}" must be an object`);
7538
- const obj = raw;
7539
- 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`);
7540
- if (!Array.isArray(obj.entities)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Assignee for state "${stateName}" must have an "entities" array`);
7541
- const entities = obj.entities.map((item, i) => parseProcessEntity(item, i));
7523
+ if (!Array.isArray(raw.entities)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Assignee for state "${stateName}" must have an "entities" array`);
7542
7524
  return {
7543
- type: obj.type,
7544
- entities
7525
+ type: parseEnum(raw.type, VALID_ASSIGNEE_TYPES, ProcessManagementErrorCode.PmInvalidAssigneeType, `Assignee for state "${stateName}" has invalid type: ${String(raw.type)}. Must be ONE, ALL, or ANY`),
7526
+ entities: raw.entities.map((item, i) => parseProcessEntity(item, i))
7545
7527
  };
7546
7528
  }
7547
7529
  function parseState(raw, stateName) {
7548
7530
  if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must be an object`);
7549
- const obj = raw;
7550
- if (typeof obj.index !== "number") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must have a numeric "index" property`);
7551
- if (obj.assignee === void 0 || obj.assignee === null) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must have an "assignee" property`);
7552
- const assignee = parseAssignee(obj.assignee, stateName);
7531
+ if (typeof raw.index !== "number") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must have a numeric "index" property`);
7532
+ if (raw.assignee === void 0 || raw.assignee === null) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `State "${stateName}" must have an "assignee" property`);
7533
+ const assignee = parseAssignee(raw.assignee, stateName);
7553
7534
  return {
7554
- index: obj.index,
7535
+ index: raw.index,
7555
7536
  assignee
7556
7537
  };
7557
7538
  }
7558
7539
  function parseExecutableUser(raw, actionIndex) {
7559
7540
  if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${actionIndex}: executableUser must be an object`);
7560
- const obj = raw;
7561
- if (!Array.isArray(obj.entities)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${actionIndex}: executableUser must have an "entities" array`);
7562
- return { entities: obj.entities.map((item, i) => parseProcessEntity(item, i)) };
7541
+ if (!Array.isArray(raw.entities)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${actionIndex}: executableUser must have an "entities" array`);
7542
+ return { entities: raw.entities.map((item, i) => parseProcessEntity(item, i)) };
7563
7543
  }
7564
7544
  function parseAction(raw, index) {
7565
7545
  if (!isRecord(raw)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must be an object`);
7566
- const obj = raw;
7567
- if (typeof obj.name !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "name" string property`);
7568
- if (typeof obj.from !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "from" string property`);
7569
- if (typeof obj.to !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "to" string property`);
7570
- if (obj.type !== void 0 && obj.type !== null && (typeof obj.type !== "string" || !VALID_ACTION_TYPES.has(obj.type))) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} has invalid type: ${String(obj.type)}. Must be PRIMARY or SECONDARY`);
7571
- const actionType = typeof obj.type === "string" && VALID_ACTION_TYPES.has(obj.type) ? obj.type : "PRIMARY";
7546
+ if (typeof raw.name !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "name" string property`);
7547
+ if (typeof raw.from !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "from" string property`);
7548
+ if (typeof raw.to !== "string") throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} must have a "to" string property`);
7549
+ const actionType = raw.type === void 0 || raw.type === null ? "PRIMARY" : parseEnum(raw.type, VALID_ACTION_TYPES, ProcessManagementErrorCode.PmInvalidConfigStructure, `Action at index ${index} has invalid type: ${String(raw.type)}. Must be PRIMARY or SECONDARY`);
7572
7550
  const result = {
7573
- name: obj.name,
7574
- from: obj.from,
7575
- to: obj.to,
7576
- filterCond: typeof obj.filterCond === "string" ? obj.filterCond : "",
7551
+ name: raw.name,
7552
+ from: raw.from,
7553
+ to: raw.to,
7554
+ filterCond: typeof raw.filterCond === "string" ? raw.filterCond : "",
7577
7555
  type: actionType
7578
7556
  };
7579
- if (actionType === "SECONDARY" && obj.executableUser !== void 0 && obj.executableUser !== null) return {
7557
+ if (actionType === "SECONDARY" && raw.executableUser !== void 0 && raw.executableUser !== null) return {
7580
7558
  ...result,
7581
- executableUser: parseExecutableUser(obj.executableUser, index)
7559
+ executableUser: parseExecutableUser(raw.executableUser, index)
7582
7560
  };
7583
7561
  return result;
7584
7562
  }
7585
7563
  const ProcessManagementConfigParser = { parse: (rawText) => {
7586
- if (rawText.trim().length === 0) throw new BusinessRuleError(ProcessManagementErrorCode.PmEmptyConfigText, "Process management config text is empty");
7587
- let parsed;
7588
- try {
7589
- parsed = parse(rawText);
7590
- } catch (error) {
7591
- throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
7592
- }
7593
- if (!isRecord(parsed)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config must be a YAML object");
7594
- const obj = parsed;
7595
- const enable = obj.enable !== void 0 && obj.enable !== null ? Boolean(obj.enable) : false;
7596
- 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)");
7597
- const rawStates = isRecord(obj.states) ? obj.states : {};
7564
+ const parsed = parseYamlConfig(rawText, {
7565
+ emptyConfigText: ProcessManagementErrorCode.PmEmptyConfigText,
7566
+ invalidConfigYaml: ProcessManagementErrorCode.PmInvalidConfigYaml,
7567
+ invalidConfigStructure: ProcessManagementErrorCode.PmInvalidConfigStructure
7568
+ }, "Process management");
7569
+ const enable = parsed.enable !== void 0 && parsed.enable !== null ? parseStrictBoolean(parsed.enable, "enable", "Config", ProcessManagementErrorCode.PmInvalidBooleanField) : false;
7570
+ if (parsed.states !== void 0 && parsed.states !== null && !isRecord(parsed.states)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config \"states\" must be an object (map of state name to state definition)");
7571
+ const rawStates = isRecord(parsed.states) ? parsed.states : {};
7598
7572
  const states = {};
7599
7573
  for (const [name, value] of Object.entries(rawStates)) states[name] = parseState(value, name);
7600
- if (!Array.isArray(obj.actions) && obj.actions !== void 0) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config \"actions\" must be an array");
7601
- const actions = (obj.actions ?? []).map((item, i) => parseAction(item, i));
7574
+ if (!Array.isArray(parsed.actions) && parsed.actions !== void 0) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidConfigStructure, "Config \"actions\" must be an array");
7575
+ const actions = (Array.isArray(parsed.actions) ? parsed.actions : []).map((item, i) => parseAction(item, i));
7576
+ const actionNames = /* @__PURE__ */ new Set();
7577
+ for (const action of actions) {
7578
+ if (actionNames.has(action.name)) throw new BusinessRuleError(ProcessManagementErrorCode.PmDuplicateActionName, `Duplicate action name: "${action.name}"`);
7579
+ actionNames.add(action.name);
7580
+ }
7602
7581
  const stateNames = new Set(Object.keys(states));
7603
7582
  for (const action of actions) {
7604
7583
  if (!stateNames.has(action.from)) throw new BusinessRuleError(ProcessManagementErrorCode.PmInvalidActionReference, `Action "${action.name}" references unknown "from" state: "${action.from}"`);
@@ -7751,7 +7730,7 @@ var capture_default$6 = define({
7751
7730
  //#region src/core/domain/processManagement/services/diffDetector.ts
7752
7731
  function isEntityEqual(a, b) {
7753
7732
  if (a.type !== b.type) return false;
7754
- if (a.code !== b.code) return false;
7733
+ if ((a.code ?? "") !== (b.code ?? "")) return false;
7755
7734
  if (Boolean(a.includeSubs) !== Boolean(b.includeSubs)) return false;
7756
7735
  return true;
7757
7736
  }
@@ -7889,56 +7868,51 @@ const VALID_ENTITY_TYPES = new Set([
7889
7868
  "ORGANIZATION",
7890
7869
  "FIELD_ENTITY"
7891
7870
  ]);
7892
- function parseBooleanField(value, fieldName, context) {
7893
- if (value === void 0) return false;
7894
- if (typeof value === "boolean") return value;
7895
- throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidPermissionValue, `${context} has invalid "${fieldName}" value: ${String(value)}. Must be a boolean`);
7896
- }
7897
7871
  function parseEntity(raw, index) {
7898
- if (!isRecord(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Entity at index ${index} must be an object`);
7899
- const obj = raw;
7900
- 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`);
7901
- 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`);
7902
- return {
7903
- type: obj.type,
7904
- code: obj.code
7905
- };
7872
+ return parseEntityBase(raw, index, VALID_ENTITY_TYPES, {
7873
+ invalidStructure: RecordPermissionErrorCode.RpInvalidConfigStructure,
7874
+ invalidType: RecordPermissionErrorCode.RpInvalidEntityType,
7875
+ emptyCode: RecordPermissionErrorCode.RpEmptyEntityCode
7876
+ });
7906
7877
  }
7907
7878
  function parseRecordRightEntity(raw, index) {
7908
7879
  if (!isRecord(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right entity at index ${index} must be an object`);
7909
- const obj = raw;
7910
- const entity = parseEntity(obj.entity, index);
7880
+ const entity = parseEntity(raw.entity, index);
7911
7881
  const context = `Record right entity at index ${index}`;
7912
7882
  return {
7913
7883
  entity,
7914
- viewable: parseBooleanField(obj.viewable, "viewable", context),
7915
- editable: parseBooleanField(obj.editable, "editable", context),
7916
- deletable: parseBooleanField(obj.deletable, "deletable", context),
7917
- includeSubs: parseBooleanField(obj.includeSubs, "includeSubs", context)
7884
+ viewable: parseStrictBoolean(raw.viewable, "viewable", context, RecordPermissionErrorCode.RpInvalidPermissionValue, false),
7885
+ editable: parseStrictBoolean(raw.editable, "editable", context, RecordPermissionErrorCode.RpInvalidPermissionValue, false),
7886
+ deletable: parseStrictBoolean(raw.deletable, "deletable", context, RecordPermissionErrorCode.RpInvalidPermissionValue, false),
7887
+ includeSubs: parseStrictBoolean(raw.includeSubs, "includeSubs", context, RecordPermissionErrorCode.RpInvalidPermissionValue, false)
7918
7888
  };
7919
7889
  }
7920
7890
  function parseRecordRight(raw, index) {
7921
7891
  if (!isRecord(raw)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right at index ${index} must be an object`);
7922
- const obj = raw;
7923
- if (!Array.isArray(obj.entities)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right at index ${index} must have an "entities" array`);
7924
- const entities = obj.entities.map((item, i) => parseRecordRightEntity(item, i));
7892
+ if (!Array.isArray(raw.entities)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, `Record right at index ${index} must have an "entities" array`);
7893
+ const entities = raw.entities.map((item, i) => parseRecordRightEntity(item, i));
7925
7894
  return {
7926
- filterCond: typeof obj.filterCond === "string" ? obj.filterCond : "",
7895
+ filterCond: typeof raw.filterCond === "string" ? raw.filterCond : "",
7927
7896
  entities
7928
7897
  };
7929
7898
  }
7930
7899
  const RecordPermissionConfigParser = { parse: (rawText) => {
7931
- if (rawText.trim().length === 0) throw new BusinessRuleError(RecordPermissionErrorCode.RpEmptyConfigText, "Record permission config text is empty");
7932
- let parsed;
7933
- try {
7934
- parsed = parse(rawText);
7935
- } catch (error) {
7936
- throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
7900
+ const parsed = parseYamlConfig(rawText, {
7901
+ emptyConfigText: RecordPermissionErrorCode.RpEmptyConfigText,
7902
+ invalidConfigYaml: RecordPermissionErrorCode.RpInvalidConfigYaml,
7903
+ invalidConfigStructure: RecordPermissionErrorCode.RpInvalidConfigStructure
7904
+ }, "Record permission");
7905
+ if (!Array.isArray(parsed.rights)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, "Config must have a \"rights\" array");
7906
+ const rights = parsed.rights.map((item, i) => parseRecordRight(item, i));
7907
+ for (const right of rights) {
7908
+ const seenKeys = /* @__PURE__ */ new Set();
7909
+ for (const re of right.entities) {
7910
+ const key = `${re.entity.type}:${re.entity.code}`;
7911
+ if (seenKeys.has(key)) throw new BusinessRuleError(RecordPermissionErrorCode.RpDuplicateEntity, `Duplicate entity in filterCond "${right.filterCond}": ${re.entity.type} ${re.entity.code}`);
7912
+ seenKeys.add(key);
7913
+ }
7937
7914
  }
7938
- if (!isRecord(parsed)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, "Config must be a YAML object");
7939
- const obj = parsed;
7940
- if (!Array.isArray(obj.rights)) throw new BusinessRuleError(RecordPermissionErrorCode.RpInvalidConfigStructure, "Config must have a \"rights\" array");
7941
- return { rights: obj.rights.map((item, i) => parseRecordRight(item, i)) };
7915
+ return { rights };
7942
7916
  } };
7943
7917
 
7944
7918
  //#endregion
@@ -7950,13 +7924,17 @@ function parseRecordPermissionConfigText(rawText) {
7950
7924
  //#endregion
7951
7925
  //#region src/core/application/recordPermission/applyRecordPermission.ts
7952
7926
  async function applyRecordPermission({ container }) {
7953
- const result = await container.recordPermissionStorage.get();
7954
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Record permission config file not found");
7955
- const config = parseRecordPermissionConfigText(result.content);
7956
- const current = await container.recordPermissionConfigurator.getRecordPermissions();
7957
- await container.recordPermissionConfigurator.updateRecordPermissions({
7958
- rights: config.rights,
7959
- revision: current.revision
7927
+ await applyFromConfig({
7928
+ getStorage: () => container.recordPermissionStorage.get(),
7929
+ parseConfig: parseRecordPermissionConfigText,
7930
+ fetchRemote: () => container.recordPermissionConfigurator.getRecordPermissions(),
7931
+ update: async (config, current) => {
7932
+ await container.recordPermissionConfigurator.updateRecordPermissions({
7933
+ rights: config.rights,
7934
+ revision: current.revision
7935
+ });
7936
+ },
7937
+ notFoundMessage: "Record permission config file not found"
7960
7938
  });
7961
7939
  }
7962
7940
 
@@ -8073,7 +8051,7 @@ var capture_default$5 = define({
8073
8051
 
8074
8052
  //#endregion
8075
8053
  //#region src/core/domain/recordPermission/services/diffDetector.ts
8076
- function areRightsEqual(a, b) {
8054
+ function isRightEqual(a, b) {
8077
8055
  return deepEqual(a.entities.map((e) => ({
8078
8056
  type: e.entity.type,
8079
8057
  code: e.entity.code,
@@ -8122,7 +8100,7 @@ const RecordPermissionDiffDetector = { detect: (local, remote) => {
8122
8100
  details: describeRight(remoteRight)
8123
8101
  });
8124
8102
  else if (localRight && remoteRight) {
8125
- if (!areRightsEqual(localRight, remoteRight)) {
8103
+ if (!isRightEqual(localRight, remoteRight)) {
8126
8104
  const diffs = [];
8127
8105
  if (localRight.entities.length !== remoteRight.entities.length) diffs.push(`entities: ${remoteRight.entities.length} -> ${localRight.entities.length}`);
8128
8106
  else diffs.push("entities changed");
@@ -8332,13 +8310,17 @@ function parseReportConfigText(rawText) {
8332
8310
  //#endregion
8333
8311
  //#region src/core/application/report/applyReport.ts
8334
8312
  async function applyReport({ container }) {
8335
- const result = await container.reportStorage.get();
8336
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Report config file not found");
8337
- const config = parseReportConfigText(result.content);
8338
- const current = await container.reportConfigurator.getReports();
8339
- await container.reportConfigurator.updateReports({
8340
- reports: config.reports,
8341
- revision: current.revision
8313
+ await applyFromConfig({
8314
+ getStorage: () => container.reportStorage.get(),
8315
+ parseConfig: parseReportConfigText,
8316
+ fetchRemote: () => container.reportConfigurator.getReports(),
8317
+ update: async (config, current) => {
8318
+ await container.reportConfigurator.updateReports({
8319
+ reports: config.reports,
8320
+ revision: current.revision
8321
+ });
8322
+ },
8323
+ notFoundMessage: "Report config file not found"
8342
8324
  });
8343
8325
  }
8344
8326
 
@@ -9132,12 +9114,25 @@ function parseSchemaText(rawText) {
9132
9114
 
9133
9115
  //#endregion
9134
9116
  //#region src/core/application/formSchema/detectDiff.ts
9117
+ function fieldPropertiesToDto(field) {
9118
+ if (field.type === "SUBTABLE") {
9119
+ const innerFields = {};
9120
+ for (const [code, def] of field.properties.fields) innerFields[code] = {
9121
+ code: def.code,
9122
+ type: def.type,
9123
+ label: def.label,
9124
+ properties: def.properties
9125
+ };
9126
+ return { fields: innerFields };
9127
+ }
9128
+ return { ...field.properties };
9129
+ }
9135
9130
  function toFieldDto(field) {
9136
9131
  return {
9137
9132
  code: field.code,
9138
9133
  type: field.type,
9139
9134
  label: field.label,
9140
- properties: field.properties
9135
+ properties: fieldPropertiesToDto(field)
9141
9136
  };
9142
9137
  }
9143
9138
  async function detectDiff({ container }) {
@@ -9265,11 +9260,7 @@ var LocalFileDumpStorage = class {
9265
9260
  //#region src/core/application/container/dumpCli.ts
9266
9261
  function createDumpCliContainer(config) {
9267
9262
  return {
9268
- formDumpReader: new KintoneFormDumpReader(new KintoneRestAPIClient({
9269
- baseUrl: config.baseUrl,
9270
- auth: buildKintoneAuth(config.auth),
9271
- guestSpaceId: config.guestSpaceId
9272
- }), config.appId),
9263
+ formDumpReader: new KintoneFormDumpReader(config.client ?? createKintoneClient(config), config.appId),
9273
9264
  dumpStorage: new LocalFileDumpStorage(config.filePrefix)
9274
9265
  };
9275
9266
  }
@@ -9341,6 +9332,19 @@ var dump_default = define({
9341
9332
  }
9342
9333
  });
9343
9334
 
9335
+ //#endregion
9336
+ //#region src/core/domain/formSchema/services/subtableFieldSplitter.ts
9337
+ function splitSubtableInnerFields(desired, current) {
9338
+ const newInnerFields = /* @__PURE__ */ new Map();
9339
+ const existingInnerFields = /* @__PURE__ */ new Map();
9340
+ for (const [code, def] of desired.properties.fields) if (current.properties.fields.has(code)) existingInnerFields.set(code, def);
9341
+ else newInnerFields.set(code, def);
9342
+ return {
9343
+ newInnerFields,
9344
+ existingInnerFields
9345
+ };
9346
+ }
9347
+
9344
9348
  //#endregion
9345
9349
  //#region src/core/domain/formSchema/services/schemaValidator.ts
9346
9350
  const SELECTION_TYPES = new Set([
@@ -9513,10 +9517,7 @@ async function executeMigration({ container }) {
9513
9517
  const after = entry.after;
9514
9518
  const before = entry.before;
9515
9519
  if (after.type === "SUBTABLE" && before !== void 0 && before.type === "SUBTABLE") {
9516
- const newInnerFields = /* @__PURE__ */ new Map();
9517
- const existingInnerFields = /* @__PURE__ */ new Map();
9518
- for (const [code, def] of after.properties.fields) if (before.properties.fields.has(code)) existingInnerFields.set(code, def);
9519
- else newInnerFields.set(code, def);
9520
+ const { newInnerFields, existingInnerFields } = splitSubtableInnerFields(after, before);
9520
9521
  if (newInnerFields.size > 0) fieldsToAdd.push({
9521
9522
  ...after,
9522
9523
  properties: { fields: newInnerFields }
@@ -9651,10 +9652,7 @@ async function forceOverrideForm({ container }) {
9651
9652
  if (currentFields.has(fieldCode)) if (schemaDef.type === "SUBTABLE") {
9652
9653
  const currentDef = currentFields.get(fieldCode);
9653
9654
  if (currentDef !== void 0 && currentDef.type === "SUBTABLE") {
9654
- const newInnerFields = /* @__PURE__ */ new Map();
9655
- const existingInnerFields = /* @__PURE__ */ new Map();
9656
- for (const [code, def] of schemaDef.properties.fields) if (currentDef.properties.fields.has(code)) existingInnerFields.set(code, def);
9657
- else newInnerFields.set(code, def);
9655
+ const { newInnerFields, existingInnerFields } = splitSubtableInnerFields(schemaDef, currentDef);
9658
9656
  if (newInnerFields.size > 0) toAdd.push({
9659
9657
  ...schemaDef,
9660
9658
  properties: { fields: newInnerFields }
@@ -9821,9 +9819,9 @@ async function validateSchema({ container }) {
9821
9819
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Schema file not found");
9822
9820
  let schema;
9823
9821
  try {
9824
- schema = SchemaParser.parse(result.content);
9822
+ schema = parseSchemaText(result.content);
9825
9823
  } catch (error) {
9826
- if (isBusinessRuleError(error)) return {
9824
+ if (isValidationError(error)) return {
9827
9825
  parseError: error.message,
9828
9826
  fieldCount: 0
9829
9827
  };
@@ -9932,6 +9930,50 @@ var schema_default = define({
9932
9930
  run: () => {}
9933
9931
  });
9934
9932
 
9933
+ //#endregion
9934
+ //#region src/core/domain/seedData/services/upsertPlanner.ts
9935
+ function recordsEqual(seed, existing, keyField) {
9936
+ const seedKeys = Object.keys(seed).filter((k) => k !== keyField);
9937
+ for (const key of seedKeys) {
9938
+ const seedValue = seed[key];
9939
+ const existingValue = existing[key];
9940
+ if (seedValue === void 0 && existingValue === void 0) continue;
9941
+ if (seedValue === void 0 || existingValue === void 0) return false;
9942
+ if (!deepEqual(seedValue, existingValue)) return false;
9943
+ }
9944
+ return true;
9945
+ }
9946
+ const UpsertPlanner = { plan: (key, seedRecords, existingRecords) => {
9947
+ const keyField = key;
9948
+ const existingMap = /* @__PURE__ */ new Map();
9949
+ for (const { id, record } of existingRecords) {
9950
+ const keyValue = record[keyField];
9951
+ if (typeof keyValue === "string") existingMap.set(keyValue, {
9952
+ id,
9953
+ record
9954
+ });
9955
+ }
9956
+ const toAdd = [];
9957
+ const toUpdate = [];
9958
+ let unchanged = 0;
9959
+ for (const seedRecord of seedRecords) {
9960
+ const keyValue = seedRecord[keyField];
9961
+ if (typeof keyValue !== "string") throw new BusinessRuleError(SeedDataErrorCode.SdInvalidKeyFieldValue, `Key field "${keyField}" value must be a string, got ${typeof keyValue}`);
9962
+ const existing = existingMap.get(keyValue);
9963
+ if (existing === void 0) toAdd.push(seedRecord);
9964
+ else if (recordsEqual(seedRecord, existing.record, keyField)) unchanged++;
9965
+ else toUpdate.push({
9966
+ id: existing.id,
9967
+ record: seedRecord
9968
+ });
9969
+ }
9970
+ return {
9971
+ toAdd,
9972
+ toUpdate,
9973
+ unchanged
9974
+ };
9975
+ } };
9976
+
9935
9977
  //#endregion
9936
9978
  //#region src/core/domain/seedData/services/seedParser.ts
9937
9979
  function normalizeValue(value) {
@@ -9993,55 +10035,17 @@ const SeedParser = { parse: (rawText) => {
9993
10035
  } };
9994
10036
 
9995
10037
  //#endregion
9996
- //#region src/core/domain/seedData/services/upsertPlanner.ts
9997
- function recordsEqual(seed, existing, keyField) {
9998
- const seedKeys = Object.keys(seed).filter((k) => k !== keyField);
9999
- for (const key of seedKeys) {
10000
- const seedValue = seed[key];
10001
- const existingValue = existing[key];
10002
- if (seedValue === void 0 && existingValue === void 0) continue;
10003
- if (seedValue === void 0 || existingValue === void 0) return false;
10004
- if (!deepEqual(seedValue, existingValue)) return false;
10005
- }
10006
- return true;
10038
+ //#region src/core/application/seedData/parseConfig.ts
10039
+ function parseSeedText(rawText) {
10040
+ return wrapBusinessRuleError(() => SeedParser.parse(rawText));
10007
10041
  }
10008
- const UpsertPlanner = { plan: (key, seedRecords, existingRecords) => {
10009
- const keyField = key;
10010
- const existingMap = /* @__PURE__ */ new Map();
10011
- for (const { id, record } of existingRecords) {
10012
- const keyValue = record[keyField];
10013
- if (typeof keyValue === "string") existingMap.set(keyValue, {
10014
- id,
10015
- record
10016
- });
10017
- }
10018
- const toAdd = [];
10019
- const toUpdate = [];
10020
- let unchanged = 0;
10021
- for (const seedRecord of seedRecords) {
10022
- const keyValue = seedRecord[keyField];
10023
- if (typeof keyValue !== "string") throw new BusinessRuleError(SeedDataErrorCode.SdInvalidKeyFieldValue, `Key field "${keyField}" value must be a string, got ${typeof keyValue}`);
10024
- const existing = existingMap.get(keyValue);
10025
- if (existing === void 0) toAdd.push(seedRecord);
10026
- else if (recordsEqual(seedRecord, existing.record, keyField)) unchanged++;
10027
- else toUpdate.push({
10028
- id: existing.id,
10029
- record: seedRecord
10030
- });
10031
- }
10032
- return {
10033
- toAdd,
10034
- toUpdate,
10035
- unchanged
10036
- };
10037
- } };
10038
10042
 
10039
10043
  //#endregion
10040
10044
  //#region src/core/application/seedData/upsertSeed.ts
10041
10045
  async function upsertSeed({ container, input }) {
10042
10046
  const result = await container.seedStorage.get();
10043
10047
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Seed file not found");
10044
- const seedData = SeedParser.parse(result.content);
10048
+ const seedData = parseSeedText(result.content);
10045
10049
  if (input.clean) {
10046
10050
  const { deletedCount } = await container.recordManager.deleteAllRecords();
10047
10051
  if (seedData.records.length > 0) await container.recordManager.addRecords(seedData.records);
@@ -10249,6 +10253,11 @@ var seed_default = define({
10249
10253
 
10250
10254
  //#endregion
10251
10255
  //#region src/core/domain/generalSettings/services/configParser.ts
10256
+ function parseOptionalBoolean(parsed, fieldName) {
10257
+ const value = parsed[fieldName];
10258
+ if (value === void 0 || value === null) return void 0;
10259
+ return parseStrictBoolean(value, fieldName, "Config", GeneralSettingsErrorCode.GsInvalidBooleanField);
10260
+ }
10252
10261
  const VALID_THEMES = new Set([
10253
10262
  "WHITE",
10254
10263
  "RED",
@@ -10270,86 +10279,70 @@ const VALID_ROUNDING_MODES = new Set([
10270
10279
  ]);
10271
10280
  function parseIcon(raw) {
10272
10281
  if (!isRecord(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "icon must be an object with \"type\" and \"key\" properties");
10273
- const obj = raw;
10274
- 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)}`);
10275
- if (typeof obj.key !== "string" || obj.key.length === 0) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "icon must have a non-empty \"key\" property");
10282
+ if (typeof raw.key !== "string" || raw.key.length === 0) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "icon must have a non-empty \"key\" property");
10276
10283
  return {
10277
- type: obj.type,
10278
- key: obj.key
10284
+ type: parseEnum(raw.type, VALID_ICON_TYPES, GeneralSettingsErrorCode.GsInvalidIconType, `icon.type must be PRESET or FILE, got: ${String(raw.type)}`),
10285
+ key: raw.key
10279
10286
  };
10280
10287
  }
10281
10288
  function parseTitleField(raw) {
10282
10289
  if (!isRecord(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "titleField must be an object with \"selectionMode\" property");
10283
- const obj = raw;
10284
- 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)}`);
10285
- const result = { selectionMode: obj.selectionMode };
10286
- if (obj.code !== void 0 && obj.code !== null) {
10287
- if (typeof obj.code !== "string") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "titleField.code must be a string");
10290
+ const result = { selectionMode: parseEnum(raw.selectionMode, VALID_SELECTION_MODES, GeneralSettingsErrorCode.GsInvalidConfigStructure, `titleField.selectionMode must be AUTO or MANUAL, got: ${String(raw.selectionMode)}`) };
10291
+ if (raw.code !== void 0 && raw.code !== null) {
10292
+ if (typeof raw.code !== "string") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "titleField.code must be a string");
10288
10293
  return {
10289
10294
  ...result,
10290
- code: obj.code
10295
+ code: raw.code
10291
10296
  };
10292
10297
  }
10293
10298
  return result;
10294
10299
  }
10295
10300
  function parseNumberPrecision(raw) {
10296
10301
  if (!isRecord(raw)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision must be an object");
10297
- const obj = raw;
10298
- if (typeof obj.digits !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision.digits must be a number");
10299
- if (typeof obj.decimalPlaces !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision.decimalPlaces must be a number");
10300
- if (typeof obj.roundingMode !== "string" || !VALID_ROUNDING_MODES.has(obj.roundingMode)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, `numberPrecision.roundingMode must be HALF_EVEN, UP, or DOWN, got: ${String(obj.roundingMode)}`);
10302
+ if (typeof raw.digits !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision.digits must be a number");
10303
+ if (!Number.isInteger(raw.digits) || raw.digits < 0) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidNumberPrecision, `numberPrecision.digits must be a non-negative integer, got: ${raw.digits}`);
10304
+ if (typeof raw.decimalPlaces !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "numberPrecision.decimalPlaces must be a number");
10305
+ if (!Number.isInteger(raw.decimalPlaces) || raw.decimalPlaces < 0) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidNumberPrecision, `numberPrecision.decimalPlaces must be a non-negative integer, got: ${raw.decimalPlaces}`);
10301
10306
  return {
10302
- digits: obj.digits,
10303
- decimalPlaces: obj.decimalPlaces,
10304
- roundingMode: obj.roundingMode
10307
+ digits: raw.digits,
10308
+ decimalPlaces: raw.decimalPlaces,
10309
+ roundingMode: parseEnum(raw.roundingMode, VALID_ROUNDING_MODES, GeneralSettingsErrorCode.GsInvalidConfigStructure, `numberPrecision.roundingMode must be HALF_EVEN, UP, or DOWN, got: ${String(raw.roundingMode)}`)
10305
10310
  };
10306
10311
  }
10307
10312
  const GeneralSettingsConfigParser = { parse: (rawText) => {
10308
- if (rawText.trim().length === 0) throw new BusinessRuleError(GeneralSettingsErrorCode.GsEmptyConfigText, "General settings config text is empty");
10309
- let parsed;
10310
- try {
10311
- parsed = parse(rawText);
10312
- } catch (error) {
10313
- throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigYaml, `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
10314
- }
10315
- if (!isRecord(parsed)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "Config must be a YAML object");
10316
- const obj = parsed;
10313
+ const parsed = parseYamlConfig(rawText, {
10314
+ emptyConfigText: GeneralSettingsErrorCode.GsEmptyConfigText,
10315
+ invalidConfigYaml: GeneralSettingsErrorCode.GsInvalidConfigYaml,
10316
+ invalidConfigStructure: GeneralSettingsErrorCode.GsInvalidConfigStructure
10317
+ }, "General settings");
10317
10318
  let name;
10318
- if (obj.name !== void 0 && obj.name !== null) {
10319
- if (typeof obj.name !== "string") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "name must be a string");
10320
- name = obj.name;
10319
+ if (parsed.name !== void 0 && parsed.name !== null) {
10320
+ if (typeof parsed.name !== "string") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "name must be a string");
10321
+ name = parsed.name;
10321
10322
  }
10322
10323
  let description;
10323
- if (obj.description !== void 0 && obj.description !== null) {
10324
- if (typeof obj.description !== "string") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "description must be a string");
10325
- description = obj.description;
10324
+ if (parsed.description !== void 0 && parsed.description !== null) {
10325
+ if (typeof parsed.description !== "string") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "description must be a string");
10326
+ description = parsed.description;
10326
10327
  }
10327
10328
  let icon;
10328
- if (obj.icon !== void 0 && obj.icon !== null) icon = parseIcon(obj.icon);
10329
+ if (parsed.icon !== void 0 && parsed.icon !== null) icon = parseIcon(parsed.icon);
10329
10330
  let theme;
10330
- if (obj.theme !== void 0 && obj.theme !== null) {
10331
- if (typeof obj.theme !== "string" || !VALID_THEMES.has(obj.theme)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidTheme, `theme must be WHITE, RED, GREEN, BLUE, YELLOW, BLACK, CLIPBOARD, BINDER, PENCIL, or CLIPS, got: ${String(obj.theme)}`);
10332
- theme = obj.theme;
10333
- }
10331
+ if (parsed.theme !== void 0 && parsed.theme !== null) theme = parseEnum(parsed.theme, VALID_THEMES, GeneralSettingsErrorCode.GsInvalidTheme, `theme must be WHITE, RED, GREEN, BLUE, YELLOW, BLACK, CLIPBOARD, BINDER, PENCIL, or CLIPS, got: ${String(parsed.theme)}`);
10334
10332
  let titleField;
10335
- if (obj.titleField !== void 0 && obj.titleField !== null) titleField = parseTitleField(obj.titleField);
10336
- let enableThumbnails;
10337
- if (obj.enableThumbnails !== void 0 && obj.enableThumbnails !== null) enableThumbnails = Boolean(obj.enableThumbnails);
10338
- let enableBulkDeletion;
10339
- if (obj.enableBulkDeletion !== void 0 && obj.enableBulkDeletion !== null) enableBulkDeletion = Boolean(obj.enableBulkDeletion);
10340
- let enableComments;
10341
- if (obj.enableComments !== void 0 && obj.enableComments !== null) enableComments = Boolean(obj.enableComments);
10342
- let enableDuplicateRecord;
10343
- if (obj.enableDuplicateRecord !== void 0 && obj.enableDuplicateRecord !== null) enableDuplicateRecord = Boolean(obj.enableDuplicateRecord);
10344
- let enableInlineRecordEditing;
10345
- if (obj.enableInlineRecordEditing !== void 0 && obj.enableInlineRecordEditing !== null) enableInlineRecordEditing = Boolean(obj.enableInlineRecordEditing);
10333
+ if (parsed.titleField !== void 0 && parsed.titleField !== null) titleField = parseTitleField(parsed.titleField);
10334
+ const enableThumbnails = parseOptionalBoolean(parsed, "enableThumbnails");
10335
+ const enableBulkDeletion = parseOptionalBoolean(parsed, "enableBulkDeletion");
10336
+ const enableComments = parseOptionalBoolean(parsed, "enableComments");
10337
+ const enableDuplicateRecord = parseOptionalBoolean(parsed, "enableDuplicateRecord");
10338
+ const enableInlineRecordEditing = parseOptionalBoolean(parsed, "enableInlineRecordEditing");
10346
10339
  let numberPrecision;
10347
- if (obj.numberPrecision !== void 0 && obj.numberPrecision !== null) numberPrecision = parseNumberPrecision(obj.numberPrecision);
10340
+ if (parsed.numberPrecision !== void 0 && parsed.numberPrecision !== null) numberPrecision = parseNumberPrecision(parsed.numberPrecision);
10348
10341
  let firstMonthOfFiscalYear;
10349
- if (obj.firstMonthOfFiscalYear !== void 0 && obj.firstMonthOfFiscalYear !== null) {
10350
- if (typeof obj.firstMonthOfFiscalYear !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "firstMonthOfFiscalYear must be a number");
10351
- if (obj.firstMonthOfFiscalYear < 1 || obj.firstMonthOfFiscalYear > 12 || !Number.isInteger(obj.firstMonthOfFiscalYear)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, `firstMonthOfFiscalYear must be an integer between 1 and 12, got: ${obj.firstMonthOfFiscalYear}`);
10352
- firstMonthOfFiscalYear = obj.firstMonthOfFiscalYear;
10342
+ if (parsed.firstMonthOfFiscalYear !== void 0 && parsed.firstMonthOfFiscalYear !== null) {
10343
+ if (typeof parsed.firstMonthOfFiscalYear !== "number") throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, "firstMonthOfFiscalYear must be a number");
10344
+ if (parsed.firstMonthOfFiscalYear < 1 || parsed.firstMonthOfFiscalYear > 12 || !Number.isInteger(parsed.firstMonthOfFiscalYear)) throw new BusinessRuleError(GeneralSettingsErrorCode.GsInvalidConfigStructure, `firstMonthOfFiscalYear must be an integer between 1 and 12, got: ${parsed.firstMonthOfFiscalYear}`);
10345
+ firstMonthOfFiscalYear = parsed.firstMonthOfFiscalYear;
10353
10346
  }
10354
10347
  return {
10355
10348
  ...name !== void 0 ? { name } : {},
@@ -10376,13 +10369,17 @@ function parseGeneralSettingsConfigText(rawText) {
10376
10369
  //#endregion
10377
10370
  //#region src/core/application/generalSettings/applyGeneralSettings.ts
10378
10371
  async function applyGeneralSettings({ container }) {
10379
- const result = await container.generalSettingsStorage.get();
10380
- if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "General settings config file not found");
10381
- const config = parseGeneralSettingsConfigText(result.content);
10382
- const current = await container.generalSettingsConfigurator.getGeneralSettings();
10383
- await container.generalSettingsConfigurator.updateGeneralSettings({
10384
- config,
10385
- revision: current.revision
10372
+ await applyFromConfig({
10373
+ getStorage: () => container.generalSettingsStorage.get(),
10374
+ parseConfig: parseGeneralSettingsConfigText,
10375
+ fetchRemote: () => container.generalSettingsConfigurator.getGeneralSettings(),
10376
+ update: async (config, current) => {
10377
+ await container.generalSettingsConfigurator.updateGeneralSettings({
10378
+ config,
10379
+ revision: current.revision
10380
+ });
10381
+ },
10382
+ notFoundMessage: "General settings config file not found"
10386
10383
  });
10387
10384
  }
10388
10385
 
@@ -10531,11 +10528,14 @@ function compareConfigs(local, remote) {
10531
10528
  details: `${rv} -> ${lv}`
10532
10529
  });
10533
10530
  }
10531
+ function formatValue(v) {
10532
+ return v === void 0 ? "(none)" : JSON.stringify(v);
10533
+ }
10534
10534
  function compareDeepEqual(field, l, r) {
10535
10535
  if (!deepEqual(l, r)) entries.push({
10536
10536
  type: "modified",
10537
10537
  field,
10538
- details: `${field} changed`
10538
+ details: `${formatValue(r)} -> ${formatValue(l)}`
10539
10539
  });
10540
10540
  }
10541
10541
  compareString("name", local.name, remote.name, DEFAULT_STRING);