@strapi/data-transfer 5.4.1 → 5.5.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 (33) hide show
  1. package/dist/engine/index.d.ts +1 -1
  2. package/dist/engine/index.d.ts.map +1 -1
  3. package/dist/errors/constants.d.ts +1 -1
  4. package/dist/errors/constants.d.ts.map +1 -1
  5. package/dist/file/providers/destination/index.d.ts +2 -1
  6. package/dist/file/providers/destination/index.d.ts.map +1 -1
  7. package/dist/file/providers/source/index.d.ts +2 -1
  8. package/dist/file/providers/source/index.d.ts.map +1 -1
  9. package/dist/index.js +304 -95
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +303 -93
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/strapi/providers/local-destination/index.d.ts +2 -1
  14. package/dist/strapi/providers/local-destination/index.d.ts.map +1 -1
  15. package/dist/strapi/providers/local-source/index.d.ts +3 -1
  16. package/dist/strapi/providers/local-source/index.d.ts.map +1 -1
  17. package/dist/strapi/providers/remote-destination/index.d.ts +2 -1
  18. package/dist/strapi/providers/remote-destination/index.d.ts.map +1 -1
  19. package/dist/strapi/providers/remote-source/index.d.ts +2 -1
  20. package/dist/strapi/providers/remote-source/index.d.ts.map +1 -1
  21. package/dist/strapi/providers/utils.d.ts +3 -2
  22. package/dist/strapi/providers/utils.d.ts.map +1 -1
  23. package/dist/strapi/remote/handlers/abstract.d.ts +4 -0
  24. package/dist/strapi/remote/handlers/abstract.d.ts.map +1 -1
  25. package/dist/strapi/remote/handlers/pull.d.ts.map +1 -1
  26. package/dist/strapi/remote/handlers/push.d.ts.map +1 -1
  27. package/dist/strapi/remote/handlers/utils.d.ts.map +1 -1
  28. package/dist/{engine → utils}/diagnostic.d.ts +1 -0
  29. package/dist/utils/diagnostic.d.ts.map +1 -0
  30. package/dist/utils/index.d.ts +1 -0
  31. package/dist/utils/index.d.ts.map +1 -1
  32. package/package.json +7 -7
  33. package/dist/engine/diagnostic.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -298,8 +298,57 @@ const middleware = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
298
298
  __proto__: null,
299
299
  runMiddleware
300
300
  }, Symbol.toStringTag, { value: "Module" }));
301
+ const createDiagnosticReporter = (options = {}) => {
302
+ const { stackSize = -1 } = options;
303
+ const emitter = new EventEmitter();
304
+ const stack = [];
305
+ const addListener = (event, listener) => {
306
+ emitter.on(event, listener);
307
+ };
308
+ const isDiagnosticValid = (diagnostic2) => {
309
+ if (!diagnostic2.kind || !diagnostic2.details || !diagnostic2.details.message) {
310
+ return false;
311
+ }
312
+ return true;
313
+ };
314
+ return {
315
+ stack: {
316
+ get size() {
317
+ return stack.length;
318
+ },
319
+ get items() {
320
+ return stack;
321
+ }
322
+ },
323
+ report(diagnostic2) {
324
+ if (!isDiagnosticValid(diagnostic2)) {
325
+ return this;
326
+ }
327
+ emitter.emit("diagnostic", diagnostic2);
328
+ emitter.emit(`diagnostic.${diagnostic2.kind}`, diagnostic2);
329
+ if (stackSize !== -1 && stack.length >= stackSize) {
330
+ stack.shift();
331
+ }
332
+ stack.push(diagnostic2);
333
+ return this;
334
+ },
335
+ onDiagnostic(listener) {
336
+ addListener("diagnostic", listener);
337
+ return this;
338
+ },
339
+ on(kind, listener) {
340
+ addListener(`diagnostic.${kind}`, listener);
341
+ return this;
342
+ }
343
+ };
344
+ };
345
+ const diagnostic = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
346
+ __proto__: null,
347
+ createDiagnosticReporter
348
+ }, Symbol.toStringTag, { value: "Module" }));
301
349
  const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
302
350
  __proto__: null,
351
+ diagnostics: diagnostic,
303
352
  encryption: index$9,
304
353
  json,
305
354
  middleware,
@@ -400,50 +449,6 @@ const validateProvider = (type, provider) => {
400
449
  );
401
450
  }
402
451
  };
403
- const createDiagnosticReporter = (options = {}) => {
404
- const { stackSize = -1 } = options;
405
- const emitter = new EventEmitter();
406
- const stack = [];
407
- const addListener = (event, listener) => {
408
- emitter.on(event, listener);
409
- };
410
- const isDiagnosticValid = (diagnostic) => {
411
- if (!diagnostic.kind || !diagnostic.details || !diagnostic.details.message) {
412
- return false;
413
- }
414
- return true;
415
- };
416
- return {
417
- stack: {
418
- get size() {
419
- return stack.length;
420
- },
421
- get items() {
422
- return stack;
423
- }
424
- },
425
- report(diagnostic) {
426
- if (!isDiagnosticValid(diagnostic)) {
427
- return this;
428
- }
429
- emitter.emit("diagnostic", diagnostic);
430
- emitter.emit(`diagnostic.${diagnostic.kind}`, diagnostic);
431
- if (stackSize !== -1 && stack.length >= stackSize) {
432
- stack.shift();
433
- }
434
- stack.push(diagnostic);
435
- return this;
436
- },
437
- onDiagnostic(listener) {
438
- addListener("diagnostic", listener);
439
- return this;
440
- },
441
- on(kind, listener) {
442
- addListener(`diagnostic.${kind}`, listener);
443
- return this;
444
- }
445
- };
446
- };
447
452
  class ProviderError extends DataTransferError {
448
453
  constructor(severity, message, details) {
449
454
  super("provider", severity, message, details);
@@ -576,7 +581,7 @@ class TransferEngine {
576
581
  reportInfo(message, params) {
577
582
  this.diagnostics.report({
578
583
  kind: "info",
579
- details: { createdAt: /* @__PURE__ */ new Date(), message, params }
584
+ details: { createdAt: /* @__PURE__ */ new Date(), message, params, origin: "engine" }
580
585
  });
581
586
  }
582
587
  /**
@@ -868,8 +873,8 @@ ${formattedDiffs}`,
868
873
  */
869
874
  async bootstrap() {
870
875
  const results = await Promise.allSettled([
871
- this.sourceProvider.bootstrap?.(),
872
- this.destinationProvider.bootstrap?.()
876
+ this.sourceProvider.bootstrap?.(this.diagnostics),
877
+ this.destinationProvider.bootstrap?.(this.diagnostics)
873
878
  ]);
874
879
  results.forEach((result) => {
875
880
  if (result.status === "rejected") {
@@ -1874,6 +1879,7 @@ class LocalStrapiDestinationProvider {
1874
1879
  transaction;
1875
1880
  uploadsBackupDirectoryName;
1876
1881
  onWarning;
1882
+ #diagnostics;
1877
1883
  /**
1878
1884
  * The entities mapper is used to map old entities to their new IDs
1879
1885
  */
@@ -1883,7 +1889,8 @@ class LocalStrapiDestinationProvider {
1883
1889
  this.#entitiesMapper = {};
1884
1890
  this.uploadsBackupDirectoryName = `uploads_backup_${Date.now()}`;
1885
1891
  }
1886
- async bootstrap() {
1892
+ async bootstrap(diagnostics) {
1893
+ this.#diagnostics = diagnostics;
1887
1894
  this.#validateOptions();
1888
1895
  this.strapi = await this.options.getStrapi();
1889
1896
  if (!this.strapi) {
@@ -1900,6 +1907,16 @@ class LocalStrapiDestinationProvider {
1900
1907
  const excluded = this.options.restore?.entities?.exclude && this.options.restore?.entities.exclude.includes(type);
1901
1908
  return !excluded && !notIncluded;
1902
1909
  };
1910
+ #reportInfo(message) {
1911
+ this.#diagnostics?.report({
1912
+ details: {
1913
+ createdAt: /* @__PURE__ */ new Date(),
1914
+ message,
1915
+ origin: "local-destination-provider"
1916
+ },
1917
+ kind: "info"
1918
+ });
1919
+ }
1903
1920
  async close() {
1904
1921
  const { autoDestroy } = this.options;
1905
1922
  this.transaction?.end();
@@ -1908,6 +1925,7 @@ class LocalStrapiDestinationProvider {
1908
1925
  }
1909
1926
  }
1910
1927
  #validateOptions() {
1928
+ this.#reportInfo("validating options");
1911
1929
  if (!VALID_CONFLICT_STRATEGIES.includes(this.options.strategy)) {
1912
1930
  throw new ProviderValidationError(`Invalid strategy ${this.options.strategy}`, {
1913
1931
  check: "strategy",
@@ -1924,10 +1942,12 @@ class LocalStrapiDestinationProvider {
1924
1942
  if (!this.options.restore) {
1925
1943
  throw new ProviderValidationError("Missing restore options");
1926
1944
  }
1945
+ this.#reportInfo("deleting record ");
1927
1946
  return deleteRecords(this.strapi, this.options.restore);
1928
1947
  }
1929
1948
  async #deleteAllAssets(trx) {
1930
1949
  assertValidStrapi(this.strapi);
1950
+ this.#reportInfo("deleting all assets");
1931
1951
  if (!this.#areAssetsIncluded()) {
1932
1952
  return;
1933
1953
  }
@@ -1940,9 +1960,12 @@ class LocalStrapiDestinationProvider {
1940
1960
  }
1941
1961
  }
1942
1962
  }
1963
+ this.#reportInfo("deleted all assets");
1943
1964
  }
1944
1965
  async rollback() {
1966
+ this.#reportInfo("Rolling back transaction");
1945
1967
  await this.transaction?.rollback();
1968
+ this.#reportInfo("Rolled back transaction");
1946
1969
  }
1947
1970
  async beforeTransfer() {
1948
1971
  if (!this.strapi) {
@@ -1961,6 +1984,7 @@ class LocalStrapiDestinationProvider {
1961
1984
  });
1962
1985
  }
1963
1986
  getMetadata() {
1987
+ this.#reportInfo("getting metadata");
1964
1988
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1965
1989
  const strapiVersion = this.strapi.config.get("info.strapi");
1966
1990
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1972,6 +1996,7 @@ class LocalStrapiDestinationProvider {
1972
1996
  };
1973
1997
  }
1974
1998
  getSchemas() {
1999
+ this.#reportInfo("getting schema");
1975
2000
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1976
2001
  const schemas = schemasToValidJSON({
1977
2002
  ...this.strapi.contentTypes,
@@ -1981,6 +2006,7 @@ class LocalStrapiDestinationProvider {
1981
2006
  }
1982
2007
  createEntitiesWriteStream() {
1983
2008
  assertValidStrapi(this.strapi, "Not able to import entities");
2009
+ this.#reportInfo("creating entities stream");
1984
2010
  const { strategy } = this.options;
1985
2011
  const updateMappingTable = (type, oldID, newID) => {
1986
2012
  if (!this.#entitiesMapper[type]) {
@@ -2007,6 +2033,7 @@ class LocalStrapiDestinationProvider {
2007
2033
  return;
2008
2034
  }
2009
2035
  if (this.strapi.config.get("plugin::upload").provider === "local") {
2036
+ this.#reportInfo("creating assets backup directory");
2010
2037
  const assetsDirectory = path.join(this.strapi.dirs.static.public, "uploads");
2011
2038
  const backupDirectory = path.join(
2012
2039
  this.strapi.dirs.static.public,
@@ -2022,6 +2049,7 @@ class LocalStrapiDestinationProvider {
2022
2049
  await fse.move(assetsDirectory, backupDirectory);
2023
2050
  await fse.mkdir(assetsDirectory);
2024
2051
  await fse.outputFile(path.join(assetsDirectory, ".gitkeep"), "");
2052
+ this.#reportInfo(`created assets backup directory ${backupDirectory}`);
2025
2053
  } catch (err) {
2026
2054
  throw new ProviderTransferError(
2027
2055
  "The backup folder for the assets could not be created inside the public folder. Please ensure Strapi has write permissions on the public directory",
@@ -2039,17 +2067,20 @@ class LocalStrapiDestinationProvider {
2039
2067
  return;
2040
2068
  }
2041
2069
  if (this.strapi.config.get("plugin::upload").provider === "local") {
2070
+ this.#reportInfo("removing assets backup");
2042
2071
  assertValidStrapi(this.strapi);
2043
2072
  const backupDirectory = path.join(
2044
2073
  this.strapi.dirs.static.public,
2045
2074
  this.uploadsBackupDirectoryName
2046
2075
  );
2047
2076
  await fse.rm(backupDirectory, { recursive: true, force: true });
2077
+ this.#reportInfo("successfully removed assets backup");
2048
2078
  }
2049
2079
  }
2050
2080
  // TODO: Move this logic to the restore strategy
2051
2081
  async createAssetsWriteStream() {
2052
2082
  assertValidStrapi(this.strapi, "Not able to stream Assets");
2083
+ this.#reportInfo("creating assets write stream");
2053
2084
  if (!this.#areAssetsIncluded()) {
2054
2085
  throw new ProviderTransferError(
2055
2086
  "Attempting to transfer assets when `assets` is not set in restore options"
@@ -2058,7 +2089,6 @@ class LocalStrapiDestinationProvider {
2058
2089
  const removeAssetsBackup = this.#removeAssetsBackup.bind(this);
2059
2090
  const strapi2 = this.strapi;
2060
2091
  const transaction2 = this.transaction;
2061
- const backupDirectory = this.uploadsBackupDirectoryName;
2062
2092
  const fileEntitiesMapper = this.#entitiesMapper["plugin::upload.file"];
2063
2093
  const restoreMediaEntitiesContent = this.#isContentTypeIncluded("plugin::upload.file");
2064
2094
  return new Writable({
@@ -2069,47 +2099,28 @@ class LocalStrapiDestinationProvider {
2069
2099
  },
2070
2100
  async write(chunk, _encoding, callback) {
2071
2101
  await transaction2?.attach(async () => {
2072
- if (!chunk.metadata) {
2073
- const assetsDirectory = path.join(strapi2.dirs.static.public, "uploads");
2074
- const entryPath = path.join(assetsDirectory, chunk.filename);
2075
- const writableStream = fse.createWriteStream(entryPath);
2076
- chunk.stream.pipe(writableStream).on("close", () => {
2077
- callback(null);
2078
- }).on("error", async (error) => {
2079
- const errorMessage = error.code === "ENOSPC" ? " Your server doesn't have space to proceed with the import. " : " ";
2080
- try {
2081
- await fse.rm(assetsDirectory, { recursive: true, force: true });
2082
- this.destroy(
2083
- new ProviderTransferError(
2084
- `There was an error during the transfer process.${errorMessage}The original files have been restored to ${assetsDirectory}`
2085
- )
2086
- );
2087
- } catch (err) {
2088
- throw new ProviderTransferError(
2089
- `There was an error doing the rollback process. The original files are in ${backupDirectory}, but we failed to restore them to ${assetsDirectory}`
2090
- );
2091
- } finally {
2092
- callback(error);
2093
- }
2094
- });
2095
- return;
2096
- }
2097
2102
  const uploadData = {
2098
2103
  ...chunk.metadata,
2099
2104
  stream: Readable.from(chunk.stream),
2100
2105
  buffer: chunk?.buffer
2101
2106
  };
2102
2107
  const provider = strapi2.config.get("plugin::upload").provider;
2108
+ const fileId = fileEntitiesMapper?.[uploadData.id];
2109
+ if (!fileId) {
2110
+ callback(new Error(`File ID not found for ID: ${uploadData.id}`));
2111
+ }
2103
2112
  try {
2104
2113
  await strapi2.plugin("upload").provider.uploadStream(uploadData);
2105
2114
  if (!restoreMediaEntitiesContent) {
2106
2115
  return callback();
2107
2116
  }
2108
2117
  if (uploadData?.type) {
2109
- const condition = uploadData?.id ? { id: fileEntitiesMapper[uploadData.id] } : { hash: uploadData.mainHash };
2110
2118
  const entry2 = await strapi2.db.query("plugin::upload.file").findOne({
2111
- where: condition
2119
+ where: { id: fileId }
2112
2120
  });
2121
+ if (!entry2) {
2122
+ throw new Error("file not found");
2123
+ }
2113
2124
  const specificFormat = entry2?.formats?.[uploadData.type];
2114
2125
  if (specificFormat) {
2115
2126
  specificFormat.url = uploadData.url;
@@ -2124,8 +2135,11 @@ class LocalStrapiDestinationProvider {
2124
2135
  return callback();
2125
2136
  }
2126
2137
  const entry = await strapi2.db.query("plugin::upload.file").findOne({
2127
- where: { id: fileEntitiesMapper[uploadData.id] }
2138
+ where: { id: fileId }
2128
2139
  });
2140
+ if (!entry) {
2141
+ throw new Error("file not found");
2142
+ }
2129
2143
  entry.url = uploadData.url;
2130
2144
  await strapi2.db.query("plugin::upload.file").update({
2131
2145
  where: { id: entry.id },
@@ -2144,6 +2158,7 @@ class LocalStrapiDestinationProvider {
2144
2158
  }
2145
2159
  async createConfigurationWriteStream() {
2146
2160
  assertValidStrapi(this.strapi, "Not able to stream Configurations");
2161
+ this.#reportInfo("creating configuration write stream");
2147
2162
  const { strategy } = this.options;
2148
2163
  if (strategy === "restore") {
2149
2164
  return createConfigurationWriteStream(this.strapi, this.transaction);
@@ -2155,6 +2170,7 @@ class LocalStrapiDestinationProvider {
2155
2170
  });
2156
2171
  }
2157
2172
  async createLinksWriteStream() {
2173
+ this.#reportInfo("creating links write stream");
2158
2174
  if (!this.strapi) {
2159
2175
  throw new Error("Not able to stream links. Strapi instance not found");
2160
2176
  }
@@ -2353,12 +2369,24 @@ class LocalStrapiSourceProvider {
2353
2369
  type = "source";
2354
2370
  options;
2355
2371
  strapi;
2372
+ #diagnostics;
2356
2373
  constructor(options) {
2357
2374
  this.options = options;
2358
2375
  }
2359
- async bootstrap() {
2376
+ async bootstrap(diagnostics) {
2377
+ this.#diagnostics = diagnostics;
2360
2378
  this.strapi = await this.options.getStrapi();
2361
2379
  }
2380
+ #reportInfo(message) {
2381
+ this.#diagnostics?.report({
2382
+ details: {
2383
+ createdAt: /* @__PURE__ */ new Date(),
2384
+ message,
2385
+ origin: "local-source-provider"
2386
+ },
2387
+ kind: "info"
2388
+ });
2389
+ }
2362
2390
  async close() {
2363
2391
  const { autoDestroy } = this.options;
2364
2392
  if (autoDestroy === void 0 || autoDestroy === true) {
@@ -2366,6 +2394,7 @@ class LocalStrapiSourceProvider {
2366
2394
  }
2367
2395
  }
2368
2396
  getMetadata() {
2397
+ this.#reportInfo("getting metadata");
2369
2398
  const strapiVersion = strapi.config.get("info.strapi");
2370
2399
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2371
2400
  return {
@@ -2377,6 +2406,7 @@ class LocalStrapiSourceProvider {
2377
2406
  }
2378
2407
  async createEntitiesReadStream() {
2379
2408
  assertValidStrapi(this.strapi, "Not able to stream entities");
2409
+ this.#reportInfo("creating entities read stream");
2380
2410
  return chain([
2381
2411
  // Entities stream
2382
2412
  createEntitiesStream(this.strapi),
@@ -2386,14 +2416,17 @@ class LocalStrapiSourceProvider {
2386
2416
  }
2387
2417
  createLinksReadStream() {
2388
2418
  assertValidStrapi(this.strapi, "Not able to stream links");
2419
+ this.#reportInfo("creating links read stream");
2389
2420
  return createLinksStream(this.strapi);
2390
2421
  }
2391
2422
  createConfigurationReadStream() {
2392
2423
  assertValidStrapi(this.strapi, "Not able to stream configuration");
2424
+ this.#reportInfo("creating configuration read stream");
2393
2425
  return createConfigurationStream(this.strapi);
2394
2426
  }
2395
2427
  getSchemas() {
2396
2428
  assertValidStrapi(this.strapi, "Not able to get Schemas");
2429
+ this.#reportInfo("getting schemas");
2397
2430
  const schemas = schemasToValidJSON({
2398
2431
  ...this.strapi.contentTypes,
2399
2432
  ...this.strapi.components
@@ -2405,13 +2438,14 @@ class LocalStrapiSourceProvider {
2405
2438
  }
2406
2439
  createAssetsReadStream() {
2407
2440
  assertValidStrapi(this.strapi, "Not able to stream assets");
2441
+ this.#reportInfo("creating assets read stream");
2408
2442
  return createAssetsStream(this.strapi);
2409
2443
  }
2410
2444
  }
2411
2445
  const createDispatcher = (ws, retryMessageOptions = {
2412
2446
  retryMessageMaxRetries: 5,
2413
2447
  retryMessageTimeout: 3e4
2414
- }) => {
2448
+ }, reportInfo) => {
2415
2449
  const state = {};
2416
2450
  const dispatch = async (message, options = {}) => {
2417
2451
  if (!ws) {
@@ -2424,6 +2458,16 @@ const createDispatcher = (ws, retryMessageOptions = {
2424
2458
  if (options.attachTransfer) {
2425
2459
  Object.assign(payload, { transferID: state.transfer?.id });
2426
2460
  }
2461
+ if (message.type === "command") {
2462
+ reportInfo?.(
2463
+ `dispatching message command:${message.command} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2464
+ );
2465
+ } else if (message.type === "transfer") {
2466
+ const messageToSend = message;
2467
+ reportInfo?.(
2468
+ `dispatching message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2469
+ );
2470
+ }
2427
2471
  const stringifiedPayload = JSON.stringify(payload);
2428
2472
  ws.send(stringifiedPayload, (error) => {
2429
2473
  if (error) {
@@ -2446,6 +2490,16 @@ const createDispatcher = (ws, retryMessageOptions = {
2446
2490
  const interval = setInterval(sendPeriodically, retryMessageTimeout);
2447
2491
  const onResponse = (raw) => {
2448
2492
  const response = JSON.parse(raw.toString());
2493
+ if (message.type === "command") {
2494
+ reportInfo?.(
2495
+ `received response to message command: ${message.command} uuid: ${uuid} sent: ${numberOfTimesMessageWasSent}`
2496
+ );
2497
+ } else if (message.type === "transfer") {
2498
+ const messageToSend = message;
2499
+ reportInfo?.(
2500
+ `received response to message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2501
+ );
2502
+ }
2449
2503
  if (response.uuid === uuid) {
2450
2504
  clearInterval(interval);
2451
2505
  if (response.error) {
@@ -2502,7 +2556,7 @@ const createDispatcher = (ws, retryMessageOptions = {
2502
2556
  dispatchTransferStep
2503
2557
  };
2504
2558
  };
2505
- const connectToWebsocket = (address, options) => {
2559
+ const connectToWebsocket = (address, options, diagnostics) => {
2506
2560
  return new Promise((resolve, reject2) => {
2507
2561
  const server = new WebSocket(address, options);
2508
2562
  server.once("open", () => {
@@ -2536,6 +2590,14 @@ const connectToWebsocket = (address, options) => {
2536
2590
  )
2537
2591
  );
2538
2592
  });
2593
+ server.on("message", (raw) => {
2594
+ const response = JSON.parse(raw.toString());
2595
+ if (response.diagnostic) {
2596
+ diagnostics?.report({
2597
+ ...response.diagnostic
2598
+ });
2599
+ }
2600
+ });
2539
2601
  server.once("error", (err) => {
2540
2602
  reject2(
2541
2603
  new ProviderTransferError(err.message, {
@@ -2577,6 +2639,7 @@ class RemoteStrapiDestinationProvider {
2577
2639
  dispatcher;
2578
2640
  transferID;
2579
2641
  stats;
2642
+ #diagnostics;
2580
2643
  constructor(options) {
2581
2644
  this.options = options;
2582
2645
  this.ws = null;
@@ -2703,7 +2766,18 @@ class RemoteStrapiDestinationProvider {
2703
2766
  }
2704
2767
  });
2705
2768
  }
2706
- async bootstrap() {
2769
+ #reportInfo(message) {
2770
+ this.#diagnostics?.report({
2771
+ details: {
2772
+ createdAt: /* @__PURE__ */ new Date(),
2773
+ message,
2774
+ origin: "remote-destination-provider"
2775
+ },
2776
+ kind: "info"
2777
+ });
2778
+ }
2779
+ async bootstrap(diagnostics) {
2780
+ this.#diagnostics = diagnostics;
2707
2781
  const { url, auth } = this.options;
2708
2782
  const validProtocols = ["https:", "http:"];
2709
2783
  let ws;
@@ -2720,11 +2794,12 @@ class RemoteStrapiDestinationProvider {
2720
2794
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
2721
2795
  url.pathname
2722
2796
  )}${TRANSFER_PATH}/push`;
2797
+ this.#reportInfo("establishing websocket connection");
2723
2798
  if (!auth) {
2724
- ws = await connectToWebsocket(wsUrl);
2799
+ ws = await connectToWebsocket(wsUrl, void 0, this.#diagnostics);
2725
2800
  } else if (auth.type === "token") {
2726
2801
  const headers = { Authorization: `Bearer ${auth.token}` };
2727
- ws = await connectToWebsocket(wsUrl, { headers });
2802
+ ws = await connectToWebsocket(wsUrl, { headers }, this.#diagnostics);
2728
2803
  } else {
2729
2804
  throw new ProviderValidationError("Auth method not available", {
2730
2805
  check: "auth.type",
@@ -2733,10 +2808,19 @@ class RemoteStrapiDestinationProvider {
2733
2808
  }
2734
2809
  });
2735
2810
  }
2811
+ this.#reportInfo("established websocket connection");
2736
2812
  this.ws = ws;
2737
2813
  const { retryMessageOptions } = this.options;
2738
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
2814
+ this.#reportInfo("creating dispatcher");
2815
+ this.dispatcher = createDispatcher(
2816
+ this.ws,
2817
+ retryMessageOptions,
2818
+ (message) => this.#reportInfo(message)
2819
+ );
2820
+ this.#reportInfo("created dispatcher");
2821
+ this.#reportInfo("initialize transfer");
2739
2822
  this.transferID = await this.initTransfer();
2823
+ this.#reportInfo(`initialized transfer ${this.transferID}`);
2740
2824
  this.dispatcher.setTransferProperties({ id: this.transferID, kind: "push" });
2741
2825
  await this.dispatcher.dispatchTransferAction("bootstrap");
2742
2826
  }
@@ -2863,6 +2947,7 @@ class RemoteStrapiSourceProvider {
2863
2947
  this.dispatcher = null;
2864
2948
  }
2865
2949
  results;
2950
+ #diagnostics;
2866
2951
  async #createStageReadStream(stage) {
2867
2952
  const startResult = await this.#startStep(stage);
2868
2953
  if (startResult instanceof Error) {
@@ -3045,7 +3130,18 @@ class RemoteStrapiSourceProvider {
3045
3130
  }
3046
3131
  return res.transferID;
3047
3132
  }
3048
- async bootstrap() {
3133
+ #reportInfo(message) {
3134
+ this.#diagnostics?.report({
3135
+ details: {
3136
+ createdAt: /* @__PURE__ */ new Date(),
3137
+ message,
3138
+ origin: "remote-source-provider"
3139
+ },
3140
+ kind: "info"
3141
+ });
3142
+ }
3143
+ async bootstrap(diagnostics) {
3144
+ this.#diagnostics = diagnostics;
3049
3145
  const { url, auth } = this.options;
3050
3146
  let ws;
3051
3147
  this.assertValidProtocol(url);
@@ -3053,11 +3149,12 @@ class RemoteStrapiSourceProvider {
3053
3149
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
3054
3150
  url.pathname
3055
3151
  )}${TRANSFER_PATH}/pull`;
3152
+ this.#reportInfo("establishing websocket connection");
3056
3153
  if (!auth) {
3057
- ws = await connectToWebsocket(wsUrl);
3154
+ ws = await connectToWebsocket(wsUrl, void 0, this.#diagnostics);
3058
3155
  } else if (auth.type === "token") {
3059
3156
  const headers = { Authorization: `Bearer ${auth.token}` };
3060
- ws = await connectToWebsocket(wsUrl, { headers });
3157
+ ws = await connectToWebsocket(wsUrl, { headers }, this.#diagnostics);
3061
3158
  } else {
3062
3159
  throw new ProviderValidationError("Auth method not available", {
3063
3160
  check: "auth.type",
@@ -3066,10 +3163,19 @@ class RemoteStrapiSourceProvider {
3066
3163
  }
3067
3164
  });
3068
3165
  }
3166
+ this.#reportInfo("established websocket connection");
3069
3167
  this.ws = ws;
3070
3168
  const { retryMessageOptions } = this.options;
3071
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
3169
+ this.#reportInfo("creating dispatcher");
3170
+ this.dispatcher = createDispatcher(
3171
+ this.ws,
3172
+ retryMessageOptions,
3173
+ (message) => this.#reportInfo(message)
3174
+ );
3175
+ this.#reportInfo("creating dispatcher");
3176
+ this.#reportInfo("initialize transfer");
3072
3177
  const transferID = await this.initTransfer();
3178
+ this.#reportInfo(`initialized transfer ${transferID}`);
3073
3179
  this.dispatcher.setTransferProperties({ id: transferID, kind: "pull" });
3074
3180
  await this.dispatcher.dispatchTransferAction("bootstrap");
3075
3181
  }
@@ -3299,6 +3405,7 @@ const handlerControllerFactory = (implementation) => (options) => {
3299
3405
  const cb = (ws) => {
3300
3406
  const state = { id: void 0 };
3301
3407
  const messageUUIDs = /* @__PURE__ */ new Set();
3408
+ const diagnostics = createDiagnosticReporter();
3302
3409
  const cannotRespondHandler = (err) => {
3303
3410
  strapi?.log?.error(
3304
3411
  "[Data transfer] Cannot send error response to client, closing connection"
@@ -3332,6 +3439,9 @@ const handlerControllerFactory = (implementation) => (options) => {
3332
3439
  set response(response) {
3333
3440
  state.response = response;
3334
3441
  },
3442
+ get diagnostics() {
3443
+ return diagnostics;
3444
+ },
3335
3445
  addUUID(uuid) {
3336
3446
  messageUUIDs.add(uuid);
3337
3447
  },
@@ -3447,6 +3557,10 @@ const handlerControllerFactory = (implementation) => (options) => {
3447
3557
  onError() {
3448
3558
  },
3449
3559
  onClose() {
3560
+ },
3561
+ onInfo() {
3562
+ },
3563
+ onWarning() {
3450
3564
  }
3451
3565
  };
3452
3566
  const handler = Object.assign(Object.create(prototype), implementation(prototype));
@@ -3479,6 +3593,14 @@ const handlerControllerFactory = (implementation) => (options) => {
3479
3593
  cannotRespondHandler(err);
3480
3594
  }
3481
3595
  });
3596
+ diagnostics.onDiagnostic((diagnostic2) => {
3597
+ const uuid = randomUUID();
3598
+ const payload = JSON.stringify({
3599
+ diagnostic: diagnostic2,
3600
+ uuid
3601
+ });
3602
+ handler.send(payload);
3603
+ });
3482
3604
  };
3483
3605
  try {
3484
3606
  handleWSUpgrade(wss, ctx, cb);
@@ -3514,6 +3636,26 @@ const createPushController = handlerControllerFactory((proto) => ({
3514
3636
  verifyAuth() {
3515
3637
  return proto.verifyAuth.call(this, TRANSFER_KIND$1);
3516
3638
  },
3639
+ onInfo(message) {
3640
+ this.diagnostics?.report({
3641
+ details: {
3642
+ message,
3643
+ origin: "push-handler",
3644
+ createdAt: /* @__PURE__ */ new Date()
3645
+ },
3646
+ kind: "info"
3647
+ });
3648
+ },
3649
+ onWarning(message) {
3650
+ this.diagnostics?.report({
3651
+ details: {
3652
+ message,
3653
+ createdAt: /* @__PURE__ */ new Date(),
3654
+ origin: "push-handler"
3655
+ },
3656
+ kind: "warning"
3657
+ });
3658
+ },
3517
3659
  cleanup() {
3518
3660
  proto.cleanup.call(this);
3519
3661
  this.streams = {};
@@ -3590,6 +3732,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3590
3732
  proto.addUUID(uuid);
3591
3733
  if (type === "command") {
3592
3734
  const { command } = msg;
3735
+ this.onInfo(`received command:${command} uuid:${uuid}`);
3593
3736
  await this.executeAndRespond(uuid, () => {
3594
3737
  this.assertValidTransferCommand(command);
3595
3738
  if (command === "status") {
@@ -3598,6 +3741,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3598
3741
  return this[command](msg.params);
3599
3742
  });
3600
3743
  } else if (type === "transfer") {
3744
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3601
3745
  await this.executeAndRespond(uuid, async () => {
3602
3746
  await this.verifyAuth();
3603
3747
  this.assertValidTransfer();
@@ -3699,6 +3843,9 @@ const createPushController = handlerControllerFactory((proto) => ({
3699
3843
  }
3700
3844
  this.flow?.set(step);
3701
3845
  }
3846
+ if (action === "bootstrap") {
3847
+ return this.provider?.[action](this.diagnostics);
3848
+ }
3702
3849
  return this.provider?.[action]();
3703
3850
  },
3704
3851
  async streamAsset(payload) {
@@ -3764,6 +3911,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3764
3911
  getStrapi: () => strapi
3765
3912
  });
3766
3913
  this.provider.onWarning = (message) => {
3914
+ this.onWarning(message);
3767
3915
  strapi.log.warn(message);
3768
3916
  };
3769
3917
  return { transferID: this.transferID };
@@ -3804,6 +3952,26 @@ const createPullController = handlerControllerFactory((proto) => ({
3804
3952
  this.streams = {};
3805
3953
  delete this.provider;
3806
3954
  },
3955
+ onInfo(message) {
3956
+ this.diagnostics?.report({
3957
+ details: {
3958
+ message,
3959
+ origin: "pull-handler",
3960
+ createdAt: /* @__PURE__ */ new Date()
3961
+ },
3962
+ kind: "info"
3963
+ });
3964
+ },
3965
+ onWarning(message) {
3966
+ this.diagnostics?.report({
3967
+ details: {
3968
+ message,
3969
+ createdAt: /* @__PURE__ */ new Date(),
3970
+ origin: "pull-handler"
3971
+ },
3972
+ kind: "warning"
3973
+ });
3974
+ },
3807
3975
  assertValidTransferAction(action) {
3808
3976
  const validActions = VALID_TRANSFER_ACTIONS;
3809
3977
  if (validActions.includes(action)) {
@@ -3833,6 +4001,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3833
4001
  proto.addUUID(uuid);
3834
4002
  if (type === "command") {
3835
4003
  const { command } = msg;
4004
+ this.onInfo(`received command:${command} uuid:${uuid}`);
3836
4005
  await this.executeAndRespond(uuid, () => {
3837
4006
  this.assertValidTransferCommand(command);
3838
4007
  if (command === "status") {
@@ -3841,6 +4010,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3841
4010
  return this[command](msg.params);
3842
4011
  });
3843
4012
  } else if (type === "transfer") {
4013
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3844
4014
  await this.executeAndRespond(uuid, async () => {
3845
4015
  await this.verifyAuth();
3846
4016
  this.assertValidTransfer();
@@ -3862,6 +4032,9 @@ const createPullController = handlerControllerFactory((proto) => ({
3862
4032
  async onTransferAction(msg) {
3863
4033
  const { action } = msg;
3864
4034
  this.assertValidTransferAction(action);
4035
+ if (action === "bootstrap") {
4036
+ return this.provider?.[action](this.diagnostics);
4037
+ }
3865
4038
  return this.provider?.[action]();
3866
4039
  },
3867
4040
  async flush(stage, id) {
@@ -4060,6 +4233,7 @@ class LocalFileSourceProvider {
4060
4233
  name = "source::local-file";
4061
4234
  options;
4062
4235
  #metadata;
4236
+ #diagnostics;
4063
4237
  constructor(options) {
4064
4238
  this.options = options;
4065
4239
  const { encryption } = this.options;
@@ -4067,10 +4241,21 @@ class LocalFileSourceProvider {
4067
4241
  throw new Error("Missing encryption key");
4068
4242
  }
4069
4243
  }
4244
+ #reportInfo(message) {
4245
+ this.#diagnostics?.report({
4246
+ details: {
4247
+ createdAt: /* @__PURE__ */ new Date(),
4248
+ message,
4249
+ origin: "file-source-provider"
4250
+ },
4251
+ kind: "info"
4252
+ });
4253
+ }
4070
4254
  /**
4071
4255
  * Pre flight checks regarding the provided options, making sure that the file can be opened (decrypted, decompressed), etc.
4072
4256
  */
4073
- async bootstrap() {
4257
+ async bootstrap(diagnostics) {
4258
+ this.#diagnostics = diagnostics;
4074
4259
  const { path: filePath } = this.options.file;
4075
4260
  try {
4076
4261
  await this.#loadMetadata();
@@ -4095,12 +4280,14 @@ class LocalFileSourceProvider {
4095
4280
  return this.#parseJSONFile(backupStream, path2);
4096
4281
  }
4097
4282
  async getMetadata() {
4283
+ this.#reportInfo("getting metadata");
4098
4284
  if (!this.#metadata) {
4099
4285
  await this.#loadMetadata();
4100
4286
  }
4101
4287
  return this.#metadata ?? null;
4102
4288
  }
4103
4289
  async getSchemas() {
4290
+ this.#reportInfo("getting schemas");
4104
4291
  const schemaCollection = await collect(
4105
4292
  this.createSchemasReadStream()
4106
4293
  );
@@ -4111,21 +4298,26 @@ class LocalFileSourceProvider {
4111
4298
  return schemasToValidJSON(schemas);
4112
4299
  }
4113
4300
  createEntitiesReadStream() {
4301
+ this.#reportInfo("creating entities read stream");
4114
4302
  return this.#streamJsonlDirectory("entities");
4115
4303
  }
4116
4304
  createSchemasReadStream() {
4305
+ this.#reportInfo("creating schemas read stream");
4117
4306
  return this.#streamJsonlDirectory("schemas");
4118
4307
  }
4119
4308
  createLinksReadStream() {
4309
+ this.#reportInfo("creating links read stream");
4120
4310
  return this.#streamJsonlDirectory("links");
4121
4311
  }
4122
4312
  createConfigurationReadStream() {
4313
+ this.#reportInfo("creating configuration read stream");
4123
4314
  return this.#streamJsonlDirectory("configuration");
4124
4315
  }
4125
4316
  createAssetsReadStream() {
4126
4317
  const inStream = this.#getBackupStream();
4127
4318
  const outStream = new PassThrough({ objectMode: true });
4128
4319
  const loadAssetMetadata = this.#loadAssetMetadata.bind(this);
4320
+ this.#reportInfo("creating assets read stream");
4129
4321
  pipeline(
4130
4322
  [
4131
4323
  inStream,
@@ -4145,9 +4337,7 @@ class LocalFileSourceProvider {
4145
4337
  try {
4146
4338
  metadata = await loadAssetMetadata(`assets/metadata/${file}.json`);
4147
4339
  } catch (error) {
4148
- console.warn(
4149
- ` Failed to read metadata for ${file}, Strapi will try to fix this issue automatically`
4150
- );
4340
+ throw new Error(`Failed to read metadata for ${file}`);
4151
4341
  }
4152
4342
  const asset = {
4153
4343
  metadata,
@@ -4326,9 +4516,20 @@ class LocalFileDestinationProvider {
4326
4516
  results = {};
4327
4517
  #providersMetadata = {};
4328
4518
  #archive = {};
4519
+ #diagnostics;
4329
4520
  constructor(options) {
4330
4521
  this.options = options;
4331
4522
  }
4523
+ #reportInfo(message) {
4524
+ this.#diagnostics?.report({
4525
+ details: {
4526
+ createdAt: /* @__PURE__ */ new Date(),
4527
+ message,
4528
+ origin: "file-destination-provider"
4529
+ },
4530
+ kind: "info"
4531
+ });
4532
+ }
4332
4533
  get #archivePath() {
4333
4534
  const { encryption, compression, file } = this.options;
4334
4535
  let filePath = `${file.path}.tar`;
@@ -4345,9 +4546,11 @@ class LocalFileDestinationProvider {
4345
4546
  return this;
4346
4547
  }
4347
4548
  createGzip() {
4549
+ this.#reportInfo("creating gzip");
4348
4550
  return zip$1.createGzip();
4349
4551
  }
4350
- bootstrap() {
4552
+ bootstrap(diagnostics) {
4553
+ this.#diagnostics = diagnostics;
4351
4554
  const { compression, encryption } = this.options;
4352
4555
  if (encryption.enabled && !encryption.key) {
4353
4556
  throw new Error("Can't encrypt without a key");
@@ -4386,6 +4589,7 @@ class LocalFileDestinationProvider {
4386
4589
  }
4387
4590
  }
4388
4591
  async rollback() {
4592
+ this.#reportInfo("rolling back");
4389
4593
  await this.close();
4390
4594
  await rm(this.#archivePath, { force: true });
4391
4595
  }
@@ -4393,6 +4597,7 @@ class LocalFileDestinationProvider {
4393
4597
  return null;
4394
4598
  }
4395
4599
  async #writeMetadata() {
4600
+ this.#reportInfo("writing metadata");
4396
4601
  const metadata = this.#providersMetadata.source;
4397
4602
  if (metadata) {
4398
4603
  await new Promise((resolve) => {
@@ -4413,6 +4618,7 @@ class LocalFileDestinationProvider {
4413
4618
  if (!this.#archive.stream) {
4414
4619
  throw new Error("Archive stream is unavailable");
4415
4620
  }
4621
+ this.#reportInfo("creating schemas write stream");
4416
4622
  const filePathFactory = createFilePathFactory("schemas");
4417
4623
  const entryStream = createTarEntryStream(
4418
4624
  this.#archive.stream,
@@ -4425,6 +4631,7 @@ class LocalFileDestinationProvider {
4425
4631
  if (!this.#archive.stream) {
4426
4632
  throw new Error("Archive stream is unavailable");
4427
4633
  }
4634
+ this.#reportInfo("creating entities write stream");
4428
4635
  const filePathFactory = createFilePathFactory("entities");
4429
4636
  const entryStream = createTarEntryStream(
4430
4637
  this.#archive.stream,
@@ -4437,6 +4644,7 @@ class LocalFileDestinationProvider {
4437
4644
  if (!this.#archive.stream) {
4438
4645
  throw new Error("Archive stream is unavailable");
4439
4646
  }
4647
+ this.#reportInfo("creating links write stream");
4440
4648
  const filePathFactory = createFilePathFactory("links");
4441
4649
  const entryStream = createTarEntryStream(
4442
4650
  this.#archive.stream,
@@ -4449,6 +4657,7 @@ class LocalFileDestinationProvider {
4449
4657
  if (!this.#archive.stream) {
4450
4658
  throw new Error("Archive stream is unavailable");
4451
4659
  }
4660
+ this.#reportInfo("creating configuration write stream");
4452
4661
  const filePathFactory = createFilePathFactory("configuration");
4453
4662
  const entryStream = createTarEntryStream(
4454
4663
  this.#archive.stream,
@@ -4462,6 +4671,7 @@ class LocalFileDestinationProvider {
4462
4671
  if (!archiveStream) {
4463
4672
  throw new Error("Archive stream is unavailable");
4464
4673
  }
4674
+ this.#reportInfo("creating assets write stream");
4465
4675
  return new Writable({
4466
4676
  objectMode: true,
4467
4677
  write(data, _encoding, callback) {