@lang-tag/cli 0.15.0 → 0.16.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.
package/index.cjs CHANGED
@@ -9,8 +9,9 @@ const path = require("path");
9
9
  const globby = require("globby");
10
10
  const path$1 = require("pathe");
11
11
  const url = require("url");
12
- const namespaceCollector = require("./namespace-collector-DRnZvkDR.cjs");
12
+ require("case");
13
13
  const process$1 = require("node:process");
14
+ const flexibleImportAlgorithm = require("./flexible-import-algorithm-Fa-l4jWj.cjs");
14
15
  const acorn = require("acorn");
15
16
  const micromatch = require("micromatch");
16
17
  const chokidar = require("chokidar");
@@ -499,7 +500,7 @@ function isConfigSame(c1, c2) {
499
500
  return false;
500
501
  }
501
502
  const CONFIG_FILE_NAME = ".lang-tag.config.js";
502
- const EXPORTS_FILE_NAME = ".lang-tag.exports.json";
503
+ const EXPORTS_FILE_NAME = "lang-tags.json";
503
504
  const LANG_TAG_DEFAULT_CONFIG = {
504
505
  tagName: "lang",
505
506
  isLibrary: false,
@@ -508,7 +509,7 @@ const LANG_TAG_DEFAULT_CONFIG = {
508
509
  localesDirectory: "locales",
509
510
  baseLanguageCode: "en",
510
511
  collect: {
511
- collector: new namespaceCollector.NamespaceCollector(),
512
+ collector: new flexibleImportAlgorithm.NamespaceCollector(),
512
513
  defaultNamespace: "common",
513
514
  ignoreConflictsWithMatchingValues: true,
514
515
  onCollectConfigFix: ({ config, langTagConfig }) => {
@@ -528,12 +529,18 @@ const LANG_TAG_DEFAULT_CONFIG = {
528
529
  import: {
529
530
  dir: "src/lang-libraries",
530
531
  tagImportPath: 'import { lang } from "@/my-lang-tag-path"',
531
- onImport: ({ importedRelativePath, fileGenerationData }, actions) => {
532
- const exportIndex = (fileGenerationData.index || 0) + 1;
533
- fileGenerationData.index = exportIndex;
534
- actions.setFile(path$1.basename(importedRelativePath));
535
- actions.setExportName(`translations${exportIndex}`);
536
- }
532
+ onImport: flexibleImportAlgorithm.flexibleImportAlgorithm({
533
+ filePath: {
534
+ includePackageInPath: true
535
+ }
536
+ })
537
+ // onImport: ({importedRelativePath, fileGenerationData}: LangTagCLIOnImportParams, actions)=> {
538
+ // const exportIndex = (fileGenerationData.index || 0) + 1;
539
+ // fileGenerationData.index = exportIndex;
540
+ //
541
+ // actions.setFile(path.basename(importedRelativePath));
542
+ // actions.setExportName(`translations${exportIndex}`);
543
+ // }
537
544
  },
538
545
  translationArgPosition: 1,
539
546
  onConfigGeneration: async (event) => {
@@ -786,7 +793,7 @@ function printLines(lines, startLineNumber, errorLines = /* @__PURE__ */ new Set
786
793
  }
787
794
  async function getLangTagCodeSection(tagInfo) {
788
795
  const { tag, relativeFilePath } = tagInfo;
789
- const fileContent = await namespaceCollector.$LT_ReadFileContent(relativeFilePath);
796
+ const fileContent = await flexibleImportAlgorithm.$LT_ReadFileContent(relativeFilePath);
790
797
  const fileLines = fileContent.split("\n");
791
798
  const startLine = tag.line;
792
799
  const endLine = tag.line + tag.fullMatch.split("\n").length - 1;
@@ -1042,20 +1049,20 @@ function deepMergeTranslations(target, source) {
1042
1049
  async function $LT_WriteToCollections({ config, collections, logger, clean }) {
1043
1050
  await config.collect.collector.preWrite(clean);
1044
1051
  const changedCollections = [];
1045
- for (let namespace of Object.keys(collections)) {
1046
- if (!namespace) {
1052
+ for (let collectionName of Object.keys(collections)) {
1053
+ if (!collectionName) {
1047
1054
  continue;
1048
1055
  }
1049
- const filePath = await config.collect.collector.resolveCollectionFilePath(namespace);
1056
+ const filePath = await config.collect.collector.resolveCollectionFilePath(collectionName);
1050
1057
  let originalJSON = {};
1051
1058
  try {
1052
- originalJSON = await namespaceCollector.$LT_ReadJSON(filePath);
1059
+ originalJSON = await flexibleImportAlgorithm.$LT_ReadJSON(filePath);
1053
1060
  } catch (e) {
1054
- await config.collect.collector.onMissingCollection(namespace);
1061
+ await config.collect.collector.onMissingCollection(collectionName);
1055
1062
  }
1056
- if (deepMergeTranslations(originalJSON, collections[namespace])) {
1057
- changedCollections.push(namespace);
1058
- await namespaceCollector.$LT_WriteJSON(filePath, originalJSON);
1063
+ if (deepMergeTranslations(originalJSON, collections[collectionName])) {
1064
+ changedCollections.push(collectionName);
1065
+ await flexibleImportAlgorithm.$LT_WriteJSON(filePath, originalJSON);
1059
1066
  }
1060
1067
  }
1061
1068
  await config.collect.collector.postWrite(changedCollections);
@@ -1089,32 +1096,22 @@ async function $LT_CollectCandidateFilesWithTags(props) {
1089
1096
  return candidates;
1090
1097
  }
1091
1098
  async function $LT_WriteAsExportFile({ config, logger, files }) {
1092
- const packageJson = await namespaceCollector.$LT_ReadJSON(path.resolve(process.cwd(), "package.json"));
1099
+ const packageJson = await flexibleImportAlgorithm.$LT_ReadJSON(path.resolve(process.cwd(), "package.json"));
1093
1100
  if (!packageJson) {
1094
1101
  throw new Error("package.json not found");
1095
1102
  }
1096
- const langTagFiles = {};
1097
- for (const file of files) {
1098
- langTagFiles[file.relativeFilePath] = {
1099
- matches: file.tags.map((tag) => {
1100
- let T = config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text;
1101
- let C = config.translationArgPosition === 1 ? tag.parameter2Text : tag.parameter1Text;
1102
- if (!T) T = "{}";
1103
- if (!C) C = "{}";
1104
- return {
1105
- translations: T,
1106
- config: C,
1107
- variableName: tag.variableName
1108
- };
1109
- })
1110
- };
1111
- }
1112
- const data = {
1113
- language: config.baseLanguageCode,
1114
- packageName: packageJson.name || "",
1115
- files: langTagFiles
1103
+ const exportData = {
1104
+ baseLanguageCode: config.baseLanguageCode,
1105
+ files: files.map(({ relativeFilePath, tags }) => ({
1106
+ relativeFilePath,
1107
+ tags: tags.map((tag) => ({
1108
+ variableName: tag.variableName,
1109
+ config: tag.parameterConfig,
1110
+ translations: tag.parameterTranslations
1111
+ }))
1112
+ }))
1116
1113
  };
1117
- await namespaceCollector.$LT_WriteJSON(EXPORTS_FILE_NAME, data);
1114
+ await promises.writeFile(EXPORTS_FILE_NAME, JSON.stringify(exportData), "utf-8");
1118
1115
  logger.success(`Written {file}`, { file: EXPORTS_FILE_NAME });
1119
1116
  }
1120
1117
  async function $LT_GroupTagsToCollections({ logger, files, config }) {
@@ -1485,115 +1482,189 @@ async function $LT_CMD_InitConfig() {
1485
1482
  logger.error(error?.message);
1486
1483
  }
1487
1484
  }
1488
- function $LT_CollectNodeModulesExportFilePaths(logger) {
1485
+ async function $LT_CollectExportFiles(logger) {
1489
1486
  const nodeModulesPath = path$1.join(process$1.cwd(), "node_modules");
1490
1487
  if (!fs.existsSync(nodeModulesPath)) {
1491
1488
  logger.error('"node_modules" directory not found');
1492
1489
  return [];
1493
1490
  }
1494
- function findExportJson(dir, depth = 0, maxDepth = 3) {
1495
- if (depth > maxDepth) return [];
1496
- let results = [];
1497
- try {
1498
- const files = fs.readdirSync(dir);
1499
- for (const file of files) {
1500
- const fullPath = path$1.join(dir, file);
1501
- const stat = fs.statSync(fullPath);
1502
- if (stat.isDirectory()) {
1503
- results = results.concat(findExportJson(fullPath, depth + 1, maxDepth));
1504
- } else if (file === EXPORTS_FILE_NAME) {
1505
- results.push(fullPath);
1506
- }
1491
+ const results = [];
1492
+ try {
1493
+ const exportFiles = await globby.globby([
1494
+ `node_modules/**/${EXPORTS_FILE_NAME}`
1495
+ ], {
1496
+ cwd: process$1.cwd(),
1497
+ onlyFiles: true,
1498
+ ignore: ["**/node_modules/**/node_modules/**"],
1499
+ deep: 4
1500
+ });
1501
+ for (const exportFile of exportFiles) {
1502
+ const fullExportPath = path$1.resolve(exportFile);
1503
+ const packageJsonPath = findPackageJsonForExport(fullExportPath, nodeModulesPath);
1504
+ if (packageJsonPath) {
1505
+ results.push({
1506
+ exportPath: fullExportPath,
1507
+ packageJsonPath
1508
+ });
1509
+ } else {
1510
+ logger.warn("Found export file but could not match package.json: {exportPath}", {
1511
+ exportPath: fullExportPath
1512
+ });
1507
1513
  }
1508
- } catch (error) {
1509
- logger.error('Error reading directory "{dir}": {error}', {
1510
- dir,
1511
- error: String(error)
1512
- });
1513
1514
  }
1514
- return results;
1515
+ logger.debug("Found {count} export files with matching package.json in node_modules", {
1516
+ count: results.length
1517
+ });
1518
+ } catch (error) {
1519
+ logger.error("Error scanning node_modules with globby: {error}", {
1520
+ error: String(error)
1521
+ });
1515
1522
  }
1516
- return findExportJson(nodeModulesPath);
1523
+ return results;
1517
1524
  }
1518
- async function $LT_ImportLibraries(config, logger) {
1519
- const files = $LT_CollectNodeModulesExportFilePaths(logger);
1520
- const generationFiles = {};
1521
- for (const filePath of files) {
1522
- const exportData = await namespaceCollector.$LT_ReadJSON(filePath);
1523
- for (let langTagFilePath in exportData.files) {
1524
- const fileGenerationData = {};
1525
- const matches = exportData.files[langTagFilePath].matches;
1526
- for (let match of matches) {
1527
- let parsedTranslations = typeof match.translations === "string" ? JSON5.parse(match.translations) : match.translations;
1528
- let parsedConfig = typeof match.config === "string" ? JSON5.parse(match.config) : match.config === void 0 ? {} : match.config;
1529
- let file = langTagFilePath;
1530
- let exportName = match.variableName || "";
1531
- config.import.onImport({
1532
- packageName: exportData.packageName,
1533
- importedRelativePath: langTagFilePath,
1534
- originalExportName: match.variableName,
1535
- translations: parsedTranslations,
1536
- config: parsedConfig,
1537
- fileGenerationData
1538
- }, {
1539
- setFile: (f) => {
1540
- file = f;
1541
- },
1542
- setExportName: (name) => {
1543
- exportName = name;
1544
- },
1545
- setConfig: (newConfig) => {
1546
- parsedConfig = newConfig;
1547
- }
1548
- });
1549
- if (!file || !exportName) {
1550
- throw new Error(`[lang-tag] onImport did not set fileName or exportName for package: ${exportData.packageName}, file: '${file}' (original: '${langTagFilePath}'), exportName: '${exportName}' (original: ${match.variableName})`);
1551
- }
1552
- let exports2 = generationFiles[file];
1553
- if (!exports2) {
1554
- exports2 = {};
1555
- generationFiles[file] = exports2;
1556
- }
1557
- const param1 = config.translationArgPosition === 1 ? parsedTranslations : parsedConfig;
1558
- const param2 = config.translationArgPosition === 1 ? parsedConfig : parsedTranslations;
1559
- exports2[exportName] = `${config.tagName}(${JSON5.stringify(param1, void 0, 4)}, ${JSON5.stringify(param2, void 0, 4)})`;
1525
+ function findPackageJsonForExport(exportPath, nodeModulesPath) {
1526
+ const relativePath = path$1.relative(nodeModulesPath, exportPath);
1527
+ const pathParts = relativePath.split(path$1.sep);
1528
+ if (pathParts.length < 2) {
1529
+ return null;
1530
+ }
1531
+ if (pathParts[0].startsWith("@")) {
1532
+ if (pathParts.length >= 3) {
1533
+ const packageDir = path$1.join(nodeModulesPath, pathParts[0], pathParts[1]);
1534
+ const packageJsonPath = path$1.join(packageDir, "package.json");
1535
+ if (fs.existsSync(packageJsonPath)) {
1536
+ return packageJsonPath;
1560
1537
  }
1561
1538
  }
1539
+ } else {
1540
+ const packageDir = path$1.join(nodeModulesPath, pathParts[0]);
1541
+ const packageJsonPath = path$1.join(packageDir, "package.json");
1542
+ if (fs.existsSync(packageJsonPath)) {
1543
+ return packageJsonPath;
1544
+ }
1562
1545
  }
1563
- for (let fileName of Object.keys(generationFiles)) {
1546
+ return null;
1547
+ }
1548
+ const __filename$1 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
1549
+ const __dirname$1 = path.dirname(__filename$1);
1550
+ const templatePath = path.join(__dirname$1, "templates", "import", "imported-tag.mustache");
1551
+ const template = fs.readFileSync(templatePath, "utf-8");
1552
+ function renderTemplate$1(data) {
1553
+ return mustache.render(template, data, {}, { escape: (text) => text });
1554
+ }
1555
+ async function generateImportFiles(config, logger, importManager) {
1556
+ const importedFiles = importManager.getImportedFiles();
1557
+ for (const importedFile of importedFiles) {
1564
1558
  const filePath = path$1.resolve(
1565
1559
  process__namespace.cwd(),
1566
1560
  config.import.dir,
1567
- fileName
1561
+ importedFile.pathRelativeToImportDir
1568
1562
  );
1569
- const exports2 = Object.entries(generationFiles[fileName]).map(([name, tag]) => {
1570
- return `export const ${name} = ${tag};`;
1571
- }).join("\n\n");
1572
- const content = `${config.import.tagImportPath}
1573
-
1574
- ${exports2}`;
1575
- await namespaceCollector.$LT_EnsureDirectoryExists(path$1.dirname(filePath));
1563
+ const processedExports = importedFile.tags.map((tag) => {
1564
+ const parameter1 = config.translationArgPosition === 1 ? tag.translations : tag.config;
1565
+ const parameter2 = config.translationArgPosition === 1 ? tag.config : tag.translations;
1566
+ const hasParameter2 = parameter2 !== null && parameter2 !== void 0 && (typeof parameter2 !== "object" || Object.keys(parameter2).length > 0);
1567
+ return {
1568
+ name: tag.variableName,
1569
+ parameter1: JSON5.stringify(parameter1, void 0, 4),
1570
+ parameter2: hasParameter2 ? JSON5.stringify(parameter2, void 0, 4) : null,
1571
+ hasParameter2,
1572
+ config: {
1573
+ tagName: config.tagName
1574
+ }
1575
+ };
1576
+ });
1577
+ const templateData = {
1578
+ tagImportPath: config.import.tagImportPath,
1579
+ exports: processedExports
1580
+ };
1581
+ const content = renderTemplate$1(templateData);
1582
+ await flexibleImportAlgorithm.$LT_EnsureDirectoryExists(path.dirname(filePath));
1576
1583
  await promises.writeFile(filePath, content, "utf-8");
1577
- logger.success('Imported node_modules file: "{fileName}"', { fileName });
1584
+ logger.success('Created tag file: "{file}"', { file: importedFile.pathRelativeToImportDir });
1585
+ }
1586
+ }
1587
+ class ImportManager {
1588
+ importedFiles = [];
1589
+ constructor() {
1590
+ this.importedFiles = [];
1591
+ }
1592
+ importTag(pathRelativeToImportDir, tag) {
1593
+ if (!pathRelativeToImportDir) {
1594
+ throw new Error(`pathRelativeToImportDir required, got: ${pathRelativeToImportDir}`);
1595
+ }
1596
+ if (!tag?.variableName) {
1597
+ throw new Error(`tag.variableName required, got: ${tag?.variableName}`);
1598
+ }
1599
+ if (!this.isValidJavaScriptIdentifier(tag.variableName)) {
1600
+ throw new Error(`Invalid JavaScript identifier: "${tag.variableName}". Variable names must start with a letter, underscore, or dollar sign, and contain only letters, digits, underscores, and dollar signs.`);
1601
+ }
1602
+ if (tag.translations == null) {
1603
+ throw new Error(`tag.translations required`);
1604
+ }
1605
+ let importedFile = this.importedFiles.find(
1606
+ (file) => file.pathRelativeToImportDir === pathRelativeToImportDir
1607
+ );
1608
+ if (importedFile) {
1609
+ const duplicateTag = importedFile.tags.find(
1610
+ (existingTag) => existingTag.variableName === tag.variableName
1611
+ );
1612
+ if (duplicateTag) {
1613
+ throw new Error(`Duplicate variable name "${tag.variableName}" in file "${pathRelativeToImportDir}". Variable names must be unique within the same file.`);
1614
+ }
1615
+ }
1616
+ if (!importedFile) {
1617
+ importedFile = { pathRelativeToImportDir, tags: [] };
1618
+ this.importedFiles.push(importedFile);
1619
+ }
1620
+ importedFile.tags.push(tag);
1621
+ }
1622
+ getImportedFiles() {
1623
+ return [...this.importedFiles];
1624
+ }
1625
+ getImportedFilesCount() {
1626
+ return this.importedFiles.length;
1578
1627
  }
1628
+ hasImportedFiles() {
1629
+ return this.importedFiles.length > 0;
1630
+ }
1631
+ isValidJavaScriptIdentifier(name) {
1632
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
1633
+ }
1634
+ }
1635
+ async function $LT_ImportLibraries(config, logger) {
1636
+ const exportFiles = await $LT_CollectExportFiles(logger);
1637
+ const importManager = new ImportManager();
1638
+ let exports2 = [];
1639
+ for (const { exportPath, packageJsonPath } of exportFiles) {
1640
+ const exportData = await flexibleImportAlgorithm.$LT_ReadJSON(exportPath);
1641
+ const packageJSON = await flexibleImportAlgorithm.$LT_ReadJSON(packageJsonPath);
1642
+ exports2.push({ packageJSON, exportData });
1643
+ }
1644
+ config.import.onImport({ exports: exports2, importManager, logger, langTagConfig: config });
1645
+ if (!importManager.hasImportedFiles()) {
1646
+ logger.warn("No tags were imported from any library files");
1647
+ return;
1648
+ }
1649
+ await generateImportFiles(config, logger, importManager);
1579
1650
  if (config.import.onImportFinish) config.import.onImportFinish();
1580
1651
  }
1581
1652
  async function $LT_ImportTranslations() {
1582
1653
  const { config, logger } = await $LT_GetCommandEssentials();
1583
- await namespaceCollector.$LT_EnsureDirectoryExists(config.import.dir);
1654
+ await flexibleImportAlgorithm.$LT_EnsureDirectoryExists(config.import.dir);
1584
1655
  logger.info("Importing translations from libraries...");
1585
1656
  await $LT_ImportLibraries(config, logger);
1586
1657
  logger.success("Successfully imported translations from libraries.");
1587
1658
  }
1588
- function renderTemplate(template, data) {
1589
- return mustache.render(template, data, {}, { escape: (text) => text });
1659
+ function renderTemplate(template2, data) {
1660
+ return mustache.render(template2, data, {}, { escape: (text) => text });
1590
1661
  }
1591
1662
  function loadTemplate(templateName) {
1592
- const __filename = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
1593
- const __dirname = path.dirname(__filename);
1594
- const templatePath = path.join(__dirname, "template", `${templateName}.mustache`);
1663
+ const __filename2 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename2).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
1664
+ const __dirname = path.dirname(__filename2);
1665
+ const templatePath2 = path.join(__dirname, "templates", "tag", `${templateName}.mustache`);
1595
1666
  try {
1596
- return fs.readFileSync(templatePath, "utf-8");
1667
+ return fs.readFileSync(templatePath2, "utf-8");
1597
1668
  } catch (error) {
1598
1669
  throw new Error(`Failed to load template ${templateName}: ${error}`);
1599
1670
  }
@@ -1681,7 +1752,7 @@ async function $LT_CMD_InitTagFile(options = {}) {
1681
1752
  return;
1682
1753
  }
1683
1754
  try {
1684
- await namespaceCollector.$LT_WriteFileWithDirs(outputPath, renderedContent);
1755
+ await flexibleImportAlgorithm.$LT_WriteFileWithDirs(outputPath, renderedContent);
1685
1756
  logger.success("Lang-tag file created successfully: {outputPath}", { outputPath });
1686
1757
  logger.info("Next steps:");
1687
1758
  logger.info("1. Import the {tagName} function in your files:", { tagName: renderOptions.tagName });