@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.js CHANGED
@@ -21,8 +21,7 @@ const tar$1 = require("tar-stream");
21
21
  const Stringer = require("stream-json/jsonl/Stringer");
22
22
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
23
23
  function _interopNamespace(e) {
24
- if (e && e.__esModule)
25
- return e;
24
+ if (e && e.__esModule) return e;
26
25
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
27
26
  if (e) {
28
27
  for (const k in e) {
@@ -326,8 +325,57 @@ const middleware = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
326
325
  __proto__: null,
327
326
  runMiddleware
328
327
  }, Symbol.toStringTag, { value: "Module" }));
328
+ const createDiagnosticReporter = (options = {}) => {
329
+ const { stackSize = -1 } = options;
330
+ const emitter = new events.EventEmitter();
331
+ const stack = [];
332
+ const addListener = (event, listener) => {
333
+ emitter.on(event, listener);
334
+ };
335
+ const isDiagnosticValid = (diagnostic2) => {
336
+ if (!diagnostic2.kind || !diagnostic2.details || !diagnostic2.details.message) {
337
+ return false;
338
+ }
339
+ return true;
340
+ };
341
+ return {
342
+ stack: {
343
+ get size() {
344
+ return stack.length;
345
+ },
346
+ get items() {
347
+ return stack;
348
+ }
349
+ },
350
+ report(diagnostic2) {
351
+ if (!isDiagnosticValid(diagnostic2)) {
352
+ return this;
353
+ }
354
+ emitter.emit("diagnostic", diagnostic2);
355
+ emitter.emit(`diagnostic.${diagnostic2.kind}`, diagnostic2);
356
+ if (stackSize !== -1 && stack.length >= stackSize) {
357
+ stack.shift();
358
+ }
359
+ stack.push(diagnostic2);
360
+ return this;
361
+ },
362
+ onDiagnostic(listener) {
363
+ addListener("diagnostic", listener);
364
+ return this;
365
+ },
366
+ on(kind, listener) {
367
+ addListener(`diagnostic.${kind}`, listener);
368
+ return this;
369
+ }
370
+ };
371
+ };
372
+ const diagnostic = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
373
+ __proto__: null,
374
+ createDiagnosticReporter
375
+ }, Symbol.toStringTag, { value: "Module" }));
329
376
  const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
330
377
  __proto__: null,
378
+ diagnostics: diagnostic,
331
379
  encryption: index$9,
332
380
  json,
333
381
  middleware,
@@ -428,50 +476,6 @@ const validateProvider = (type, provider) => {
428
476
  );
429
477
  }
430
478
  };
431
- const createDiagnosticReporter = (options = {}) => {
432
- const { stackSize = -1 } = options;
433
- const emitter = new events.EventEmitter();
434
- const stack = [];
435
- const addListener = (event, listener) => {
436
- emitter.on(event, listener);
437
- };
438
- const isDiagnosticValid = (diagnostic) => {
439
- if (!diagnostic.kind || !diagnostic.details || !diagnostic.details.message) {
440
- return false;
441
- }
442
- return true;
443
- };
444
- return {
445
- stack: {
446
- get size() {
447
- return stack.length;
448
- },
449
- get items() {
450
- return stack;
451
- }
452
- },
453
- report(diagnostic) {
454
- if (!isDiagnosticValid(diagnostic)) {
455
- return this;
456
- }
457
- emitter.emit("diagnostic", diagnostic);
458
- emitter.emit(`diagnostic.${diagnostic.kind}`, diagnostic);
459
- if (stackSize !== -1 && stack.length >= stackSize) {
460
- stack.shift();
461
- }
462
- stack.push(diagnostic);
463
- return this;
464
- },
465
- onDiagnostic(listener) {
466
- addListener("diagnostic", listener);
467
- return this;
468
- },
469
- on(kind, listener) {
470
- addListener(`diagnostic.${kind}`, listener);
471
- return this;
472
- }
473
- };
474
- };
475
479
  class ProviderError extends DataTransferError {
476
480
  constructor(severity, message, details) {
477
481
  super("provider", severity, message, details);
@@ -604,7 +608,7 @@ class TransferEngine {
604
608
  reportInfo(message, params) {
605
609
  this.diagnostics.report({
606
610
  kind: "info",
607
- details: { createdAt: /* @__PURE__ */ new Date(), message, params }
611
+ details: { createdAt: /* @__PURE__ */ new Date(), message, params, origin: "engine" }
608
612
  });
609
613
  }
610
614
  /**
@@ -896,8 +900,8 @@ ${formattedDiffs}`,
896
900
  */
897
901
  async bootstrap() {
898
902
  const results = await Promise.allSettled([
899
- this.sourceProvider.bootstrap?.(),
900
- this.destinationProvider.bootstrap?.()
903
+ this.sourceProvider.bootstrap?.(this.diagnostics),
904
+ this.destinationProvider.bootstrap?.(this.diagnostics)
901
905
  ]);
902
906
  results.forEach((result) => {
903
907
  if (result.status === "rejected") {
@@ -1902,6 +1906,7 @@ class LocalStrapiDestinationProvider {
1902
1906
  transaction;
1903
1907
  uploadsBackupDirectoryName;
1904
1908
  onWarning;
1909
+ #diagnostics;
1905
1910
  /**
1906
1911
  * The entities mapper is used to map old entities to their new IDs
1907
1912
  */
@@ -1911,7 +1916,8 @@ class LocalStrapiDestinationProvider {
1911
1916
  this.#entitiesMapper = {};
1912
1917
  this.uploadsBackupDirectoryName = `uploads_backup_${Date.now()}`;
1913
1918
  }
1914
- async bootstrap() {
1919
+ async bootstrap(diagnostics) {
1920
+ this.#diagnostics = diagnostics;
1915
1921
  this.#validateOptions();
1916
1922
  this.strapi = await this.options.getStrapi();
1917
1923
  if (!this.strapi) {
@@ -1928,6 +1934,16 @@ class LocalStrapiDestinationProvider {
1928
1934
  const excluded = this.options.restore?.entities?.exclude && this.options.restore?.entities.exclude.includes(type);
1929
1935
  return !excluded && !notIncluded;
1930
1936
  };
1937
+ #reportInfo(message) {
1938
+ this.#diagnostics?.report({
1939
+ details: {
1940
+ createdAt: /* @__PURE__ */ new Date(),
1941
+ message,
1942
+ origin: "local-destination-provider"
1943
+ },
1944
+ kind: "info"
1945
+ });
1946
+ }
1931
1947
  async close() {
1932
1948
  const { autoDestroy } = this.options;
1933
1949
  this.transaction?.end();
@@ -1936,6 +1952,7 @@ class LocalStrapiDestinationProvider {
1936
1952
  }
1937
1953
  }
1938
1954
  #validateOptions() {
1955
+ this.#reportInfo("validating options");
1939
1956
  if (!VALID_CONFLICT_STRATEGIES.includes(this.options.strategy)) {
1940
1957
  throw new ProviderValidationError(`Invalid strategy ${this.options.strategy}`, {
1941
1958
  check: "strategy",
@@ -1952,10 +1969,12 @@ class LocalStrapiDestinationProvider {
1952
1969
  if (!this.options.restore) {
1953
1970
  throw new ProviderValidationError("Missing restore options");
1954
1971
  }
1972
+ this.#reportInfo("deleting record ");
1955
1973
  return deleteRecords(this.strapi, this.options.restore);
1956
1974
  }
1957
1975
  async #deleteAllAssets(trx) {
1958
1976
  assertValidStrapi(this.strapi);
1977
+ this.#reportInfo("deleting all assets");
1959
1978
  if (!this.#areAssetsIncluded()) {
1960
1979
  return;
1961
1980
  }
@@ -1968,9 +1987,12 @@ class LocalStrapiDestinationProvider {
1968
1987
  }
1969
1988
  }
1970
1989
  }
1990
+ this.#reportInfo("deleted all assets");
1971
1991
  }
1972
1992
  async rollback() {
1993
+ this.#reportInfo("Rolling back transaction");
1973
1994
  await this.transaction?.rollback();
1995
+ this.#reportInfo("Rolled back transaction");
1974
1996
  }
1975
1997
  async beforeTransfer() {
1976
1998
  if (!this.strapi) {
@@ -1989,6 +2011,7 @@ class LocalStrapiDestinationProvider {
1989
2011
  });
1990
2012
  }
1991
2013
  getMetadata() {
2014
+ this.#reportInfo("getting metadata");
1992
2015
  assertValidStrapi(this.strapi, "Not able to get Schemas");
1993
2016
  const strapiVersion = this.strapi.config.get("info.strapi");
1994
2017
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2000,6 +2023,7 @@ class LocalStrapiDestinationProvider {
2000
2023
  };
2001
2024
  }
2002
2025
  getSchemas() {
2026
+ this.#reportInfo("getting schema");
2003
2027
  assertValidStrapi(this.strapi, "Not able to get Schemas");
2004
2028
  const schemas = schemasToValidJSON({
2005
2029
  ...this.strapi.contentTypes,
@@ -2009,6 +2033,7 @@ class LocalStrapiDestinationProvider {
2009
2033
  }
2010
2034
  createEntitiesWriteStream() {
2011
2035
  assertValidStrapi(this.strapi, "Not able to import entities");
2036
+ this.#reportInfo("creating entities stream");
2012
2037
  const { strategy } = this.options;
2013
2038
  const updateMappingTable = (type, oldID, newID) => {
2014
2039
  if (!this.#entitiesMapper[type]) {
@@ -2035,6 +2060,7 @@ class LocalStrapiDestinationProvider {
2035
2060
  return;
2036
2061
  }
2037
2062
  if (this.strapi.config.get("plugin::upload").provider === "local") {
2063
+ this.#reportInfo("creating assets backup directory");
2038
2064
  const assetsDirectory = path__default.default.join(this.strapi.dirs.static.public, "uploads");
2039
2065
  const backupDirectory = path__default.default.join(
2040
2066
  this.strapi.dirs.static.public,
@@ -2050,6 +2076,7 @@ class LocalStrapiDestinationProvider {
2050
2076
  await fse__namespace.move(assetsDirectory, backupDirectory);
2051
2077
  await fse__namespace.mkdir(assetsDirectory);
2052
2078
  await fse__namespace.outputFile(path__default.default.join(assetsDirectory, ".gitkeep"), "");
2079
+ this.#reportInfo(`created assets backup directory ${backupDirectory}`);
2053
2080
  } catch (err) {
2054
2081
  throw new ProviderTransferError(
2055
2082
  "The backup folder for the assets could not be created inside the public folder. Please ensure Strapi has write permissions on the public directory",
@@ -2067,17 +2094,20 @@ class LocalStrapiDestinationProvider {
2067
2094
  return;
2068
2095
  }
2069
2096
  if (this.strapi.config.get("plugin::upload").provider === "local") {
2097
+ this.#reportInfo("removing assets backup");
2070
2098
  assertValidStrapi(this.strapi);
2071
2099
  const backupDirectory = path__default.default.join(
2072
2100
  this.strapi.dirs.static.public,
2073
2101
  this.uploadsBackupDirectoryName
2074
2102
  );
2075
2103
  await fse__namespace.rm(backupDirectory, { recursive: true, force: true });
2104
+ this.#reportInfo("successfully removed assets backup");
2076
2105
  }
2077
2106
  }
2078
2107
  // TODO: Move this logic to the restore strategy
2079
2108
  async createAssetsWriteStream() {
2080
2109
  assertValidStrapi(this.strapi, "Not able to stream Assets");
2110
+ this.#reportInfo("creating assets write stream");
2081
2111
  if (!this.#areAssetsIncluded()) {
2082
2112
  throw new ProviderTransferError(
2083
2113
  "Attempting to transfer assets when `assets` is not set in restore options"
@@ -2086,7 +2116,6 @@ class LocalStrapiDestinationProvider {
2086
2116
  const removeAssetsBackup = this.#removeAssetsBackup.bind(this);
2087
2117
  const strapi2 = this.strapi;
2088
2118
  const transaction2 = this.transaction;
2089
- const backupDirectory = this.uploadsBackupDirectoryName;
2090
2119
  const fileEntitiesMapper = this.#entitiesMapper["plugin::upload.file"];
2091
2120
  const restoreMediaEntitiesContent = this.#isContentTypeIncluded("plugin::upload.file");
2092
2121
  return new stream$1.Writable({
@@ -2097,47 +2126,28 @@ class LocalStrapiDestinationProvider {
2097
2126
  },
2098
2127
  async write(chunk, _encoding, callback) {
2099
2128
  await transaction2?.attach(async () => {
2100
- if (!chunk.metadata) {
2101
- const assetsDirectory = path__default.default.join(strapi2.dirs.static.public, "uploads");
2102
- const entryPath = path__default.default.join(assetsDirectory, chunk.filename);
2103
- const writableStream = fse__namespace.createWriteStream(entryPath);
2104
- chunk.stream.pipe(writableStream).on("close", () => {
2105
- callback(null);
2106
- }).on("error", async (error) => {
2107
- const errorMessage = error.code === "ENOSPC" ? " Your server doesn't have space to proceed with the import. " : " ";
2108
- try {
2109
- await fse__namespace.rm(assetsDirectory, { recursive: true, force: true });
2110
- this.destroy(
2111
- new ProviderTransferError(
2112
- `There was an error during the transfer process.${errorMessage}The original files have been restored to ${assetsDirectory}`
2113
- )
2114
- );
2115
- } catch (err) {
2116
- throw new ProviderTransferError(
2117
- `There was an error doing the rollback process. The original files are in ${backupDirectory}, but we failed to restore them to ${assetsDirectory}`
2118
- );
2119
- } finally {
2120
- callback(error);
2121
- }
2122
- });
2123
- return;
2124
- }
2125
2129
  const uploadData = {
2126
2130
  ...chunk.metadata,
2127
2131
  stream: stream$1.Readable.from(chunk.stream),
2128
2132
  buffer: chunk?.buffer
2129
2133
  };
2130
2134
  const provider = strapi2.config.get("plugin::upload").provider;
2135
+ const fileId = fileEntitiesMapper?.[uploadData.id];
2136
+ if (!fileId) {
2137
+ callback(new Error(`File ID not found for ID: ${uploadData.id}`));
2138
+ }
2131
2139
  try {
2132
2140
  await strapi2.plugin("upload").provider.uploadStream(uploadData);
2133
2141
  if (!restoreMediaEntitiesContent) {
2134
2142
  return callback();
2135
2143
  }
2136
2144
  if (uploadData?.type) {
2137
- const condition = uploadData?.id ? { id: fileEntitiesMapper[uploadData.id] } : { hash: uploadData.mainHash };
2138
2145
  const entry2 = await strapi2.db.query("plugin::upload.file").findOne({
2139
- where: condition
2146
+ where: { id: fileId }
2140
2147
  });
2148
+ if (!entry2) {
2149
+ throw new Error("file not found");
2150
+ }
2141
2151
  const specificFormat = entry2?.formats?.[uploadData.type];
2142
2152
  if (specificFormat) {
2143
2153
  specificFormat.url = uploadData.url;
@@ -2152,8 +2162,11 @@ class LocalStrapiDestinationProvider {
2152
2162
  return callback();
2153
2163
  }
2154
2164
  const entry = await strapi2.db.query("plugin::upload.file").findOne({
2155
- where: { id: fileEntitiesMapper[uploadData.id] }
2165
+ where: { id: fileId }
2156
2166
  });
2167
+ if (!entry) {
2168
+ throw new Error("file not found");
2169
+ }
2157
2170
  entry.url = uploadData.url;
2158
2171
  await strapi2.db.query("plugin::upload.file").update({
2159
2172
  where: { id: entry.id },
@@ -2172,6 +2185,7 @@ class LocalStrapiDestinationProvider {
2172
2185
  }
2173
2186
  async createConfigurationWriteStream() {
2174
2187
  assertValidStrapi(this.strapi, "Not able to stream Configurations");
2188
+ this.#reportInfo("creating configuration write stream");
2175
2189
  const { strategy } = this.options;
2176
2190
  if (strategy === "restore") {
2177
2191
  return createConfigurationWriteStream(this.strapi, this.transaction);
@@ -2183,6 +2197,7 @@ class LocalStrapiDestinationProvider {
2183
2197
  });
2184
2198
  }
2185
2199
  async createLinksWriteStream() {
2200
+ this.#reportInfo("creating links write stream");
2186
2201
  if (!this.strapi) {
2187
2202
  throw new Error("Not able to stream links. Strapi instance not found");
2188
2203
  }
@@ -2381,12 +2396,24 @@ class LocalStrapiSourceProvider {
2381
2396
  type = "source";
2382
2397
  options;
2383
2398
  strapi;
2399
+ #diagnostics;
2384
2400
  constructor(options) {
2385
2401
  this.options = options;
2386
2402
  }
2387
- async bootstrap() {
2403
+ async bootstrap(diagnostics) {
2404
+ this.#diagnostics = diagnostics;
2388
2405
  this.strapi = await this.options.getStrapi();
2389
2406
  }
2407
+ #reportInfo(message) {
2408
+ this.#diagnostics?.report({
2409
+ details: {
2410
+ createdAt: /* @__PURE__ */ new Date(),
2411
+ message,
2412
+ origin: "local-source-provider"
2413
+ },
2414
+ kind: "info"
2415
+ });
2416
+ }
2390
2417
  async close() {
2391
2418
  const { autoDestroy } = this.options;
2392
2419
  if (autoDestroy === void 0 || autoDestroy === true) {
@@ -2394,6 +2421,7 @@ class LocalStrapiSourceProvider {
2394
2421
  }
2395
2422
  }
2396
2423
  getMetadata() {
2424
+ this.#reportInfo("getting metadata");
2397
2425
  const strapiVersion = strapi.config.get("info.strapi");
2398
2426
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2399
2427
  return {
@@ -2405,6 +2433,7 @@ class LocalStrapiSourceProvider {
2405
2433
  }
2406
2434
  async createEntitiesReadStream() {
2407
2435
  assertValidStrapi(this.strapi, "Not able to stream entities");
2436
+ this.#reportInfo("creating entities read stream");
2408
2437
  return streamChain.chain([
2409
2438
  // Entities stream
2410
2439
  createEntitiesStream(this.strapi),
@@ -2414,14 +2443,17 @@ class LocalStrapiSourceProvider {
2414
2443
  }
2415
2444
  createLinksReadStream() {
2416
2445
  assertValidStrapi(this.strapi, "Not able to stream links");
2446
+ this.#reportInfo("creating links read stream");
2417
2447
  return createLinksStream(this.strapi);
2418
2448
  }
2419
2449
  createConfigurationReadStream() {
2420
2450
  assertValidStrapi(this.strapi, "Not able to stream configuration");
2451
+ this.#reportInfo("creating configuration read stream");
2421
2452
  return createConfigurationStream(this.strapi);
2422
2453
  }
2423
2454
  getSchemas() {
2424
2455
  assertValidStrapi(this.strapi, "Not able to get Schemas");
2456
+ this.#reportInfo("getting schemas");
2425
2457
  const schemas = schemasToValidJSON({
2426
2458
  ...this.strapi.contentTypes,
2427
2459
  ...this.strapi.components
@@ -2433,13 +2465,14 @@ class LocalStrapiSourceProvider {
2433
2465
  }
2434
2466
  createAssetsReadStream() {
2435
2467
  assertValidStrapi(this.strapi, "Not able to stream assets");
2468
+ this.#reportInfo("creating assets read stream");
2436
2469
  return createAssetsStream(this.strapi);
2437
2470
  }
2438
2471
  }
2439
2472
  const createDispatcher = (ws2, retryMessageOptions = {
2440
2473
  retryMessageMaxRetries: 5,
2441
2474
  retryMessageTimeout: 3e4
2442
- }) => {
2475
+ }, reportInfo) => {
2443
2476
  const state = {};
2444
2477
  const dispatch = async (message, options = {}) => {
2445
2478
  if (!ws2) {
@@ -2452,6 +2485,16 @@ const createDispatcher = (ws2, retryMessageOptions = {
2452
2485
  if (options.attachTransfer) {
2453
2486
  Object.assign(payload, { transferID: state.transfer?.id });
2454
2487
  }
2488
+ if (message.type === "command") {
2489
+ reportInfo?.(
2490
+ `dispatching message command:${message.command} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2491
+ );
2492
+ } else if (message.type === "transfer") {
2493
+ const messageToSend = message;
2494
+ reportInfo?.(
2495
+ `dispatching message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2496
+ );
2497
+ }
2455
2498
  const stringifiedPayload = JSON.stringify(payload);
2456
2499
  ws2.send(stringifiedPayload, (error) => {
2457
2500
  if (error) {
@@ -2474,6 +2517,16 @@ const createDispatcher = (ws2, retryMessageOptions = {
2474
2517
  const interval = setInterval(sendPeriodically, retryMessageTimeout);
2475
2518
  const onResponse = (raw) => {
2476
2519
  const response = JSON.parse(raw.toString());
2520
+ if (message.type === "command") {
2521
+ reportInfo?.(
2522
+ `received response to message command: ${message.command} uuid: ${uuid} sent: ${numberOfTimesMessageWasSent}`
2523
+ );
2524
+ } else if (message.type === "transfer") {
2525
+ const messageToSend = message;
2526
+ reportInfo?.(
2527
+ `received response to message action:${messageToSend.action} ${messageToSend.kind === "step" ? `step:${messageToSend.step}` : ""} uuid:${uuid} sent:${numberOfTimesMessageWasSent}`
2528
+ );
2529
+ }
2477
2530
  if (response.uuid === uuid) {
2478
2531
  clearInterval(interval);
2479
2532
  if (response.error) {
@@ -2530,7 +2583,7 @@ const createDispatcher = (ws2, retryMessageOptions = {
2530
2583
  dispatchTransferStep
2531
2584
  };
2532
2585
  };
2533
- const connectToWebsocket = (address, options) => {
2586
+ const connectToWebsocket = (address, options, diagnostics) => {
2534
2587
  return new Promise((resolve, reject2) => {
2535
2588
  const server = new ws.WebSocket(address, options);
2536
2589
  server.once("open", () => {
@@ -2564,6 +2617,14 @@ const connectToWebsocket = (address, options) => {
2564
2617
  )
2565
2618
  );
2566
2619
  });
2620
+ server.on("message", (raw) => {
2621
+ const response = JSON.parse(raw.toString());
2622
+ if (response.diagnostic) {
2623
+ diagnostics?.report({
2624
+ ...response.diagnostic
2625
+ });
2626
+ }
2627
+ });
2567
2628
  server.once("error", (err) => {
2568
2629
  reject2(
2569
2630
  new ProviderTransferError(err.message, {
@@ -2605,6 +2666,7 @@ class RemoteStrapiDestinationProvider {
2605
2666
  dispatcher;
2606
2667
  transferID;
2607
2668
  stats;
2669
+ #diagnostics;
2608
2670
  constructor(options) {
2609
2671
  this.options = options;
2610
2672
  this.ws = null;
@@ -2731,7 +2793,18 @@ class RemoteStrapiDestinationProvider {
2731
2793
  }
2732
2794
  });
2733
2795
  }
2734
- async bootstrap() {
2796
+ #reportInfo(message) {
2797
+ this.#diagnostics?.report({
2798
+ details: {
2799
+ createdAt: /* @__PURE__ */ new Date(),
2800
+ message,
2801
+ origin: "remote-destination-provider"
2802
+ },
2803
+ kind: "info"
2804
+ });
2805
+ }
2806
+ async bootstrap(diagnostics) {
2807
+ this.#diagnostics = diagnostics;
2735
2808
  const { url, auth } = this.options;
2736
2809
  const validProtocols = ["https:", "http:"];
2737
2810
  let ws2;
@@ -2748,11 +2821,12 @@ class RemoteStrapiDestinationProvider {
2748
2821
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
2749
2822
  url.pathname
2750
2823
  )}${TRANSFER_PATH}/push`;
2824
+ this.#reportInfo("establishing websocket connection");
2751
2825
  if (!auth) {
2752
- ws2 = await connectToWebsocket(wsUrl);
2826
+ ws2 = await connectToWebsocket(wsUrl, void 0, this.#diagnostics);
2753
2827
  } else if (auth.type === "token") {
2754
2828
  const headers = { Authorization: `Bearer ${auth.token}` };
2755
- ws2 = await connectToWebsocket(wsUrl, { headers });
2829
+ ws2 = await connectToWebsocket(wsUrl, { headers }, this.#diagnostics);
2756
2830
  } else {
2757
2831
  throw new ProviderValidationError("Auth method not available", {
2758
2832
  check: "auth.type",
@@ -2761,10 +2835,19 @@ class RemoteStrapiDestinationProvider {
2761
2835
  }
2762
2836
  });
2763
2837
  }
2838
+ this.#reportInfo("established websocket connection");
2764
2839
  this.ws = ws2;
2765
2840
  const { retryMessageOptions } = this.options;
2766
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
2841
+ this.#reportInfo("creating dispatcher");
2842
+ this.dispatcher = createDispatcher(
2843
+ this.ws,
2844
+ retryMessageOptions,
2845
+ (message) => this.#reportInfo(message)
2846
+ );
2847
+ this.#reportInfo("created dispatcher");
2848
+ this.#reportInfo("initialize transfer");
2767
2849
  this.transferID = await this.initTransfer();
2850
+ this.#reportInfo(`initialized transfer ${this.transferID}`);
2768
2851
  this.dispatcher.setTransferProperties({ id: this.transferID, kind: "push" });
2769
2852
  await this.dispatcher.dispatchTransferAction("bootstrap");
2770
2853
  }
@@ -2891,6 +2974,7 @@ class RemoteStrapiSourceProvider {
2891
2974
  this.dispatcher = null;
2892
2975
  }
2893
2976
  results;
2977
+ #diagnostics;
2894
2978
  async #createStageReadStream(stage) {
2895
2979
  const startResult = await this.#startStep(stage);
2896
2980
  if (startResult instanceof Error) {
@@ -3073,7 +3157,18 @@ class RemoteStrapiSourceProvider {
3073
3157
  }
3074
3158
  return res.transferID;
3075
3159
  }
3076
- async bootstrap() {
3160
+ #reportInfo(message) {
3161
+ this.#diagnostics?.report({
3162
+ details: {
3163
+ createdAt: /* @__PURE__ */ new Date(),
3164
+ message,
3165
+ origin: "remote-source-provider"
3166
+ },
3167
+ kind: "info"
3168
+ });
3169
+ }
3170
+ async bootstrap(diagnostics) {
3171
+ this.#diagnostics = diagnostics;
3077
3172
  const { url, auth } = this.options;
3078
3173
  let ws2;
3079
3174
  this.assertValidProtocol(url);
@@ -3081,11 +3176,12 @@ class RemoteStrapiSourceProvider {
3081
3176
  const wsUrl = `${wsProtocol}//${url.host}${trimTrailingSlash(
3082
3177
  url.pathname
3083
3178
  )}${TRANSFER_PATH}/pull`;
3179
+ this.#reportInfo("establishing websocket connection");
3084
3180
  if (!auth) {
3085
- ws2 = await connectToWebsocket(wsUrl);
3181
+ ws2 = await connectToWebsocket(wsUrl, void 0, this.#diagnostics);
3086
3182
  } else if (auth.type === "token") {
3087
3183
  const headers = { Authorization: `Bearer ${auth.token}` };
3088
- ws2 = await connectToWebsocket(wsUrl, { headers });
3184
+ ws2 = await connectToWebsocket(wsUrl, { headers }, this.#diagnostics);
3089
3185
  } else {
3090
3186
  throw new ProviderValidationError("Auth method not available", {
3091
3187
  check: "auth.type",
@@ -3094,10 +3190,19 @@ class RemoteStrapiSourceProvider {
3094
3190
  }
3095
3191
  });
3096
3192
  }
3193
+ this.#reportInfo("established websocket connection");
3097
3194
  this.ws = ws2;
3098
3195
  const { retryMessageOptions } = this.options;
3099
- this.dispatcher = createDispatcher(this.ws, retryMessageOptions);
3196
+ this.#reportInfo("creating dispatcher");
3197
+ this.dispatcher = createDispatcher(
3198
+ this.ws,
3199
+ retryMessageOptions,
3200
+ (message) => this.#reportInfo(message)
3201
+ );
3202
+ this.#reportInfo("creating dispatcher");
3203
+ this.#reportInfo("initialize transfer");
3100
3204
  const transferID = await this.initTransfer();
3205
+ this.#reportInfo(`initialized transfer ${transferID}`);
3101
3206
  this.dispatcher.setTransferProperties({ id: transferID, kind: "pull" });
3102
3207
  await this.dispatcher.dispatchTransferAction("bootstrap");
3103
3208
  }
@@ -3327,6 +3432,7 @@ const handlerControllerFactory = (implementation) => (options) => {
3327
3432
  const cb = (ws2) => {
3328
3433
  const state = { id: void 0 };
3329
3434
  const messageUUIDs = /* @__PURE__ */ new Set();
3435
+ const diagnostics = createDiagnosticReporter();
3330
3436
  const cannotRespondHandler = (err) => {
3331
3437
  strapi?.log?.error(
3332
3438
  "[Data transfer] Cannot send error response to client, closing connection"
@@ -3360,6 +3466,9 @@ const handlerControllerFactory = (implementation) => (options) => {
3360
3466
  set response(response) {
3361
3467
  state.response = response;
3362
3468
  },
3469
+ get diagnostics() {
3470
+ return diagnostics;
3471
+ },
3363
3472
  addUUID(uuid) {
3364
3473
  messageUUIDs.add(uuid);
3365
3474
  },
@@ -3475,6 +3584,10 @@ const handlerControllerFactory = (implementation) => (options) => {
3475
3584
  onError() {
3476
3585
  },
3477
3586
  onClose() {
3587
+ },
3588
+ onInfo() {
3589
+ },
3590
+ onWarning() {
3478
3591
  }
3479
3592
  };
3480
3593
  const handler = Object.assign(Object.create(prototype), implementation(prototype));
@@ -3507,6 +3620,14 @@ const handlerControllerFactory = (implementation) => (options) => {
3507
3620
  cannotRespondHandler(err);
3508
3621
  }
3509
3622
  });
3623
+ diagnostics.onDiagnostic((diagnostic2) => {
3624
+ const uuid = crypto.randomUUID();
3625
+ const payload = JSON.stringify({
3626
+ diagnostic: diagnostic2,
3627
+ uuid
3628
+ });
3629
+ handler.send(payload);
3630
+ });
3510
3631
  };
3511
3632
  try {
3512
3633
  handleWSUpgrade(wss, ctx, cb);
@@ -3542,6 +3663,26 @@ const createPushController = handlerControllerFactory((proto) => ({
3542
3663
  verifyAuth() {
3543
3664
  return proto.verifyAuth.call(this, TRANSFER_KIND$1);
3544
3665
  },
3666
+ onInfo(message) {
3667
+ this.diagnostics?.report({
3668
+ details: {
3669
+ message,
3670
+ origin: "push-handler",
3671
+ createdAt: /* @__PURE__ */ new Date()
3672
+ },
3673
+ kind: "info"
3674
+ });
3675
+ },
3676
+ onWarning(message) {
3677
+ this.diagnostics?.report({
3678
+ details: {
3679
+ message,
3680
+ createdAt: /* @__PURE__ */ new Date(),
3681
+ origin: "push-handler"
3682
+ },
3683
+ kind: "warning"
3684
+ });
3685
+ },
3545
3686
  cleanup() {
3546
3687
  proto.cleanup.call(this);
3547
3688
  this.streams = {};
@@ -3618,6 +3759,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3618
3759
  proto.addUUID(uuid);
3619
3760
  if (type === "command") {
3620
3761
  const { command } = msg;
3762
+ this.onInfo(`received command:${command} uuid:${uuid}`);
3621
3763
  await this.executeAndRespond(uuid, () => {
3622
3764
  this.assertValidTransferCommand(command);
3623
3765
  if (command === "status") {
@@ -3626,6 +3768,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3626
3768
  return this[command](msg.params);
3627
3769
  });
3628
3770
  } else if (type === "transfer") {
3771
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3629
3772
  await this.executeAndRespond(uuid, async () => {
3630
3773
  await this.verifyAuth();
3631
3774
  this.assertValidTransfer();
@@ -3727,6 +3870,9 @@ const createPushController = handlerControllerFactory((proto) => ({
3727
3870
  }
3728
3871
  this.flow?.set(step);
3729
3872
  }
3873
+ if (action === "bootstrap") {
3874
+ return this.provider?.[action](this.diagnostics);
3875
+ }
3730
3876
  return this.provider?.[action]();
3731
3877
  },
3732
3878
  async streamAsset(payload) {
@@ -3792,6 +3938,7 @@ const createPushController = handlerControllerFactory((proto) => ({
3792
3938
  getStrapi: () => strapi
3793
3939
  });
3794
3940
  this.provider.onWarning = (message) => {
3941
+ this.onWarning(message);
3795
3942
  strapi.log.warn(message);
3796
3943
  };
3797
3944
  return { transferID: this.transferID };
@@ -3832,6 +3979,26 @@ const createPullController = handlerControllerFactory((proto) => ({
3832
3979
  this.streams = {};
3833
3980
  delete this.provider;
3834
3981
  },
3982
+ onInfo(message) {
3983
+ this.diagnostics?.report({
3984
+ details: {
3985
+ message,
3986
+ origin: "pull-handler",
3987
+ createdAt: /* @__PURE__ */ new Date()
3988
+ },
3989
+ kind: "info"
3990
+ });
3991
+ },
3992
+ onWarning(message) {
3993
+ this.diagnostics?.report({
3994
+ details: {
3995
+ message,
3996
+ createdAt: /* @__PURE__ */ new Date(),
3997
+ origin: "pull-handler"
3998
+ },
3999
+ kind: "warning"
4000
+ });
4001
+ },
3835
4002
  assertValidTransferAction(action) {
3836
4003
  const validActions = VALID_TRANSFER_ACTIONS;
3837
4004
  if (validActions.includes(action)) {
@@ -3861,6 +4028,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3861
4028
  proto.addUUID(uuid);
3862
4029
  if (type === "command") {
3863
4030
  const { command } = msg;
4031
+ this.onInfo(`received command:${command} uuid:${uuid}`);
3864
4032
  await this.executeAndRespond(uuid, () => {
3865
4033
  this.assertValidTransferCommand(command);
3866
4034
  if (command === "status") {
@@ -3869,6 +4037,7 @@ const createPullController = handlerControllerFactory((proto) => ({
3869
4037
  return this[command](msg.params);
3870
4038
  });
3871
4039
  } else if (type === "transfer") {
4040
+ this.onInfo(`received transfer action:${msg.action} step:${msg.kind} uuid:${uuid}`);
3872
4041
  await this.executeAndRespond(uuid, async () => {
3873
4042
  await this.verifyAuth();
3874
4043
  this.assertValidTransfer();
@@ -3890,6 +4059,9 @@ const createPullController = handlerControllerFactory((proto) => ({
3890
4059
  async onTransferAction(msg) {
3891
4060
  const { action } = msg;
3892
4061
  this.assertValidTransferAction(action);
4062
+ if (action === "bootstrap") {
4063
+ return this.provider?.[action](this.diagnostics);
4064
+ }
3893
4065
  return this.provider?.[action]();
3894
4066
  },
3895
4067
  async flush(stage, id) {
@@ -4088,6 +4260,7 @@ class LocalFileSourceProvider {
4088
4260
  name = "source::local-file";
4089
4261
  options;
4090
4262
  #metadata;
4263
+ #diagnostics;
4091
4264
  constructor(options) {
4092
4265
  this.options = options;
4093
4266
  const { encryption } = this.options;
@@ -4095,10 +4268,21 @@ class LocalFileSourceProvider {
4095
4268
  throw new Error("Missing encryption key");
4096
4269
  }
4097
4270
  }
4271
+ #reportInfo(message) {
4272
+ this.#diagnostics?.report({
4273
+ details: {
4274
+ createdAt: /* @__PURE__ */ new Date(),
4275
+ message,
4276
+ origin: "file-source-provider"
4277
+ },
4278
+ kind: "info"
4279
+ });
4280
+ }
4098
4281
  /**
4099
4282
  * Pre flight checks regarding the provided options, making sure that the file can be opened (decrypted, decompressed), etc.
4100
4283
  */
4101
- async bootstrap() {
4284
+ async bootstrap(diagnostics) {
4285
+ this.#diagnostics = diagnostics;
4102
4286
  const { path: filePath } = this.options.file;
4103
4287
  try {
4104
4288
  await this.#loadMetadata();
@@ -4123,12 +4307,14 @@ class LocalFileSourceProvider {
4123
4307
  return this.#parseJSONFile(backupStream, path2);
4124
4308
  }
4125
4309
  async getMetadata() {
4310
+ this.#reportInfo("getting metadata");
4126
4311
  if (!this.#metadata) {
4127
4312
  await this.#loadMetadata();
4128
4313
  }
4129
4314
  return this.#metadata ?? null;
4130
4315
  }
4131
4316
  async getSchemas() {
4317
+ this.#reportInfo("getting schemas");
4132
4318
  const schemaCollection = await collect(
4133
4319
  this.createSchemasReadStream()
4134
4320
  );
@@ -4139,21 +4325,26 @@ class LocalFileSourceProvider {
4139
4325
  return schemasToValidJSON(schemas);
4140
4326
  }
4141
4327
  createEntitiesReadStream() {
4328
+ this.#reportInfo("creating entities read stream");
4142
4329
  return this.#streamJsonlDirectory("entities");
4143
4330
  }
4144
4331
  createSchemasReadStream() {
4332
+ this.#reportInfo("creating schemas read stream");
4145
4333
  return this.#streamJsonlDirectory("schemas");
4146
4334
  }
4147
4335
  createLinksReadStream() {
4336
+ this.#reportInfo("creating links read stream");
4148
4337
  return this.#streamJsonlDirectory("links");
4149
4338
  }
4150
4339
  createConfigurationReadStream() {
4340
+ this.#reportInfo("creating configuration read stream");
4151
4341
  return this.#streamJsonlDirectory("configuration");
4152
4342
  }
4153
4343
  createAssetsReadStream() {
4154
4344
  const inStream = this.#getBackupStream();
4155
4345
  const outStream = new stream$1.PassThrough({ objectMode: true });
4156
4346
  const loadAssetMetadata = this.#loadAssetMetadata.bind(this);
4347
+ this.#reportInfo("creating assets read stream");
4157
4348
  stream$1.pipeline(
4158
4349
  [
4159
4350
  inStream,
@@ -4173,9 +4364,7 @@ class LocalFileSourceProvider {
4173
4364
  try {
4174
4365
  metadata = await loadAssetMetadata(`assets/metadata/${file}.json`);
4175
4366
  } catch (error) {
4176
- console.warn(
4177
- ` Failed to read metadata for ${file}, Strapi will try to fix this issue automatically`
4178
- );
4367
+ throw new Error(`Failed to read metadata for ${file}`);
4179
4368
  }
4180
4369
  const asset = {
4181
4370
  metadata,
@@ -4354,9 +4543,20 @@ class LocalFileDestinationProvider {
4354
4543
  results = {};
4355
4544
  #providersMetadata = {};
4356
4545
  #archive = {};
4546
+ #diagnostics;
4357
4547
  constructor(options) {
4358
4548
  this.options = options;
4359
4549
  }
4550
+ #reportInfo(message) {
4551
+ this.#diagnostics?.report({
4552
+ details: {
4553
+ createdAt: /* @__PURE__ */ new Date(),
4554
+ message,
4555
+ origin: "file-destination-provider"
4556
+ },
4557
+ kind: "info"
4558
+ });
4559
+ }
4360
4560
  get #archivePath() {
4361
4561
  const { encryption, compression, file } = this.options;
4362
4562
  let filePath = `${file.path}.tar`;
@@ -4373,9 +4573,11 @@ class LocalFileDestinationProvider {
4373
4573
  return this;
4374
4574
  }
4375
4575
  createGzip() {
4576
+ this.#reportInfo("creating gzip");
4376
4577
  return zip__default.default.createGzip();
4377
4578
  }
4378
- bootstrap() {
4579
+ bootstrap(diagnostics) {
4580
+ this.#diagnostics = diagnostics;
4379
4581
  const { compression, encryption } = this.options;
4380
4582
  if (encryption.enabled && !encryption.key) {
4381
4583
  throw new Error("Can't encrypt without a key");
@@ -4414,6 +4616,7 @@ class LocalFileDestinationProvider {
4414
4616
  }
4415
4617
  }
4416
4618
  async rollback() {
4619
+ this.#reportInfo("rolling back");
4417
4620
  await this.close();
4418
4621
  await fse.rm(this.#archivePath, { force: true });
4419
4622
  }
@@ -4421,6 +4624,7 @@ class LocalFileDestinationProvider {
4421
4624
  return null;
4422
4625
  }
4423
4626
  async #writeMetadata() {
4627
+ this.#reportInfo("writing metadata");
4424
4628
  const metadata = this.#providersMetadata.source;
4425
4629
  if (metadata) {
4426
4630
  await new Promise((resolve) => {
@@ -4441,6 +4645,7 @@ class LocalFileDestinationProvider {
4441
4645
  if (!this.#archive.stream) {
4442
4646
  throw new Error("Archive stream is unavailable");
4443
4647
  }
4648
+ this.#reportInfo("creating schemas write stream");
4444
4649
  const filePathFactory = createFilePathFactory("schemas");
4445
4650
  const entryStream = createTarEntryStream(
4446
4651
  this.#archive.stream,
@@ -4453,6 +4658,7 @@ class LocalFileDestinationProvider {
4453
4658
  if (!this.#archive.stream) {
4454
4659
  throw new Error("Archive stream is unavailable");
4455
4660
  }
4661
+ this.#reportInfo("creating entities write stream");
4456
4662
  const filePathFactory = createFilePathFactory("entities");
4457
4663
  const entryStream = createTarEntryStream(
4458
4664
  this.#archive.stream,
@@ -4465,6 +4671,7 @@ class LocalFileDestinationProvider {
4465
4671
  if (!this.#archive.stream) {
4466
4672
  throw new Error("Archive stream is unavailable");
4467
4673
  }
4674
+ this.#reportInfo("creating links write stream");
4468
4675
  const filePathFactory = createFilePathFactory("links");
4469
4676
  const entryStream = createTarEntryStream(
4470
4677
  this.#archive.stream,
@@ -4477,6 +4684,7 @@ class LocalFileDestinationProvider {
4477
4684
  if (!this.#archive.stream) {
4478
4685
  throw new Error("Archive stream is unavailable");
4479
4686
  }
4687
+ this.#reportInfo("creating configuration write stream");
4480
4688
  const filePathFactory = createFilePathFactory("configuration");
4481
4689
  const entryStream = createTarEntryStream(
4482
4690
  this.#archive.stream,
@@ -4490,6 +4698,7 @@ class LocalFileDestinationProvider {
4490
4698
  if (!archiveStream) {
4491
4699
  throw new Error("Archive stream is unavailable");
4492
4700
  }
4701
+ this.#reportInfo("creating assets write stream");
4493
4702
  return new stream$1.Writable({
4494
4703
  objectMode: true,
4495
4704
  write(data, _encoding, callback) {