@localazy/strapi-plugin 1.3.0 → 1.4.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 (45) hide show
  1. package/dist/_chunks/{FetchIdentity-CtBRO-Cn.js → FetchIdentity-BpFOT163.js} +2 -2
  2. package/dist/_chunks/{FetchIdentity-BJGoZPpM.mjs → FetchIdentity-Cor4ooZv.mjs} +2 -2
  3. package/dist/_chunks/{LanguagesSelector-DJxgCwgM.mjs → LanguagesSelector-BbrMKwQY.mjs} +3 -3
  4. package/dist/_chunks/{LanguagesSelector-CNaKrS-m.js → LanguagesSelector-CC1ecWnc.js} +3 -3
  5. package/dist/_chunks/{LocalazyApp-B1qCCEPe.js → LocalazyApp-D0JGRQeF.js} +85 -8
  6. package/dist/_chunks/{LocalazyApp-CJRpsTKr.mjs → LocalazyApp-DpFR8p10.mjs} +86 -9
  7. package/dist/_chunks/{LocalazyContentTransferSetup-CCxiLHdG.js → LocalazyContentTransferSetup-Cr7D-BUi.js} +5 -5
  8. package/dist/_chunks/{LocalazyContentTransferSetup-CLAWn95U.mjs → LocalazyContentTransferSetup-Dael1Q4w.mjs} +5 -5
  9. package/dist/_chunks/{LocalazyGlobalSettings-BJ5qfFfK.js → LocalazyGlobalSettings-D3nG4zl7.js} +6 -6
  10. package/dist/_chunks/{LocalazyGlobalSettings-Z-4Ykc_J.mjs → LocalazyGlobalSettings-DzBI5M-K.mjs} +6 -6
  11. package/dist/_chunks/{LocalazyPanel-DE3TtHOQ.js → LocalazyPanel-KQPLxTDz.js} +3 -3
  12. package/dist/_chunks/{LocalazyPanel-CL_mzzfD.mjs → LocalazyPanel-W6j8N1WC.mjs} +3 -3
  13. package/dist/_chunks/{LocalazyStatusColumn-BMLcklG-.js → LocalazyStatusColumn-B7Q_U2Lu.js} +2 -2
  14. package/dist/_chunks/{LocalazyStatusColumn-R5n6q_cx.mjs → LocalazyStatusColumn-CrhsCjgp.mjs} +2 -2
  15. package/dist/_chunks/{RequireLocalazyAuth-Dzsu6UNS.mjs → RequireLocalazyAuth-BwoGRfJq.mjs} +4 -4
  16. package/dist/_chunks/{RequireLocalazyAuth-WKKFdQlR.js → RequireLocalazyAuth-D3GeZPDh.js} +4 -4
  17. package/dist/_chunks/{entry-exclusion-service-CggmAKxq.js → entry-exclusion-service-Bwruvyce.js} +1 -1
  18. package/dist/_chunks/{entry-exclusion-service-DxqTlZFR.mjs → entry-exclusion-service-D7gaRHPE.mjs} +1 -1
  19. package/dist/_chunks/{has-model-changed-BR53L6pA.js → has-model-changed-5IV9WI_-.js} +2 -2
  20. package/dist/_chunks/{has-model-changed-FMGVcFHU.mjs → has-model-changed-Dp2Vkwjw.mjs} +2 -2
  21. package/dist/_chunks/{i18n-CAsbU4pi.js → i18n-C_5tJvr0.js} +5 -1
  22. package/dist/_chunks/{i18n-QelOdIB2.mjs → i18n-o24yXf-j.mjs} +5 -1
  23. package/dist/_chunks/{index-CHjIMO9T.mjs → index-BjL9XpRh.mjs} +4 -3
  24. package/dist/_chunks/{index-toauQlIK.mjs → index-C9hN3KbD.mjs} +8 -8
  25. package/dist/_chunks/{index-BK7lmZSN.js → index-CTkxoioB.js} +1 -0
  26. package/dist/_chunks/{index-BmxGKspv.js → index-jAg6jjCu.js} +8 -8
  27. package/dist/_chunks/{strapi-api-base-BflplVsH.mjs → strapi-api-base-CAzGcwF6.mjs} +1 -1
  28. package/dist/_chunks/{strapi-api-base-CT9lknwW.js → strapi-api-base-IG3Pe5Sg.js} +1 -1
  29. package/dist/admin/index.js +1 -1
  30. package/dist/admin/index.mjs +1 -1
  31. package/dist/admin/src/modules/activity-logs/locale/en.d.ts +4 -0
  32. package/dist/admin/src/modules/activity-logs/services/debug-bundle-service.d.ts +10 -0
  33. package/dist/server/index.js +510 -4
  34. package/dist/server/index.mjs +510 -5
  35. package/dist/server/src/controllers/debug-bundle-controller.d.ts +7 -0
  36. package/dist/server/src/controllers/index.d.ts +5 -0
  37. package/dist/server/src/core/index.d.ts +2 -0
  38. package/dist/server/src/db/model/activity-logs.d.ts +10 -0
  39. package/dist/server/src/index.d.ts +7 -0
  40. package/dist/server/src/routes/debug-bundle-routes.d.ts +9 -0
  41. package/dist/server/src/services/activity-logs-service.d.ts +3 -1
  42. package/dist/server/src/services/index.d.ts +2 -0
  43. package/dist/server/src/utils/build-debug-bundle.d.ts +68 -0
  44. package/dist/server/src/utils/sanitize-debug-bundle.d.ts +25 -0
  45. package/package.json +2 -1
@@ -10,6 +10,7 @@ const require$$0$2 = require("crypto");
10
10
  const require$$0$3 = require("events");
11
11
  const require$$2 = require("timers");
12
12
  const require$$8 = require("ws");
13
+ const JSZip = require("jszip");
13
14
  const _interopDefault = (e2) => e2 && e2.__esModule ? e2 : { default: e2 };
14
15
  const require$$0__default$2 = /* @__PURE__ */ _interopDefault(require$$0$4);
15
16
  const require$$1__default$4 = /* @__PURE__ */ _interopDefault(require$$1$4);
@@ -22,6 +23,7 @@ const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0$2);
22
23
  const require$$0__default$1 = /* @__PURE__ */ _interopDefault(require$$0$3);
23
24
  const require$$2__default = /* @__PURE__ */ _interopDefault(require$$2);
24
25
  const require$$8__default = /* @__PURE__ */ _interopDefault(require$$8);
26
+ const JSZip__default = /* @__PURE__ */ _interopDefault(JSZip);
25
27
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
26
28
  function getDefaultExportFromCjs(x2) {
27
29
  return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
@@ -20532,7 +20534,7 @@ var distExports = dist$1.exports;
20532
20534
  const io = /* @__PURE__ */ getDefaultExportFromCjs(distExports);
20533
20535
  const { Server: Server2, Namespace, Socket: Socket3 } = io;
20534
20536
  const name = "@localazy/strapi-plugin";
20535
- const version = "1.3.0";
20537
+ const version = "1.4.0";
20536
20538
  const description = "The official Strapi Plugin by Localazy.";
20537
20539
  const keywords = [
20538
20540
  "strapi",
@@ -20602,6 +20604,7 @@ const scripts = {
20602
20604
  prepare: "husky || true"
20603
20605
  };
20604
20606
  const dependencies = {
20607
+ jszip: "^3.10.1",
20605
20608
  shelljs: "^0.8.5",
20606
20609
  ws: "^8.18.0"
20607
20610
  };
@@ -24501,6 +24504,413 @@ const ActivityLogsController = ({ strapi: _strapi }) => ({
24501
24504
  ctx.body = { success: true };
24502
24505
  }
24503
24506
  });
24507
+ const URL_SECRET_KEYS = ["token", "secret", "signature", "key", "apikey", "api_key"];
24508
+ const DEBUG_BUNDLE_ENV_ALLOWLIST = [
24509
+ "STRAPI_ADMIN_LOCALAZY_ENV",
24510
+ "STRAPI_ADMIN_BACKEND_URL",
24511
+ "NODE_ENV",
24512
+ "STRAPI_TELEMETRY_DISABLED"
24513
+ ];
24514
+ const redactIdentity = (identity2) => {
24515
+ if (!identity2) return null;
24516
+ return {
24517
+ userId: identity2.user?.id ?? "",
24518
+ name: identity2.user?.name ?? "",
24519
+ scope: identity2.scope ?? "",
24520
+ hasAccessToken: Boolean(identity2.accessToken)
24521
+ };
24522
+ };
24523
+ const scrubUrlSecrets = (input) => {
24524
+ if (typeof input !== "string") return input;
24525
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input) && !input.includes("?")) return input;
24526
+ try {
24527
+ const url2 = new URL(input);
24528
+ let mutated = false;
24529
+ for (const k2 of [...url2.searchParams.keys()]) {
24530
+ if (URL_SECRET_KEYS.includes(k2.toLowerCase())) {
24531
+ url2.searchParams.delete(k2);
24532
+ mutated = true;
24533
+ }
24534
+ }
24535
+ return mutated ? url2.toString() : input;
24536
+ } catch {
24537
+ return input;
24538
+ }
24539
+ };
24540
+ const pickEnvAllowlist = (env) => {
24541
+ const out = {};
24542
+ for (const key of DEBUG_BUNDLE_ENV_ALLOWLIST) {
24543
+ const raw = env[key];
24544
+ const present = raw !== void 0;
24545
+ out[key] = {
24546
+ present,
24547
+ value: present ? raw : null
24548
+ };
24549
+ }
24550
+ return out;
24551
+ };
24552
+ const serializeContentTypeSchema = (contentType) => {
24553
+ if (!contentType || typeof contentType !== "object") return null;
24554
+ const ct = contentType;
24555
+ return {
24556
+ kind: ct.kind,
24557
+ collectionName: ct.collectionName,
24558
+ info: ct.info,
24559
+ options: ct.options,
24560
+ pluginOptions: ct.pluginOptions,
24561
+ attributes: ct.attributes
24562
+ };
24563
+ };
24564
+ const DOCUMENT_INTERNAL_FIELDS = ["createdBy", "updatedBy"];
24565
+ const stripDocumentInternals = (doc) => {
24566
+ if (!doc || typeof doc !== "object") return doc;
24567
+ const clone = { ...doc };
24568
+ for (const field of DOCUMENT_INTERNAL_FIELDS) {
24569
+ delete clone[field];
24570
+ }
24571
+ return clone;
24572
+ };
24573
+ class SessionNotFoundError extends Error {
24574
+ constructor(sessionId) {
24575
+ super(`Session ${sessionId} not found`);
24576
+ this.name = "SessionNotFoundError";
24577
+ }
24578
+ }
24579
+ const DEBUG_BUNDLE_SCHEMA_VERSION = 1;
24580
+ const CAPS = {
24581
+ perEntryBytes: 1 * 1024 * 1024,
24582
+ // 1 MB
24583
+ totalEntries: 100,
24584
+ errorLogBytes: 2 * 1024 * 1024,
24585
+ // 2 MB
24586
+ consoleErrorsLines: 200,
24587
+ browserPayloadBytes: 64 * 1024
24588
+ // 64 KB
24589
+ };
24590
+ const selectAttemptedEntries = (attempted) => {
24591
+ const failedByKey = /* @__PURE__ */ new Map();
24592
+ const successByKey = /* @__PURE__ */ new Map();
24593
+ for (const record of attempted) {
24594
+ const key = `${record.uid}|${record.documentId}|${record.locale}`;
24595
+ const selected = {
24596
+ uid: record.uid,
24597
+ documentId: record.documentId,
24598
+ locale: record.locale,
24599
+ status: record.status,
24600
+ attemptedAt: record.attemptedAt
24601
+ };
24602
+ if (record.status === "failed") {
24603
+ successByKey.delete(key);
24604
+ if (!failedByKey.has(key)) failedByKey.set(key, selected);
24605
+ } else if (!failedByKey.has(key) && !successByKey.has(key)) {
24606
+ successByKey.set(key, selected);
24607
+ }
24608
+ }
24609
+ return [...failedByKey.values(), ...successByKey.values()];
24610
+ };
24611
+ const formatTimestampForFilename = (ms2) => new Date(ms2).toISOString().replace(/[:.]/g, "-");
24612
+ const safeUidForPath = (uid) => uid.replace(/::/g, "--").replace(/\./g, "-");
24613
+ const decodeBrowserPayload = (encoded) => {
24614
+ const markers = [];
24615
+ if (!encoded) return { payload: null, truncationMarkers: markers };
24616
+ try {
24617
+ const raw = Buffer.from(encoded, "base64").toString("utf8");
24618
+ const rawBytes = Buffer.byteLength(raw, "utf8");
24619
+ if (rawBytes > CAPS.browserPayloadBytes) {
24620
+ markers.push({ kind: "browser-payload-truncated", originalSizeBytes: rawBytes });
24621
+ return { payload: null, truncationMarkers: markers };
24622
+ }
24623
+ const parsed = JSON.parse(raw);
24624
+ if (parsed && Array.isArray(parsed.consoleErrors) && parsed.consoleErrors.length > CAPS.consoleErrorsLines) {
24625
+ markers.push({ kind: "console-errors-truncated", originalCount: parsed.consoleErrors.length });
24626
+ parsed.consoleErrors = parsed.consoleErrors.slice(0, CAPS.consoleErrorsLines);
24627
+ }
24628
+ return { payload: parsed, truncationMarkers: markers };
24629
+ } catch {
24630
+ return { payload: null, truncationMarkers: markers };
24631
+ }
24632
+ };
24633
+ const deepScrubUrls = (value) => {
24634
+ if (value == null) return value;
24635
+ if (typeof value === "string") return scrubUrlSecrets(value);
24636
+ if (Array.isArray(value)) return value.map(deepScrubUrls);
24637
+ if (typeof value === "object") {
24638
+ const out = {};
24639
+ for (const [k2, v2] of Object.entries(value)) {
24640
+ out[k2] = deepScrubUrls(v2);
24641
+ }
24642
+ return out;
24643
+ }
24644
+ return value;
24645
+ };
24646
+ const sanitizePluginSettings = (settings) => {
24647
+ const cloned = JSON.parse(JSON.stringify(settings ?? {}));
24648
+ if (cloned.webhookConfig?.url) {
24649
+ cloned.webhookConfig.url = scrubUrlSecrets(cloned.webhookConfig.url);
24650
+ }
24651
+ return cloned;
24652
+ };
24653
+ const sanitizeSession = (session) => {
24654
+ const cloned = JSON.parse(JSON.stringify(session));
24655
+ for (const entry of cloned.entries ?? []) {
24656
+ if (entry.data !== void 0) {
24657
+ entry.data = deepScrubUrls(entry.data);
24658
+ }
24659
+ }
24660
+ return cloned;
24661
+ };
24662
+ const buildErrorLog = (session) => {
24663
+ const lines = [];
24664
+ for (const entry of session.entries ?? []) {
24665
+ const ts = Number.isFinite(entry.timestamp) ? new Date(entry.timestamp).toISOString() : "unknown";
24666
+ lines.push(`${ts} ${entry.message}`);
24667
+ }
24668
+ if (session.status === "failed" && session.summary) {
24669
+ lines.push(`ERROR: ${session.summary}`);
24670
+ }
24671
+ const full = lines.join("\n");
24672
+ const fullBytes = Buffer.byteLength(full, "utf8");
24673
+ if (fullBytes <= CAPS.errorLogBytes) {
24674
+ return { text: full, markers: [] };
24675
+ }
24676
+ const truncated = Buffer.from(full, "utf8").subarray(fullBytes - CAPS.errorLogBytes).toString("utf8");
24677
+ return {
24678
+ text: `...[truncated, kept last ${CAPS.errorLogBytes} bytes]
24679
+ ${truncated}`,
24680
+ markers: [{ kind: "error-log-truncated", originalSizeBytes: fullBytes }]
24681
+ };
24682
+ };
24683
+ const readHostPackages = async (strapi2) => {
24684
+ const appRoot = strapi2?.dirs?.app?.root ?? process.cwd();
24685
+ if (!appRoot) return { data: null, reason: "host-app-root-unknown" };
24686
+ try {
24687
+ const raw = await require$$1$4.promises.readFile(require$$1__default.default.join(appRoot, "package.json"), "utf8");
24688
+ const parsed = JSON.parse(raw);
24689
+ return {
24690
+ data: {
24691
+ name: parsed.name,
24692
+ version: parsed.version,
24693
+ dependencies: parsed.dependencies ?? {},
24694
+ devDependencies: parsed.devDependencies ?? {}
24695
+ }
24696
+ };
24697
+ } catch (err) {
24698
+ return { data: null, reason: err instanceof Error ? err.message : String(err) };
24699
+ }
24700
+ };
24701
+ const buildReadme = ({
24702
+ sessionShortId,
24703
+ sessionId,
24704
+ schemaVersion,
24705
+ hostPackagesSkipped
24706
+ }) => {
24707
+ const lines = [];
24708
+ lines.push(`# Localazy Strapi Plugin — Troubleshooting Bundle`);
24709
+ lines.push("");
24710
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
24711
+ lines.push(`Session id: ${sessionId}`);
24712
+ lines.push(`Schema version: ${schemaVersion}`);
24713
+ lines.push("");
24714
+ lines.push("## Contents");
24715
+ lines.push("");
24716
+ lines.push("- `meta.json` — schema version, session metadata.");
24717
+ lines.push("- `versions.json` — plugin version, Strapi version, Node version, platform/arch.");
24718
+ lines.push("- `env.json` — allow-listed environment variables only.");
24719
+ lines.push("- `browser.json` — user agent, language, viewport, console errors (from the admin UI).");
24720
+ lines.push("- `session.json` — full activity-log session record (URLs scrubbed of secret query keys).");
24721
+ lines.push("- `error.log` — chronological text log of session entries.");
24722
+ lines.push("- `plugin-settings.json` — full plugin settings (webhook URL secrets scrubbed).");
24723
+ lines.push("- `content-transfer-setup.json` — content-transfer setup record.");
24724
+ if (hostPackagesSkipped) {
24725
+ lines.push("- `host-packages.json` — NOT INCLUDED (could not be read; see `bundle-truncated.json`).");
24726
+ } else {
24727
+ lines.push("- `host-packages.json` — host project's package.json (deps + devDeps only).");
24728
+ }
24729
+ lines.push("- `content-types/` — schema.json per touched content-type UID.");
24730
+ lines.push("- `entries/<uid>/<documentId>--<locale>.json` — per-entry payload from `strapi.documents().findOne()`.");
24731
+ lines.push("- `bundle-truncated.json` — present only if any per-section cap was exceeded.");
24732
+ lines.push("");
24733
+ lines.push("## Reproducing locally");
24734
+ lines.push("");
24735
+ lines.push("1. `unzip` the bundle.");
24736
+ lines.push("2. Recreate the content types from `content-types/*.schema.json` in your Strapi project.");
24737
+ lines.push("3. Import the entries via the Document Service using `entries/<uid>/*.json`.");
24738
+ lines.push("4. Apply `plugin-settings.json` and `content-transfer-setup.json` via the plugin admin UI.");
24739
+ lines.push("5. Trigger the same upload/download/webhook with the same locale set.");
24740
+ lines.push("6. Compare the failure against `error.log` and `session.json`.");
24741
+ lines.push("");
24742
+ lines.push(`Bundle short id: ${sessionShortId}`);
24743
+ return lines.join("\n");
24744
+ };
24745
+ const buildDebugBundle = async ({
24746
+ strapi: strapi2,
24747
+ sessionId,
24748
+ browserPayloadEncoded
24749
+ }) => {
24750
+ const zip = new JSZip__default.default();
24751
+ const truncationMarkers = [];
24752
+ const pluginName = "localazy";
24753
+ const activityLogsService = strapi2.plugin(pluginName).service("ActivityLogsService");
24754
+ const pluginSettingsService = strapi2.plugin(pluginName).service("PluginSettingsService");
24755
+ const strapiService = strapi2.plugin(pluginName).service("StrapiService");
24756
+ const localazyUserService = strapi2.plugin(pluginName).service("LocalazyUserService");
24757
+ const session = await activityLogsService.getSession(sessionId);
24758
+ if (!session) {
24759
+ throw new SessionNotFoundError(sessionId);
24760
+ }
24761
+ const sessionShortId = session.id.slice(0, 8);
24762
+ const pluginSettingsRaw = await pluginSettingsService.getPluginSettings();
24763
+ const contentTransferSetup = await pluginSettingsService.getContentTransferSetup();
24764
+ const identity2 = await localazyUserService.getUser();
24765
+ const redactedIdentity = redactIdentity(identity2);
24766
+ const pluginVersion = await strapiService.getPluginVersion();
24767
+ const strapiVersion = strapiService.getStrapiVersion();
24768
+ zip.file(
24769
+ "meta.json",
24770
+ JSON.stringify(
24771
+ {
24772
+ schemaVersion: DEBUG_BUNDLE_SCHEMA_VERSION,
24773
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24774
+ sessionId: session.id,
24775
+ sessionShortId,
24776
+ eventType: session.eventType,
24777
+ status: session.status,
24778
+ startedAt: session.startedAt,
24779
+ finishedAt: session.finishedAt ?? null,
24780
+ initiatedBy: session.initiatedBy,
24781
+ identity: redactedIdentity
24782
+ },
24783
+ null,
24784
+ 2
24785
+ )
24786
+ );
24787
+ zip.file(
24788
+ "versions.json",
24789
+ JSON.stringify(
24790
+ {
24791
+ plugin: { name: "@localazy/strapi-plugin", version: pluginVersion },
24792
+ strapi: { version: strapiVersion },
24793
+ node: process.version,
24794
+ platform: process.platform,
24795
+ arch: process.arch
24796
+ },
24797
+ null,
24798
+ 2
24799
+ )
24800
+ );
24801
+ const envSnapshot = pickEnvAllowlist(process.env);
24802
+ zip.file(
24803
+ "env.json",
24804
+ JSON.stringify(
24805
+ {
24806
+ allowlist: [...DEBUG_BUNDLE_ENV_ALLOWLIST],
24807
+ values: envSnapshot
24808
+ },
24809
+ null,
24810
+ 2
24811
+ )
24812
+ );
24813
+ const { payload: browserPayload, truncationMarkers: browserMarkers } = decodeBrowserPayload(browserPayloadEncoded);
24814
+ truncationMarkers.push(...browserMarkers);
24815
+ zip.file("browser.json", JSON.stringify(browserPayload ? browserPayload : { captured: false }, null, 2));
24816
+ zip.file("session.json", JSON.stringify(sanitizeSession(session), null, 2));
24817
+ const { text: errorLogText, markers: errorLogMarkers } = buildErrorLog(session);
24818
+ truncationMarkers.push(...errorLogMarkers);
24819
+ zip.file("error.log", errorLogText);
24820
+ zip.file("plugin-settings.json", JSON.stringify(sanitizePluginSettings(pluginSettingsRaw), null, 2));
24821
+ zip.file("content-transfer-setup.json", JSON.stringify(contentTransferSetup, null, 2));
24822
+ const hostPackages = await readHostPackages(strapi2);
24823
+ if (hostPackages.data) {
24824
+ zip.file("host-packages.json", JSON.stringify(hostPackages.data, null, 2));
24825
+ }
24826
+ if (session.attemptedEntries === void 0) {
24827
+ truncationMarkers.push({ kind: "attempted-entries-missing-legacy-session" });
24828
+ }
24829
+ const selected = selectAttemptedEntries(session.attemptedEntries ?? []);
24830
+ const kept = selected.slice(0, CAPS.totalEntries);
24831
+ const skipped = selected.slice(CAPS.totalEntries);
24832
+ if (skipped.length > 0) {
24833
+ truncationMarkers.push({
24834
+ kind: "entries-skipped",
24835
+ tuples: skipped.map(({ uid, documentId, locale }) => ({ uid, documentId, locale }))
24836
+ });
24837
+ }
24838
+ const uniqueUids = [...new Set(kept.map((t3) => t3.uid))];
24839
+ for (const uid of uniqueUids) {
24840
+ const schema = serializeContentTypeSchema(strapi2.contentTypes?.[uid]);
24841
+ if (schema) {
24842
+ zip.file(`content-types/${safeUidForPath(uid)}.schema.json`, JSON.stringify(schema, null, 2));
24843
+ }
24844
+ }
24845
+ for (const tuple of kept) {
24846
+ let documentJson = null;
24847
+ try {
24848
+ const doc = await strapi2.documents(tuple.uid).findOne({ documentId: tuple.documentId, locale: tuple.locale, populate: "*" });
24849
+ documentJson = doc ? stripDocumentInternals(doc) : { __not_found: true, attemptedAt: tuple.attemptedAt };
24850
+ } catch (err) {
24851
+ documentJson = { __error: err instanceof Error ? err.message : String(err) };
24852
+ }
24853
+ const serialised = JSON.stringify(documentJson, null, 2);
24854
+ const sizeBytes = Buffer.byteLength(serialised, "utf8");
24855
+ let body;
24856
+ if (sizeBytes > CAPS.perEntryBytes) {
24857
+ truncationMarkers.push({
24858
+ kind: "entry-too-large",
24859
+ uid: tuple.uid,
24860
+ documentId: tuple.documentId,
24861
+ locale: tuple.locale,
24862
+ originalSizeBytes: sizeBytes
24863
+ });
24864
+ body = JSON.stringify({ __truncated: true, originalSizeBytes: sizeBytes }, null, 2);
24865
+ } else {
24866
+ body = serialised;
24867
+ }
24868
+ const safeDocId = tuple.documentId.replace(/[^a-zA-Z0-9_-]/g, "_");
24869
+ zip.file(`entries/${safeUidForPath(tuple.uid)}/${safeDocId}--${tuple.locale}.json`, body);
24870
+ }
24871
+ zip.file(
24872
+ "README.md",
24873
+ buildReadme({
24874
+ sessionShortId,
24875
+ sessionId: session.id,
24876
+ schemaVersion: DEBUG_BUNDLE_SCHEMA_VERSION,
24877
+ hostPackagesSkipped: !hostPackages.data
24878
+ })
24879
+ );
24880
+ if (truncationMarkers.length > 0 || !hostPackages.data) {
24881
+ const marker = {
24882
+ schemaVersion: DEBUG_BUNDLE_SCHEMA_VERSION,
24883
+ markers: truncationMarkers,
24884
+ hostPackagesSkipped: hostPackages.data ? null : hostPackages.reason ?? "unknown"
24885
+ };
24886
+ zip.file("bundle-truncated.json", JSON.stringify(marker, null, 2));
24887
+ }
24888
+ const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
24889
+ const filename = `localazy-debug-bundle-${sessionShortId}-${formatTimestampForFilename(Date.now())}.zip`;
24890
+ return { zipBuffer, filename };
24891
+ };
24892
+ const DebugBundleController = ({ strapi: strapi2 }) => ({
24893
+ async getBundle(ctx) {
24894
+ const { sessionId } = ctx.params;
24895
+ const browserPayloadEncoded = typeof ctx.query.browser === "string" ? ctx.query.browser : null;
24896
+ try {
24897
+ const { zipBuffer, filename } = await buildDebugBundle({
24898
+ strapi: strapi2,
24899
+ sessionId,
24900
+ browserPayloadEncoded
24901
+ });
24902
+ ctx.type = "application/zip";
24903
+ ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
24904
+ ctx.body = zipBuffer;
24905
+ } catch (err) {
24906
+ if (err instanceof SessionNotFoundError) {
24907
+ return ctx.notFound("Session not found");
24908
+ }
24909
+ strapi2.log.error(`[localazy] failed to build debug bundle: ${err instanceof Error ? err.message : err}`);
24910
+ throw err;
24911
+ }
24912
+ }
24913
+ });
24504
24914
  const controllers = {
24505
24915
  LocalazyUserController,
24506
24916
  LocalazyAuthController,
@@ -24509,7 +24919,8 @@ const controllers = {
24509
24919
  PluginSettingsController,
24510
24920
  LocalazyTransferController,
24511
24921
  EntryExclusionController,
24512
- ActivityLogsController
24922
+ ActivityLogsController,
24923
+ DebugBundleController
24513
24924
  };
24514
24925
  const verifyWebhook = (config2, { strapi: strapi2 }) => {
24515
24926
  return async (ctx, next) => {
@@ -24820,6 +25231,16 @@ const ActivityLogsRoutes = [
24820
25231
  }
24821
25232
  }
24822
25233
  ];
25234
+ const DebugBundleRoutes = [
25235
+ {
25236
+ method: "GET",
25237
+ path: "/activity-logs/:sessionId/bundle",
25238
+ handler: "DebugBundleController.getBundle",
25239
+ config: {
25240
+ policies: []
25241
+ }
25242
+ }
25243
+ ];
24823
25244
  const routes = {
24824
25245
  "content-api": {
24825
25246
  routes: [...StrapiRoutes]
@@ -24834,7 +25255,8 @@ const routes = {
24834
25255
  ...LocalazyTransferRoutes,
24835
25256
  ...LocalazyProjectRoutes,
24836
25257
  ...EntryExclusionRoutes,
24837
- ...ActivityLogsRoutes
25258
+ ...ActivityLogsRoutes,
25259
+ ...DebugBundleRoutes
24838
25260
  ]
24839
25261
  },
24840
25262
  LocalazyPublicTransferRoutes: {
@@ -25113,6 +25535,8 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25113
25535
  const { setup: setup2 } = contentTransferSetup;
25114
25536
  const collectionsNames = getCollectionsNames(setup2);
25115
25537
  const models = await StrapiService2.getModels();
25538
+ const strapiDefaultLocaleCode = await StrapiI18nService2.getDefaultLocaleCode();
25539
+ const attemptedTuples = [];
25116
25540
  let pickedFlattenStrapiContent = {};
25117
25541
  for (const collectionName of collectionsNames) {
25118
25542
  const currentModel = models.find((model) => model.collectionName === collectionName);
@@ -25175,6 +25599,15 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25175
25599
  });
25176
25600
  await activityLogsService.addEntry(sessionId, message);
25177
25601
  strapi2.log.info(message);
25602
+ if (strapiDefaultLocaleCode) {
25603
+ for (const entry of entries) {
25604
+ attemptedTuples.push({
25605
+ uid: modelUid,
25606
+ documentId: entry.documentId,
25607
+ locale: strapiDefaultLocaleCode
25608
+ });
25609
+ }
25610
+ }
25178
25611
  entries = omitDeep(entries, ["locale", "createdAt", "updatedAt", "publishedAt"]);
25179
25612
  entries.forEach((entry) => {
25180
25613
  const flatten = flattenObject({
@@ -25192,7 +25625,6 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25192
25625
  ...characterLimitsMetadata
25193
25626
  };
25194
25627
  }
25195
- const strapiDefaultLocaleCode = await StrapiI18nService2.getDefaultLocaleCode();
25196
25628
  const localazyLocaleCode = strapiDefaultLocaleCode ? isoStrapiToLocalazy(strapiDefaultLocaleCode) : config.default.LOCALAZY_DEFAULT_LOCALE;
25197
25629
  const importFile = LocalazyUploadService2.createImportFileRepresentation(
25198
25630
  localazyLocaleCode,
@@ -25214,6 +25646,10 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25214
25646
  await activityLogsService.addEntry(sessionId, "Uploading collections to Localazy...");
25215
25647
  try {
25216
25648
  await LocalazyUploadService2.upload(importFile, uploadConfig);
25649
+ await activityLogsService.recordAttemptedEntries(
25650
+ sessionId,
25651
+ attemptedTuples.map((t3) => ({ ...t3, status: "success" }))
25652
+ );
25217
25653
  } catch (error) {
25218
25654
  success = false;
25219
25655
  const errorMessage = `Upload to Localazy failed: ${error instanceof Error ? error.message : String(error)}`;
@@ -25223,6 +25659,10 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25223
25659
  message: errorMessage
25224
25660
  });
25225
25661
  await activityLogsService.addEntry(sessionId, errorMessage);
25662
+ await activityLogsService.recordAttemptedEntries(
25663
+ sessionId,
25664
+ attemptedTuples.map((t3) => ({ ...t3, status: "failed", errorMessage }))
25665
+ );
25226
25666
  await activityLogsService.finishSession(sessionId, "failed", errorMessage);
25227
25667
  console.timeEnd("upload");
25228
25668
  return;
@@ -25947,6 +26387,12 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25947
26387
  message
25948
26388
  });
25949
26389
  await activityLogsService.addEntry(sessionId, message);
26390
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26391
+ uid,
26392
+ documentId,
26393
+ locale: isoStrapi,
26394
+ status: "success"
26395
+ });
25950
26396
  await markKeysProcessed(sourceKeys);
25951
26397
  } catch (e2) {
25952
26398
  success = false;
@@ -25963,6 +26409,13 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25963
26409
  sessionId,
25964
26410
  `Failed to create ${uid} [${documentId}] in ${isoStrapi}: ${e2.message}`
25965
26411
  );
26412
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26413
+ uid,
26414
+ documentId,
26415
+ locale: isoStrapi,
26416
+ status: "failed",
26417
+ errorMessage: e2.message
26418
+ });
25966
26419
  }
25967
26420
  } else {
25968
26421
  try {
@@ -25981,6 +26434,12 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25981
26434
  message
25982
26435
  });
25983
26436
  await activityLogsService.addEntry(sessionId, message);
26437
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26438
+ uid,
26439
+ documentId,
26440
+ locale: isoStrapi,
26441
+ status: "success"
26442
+ });
25984
26443
  await markKeysProcessed(sourceKeys);
25985
26444
  } catch (e2) {
25986
26445
  success = false;
@@ -25997,6 +26456,13 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25997
26456
  sessionId,
25998
26457
  `Failed to update ${uid} [${documentId}] in ${isoStrapi}: ${e2.message}`
25999
26458
  );
26459
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26460
+ uid,
26461
+ documentId,
26462
+ locale: isoStrapi,
26463
+ status: "failed",
26464
+ errorMessage: e2.message
26465
+ });
26000
26466
  }
26001
26467
  }
26002
26468
  } catch (e2) {
@@ -26009,6 +26475,13 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
26009
26475
  sessionId,
26010
26476
  `Error processing ${uid} [${documentId}] in ${isoStrapi}: ${e2.message}`
26011
26477
  );
26478
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26479
+ uid,
26480
+ documentId,
26481
+ locale: isoStrapi,
26482
+ status: "failed",
26483
+ errorMessage: e2.message
26484
+ });
26012
26485
  }
26013
26486
  }
26014
26487
  }
@@ -26759,6 +27232,7 @@ const emptyActivityLogs = {
26759
27232
  sessions: []
26760
27233
  };
26761
27234
  const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
27235
+ const ATTEMPTED_ENTRIES_CAP = 5e3;
26762
27236
  let writeLock = Promise.resolve();
26763
27237
  const withLock = async (fn) => {
26764
27238
  let release;
@@ -26823,6 +27297,38 @@ const ActivityLogsService = ({ strapi: strapi2 }) => ({
26823
27297
  await this.saveActivityLogs(logs);
26824
27298
  });
26825
27299
  },
27300
+ async recordAttemptedEntry(sessionId, entry) {
27301
+ return this.recordAttemptedEntries(sessionId, [entry]);
27302
+ },
27303
+ async recordAttemptedEntries(sessionId, entries) {
27304
+ if (!entries.length) {
27305
+ return;
27306
+ }
27307
+ return withLock(async () => {
27308
+ const logs = await this.getActivityLogs();
27309
+ const session = logs.sessions.find((s2) => s2.id === sessionId);
27310
+ if (!session) {
27311
+ return;
27312
+ }
27313
+ if (!session.attemptedEntries) {
27314
+ session.attemptedEntries = [];
27315
+ }
27316
+ const now = Date.now();
27317
+ const wasAtCap = session.attemptedEntries.length >= ATTEMPTED_ENTRIES_CAP;
27318
+ const remaining = Math.max(0, ATTEMPTED_ENTRIES_CAP - session.attemptedEntries.length);
27319
+ const accepted = entries.slice(0, remaining);
27320
+ const dropped = entries.length - accepted.length;
27321
+ for (const entry of accepted) {
27322
+ session.attemptedEntries.push({ ...entry, attemptedAt: now });
27323
+ }
27324
+ if (dropped > 0 && !wasAtCap) {
27325
+ strapi2.log.warn(
27326
+ `[localazy] attemptedEntries cap (${ATTEMPTED_ENTRIES_CAP}) reached for session ${sessionId}; ${dropped} record(s) dropped.`
27327
+ );
27328
+ }
27329
+ await this.saveActivityLogs(logs);
27330
+ });
27331
+ },
26826
27332
  async finishSession(sessionId, status, summary) {
26827
27333
  return withLock(async () => {
26828
27334
  const logs = await this.getActivityLogs();