@localazy/strapi-plugin 1.1.3 → 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 (49) hide show
  1. package/dist/_chunks/{FetchIdentity-Dl2vx6zB.js → FetchIdentity-BpFOT163.js} +2 -2
  2. package/dist/_chunks/{FetchIdentity-C8-hFqEW.mjs → FetchIdentity-Cor4ooZv.mjs} +2 -2
  3. package/dist/_chunks/{LanguagesSelector-vu-Lj7L6.mjs → LanguagesSelector-BbrMKwQY.mjs} +3 -3
  4. package/dist/_chunks/{LanguagesSelector-DsW3aJ7p.js → LanguagesSelector-CC1ecWnc.js} +3 -3
  5. package/dist/_chunks/{LocalazyApp-BIll8u_G.js → LocalazyApp-D0JGRQeF.js} +87 -10
  6. package/dist/_chunks/{LocalazyApp-Dxygk1mM.mjs → LocalazyApp-DpFR8p10.mjs} +88 -11
  7. package/dist/_chunks/{LocalazyContentTransferSetup-BhEjlIgE.js → LocalazyContentTransferSetup-Cr7D-BUi.js} +5 -5
  8. package/dist/_chunks/{LocalazyContentTransferSetup-Cz88htly.mjs → LocalazyContentTransferSetup-Dael1Q4w.mjs} +5 -5
  9. package/dist/_chunks/{LocalazyGlobalSettings-B3Z8f4G8.js → LocalazyGlobalSettings-D3nG4zl7.js} +6 -6
  10. package/dist/_chunks/{LocalazyGlobalSettings-CgOADY93.mjs → LocalazyGlobalSettings-DzBI5M-K.mjs} +6 -6
  11. package/dist/_chunks/{LocalazyPanel-CQ8-K88T.js → LocalazyPanel-KQPLxTDz.js} +3 -3
  12. package/dist/_chunks/{LocalazyPanel-BjrSVZpI.mjs → LocalazyPanel-W6j8N1WC.mjs} +3 -3
  13. package/dist/_chunks/{LocalazyStatusColumn-CA34yhSE.js → LocalazyStatusColumn-B7Q_U2Lu.js} +2 -2
  14. package/dist/_chunks/{LocalazyStatusColumn-D0b2LCpg.mjs → LocalazyStatusColumn-CrhsCjgp.mjs} +2 -2
  15. package/dist/_chunks/{RequireLocalazyAuth-DpimzlRh.mjs → RequireLocalazyAuth-BwoGRfJq.mjs} +4 -4
  16. package/dist/_chunks/{RequireLocalazyAuth-BPsrUlFm.js → RequireLocalazyAuth-D3GeZPDh.js} +4 -4
  17. package/dist/_chunks/{entry-exclusion-service-nJswJPwF.js → entry-exclusion-service-Bwruvyce.js} +1 -1
  18. package/dist/_chunks/{entry-exclusion-service-DNJZg3U2.mjs → entry-exclusion-service-D7gaRHPE.mjs} +1 -1
  19. package/dist/_chunks/{has-model-changed-BnL6D-eR.js → has-model-changed-5IV9WI_-.js} +2 -2
  20. package/dist/_chunks/{has-model-changed-D6fjlIdc.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-CPBTdynk.mjs → index-C9hN3KbD.mjs} +8 -8
  25. package/dist/_chunks/{index-BK7lmZSN.js → index-CTkxoioB.js} +1 -0
  26. package/dist/_chunks/{index-BXPaiO0d.js → index-jAg6jjCu.js} +8 -8
  27. package/dist/_chunks/strapi-api-base-CAzGcwF6.mjs +23 -0
  28. package/dist/_chunks/strapi-api-base-IG3Pe5Sg.js +22 -0
  29. package/dist/admin/index.js +1 -1
  30. package/dist/admin/index.mjs +1 -1
  31. package/dist/admin/src/modules/@common/api/strapi-api-base.d.ts +19 -4
  32. package/dist/admin/src/modules/@common/utils/sort-by-model-name.d.ts +1 -1
  33. package/dist/admin/src/modules/activity-logs/locale/en.d.ts +4 -0
  34. package/dist/admin/src/modules/activity-logs/services/debug-bundle-service.d.ts +10 -0
  35. package/dist/server/index.js +510 -5
  36. package/dist/server/index.mjs +510 -6
  37. package/dist/server/src/controllers/debug-bundle-controller.d.ts +7 -0
  38. package/dist/server/src/controllers/index.d.ts +5 -0
  39. package/dist/server/src/core/index.d.ts +2 -0
  40. package/dist/server/src/db/model/activity-logs.d.ts +10 -0
  41. package/dist/server/src/index.d.ts +7 -0
  42. package/dist/server/src/routes/debug-bundle-routes.d.ts +9 -0
  43. package/dist/server/src/services/activity-logs-service.d.ts +3 -1
  44. package/dist/server/src/services/index.d.ts +2 -0
  45. package/dist/server/src/utils/build-debug-bundle.d.ts +68 -0
  46. package/dist/server/src/utils/sanitize-debug-bundle.d.ts +25 -0
  47. package/package.json +2 -2
  48. package/dist/_chunks/strapi-api-base-DkjMgMHl.js +0 -2425
  49. package/dist/_chunks/strapi-api-base-GgrMbYak.mjs +0 -2426
@@ -1,5 +1,5 @@
1
1
  import require$$0$4 from "http";
2
- import require$$1$4 from "fs";
2
+ import require$$1$4, { promises } from "fs";
3
3
  import require$$1$1 from "zlib";
4
4
  import require$$1 from "path";
5
5
  import require$$4 from "stream";
@@ -9,6 +9,7 @@ import require$$0$2 from "crypto";
9
9
  import require$$0$3 from "events";
10
10
  import require$$2 from "timers";
11
11
  import require$$8, { WebSocketServer } from "ws";
12
+ import JSZip from "jszip";
12
13
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
13
14
  function getDefaultExportFromCjs(x2) {
14
15
  return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
@@ -20519,7 +20520,7 @@ var distExports = dist$1.exports;
20519
20520
  const io = /* @__PURE__ */ getDefaultExportFromCjs(distExports);
20520
20521
  const { Server: Server2, Namespace, Socket: Socket3 } = io;
20521
20522
  const name = "@localazy/strapi-plugin";
20522
- const version = "1.1.3";
20523
+ const version = "1.4.0";
20523
20524
  const description = "The official Strapi Plugin by Localazy.";
20524
20525
  const keywords = [
20525
20526
  "strapi",
@@ -20589,6 +20590,7 @@ const scripts = {
20589
20590
  prepare: "husky || true"
20590
20591
  };
20591
20592
  const dependencies = {
20593
+ jszip: "^3.10.1",
20592
20594
  shelljs: "^0.8.5",
20593
20595
  ws: "^8.18.0"
20594
20596
  };
@@ -20607,7 +20609,6 @@ const devDependencies = {
20607
20609
  "@types/react-dom": "^19.0.2",
20608
20610
  "@typescript-eslint/eslint-plugin": "^8.21.0",
20609
20611
  "@typescript-eslint/parser": "^8.21.0",
20610
- axios: "^1.7.9",
20611
20612
  chalk: "^5.4.1",
20612
20613
  "conventional-changelog": "^5.1.0",
20613
20614
  "conventional-changelog-cli": "^4.1.0",
@@ -24489,6 +24490,413 @@ const ActivityLogsController = ({ strapi: _strapi }) => ({
24489
24490
  ctx.body = { success: true };
24490
24491
  }
24491
24492
  });
24493
+ const URL_SECRET_KEYS = ["token", "secret", "signature", "key", "apikey", "api_key"];
24494
+ const DEBUG_BUNDLE_ENV_ALLOWLIST = [
24495
+ "STRAPI_ADMIN_LOCALAZY_ENV",
24496
+ "STRAPI_ADMIN_BACKEND_URL",
24497
+ "NODE_ENV",
24498
+ "STRAPI_TELEMETRY_DISABLED"
24499
+ ];
24500
+ const redactIdentity = (identity2) => {
24501
+ if (!identity2) return null;
24502
+ return {
24503
+ userId: identity2.user?.id ?? "",
24504
+ name: identity2.user?.name ?? "",
24505
+ scope: identity2.scope ?? "",
24506
+ hasAccessToken: Boolean(identity2.accessToken)
24507
+ };
24508
+ };
24509
+ const scrubUrlSecrets = (input) => {
24510
+ if (typeof input !== "string") return input;
24511
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input) && !input.includes("?")) return input;
24512
+ try {
24513
+ const url2 = new URL(input);
24514
+ let mutated = false;
24515
+ for (const k2 of [...url2.searchParams.keys()]) {
24516
+ if (URL_SECRET_KEYS.includes(k2.toLowerCase())) {
24517
+ url2.searchParams.delete(k2);
24518
+ mutated = true;
24519
+ }
24520
+ }
24521
+ return mutated ? url2.toString() : input;
24522
+ } catch {
24523
+ return input;
24524
+ }
24525
+ };
24526
+ const pickEnvAllowlist = (env) => {
24527
+ const out = {};
24528
+ for (const key of DEBUG_BUNDLE_ENV_ALLOWLIST) {
24529
+ const raw = env[key];
24530
+ const present = raw !== void 0;
24531
+ out[key] = {
24532
+ present,
24533
+ value: present ? raw : null
24534
+ };
24535
+ }
24536
+ return out;
24537
+ };
24538
+ const serializeContentTypeSchema = (contentType) => {
24539
+ if (!contentType || typeof contentType !== "object") return null;
24540
+ const ct = contentType;
24541
+ return {
24542
+ kind: ct.kind,
24543
+ collectionName: ct.collectionName,
24544
+ info: ct.info,
24545
+ options: ct.options,
24546
+ pluginOptions: ct.pluginOptions,
24547
+ attributes: ct.attributes
24548
+ };
24549
+ };
24550
+ const DOCUMENT_INTERNAL_FIELDS = ["createdBy", "updatedBy"];
24551
+ const stripDocumentInternals = (doc) => {
24552
+ if (!doc || typeof doc !== "object") return doc;
24553
+ const clone = { ...doc };
24554
+ for (const field of DOCUMENT_INTERNAL_FIELDS) {
24555
+ delete clone[field];
24556
+ }
24557
+ return clone;
24558
+ };
24559
+ class SessionNotFoundError extends Error {
24560
+ constructor(sessionId) {
24561
+ super(`Session ${sessionId} not found`);
24562
+ this.name = "SessionNotFoundError";
24563
+ }
24564
+ }
24565
+ const DEBUG_BUNDLE_SCHEMA_VERSION = 1;
24566
+ const CAPS = {
24567
+ perEntryBytes: 1 * 1024 * 1024,
24568
+ // 1 MB
24569
+ totalEntries: 100,
24570
+ errorLogBytes: 2 * 1024 * 1024,
24571
+ // 2 MB
24572
+ consoleErrorsLines: 200,
24573
+ browserPayloadBytes: 64 * 1024
24574
+ // 64 KB
24575
+ };
24576
+ const selectAttemptedEntries = (attempted) => {
24577
+ const failedByKey = /* @__PURE__ */ new Map();
24578
+ const successByKey = /* @__PURE__ */ new Map();
24579
+ for (const record of attempted) {
24580
+ const key = `${record.uid}|${record.documentId}|${record.locale}`;
24581
+ const selected = {
24582
+ uid: record.uid,
24583
+ documentId: record.documentId,
24584
+ locale: record.locale,
24585
+ status: record.status,
24586
+ attemptedAt: record.attemptedAt
24587
+ };
24588
+ if (record.status === "failed") {
24589
+ successByKey.delete(key);
24590
+ if (!failedByKey.has(key)) failedByKey.set(key, selected);
24591
+ } else if (!failedByKey.has(key) && !successByKey.has(key)) {
24592
+ successByKey.set(key, selected);
24593
+ }
24594
+ }
24595
+ return [...failedByKey.values(), ...successByKey.values()];
24596
+ };
24597
+ const formatTimestampForFilename = (ms2) => new Date(ms2).toISOString().replace(/[:.]/g, "-");
24598
+ const safeUidForPath = (uid) => uid.replace(/::/g, "--").replace(/\./g, "-");
24599
+ const decodeBrowserPayload = (encoded) => {
24600
+ const markers = [];
24601
+ if (!encoded) return { payload: null, truncationMarkers: markers };
24602
+ try {
24603
+ const raw = Buffer.from(encoded, "base64").toString("utf8");
24604
+ const rawBytes = Buffer.byteLength(raw, "utf8");
24605
+ if (rawBytes > CAPS.browserPayloadBytes) {
24606
+ markers.push({ kind: "browser-payload-truncated", originalSizeBytes: rawBytes });
24607
+ return { payload: null, truncationMarkers: markers };
24608
+ }
24609
+ const parsed = JSON.parse(raw);
24610
+ if (parsed && Array.isArray(parsed.consoleErrors) && parsed.consoleErrors.length > CAPS.consoleErrorsLines) {
24611
+ markers.push({ kind: "console-errors-truncated", originalCount: parsed.consoleErrors.length });
24612
+ parsed.consoleErrors = parsed.consoleErrors.slice(0, CAPS.consoleErrorsLines);
24613
+ }
24614
+ return { payload: parsed, truncationMarkers: markers };
24615
+ } catch {
24616
+ return { payload: null, truncationMarkers: markers };
24617
+ }
24618
+ };
24619
+ const deepScrubUrls = (value) => {
24620
+ if (value == null) return value;
24621
+ if (typeof value === "string") return scrubUrlSecrets(value);
24622
+ if (Array.isArray(value)) return value.map(deepScrubUrls);
24623
+ if (typeof value === "object") {
24624
+ const out = {};
24625
+ for (const [k2, v2] of Object.entries(value)) {
24626
+ out[k2] = deepScrubUrls(v2);
24627
+ }
24628
+ return out;
24629
+ }
24630
+ return value;
24631
+ };
24632
+ const sanitizePluginSettings = (settings) => {
24633
+ const cloned = JSON.parse(JSON.stringify(settings ?? {}));
24634
+ if (cloned.webhookConfig?.url) {
24635
+ cloned.webhookConfig.url = scrubUrlSecrets(cloned.webhookConfig.url);
24636
+ }
24637
+ return cloned;
24638
+ };
24639
+ const sanitizeSession = (session) => {
24640
+ const cloned = JSON.parse(JSON.stringify(session));
24641
+ for (const entry of cloned.entries ?? []) {
24642
+ if (entry.data !== void 0) {
24643
+ entry.data = deepScrubUrls(entry.data);
24644
+ }
24645
+ }
24646
+ return cloned;
24647
+ };
24648
+ const buildErrorLog = (session) => {
24649
+ const lines = [];
24650
+ for (const entry of session.entries ?? []) {
24651
+ const ts = Number.isFinite(entry.timestamp) ? new Date(entry.timestamp).toISOString() : "unknown";
24652
+ lines.push(`${ts} ${entry.message}`);
24653
+ }
24654
+ if (session.status === "failed" && session.summary) {
24655
+ lines.push(`ERROR: ${session.summary}`);
24656
+ }
24657
+ const full = lines.join("\n");
24658
+ const fullBytes = Buffer.byteLength(full, "utf8");
24659
+ if (fullBytes <= CAPS.errorLogBytes) {
24660
+ return { text: full, markers: [] };
24661
+ }
24662
+ const truncated = Buffer.from(full, "utf8").subarray(fullBytes - CAPS.errorLogBytes).toString("utf8");
24663
+ return {
24664
+ text: `...[truncated, kept last ${CAPS.errorLogBytes} bytes]
24665
+ ${truncated}`,
24666
+ markers: [{ kind: "error-log-truncated", originalSizeBytes: fullBytes }]
24667
+ };
24668
+ };
24669
+ const readHostPackages = async (strapi2) => {
24670
+ const appRoot = strapi2?.dirs?.app?.root ?? process.cwd();
24671
+ if (!appRoot) return { data: null, reason: "host-app-root-unknown" };
24672
+ try {
24673
+ const raw = await promises.readFile(require$$1.join(appRoot, "package.json"), "utf8");
24674
+ const parsed = JSON.parse(raw);
24675
+ return {
24676
+ data: {
24677
+ name: parsed.name,
24678
+ version: parsed.version,
24679
+ dependencies: parsed.dependencies ?? {},
24680
+ devDependencies: parsed.devDependencies ?? {}
24681
+ }
24682
+ };
24683
+ } catch (err) {
24684
+ return { data: null, reason: err instanceof Error ? err.message : String(err) };
24685
+ }
24686
+ };
24687
+ const buildReadme = ({
24688
+ sessionShortId,
24689
+ sessionId,
24690
+ schemaVersion,
24691
+ hostPackagesSkipped
24692
+ }) => {
24693
+ const lines = [];
24694
+ lines.push(`# Localazy Strapi Plugin — Troubleshooting Bundle`);
24695
+ lines.push("");
24696
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
24697
+ lines.push(`Session id: ${sessionId}`);
24698
+ lines.push(`Schema version: ${schemaVersion}`);
24699
+ lines.push("");
24700
+ lines.push("## Contents");
24701
+ lines.push("");
24702
+ lines.push("- `meta.json` — schema version, session metadata.");
24703
+ lines.push("- `versions.json` — plugin version, Strapi version, Node version, platform/arch.");
24704
+ lines.push("- `env.json` — allow-listed environment variables only.");
24705
+ lines.push("- `browser.json` — user agent, language, viewport, console errors (from the admin UI).");
24706
+ lines.push("- `session.json` — full activity-log session record (URLs scrubbed of secret query keys).");
24707
+ lines.push("- `error.log` — chronological text log of session entries.");
24708
+ lines.push("- `plugin-settings.json` — full plugin settings (webhook URL secrets scrubbed).");
24709
+ lines.push("- `content-transfer-setup.json` — content-transfer setup record.");
24710
+ if (hostPackagesSkipped) {
24711
+ lines.push("- `host-packages.json` — NOT INCLUDED (could not be read; see `bundle-truncated.json`).");
24712
+ } else {
24713
+ lines.push("- `host-packages.json` — host project's package.json (deps + devDeps only).");
24714
+ }
24715
+ lines.push("- `content-types/` — schema.json per touched content-type UID.");
24716
+ lines.push("- `entries/<uid>/<documentId>--<locale>.json` — per-entry payload from `strapi.documents().findOne()`.");
24717
+ lines.push("- `bundle-truncated.json` — present only if any per-section cap was exceeded.");
24718
+ lines.push("");
24719
+ lines.push("## Reproducing locally");
24720
+ lines.push("");
24721
+ lines.push("1. `unzip` the bundle.");
24722
+ lines.push("2. Recreate the content types from `content-types/*.schema.json` in your Strapi project.");
24723
+ lines.push("3. Import the entries via the Document Service using `entries/<uid>/*.json`.");
24724
+ lines.push("4. Apply `plugin-settings.json` and `content-transfer-setup.json` via the plugin admin UI.");
24725
+ lines.push("5. Trigger the same upload/download/webhook with the same locale set.");
24726
+ lines.push("6. Compare the failure against `error.log` and `session.json`.");
24727
+ lines.push("");
24728
+ lines.push(`Bundle short id: ${sessionShortId}`);
24729
+ return lines.join("\n");
24730
+ };
24731
+ const buildDebugBundle = async ({
24732
+ strapi: strapi2,
24733
+ sessionId,
24734
+ browserPayloadEncoded
24735
+ }) => {
24736
+ const zip = new JSZip();
24737
+ const truncationMarkers = [];
24738
+ const pluginName = "localazy";
24739
+ const activityLogsService = strapi2.plugin(pluginName).service("ActivityLogsService");
24740
+ const pluginSettingsService = strapi2.plugin(pluginName).service("PluginSettingsService");
24741
+ const strapiService = strapi2.plugin(pluginName).service("StrapiService");
24742
+ const localazyUserService = strapi2.plugin(pluginName).service("LocalazyUserService");
24743
+ const session = await activityLogsService.getSession(sessionId);
24744
+ if (!session) {
24745
+ throw new SessionNotFoundError(sessionId);
24746
+ }
24747
+ const sessionShortId = session.id.slice(0, 8);
24748
+ const pluginSettingsRaw = await pluginSettingsService.getPluginSettings();
24749
+ const contentTransferSetup = await pluginSettingsService.getContentTransferSetup();
24750
+ const identity2 = await localazyUserService.getUser();
24751
+ const redactedIdentity = redactIdentity(identity2);
24752
+ const pluginVersion = await strapiService.getPluginVersion();
24753
+ const strapiVersion = strapiService.getStrapiVersion();
24754
+ zip.file(
24755
+ "meta.json",
24756
+ JSON.stringify(
24757
+ {
24758
+ schemaVersion: DEBUG_BUNDLE_SCHEMA_VERSION,
24759
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24760
+ sessionId: session.id,
24761
+ sessionShortId,
24762
+ eventType: session.eventType,
24763
+ status: session.status,
24764
+ startedAt: session.startedAt,
24765
+ finishedAt: session.finishedAt ?? null,
24766
+ initiatedBy: session.initiatedBy,
24767
+ identity: redactedIdentity
24768
+ },
24769
+ null,
24770
+ 2
24771
+ )
24772
+ );
24773
+ zip.file(
24774
+ "versions.json",
24775
+ JSON.stringify(
24776
+ {
24777
+ plugin: { name: "@localazy/strapi-plugin", version: pluginVersion },
24778
+ strapi: { version: strapiVersion },
24779
+ node: process.version,
24780
+ platform: process.platform,
24781
+ arch: process.arch
24782
+ },
24783
+ null,
24784
+ 2
24785
+ )
24786
+ );
24787
+ const envSnapshot = pickEnvAllowlist(process.env);
24788
+ zip.file(
24789
+ "env.json",
24790
+ JSON.stringify(
24791
+ {
24792
+ allowlist: [...DEBUG_BUNDLE_ENV_ALLOWLIST],
24793
+ values: envSnapshot
24794
+ },
24795
+ null,
24796
+ 2
24797
+ )
24798
+ );
24799
+ const { payload: browserPayload, truncationMarkers: browserMarkers } = decodeBrowserPayload(browserPayloadEncoded);
24800
+ truncationMarkers.push(...browserMarkers);
24801
+ zip.file("browser.json", JSON.stringify(browserPayload ? browserPayload : { captured: false }, null, 2));
24802
+ zip.file("session.json", JSON.stringify(sanitizeSession(session), null, 2));
24803
+ const { text: errorLogText, markers: errorLogMarkers } = buildErrorLog(session);
24804
+ truncationMarkers.push(...errorLogMarkers);
24805
+ zip.file("error.log", errorLogText);
24806
+ zip.file("plugin-settings.json", JSON.stringify(sanitizePluginSettings(pluginSettingsRaw), null, 2));
24807
+ zip.file("content-transfer-setup.json", JSON.stringify(contentTransferSetup, null, 2));
24808
+ const hostPackages = await readHostPackages(strapi2);
24809
+ if (hostPackages.data) {
24810
+ zip.file("host-packages.json", JSON.stringify(hostPackages.data, null, 2));
24811
+ }
24812
+ if (session.attemptedEntries === void 0) {
24813
+ truncationMarkers.push({ kind: "attempted-entries-missing-legacy-session" });
24814
+ }
24815
+ const selected = selectAttemptedEntries(session.attemptedEntries ?? []);
24816
+ const kept = selected.slice(0, CAPS.totalEntries);
24817
+ const skipped = selected.slice(CAPS.totalEntries);
24818
+ if (skipped.length > 0) {
24819
+ truncationMarkers.push({
24820
+ kind: "entries-skipped",
24821
+ tuples: skipped.map(({ uid, documentId, locale }) => ({ uid, documentId, locale }))
24822
+ });
24823
+ }
24824
+ const uniqueUids = [...new Set(kept.map((t3) => t3.uid))];
24825
+ for (const uid of uniqueUids) {
24826
+ const schema = serializeContentTypeSchema(strapi2.contentTypes?.[uid]);
24827
+ if (schema) {
24828
+ zip.file(`content-types/${safeUidForPath(uid)}.schema.json`, JSON.stringify(schema, null, 2));
24829
+ }
24830
+ }
24831
+ for (const tuple of kept) {
24832
+ let documentJson = null;
24833
+ try {
24834
+ const doc = await strapi2.documents(tuple.uid).findOne({ documentId: tuple.documentId, locale: tuple.locale, populate: "*" });
24835
+ documentJson = doc ? stripDocumentInternals(doc) : { __not_found: true, attemptedAt: tuple.attemptedAt };
24836
+ } catch (err) {
24837
+ documentJson = { __error: err instanceof Error ? err.message : String(err) };
24838
+ }
24839
+ const serialised = JSON.stringify(documentJson, null, 2);
24840
+ const sizeBytes = Buffer.byteLength(serialised, "utf8");
24841
+ let body;
24842
+ if (sizeBytes > CAPS.perEntryBytes) {
24843
+ truncationMarkers.push({
24844
+ kind: "entry-too-large",
24845
+ uid: tuple.uid,
24846
+ documentId: tuple.documentId,
24847
+ locale: tuple.locale,
24848
+ originalSizeBytes: sizeBytes
24849
+ });
24850
+ body = JSON.stringify({ __truncated: true, originalSizeBytes: sizeBytes }, null, 2);
24851
+ } else {
24852
+ body = serialised;
24853
+ }
24854
+ const safeDocId = tuple.documentId.replace(/[^a-zA-Z0-9_-]/g, "_");
24855
+ zip.file(`entries/${safeUidForPath(tuple.uid)}/${safeDocId}--${tuple.locale}.json`, body);
24856
+ }
24857
+ zip.file(
24858
+ "README.md",
24859
+ buildReadme({
24860
+ sessionShortId,
24861
+ sessionId: session.id,
24862
+ schemaVersion: DEBUG_BUNDLE_SCHEMA_VERSION,
24863
+ hostPackagesSkipped: !hostPackages.data
24864
+ })
24865
+ );
24866
+ if (truncationMarkers.length > 0 || !hostPackages.data) {
24867
+ const marker = {
24868
+ schemaVersion: DEBUG_BUNDLE_SCHEMA_VERSION,
24869
+ markers: truncationMarkers,
24870
+ hostPackagesSkipped: hostPackages.data ? null : hostPackages.reason ?? "unknown"
24871
+ };
24872
+ zip.file("bundle-truncated.json", JSON.stringify(marker, null, 2));
24873
+ }
24874
+ const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
24875
+ const filename = `localazy-debug-bundle-${sessionShortId}-${formatTimestampForFilename(Date.now())}.zip`;
24876
+ return { zipBuffer, filename };
24877
+ };
24878
+ const DebugBundleController = ({ strapi: strapi2 }) => ({
24879
+ async getBundle(ctx) {
24880
+ const { sessionId } = ctx.params;
24881
+ const browserPayloadEncoded = typeof ctx.query.browser === "string" ? ctx.query.browser : null;
24882
+ try {
24883
+ const { zipBuffer, filename } = await buildDebugBundle({
24884
+ strapi: strapi2,
24885
+ sessionId,
24886
+ browserPayloadEncoded
24887
+ });
24888
+ ctx.type = "application/zip";
24889
+ ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
24890
+ ctx.body = zipBuffer;
24891
+ } catch (err) {
24892
+ if (err instanceof SessionNotFoundError) {
24893
+ return ctx.notFound("Session not found");
24894
+ }
24895
+ strapi2.log.error(`[localazy] failed to build debug bundle: ${err instanceof Error ? err.message : err}`);
24896
+ throw err;
24897
+ }
24898
+ }
24899
+ });
24492
24900
  const controllers = {
24493
24901
  LocalazyUserController,
24494
24902
  LocalazyAuthController,
@@ -24497,7 +24905,8 @@ const controllers = {
24497
24905
  PluginSettingsController,
24498
24906
  LocalazyTransferController,
24499
24907
  EntryExclusionController,
24500
- ActivityLogsController
24908
+ ActivityLogsController,
24909
+ DebugBundleController
24501
24910
  };
24502
24911
  const verifyWebhook = (config2, { strapi: strapi2 }) => {
24503
24912
  return async (ctx, next) => {
@@ -24808,6 +25217,16 @@ const ActivityLogsRoutes = [
24808
25217
  }
24809
25218
  }
24810
25219
  ];
25220
+ const DebugBundleRoutes = [
25221
+ {
25222
+ method: "GET",
25223
+ path: "/activity-logs/:sessionId/bundle",
25224
+ handler: "DebugBundleController.getBundle",
25225
+ config: {
25226
+ policies: []
25227
+ }
25228
+ }
25229
+ ];
24811
25230
  const routes = {
24812
25231
  "content-api": {
24813
25232
  routes: [...StrapiRoutes]
@@ -24822,7 +25241,8 @@ const routes = {
24822
25241
  ...LocalazyTransferRoutes,
24823
25242
  ...LocalazyProjectRoutes,
24824
25243
  ...EntryExclusionRoutes,
24825
- ...ActivityLogsRoutes
25244
+ ...ActivityLogsRoutes,
25245
+ ...DebugBundleRoutes
24826
25246
  ]
24827
25247
  },
24828
25248
  LocalazyPublicTransferRoutes: {
@@ -25101,6 +25521,8 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25101
25521
  const { setup: setup2 } = contentTransferSetup;
25102
25522
  const collectionsNames = getCollectionsNames(setup2);
25103
25523
  const models = await StrapiService2.getModels();
25524
+ const strapiDefaultLocaleCode = await StrapiI18nService2.getDefaultLocaleCode();
25525
+ const attemptedTuples = [];
25104
25526
  let pickedFlattenStrapiContent = {};
25105
25527
  for (const collectionName of collectionsNames) {
25106
25528
  const currentModel = models.find((model) => model.collectionName === collectionName);
@@ -25163,6 +25585,15 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25163
25585
  });
25164
25586
  await activityLogsService.addEntry(sessionId, message);
25165
25587
  strapi2.log.info(message);
25588
+ if (strapiDefaultLocaleCode) {
25589
+ for (const entry of entries) {
25590
+ attemptedTuples.push({
25591
+ uid: modelUid,
25592
+ documentId: entry.documentId,
25593
+ locale: strapiDefaultLocaleCode
25594
+ });
25595
+ }
25596
+ }
25166
25597
  entries = omitDeep(entries, ["locale", "createdAt", "updatedAt", "publishedAt"]);
25167
25598
  entries.forEach((entry) => {
25168
25599
  const flatten = flattenObject({
@@ -25180,7 +25611,6 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25180
25611
  ...characterLimitsMetadata
25181
25612
  };
25182
25613
  }
25183
- const strapiDefaultLocaleCode = await StrapiI18nService2.getDefaultLocaleCode();
25184
25614
  const localazyLocaleCode = strapiDefaultLocaleCode ? isoStrapiToLocalazy(strapiDefaultLocaleCode) : config.default.LOCALAZY_DEFAULT_LOCALE;
25185
25615
  const importFile = LocalazyUploadService2.createImportFileRepresentation(
25186
25616
  localazyLocaleCode,
@@ -25202,6 +25632,10 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25202
25632
  await activityLogsService.addEntry(sessionId, "Uploading collections to Localazy...");
25203
25633
  try {
25204
25634
  await LocalazyUploadService2.upload(importFile, uploadConfig);
25635
+ await activityLogsService.recordAttemptedEntries(
25636
+ sessionId,
25637
+ attemptedTuples.map((t3) => ({ ...t3, status: "success" }))
25638
+ );
25205
25639
  } catch (error) {
25206
25640
  success = false;
25207
25641
  const errorMessage = `Upload to Localazy failed: ${error instanceof Error ? error.message : String(error)}`;
@@ -25211,6 +25645,10 @@ const LocalazyTransferUploadService = ({ strapi: strapi2 }) => ({
25211
25645
  message: errorMessage
25212
25646
  });
25213
25647
  await activityLogsService.addEntry(sessionId, errorMessage);
25648
+ await activityLogsService.recordAttemptedEntries(
25649
+ sessionId,
25650
+ attemptedTuples.map((t3) => ({ ...t3, status: "failed", errorMessage }))
25651
+ );
25214
25652
  await activityLogsService.finishSession(sessionId, "failed", errorMessage);
25215
25653
  console.timeEnd("upload");
25216
25654
  return;
@@ -25935,6 +26373,12 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25935
26373
  message
25936
26374
  });
25937
26375
  await activityLogsService.addEntry(sessionId, message);
26376
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26377
+ uid,
26378
+ documentId,
26379
+ locale: isoStrapi,
26380
+ status: "success"
26381
+ });
25938
26382
  await markKeysProcessed(sourceKeys);
25939
26383
  } catch (e2) {
25940
26384
  success = false;
@@ -25951,6 +26395,13 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25951
26395
  sessionId,
25952
26396
  `Failed to create ${uid} [${documentId}] in ${isoStrapi}: ${e2.message}`
25953
26397
  );
26398
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26399
+ uid,
26400
+ documentId,
26401
+ locale: isoStrapi,
26402
+ status: "failed",
26403
+ errorMessage: e2.message
26404
+ });
25954
26405
  }
25955
26406
  } else {
25956
26407
  try {
@@ -25969,6 +26420,12 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25969
26420
  message
25970
26421
  });
25971
26422
  await activityLogsService.addEntry(sessionId, message);
26423
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26424
+ uid,
26425
+ documentId,
26426
+ locale: isoStrapi,
26427
+ status: "success"
26428
+ });
25972
26429
  await markKeysProcessed(sourceKeys);
25973
26430
  } catch (e2) {
25974
26431
  success = false;
@@ -25985,6 +26442,13 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25985
26442
  sessionId,
25986
26443
  `Failed to update ${uid} [${documentId}] in ${isoStrapi}: ${e2.message}`
25987
26444
  );
26445
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26446
+ uid,
26447
+ documentId,
26448
+ locale: isoStrapi,
26449
+ status: "failed",
26450
+ errorMessage: e2.message
26451
+ });
25988
26452
  }
25989
26453
  }
25990
26454
  } catch (e2) {
@@ -25997,6 +26461,13 @@ const LocalazyTransferDownloadService = ({ strapi: strapi2 }) => ({
25997
26461
  sessionId,
25998
26462
  `Error processing ${uid} [${documentId}] in ${isoStrapi}: ${e2.message}`
25999
26463
  );
26464
+ await activityLogsService.recordAttemptedEntry(sessionId, {
26465
+ uid,
26466
+ documentId,
26467
+ locale: isoStrapi,
26468
+ status: "failed",
26469
+ errorMessage: e2.message
26470
+ });
26000
26471
  }
26001
26472
  }
26002
26473
  }
@@ -26747,6 +27218,7 @@ const emptyActivityLogs = {
26747
27218
  sessions: []
26748
27219
  };
26749
27220
  const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
27221
+ const ATTEMPTED_ENTRIES_CAP = 5e3;
26750
27222
  let writeLock = Promise.resolve();
26751
27223
  const withLock = async (fn) => {
26752
27224
  let release;
@@ -26811,6 +27283,38 @@ const ActivityLogsService = ({ strapi: strapi2 }) => ({
26811
27283
  await this.saveActivityLogs(logs);
26812
27284
  });
26813
27285
  },
27286
+ async recordAttemptedEntry(sessionId, entry) {
27287
+ return this.recordAttemptedEntries(sessionId, [entry]);
27288
+ },
27289
+ async recordAttemptedEntries(sessionId, entries) {
27290
+ if (!entries.length) {
27291
+ return;
27292
+ }
27293
+ return withLock(async () => {
27294
+ const logs = await this.getActivityLogs();
27295
+ const session = logs.sessions.find((s2) => s2.id === sessionId);
27296
+ if (!session) {
27297
+ return;
27298
+ }
27299
+ if (!session.attemptedEntries) {
27300
+ session.attemptedEntries = [];
27301
+ }
27302
+ const now = Date.now();
27303
+ const wasAtCap = session.attemptedEntries.length >= ATTEMPTED_ENTRIES_CAP;
27304
+ const remaining = Math.max(0, ATTEMPTED_ENTRIES_CAP - session.attemptedEntries.length);
27305
+ const accepted = entries.slice(0, remaining);
27306
+ const dropped = entries.length - accepted.length;
27307
+ for (const entry of accepted) {
27308
+ session.attemptedEntries.push({ ...entry, attemptedAt: now });
27309
+ }
27310
+ if (dropped > 0 && !wasAtCap) {
27311
+ strapi2.log.warn(
27312
+ `[localazy] attemptedEntries cap (${ATTEMPTED_ENTRIES_CAP}) reached for session ${sessionId}; ${dropped} record(s) dropped.`
27313
+ );
27314
+ }
27315
+ await this.saveActivityLogs(logs);
27316
+ });
27317
+ },
26814
27318
  async finishSession(sessionId, status, summary) {
26815
27319
  return withLock(async () => {
26816
27320
  const logs = await this.getActivityLogs();
@@ -0,0 +1,7 @@
1
+ import type { Core } from '@strapi/strapi';
2
+ declare const DebugBundleController: ({ strapi }: {
3
+ strapi: Core.Strapi;
4
+ }) => {
5
+ getBundle(ctx: any): Promise<any>;
6
+ };
7
+ export default DebugBundleController;
@@ -58,5 +58,10 @@ declare const _default: {
58
58
  exportSessions(ctx: any): Promise<void>;
59
59
  clearSessions(ctx: any): Promise<void>;
60
60
  };
61
+ DebugBundleController: ({ strapi }: {
62
+ strapi: import("@strapi/types/dist/core").Strapi;
63
+ }) => {
64
+ getBundle(ctx: any): Promise<any>;
65
+ };
61
66
  };
62
67
  export default _default;
@@ -86,6 +86,8 @@ export declare const getActivityLogsService: () => {
86
86
  saveActivityLogs(logs: import("../db/model/activity-logs").ActivityLogs): Promise<void>;
87
87
  startSession(eventType: "upload" | "download" | "webhook", initiatedBy: string): Promise<string>;
88
88
  addEntry(sessionId: string, message: string, data?: any): Promise<void>;
89
+ recordAttemptedEntry(sessionId: string, entry: Omit<import("../db/model/activity-logs").AttemptedEntry, "attemptedAt">): Promise<void>;
90
+ recordAttemptedEntries(sessionId: string, entries: Omit<import("../db/model/activity-logs").AttemptedEntry, "attemptedAt">[]): Promise<void>;
89
91
  finishSession(sessionId: string, status: "completed" | "failed", summary: string): Promise<void>;
90
92
  getSessions(eventType?: "upload" | "download" | "webhook"): Promise<import("../db/model/activity-logs").ActivityLogSession[]>;
91
93
  getAllSessionsWithEntries(): Promise<import("../db/model/activity-logs").ActivityLogSession[]>;