kintone-migrator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/index.js +1656 -0
  4. package/package.json +65 -0
package/dist/index.js ADDED
@@ -0,0 +1,1656 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { cli, define } from "gunshi";
4
+ import * as p from "@clack/prompts";
5
+ import pc from "picocolors";
6
+ import { KintoneRestAPIClient, KintoneRestAPIError } from "@kintone/rest-api-client";
7
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
8
+ import { dirname } from "node:path";
9
+ import { parse, stringify } from "yaml";
10
+ import * as v from "valibot";
11
+
12
+ //#region src/lib/error.ts
13
+ var AnyError = class extends Error {
14
+ constructor(message, cause) {
15
+ super(message, { cause });
16
+ }
17
+ };
18
+
19
+ //#endregion
20
+ //#region src/core/application/error.ts
21
+ var ApplicationError = class extends AnyError {
22
+ name = "ApplicationError";
23
+ constructor(code, message, cause) {
24
+ super(message, cause);
25
+ this.code = code;
26
+ }
27
+ };
28
+ const ConflictErrorCode = { Conflict: "CONFLICT" };
29
+ var ConflictError = class extends ApplicationError {
30
+ name = "ConflictError";
31
+ constructor(code, message, cause) {
32
+ super(code, message, cause);
33
+ this.code = code;
34
+ }
35
+ };
36
+ const ValidationErrorCode = { InvalidInput: "INVALID_INPUT" };
37
+ var ValidationError = class extends ApplicationError {
38
+ name = "ValidationError";
39
+ constructor(code, message, cause) {
40
+ super(code, message, cause);
41
+ this.code = code;
42
+ }
43
+ };
44
+ function isValidationError(error) {
45
+ return error instanceof ValidationError;
46
+ }
47
+ const SystemErrorCode = {
48
+ InternalServerError: "INTERNAL_SERVER_ERROR",
49
+ DatabaseError: "DATABASE_ERROR",
50
+ NetworkError: "NETWORK_ERROR",
51
+ StorageError: "STORAGE_ERROR",
52
+ DocumentGenerationError: "DOCUMENT_GENERATION_ERROR",
53
+ ExternalApiError: "EXTERNAL_API_ERROR"
54
+ };
55
+ var SystemError = class extends ApplicationError {
56
+ name = "SystemError";
57
+ constructor(code, message, cause) {
58
+ super(code, message, cause);
59
+ this.code = code;
60
+ }
61
+ };
62
+ function isSystemError(error) {
63
+ return error instanceof SystemError;
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/core/domain/formSchema/errorCode.ts
68
+ const FormSchemaErrorCode = {
69
+ EmptyFieldCode: "EMPTY_FIELD_CODE",
70
+ EmptySchemaText: "EMPTY_SCHEMA_TEXT",
71
+ InvalidSchemaJson: "INVALID_SCHEMA_JSON",
72
+ InvalidSchemaStructure: "INVALID_SCHEMA_STRUCTURE",
73
+ DuplicateFieldCode: "DUPLICATE_FIELD_CODE",
74
+ InvalidFieldType: "INVALID_FIELD_TYPE",
75
+ InvalidLayoutStructure: "INVALID_LAYOUT_STRUCTURE",
76
+ InvalidDecorationElement: "INVALID_DECORATION_ELEMENT"
77
+ };
78
+
79
+ //#endregion
80
+ //#region src/core/domain/error.ts
81
+ const BusinessRuleErrorCode = { ...FormSchemaErrorCode };
82
+ /**
83
+ * Domain Layer - Business Rule Error
84
+ *
85
+ * Represents a violation of business rules in the domain layer.
86
+ * This error is thrown when domain logic determines that an operation cannot proceed.
87
+ */
88
+ var BusinessRuleError = class extends AnyError {
89
+ name = "BusinessRuleError";
90
+ constructor(code, message, cause) {
91
+ super(message, cause);
92
+ this.code = code;
93
+ }
94
+ };
95
+ function isBusinessRuleError(error) {
96
+ return error instanceof BusinessRuleError;
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/core/adapters/kintone/appDeployer.ts
101
+ const DEFAULT_POLL_INTERVAL_MS = 1e3;
102
+ const DEFAULT_MAX_RETRIES = 180;
103
+ var KintoneAppDeployer = class {
104
+ pollIntervalMs;
105
+ maxRetries;
106
+ constructor(client, appId, config) {
107
+ this.client = client;
108
+ this.appId = appId;
109
+ this.pollIntervalMs = config?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
110
+ this.maxRetries = config?.maxRetries ?? DEFAULT_MAX_RETRIES;
111
+ }
112
+ async deploy() {
113
+ try {
114
+ await this.client.app.deployApp({ apps: [{ app: this.appId }] });
115
+ await this.waitForDeployment();
116
+ } catch (error) {
117
+ if (isBusinessRuleError(error)) throw error;
118
+ if (error instanceof SystemError) throw error;
119
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to deploy app", error);
120
+ }
121
+ }
122
+ async waitForDeployment() {
123
+ for (let i = 0; i < this.maxRetries; i++) {
124
+ await this.sleep(this.pollIntervalMs);
125
+ const { apps } = await this.client.app.getDeployStatus({ apps: [this.appId] });
126
+ const status = apps[0]?.status;
127
+ switch (status) {
128
+ case "SUCCESS": return;
129
+ case "FAIL": throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment failed");
130
+ case "CANCEL": throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment was cancelled");
131
+ case "PROCESSING": continue;
132
+ case void 0: throw new SystemError(SystemErrorCode.ExternalApiError, "Deploy status unavailable");
133
+ default: throw new SystemError(SystemErrorCode.ExternalApiError, `Unexpected deploy status: ${status}`);
134
+ }
135
+ }
136
+ throw new SystemError(SystemErrorCode.ExternalApiError, "App deployment timed out");
137
+ }
138
+ sleep(ms) {
139
+ return new Promise((resolve) => setTimeout(resolve, ms));
140
+ }
141
+ };
142
+
143
+ //#endregion
144
+ //#region src/core/domain/formSchema/valueObject.ts
145
+ const FieldCode = { create: (code) => {
146
+ if (code.length === 0) throw new BusinessRuleError(FormSchemaErrorCode.EmptyFieldCode, "Field code cannot be empty");
147
+ return code;
148
+ } };
149
+
150
+ //#endregion
151
+ //#region src/core/adapters/kintone/formConfigurator.ts
152
+ const KNOWN_FIELD_TYPES = new Set([
153
+ "SINGLE_LINE_TEXT",
154
+ "MULTI_LINE_TEXT",
155
+ "RICH_TEXT",
156
+ "NUMBER",
157
+ "CALC",
158
+ "CHECK_BOX",
159
+ "RADIO_BUTTON",
160
+ "MULTI_SELECT",
161
+ "DROP_DOWN",
162
+ "DATE",
163
+ "TIME",
164
+ "DATETIME",
165
+ "LINK",
166
+ "USER_SELECT",
167
+ "ORGANIZATION_SELECT",
168
+ "GROUP_SELECT",
169
+ "FILE",
170
+ "GROUP",
171
+ "SUBTABLE",
172
+ "REFERENCE_TABLE"
173
+ ]);
174
+ const SYSTEM_FIELD_TYPES$1 = new Set([
175
+ "RECORD_NUMBER",
176
+ "CREATOR",
177
+ "CREATED_TIME",
178
+ "MODIFIER",
179
+ "UPDATED_TIME",
180
+ "CATEGORY",
181
+ "STATUS",
182
+ "STATUS_ASSIGNEE"
183
+ ]);
184
+ const DECORATION_TYPES$1 = new Set([
185
+ "LABEL",
186
+ "SPACER",
187
+ "HR"
188
+ ]);
189
+ const KINTONE_REVISION_CONFLICT_CODE = "GAIA_CO02";
190
+ function isRevisionConflict(error) {
191
+ return error instanceof KintoneRestAPIError && error.code === KINTONE_REVISION_CONFLICT_CODE;
192
+ }
193
+ function fromKintoneProperty(prop) {
194
+ const { type, code, label, noLabel, ...rest } = prop;
195
+ const base = {
196
+ code: FieldCode.create(code),
197
+ label,
198
+ ...noLabel !== void 0 ? { noLabel } : {}
199
+ };
200
+ if (type === "SUBTABLE" && "fields" in rest) {
201
+ const kintoneSubFields = rest.fields;
202
+ const subFields = /* @__PURE__ */ new Map();
203
+ for (const [subCode, subProp] of Object.entries(kintoneSubFields)) subFields.set(FieldCode.create(subCode), fromKintoneProperty(subProp));
204
+ return {
205
+ ...base,
206
+ type: "SUBTABLE",
207
+ properties: { fields: subFields }
208
+ };
209
+ }
210
+ if (type === "REFERENCE_TABLE" && "referenceTable" in rest) {
211
+ const rt = rest.referenceTable;
212
+ const condition = rt.condition;
213
+ const displayFields = rt.displayFields.map((f) => FieldCode.create(f));
214
+ return {
215
+ ...base,
216
+ type: "REFERENCE_TABLE",
217
+ properties: { referenceTable: {
218
+ relatedApp: rt.relatedApp,
219
+ condition: {
220
+ field: FieldCode.create(condition.field),
221
+ relatedField: FieldCode.create(condition.relatedField)
222
+ },
223
+ ...rt.filterCond !== void 0 ? { filterCond: rt.filterCond } : {},
224
+ displayFields,
225
+ ...rt.sort !== void 0 ? { sort: rt.sort } : {},
226
+ ...rt.size !== void 0 ? { size: rt.size } : {}
227
+ } }
228
+ };
229
+ }
230
+ if (!KNOWN_FIELD_TYPES.has(type)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unknown field type: ${type}`);
231
+ return {
232
+ ...base,
233
+ type,
234
+ properties: rest
235
+ };
236
+ }
237
+ function toKintoneProperty(field) {
238
+ const base = {
239
+ type: field.type,
240
+ code: field.code,
241
+ label: field.label,
242
+ ...field.noLabel !== void 0 ? { noLabel: field.noLabel } : {}
243
+ };
244
+ if (field.type === "SUBTABLE") {
245
+ const subFields = {};
246
+ for (const [code, subField] of field.properties.fields) subFields[code] = toKintoneProperty(subField);
247
+ return {
248
+ ...base,
249
+ fields: subFields
250
+ };
251
+ }
252
+ if (field.type === "REFERENCE_TABLE") {
253
+ const ref = field.properties.referenceTable;
254
+ return {
255
+ ...base,
256
+ referenceTable: {
257
+ relatedApp: ref.relatedApp,
258
+ condition: {
259
+ field: ref.condition.field,
260
+ relatedField: ref.condition.relatedField
261
+ },
262
+ ...ref.filterCond !== void 0 ? { filterCond: ref.filterCond } : {},
263
+ displayFields: ref.displayFields.map((f) => f),
264
+ ...ref.sort !== void 0 ? { sort: ref.sort } : {},
265
+ ...ref.size !== void 0 ? { size: ref.size } : {}
266
+ }
267
+ };
268
+ }
269
+ return {
270
+ ...base,
271
+ ...field.properties
272
+ };
273
+ }
274
+ function parseElementSize(raw) {
275
+ if (!raw) return void 0;
276
+ return {
277
+ ...raw.width !== void 0 ? { width: raw.width } : {},
278
+ ...raw.height !== void 0 ? { height: raw.height } : {},
279
+ ...raw.innerHeight !== void 0 ? { innerHeight: raw.innerHeight } : {}
280
+ };
281
+ }
282
+ function fromKintoneLayoutElement(raw) {
283
+ const type = raw.type;
284
+ if (DECORATION_TYPES$1.has(type)) {
285
+ const size$1 = parseElementSize(raw.size) ?? {};
286
+ const elementId = String(raw.elementId ?? "");
287
+ switch (type) {
288
+ case "LABEL": return {
289
+ type: "LABEL",
290
+ label: String(raw.label ?? ""),
291
+ elementId,
292
+ size: size$1
293
+ };
294
+ case "SPACER": return {
295
+ type: "SPACER",
296
+ elementId,
297
+ size: size$1
298
+ };
299
+ case "HR": return {
300
+ type: "HR",
301
+ elementId,
302
+ size: size$1
303
+ };
304
+ default: throw new SystemError(SystemErrorCode.ExternalApiError, `Unknown decoration type: ${type}`);
305
+ }
306
+ }
307
+ if (SYSTEM_FIELD_TYPES$1.has(type)) return {
308
+ code: String(raw.code ?? ""),
309
+ type,
310
+ ...raw.size !== void 0 ? { size: parseElementSize(raw.size) } : {}
311
+ };
312
+ if (!KNOWN_FIELD_TYPES.has(type)) throw new SystemError(SystemErrorCode.ExternalApiError, `Unknown field type: ${type}`);
313
+ const fieldType = type;
314
+ const code = FieldCode.create(String(raw.code ?? ""));
315
+ const size = parseElementSize(raw.size);
316
+ return {
317
+ field: {
318
+ type: fieldType,
319
+ code,
320
+ label: "",
321
+ properties: {}
322
+ },
323
+ ...size !== void 0 ? { size } : {}
324
+ };
325
+ }
326
+ function fromKintoneLayoutRow(raw) {
327
+ return {
328
+ type: "ROW",
329
+ fields: (raw.fields ?? []).map(fromKintoneLayoutElement)
330
+ };
331
+ }
332
+ function fromKintoneLayoutItem(raw) {
333
+ switch (raw.type) {
334
+ case "ROW": return fromKintoneLayoutRow(raw);
335
+ case "GROUP": return {
336
+ type: "GROUP",
337
+ code: FieldCode.create(String(raw.code ?? "")),
338
+ label: String(raw.label ?? ""),
339
+ ...raw.openGroup !== void 0 ? { openGroup: raw.openGroup } : {},
340
+ layout: (raw.layout ?? []).map(fromKintoneLayoutRow)
341
+ };
342
+ case "SUBTABLE": return {
343
+ type: "SUBTABLE",
344
+ code: FieldCode.create(String(raw.code ?? "")),
345
+ label: String(raw.label ?? ""),
346
+ fields: (raw.fields ?? []).map(fromKintoneLayoutElement)
347
+ };
348
+ default: throw new SystemError(SystemErrorCode.ExternalApiError, `Unknown layout item type: ${raw.type}`);
349
+ }
350
+ }
351
+ function toKintoneLayoutElement(element) {
352
+ if ("field" in element) {
353
+ const lf = element;
354
+ const result = {
355
+ type: lf.field.type,
356
+ code: lf.field.code
357
+ };
358
+ if (lf.size !== void 0) result.size = lf.size;
359
+ return result;
360
+ }
361
+ if ("elementId" in element && !("code" in element)) {
362
+ const dec = element;
363
+ const result = {
364
+ type: dec.type,
365
+ elementId: dec.elementId,
366
+ size: dec.size
367
+ };
368
+ if (dec.type === "LABEL") result.label = dec.label;
369
+ return result;
370
+ }
371
+ const sys = element;
372
+ return {
373
+ type: sys.type,
374
+ code: sys.code,
375
+ ...sys.size !== void 0 ? { size: sys.size } : {}
376
+ };
377
+ }
378
+ function toKintoneLayoutRow(row) {
379
+ return {
380
+ type: "ROW",
381
+ fields: row.fields.map(toKintoneLayoutElement)
382
+ };
383
+ }
384
+ function toKintoneLayoutItem(item) {
385
+ switch (item.type) {
386
+ case "ROW": return toKintoneLayoutRow(item);
387
+ case "GROUP": {
388
+ const group = item;
389
+ return {
390
+ type: "GROUP",
391
+ code: group.code,
392
+ ...group.openGroup !== void 0 ? { openGroup: group.openGroup } : {},
393
+ layout: group.layout.map(toKintoneLayoutRow)
394
+ };
395
+ }
396
+ case "SUBTABLE": {
397
+ const subtable = item;
398
+ return {
399
+ type: "SUBTABLE",
400
+ code: subtable.code,
401
+ fields: subtable.fields.map(toKintoneLayoutElement)
402
+ };
403
+ }
404
+ default: throw new SystemError(SystemErrorCode.ExternalApiError, `Unknown layout item type: ${item.type}`);
405
+ }
406
+ }
407
+ var KintoneFormConfigurator = class {
408
+ latestRevision = void 0;
409
+ constructor(client, appId) {
410
+ this.client = client;
411
+ this.appId = appId;
412
+ }
413
+ trackRevision(revision) {
414
+ if (this.latestRevision === void 0 || Number(revision) > Number(this.latestRevision)) this.latestRevision = revision;
415
+ }
416
+ async getFields() {
417
+ try {
418
+ const { properties, revision } = await this.client.app.getFormFields({
419
+ app: this.appId,
420
+ preview: true
421
+ });
422
+ this.trackRevision(revision);
423
+ const fields = /* @__PURE__ */ new Map();
424
+ for (const [code, prop] of Object.entries(properties)) {
425
+ const kintoneProp = prop;
426
+ if (SYSTEM_FIELD_TYPES$1.has(kintoneProp.type)) continue;
427
+ const fieldDef = fromKintoneProperty(kintoneProp);
428
+ fields.set(FieldCode.create(code), fieldDef);
429
+ if (fieldDef.type === "SUBTABLE") for (const [subCode, subDef] of fieldDef.properties.fields) fields.set(subCode, subDef);
430
+ }
431
+ return fields;
432
+ } catch (error) {
433
+ if (isBusinessRuleError(error)) throw error;
434
+ if (error instanceof SystemError) throw error;
435
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get form fields", error);
436
+ }
437
+ }
438
+ async addFields(fields) {
439
+ try {
440
+ const properties = {};
441
+ for (const field of fields) properties[field.code] = toKintoneProperty(field);
442
+ const response = await this.client.app.addFormFields({
443
+ app: this.appId,
444
+ properties,
445
+ ...this.latestRevision !== void 0 ? { revision: this.latestRevision } : {}
446
+ });
447
+ if (response.revision) this.trackRevision(response.revision);
448
+ } catch (error) {
449
+ if (isBusinessRuleError(error)) throw error;
450
+ if (error instanceof SystemError) throw error;
451
+ if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
452
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to add form fields", error);
453
+ }
454
+ }
455
+ async updateFields(fields) {
456
+ try {
457
+ const properties = {};
458
+ for (const field of fields) properties[field.code] = toKintoneProperty(field);
459
+ const response = await this.client.app.updateFormFields({
460
+ app: this.appId,
461
+ properties,
462
+ ...this.latestRevision !== void 0 ? { revision: this.latestRevision } : {}
463
+ });
464
+ if (response.revision) this.trackRevision(response.revision);
465
+ } catch (error) {
466
+ if (isBusinessRuleError(error)) throw error;
467
+ if (error instanceof SystemError) throw error;
468
+ if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
469
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update form fields", error);
470
+ }
471
+ }
472
+ async deleteFields(fieldCodes) {
473
+ try {
474
+ const response = await this.client.app.deleteFormFields({
475
+ app: this.appId,
476
+ fields: fieldCodes.map((code) => code),
477
+ ...this.latestRevision !== void 0 ? { revision: this.latestRevision } : {}
478
+ });
479
+ if (response.revision) this.trackRevision(response.revision);
480
+ } catch (error) {
481
+ if (isBusinessRuleError(error)) throw error;
482
+ if (error instanceof SystemError) throw error;
483
+ if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
484
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to delete form fields", error);
485
+ }
486
+ }
487
+ async getLayout() {
488
+ try {
489
+ const response = await this.client.app.getFormLayout({
490
+ app: this.appId,
491
+ preview: true
492
+ });
493
+ this.trackRevision(response.revision);
494
+ return response.layout.map(fromKintoneLayoutItem);
495
+ } catch (error) {
496
+ if (isBusinessRuleError(error)) throw error;
497
+ if (error instanceof SystemError) throw error;
498
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to get form layout", error);
499
+ }
500
+ }
501
+ async updateLayout(layout) {
502
+ try {
503
+ const kintoneLayout = layout.map(toKintoneLayoutItem);
504
+ const response = await this.client.app.updateFormLayout({
505
+ app: this.appId,
506
+ layout: kintoneLayout,
507
+ revision: this.latestRevision !== void 0 ? Number(this.latestRevision) : -1
508
+ });
509
+ if (response.revision) this.trackRevision(response.revision);
510
+ } catch (error) {
511
+ if (isBusinessRuleError(error)) throw error;
512
+ if (error instanceof SystemError) throw error;
513
+ if (isRevisionConflict(error)) throw new ConflictError(ConflictErrorCode.Conflict, "Form configuration was modified by another process. Please retry the operation.", error);
514
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update form layout", error);
515
+ }
516
+ }
517
+ };
518
+
519
+ //#endregion
520
+ //#region src/core/adapters/local/schemaStorage.ts
521
+ var LocalFileSchemaStorage = class {
522
+ constructor(filePath) {
523
+ this.filePath = filePath;
524
+ }
525
+ async get() {
526
+ try {
527
+ return await readFile(this.filePath, "utf-8");
528
+ } catch (error) {
529
+ if (isNodeError(error) && error.code === "ENOENT") return "";
530
+ throw new SystemError(SystemErrorCode.StorageError, `Failed to read schema file: ${this.filePath}`, error);
531
+ }
532
+ }
533
+ async update(content) {
534
+ try {
535
+ await mkdir(dirname(this.filePath), { recursive: true });
536
+ await writeFile(this.filePath, content, "utf-8");
537
+ } catch (error) {
538
+ throw new SystemError(SystemErrorCode.StorageError, `Failed to write schema file: ${this.filePath}`, error);
539
+ }
540
+ }
541
+ };
542
+ function isNodeError(error) {
543
+ return error instanceof Error && "code" in error;
544
+ }
545
+
546
+ //#endregion
547
+ //#region src/core/application/container/cli.ts
548
+ function createCliContainer(config) {
549
+ const client = new KintoneRestAPIClient({
550
+ baseUrl: config.baseUrl,
551
+ auth: {
552
+ username: config.username,
553
+ password: config.password
554
+ }
555
+ });
556
+ return {
557
+ formConfigurator: new KintoneFormConfigurator(client, config.appId),
558
+ schemaStorage: new LocalFileSchemaStorage(config.schemaFilePath),
559
+ appDeployer: new KintoneAppDeployer(client, config.appId)
560
+ };
561
+ }
562
+
563
+ //#endregion
564
+ //#region src/core/domain/formSchema/entity.ts
565
+ function enrichLayoutElement(element, fields) {
566
+ if (!("field" in element)) return element;
567
+ const lf = element;
568
+ const fullField = fields.get(lf.field.code);
569
+ if (fullField === void 0) return element;
570
+ return {
571
+ field: fullField,
572
+ ...lf.size !== void 0 ? { size: lf.size } : {}
573
+ };
574
+ }
575
+ function enrichLayoutRow(row, fields) {
576
+ return {
577
+ type: "ROW",
578
+ fields: row.fields.map((e) => enrichLayoutElement(e, fields))
579
+ };
580
+ }
581
+ function enrichLayoutItem(item, fields) {
582
+ switch (item.type) {
583
+ case "ROW": return enrichLayoutRow(item, fields);
584
+ case "GROUP": {
585
+ const groupDef = fields.get(item.code);
586
+ return {
587
+ ...item,
588
+ ...groupDef !== void 0 ? { label: groupDef.label } : {},
589
+ ...groupDef !== void 0 && groupDef.noLabel !== void 0 ? { noLabel: groupDef.noLabel } : {},
590
+ ...groupDef !== void 0 && groupDef.type === "GROUP" && groupDef.properties.openGroup !== void 0 ? { openGroup: groupDef.properties.openGroup } : {},
591
+ layout: item.layout.map((r) => enrichLayoutRow(r, fields))
592
+ };
593
+ }
594
+ case "SUBTABLE": {
595
+ const subtableDef = fields.get(item.code);
596
+ const subFieldsMap = subtableDef !== void 0 && subtableDef.type === "SUBTABLE" ? subtableDef.properties.fields : /* @__PURE__ */ new Map();
597
+ return {
598
+ ...item,
599
+ ...subtableDef !== void 0 ? { label: subtableDef.label } : {},
600
+ ...subtableDef !== void 0 && subtableDef.noLabel !== void 0 ? { noLabel: subtableDef.noLabel } : {},
601
+ fields: item.fields.map((e) => enrichLayoutElement(e, subFieldsMap))
602
+ };
603
+ }
604
+ }
605
+ }
606
+ function enrichLayoutWithFields(layout, fields) {
607
+ return layout.map((item) => enrichLayoutItem(item, fields));
608
+ }
609
+ function collectSubtableInnerFieldCodes(fields) {
610
+ const innerCodes = /* @__PURE__ */ new Set();
611
+ for (const def of fields.values()) if (def.type === "SUBTABLE") for (const subCode of def.properties.fields.keys()) innerCodes.add(subCode);
612
+ return innerCodes;
613
+ }
614
+ const FormDiff = { create: (entries) => {
615
+ const summary = {
616
+ added: entries.filter((e) => e.type === "added").length,
617
+ modified: entries.filter((e) => e.type === "modified").length,
618
+ deleted: entries.filter((e) => e.type === "deleted").length,
619
+ total: entries.length
620
+ };
621
+ const sortOrder = {
622
+ added: 0,
623
+ modified: 1,
624
+ deleted: 2
625
+ };
626
+ return {
627
+ entries: [...entries].sort((a, b) => sortOrder[a.type] - sortOrder[b.type]),
628
+ summary,
629
+ isEmpty: entries.length === 0
630
+ };
631
+ } };
632
+
633
+ //#endregion
634
+ //#region src/core/domain/formSchema/services/schemaSerializer.ts
635
+ function isLayoutField(element) {
636
+ return "field" in element;
637
+ }
638
+ function isDecorationElement(element) {
639
+ return "elementId" in element;
640
+ }
641
+ function isSystemFieldLayout(element) {
642
+ return !("field" in element) && !("elementId" in element);
643
+ }
644
+ function serializeSize(size) {
645
+ const result = {};
646
+ if (size.width !== void 0) result.width = size.width;
647
+ if (size.height !== void 0) result.height = size.height;
648
+ if (size.innerHeight !== void 0) result.innerHeight = size.innerHeight;
649
+ return result;
650
+ }
651
+ function serializeFlatField(field, size) {
652
+ const result = {
653
+ code: field.code,
654
+ type: field.type,
655
+ label: field.label
656
+ };
657
+ if (field.noLabel !== void 0) result.noLabel = field.noLabel;
658
+ if (size !== void 0) result.size = serializeSize(size);
659
+ if (field.type === "REFERENCE_TABLE") {
660
+ const ref = field.properties.referenceTable;
661
+ result.referenceTable = {
662
+ relatedApp: ref.relatedApp,
663
+ condition: {
664
+ field: ref.condition.field,
665
+ relatedField: ref.condition.relatedField
666
+ },
667
+ ...ref.filterCond !== void 0 ? { filterCond: ref.filterCond } : {},
668
+ displayFields: ref.displayFields.map((f) => f),
669
+ ...ref.sort !== void 0 ? { sort: ref.sort } : {},
670
+ ...ref.size !== void 0 ? { size: ref.size } : {}
671
+ };
672
+ return result;
673
+ }
674
+ if (field.type !== "SUBTABLE" && field.type !== "GROUP") {
675
+ const properties = field.properties;
676
+ for (const [key, value] of Object.entries(properties)) result[key] = value;
677
+ }
678
+ return result;
679
+ }
680
+ function serializeLayoutElement(element) {
681
+ if (isLayoutField(element)) return serializeFlatField(element.field, element.size);
682
+ if (isDecorationElement(element)) {
683
+ const dec = element;
684
+ const result = { type: dec.type };
685
+ if (dec.type === "LABEL") result.label = dec.label;
686
+ result.elementId = dec.elementId;
687
+ if (dec.size !== void 0) result.size = serializeSize(dec.size);
688
+ return result;
689
+ }
690
+ if (isSystemFieldLayout(element)) {
691
+ const sys = element;
692
+ const result = {
693
+ code: sys.code,
694
+ type: sys.type
695
+ };
696
+ if (sys.size !== void 0) result.size = serializeSize(sys.size);
697
+ return result;
698
+ }
699
+ return element;
700
+ }
701
+ function serializeLayoutRow(row) {
702
+ return {
703
+ type: "ROW",
704
+ fields: row.fields.map(serializeLayoutElement)
705
+ };
706
+ }
707
+ function serializeLayoutItem(item) {
708
+ switch (item.type) {
709
+ case "ROW": return serializeLayoutRow(item);
710
+ case "GROUP": return {
711
+ type: "GROUP",
712
+ code: item.code,
713
+ label: item.label,
714
+ ...item.noLabel !== void 0 ? { noLabel: item.noLabel } : {},
715
+ ...item.openGroup !== void 0 ? { openGroup: item.openGroup } : {},
716
+ layout: item.layout.map(serializeLayoutRow)
717
+ };
718
+ case "SUBTABLE": return {
719
+ type: "SUBTABLE",
720
+ code: item.code,
721
+ label: item.label,
722
+ ...item.noLabel !== void 0 ? { noLabel: item.noLabel } : {},
723
+ fields: item.fields.map(serializeLayoutElement)
724
+ };
725
+ }
726
+ }
727
+ const SchemaSerializer = { serialize: (layout) => {
728
+ return stringify({ layout: layout.map(serializeLayoutItem) }, {
729
+ lineWidth: 0,
730
+ defaultKeyType: "PLAIN",
731
+ defaultStringType: "PLAIN"
732
+ });
733
+ } };
734
+
735
+ //#endregion
736
+ //#region src/core/application/formSchema/captureSchema.ts
737
+ async function captureSchema({ container }) {
738
+ const [currentFields, currentLayout] = await Promise.all([container.formConfigurator.getFields(), container.formConfigurator.getLayout()]);
739
+ const enrichedLayout = enrichLayoutWithFields(currentLayout, currentFields);
740
+ return {
741
+ schemaText: SchemaSerializer.serialize(enrichedLayout),
742
+ hasExistingSchema: (await container.schemaStorage.get()).length > 0
743
+ };
744
+ }
745
+
746
+ //#endregion
747
+ //#region src/core/application/formSchema/saveSchema.ts
748
+ async function saveSchema({ container, input }) {
749
+ await container.schemaStorage.update(input.schemaText);
750
+ }
751
+
752
+ //#endregion
753
+ //#region src/cli/config.ts
754
+ const CliConfigSchema = v.object({
755
+ KINTONE_DOMAIN: v.pipe(v.string(), v.nonEmpty("KINTONE_DOMAIN is required")),
756
+ KINTONE_USERNAME: v.pipe(v.string(), v.nonEmpty("KINTONE_USERNAME is required")),
757
+ KINTONE_PASSWORD: v.pipe(v.string(), v.nonEmpty("KINTONE_PASSWORD is required")),
758
+ KINTONE_APP_ID: v.pipe(v.string(), v.nonEmpty("KINTONE_APP_ID is required")),
759
+ SCHEMA_FILE_PATH: v.optional(v.string(), "schema.yaml")
760
+ });
761
+ const kintoneArgs = {
762
+ domain: {
763
+ type: "string",
764
+ short: "d",
765
+ description: "kintone domain (overrides KINTONE_DOMAIN env var)"
766
+ },
767
+ username: {
768
+ type: "string",
769
+ short: "u",
770
+ description: "kintone username (overrides KINTONE_USERNAME env var)"
771
+ },
772
+ password: {
773
+ type: "string",
774
+ short: "p",
775
+ description: "kintone password (overrides KINTONE_PASSWORD env var)"
776
+ },
777
+ "app-id": {
778
+ type: "string",
779
+ short: "a",
780
+ description: "kintone app ID (overrides KINTONE_APP_ID env var)"
781
+ },
782
+ "schema-file": {
783
+ type: "string",
784
+ short: "f",
785
+ description: "Schema file path (default: schema.yaml)"
786
+ }
787
+ };
788
+ function resolveConfig(cliValues) {
789
+ const result = v.safeParse(CliConfigSchema, {
790
+ KINTONE_DOMAIN: cliValues.domain ?? process.env.KINTONE_DOMAIN ?? "",
791
+ KINTONE_USERNAME: cliValues.username ?? process.env.KINTONE_USERNAME ?? "",
792
+ KINTONE_PASSWORD: cliValues.password ?? process.env.KINTONE_PASSWORD ?? "",
793
+ KINTONE_APP_ID: cliValues["app-id"] ?? process.env.KINTONE_APP_ID ?? "",
794
+ SCHEMA_FILE_PATH: cliValues["schema-file"] ?? process.env.SCHEMA_FILE_PATH
795
+ });
796
+ if (!result.success) {
797
+ const missing = result.issues.map((issue) => issue.message).join("\n ");
798
+ throw new Error(`Missing required configuration:\n ${missing}`);
799
+ }
800
+ return {
801
+ baseUrl: `https://${result.output.KINTONE_DOMAIN}`,
802
+ username: result.output.KINTONE_USERNAME,
803
+ password: result.output.KINTONE_PASSWORD,
804
+ appId: result.output.KINTONE_APP_ID,
805
+ schemaFilePath: result.output.SCHEMA_FILE_PATH
806
+ };
807
+ }
808
+
809
+ //#endregion
810
+ //#region src/cli/handleError.ts
811
+ function handleCliError(error) {
812
+ if (isBusinessRuleError(error)) {
813
+ p.log.error(`[BusinessRuleError] ${error.code}: ${error.message}`);
814
+ logErrorDetails(error);
815
+ p.outro("Failed.");
816
+ process.exit(1);
817
+ }
818
+ if (isValidationError(error)) {
819
+ p.log.error(`[ValidationError] ${error.message}`);
820
+ logErrorDetails(error);
821
+ p.outro("Failed.");
822
+ process.exit(1);
823
+ }
824
+ if (isSystemError(error)) {
825
+ p.log.error(`[SystemError] ${error.code}: ${error.message}`);
826
+ logErrorDetails(error);
827
+ p.outro("Failed.");
828
+ process.exit(1);
829
+ }
830
+ if (isApplicationError(error)) {
831
+ p.log.error(`[${error.name}] ${error.code}: ${error.message}`);
832
+ logErrorDetails(error);
833
+ p.outro("Failed.");
834
+ process.exit(1);
835
+ }
836
+ if (error instanceof Error) {
837
+ p.log.error(`[Error] ${error.message}`);
838
+ logErrorDetails(error);
839
+ p.outro("Failed.");
840
+ process.exit(1);
841
+ }
842
+ p.log.error(`[Error] 予期しないエラーが発生しました: ${String(error)}`);
843
+ p.outro("Failed.");
844
+ process.exit(1);
845
+ }
846
+ function logErrorDetails(error) {
847
+ if (error.cause) {
848
+ p.log.warn(`Cause: ${String(error.cause)}`);
849
+ const cause = error.cause;
850
+ if (cause.errors) p.log.warn(`Details: ${JSON.stringify(cause.errors, null, 2)}`);
851
+ }
852
+ if (error.stack) p.log.warn(`Stack: ${error.stack}`);
853
+ }
854
+ function isApplicationError(error) {
855
+ return error instanceof Error && "code" in error && error.name !== "BusinessRuleError";
856
+ }
857
+
858
+ //#endregion
859
+ //#region src/cli/commands/capture.ts
860
+ var capture_default = define({
861
+ name: "capture",
862
+ description: "Capture current kintone form schema to file",
863
+ args: { ...kintoneArgs },
864
+ run: async (ctx) => {
865
+ try {
866
+ const config = resolveConfig(ctx.values);
867
+ const container = createCliContainer(config);
868
+ const s = p.spinner();
869
+ s.start("Capturing current form schema...");
870
+ const result = await captureSchema({ container });
871
+ s.stop("Form schema captured.");
872
+ await saveSchema({
873
+ container,
874
+ input: { schemaText: result.schemaText }
875
+ });
876
+ p.log.success(`Schema saved to: ${pc.cyan(config.schemaFilePath)}`);
877
+ if (result.hasExistingSchema) p.log.warn("Existing schema was overwritten.");
878
+ } catch (error) {
879
+ handleCliError(error);
880
+ }
881
+ }
882
+ });
883
+
884
+ //#endregion
885
+ //#region src/core/domain/formSchema/services/diffDetector.ts
886
+ function isArrayEqual(a, b) {
887
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
888
+ if (a.length !== b.length) return false;
889
+ for (let i = 0; i < a.length; i++) if (!isValueEqual(a[i], b[i])) return false;
890
+ return true;
891
+ }
892
+ function isRecordEqual(a, b) {
893
+ const keysA = Object.keys(a);
894
+ const keysB = Object.keys(b);
895
+ if (keysA.length !== keysB.length) return false;
896
+ for (const key of keysA) {
897
+ if (!Object.hasOwn(b, key)) return false;
898
+ if (!isValueEqual(a[key], b[key])) return false;
899
+ }
900
+ return true;
901
+ }
902
+ function isValueEqual(a, b) {
903
+ if (a === b) return true;
904
+ if (a === null || b === null) return a === b;
905
+ if (typeof a !== typeof b) return false;
906
+ if (Array.isArray(a)) return isArrayEqual(a, b);
907
+ if (typeof a === "object") return isRecordEqual(a, b);
908
+ return false;
909
+ }
910
+ function isMapEqual(a, b) {
911
+ if (a.size !== b.size) return false;
912
+ for (const [key, valA] of a) {
913
+ const valB = b.get(key);
914
+ if (valB === void 0) return false;
915
+ if (!isFieldEqual(valA, valB)) return false;
916
+ }
917
+ return true;
918
+ }
919
+ function isPropertiesEqual(a, b) {
920
+ if (a.type === "SUBTABLE" && b.type === "SUBTABLE") return isMapEqual(a.properties.fields, b.properties.fields);
921
+ if (a.type === "REFERENCE_TABLE" && b.type === "REFERENCE_TABLE") {
922
+ const refA = a.properties.referenceTable;
923
+ const refB = b.properties.referenceTable;
924
+ return isValueEqual({
925
+ ...refA,
926
+ displayFields: [...refA.displayFields]
927
+ }, {
928
+ ...refB,
929
+ displayFields: [...refB.displayFields]
930
+ });
931
+ }
932
+ return isValueEqual(a.properties, b.properties);
933
+ }
934
+ function isFieldEqual(a, b) {
935
+ if (a.type !== b.type) return false;
936
+ if (a.label !== b.label) return false;
937
+ if (a.code !== b.code) return false;
938
+ if (Boolean(a.noLabel) !== Boolean(b.noLabel)) return false;
939
+ return isPropertiesEqual(a, b);
940
+ }
941
+ function hasPropertiesChanged(before, after) {
942
+ if (before.type === "SUBTABLE" && after.type === "SUBTABLE") return !isMapEqual(before.properties.fields, after.properties.fields);
943
+ if (before.type === "REFERENCE_TABLE" && after.type === "REFERENCE_TABLE") {
944
+ const refB = before.properties.referenceTable;
945
+ const refA = after.properties.referenceTable;
946
+ return !isValueEqual({
947
+ ...refB,
948
+ displayFields: [...refB.displayFields]
949
+ }, {
950
+ ...refA,
951
+ displayFields: [...refA.displayFields]
952
+ });
953
+ }
954
+ return !isValueEqual(before.properties, after.properties);
955
+ }
956
+ function describeChanges(before, after) {
957
+ const changes = [];
958
+ if (before.type !== after.type) changes.push(`type: ${before.type} -> ${after.type}`);
959
+ if (before.label !== after.label) changes.push(`label: ${before.label} -> ${after.label}`);
960
+ if (Boolean(before.noLabel) !== Boolean(after.noLabel)) changes.push(`noLabel: ${before.noLabel ?? false} -> ${after.noLabel ?? false}`);
961
+ if (hasPropertiesChanged(before, after)) changes.push("properties changed");
962
+ return changes.length > 0 ? changes.join(", ") : "no visible changes";
963
+ }
964
+ function isLayoutEqual(a, b) {
965
+ return isValueEqual(a, b);
966
+ }
967
+ const DiffDetector = {
968
+ detectLayoutChanges: (schemaLayout, currentLayout) => {
969
+ return !isLayoutEqual(schemaLayout, currentLayout);
970
+ },
971
+ detect: (schema, current) => {
972
+ const entries = [];
973
+ for (const [fieldCode, schemaDef] of schema.fields) {
974
+ const currentDef = current.get(fieldCode);
975
+ if (currentDef === void 0) entries.push({
976
+ type: "added",
977
+ fieldCode,
978
+ fieldLabel: schemaDef.label,
979
+ details: "new field",
980
+ after: schemaDef
981
+ });
982
+ else if (!isFieldEqual(schemaDef, currentDef)) entries.push({
983
+ type: "modified",
984
+ fieldCode,
985
+ fieldLabel: schemaDef.label,
986
+ details: describeChanges(currentDef, schemaDef),
987
+ before: currentDef,
988
+ after: schemaDef
989
+ });
990
+ }
991
+ for (const [fieldCode, currentDef] of current) if (!schema.fields.has(fieldCode)) entries.push({
992
+ type: "deleted",
993
+ fieldCode,
994
+ fieldLabel: currentDef.label,
995
+ details: "deleted",
996
+ before: currentDef
997
+ });
998
+ return FormDiff.create(entries);
999
+ }
1000
+ };
1001
+
1002
+ //#endregion
1003
+ //#region src/core/domain/formSchema/services/schemaParser.ts
1004
+ const VALID_UNIT_POSITIONS = new Set(["BEFORE", "AFTER"]);
1005
+ const VALID_CALC_FORMATS = new Set([
1006
+ "NUMBER",
1007
+ "NUMBER_DIGIT",
1008
+ "DATE",
1009
+ "TIME",
1010
+ "DATETIME",
1011
+ "HOUR_MINUTE",
1012
+ "DAY_HOUR_MINUTE"
1013
+ ]);
1014
+ const VALID_SELECTION_ALIGNS = new Set(["HORIZONTAL", "VERTICAL"]);
1015
+ const VALID_LINK_PROTOCOLS = new Set([
1016
+ "WEB",
1017
+ "CALL",
1018
+ "MAIL"
1019
+ ]);
1020
+ function validateEnumProperty(fieldCode, propName, value, validValues) {
1021
+ if (value !== void 0 && !validValues.has(value)) throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, `Invalid ${propName} "${String(value)}" for field "${fieldCode}". Expected one of: ${[...validValues].join(", ")}`);
1022
+ }
1023
+ function validateFieldProperties(code, fieldType, properties) {
1024
+ switch (fieldType) {
1025
+ case "NUMBER":
1026
+ validateEnumProperty(code, "unitPosition", properties.unitPosition, VALID_UNIT_POSITIONS);
1027
+ break;
1028
+ case "CALC":
1029
+ validateEnumProperty(code, "format", properties.format, VALID_CALC_FORMATS);
1030
+ validateEnumProperty(code, "unitPosition", properties.unitPosition, VALID_UNIT_POSITIONS);
1031
+ break;
1032
+ case "CHECK_BOX":
1033
+ case "RADIO_BUTTON":
1034
+ case "MULTI_SELECT":
1035
+ case "DROP_DOWN":
1036
+ validateEnumProperty(code, "align", properties.align, VALID_SELECTION_ALIGNS);
1037
+ break;
1038
+ case "LINK":
1039
+ validateEnumProperty(code, "protocol", properties.protocol, VALID_LINK_PROTOCOLS);
1040
+ break;
1041
+ }
1042
+ }
1043
+ const VALID_FIELD_TYPES = new Set([
1044
+ "SINGLE_LINE_TEXT",
1045
+ "MULTI_LINE_TEXT",
1046
+ "RICH_TEXT",
1047
+ "NUMBER",
1048
+ "CALC",
1049
+ "CHECK_BOX",
1050
+ "RADIO_BUTTON",
1051
+ "MULTI_SELECT",
1052
+ "DROP_DOWN",
1053
+ "DATE",
1054
+ "TIME",
1055
+ "DATETIME",
1056
+ "LINK",
1057
+ "USER_SELECT",
1058
+ "ORGANIZATION_SELECT",
1059
+ "GROUP_SELECT",
1060
+ "FILE",
1061
+ "GROUP",
1062
+ "SUBTABLE",
1063
+ "REFERENCE_TABLE"
1064
+ ]);
1065
+ const DECORATION_TYPES = new Set([
1066
+ "LABEL",
1067
+ "SPACER",
1068
+ "HR"
1069
+ ]);
1070
+ const SYSTEM_FIELD_TYPES = new Set([
1071
+ "RECORD_NUMBER",
1072
+ "CREATOR",
1073
+ "CREATED_TIME",
1074
+ "MODIFIER",
1075
+ "UPDATED_TIME",
1076
+ "CATEGORY",
1077
+ "STATUS",
1078
+ "STATUS_ASSIGNEE"
1079
+ ]);
1080
+ const BASE_ATTRIBUTES = new Set([
1081
+ "code",
1082
+ "type",
1083
+ "label",
1084
+ "noLabel"
1085
+ ]);
1086
+ const LAYOUT_ATTRIBUTES = new Set(["size"]);
1087
+ const DECORATION_ATTRIBUTES = new Set(["elementId"]);
1088
+ const GROUP_ATTRIBUTES = new Set(["openGroup", "layout"]);
1089
+ const SUBTABLE_ATTRIBUTES = new Set(["fields"]);
1090
+ function parseSize(raw) {
1091
+ if (raw === void 0 || raw === null) return void 0;
1092
+ if (typeof raw !== "object") return void 0;
1093
+ const obj = raw;
1094
+ return {
1095
+ ...obj.width !== void 0 ? { width: String(obj.width) } : {},
1096
+ ...obj.height !== void 0 ? { height: String(obj.height) } : {},
1097
+ ...obj.innerHeight !== void 0 ? { innerHeight: String(obj.innerHeight) } : {}
1098
+ };
1099
+ }
1100
+ function normalizePropertyValue(value) {
1101
+ if (typeof value === "number") return String(value);
1102
+ return value;
1103
+ }
1104
+ function extractProperties(raw) {
1105
+ const properties = {};
1106
+ for (const [key, value] of Object.entries(raw)) {
1107
+ if (BASE_ATTRIBUTES.has(key)) continue;
1108
+ if (LAYOUT_ATTRIBUTES.has(key)) continue;
1109
+ if (DECORATION_ATTRIBUTES.has(key)) continue;
1110
+ if (GROUP_ATTRIBUTES.has(key)) continue;
1111
+ if (SUBTABLE_ATTRIBUTES.has(key)) continue;
1112
+ properties[key] = normalizePropertyValue(value);
1113
+ }
1114
+ return properties;
1115
+ }
1116
+ function buildFieldDefinition(base, fieldType, properties) {
1117
+ switch (fieldType) {
1118
+ case "SINGLE_LINE_TEXT": return {
1119
+ ...base,
1120
+ type: fieldType,
1121
+ properties
1122
+ };
1123
+ case "MULTI_LINE_TEXT": return {
1124
+ ...base,
1125
+ type: fieldType,
1126
+ properties
1127
+ };
1128
+ case "RICH_TEXT": return {
1129
+ ...base,
1130
+ type: fieldType,
1131
+ properties
1132
+ };
1133
+ case "NUMBER": return {
1134
+ ...base,
1135
+ type: fieldType,
1136
+ properties
1137
+ };
1138
+ case "CALC": return {
1139
+ ...base,
1140
+ type: fieldType,
1141
+ properties
1142
+ };
1143
+ case "CHECK_BOX":
1144
+ case "RADIO_BUTTON":
1145
+ case "MULTI_SELECT":
1146
+ case "DROP_DOWN": return {
1147
+ ...base,
1148
+ type: fieldType,
1149
+ properties
1150
+ };
1151
+ case "DATE": return {
1152
+ ...base,
1153
+ type: fieldType,
1154
+ properties
1155
+ };
1156
+ case "TIME": return {
1157
+ ...base,
1158
+ type: fieldType,
1159
+ properties
1160
+ };
1161
+ case "DATETIME": return {
1162
+ ...base,
1163
+ type: fieldType,
1164
+ properties
1165
+ };
1166
+ case "LINK": return {
1167
+ ...base,
1168
+ type: fieldType,
1169
+ properties
1170
+ };
1171
+ case "USER_SELECT":
1172
+ case "ORGANIZATION_SELECT":
1173
+ case "GROUP_SELECT": return {
1174
+ ...base,
1175
+ type: fieldType,
1176
+ properties
1177
+ };
1178
+ case "FILE": return {
1179
+ ...base,
1180
+ type: fieldType,
1181
+ properties
1182
+ };
1183
+ case "GROUP": return {
1184
+ ...base,
1185
+ type: fieldType,
1186
+ properties
1187
+ };
1188
+ }
1189
+ }
1190
+ function parseFieldDefinitionFromFlat(raw) {
1191
+ const code = String(raw.code);
1192
+ const type = String(raw.type);
1193
+ if (!VALID_FIELD_TYPES.has(type)) throw new BusinessRuleError(FormSchemaErrorCode.InvalidFieldType, `Invalid field type "${type}" for field "${code}"`);
1194
+ const fieldCode = FieldCode.create(code);
1195
+ const fieldType = type;
1196
+ const base = {
1197
+ code: fieldCode,
1198
+ label: String(raw.label ?? ""),
1199
+ ...raw.noLabel !== void 0 ? { noLabel: raw.noLabel } : {}
1200
+ };
1201
+ if (fieldType === "SUBTABLE") {
1202
+ const rawFields = raw.fields ?? [];
1203
+ const subFields = /* @__PURE__ */ new Map();
1204
+ for (const subRaw of rawFields) {
1205
+ const subDef = parseFieldDefinitionFromFlat(subRaw);
1206
+ subFields.set(subDef.code, subDef);
1207
+ }
1208
+ return {
1209
+ ...base,
1210
+ type: "SUBTABLE",
1211
+ properties: { fields: subFields }
1212
+ };
1213
+ }
1214
+ if (fieldType === "REFERENCE_TABLE") {
1215
+ if (raw.referenceTable === void 0 || raw.referenceTable === null || typeof raw.referenceTable !== "object") throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have a "referenceTable" property`);
1216
+ const refTable = raw.referenceTable;
1217
+ if (refTable.relatedApp === void 0 || refTable.relatedApp === null || typeof refTable.relatedApp !== "object") throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.relatedApp"`);
1218
+ if (refTable.condition === void 0 || refTable.condition === null || typeof refTable.condition !== "object") throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.condition"`);
1219
+ if (!Array.isArray(refTable.displayFields)) throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, `Field "${code}" of type REFERENCE_TABLE must have "referenceTable.displayFields" array`);
1220
+ const condition = refTable.condition;
1221
+ const displayFields = refTable.displayFields.map((f) => FieldCode.create(f));
1222
+ return {
1223
+ ...base,
1224
+ type: "REFERENCE_TABLE",
1225
+ properties: { referenceTable: {
1226
+ relatedApp: refTable.relatedApp,
1227
+ condition: {
1228
+ field: FieldCode.create(condition.field),
1229
+ relatedField: FieldCode.create(condition.relatedField)
1230
+ },
1231
+ ...refTable.filterCond !== void 0 ? { filterCond: refTable.filterCond } : {},
1232
+ displayFields,
1233
+ ...refTable.sort !== void 0 ? { sort: refTable.sort } : {},
1234
+ ...refTable.size !== void 0 ? { size: String(refTable.size) } : {}
1235
+ } }
1236
+ };
1237
+ }
1238
+ const properties = extractProperties(raw);
1239
+ validateFieldProperties(code, fieldType, properties);
1240
+ return buildFieldDefinition(base, fieldType, properties);
1241
+ }
1242
+ function parseDecorationElement(raw) {
1243
+ const type = String(raw.type);
1244
+ const elementId = String(raw.elementId ?? "");
1245
+ const size = parseSize(raw.size) ?? {};
1246
+ switch (type) {
1247
+ case "LABEL": return {
1248
+ type: "LABEL",
1249
+ label: String(raw.label ?? ""),
1250
+ elementId,
1251
+ size
1252
+ };
1253
+ case "SPACER": return {
1254
+ type: "SPACER",
1255
+ elementId,
1256
+ size
1257
+ };
1258
+ case "HR": return {
1259
+ type: "HR",
1260
+ elementId,
1261
+ size
1262
+ };
1263
+ default: throw new BusinessRuleError(FormSchemaErrorCode.InvalidDecorationElement, `Unknown decoration element type: "${type}"`);
1264
+ }
1265
+ }
1266
+ function parseLayoutElement(raw) {
1267
+ const type = String(raw.type);
1268
+ if (DECORATION_TYPES.has(type)) return parseDecorationElement(raw);
1269
+ if (SYSTEM_FIELD_TYPES.has(type)) return {
1270
+ code: String(raw.code),
1271
+ type,
1272
+ ...raw.size !== void 0 ? { size: parseSize(raw.size) } : {}
1273
+ };
1274
+ const field = parseFieldDefinitionFromFlat(raw);
1275
+ const size = parseSize(raw.size);
1276
+ return {
1277
+ field,
1278
+ ...size !== void 0 ? { size } : {}
1279
+ };
1280
+ }
1281
+ function parseLayoutRow(raw) {
1282
+ if (raw.type !== "ROW") throw new BusinessRuleError(FormSchemaErrorCode.InvalidLayoutStructure, `Expected layout row type "ROW", got "${String(raw.type)}"`);
1283
+ return {
1284
+ type: "ROW",
1285
+ fields: (raw.fields ?? []).map(parseLayoutElement)
1286
+ };
1287
+ }
1288
+ function parseLayoutItem(raw, fieldMap) {
1289
+ const type = String(raw.type);
1290
+ switch (type) {
1291
+ case "ROW": {
1292
+ const row = parseLayoutRow(raw);
1293
+ collectFieldsFromElements(row.fields, fieldMap);
1294
+ return row;
1295
+ }
1296
+ case "GROUP": {
1297
+ const code = FieldCode.create(String(raw.code));
1298
+ const label = String(raw.label ?? "");
1299
+ const noLabel = raw.noLabel !== void 0 ? raw.noLabel : void 0;
1300
+ const openGroup = raw.openGroup !== void 0 ? raw.openGroup : void 0;
1301
+ const layout = (raw.layout ?? []).map((r) => {
1302
+ const row = parseLayoutRow(r);
1303
+ collectFieldsFromElements(row.fields, fieldMap);
1304
+ return row;
1305
+ });
1306
+ addFieldToMap(fieldMap, code, {
1307
+ code,
1308
+ label,
1309
+ ...noLabel !== void 0 ? { noLabel } : {},
1310
+ type: "GROUP",
1311
+ properties: { ...openGroup !== void 0 ? { openGroup } : {} }
1312
+ });
1313
+ return {
1314
+ type: "GROUP",
1315
+ code,
1316
+ label,
1317
+ ...noLabel !== void 0 ? { noLabel } : {},
1318
+ ...openGroup !== void 0 ? { openGroup } : {},
1319
+ layout
1320
+ };
1321
+ }
1322
+ case "SUBTABLE": {
1323
+ const code = FieldCode.create(String(raw.code));
1324
+ const label = String(raw.label ?? "");
1325
+ const noLabel = raw.noLabel !== void 0 ? raw.noLabel : void 0;
1326
+ const elements = (raw.fields ?? []).map(parseLayoutElement);
1327
+ const subFields = /* @__PURE__ */ new Map();
1328
+ collectFieldsFromElements(elements, subFields);
1329
+ for (const [subCode, subDef] of subFields) addFieldToMap(fieldMap, subCode, subDef);
1330
+ addFieldToMap(fieldMap, code, {
1331
+ code,
1332
+ label,
1333
+ ...noLabel !== void 0 ? { noLabel } : {},
1334
+ type: "SUBTABLE",
1335
+ properties: { fields: subFields }
1336
+ });
1337
+ return {
1338
+ type: "SUBTABLE",
1339
+ code,
1340
+ label,
1341
+ ...noLabel !== void 0 ? { noLabel } : {},
1342
+ fields: elements
1343
+ };
1344
+ }
1345
+ default: throw new BusinessRuleError(FormSchemaErrorCode.InvalidLayoutStructure, `Unknown layout item type: "${type}"`);
1346
+ }
1347
+ }
1348
+ function addFieldToMap(fieldMap, code, definition) {
1349
+ if (fieldMap.has(code)) throw new BusinessRuleError(FormSchemaErrorCode.DuplicateFieldCode, `Duplicate field code: "${code}"`);
1350
+ fieldMap.set(code, definition);
1351
+ }
1352
+ function collectFieldsFromElements(elements, fieldMap) {
1353
+ for (const element of elements) if ("field" in element) addFieldToMap(fieldMap, element.field.code, element.field);
1354
+ }
1355
+ const SchemaParser = { parse: (rawText) => {
1356
+ if (rawText.trim().length === 0) throw new BusinessRuleError(FormSchemaErrorCode.EmptySchemaText, "Schema text cannot be empty");
1357
+ let parsed;
1358
+ try {
1359
+ parsed = parse(rawText);
1360
+ } catch {
1361
+ throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaJson, "Schema text is not valid YAML/JSON");
1362
+ }
1363
+ if (typeof parsed !== "object" || parsed === null) throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, "Schema must be an object");
1364
+ const obj = parsed;
1365
+ if ("fields" in obj && !("layout" in obj)) throw new BusinessRuleError(FormSchemaErrorCode.InvalidSchemaStructure, "\"fields\" キーが検出されました。スキーマフォーマットが変更されています。「現在の設定を取り込む」で新しいフォーマットのスキーマを生成してください。");
1366
+ if (!("layout" in obj) || !Array.isArray(obj.layout)) throw new BusinessRuleError(FormSchemaErrorCode.InvalidLayoutStructure, "Schema must have a \"layout\" array");
1367
+ const rawLayout = obj.layout;
1368
+ const fieldMap = /* @__PURE__ */ new Map();
1369
+ const layout = [];
1370
+ for (const rawItem of rawLayout) layout.push(parseLayoutItem(rawItem, fieldMap));
1371
+ return {
1372
+ fields: fieldMap,
1373
+ layout
1374
+ };
1375
+ } };
1376
+
1377
+ //#endregion
1378
+ //#region src/core/application/formSchema/parseSchema.ts
1379
+ function parseSchemaText(rawText) {
1380
+ try {
1381
+ return SchemaParser.parse(rawText);
1382
+ } catch (error) {
1383
+ if (isBusinessRuleError(error)) throw new ValidationError(ValidationErrorCode.InvalidInput, error.message, error);
1384
+ throw error;
1385
+ }
1386
+ }
1387
+
1388
+ //#endregion
1389
+ //#region src/core/application/formSchema/detectDiff.ts
1390
+ function toFieldDto(field) {
1391
+ return {
1392
+ code: field.code,
1393
+ type: field.type,
1394
+ label: field.label,
1395
+ properties: field.properties
1396
+ };
1397
+ }
1398
+ async function detectDiff({ container }) {
1399
+ const schema = parseSchemaText(await container.schemaStorage.get());
1400
+ const [currentFields, currentLayout] = await Promise.all([container.formConfigurator.getFields(), container.formConfigurator.getLayout()]);
1401
+ const diff = DiffDetector.detect(schema, currentFields);
1402
+ const enrichedCurrentLayout = enrichLayoutWithFields(currentLayout, currentFields);
1403
+ const hasLayoutChanges = DiffDetector.detectLayoutChanges(schema.layout, enrichedCurrentLayout);
1404
+ return {
1405
+ entries: diff.entries.map((entry) => ({
1406
+ type: entry.type,
1407
+ fieldCode: entry.fieldCode,
1408
+ fieldLabel: entry.fieldLabel,
1409
+ details: entry.details,
1410
+ ...entry.before ? { before: toFieldDto(entry.before) } : {},
1411
+ ...entry.after ? { after: toFieldDto(entry.after) } : {}
1412
+ })),
1413
+ schemaFields: Array.from(schema.fields.entries()).map(([code, def]) => ({
1414
+ fieldCode: code,
1415
+ fieldLabel: def.label,
1416
+ fieldType: def.type
1417
+ })),
1418
+ summary: diff.summary,
1419
+ isEmpty: diff.isEmpty && !hasLayoutChanges,
1420
+ hasLayoutChanges
1421
+ };
1422
+ }
1423
+
1424
+ //#endregion
1425
+ //#region src/core/application/formSchema/deployApp.ts
1426
+ async function deployApp({ container }) {
1427
+ await container.appDeployer.deploy();
1428
+ }
1429
+
1430
+ //#endregion
1431
+ //#region src/cli/output.ts
1432
+ function printDiffResult(result) {
1433
+ const { summary } = result;
1434
+ if (result.isEmpty) {
1435
+ p.log.info("No changes detected.");
1436
+ return;
1437
+ }
1438
+ const summaryParts = [
1439
+ summary.added > 0 ? pc.green(`+${summary.added} added`) : null,
1440
+ summary.modified > 0 ? pc.yellow(`~${summary.modified} modified`) : null,
1441
+ summary.deleted > 0 ? pc.red(`-${summary.deleted} deleted`) : null
1442
+ ].filter(Boolean).join(pc.dim(" | "));
1443
+ p.log.info(`Changes: ${summaryParts}`);
1444
+ if (result.hasLayoutChanges) p.log.info("Layout changes detected.");
1445
+ const lines = result.entries.map((entry) => {
1446
+ const colorize = entry.type === "added" ? pc.green : entry.type === "deleted" ? pc.red : pc.yellow;
1447
+ return `${colorize(entry.type === "added" ? "+" : entry.type === "deleted" ? "-" : "~")} ${pc.dim("[")}${colorize(entry.fieldCode)}${pc.dim("]")} ${entry.fieldLabel}${pc.dim(":")} ${entry.details}`;
1448
+ });
1449
+ p.note(lines.join("\n"), "Diff Details");
1450
+ }
1451
+ async function promptDeploy(container) {
1452
+ const shouldDeploy = await p.confirm({ message: "運用環境に反映しますか?" });
1453
+ if (p.isCancel(shouldDeploy) || !shouldDeploy) {
1454
+ p.log.warn("テスト環境に反映済みですが、運用環境には反映されていません。");
1455
+ return;
1456
+ }
1457
+ const ds = p.spinner();
1458
+ ds.start("運用環境に反映しています...");
1459
+ await deployApp({ container });
1460
+ ds.stop("運用環境への反映が完了しました。");
1461
+ p.log.success("運用環境への反映が完了しました。");
1462
+ }
1463
+
1464
+ //#endregion
1465
+ //#region src/cli/commands/diff.ts
1466
+ var diff_default = define({
1467
+ name: "diff",
1468
+ description: "Detect differences between schema file and current kintone form",
1469
+ args: { ...kintoneArgs },
1470
+ run: async (ctx) => {
1471
+ try {
1472
+ const container = createCliContainer(resolveConfig(ctx.values));
1473
+ const s = p.spinner();
1474
+ s.start("Fetching form schema...");
1475
+ const result = await detectDiff({ container });
1476
+ s.stop("Form schema fetched.");
1477
+ printDiffResult(result);
1478
+ } catch (error) {
1479
+ handleCliError(error);
1480
+ }
1481
+ }
1482
+ });
1483
+
1484
+ //#endregion
1485
+ //#region src/cli/commands/dump.ts
1486
+ var dump_default = define({
1487
+ name: "dump",
1488
+ description: "Dump current kintone form fields and layout as JSON",
1489
+ args: { ...kintoneArgs },
1490
+ run: async (ctx) => {
1491
+ try {
1492
+ const config = resolveConfig(ctx.values);
1493
+ const client = new KintoneRestAPIClient({
1494
+ baseUrl: config.baseUrl,
1495
+ auth: {
1496
+ username: config.username,
1497
+ password: config.password
1498
+ }
1499
+ });
1500
+ const s = p.spinner();
1501
+ s.start("Fetching form fields and layout...");
1502
+ const [fields, layout] = await Promise.all([client.app.getFormFields({ app: config.appId }), client.app.getFormLayout({ app: config.appId })]);
1503
+ s.stop("Form data fetched.");
1504
+ await Promise.all([writeFile("fields.json", JSON.stringify(fields, null, 2)), writeFile("layout.json", JSON.stringify(layout, null, 2))]);
1505
+ p.log.success(`Saved ${pc.cyan("fields.json")} and ${pc.cyan("layout.json")}`);
1506
+ } catch (error) {
1507
+ handleCliError(error);
1508
+ }
1509
+ }
1510
+ });
1511
+
1512
+ //#endregion
1513
+ //#region src/core/application/formSchema/executeMigration.ts
1514
+ async function executeMigration({ container }) {
1515
+ const schema = parseSchemaText(await container.schemaStorage.get());
1516
+ const [currentFields, currentLayout] = await Promise.all([container.formConfigurator.getFields(), container.formConfigurator.getLayout()]);
1517
+ const diff = DiffDetector.detect(schema, currentFields);
1518
+ const enrichedCurrentLayout = enrichLayoutWithFields(currentLayout, currentFields);
1519
+ const hasLayoutChanges = DiffDetector.detectLayoutChanges(schema.layout, enrichedCurrentLayout);
1520
+ if (diff.isEmpty && !hasLayoutChanges) return;
1521
+ const subtableInnerCodes = collectSubtableInnerFieldCodes(schema.fields);
1522
+ const added = diff.entries.filter((e) => e.type === "added");
1523
+ const modified = diff.entries.filter((e) => e.type === "modified");
1524
+ const deleted = diff.entries.filter((e) => e.type === "deleted");
1525
+ if (added.length > 0) {
1526
+ const fields = added.filter((e) => e.after !== void 0).filter((e) => !subtableInnerCodes.has(e.fieldCode)).map((e) => e.after);
1527
+ if (fields.length > 0) await container.formConfigurator.addFields(fields);
1528
+ }
1529
+ if (modified.length > 0) {
1530
+ const fields = modified.filter((e) => e.after !== void 0).filter((e) => !subtableInnerCodes.has(e.fieldCode)).map((e) => e.after);
1531
+ if (fields.length > 0) await container.formConfigurator.updateFields(fields);
1532
+ }
1533
+ if (deleted.length > 0) {
1534
+ const currentSubtableInnerCodes = collectSubtableInnerFieldCodes(currentFields);
1535
+ const fieldCodes = deleted.filter((e) => !currentSubtableInnerCodes.has(e.fieldCode)).map((e) => e.fieldCode);
1536
+ if (fieldCodes.length > 0) await container.formConfigurator.deleteFields(fieldCodes);
1537
+ }
1538
+ if (hasLayoutChanges) await container.formConfigurator.updateLayout(schema.layout);
1539
+ }
1540
+
1541
+ //#endregion
1542
+ //#region src/cli/commands/migrate.ts
1543
+ var migrate_default = define({
1544
+ name: "migrate",
1545
+ description: "Apply schema changes to kintone form",
1546
+ args: { ...kintoneArgs },
1547
+ run: async (ctx) => {
1548
+ try {
1549
+ const container = createCliContainer(resolveConfig(ctx.values));
1550
+ const s = p.spinner();
1551
+ s.start("Detecting changes...");
1552
+ const result = await detectDiff({ container });
1553
+ s.stop("Changes detected.");
1554
+ if (result.isEmpty) {
1555
+ p.log.success("No changes detected. Form is up to date.");
1556
+ return;
1557
+ }
1558
+ printDiffResult(result);
1559
+ const shouldContinue = await p.confirm({ message: "Apply these changes?" });
1560
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
1561
+ p.cancel("Migration cancelled.");
1562
+ process.exit(0);
1563
+ }
1564
+ const ms = p.spinner();
1565
+ ms.start("Applying migration...");
1566
+ await executeMigration({ container });
1567
+ ms.stop("Migration applied.");
1568
+ p.log.success("Migration completed successfully.");
1569
+ await promptDeploy(container);
1570
+ } catch (error) {
1571
+ handleCliError(error);
1572
+ }
1573
+ }
1574
+ });
1575
+
1576
+ //#endregion
1577
+ //#region src/core/application/formSchema/forceOverrideForm.ts
1578
+ async function forceOverrideForm({ container }) {
1579
+ const schema = parseSchemaText(await container.schemaStorage.get());
1580
+ const currentFields = await container.formConfigurator.getFields();
1581
+ const subtableInnerCodes = collectSubtableInnerFieldCodes(schema.fields);
1582
+ const toAdd = [];
1583
+ const toUpdate = [];
1584
+ const toDelete = [];
1585
+ for (const [fieldCode, schemaDef] of schema.fields) {
1586
+ if (subtableInnerCodes.has(fieldCode)) continue;
1587
+ if (currentFields.has(fieldCode)) toUpdate.push(schemaDef);
1588
+ else toAdd.push(schemaDef);
1589
+ }
1590
+ const currentSubtableInnerCodes = collectSubtableInnerFieldCodes(currentFields);
1591
+ for (const fieldCode of currentFields.keys()) {
1592
+ if (currentSubtableInnerCodes.has(fieldCode)) continue;
1593
+ if (!schema.fields.has(fieldCode)) toDelete.push(fieldCode);
1594
+ }
1595
+ if (toAdd.length > 0) await container.formConfigurator.addFields(toAdd);
1596
+ if (toUpdate.length > 0) await container.formConfigurator.updateFields(toUpdate);
1597
+ if (toDelete.length > 0) await container.formConfigurator.deleteFields(toDelete);
1598
+ await container.formConfigurator.updateLayout(schema.layout);
1599
+ }
1600
+
1601
+ //#endregion
1602
+ //#region src/cli/commands/override.ts
1603
+ var override_default = define({
1604
+ name: "override",
1605
+ description: "Force override kintone form with declared schema",
1606
+ args: { ...kintoneArgs },
1607
+ run: async (ctx) => {
1608
+ try {
1609
+ const container = createCliContainer(resolveConfig(ctx.values));
1610
+ p.log.warn(`${pc.bold(pc.red("WARNING:"))} This will replace the entire form with the declared schema.`);
1611
+ p.log.warn("Fields not defined in the schema will be deleted.");
1612
+ const shouldContinue = await p.confirm({ message: "Are you sure you want to force override?" });
1613
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
1614
+ p.cancel("Force override cancelled.");
1615
+ process.exit(0);
1616
+ }
1617
+ const s = p.spinner();
1618
+ s.start("Force overriding form...");
1619
+ await forceOverrideForm({ container });
1620
+ s.stop("Force override applied.");
1621
+ p.log.success("Force override completed successfully.");
1622
+ await promptDeploy(container);
1623
+ } catch (error) {
1624
+ handleCliError(error);
1625
+ }
1626
+ }
1627
+ });
1628
+
1629
+ //#endregion
1630
+ //#region src/cli/index.ts
1631
+ function loadVersion() {
1632
+ try {
1633
+ return createRequire(import.meta.url)("../package.json").version;
1634
+ } catch {
1635
+ return "0.0.0-dev";
1636
+ }
1637
+ }
1638
+ const main = define({
1639
+ name: "kintone-migrator",
1640
+ description: "kintone form schema migration tool",
1641
+ run: () => {}
1642
+ });
1643
+ await cli(process.argv.slice(2), main, {
1644
+ name: "kintone-migrator",
1645
+ version: loadVersion(),
1646
+ subCommands: {
1647
+ diff: diff_default,
1648
+ migrate: migrate_default,
1649
+ override: override_default,
1650
+ capture: capture_default,
1651
+ dump: dump_default
1652
+ }
1653
+ });
1654
+
1655
+ //#endregion
1656
+ export { };