@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.js CHANGED
@@ -612,7 +612,7 @@ class TransferEngine {
612
612
  reportInfo(message, params) {
613
613
  this.diagnostics.report({
614
614
  kind: "info",
615
- details: { createdAt: /* @__PURE__ */ new Date(), message, params }
615
+ details: { createdAt: /* @__PURE__ */ new Date(), message, params, origin: "engine" }
616
616
  });
617
617
  }
618
618
  /**
@@ -904,8 +904,8 @@ ${formattedDiffs}`,
904
904
  */
905
905
  async bootstrap() {
906
906
  const results = await Promise.allSettled([
907
- this.sourceProvider.bootstrap?.(),
908
- this.destinationProvider.bootstrap?.()
907
+ this.sourceProvider.bootstrap?.(this.diagnostics),
908
+ this.destinationProvider.bootstrap?.(this.diagnostics)
909
909
  ]);
910
910
  results.forEach((result) => {
911
911
  if (result.status === "rejected") {
@@ -1896,6 +1896,7 @@ class LocalStrapiDestinationProvider {
1896
1896
  transaction;
1897
1897
  uploadsBackupDirectoryName;
1898
1898
  onWarning;
1899
+ #diagnostics;
1899
1900
  /**
1900
1901
  * The entities mapper is used to map old entities to their new IDs
1901
1902
  */
@@ -1905,12 +1906,14 @@ class LocalStrapiDestinationProvider {
1905
1906
  this.#entitiesMapper = {};
1906
1907
  this.uploadsBackupDirectoryName = `uploads_backup_${Date.now()}`;
1907
1908
  }
1908
- async bootstrap() {
1909
+ async bootstrap(diagnostics) {
1910
+ this.#diagnostics = diagnostics;
1909
1911
  this.#validateOptions();
1910
1912
  this.strapi = await this.options.getStrapi();
1911
1913
  if (!this.strapi) {
1912
1914
  throw new ProviderInitializationError("Could not access local strapi");
1913
1915
  }
1916
+ this.strapi.db.lifecycles.disable();
1914
1917
  this.transaction = createTransaction(this.strapi);
1915
1918
  }
1916
1919
  // 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
@@ -1922,14 +1925,27 @@ class LocalStrapiDestinationProvider {
1922
1925
  const excluded = this.options.restore?.entities?.exclude && this.options.restore?.entities.exclude.includes(type);
1923
1926
  return !excluded && !notIncluded;
1924
1927
  };
1928
+ #reportInfo(message) {
1929
+ this.#diagnostics?.report({
1930
+ details: {
1931
+ createdAt: /* @__PURE__ */ new Date(),
1932
+ message,
1933
+ origin: "local-destination-provider"
1934
+ },
1935
+ kind: "info"
1936
+ });
1937
+ }
1925
1938
  async close() {
1926
1939
  const { autoDestroy } = this.options;
1927
1940
  this.transaction?.end();
1941
+ assertValidStrapi(this.strapi);
1942
+ this.strapi.db.lifecycles.enable();
1928
1943
  if (autoDestroy === void 0 || autoDestroy === true) {
1929
1944
  await this.strapi?.destroy();
1930
1945
  }
1931
1946
  }
1932
1947
  #validateOptions() {
1948
+ this.#reportInfo("validating options");
1933
1949
  if (!VALID_CONFLICT_STRATEGIES.includes(this.options.strategy)) {
1934
1950
  throw new ProviderValidationError(`Invalid strategy ${this.options.strategy}`, {
1935
1951
  check: "strategy",
@@ -1946,10 +1962,12 @@ class LocalStrapiDestinationProvider {
1946
1962
  if (!this.options.restore) {
1947
1963
  throw new ProviderValidationError("Missing restore options");
1948
1964
  }
1965
+ this.#reportInfo("deleting record");
1949
1966
  return deleteRecords(this.strapi, this.options.restore);
1950
1967
  }
1951
1968
  async #deleteAllAssets(trx) {
1952
1969
  assertValidStrapi(this.strapi);
1970
+ this.#reportInfo("deleting all assets");
1953
1971
  if (!this.#areAssetsIncluded()) {
1954
1972
  return;
1955
1973
  }
@@ -1962,9 +1980,12 @@ class LocalStrapiDestinationProvider {
1962
1980
  }
1963
1981
  }
1964
1982
  }
1983
+ this.#reportInfo("deleted all assets");
1965
1984
  }
1966
1985
  async rollback() {
1986
+ this.#reportInfo("Rolling back transaction");
1967
1987
  await this.transaction?.rollback();
1988
+ this.#reportInfo("Rolled back transaction");
1968
1989
  }
1969
1990
  async beforeTransfer() {
1970
1991
  if (!this.strapi) {
@@ -1983,6 +2004,7 @@ class LocalStrapiDestinationProvider {
1983
2004
  });
1984
2005
  }
1985
2006
  getMetadata() {
2007
+ this.#reportInfo("getting metadata");
1986
2008
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1987
2009
  const strapiVersion = this.strapi.config.get("info.strapi");
1988
2010
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1994,6 +2016,7 @@ class LocalStrapiDestinationProvider {
1994
2016
  };
1995
2017
  }
1996
2018
  getSchemas() {
2019
+ this.#reportInfo("getting schema");
1997
2020
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1998
2021
  const schemas = {
1999
2022
  ...this.strapi.contentTypes,
@@ -2003,6 +2026,7 @@ class LocalStrapiDestinationProvider {
2003
2026
  }
2004
2027
  createEntitiesWriteStream() {
2005
2028
  assertValidStrapi(this.strapi, "Not able to import entities");
2029
+ this.#reportInfo("creating entities stream");
2006
2030
  const { strategy } = this.options;
2007
2031
  const updateMappingTable = (type, oldID, newID) => {
2008
2032
  if (!this.#entitiesMapper[type]) {
@@ -2029,6 +2053,7 @@ class LocalStrapiDestinationProvider {
2029
2053
  return;
2030
2054
  }
2031
2055
  if (this.strapi.config.get("plugin.upload").provider === "local") {
2056
+ this.#reportInfo("creating assets backup directory");
2032
2057
  const assetsDirectory = path__default.default.join(this.strapi.dirs.static.public, "uploads");
2033
2058
  const backupDirectory = path__default.default.join(
2034
2059
  this.strapi.dirs.static.public,
@@ -2044,6 +2069,7 @@ class LocalStrapiDestinationProvider {
2044
2069
  await fse__namespace.move(assetsDirectory, backupDirectory);
2045
2070
  await fse__namespace.mkdir(assetsDirectory);
2046
2071
  await fse__namespace.outputFile(path__default.default.join(assetsDirectory, ".gitkeep"), "");
2072
+ this.#reportInfo(`created assets backup directory ${backupDirectory}`);
2047
2073
  } catch (err) {
2048
2074
  throw new ProviderTransferError(
2049
2075
  "The backup folder for the assets could not be created inside the public folder. Please ensure Strapi has write permissions on the public directory",
@@ -2061,17 +2087,20 @@ class LocalStrapiDestinationProvider {
2061
2087
  return;
2062
2088
  }
2063
2089
  if (this.strapi.config.get("plugin.upload").provider === "local") {
2090
+ this.#reportInfo("removing assets backup");
2064
2091
  assertValidStrapi(this.strapi);
2065
2092
  const backupDirectory = path__default.default.join(
2066
2093
  this.strapi.dirs.static.public,
2067
2094
  this.uploadsBackupDirectoryName
2068
2095
  );
2069
2096
  await fse__namespace.rm(backupDirectory, { recursive: true, force: true });
2097
+ this.#reportInfo("successfully removed assets backup");
2070
2098
  }
2071
2099
  }
2072
2100
  // TODO: Move this logic to the restore strategy
2073
2101
  async createAssetsWriteStream() {
2074
2102
  assertValidStrapi(this.strapi, "Not able to stream Assets");
2103
+ this.#reportInfo("creating assets write stream");
2075
2104
  if (!this.#areAssetsIncluded()) {
2076
2105
  throw new ProviderTransferError(
2077
2106
  "Attempting to transfer assets when `assets` is not set in restore options"
@@ -2166,6 +2195,7 @@ class LocalStrapiDestinationProvider {
2166
2195
  }
2167
2196
  async createConfigurationWriteStream() {
2168
2197
  assertValidStrapi(this.strapi, "Not able to stream Configurations");
2198
+ this.#reportInfo("creating configuration write stream");
2169
2199
  const { strategy } = this.options;
2170
2200
  if (strategy === "restore") {
2171
2201
  return createConfigurationWriteStream(this.strapi, this.transaction);
@@ -2177,6 +2207,7 @@ class LocalStrapiDestinationProvider {
2177
2207
  });
2178
2208
  }
2179
2209
  async createLinksWriteStream() {
2210
+ this.#reportInfo("creating links write stream");
2180
2211
  if (!this.strapi) {
2181
2212
  throw new Error("Not able to stream links. Strapi instance not found");
2182
2213
  }
@@ -2377,19 +2408,35 @@ class LocalStrapiSourceProvider {
2377
2408
  type = "source";
2378
2409
  options;
2379
2410
  strapi;
2411
+ #diagnostics;
2380
2412
  constructor(options) {
2381
2413
  this.options = options;
2382
2414
  }
2383
- async bootstrap() {
2415
+ async bootstrap(diagnostics) {
2416
+ this.#diagnostics = diagnostics;
2384
2417
  this.strapi = await this.options.getStrapi();
2418
+ this.strapi.db.lifecycles.disable();
2419
+ }
2420
+ #reportInfo(message) {
2421
+ this.#diagnostics?.report({
2422
+ details: {
2423
+ createdAt: /* @__PURE__ */ new Date(),
2424
+ message,
2425
+ origin: "local-source-provider"
2426
+ },
2427
+ kind: "info"
2428
+ });
2385
2429
  }
2386
2430
  async close() {
2387
2431
  const { autoDestroy } = this.options;
2432
+ assertValidStrapi(this.strapi);
2433
+ this.strapi.db.lifecycles.enable();
2388
2434
  if (autoDestroy === void 0 || autoDestroy === true) {
2389
2435
  await this.strapi?.destroy();
2390
2436
  }
2391
2437
  }
2392
2438
  getMetadata() {
2439
+ this.#reportInfo("getting metadata");
2393
2440
  const strapiVersion = strapi.config.get("info.strapi");
2394
2441
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2395
2442
  return {
@@ -2401,6 +2448,7 @@ class LocalStrapiSourceProvider {
2401
2448
  }
2402
2449
  async createEntitiesReadStream() {
2403
2450
  assertValidStrapi(this.strapi, "Not able to stream entities");
2451
+ this.#reportInfo("creating entities read stream");
2404
2452
  return streamChain.chain([
2405
2453
  // Entities stream
2406
2454
  createEntitiesStream(this.strapi),
@@ -2410,14 +2458,17 @@ class LocalStrapiSourceProvider {
2410
2458
  }
2411
2459
  createLinksReadStream() {
2412
2460
  assertValidStrapi(this.strapi, "Not able to stream links");
2461
+ this.#reportInfo("creating links read stream");
2413
2462
  return createLinksStream(this.strapi);
2414
2463
  }
2415
2464
  createConfigurationReadStream() {
2416
2465
  assertValidStrapi(this.strapi, "Not able to stream configuration");
2466
+ this.#reportInfo("creating configuration read stream");
2417
2467
  return createConfigurationStream(this.strapi);
2418
2468
  }
2419
2469
  getSchemas() {
2420
2470
  assertValidStrapi(this.strapi, "Not able to get Schemas");
2471
+ this.#reportInfo("getting schemas");
2421
2472
  const schemas = {
2422
2473
  ...this.strapi.contentTypes,
2423
2474
  ...this.strapi.components
@@ -2429,13 +2480,14 @@ class LocalStrapiSourceProvider {
2429
2480
  }
2430
2481
  createAssetsReadStream() {
2431
2482
  assertValidStrapi(this.strapi, "Not able to stream assets");
2483
+ this.#reportInfo("creating assets read stream");
2432
2484
  return createAssetsStream(this.strapi);
2433
2485
  }
2434
2486
  }
2435
2487
  const createDispatcher = (ws2, retryMessageOptions = {
2436
2488
  retryMessageMaxRetries: 5,
2437
2489
  retryMessageTimeout: 3e4
2438
- }) => {
2490
+ }, reportInfo) => {
2439
2491
  const state = {};
2440
2492
  const dispatch = async (message, options = {}) => {
2441
2493
  if (!ws2) {
@@ -2448,6 +2500,16 @@ const createDispatcher = (ws2, retryMessageOptions = {
2448
2500
  if (options.attachTransfer) {
2449
2501
  Object.assign(payload, { transferID: state.transfer?.id });
2450
2502
  }
2503
+ if (message.type === "command") {
2504
+ reportInfo?.(
2505
+ `dispatching message command:${message.command} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2506
+ );
2507
+ } else if (message.type === "transfer") {
2508
+ const messageToSend = message;
2509
+ reportInfo?.(
2510
+ `dispatching message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2511
+ );
2512
+ }
2451
2513
  const stringifiedPayload = JSON.stringify(payload);
2452
2514
  ws2.send(stringifiedPayload, (error) => {
2453
2515
  if (error) {
@@ -2470,6 +2532,16 @@ const createDispatcher = (ws2, retryMessageOptions = {
2470
2532
  const interval = setInterval(sendPeriodically, retryMessageTimeout);
2471
2533
  const onResponse = (raw) => {
2472
2534
  const response = JSON.parse(raw.toString());
2535
+ if (message.type === "command") {
2536
+ reportInfo?.(
2537
+ `received response to message command: ${message.command} uuid: ${uuid} sent: ${numberOfTimesMessageWasSent}`
2538
+ );
2539
+ } else if (message.type === "transfer") {
2540
+ const messageToSend = message;
2541
+ reportInfo?.(
2542
+ `received response to message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2543
+ );
2544
+ }
2473
2545
  if (response.uuid === uuid) {
2474
2546
  clearInterval(interval);
2475
2547
  if (response.error) {
@@ -2526,7 +2598,7 @@ const createDispatcher = (ws2, retryMessageOptions = {
2526
2598
  dispatchTransferStep
2527
2599
  };
2528
2600
  };
2529
- const connectToWebsocket = (address, options) => {
2601
+ const connectToWebsocket = (address, options, diagnostics) => {
2530
2602
  return new Promise((resolve, reject2) => {
2531
2603
  const server = new ws.WebSocket(address, options);
2532
2604
  server.once("open", () => {
@@ -2560,6 +2632,14 @@ const connectToWebsocket = (address, options) => {
2560
2632
  )
2561
2633
  );
2562
2634
  });
2635
+ server.on("message", (raw) => {
2636
+ const response = JSON.parse(raw.toString());
2637
+ if (response.diagnostic) {
2638
+ diagnostics?.report({
2639
+ ...response.diagnostic
2640
+ });
2641
+ }
2642
+ });
2563
2643
  server.once("error", (err) => {
2564
2644
  reject2(
2565
2645
  new ProviderTransferError(err.message, {
@@ -2600,6 +2680,7 @@ class RemoteStrapiDestinationProvider {
2600
2680
  ws;
2601
2681
  dispatcher;
2602
2682
  transferID;
2683
+ #diagnostics;
2603
2684
  constructor(options) {
2604
2685
  this.options = options;
2605
2686
  this.ws = null;
@@ -2698,7 +2779,18 @@ class RemoteStrapiDestinationProvider {
2698
2779
  }
2699
2780
  });
2700
2781
  }
2701
- async bootstrap() {
2782
+ #reportInfo(message) {
2783
+ this.#diagnostics?.report({
2784
+ details: {
2785
+ createdAt: /* @__PURE__ */ new Date(),
2786
+ message,
2787
+ origin: "remote-destination-provider"
2788
+ },
2789
+ kind: "info"
2790
+ });
2791
+ }
2792
+ async bootstrap(diagnostics) {
2793
+ this.#diagnostics = diagnostics;
2702
2794
  const { url, auth } = this.options;
2703
2795
  const validProtocols = ["https:", "http:"];
2704
2796
  let ws2;
@@ -2715,6 +2807,7 @@ class RemoteStrapiDestinationProvider {
2715
2807
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
2716
2808
  url.pathname
2717
2809
  )}${TRANSFER_PATH}/push`;
2810
+ this.#reportInfo("establishing websocket connection");
2718
2811
  if (!auth) {
2719
2812
  ws2 = await connectToWebsocket(wsUrl);
2720
2813
  } else if (auth.type === "token") {
@@ -2728,10 +2821,19 @@ class RemoteStrapiDestinationProvider {
2728
2821
  }
2729
2822
  });
2730
2823
  }
2824
+ this.#reportInfo("established websocket connection");
2731
2825
  this.ws = ws2;
2732
2826
  const { retryMessageOptions } = this.options;
2733
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
2827
+ this.#reportInfo("creating dispatcher");
2828
+ this.dispatcher = createDispatcher(
2829
+ this.ws,
2830
+ retryMessageOptions,
2831
+ (message) => this.#reportInfo(message)
2832
+ );
2833
+ this.#reportInfo("created dispatcher");
2834
+ this.#reportInfo("initialize transfer");
2734
2835
  this.transferID = await this.initTransfer();
2836
+ this.#reportInfo(`initialized transfer ${this.transferID}`);
2735
2837
  this.dispatcher.setTransferProperties({ id: this.transferID, kind: "push" });
2736
2838
  await this.dispatcher.dispatchTransferAction("bootstrap");
2737
2839
  }
@@ -2852,6 +2954,7 @@ class RemoteStrapiSourceProvider {
2852
2954
  options;
2853
2955
  ws;
2854
2956
  dispatcher;
2957
+ #diagnostics;
2855
2958
  constructor(options) {
2856
2959
  this.options = options;
2857
2960
  this.ws = null;
@@ -3040,6 +3143,16 @@ class RemoteStrapiSourceProvider {
3040
3143
  }
3041
3144
  return res.transferID;
3042
3145
  }
3146
+ #reportInfo(message) {
3147
+ this.#diagnostics?.report({
3148
+ details: {
3149
+ createdAt: /* @__PURE__ */ new Date(),
3150
+ message,
3151
+ origin: "remote-source-provider"
3152
+ },
3153
+ kind: "info"
3154
+ });
3155
+ }
3043
3156
  async bootstrap() {
3044
3157
  const { url, auth } = this.options;
3045
3158
  let ws2;
@@ -3048,6 +3161,7 @@ class RemoteStrapiSourceProvider {
3048
3161
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
3049
3162
  url.pathname
3050
3163
  )}${TRANSFER_PATH}/pull`;
3164
+ this.#reportInfo("establishing websocket connection");
3051
3165
  if (!auth) {
3052
3166
  ws2 = await connectToWebsocket(wsUrl);
3053
3167
  } else if (auth.type === "token") {
@@ -3061,10 +3175,19 @@ class RemoteStrapiSourceProvider {
3061
3175
  }
3062
3176
  });
3063
3177
  }
3178
+ this.#reportInfo("established websocket connection");
3064
3179
  this.ws = ws2;
3065
3180
  const { retryMessageOptions } = this.options;
3066
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
3181
+ this.#reportInfo("creating dispatcher");
3182
+ this.dispatcher = createDispatcher(
3183
+ this.ws,
3184
+ retryMessageOptions,
3185
+ (message) => this.#reportInfo(message)
3186
+ );
3187
+ this.#reportInfo("creating dispatcher");
3188
+ this.#reportInfo("initialize transfer");
3067
3189
  const transferID = await this.initTransfer();
3190
+ this.#reportInfo(`initialized transfer ${transferID}`);
3068
3191
  this.dispatcher.setTransferProperties({ id: transferID, kind: "pull" });
3069
3192
  await this.dispatcher.dispatchTransferAction("bootstrap");
3070
3193
  }
@@ -3284,6 +3407,8 @@ const handleWSUpgrade = (wss, ctx, callback) => {
3284
3407
  return;
3285
3408
  }
3286
3409
  disableTimeouts();
3410
+ strapi.db.lifecycles.disable();
3411
+ strapi.log.info("[Data transfer] Disabling lifecycle hooks");
3287
3412
  wss.emit("connection", client, ctx.req);
3288
3413
  callback(client, request);
3289
3414
  });
@@ -3296,6 +3421,7 @@ const handlerControllerFactory = (implementation) => (options) => {
3296
3421
  const cb = (ws2) => {
3297
3422
  const state = { id: void 0 };
3298
3423
  const messageUUIDs = /* @__PURE__ */ new Set();
3424
+ const diagnostics = createDiagnosticReporter();
3299
3425
  const cannotRespondHandler = (err) => {
3300
3426
  strapi?.log?.error(
3301
3427
  "[Data transfer] Cannot send error response to client, closing connection"
@@ -3329,6 +3455,9 @@ const handlerControllerFactory = (implementation) => (options) => {
3329
3455
  set response(response) {
3330
3456
  state.response = response;
3331
3457
  },
3458
+ get diagnostics() {
3459
+ return diagnostics;
3460
+ },
3332
3461
  addUUID(uuid) {
3333
3462
  messageUUIDs.add(uuid);
3334
3463
  },
@@ -3444,6 +3573,10 @@ const handlerControllerFactory = (implementation) => (options) => {
3444
3573
  onError() {
3445
3574
  },
3446
3575
  onClose() {
3576
+ },
3577
+ onInfo() {
3578
+ },
3579
+ onWarning() {
3447
3580
  }
3448
3581
  };
3449
3582
  const handler = Object.assign(Object.create(prototype), implementation(prototype));
@@ -3456,6 +3589,8 @@ const handlerControllerFactory = (implementation) => (options) => {
3456
3589
  cannotRespondHandler(err);
3457
3590
  } finally {
3458
3591
  resetTimeouts();
3592
+ strapi.db.lifecycles.enable();
3593
+ strapi.log.info("[Data transfer] Restoring lifecycle hooks");
3459
3594
  }
3460
3595
  });
3461
3596
  ws2.on("error", async (...args) => {
@@ -3476,6 +3611,14 @@ const handlerControllerFactory = (implementation) => (options) => {
3476
3611
  cannotRespondHandler(err);
3477
3612
  }
3478
3613
  });
3614
+ diagnostics.onDiagnostic((diagnostic) => {
3615
+ const uuid = crypto.randomUUID();
3616
+ const payload = JSON.stringify({
3617
+ diagnostic,
3618
+ uuid
3619
+ });
3620
+ handler.send(payload);
3621
+ });
3479
3622
  };
3480
3623
  try {
3481
3624
  handleWSUpgrade(wss, ctx, cb);
@@ -3511,6 +3654,26 @@ const createPushController = handlerControllerFactory((proto) => ({
3511
3654
  verifyAuth() {
3512
3655
  return proto.verifyAuth.call(this, TRANSFER_KIND$1);
3513
3656
  },
3657
+ onInfo(message) {
3658
+ this.diagnostics?.report({
3659
+ details: {
3660
+ message,
3661
+ origin: "push-handler",
3662
+ createdAt: /* @__PURE__ */ new Date()
3663
+ },
3664
+ kind: "info"
3665
+ });
3666
+ },
3667
+ onWarning(message) {
3668
+ this.diagnostics?.report({
3669
+ details: {
3670
+ message,
3671
+ createdAt: /* @__PURE__ */ new Date(),
3672
+ origin: "push-handler"
3673
+ },
3674
+ kind: "warning"
3675
+ });
3676
+ },
3514
3677
  cleanup() {
3515
3678
  proto.cleanup.call(this);
3516
3679
  this.streams = {};
@@ -3587,6 +3750,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3587
3750
  proto.addUUID(uuid);
3588
3751
  if (type === "command") {
3589
3752
  const { command: command2 } = msg;
3753
+ this.onInfo(`received command:${command2} uuid:${uuid}`);
3590
3754
  await this.executeAndRespond(uuid, () => {
3591
3755
  this.assertValidTransferCommand(command2);
3592
3756
  if (command2 === "status") {
@@ -3595,6 +3759,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3595
3759
  return this[command2](msg.params);
3596
3760
  });
3597
3761
  } else if (type === "transfer") {
3762
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3598
3763
  await this.executeAndRespond(uuid, async () => {
3599
3764
  await this.verifyAuth();
3600
3765
  this.assertValidTransfer();
@@ -3689,6 +3854,9 @@ const createPushController = handlerControllerFactory((proto) => ({
3689
3854
  }
3690
3855
  this.flow?.set(step);
3691
3856
  }
3857
+ if (action2 === "bootstrap") {
3858
+ return this.provider?.[action2](this.diagnostics);
3859
+ }
3692
3860
  return this.provider?.[action2]();
3693
3861
  },
3694
3862
  async streamAsset(payload) {
@@ -3746,6 +3914,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3746
3914
  getStrapi: () => strapi
3747
3915
  });
3748
3916
  this.provider.onWarning = (message) => {
3917
+ this.onWarning(message);
3749
3918
  strapi.log.warn(message);
3750
3919
  };
3751
3920
  return { transferID: this.transferID };
@@ -3786,6 +3955,26 @@ const createPullController = handlerControllerFactory((proto) => ({
3786
3955
  this.streams = {};
3787
3956
  delete this.provider;
3788
3957
  },
3958
+ onInfo(message) {
3959
+ this.diagnostics?.report({
3960
+ details: {
3961
+ message,
3962
+ origin: "pull-handler",
3963
+ createdAt: /* @__PURE__ */ new Date()
3964
+ },
3965
+ kind: "info"
3966
+ });
3967
+ },
3968
+ onWarning(message) {
3969
+ this.diagnostics?.report({
3970
+ details: {
3971
+ message,
3972
+ createdAt: /* @__PURE__ */ new Date(),
3973
+ origin: "pull-handler"
3974
+ },
3975
+ kind: "warning"
3976
+ });
3977
+ },
3789
3978
  assertValidTransferAction(action2) {
3790
3979
  const validActions = VALID_TRANSFER_ACTIONS;
3791
3980
  if (validActions.includes(action2)) {
@@ -3815,6 +4004,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3815
4004
  proto.addUUID(uuid);
3816
4005
  if (type === "command") {
3817
4006
  const { command: command2 } = msg;
4007
+ this.onInfo(`received command:${command2} uuid:${uuid}`);
3818
4008
  await this.executeAndRespond(uuid, () => {
3819
4009
  this.assertValidTransferCommand(command2);
3820
4010
  if (command2 === "status") {
@@ -3823,6 +4013,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3823
4013
  return this[command2](msg.params);
3824
4014
  });
3825
4015
  } else if (type === "transfer") {
4016
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3826
4017
  await this.executeAndRespond(uuid, async () => {
3827
4018
  await this.verifyAuth();
3828
4019
  this.assertValidTransfer();
@@ -3844,6 +4035,9 @@ const createPullController = handlerControllerFactory((proto) => ({
3844
4035
  async onTransferAction(msg) {
3845
4036
  const { action: action2 } = msg;
3846
4037
  this.assertValidTransferAction(action2);
4038
+ if (action2 === "bootstrap") {
4039
+ return this.provider?.[action2](this.diagnostics);
4040
+ }
3847
4041
  return this.provider?.[action2]();
3848
4042
  },
3849
4043
  async flush(stage, id) {
@@ -4042,6 +4236,7 @@ class LocalFileSourceProvider {
4042
4236
  name = "source::local-file";
4043
4237
  options;
4044
4238
  #metadata;
4239
+ #diagnostics;
4045
4240
  constructor(options) {
4046
4241
  this.options = options;
4047
4242
  const { encryption } = this.options;
@@ -4049,6 +4244,16 @@ class LocalFileSourceProvider {
4049
4244
  throw new Error("Missing encryption key");
4050
4245
  }
4051
4246
  }
4247
+ #reportInfo(message) {
4248
+ this.#diagnostics?.report({
4249
+ details: {
4250
+ createdAt: /* @__PURE__ */ new Date(),
4251
+ message,
4252
+ origin: "file-source-provider"
4253
+ },
4254
+ kind: "info"
4255
+ });
4256
+ }
4052
4257
  /**
4053
4258
  * Pre flight checks regarding the provided options, making sure that the file can be opened (decrypted, decompressed), etc.
4054
4259
  */
@@ -4077,12 +4282,14 @@ class LocalFileSourceProvider {
4077
4282
  return this.#parseJSONFile(backupStream, path2);
4078
4283
  }
4079
4284
  async getMetadata() {
4285
+ this.#reportInfo("getting metadata");
4080
4286
  if (!this.#metadata) {
4081
4287
  await this.#loadMetadata();
4082
4288
  }
4083
4289
  return this.#metadata ?? null;
4084
4290
  }
4085
4291
  async getSchemas() {
4292
+ this.#reportInfo("getting schemas");
4086
4293
  const schemas = await collect(this.createSchemasReadStream());
4087
4294
  if (fp.isEmpty(schemas)) {
4088
4295
  throw new ProviderInitializationError("Could not load schemas from Strapi data file.");
@@ -4090,21 +4297,26 @@ class LocalFileSourceProvider {
4090
4297
  return fp.keyBy("uid", schemas);
4091
4298
  }
4092
4299
  createEntitiesReadStream() {
4300
+ this.#reportInfo("creating entities read stream");
4093
4301
  return this.#streamJsonlDirectory("entities");
4094
4302
  }
4095
4303
  createSchemasReadStream() {
4304
+ this.#reportInfo("creating schemas read stream");
4096
4305
  return this.#streamJsonlDirectory("schemas");
4097
4306
  }
4098
4307
  createLinksReadStream() {
4308
+ this.#reportInfo("creating links read stream");
4099
4309
  return this.#streamJsonlDirectory("links");
4100
4310
  }
4101
4311
  createConfigurationReadStream() {
4312
+ this.#reportInfo("creating configuration read stream");
4102
4313
  return this.#streamJsonlDirectory("configuration");
4103
4314
  }
4104
4315
  createAssetsReadStream() {
4105
4316
  const inStream = this.#getBackupStream();
4106
4317
  const outStream = new stream$1.PassThrough({ objectMode: true });
4107
4318
  const loadAssetMetadata = this.#loadAssetMetadata.bind(this);
4319
+ this.#reportInfo("creating assets read stream");
4108
4320
  stream$1.pipeline(
4109
4321
  [
4110
4322
  inStream,
@@ -4305,9 +4517,20 @@ class LocalFileDestinationProvider {
4305
4517
  results = {};
4306
4518
  #providersMetadata = {};
4307
4519
  #archive = {};
4520
+ #diagnostics;
4308
4521
  constructor(options) {
4309
4522
  this.options = options;
4310
4523
  }
4524
+ #reportInfo(message) {
4525
+ this.#diagnostics?.report({
4526
+ details: {
4527
+ createdAt: /* @__PURE__ */ new Date(),
4528
+ message,
4529
+ origin: "file-destination-provider"
4530
+ },
4531
+ kind: "info"
4532
+ });
4533
+ }
4311
4534
  get #archivePath() {
4312
4535
  const { encryption, compression, file: file2 } = this.options;
4313
4536
  let filePath = `${file2.path}.tar`;
@@ -4324,9 +4547,11 @@ class LocalFileDestinationProvider {
4324
4547
  return this;
4325
4548
  }
4326
4549
  createGzip() {
4550
+ this.#reportInfo("creating gzip");
4327
4551
  return zip__default.default.createGzip();
4328
4552
  }
4329
- bootstrap() {
4553
+ bootstrap(diagnostics) {
4554
+ this.#diagnostics = diagnostics;
4330
4555
  const { compression, encryption } = this.options;
4331
4556
  if (encryption.enabled && !encryption.key) {
4332
4557
  throw new Error("Can't encrypt without a key");
@@ -4365,6 +4590,7 @@ class LocalFileDestinationProvider {
4365
4590
  }
4366
4591
  }
4367
4592
  async rollback() {
4593
+ this.#reportInfo("rolling back");
4368
4594
  await this.close();
4369
4595
  await fse.rm(this.#archivePath, { force: true });
4370
4596
  }
@@ -4372,6 +4598,7 @@ class LocalFileDestinationProvider {
4372
4598
  return null;
4373
4599
  }
4374
4600
  async #writeMetadata() {
4601
+ this.#reportInfo("writing metadata");
4375
4602
  const metadata = this.#providersMetadata.source;
4376
4603
  if (metadata) {
4377
4604
  await new Promise((resolve) => {
@@ -4392,6 +4619,7 @@ class LocalFileDestinationProvider {
4392
4619
  if (!this.#archive.stream) {
4393
4620
  throw new Error("Archive stream is unavailable");
4394
4621
  }
4622
+ this.#reportInfo("creating schemas write stream");
4395
4623
  const filePathFactory = createFilePathFactory("schemas");
4396
4624
  const entryStream = createTarEntryStream(
4397
4625
  this.#archive.stream,
@@ -4404,6 +4632,7 @@ class LocalFileDestinationProvider {
4404
4632
  if (!this.#archive.stream) {
4405
4633
  throw new Error("Archive stream is unavailable");
4406
4634
  }
4635
+ this.#reportInfo("creating entities write stream");
4407
4636
  const filePathFactory = createFilePathFactory("entities");
4408
4637
  const entryStream = createTarEntryStream(
4409
4638
  this.#archive.stream,
@@ -4416,6 +4645,7 @@ class LocalFileDestinationProvider {
4416
4645
  if (!this.#archive.stream) {
4417
4646
  throw new Error("Archive stream is unavailable");
4418
4647
  }
4648
+ this.#reportInfo("creating links write stream");
4419
4649
  const filePathFactory = createFilePathFactory("links");
4420
4650
  const entryStream = createTarEntryStream(
4421
4651
  this.#archive.stream,
@@ -4428,6 +4658,7 @@ class LocalFileDestinationProvider {
4428
4658
  if (!this.#archive.stream) {
4429
4659
  throw new Error("Archive stream is unavailable");
4430
4660
  }
4661
+ this.#reportInfo("creating configuration write stream");
4431
4662
  const filePathFactory = createFilePathFactory("configuration");
4432
4663
  const entryStream = createTarEntryStream(
4433
4664
  this.#archive.stream,
@@ -4441,6 +4672,7 @@ class LocalFileDestinationProvider {
4441
4672
  if (!archiveStream) {
4442
4673
  throw new Error("Archive stream is unavailable");
4443
4674
  }
4675
+ this.#reportInfo("creating assets write stream");
4444
4676
  return new stream$1.Writable({
4445
4677
  objectMode: true,
4446
4678
  write(data, _encoding, callback) {
@@ -4776,30 +5008,41 @@ const errorColors = {
4776
5008
  error: chalk__default.default.red,
4777
5009
  silly: chalk__default.default.yellow
4778
5010
  };
4779
- const formatDiagnostic = (operation) => ({ details, kind }) => {
4780
- const logger$1 = logger.createLogger(
4781
- logger.configs.createOutputFileConfiguration(`${operation}_error_log_${Date.now()}.log`)
4782
- );
4783
- try {
4784
- if (kind === "error") {
4785
- const { message, severity = "fatal" } = details;
4786
- const colorizeError = errorColors[severity];
4787
- const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
4788
- logger$1.error(errorMessage);
4789
- }
4790
- if (kind === "info") {
4791
- const { message, params } = details;
4792
- const msg = `${message}
4793
- ${params ? JSON.stringify(params, null, 2) : ""}`;
4794
- logger$1.info(msg);
5011
+ const formatDiagnostic = (operation, info) => {
5012
+ let logger$1;
5013
+ const getLogger = () => {
5014
+ if (!logger$1) {
5015
+ logger$1 = logger.createLogger(
5016
+ logger.configs.createOutputFileConfiguration(`${operation}_${Date.now()}.log`, {
5017
+ level: "info",
5018
+ format: logger.formats?.detailedLogs
5019
+ })
5020
+ );
4795
5021
  }
4796
- if (kind === "warning") {
4797
- const { origin: origin2, message } = details;
4798
- logger$1.warn(`(${origin2 ?? "transfer"}) ${message}`);
5022
+ return logger$1;
5023
+ };
5024
+ return ({ details, kind }) => {
5025
+ try {
5026
+ if (kind === "error") {
5027
+ const { message, severity = "fatal" } = details;
5028
+ const colorizeError = errorColors[severity];
5029
+ const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
5030
+ getLogger().error(errorMessage);
5031
+ }
5032
+ if (kind === "info" && info) {
5033
+ const { message, params, origin: origin2 } = details;
5034
+ const msg = `[${origin2 ?? "transfer"}] ${message}
5035
+ ${params ? JSON.stringify(params, null, 2) : ""}`;
5036
+ getLogger().info(msg);
5037
+ }
5038
+ if (kind === "warning") {
5039
+ const { origin: origin2, message } = details;
5040
+ getLogger().warn(`(${origin2 ?? "transfer"}) ${message}`);
5041
+ }
5042
+ } catch (err) {
5043
+ getLogger().error(err);
4799
5044
  }
4800
- } catch (err) {
4801
- logger$1.error(err);
4802
- }
5045
+ };
4803
5046
  };
4804
5047
  const loadersFactory = (defaultLoaders = {}) => {
4805
5048
  const loaders = defaultLoaders;
@@ -4981,7 +5224,7 @@ const action$2 = async (opts) => {
4981
5224
  ]
4982
5225
  }
4983
5226
  });
4984
- engine.diagnostics.onDiagnostic(formatDiagnostic("export"));
5227
+ engine.diagnostics.onDiagnostic(formatDiagnostic("export", opts.verbose));
4985
5228
  const progress = engine.progress.stream;
4986
5229
  const { updateLoader } = loadersFactory();
4987
5230
  progress.on(`stage::start`, ({ stage, data }) => {
@@ -5053,7 +5296,7 @@ const command$2 = ({ command: command2 }) => {
5053
5296
  )
5054
5297
  ).addOption(
5055
5298
  new commander.Option("--no-compress", "Disables gzip compression of output file").default(true)
5056
- ).addOption(
5299
+ ).addOption(new commander.Option("--verbose", "Enable verbose logs")).addOption(
5057
5300
  new commander.Option(
5058
5301
  "-k, --key <string>",
5059
5302
  "Provide encryption key in command instead of using the prompt"
@@ -5109,7 +5352,7 @@ const action$1 = async (opts) => {
5109
5352
  destination.onWarning = (message) => console.warn(`
5110
5353
  ${chalk__default.default.yellow("warn")}: ${message}`);
5111
5354
  const engine2 = createTransferEngine$1(source, destination, engineOptions);
5112
- engine2.diagnostics.onDiagnostic(formatDiagnostic("import"));
5355
+ engine2.diagnostics.onDiagnostic(formatDiagnostic("import", opts.verbose));
5113
5356
  const progress = engine2.progress.stream;
5114
5357
  const { updateLoader } = loadersFactory();
5115
5358
  engine2.onSchemaDiff(getDiffHandler(engine2, { force: opts.force, action: "import" }));
@@ -5167,7 +5410,7 @@ const command$1 = ({ command: command2 }) => {
5167
5410
  "-k, --key <string>",
5168
5411
  "Provide encryption key in command instead of using the prompt"
5169
5412
  )
5170
- ).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook("preAction", async (thisCommand) => {
5413
+ ).addOption(new commander.Option("--verbose", "Enable verbose logs")).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook("preAction", async (thisCommand) => {
5171
5414
  const opts = thisCommand.opts();
5172
5415
  const ext = path__default.default.extname(String(opts.file));
5173
5416
  if (ext === ".enc") {
@@ -5297,7 +5540,7 @@ const action = async (opts) => {
5297
5540
  ]
5298
5541
  }
5299
5542
  });
5300
- engine.diagnostics.onDiagnostic(formatDiagnostic("transfer"));
5543
+ engine.diagnostics.onDiagnostic(formatDiagnostic("transfer", opts.verbose));
5301
5544
  const progress = engine.progress.stream;
5302
5545
  const { updateLoader } = loadersFactory();
5303
5546
  engine.onSchemaDiff(getDiffHandler(engine, { force: opts.force, action: "transfer" }));
@@ -5349,7 +5592,7 @@ const command = ({ command: command2 }) => {
5349
5592
  "--to <destinationURL>",
5350
5593
  `URL of the remote Strapi instance to send data to`
5351
5594
  ).argParser(parseURL)
5352
- ).addOption(new commander.Option("--to-token <token>", `Transfer token for the remote Strapi destination`)).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook(
5595
+ ).addOption(new commander.Option("--to-token <token>", `Transfer token for the remote Strapi destination`)).addOption(new commander.Option("--verbose", "Enable verbose logs")).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook("preAction", validateExcludeOnly).hook(
5353
5596
  "preAction",
5354
5597
  ifOptions(
5355
5598
  (opts) => !(opts.from || opts.to) || opts.from && opts.to,