livelocale 0.8.1 → 0.9.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 (51) hide show
  1. package/README.md +230 -5
  2. package/dist/client/index.cjs +150 -86
  3. package/dist/client/index.cjs.map +1 -1
  4. package/dist/client/index.d.cts +35 -21
  5. package/dist/client/index.d.ts +35 -21
  6. package/dist/client/index.js +150 -86
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/codemod/index.cjs +3057 -152
  9. package/dist/codemod/index.cjs.map +1 -1
  10. package/dist/codemod/index.js +3058 -153
  11. package/dist/codemod/index.js.map +1 -1
  12. package/dist/react/index.cjs +157 -92
  13. package/dist/react/index.cjs.map +1 -1
  14. package/dist/react/index.js +157 -92
  15. package/dist/react/index.js.map +1 -1
  16. package/dist/server/index.cjs +100 -64
  17. package/dist/server/index.cjs.map +1 -1
  18. package/dist/server/index.d.cts +12 -2
  19. package/dist/server/index.d.ts +12 -2
  20. package/dist/server/index.js +100 -64
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/studio/assets/index-Bvbtkry-.css +1 -0
  23. package/dist/studio/assets/index-DC_jRsox.js +13 -0
  24. package/dist/studio/assets/inter-latin-300-normal-BVlfKGgI.woff2 +0 -0
  25. package/dist/studio/assets/inter-latin-300-normal-i8F0SvXL.woff +0 -0
  26. package/dist/studio/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  27. package/dist/studio/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  28. package/dist/studio/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  29. package/dist/studio/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  30. package/dist/studio/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  31. package/dist/studio/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  32. package/dist/studio/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
  33. package/dist/studio/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  34. package/dist/studio/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
  35. package/dist/studio/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
  36. package/dist/studio/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
  37. package/dist/studio/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
  38. package/dist/studio/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
  39. package/dist/studio/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
  40. package/dist/studio/assets/outfit-latin-700-normal-Cu9v6i1X.woff2 +0 -0
  41. package/dist/studio/assets/outfit-latin-700-normal-D4itBLBr.woff +0 -0
  42. package/dist/studio/index.html +3 -6
  43. package/dist/vite/index.cjs +739 -77
  44. package/dist/vite/index.cjs.map +1 -1
  45. package/dist/vite/index.d.cts +22 -2
  46. package/dist/vite/index.d.ts +22 -2
  47. package/dist/vite/index.js +738 -77
  48. package/dist/vite/index.js.map +1 -1
  49. package/package.json +4 -1
  50. package/dist/studio/assets/index-D6D5nPQI.js +0 -10
  51. package/dist/studio/assets/index-z0sbyt6N.css +0 -2
@@ -593,14 +593,6 @@ var KeyScanner = class {
593
593
  };
594
594
 
595
595
  // src/server/file-mutator.ts
596
- function assertEnabled(component) {
597
- const reason = getDisabledReason();
598
- if (reason !== null) {
599
- throw new Error(
600
- `[livelocale] ${component} can only be executed in development mode (${reason}).`
601
- );
602
- }
603
- }
604
596
  var FileMutex = class {
605
597
  queue = Promise.resolve();
606
598
  async acquire(fn) {
@@ -616,7 +608,23 @@ var FileMutex = class {
616
608
  };
617
609
  var FileMutator = class {
618
610
  fileLocks = /* @__PURE__ */ new Map();
619
- scanner = new KeyScanner();
611
+ requireEnabled;
612
+ quiet;
613
+ scanner;
614
+ constructor(options = {}) {
615
+ this.requireEnabled = options.requireEnabled ?? true;
616
+ this.quiet = options.quiet ?? false;
617
+ this.scanner = new KeyScanner({ requireEnabled: this.requireEnabled });
618
+ }
619
+ assertEnabled() {
620
+ if (!this.requireEnabled) return;
621
+ const reason = getDisabledReason();
622
+ if (reason !== null) {
623
+ throw new Error(
624
+ `[livelocale] FileMutator can only be executed in development mode (${reason}).`
625
+ );
626
+ }
627
+ }
620
628
  /**
621
629
  * Helper to serialize execution per file path
622
630
  */
@@ -634,7 +642,7 @@ var FileMutator = class {
634
642
  * structural JSON validation, and concurrency serialization.
635
643
  */
636
644
  async updateLocaleKey(basePath, locale, keyPath, newValue) {
637
- assertEnabled("FileMutator");
645
+ this.assertEnabled();
638
646
  if (!isSafeLocaleSegment(locale)) {
639
647
  throw new Error(
640
648
  `[livelocale] Path traversal attempt detected in locale identifier: "${locale}"`
@@ -737,7 +745,9 @@ var FileMutator = class {
737
745
  `[livelocale] Failed to rename temp file to target at ${filePath}. Operation: rename. Original error: ${err.message}`
738
746
  );
739
747
  }
740
- console.log(`[livelocale] Updated key '${keyPath}' in locale '${locale}'`);
748
+ if (!this.quiet) {
749
+ console.log(`[livelocale] Updated key '${keyPath}' in locale '${locale}'`);
750
+ }
741
751
  });
742
752
  }
743
753
  /**
@@ -745,7 +755,7 @@ var FileMutator = class {
745
755
  * `*.json` inside the `{locale}/` folder merged under its namespace.
746
756
  */
747
757
  async readLocale(basePath, locale) {
748
- assertEnabled("FileMutator");
758
+ this.assertEnabled();
749
759
  const localePath = path3.resolve(basePath, locale);
750
760
  let isDir = false;
751
761
  try {
@@ -772,7 +782,7 @@ var FileMutator = class {
772
782
  * Lists all locale codes dynamically scanned from the locales directory.
773
783
  */
774
784
  async listLocales(basePath) {
775
- assertEnabled("FileMutator");
785
+ this.assertEnabled();
776
786
  const resolvedBasePath = path3.resolve(basePath);
777
787
  const items = await import_fs2.promises.readdir(resolvedBasePath, { withFileTypes: true });
778
788
  const localesSet = /* @__PURE__ */ new Set();
@@ -794,7 +804,7 @@ var FileMutator = class {
794
804
  * Adds a new locale directory/file based on current repository structure.
795
805
  */
796
806
  async addLocale(basePath, locale) {
797
- assertEnabled("FileMutator");
807
+ this.assertEnabled();
798
808
  if (!isSafeLocaleSegment(locale)) {
799
809
  throw new Error(
800
810
  `[livelocale] Path traversal attempt detected in locale identifier: "${locale}"`
@@ -850,7 +860,7 @@ var FileMutator = class {
850
860
  * Renames an existing locale (directory or file) to a new name.
851
861
  */
852
862
  async renameLocale(basePath, oldLocale, newLocale) {
853
- assertEnabled("FileMutator");
863
+ this.assertEnabled();
854
864
  if (!isSafeLocaleSegment(oldLocale) || !isSafeLocaleSegment(newLocale)) {
855
865
  throw new Error("[livelocale] Path traversal attempt detected");
856
866
  }
@@ -908,7 +918,7 @@ var FileMutator = class {
908
918
  * Deletes an existing locale (directory or file).
909
919
  */
910
920
  async deleteLocale(basePath, locale) {
911
- assertEnabled("FileMutator");
921
+ this.assertEnabled();
912
922
  if (!isSafeLocaleSegment(locale)) {
913
923
  throw new Error("[livelocale] Path traversal attempt detected");
914
924
  }
@@ -1699,6 +1709,67 @@ function validateMessageSyntax(message) {
1699
1709
  return parseIcu(prepared).error;
1700
1710
  }
1701
1711
 
1712
+ // src/core/save-rules.ts
1713
+ var reject = (status, code, error) => ({
1714
+ ok: false,
1715
+ status,
1716
+ code,
1717
+ error
1718
+ });
1719
+ function validateWrite(input, options = {}) {
1720
+ const { locale, key, value } = input;
1721
+ if (!isValidLocaleFormat(locale)) {
1722
+ return reject(400, "INVALID_LOCALE_FORMAT", "Invalid locale string parameter.");
1723
+ }
1724
+ if (!isValidKeyFormat(key)) {
1725
+ return reject(
1726
+ 400,
1727
+ "INVALID_KEY_FORMAT",
1728
+ "Invalid key path. Must be 1-200 characters containing alphanumeric, dots, underscores, or hyphens."
1729
+ );
1730
+ }
1731
+ if (!isSafeKeyPath(key)) {
1732
+ return reject(
1733
+ 400,
1734
+ "INVALID_KEY_FORMAT",
1735
+ "Forbidden key path: segments like __proto__, constructor, or prototype are not allowed."
1736
+ );
1737
+ }
1738
+ if (!isValidValueFormat(value)) {
1739
+ return reject(
1740
+ 400,
1741
+ "INVALID_VALUE_FORMAT",
1742
+ "Invalid value. Must be a string less than 10000 characters."
1743
+ );
1744
+ }
1745
+ const accepted = {
1746
+ ok: true,
1747
+ locale,
1748
+ key,
1749
+ value
1750
+ };
1751
+ if (options.force === true) return accepted;
1752
+ const syntaxError = validateMessageSyntax(accepted.value);
1753
+ if (syntaxError) {
1754
+ return reject(422, "MESSAGE_SYNTAX", `Message syntax: ${syntaxError}.`);
1755
+ }
1756
+ const comparison = comparePlaceholders(options.reference, accepted.value);
1757
+ if (!comparison.ok) {
1758
+ const parts = [];
1759
+ if (comparison.missing.length) parts.push(`missing ${comparison.missing.join(", ")}`);
1760
+ if (comparison.extra.length) parts.push(`unexpected ${comparison.extra.join(", ")}`);
1761
+ return {
1762
+ ...reject(
1763
+ 422,
1764
+ "PLACEHOLDER_MISMATCH",
1765
+ `Placeholders differ from the reference: ${parts.join("; ")}.`
1766
+ ),
1767
+ details: { missing: comparison.missing, extra: comparison.extra }
1768
+ };
1769
+ }
1770
+ return accepted;
1771
+ }
1772
+
1702
1773
  // src/server/router.ts
1703
1774
  var DEFAULT_ALLOWED_ORIGINS = ["http://localhost:3010", "http://127.0.0.1:3010"];
1704
1775
  var MAX_BODY_BYTES = 1e6;
@@ -1886,53 +1957,18 @@ function createMutationRouter(mutator, options) {
1886
1957
  await mutator.deleteLocale(basePath, locale);
1887
1958
  return json(200, headers, { success: true, locale });
1888
1959
  }
1889
- if (!isValidLocaleFormat(locale)) {
1890
- return json(400, headers, {
1891
- error: "Invalid locale string parameter.",
1892
- code: "INVALID_LOCALE_FORMAT"
1893
- });
1894
- }
1895
- if (!isValidKeyFormat(key)) {
1896
- return json(400, headers, {
1897
- error: "Invalid key path. Must be 1-200 characters containing alphanumeric, dots, underscores, or hyphens.",
1898
- code: "INVALID_KEY_FORMAT"
1899
- });
1900
- }
1901
- if (!isSafeKeyPath(key)) {
1902
- return json(400, headers, {
1903
- error: "Forbidden key path: segments like __proto__, constructor, or prototype are not allowed.",
1904
- code: "INVALID_KEY_FORMAT"
1905
- });
1906
- }
1907
- if (!isValidValueFormat(value)) {
1908
- return json(400, headers, {
1909
- error: "Invalid value. Must be a string less than 10000 characters.",
1910
- code: "INVALID_VALUE_FORMAT"
1960
+ const verdict = validateWrite(
1961
+ { locale, key, value },
1962
+ { reference: typeof reference === "string" ? reference : null, force: force === true }
1963
+ );
1964
+ if (!verdict.ok) {
1965
+ return json(verdict.status, headers, {
1966
+ error: verdict.error,
1967
+ code: verdict.code,
1968
+ details: verdict.details
1911
1969
  });
1912
1970
  }
1913
- if (force !== true) {
1914
- const syntaxError = validateMessageSyntax(value);
1915
- if (syntaxError) {
1916
- return json(422, headers, {
1917
- error: `Message syntax: ${syntaxError}.`,
1918
- code: "MESSAGE_SYNTAX"
1919
- });
1920
- }
1921
- if (typeof reference === "string") {
1922
- const comparison = comparePlaceholders(reference, value);
1923
- if (!comparison.ok) {
1924
- const parts = [];
1925
- if (comparison.missing.length) parts.push(`missing ${comparison.missing.join(", ")}`);
1926
- if (comparison.extra.length) parts.push(`unexpected ${comparison.extra.join(", ")}`);
1927
- return json(422, headers, {
1928
- error: `Placeholders differ from the reference: ${parts.join("; ")}.`,
1929
- code: "PLACEHOLDER_MISMATCH",
1930
- details: { missing: comparison.missing, extra: comparison.extra }
1931
- });
1932
- }
1933
- }
1934
- }
1935
- await mutator.updateLocaleKey(basePath, locale, key, value);
1971
+ await mutator.updateLocaleKey(basePath, verdict.locale, verdict.key, verdict.value);
1936
1972
  return json(200, headers, { success: true, key, locale });
1937
1973
  }
1938
1974
  return {
@@ -2006,7 +2042,7 @@ var BodyTooLargeError = class extends Error {
2006
2042
  }
2007
2043
  };
2008
2044
  function readNodeBody(req, maxBytes) {
2009
- return new Promise((resolve5, reject) => {
2045
+ return new Promise((resolve5, reject2) => {
2010
2046
  let bodyText = "";
2011
2047
  let settled = false;
2012
2048
  req.on("data", (chunk) => {
@@ -2014,7 +2050,7 @@ function readNodeBody(req, maxBytes) {
2014
2050
  bodyText += chunk.toString();
2015
2051
  if (Buffer.byteLength(bodyText, "utf-8") > maxBytes) {
2016
2052
  settled = true;
2017
- reject(new BodyTooLargeError());
2053
+ reject2(new BodyTooLargeError());
2018
2054
  req.destroy?.();
2019
2055
  }
2020
2056
  });
@@ -2026,7 +2062,7 @@ function readNodeBody(req, maxBytes) {
2026
2062
  req.on("error", (err) => {
2027
2063
  if (settled) return;
2028
2064
  settled = true;
2029
- reject(err);
2065
+ reject2(err);
2030
2066
  });
2031
2067
  });
2032
2068
  }