kintone-migrator 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -178,12 +178,17 @@ Applies seed data (records) to a kintone app using upsert (insert or update base
178
178
  kintone-migrator seed
179
179
  kintone-migrator seed -s my-seed.yaml
180
180
 
181
+ # Clean apply: delete all existing records, then apply seed data
182
+ kintone-migrator seed --clean
183
+ kintone-migrator seed --clean --yes
184
+
181
185
  # Capture records from a kintone app
182
186
  kintone-migrator seed --capture --key-field customer_code
183
187
  kintone-migrator seed --capture --key-field customer_code -s seeds/customer.yaml
184
188
 
185
189
  # Multi-app mode
186
190
  kintone-migrator seed --all
191
+ kintone-migrator seed --clean --all --yes
187
192
  kintone-migrator seed --capture --key-field code --all
188
193
  kintone-migrator seed --app customer
189
194
  ```
@@ -193,8 +198,10 @@ kintone-migrator seed --app customer
193
198
  | CLI Argument | Description |
194
199
  |---------|------|
195
200
  | `--capture` | Capture mode: fetch records from kintone and save to seed file |
201
+ | `--clean` | Delete all existing records before applying seed data. Cannot be used with `--capture`. Prompts for confirmation unless `--yes` is specified. |
196
202
  | `--key-field`, `-k` | Key field code for upsert (required for `--capture`) |
197
203
  | `--seed-file`, `-s` | Seed file path (default: `seed.yaml`) |
204
+ | `--yes`, `-y` | Skip confirmation prompts (for `--clean` mode) |
198
205
 
199
206
  ### `customize`
200
207
 
package/dist/index.mjs CHANGED
@@ -845,6 +845,25 @@ var KintoneRecordManager = class {
845
845
  throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to update records", error);
846
846
  }
847
847
  }
848
+ async deleteAllRecords() {
849
+ try {
850
+ const records = await this.client.record.getAllRecords({
851
+ app: this.appId,
852
+ fields: ["$id"]
853
+ });
854
+ if (records.length === 0) return { deletedCount: 0 };
855
+ const validated = records.map((r) => toKintoneRecordForResponse(r));
856
+ await this.client.record.deleteAllRecords({
857
+ app: this.appId,
858
+ records: validated.map((r) => ({ id: Number(r.$id.value) }))
859
+ });
860
+ return { deletedCount: records.length };
861
+ } catch (error) {
862
+ if (isBusinessRuleError(error)) throw error;
863
+ if (error instanceof SystemError) throw error;
864
+ throw new SystemError(SystemErrorCode.ExternalApiError, "Failed to delete all records", error);
865
+ }
866
+ }
848
867
  };
849
868
 
850
869
  //#endregion
@@ -3643,10 +3662,22 @@ const UpsertPlanner = { plan: (key, seedRecords, existingRecords) => {
3643
3662
 
3644
3663
  //#endregion
3645
3664
  //#region src/core/application/seedData/upsertSeed.ts
3646
- async function upsertSeed({ container }) {
3665
+ async function upsertSeed({ container, input }) {
3647
3666
  const result = await container.seedStorage.get();
3648
3667
  if (!result.exists) throw new ValidationError(ValidationErrorCode.InvalidInput, "Seed file not found");
3649
3668
  const seedData = SeedParser.parse(result.content);
3669
+ if (input.clean) {
3670
+ const { deletedCount } = await container.recordManager.deleteAllRecords();
3671
+ const kintoneRecords = seedData.records.map(RecordConverter.toKintoneRecord);
3672
+ if (kintoneRecords.length > 0) await container.recordManager.addRecords(kintoneRecords);
3673
+ return {
3674
+ added: seedData.records.length,
3675
+ updated: 0,
3676
+ unchanged: 0,
3677
+ deleted: deletedCount,
3678
+ total: seedData.records.length
3679
+ };
3680
+ }
3650
3681
  if (seedData.key === null) {
3651
3682
  const kintoneRecords = seedData.records.map(RecordConverter.toKintoneRecord);
3652
3683
  if (kintoneRecords.length > 0) await container.recordManager.addRecords(kintoneRecords);
@@ -3654,6 +3685,7 @@ async function upsertSeed({ container }) {
3654
3685
  added: seedData.records.length,
3655
3686
  updated: 0,
3656
3687
  unchanged: 0,
3688
+ deleted: 0,
3657
3689
  total: seedData.records.length
3658
3690
  };
3659
3691
  }
@@ -3674,6 +3706,7 @@ async function upsertSeed({ container }) {
3674
3706
  added: plan.toAdd.length,
3675
3707
  updated: plan.toUpdate.length,
3676
3708
  unchanged: plan.unchanged,
3709
+ deleted: 0,
3677
3710
  total: plan.toAdd.length + plan.toUpdate.length + plan.unchanged
3678
3711
  };
3679
3712
  }
@@ -3683,10 +3716,15 @@ async function upsertSeed({ container }) {
3683
3716
  const seedArgs = {
3684
3717
  ...kintoneArgs,
3685
3718
  ...multiAppArgs,
3719
+ ...confirmArgs,
3686
3720
  capture: {
3687
3721
  type: "boolean",
3688
3722
  description: "Capture records from kintone app to seed file"
3689
3723
  },
3724
+ clean: {
3725
+ type: "boolean",
3726
+ description: "Delete all existing records before applying seed data (clean apply)"
3727
+ },
3690
3728
  "key-field": {
3691
3729
  type: "string",
3692
3730
  short: "k",
@@ -3699,7 +3737,11 @@ const seedArgs = {
3699
3737
  }
3700
3738
  };
3701
3739
  function resolveSeedMode(values) {
3702
- if (values.capture !== true) return { type: "upsert" };
3740
+ if (values.capture === true && values.clean === true) throw new ValidationError(ValidationErrorCode.InvalidInput, "--capture and --clean cannot be used together");
3741
+ if (values.capture !== true) return {
3742
+ type: "upsert",
3743
+ clean: values.clean === true
3744
+ };
3703
3745
  const keyField = values["key-field"];
3704
3746
  if (!keyField) throw new ValidationError(ValidationErrorCode.InvalidInput, "--key-field is required when using --capture mode");
3705
3747
  return {
@@ -3732,18 +3774,33 @@ function resolveSeedAppConfig(app, projectConfig, cliValues) {
3732
3774
  }
3733
3775
  function printUpsertResult(result) {
3734
3776
  const parts = [
3777
+ result.deleted > 0 ? pc.red(`-${result.deleted} deleted`) : null,
3735
3778
  result.added > 0 ? pc.green(`+${result.added} added`) : null,
3736
3779
  result.updated > 0 ? pc.yellow(`~${result.updated} updated`) : null,
3737
3780
  result.unchanged > 0 ? `${result.unchanged} unchanged` : null
3738
3781
  ].filter(Boolean).join(pc.dim(" | "));
3739
3782
  p.log.info(`Records: ${parts} (${result.total} total)`);
3740
3783
  }
3741
- async function runUpsert(config) {
3784
+ async function confirmClean(skipConfirm) {
3785
+ p.log.warn(`${pc.bold(pc.red("WARNING:"))} This will delete ALL existing records before applying seed data.`);
3786
+ if (!skipConfirm) {
3787
+ const shouldContinue = await p.confirm({ message: "Are you sure you want to clean and re-apply seed data?" });
3788
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
3789
+ p.cancel("Clean seed cancelled.");
3790
+ process.exit(0);
3791
+ }
3792
+ }
3793
+ }
3794
+ async function runUpsert(config, clean, skipConfirm) {
3795
+ if (clean) await confirmClean(skipConfirm);
3742
3796
  const container = createSeedCliContainer(config);
3743
3797
  const s = p.spinner();
3744
- s.start("Applying seed data...");
3745
- const result = await upsertSeed({ container });
3746
- s.stop("Seed data applied.");
3798
+ s.start(clean ? "Cleaning and applying seed data..." : "Applying seed data...");
3799
+ const result = await upsertSeed({
3800
+ container,
3801
+ input: { clean }
3802
+ });
3803
+ s.stop(clean ? "Clean seed applied." : "Seed data applied.");
3747
3804
  printUpsertResult(result);
3748
3805
  }
3749
3806
  async function runSeedCapture(config, keyField) {
@@ -3762,9 +3819,9 @@ async function runSeedCapture(config, keyField) {
3762
3819
  p.log.success(`Seed saved to: ${pc.cyan(config.seedFilePath)} (${result.recordCount} records)`);
3763
3820
  if (result.hasExistingSeed) p.log.warn("Existing seed file was overwritten.");
3764
3821
  }
3765
- async function runSeedOperation(config, mode) {
3822
+ async function runSeedOperation(config, mode, skipConfirm) {
3766
3823
  if (mode.type === "capture") await runSeedCapture(config, mode.keyField);
3767
- else await runUpsert(config);
3824
+ else await runUpsert(config, mode.clean, skipConfirm);
3768
3825
  }
3769
3826
  var seed_default = define({
3770
3827
  name: "seed",
@@ -3774,18 +3831,20 @@ var seed_default = define({
3774
3831
  try {
3775
3832
  const values = ctx.values;
3776
3833
  const mode = resolveSeedMode(values);
3834
+ const skipConfirm = values.yes === true;
3777
3835
  await routeMultiApp(values, {
3778
3836
  singleLegacy: async () => {
3779
- await runSeedOperation(resolveSeedConfig(values), mode);
3837
+ await runSeedOperation(resolveSeedConfig(values), mode, skipConfirm);
3780
3838
  },
3781
3839
  singleApp: async (app, projectConfig) => {
3782
- await runSeedOperation(resolveSeedAppConfig(app, projectConfig, values), mode);
3840
+ await runSeedOperation(resolveSeedAppConfig(app, projectConfig, values), mode, skipConfirm);
3783
3841
  },
3784
3842
  multiApp: async (plan, projectConfig) => {
3843
+ if (mode.type === "upsert" && mode.clean) await confirmClean(skipConfirm);
3785
3844
  await runMultiAppWithFailCheck(plan, async (app) => {
3786
3845
  const config = resolveSeedAppConfig(app, projectConfig, values);
3787
3846
  printAppHeader(app.name, app.appId);
3788
- await runSeedOperation(config, mode);
3847
+ await runSeedOperation(config, mode, true);
3789
3848
  }, mode.type === "capture" ? "All captures completed successfully." : "All seed applications completed successfully.");
3790
3849
  }
3791
3850
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kintone-migrator",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "kintone form schema migration tool",
5
5
  "license": "MIT",
6
6
  "author": "Hikaru Otabe",