@strapi/data-transfer 4.25.18 → 4.25.20

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 (40) hide show
  1. package/dist/commands/data-transfer.d.ts +1 -1
  2. package/dist/commands/data-transfer.d.ts.map +1 -1
  3. package/dist/commands/export/action.d.ts +1 -0
  4. package/dist/commands/export/action.d.ts.map +1 -1
  5. package/dist/commands/export/command.d.ts.map +1 -1
  6. package/dist/commands/import/action.d.ts +1 -0
  7. package/dist/commands/import/action.d.ts.map +1 -1
  8. package/dist/commands/import/command.d.ts.map +1 -1
  9. package/dist/commands/transfer/action.d.ts +1 -0
  10. package/dist/commands/transfer/action.d.ts.map +1 -1
  11. package/dist/commands/transfer/command.d.ts.map +1 -1
  12. package/dist/engine/index.d.ts +1 -1
  13. package/dist/engine/index.d.ts.map +1 -1
  14. package/dist/errors/constants.d.ts +1 -1
  15. package/dist/errors/constants.d.ts.map +1 -1
  16. package/dist/file/providers/destination/index.d.ts +2 -1
  17. package/dist/file/providers/destination/index.d.ts.map +1 -1
  18. package/dist/file/providers/source/index.d.ts.map +1 -1
  19. package/dist/index.js +282 -39
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +283 -40
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/strapi/providers/local-destination/index.d.ts +2 -1
  24. package/dist/strapi/providers/local-destination/index.d.ts.map +1 -1
  25. package/dist/strapi/providers/local-source/index.d.ts +3 -1
  26. package/dist/strapi/providers/local-source/index.d.ts.map +1 -1
  27. package/dist/strapi/providers/remote-destination/index.d.ts +2 -1
  28. package/dist/strapi/providers/remote-destination/index.d.ts.map +1 -1
  29. package/dist/strapi/providers/remote-source/index.d.ts.map +1 -1
  30. package/dist/strapi/providers/utils.d.ts +3 -2
  31. package/dist/strapi/providers/utils.d.ts.map +1 -1
  32. package/dist/strapi/remote/handlers/abstract.d.ts +4 -0
  33. package/dist/strapi/remote/handlers/abstract.d.ts.map +1 -1
  34. package/dist/strapi/remote/handlers/pull.d.ts.map +1 -1
  35. package/dist/strapi/remote/handlers/push.d.ts.map +1 -1
  36. package/dist/strapi/remote/handlers/utils.d.ts.map +1 -1
  37. package/dist/{engine → utils}/diagnostic.d.ts +1 -0
  38. package/dist/utils/diagnostic.d.ts.map +1 -0
  39. package/package.json +6 -6
  40. package/dist/engine/diagnostic.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -21,7 +21,7 @@ import tar$1 from "tar-stream";
21
21
  import { stringer } from "stream-json/jsonl/Stringer";
22
22
  import { Option, InvalidOptionArgumentError } from "commander";
23
23
  import Table from "cli-table3";
24
- import { createLogger, configs } from "@strapi/logger";
24
+ import { createLogger, configs, formats } from "@strapi/logger";
25
25
  import strapiFactory from "@strapi/strapi";
26
26
  import ora from "ora";
27
27
  import inquirer from "inquirer";
@@ -579,7 +579,7 @@ class TransferEngine {
579
579
  reportInfo(message, params) {
580
580
  this.diagnostics.report({
581
581
  kind: "info",
582
- details: { createdAt: /* @__PURE__ */ new Date(), message, params }
582
+ details: { createdAt: /* @__PURE__ */ new Date(), message, params, origin: "engine" }
583
583
  });
584
584
  }
585
585
  /**
@@ -871,8 +871,8 @@ ${formattedDiffs}`,
871
871
  */
872
872
  async bootstrap() {
873
873
  const results = await Promise.allSettled([
874
- this.sourceProvider.bootstrap?.(),
875
- this.destinationProvider.bootstrap?.()
874
+ this.sourceProvider.bootstrap?.(this.diagnostics),
875
+ this.destinationProvider.bootstrap?.(this.diagnostics)
876
876
  ]);
877
877
  results.forEach((result) => {
878
878
  if (result.status === "rejected") {
@@ -1863,6 +1863,7 @@ class LocalStrapiDestinationProvider {
1863
1863
  transaction;
1864
1864
  uploadsBackupDirectoryName;
1865
1865
  onWarning;
1866
+ #diagnostics;
1866
1867
  /**
1867
1868
  * The entities mapper is used to map old entities to their new IDs
1868
1869
  */
@@ -1872,12 +1873,14 @@ class LocalStrapiDestinationProvider {
1872
1873
  this.#entitiesMapper = {};
1873
1874
  this.uploadsBackupDirectoryName = `uploads_backup_${Date.now()}`;
1874
1875
  }
1875
- async bootstrap() {
1876
+ async bootstrap(diagnostics) {
1877
+ this.#diagnostics = diagnostics;
1876
1878
  this.#validateOptions();
1877
1879
  this.strapi = await this.options.getStrapi();
1878
1880
  if (!this.strapi) {
1879
1881
  throw new ProviderInitializationError("Could not access local strapi");
1880
1882
  }
1883
+ this.strapi.db.lifecycles.disable();
1881
1884
  this.transaction = createTransaction(this.strapi);
1882
1885
  }
1883
1886
  // TODO: either move this to restore strategy, or restore strategy should given access to these instead of repeating the logic possibly in a different way
@@ -1889,14 +1892,27 @@ class LocalStrapiDestinationProvider {
1889
1892
  const excluded = this.options.restore?.entities?.exclude && this.options.restore?.entities.exclude.includes(type);
1890
1893
  return !excluded && !notIncluded;
1891
1894
  };
1895
+ #reportInfo(message) {
1896
+ this.#diagnostics?.report({
1897
+ details: {
1898
+ createdAt: /* @__PURE__ */ new Date(),
1899
+ message,
1900
+ origin: "local-destination-provider"
1901
+ },
1902
+ kind: "info"
1903
+ });
1904
+ }
1892
1905
  async close() {
1893
1906
  const { autoDestroy } = this.options;
1894
1907
  this.transaction?.end();
1908
+ assertValidStrapi(this.strapi);
1909
+ this.strapi.db.lifecycles.enable();
1895
1910
  if (autoDestroy === void 0 || autoDestroy === true) {
1896
1911
  await this.strapi?.destroy();
1897
1912
  }
1898
1913
  }
1899
1914
  #validateOptions() {
1915
+ this.#reportInfo("validating options");
1900
1916
  if (!VALID_CONFLICT_STRATEGIES.includes(this.options.strategy)) {
1901
1917
  throw new ProviderValidationError(`Invalid strategy ${this.options.strategy}`, {
1902
1918
  check: "strategy",
@@ -1913,10 +1929,12 @@ class LocalStrapiDestinationProvider {
1913
1929
  if (!this.options.restore) {
1914
1930
  throw new ProviderValidationError("Missing restore options");
1915
1931
  }
1932
+ this.#reportInfo("deleting record");
1916
1933
  return deleteRecords(this.strapi, this.options.restore);
1917
1934
  }
1918
1935
  async #deleteAllAssets(trx) {
1919
1936
  assertValidStrapi(this.strapi);
1937
+ this.#reportInfo("deleting all assets");
1920
1938
  if (!this.#areAssetsIncluded()) {
1921
1939
  return;
1922
1940
  }
@@ -1929,9 +1947,12 @@ class LocalStrapiDestinationProvider {
1929
1947
  }
1930
1948
  }
1931
1949
  }
1950
+ this.#reportInfo("deleted all assets");
1932
1951
  }
1933
1952
  async rollback() {
1953
+ this.#reportInfo("Rolling back transaction");
1934
1954
  await this.transaction?.rollback();
1955
+ this.#reportInfo("Rolled back transaction");
1935
1956
  }
1936
1957
  async beforeTransfer() {
1937
1958
  if (!this.strapi) {
@@ -1950,6 +1971,7 @@ class LocalStrapiDestinationProvider {
1950
1971
  });
1951
1972
  }
1952
1973
  getMetadata() {
1974
+ this.#reportInfo("getting metadata");
1953
1975
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1954
1976
  const strapiVersion = this.strapi.config.get("info.strapi");
1955
1977
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1961,6 +1983,7 @@ class LocalStrapiDestinationProvider {
1961
1983
  };
1962
1984
  }
1963
1985
  getSchemas() {
1986
+ this.#reportInfo("getting schema");
1964
1987
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1965
1988
  const schemas = {
1966
1989
  ...this.strapi.contentTypes,
@@ -1970,6 +1993,7 @@ class LocalStrapiDestinationProvider {
1970
1993
  }
1971
1994
  createEntitiesWriteStream() {
1972
1995
  assertValidStrapi(this.strapi, "Not able to import entities");
1996
+ this.#reportInfo("creating entities stream");
1973
1997
  const { strategy } = this.options;
1974
1998
  const updateMappingTable = (type, oldID, newID) => {
1975
1999
  if (!this.#entitiesMapper[type]) {
@@ -1996,6 +2020,7 @@ class LocalStrapiDestinationProvider {
1996
2020
  return;
1997
2021
  }
1998
2022
  if (this.strapi.config.get("plugin.upload").provider === "local") {
2023
+ this.#reportInfo("creating assets backup directory");
1999
2024
  const assetsDirectory = path.join(this.strapi.dirs.static.public, "uploads");
2000
2025
  const backupDirectory = path.join(
2001
2026
  this.strapi.dirs.static.public,
@@ -2011,6 +2036,7 @@ class LocalStrapiDestinationProvider {
2011
2036
  await fse.move(assetsDirectory, backupDirectory);
2012
2037
  await fse.mkdir(assetsDirectory);
2013
2038
  await fse.outputFile(path.join(assetsDirectory, ".gitkeep"), "");
2039
+ this.#reportInfo(`created assets backup directory ${backupDirectory}`);
2014
2040
  } catch (err) {
2015
2041
  throw new ProviderTransferError(
2016
2042
  "The backup folder for the assets could not be created inside the public folder. Please ensure Strapi has write permissions on the public directory",
@@ -2028,17 +2054,20 @@ class LocalStrapiDestinationProvider {
2028
2054
  return;
2029
2055
  }
2030
2056
  if (this.strapi.config.get("plugin.upload").provider === "local") {
2057
+ this.#reportInfo("removing assets backup");
2031
2058
  assertValidStrapi(this.strapi);
2032
2059
  const backupDirectory = path.join(
2033
2060
  this.strapi.dirs.static.public,
2034
2061
  this.uploadsBackupDirectoryName
2035
2062
  );
2036
2063
  await fse.rm(backupDirectory, { recursive: true, force: true });
2064
+ this.#reportInfo("successfully removed assets backup");
2037
2065
  }
2038
2066
  }
2039
2067
  // TODO: Move this logic to the restore strategy
2040
2068
  async createAssetsWriteStream() {
2041
2069
  assertValidStrapi(this.strapi, "Not able to stream Assets");
2070
+ this.#reportInfo("creating assets write stream");
2042
2071
  if (!this.#areAssetsIncluded()) {
2043
2072
  throw new ProviderTransferError(
2044
2073
  "Attempting to transfer assets when `assets` is not set in restore options"
@@ -2133,6 +2162,7 @@ class LocalStrapiDestinationProvider {
2133
2162
  }
2134
2163
  async createConfigurationWriteStream() {
2135
2164
  assertValidStrapi(this.strapi, "Not able to stream Configurations");
2165
+ this.#reportInfo("creating configuration write stream");
2136
2166
  const { strategy } = this.options;
2137
2167
  if (strategy === "restore") {
2138
2168
  return createConfigurationWriteStream(this.strapi, this.transaction);
@@ -2144,6 +2174,7 @@ class LocalStrapiDestinationProvider {
2144
2174
  });
2145
2175
  }
2146
2176
  async createLinksWriteStream() {
2177
+ this.#reportInfo("creating links write stream");
2147
2178
  if (!this.strapi) {
2148
2179
  throw new Error("Not able to stream links. Strapi instance not found");
2149
2180
  }
@@ -2344,19 +2375,35 @@ class LocalStrapiSourceProvider {
2344
2375
  type = "source";
2345
2376
  options;
2346
2377
  strapi;
2378
+ #diagnostics;
2347
2379
  constructor(options) {
2348
2380
  this.options = options;
2349
2381
  }
2350
- async bootstrap() {
2382
+ async bootstrap(diagnostics) {
2383
+ this.#diagnostics = diagnostics;
2351
2384
  this.strapi = await this.options.getStrapi();
2385
+ this.strapi.db.lifecycles.disable();
2386
+ }
2387
+ #reportInfo(message) {
2388
+ this.#diagnostics?.report({
2389
+ details: {
2390
+ createdAt: /* @__PURE__ */ new Date(),
2391
+ message,
2392
+ origin: "local-source-provider"
2393
+ },
2394
+ kind: "info"
2395
+ });
2352
2396
  }
2353
2397
  async close() {
2354
2398
  const { autoDestroy } = this.options;
2399
+ assertValidStrapi(this.strapi);
2400
+ this.strapi.db.lifecycles.enable();
2355
2401
  if (autoDestroy === void 0 || autoDestroy === true) {
2356
2402
  await this.strapi?.destroy();
2357
2403
  }
2358
2404
  }
2359
2405
  getMetadata() {
2406
+ this.#reportInfo("getting metadata");
2360
2407
  const strapiVersion = strapi.config.get("info.strapi");
2361
2408
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2362
2409
  return {
@@ -2368,6 +2415,7 @@ class LocalStrapiSourceProvider {
2368
2415
  }
2369
2416
  async createEntitiesReadStream() {
2370
2417
  assertValidStrapi(this.strapi, "Not able to stream entities");
2418
+ this.#reportInfo("creating entities read stream");
2371
2419
  return chain([
2372
2420
  // Entities stream
2373
2421
  createEntitiesStream(this.strapi),
@@ -2377,14 +2425,17 @@ class LocalStrapiSourceProvider {
2377
2425
  }
2378
2426
  createLinksReadStream() {
2379
2427
  assertValidStrapi(this.strapi, "Not able to stream links");
2428
+ this.#reportInfo("creating links read stream");
2380
2429
  return createLinksStream(this.strapi);
2381
2430
  }
2382
2431
  createConfigurationReadStream() {
2383
2432
  assertValidStrapi(this.strapi, "Not able to stream configuration");
2433
+ this.#reportInfo("creating configuration read stream");
2384
2434
  return createConfigurationStream(this.strapi);
2385
2435
  }
2386
2436
  getSchemas() {
2387
2437
  assertValidStrapi(this.strapi, "Not able to get Schemas");
2438
+ this.#reportInfo("getting schemas");
2388
2439
  const schemas = {
2389
2440
  ...this.strapi.contentTypes,
2390
2441
  ...this.strapi.components
@@ -2396,13 +2447,14 @@ class LocalStrapiSourceProvider {
2396
2447
  }
2397
2448
  createAssetsReadStream() {
2398
2449
  assertValidStrapi(this.strapi, "Not able to stream assets");
2450
+ this.#reportInfo("creating assets read stream");
2399
2451
  return createAssetsStream(this.strapi);
2400
2452
  }
2401
2453
  }
2402
2454
  const createDispatcher = (ws, retryMessageOptions = {
2403
2455
  retryMessageMaxRetries: 5,
2404
2456
  retryMessageTimeout: 3e4
2405
- }) => {
2457
+ }, reportInfo) => {
2406
2458
  const state = {};
2407
2459
  const dispatch = async (message, options = {}) => {
2408
2460
  if (!ws) {
@@ -2415,6 +2467,16 @@ const createDispatcher = (ws, retryMessageOptions = {
2415
2467
  if (options.attachTransfer) {
2416
2468
  Object.assign(payload, { transferID: state.transfer?.id });
2417
2469
  }
2470
+ if (message.type === "command") {
2471
+ reportInfo?.(
2472
+ `dispatching message command:${message.command} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2473
+ );
2474
+ } else if (message.type === "transfer") {
2475
+ const messageToSend = message;
2476
+ reportInfo?.(
2477
+ `dispatching message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2478
+ );
2479
+ }
2418
2480
  const stringifiedPayload = JSON.stringify(payload);
2419
2481
  ws.send(stringifiedPayload, (error) => {
2420
2482
  if (error) {
@@ -2437,6 +2499,16 @@ const createDispatcher = (ws, retryMessageOptions = {
2437
2499
  const interval = setInterval(sendPeriodically, retryMessageTimeout);
2438
2500
  const onResponse = (raw) => {
2439
2501
  const response = JSON.parse(raw.toString());
2502
+ if (message.type === "command") {
2503
+ reportInfo?.(
2504
+ `received response to message command: ${message.command} uuid: ${uuid} sent: ${numberOfTimesMessageWasSent}`
2505
+ );
2506
+ } else if (message.type === "transfer") {
2507
+ const messageToSend = message;
2508
+ reportInfo?.(
2509
+ `received response to message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2510
+ );
2511
+ }
2440
2512
  if (response.uuid === uuid) {
2441
2513
  clearInterval(interval);
2442
2514
  if (response.error) {
@@ -2493,7 +2565,7 @@ const createDispatcher = (ws, retryMessageOptions = {
2493
2565
  dispatchTransferStep
2494
2566
  };
2495
2567
  };
2496
- const connectToWebsocket = (address, options) => {
2568
+ const connectToWebsocket = (address, options, diagnostics) => {
2497
2569
  return new Promise((resolve, reject2) => {
2498
2570
  const server = new WebSocket(address, options);
2499
2571
  server.once("open", () => {
@@ -2527,6 +2599,14 @@ const connectToWebsocket = (address, options) => {
2527
2599
  )
2528
2600
  );
2529
2601
  });
2602
+ server.on("message", (raw) => {
2603
+ const response = JSON.parse(raw.toString());
2604
+ if (response.diagnostic) {
2605
+ diagnostics?.report({
2606
+ ...response.diagnostic
2607
+ });
2608
+ }
2609
+ });
2530
2610
  server.once("error", (err) => {
2531
2611
  reject2(
2532
2612
  new ProviderTransferError(err.message, {
@@ -2567,6 +2647,7 @@ class RemoteStrapiDestinationProvider {
2567
2647
  ws;
2568
2648
  dispatcher;
2569
2649
  transferID;
2650
+ #diagnostics;
2570
2651
  constructor(options) {
2571
2652
  this.options = options;
2572
2653
  this.ws = null;
@@ -2665,7 +2746,18 @@ class RemoteStrapiDestinationProvider {
2665
2746
  }
2666
2747
  });
2667
2748
  }
2668
- async bootstrap() {
2749
+ #reportInfo(message) {
2750
+ this.#diagnostics?.report({
2751
+ details: {
2752
+ createdAt: /* @__PURE__ */ new Date(),
2753
+ message,
2754
+ origin: "remote-destination-provider"
2755
+ },
2756
+ kind: "info"
2757
+ });
2758
+ }
2759
+ async bootstrap(diagnostics) {
2760
+ this.#diagnostics = diagnostics;
2669
2761
  const { url, auth } = this.options;
2670
2762
  const validProtocols = ["https:", "http:"];
2671
2763
  let ws;
@@ -2682,6 +2774,7 @@ class RemoteStrapiDestinationProvider {
2682
2774
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
2683
2775
  url.pathname
2684
2776
  )}${TRANSFER_PATH}/push`;
2777
+ this.#reportInfo("establishing websocket connection");
2685
2778
  if (!auth) {
2686
2779
  ws = await connectToWebsocket(wsUrl);
2687
2780
  } else if (auth.type === "token") {
@@ -2695,10 +2788,19 @@ class RemoteStrapiDestinationProvider {
2695
2788
  }
2696
2789
  });
2697
2790
  }
2791
+ this.#reportInfo("established websocket connection");
2698
2792
  this.ws = ws;
2699
2793
  const { retryMessageOptions } = this.options;
2700
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
2794
+ this.#reportInfo("creating dispatcher");
2795
+ this.dispatcher = createDispatcher(
2796
+ this.ws,
2797
+ retryMessageOptions,
2798
+ (message) => this.#reportInfo(message)
2799
+ );
2800
+ this.#reportInfo("created dispatcher");
2801
+ this.#reportInfo("initialize transfer");
2701
2802
  this.transferID = await this.initTransfer();
2803
+ this.#reportInfo(`initialized transfer ${this.transferID}`);
2702
2804
  this.dispatcher.setTransferProperties({ id: this.transferID, kind: "push" });
2703
2805
  await this.dispatcher.dispatchTransferAction("bootstrap");
2704
2806
  }
@@ -2819,6 +2921,7 @@ class RemoteStrapiSourceProvider {
2819
2921
  options;
2820
2922
  ws;
2821
2923
  dispatcher;
2924
+ #diagnostics;
2822
2925
  constructor(options) {
2823
2926
  this.options = options;
2824
2927
  this.ws = null;
@@ -3007,6 +3110,16 @@ class RemoteStrapiSourceProvider {
3007
3110
  }
3008
3111
  return res.transferID;
3009
3112
  }
3113
+ #reportInfo(message) {
3114
+ this.#diagnostics?.report({
3115
+ details: {
3116
+ createdAt: /* @__PURE__ */ new Date(),
3117
+ message,
3118
+ origin: "remote-source-provider"
3119
+ },
3120
+ kind: "info"
3121
+ });
3122
+ }
3010
3123
  async bootstrap() {
3011
3124
  const { url, auth } = this.options;
3012
3125
  let ws;
@@ -3015,6 +3128,7 @@ class RemoteStrapiSourceProvider {
3015
3128
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
3016
3129
  url.pathname
3017
3130
  )}${TRANSFER_PATH}/pull`;
3131
+ this.#reportInfo("establishing websocket connection");
3018
3132
  if (!auth) {
3019
3133
  ws = await connectToWebsocket(wsUrl);
3020
3134
  } else if (auth.type === "token") {
@@ -3028,10 +3142,19 @@ class RemoteStrapiSourceProvider {
3028
3142
  }
3029
3143
  });
3030
3144
  }
3145
+ this.#reportInfo("established websocket connection");
3031
3146
  this.ws = ws;
3032
3147
  const { retryMessageOptions } = this.options;
3033
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
3148
+ this.#reportInfo("creating dispatcher");
3149
+ this.dispatcher = createDispatcher(
3150
+ this.ws,
3151
+ retryMessageOptions,
3152
+ (message) => this.#reportInfo(message)
3153
+ );
3154
+ this.#reportInfo("creating dispatcher");
3155
+ this.#reportInfo("initialize transfer");
3034
3156
  const transferID = await this.initTransfer();
3157
+ this.#reportInfo(`initialized transfer ${transferID}`);
3035
3158
  this.dispatcher.setTransferProperties({ id: transferID, kind: "pull" });
3036
3159
  await this.dispatcher.dispatchTransferAction("bootstrap");
3037
3160
  }
@@ -3251,6 +3374,8 @@ const handleWSUpgrade = (wss, ctx, callback) => {
3251
3374
  return;
3252
3375
  }
3253
3376
  disableTimeouts();
3377
+ strapi.db.lifecycles.disable();
3378
+ strapi.log.info("[Data transfer] Disabling lifecycle hooks");
3254
3379
  wss.emit("connection", client, ctx.req);
3255
3380
  callback(client, request);
3256
3381
  });
@@ -3263,6 +3388,7 @@ const handlerControllerFactory = (implementation) => (options) => {
3263
3388
  const cb = (ws) => {
3264
3389
  const state = { id: void 0 };
3265
3390
  const messageUUIDs = /* @__PURE__ */ new Set();
3391
+ const diagnostics = createDiagnosticReporter();
3266
3392
  const cannotRespondHandler = (err) => {
3267
3393
  strapi?.log?.error(
3268
3394
  "[Data transfer] Cannot send error response to client, closing connection"
@@ -3296,6 +3422,9 @@ const handlerControllerFactory = (implementation) => (options) => {
3296
3422
  set response(response) {
3297
3423
  state.response = response;
3298
3424
  },
3425
+ get diagnostics() {
3426
+ return diagnostics;
3427
+ },
3299
3428
  addUUID(uuid) {
3300
3429
  messageUUIDs.add(uuid);
3301
3430
  },
@@ -3411,6 +3540,10 @@ const handlerControllerFactory = (implementation) => (options) => {
3411
3540
  onError() {
3412
3541
  },
3413
3542
  onClose() {
3543
+ },
3544
+ onInfo() {
3545
+ },
3546
+ onWarning() {
3414
3547
  }
3415
3548
  };
3416
3549
  const handler = Object.assign(Object.create(prototype), implementation(prototype));
@@ -3423,6 +3556,8 @@ const handlerControllerFactory = (implementation) => (options) => {
3423
3556
  cannotRespondHandler(err);
3424
3557
  } finally {
3425
3558
  resetTimeouts();
3559
+ strapi.db.lifecycles.enable();
3560
+ strapi.log.info("[Data transfer] Restoring lifecycle hooks");
3426
3561
  }
3427
3562
  });
3428
3563
  ws.on("error", async (...args) => {
@@ -3443,6 +3578,14 @@ const handlerControllerFactory = (implementation) => (options) => {
3443
3578
  cannotRespondHandler(err);
3444
3579
  }
3445
3580
  });
3581
+ diagnostics.onDiagnostic((diagnostic) => {
3582
+ const uuid = randomUUID();
3583
+ const payload = JSON.stringify({
3584
+ diagnostic,
3585
+ uuid
3586
+ });
3587
+ handler.send(payload);
3588
+ });
3446
3589
  };
3447
3590
  try {
3448
3591
  handleWSUpgrade(wss, ctx, cb);
@@ -3478,6 +3621,26 @@ const createPushController = handlerControllerFactory((proto) => ({
3478
3621
  verifyAuth() {
3479
3622
  return proto.verifyAuth.call(this, TRANSFER_KIND$1);
3480
3623
  },
3624
+ onInfo(message) {
3625
+ this.diagnostics?.report({
3626
+ details: {
3627
+ message,
3628
+ origin: "push-handler",
3629
+ createdAt: /* @__PURE__ */ new Date()
3630
+ },
3631
+ kind: "info"
3632
+ });
3633
+ },
3634
+ onWarning(message) {
3635
+ this.diagnostics?.report({
3636
+ details: {
3637
+ message,
3638
+ createdAt: /* @__PURE__ */ new Date(),
3639
+ origin: "push-handler"
3640
+ },
3641
+ kind: "warning"
3642
+ });
3643
+ },
3481
3644
  cleanup() {
3482
3645
  proto.cleanup.call(this);
3483
3646
  this.streams = {};
@@ -3554,6 +3717,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3554
3717
  proto.addUUID(uuid);
3555
3718
  if (type === "command") {
3556
3719
  const { command: command2 } = msg;
3720
+ this.onInfo(`received command:${command2} uuid:${uuid}`);
3557
3721
  await this.executeAndRespond(uuid, () => {
3558
3722
  this.assertValidTransferCommand(command2);
3559
3723
  if (command2 === "status") {
@@ -3562,6 +3726,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3562
3726
  return this[command2](msg.params);
3563
3727
  });
3564
3728
  } else if (type === "transfer") {
3729
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3565
3730
  await this.executeAndRespond(uuid, async () => {
3566
3731
  await this.verifyAuth();
3567
3732
  this.assertValidTransfer();
@@ -3656,6 +3821,9 @@ const createPushController = handlerControllerFactory((proto) => ({
3656
3821
  }
3657
3822
  this.flow?.set(step);
3658
3823
  }
3824
+ if (action2 === "bootstrap") {
3825
+ return this.provider?.[action2](this.diagnostics);
3826
+ }
3659
3827
  return this.provider?.[action2]();
3660
3828
  },
3661
3829
  async streamAsset(payload) {
@@ -3713,6 +3881,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3713
3881
  getStrapi: () => strapi
3714
3882
  });
3715
3883
  this.provider.onWarning = (message) => {
3884
+ this.onWarning(message);
3716
3885
  strapi.log.warn(message);
3717
3886
  };
3718
3887
  return { transferID: this.transferID };
@@ -3753,6 +3922,26 @@ const createPullController = handlerControllerFactory((proto) => ({
3753
3922
  this.streams = {};
3754
3923
  delete this.provider;
3755
3924
  },
3925
+ onInfo(message) {
3926
+ this.diagnostics?.report({
3927
+ details: {
3928
+ message,
3929
+ origin: "pull-handler",
3930
+ createdAt: /* @__PURE__ */ new Date()
3931
+ },
3932
+ kind: "info"
3933
+ });
3934
+ },
3935
+ onWarning(message) {
3936
+ this.diagnostics?.report({
3937
+ details: {
3938
+ message,
3939
+ createdAt: /* @__PURE__ */ new Date(),
3940
+ origin: "pull-handler"
3941
+ },
3942
+ kind: "warning"
3943
+ });
3944
+ },
3756
3945
  assertValidTransferAction(action2) {
3757
3946
  const validActions = VALID_TRANSFER_ACTIONS;
3758
3947
  if (validActions.includes(action2)) {
@@ -3782,6 +3971,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3782
3971
  proto.addUUID(uuid);
3783
3972
  if (type === "command") {
3784
3973
  const { command: command2 } = msg;
3974
+ this.onInfo(`received command:${command2} uuid:${uuid}`);
3785
3975
  await this.executeAndRespond(uuid, () => {
3786
3976
  this.assertValidTransferCommand(command2);
3787
3977
  if (command2 === "status") {
@@ -3790,6 +3980,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3790
3980
  return this[command2](msg.params);
3791
3981
  });
3792
3982
  } else if (type === "transfer") {
3983
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3793
3984
  await this.executeAndRespond(uuid, async () => {
3794
3985
  await this.verifyAuth();
3795
3986
  this.assertValidTransfer();
@@ -3811,6 +4002,9 @@ const createPullController = handlerControllerFactory((proto) => ({
3811
4002
  async onTransferAction(msg) {
3812
4003
  const { action: action2 } = msg;
3813
4004
  this.assertValidTransferAction(action2);
4005
+ if (action2 === "bootstrap") {
4006
+ return this.provider?.[action2](this.diagnostics);
4007
+ }
3814
4008
  return this.provider?.[action2]();
3815
4009
  },
3816
4010
  async flush(stage, id) {
@@ -4009,6 +4203,7 @@ class LocalFileSourceProvider {
4009
4203
  name = "source::local-file";
4010
4204
  options;
4011
4205
  #metadata;
4206
+ #diagnostics;
4012
4207
  constructor(options) {
4013
4208
  this.options = options;
4014
4209
  const { encryption } = this.options;
@@ -4016,6 +4211,16 @@ class LocalFileSourceProvider {
4016
4211
  throw new Error("Missing encryption key");
4017
4212
  }
4018
4213
  }
4214
+ #reportInfo(message) {
4215
+ this.#diagnostics?.report({
4216
+ details: {
4217
+ createdAt: /* @__PURE__ */ new Date(),
4218
+ message,
4219
+ origin: "file-source-provider"
4220
+ },
4221
+ kind: "info"
4222
+ });
4223
+ }
4019
4224
  /**
4020
4225
  * Pre flight checks regarding the provided options, making sure that the file can be opened (decrypted, decompressed), etc.
4021
4226
  */
@@ -4044,12 +4249,14 @@ class LocalFileSourceProvider {
4044
4249
  return this.#parseJSONFile(backupStream, path2);
4045
4250
  }
4046
4251
  async getMetadata() {
4252
+ this.#reportInfo("getting metadata");
4047
4253
  if (!this.#metadata) {
4048
4254
  await this.#loadMetadata();
4049
4255
  }
4050
4256
  return this.#metadata ?? null;
4051
4257
  }
4052
4258
  async getSchemas() {
4259
+ this.#reportInfo("getting schemas");
4053
4260
  const schemas = await collect(this.createSchemasReadStream());
4054
4261
  if (isEmpty(schemas)) {
4055
4262
  throw new ProviderInitializationError("Could not load schemas from Strapi data file.");
@@ -4057,21 +4264,26 @@ class LocalFileSourceProvider {
4057
4264
  return keyBy("uid", schemas);
4058
4265
  }
4059
4266
  createEntitiesReadStream() {
4267
+ this.#reportInfo("creating entities read stream");
4060
4268
  return this.#streamJsonlDirectory("entities");
4061
4269
  }
4062
4270
  createSchemasReadStream() {
4271
+ this.#reportInfo("creating schemas read stream");
4063
4272
  return this.#streamJsonlDirectory("schemas");
4064
4273
  }
4065
4274
  createLinksReadStream() {
4275
+ this.#reportInfo("creating links read stream");
4066
4276
  return this.#streamJsonlDirectory("links");
4067
4277
  }
4068
4278
  createConfigurationReadStream() {
4279
+ this.#reportInfo("creating configuration read stream");
4069
4280
  return this.#streamJsonlDirectory("configuration");
4070
4281
  }
4071
4282
  createAssetsReadStream() {
4072
4283
  const inStream = this.#getBackupStream();
4073
4284
  const outStream = new PassThrough({ objectMode: true });
4074
4285
  const loadAssetMetadata = this.#loadAssetMetadata.bind(this);
4286
+ this.#reportInfo("creating assets read stream");
4075
4287
  pipeline(
4076
4288
  [
4077
4289
  inStream,
@@ -4272,9 +4484,20 @@ class LocalFileDestinationProvider {
4272
4484
  results = {};
4273
4485
  #providersMetadata = {};
4274
4486
  #archive = {};
4487
+ #diagnostics;
4275
4488
  constructor(options) {
4276
4489
  this.options = options;
4277
4490
  }
4491
+ #reportInfo(message) {
4492
+ this.#diagnostics?.report({
4493
+ details: {
4494
+ createdAt: /* @__PURE__ */ new Date(),
4495
+ message,
4496
+ origin: "file-destination-provider"
4497
+ },
4498
+ kind: "info"
4499
+ });
4500
+ }
4278
4501
  get #archivePath() {
4279
4502
  const { encryption, compression, file: file2 } = this.options;
4280
4503
  let filePath = `${file2.path}.tar`;
@@ -4291,9 +4514,11 @@ class LocalFileDestinationProvider {
4291
4514
  return this;
4292
4515
  }
4293
4516
  createGzip() {
4517
+ this.#reportInfo("creating gzip");
4294
4518
  return zip$1.createGzip();
4295
4519
  }
4296
- bootstrap() {
4520
+ bootstrap(diagnostics) {
4521
+ this.#diagnostics = diagnostics;
4297
4522
  const { compression, encryption } = this.options;
4298
4523
  if (encryption.enabled && !encryption.key) {
4299
4524
  throw new Error("Can't encrypt without a key");
@@ -4332,6 +4557,7 @@ class LocalFileDestinationProvider {
4332
4557
  }
4333
4558
  }
4334
4559
  async rollback() {
4560
+ this.#reportInfo("rolling back");
4335
4561
  await this.close();
4336
4562
  await rm(this.#archivePath, { force: true });
4337
4563
  }
@@ -4339,6 +4565,7 @@ class LocalFileDestinationProvider {
4339
4565
  return null;
4340
4566
  }
4341
4567
  async #writeMetadata() {
4568
+ this.#reportInfo("writing metadata");
4342
4569
  const metadata = this.#providersMetadata.source;
4343
4570
  if (metadata) {
4344
4571
  await new Promise((resolve) => {
@@ -4359,6 +4586,7 @@ class LocalFileDestinationProvider {
4359
4586
  if (!this.#archive.stream) {
4360
4587
  throw new Error("Archive stream is unavailable");
4361
4588
  }
4589
+ this.#reportInfo("creating schemas write stream");
4362
4590
  const filePathFactory = createFilePathFactory("schemas");
4363
4591
  const entryStream = createTarEntryStream(
4364
4592
  this.#archive.stream,
@@ -4371,6 +4599,7 @@ class LocalFileDestinationProvider {
4371
4599
  if (!this.#archive.stream) {
4372
4600
  throw new Error("Archive stream is unavailable");
4373
4601
  }
4602
+ this.#reportInfo("creating entities write stream");
4374
4603
  const filePathFactory = createFilePathFactory("entities");
4375
4604
  const entryStream = createTarEntryStream(
4376
4605
  this.#archive.stream,
@@ -4383,6 +4612,7 @@ class LocalFileDestinationProvider {
4383
4612
  if (!this.#archive.stream) {
4384
4613
  throw new Error("Archive stream is unavailable");
4385
4614
  }
4615
+ this.#reportInfo("creating links write stream");
4386
4616
  const filePathFactory = createFilePathFactory("links");
4387
4617
  const entryStream = createTarEntryStream(
4388
4618
  this.#archive.stream,
@@ -4395,6 +4625,7 @@ class LocalFileDestinationProvider {
4395
4625
  if (!this.#archive.stream) {
4396
4626
  throw new Error("Archive stream is unavailable");
4397
4627
  }
4628
+ this.#reportInfo("creating configuration write stream");
4398
4629
  const filePathFactory = createFilePathFactory("configuration");
4399
4630
  const entryStream = createTarEntryStream(
4400
4631
  this.#archive.stream,
@@ -4408,6 +4639,7 @@ class LocalFileDestinationProvider {
4408
4639
  if (!archiveStream) {
4409
4640
  throw new Error("Archive stream is unavailable");
4410
4641
  }
4642
+ this.#reportInfo("creating assets write stream");
4411
4643
  return new Writable({
4412
4644
  objectMode: true,
4413
4645
  write(data, _encoding, callback) {
@@ -4743,30 +4975,41 @@ const errorColors = {
4743
4975
  error: chalk.red,
4744
4976
  silly: chalk.yellow
4745
4977
  };
4746
- const formatDiagnostic = (operation) => ({ details, kind }) => {
4747
- const logger = createLogger(
4748
- configs.createOutputFileConfiguration(`${operation}_error_log_${Date.now()}.log`)
4749
- );
4750
- try {
4751
- if (kind === "error") {
4752
- const { message, severity = "fatal" } = details;
4753
- const colorizeError = errorColors[severity];
4754
- const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
4755
- logger.error(errorMessage);
4756
- }
4757
- if (kind === "info") {
4758
- const { message, params } = details;
4759
- const msg = `${message}
4760
- ${params ? JSON.stringify(params, null, 2) : ""}`;
4761
- logger.info(msg);
4978
+ const formatDiagnostic = (operation, info) => {
4979
+ let logger;
4980
+ const getLogger = () => {
4981
+ if (!logger) {
4982
+ logger = createLogger(
4983
+ configs.createOutputFileConfiguration(`${operation}_${Date.now()}.log`, {
4984
+ level: "info",
4985
+ format: formats?.detailedLogs
4986
+ })
4987
+ );
4762
4988
  }
4763
- if (kind === "warning") {
4764
- const { origin: origin2, message } = details;
4765
- logger.warn(`(${origin2 ?? "transfer"}) ${message}`);
4989
+ return logger;
4990
+ };
4991
+ return ({ details, kind }) => {
4992
+ try {
4993
+ if (kind === "error") {
4994
+ const { message, severity = "fatal" } = details;
4995
+ const colorizeError = errorColors[severity];
4996
+ const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
4997
+ getLogger().error(errorMessage);
4998
+ }
4999
+ if (kind === "info" && info) {
5000
+ const { message, params, origin: origin2 } = details;
5001
+ const msg = `[${origin2 ?? "transfer"}] ${message}
5002
+ ${params ? JSON.stringify(params, null, 2) : ""}`;
5003
+ getLogger().info(msg);
5004
+ }
5005
+ if (kind === "warning") {
5006
+ const { origin: origin2, message } = details;
5007
+ getLogger().warn(`(${origin2 ?? "transfer"}) ${message}`);
5008
+ }
5009
+ } catch (err) {
5010
+ getLogger().error(err);
4766
5011
  }
4767
- } catch (err) {
4768
- logger.error(err);
4769
- }
5012
+ };
4770
5013
  };
4771
5014
  const loadersFactory = (defaultLoaders = {}) => {
4772
5015
  const loaders = defaultLoaders;
@@ -4948,7 +5191,7 @@ const action$2 = async (opts) => {
4948
5191
  ]
4949
5192
  }
4950
5193
  });
4951
- engine.diagnostics.onDiagnostic(formatDiagnostic("export"));
5194
+ engine.diagnostics.onDiagnostic(formatDiagnostic("export", opts.verbose));
4952
5195
  const progress = engine.progress.stream;
4953
5196
  const { updateLoader } = loadersFactory();
4954
5197
  progress.on(`stage::start`, ({ stage, data }) => {
@@ -5020,7 +5263,7 @@ const command$2 = ({ command: command2 }) => {
5020
5263
  )
5021
5264
  ).addOption(
5022
5265
  new Option("--no-compress", "Disables gzip compression of output file").default(true)
5023
- ).addOption(
5266
+ ).addOption(new Option("--verbose", "Enable verbose logs")).addOption(
5024
5267
  new Option(
5025
5268
  "-k, --key <string>",
5026
5269
  "Provide encryption key in command instead of using the prompt"
@@ -5076,7 +5319,7 @@ const action$1 = async (opts) => {
5076
5319
  destination.onWarning = (message) => console.warn(`
5077
5320
  ${chalk.yellow("warn")}: ${message}`);
5078
5321
  const engine2 = createTransferEngine$1(source, destination, engineOptions);
5079
- engine2.diagnostics.onDiagnostic(formatDiagnostic("import"));
5322
+ engine2.diagnostics.onDiagnostic(formatDiagnostic("import", opts.verbose));
5080
5323
  const progress = engine2.progress.stream;
5081
5324
  const { updateLoader } = loadersFactory();
5082
5325
  engine2.onSchemaDiff(getDiffHandler(engine2, { force: opts.force, action: "import" }));
@@ -5134,7 +5377,7 @@ const command$1 = ({ command: command2 }) => {
5134
5377
  "-k, --key <string>",
5135
5378
  "Provide encryption key in command instead of using the prompt"
5136
5379
  )
5137
- ).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook("preAction", async (thisCommand) => {
5380
+ ).addOption(new Option("--verbose", "Enable verbose logs")).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook("preAction", async (thisCommand) => {
5138
5381
  const opts = thisCommand.opts();
5139
5382
  const ext = path.extname(String(opts.file));
5140
5383
  if (ext === ".enc") {
@@ -5264,7 +5507,7 @@ const action = async (opts) => {
5264
5507
  ]
5265
5508
  }
5266
5509
  });
5267
- engine.diagnostics.onDiagnostic(formatDiagnostic("transfer"));
5510
+ engine.diagnostics.onDiagnostic(formatDiagnostic("transfer", opts.verbose));
5268
5511
  const progress = engine.progress.stream;
5269
5512
  const { updateLoader } = loadersFactory();
5270
5513
  engine.onSchemaDiff(getDiffHandler(engine, { force: opts.force, action: "transfer" }));
@@ -5316,7 +5559,7 @@ const command = ({ command: command2 }) => {
5316
5559
  "--to <destinationURL>",
5317
5560
  `URL of the remote Strapi instance to send data to`
5318
5561
  ).argParser(parseURL)
5319
- ).addOption(new Option("--to-token <token>", `Transfer token for the remote Strapi destination`)).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook(
5562
+ ).addOption(new Option("--to-token <token>", `Transfer token for the remote Strapi destination`)).addOption(new Option("--verbose", "Enable verbose logs")).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook(
5320
5563
  "preAction",
5321
5564
  ifOptions(
5322
5565
  (opts) => !(opts.from || opts.to) || opts.from && opts.to,