@lang-tag/cli 0.17.0 → 0.18.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
@@ -15,6 +15,10 @@ const namespaceCollector = require("./chunks/namespace-collector.cjs");
15
15
  const micromatch = require("micromatch");
16
16
  const acorn = require("acorn");
17
17
  const mustache = require("mustache");
18
+ const checkbox = require("@inquirer/checkbox");
19
+ const confirm = require("@inquirer/confirm");
20
+ const input = require("@inquirer/input");
21
+ const select = require("@inquirer/select");
18
22
  const chokidar = require("chokidar");
19
23
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
20
24
  function _interopNamespaceDefault(e) {
@@ -1545,7 +1549,7 @@ const templatePath = path.join(
1545
1549
  "imported-tag.mustache"
1546
1550
  );
1547
1551
  const template = fs.readFileSync(templatePath, "utf-8");
1548
- function renderTemplate$1(data) {
1552
+ function renderTemplate$2(data) {
1549
1553
  return mustache.render(template, data, {}, { escape: (text) => text });
1550
1554
  }
1551
1555
  async function generateImportFiles(config, logger, importManager) {
@@ -1574,7 +1578,7 @@ async function generateImportFiles(config, logger, importManager) {
1574
1578
  tagImportPath: config.import.tagImportPath,
1575
1579
  exports: processedExports
1576
1580
  };
1577
- const content = renderTemplate$1(templateData);
1581
+ const content = renderTemplate$2(templateData);
1578
1582
  await $LT_EnsureDirectoryExists(path.dirname(filePath));
1579
1583
  await promises.writeFile(filePath, content, "utf-8");
1580
1584
  logger.success('Created tag file: "{file}"', {
@@ -1667,6 +1671,294 @@ async function $LT_ImportTranslations() {
1667
1671
  await $LT_ImportLibraries(config, logger);
1668
1672
  logger.success("Successfully imported translations from libraries.");
1669
1673
  }
1674
+ function parseGitignore(cwd) {
1675
+ const gitignorePath = path.join(cwd, ".gitignore");
1676
+ try {
1677
+ const content = fs.readFileSync(gitignorePath, "utf-8");
1678
+ return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter((line) => !line.startsWith("!")).map((line) => {
1679
+ if (line.endsWith("/")) line = line.slice(0, -1);
1680
+ if (line.startsWith("/")) line = line.slice(1);
1681
+ return line;
1682
+ }).filter((line) => {
1683
+ if (line.startsWith("*.")) return false;
1684
+ if (line.includes(".") && line.split(".")[1].length <= 4)
1685
+ return false;
1686
+ return true;
1687
+ });
1688
+ } catch {
1689
+ return [];
1690
+ }
1691
+ }
1692
+ function isIgnored(entry, ignorePatterns) {
1693
+ if (entry.startsWith(".")) {
1694
+ return true;
1695
+ }
1696
+ if (ignorePatterns.length === 0) {
1697
+ return false;
1698
+ }
1699
+ return micromatch.isMatch(entry, ignorePatterns);
1700
+ }
1701
+ function detectProjectDirectories() {
1702
+ const cwd = process.cwd();
1703
+ const ignorePatterns = parseGitignore(cwd);
1704
+ const detectedFolders = [];
1705
+ try {
1706
+ const entries = fs.readdirSync(cwd);
1707
+ for (const entry of entries) {
1708
+ if (isIgnored(entry, ignorePatterns)) {
1709
+ continue;
1710
+ }
1711
+ try {
1712
+ const fullPath = path.join(cwd, entry);
1713
+ const stat = fs.statSync(fullPath);
1714
+ if (stat.isDirectory()) {
1715
+ detectedFolders.push(entry);
1716
+ }
1717
+ } catch {
1718
+ }
1719
+ }
1720
+ } catch {
1721
+ return ["src", "app"];
1722
+ }
1723
+ return detectedFolders.length > 0 ? detectedFolders.sort() : ["src", "app"];
1724
+ }
1725
+ async function askProjectSetupQuestions() {
1726
+ const projectType = await select({
1727
+ message: "Is this a project or a library?",
1728
+ choices: [
1729
+ {
1730
+ name: "Project (application that consumes translations)",
1731
+ value: "project",
1732
+ description: "For applications that use translations"
1733
+ },
1734
+ {
1735
+ name: "Library (exports translations for other projects)",
1736
+ value: "library",
1737
+ description: "For packages that provide translations"
1738
+ }
1739
+ ]
1740
+ });
1741
+ const tagName = await input({
1742
+ message: "What name would you like for your translation tag function?",
1743
+ default: "lang"
1744
+ });
1745
+ let collectorType = "namespace";
1746
+ let namespaceOptions;
1747
+ let localesDirectory = "locales";
1748
+ const modifyNamespaceOptions = false;
1749
+ if (projectType === "project") {
1750
+ collectorType = await select({
1751
+ message: "How would you like to collect translations?",
1752
+ choices: [
1753
+ {
1754
+ name: "Namespace (organized by modules/features)",
1755
+ value: "namespace",
1756
+ description: "Organized structure with namespaces"
1757
+ },
1758
+ {
1759
+ name: "Dictionary (flat structure, all translations in one file)",
1760
+ value: "dictionary",
1761
+ description: "Simple flat dictionary structure"
1762
+ }
1763
+ ]
1764
+ });
1765
+ localesDirectory = await input({
1766
+ message: "Where should the translation files be stored?",
1767
+ default: "public/locales"
1768
+ });
1769
+ }
1770
+ const defaultNamespace = await input({
1771
+ message: "Default namespace for tags without explicit namespace:",
1772
+ default: "common"
1773
+ });
1774
+ namespaceOptions = {
1775
+ modifyNamespaceOptions,
1776
+ defaultNamespace
1777
+ };
1778
+ const enableConfigGeneration = await confirm({
1779
+ message: "Do you want to script config generation for tags?",
1780
+ default: projectType === "project"
1781
+ });
1782
+ let configGeneration = {
1783
+ enabled: enableConfigGeneration
1784
+ };
1785
+ if (enableConfigGeneration) {
1786
+ const algorithmChoice = await select({
1787
+ message: "Which config generation approach would you like?",
1788
+ choices: [
1789
+ {
1790
+ name: "Path-based (automatic based on file structure)",
1791
+ value: "path-based",
1792
+ description: "Generates namespace and path from file location"
1793
+ },
1794
+ {
1795
+ name: "Custom (write your own algorithm)",
1796
+ value: "custom",
1797
+ description: "Implement custom config generation logic"
1798
+ }
1799
+ ]
1800
+ });
1801
+ const keepVariables = await confirm({
1802
+ message: "Add a keeper mechanism that locks parts of the configuration from being overwritten?",
1803
+ default: true
1804
+ });
1805
+ configGeneration = {
1806
+ enabled: true,
1807
+ useAlgorithm: algorithmChoice,
1808
+ keepVariables
1809
+ };
1810
+ }
1811
+ const importLibraries = await confirm({
1812
+ message: "Do you plan to import translation tags from external libraries?",
1813
+ default: projectType === "project"
1814
+ });
1815
+ const interfereWithCollection = await confirm({
1816
+ message: "Do you want to interfere with collection mechanisms (conflict resolution, collection finish)?",
1817
+ default: false
1818
+ });
1819
+ const detectedDirectories = detectProjectDirectories();
1820
+ const includeDirectories = await checkbox({
1821
+ message: "Select directories where lang tags will be used (you can add more later):",
1822
+ choices: detectedDirectories.map((directory) => ({
1823
+ name: directory,
1824
+ value: directory,
1825
+ checked: directory === "src" || detectedDirectories.length === 1
1826
+ })),
1827
+ required: true
1828
+ });
1829
+ const baseLanguageCode = await input({
1830
+ message: "Base language code:",
1831
+ default: "en",
1832
+ validate: (value) => {
1833
+ if (!value || value.length < 2) {
1834
+ return "Please enter a valid language code (e.g., en, pl, fr, de, es)";
1835
+ }
1836
+ return true;
1837
+ }
1838
+ });
1839
+ const addCommentGuides = await confirm({
1840
+ message: "Would you like guides in comments?",
1841
+ default: true
1842
+ });
1843
+ return {
1844
+ projectType,
1845
+ tagName,
1846
+ collectorType,
1847
+ namespaceOptions,
1848
+ localesDirectory,
1849
+ configGeneration,
1850
+ importLibraries,
1851
+ interfereWithCollection,
1852
+ includeDirectories,
1853
+ baseLanguageCode,
1854
+ addCommentGuides
1855
+ };
1856
+ }
1857
+ function renderTemplate$1(template2, data, partials) {
1858
+ return mustache.render(template2, data, partials, {
1859
+ escape: (text) => text
1860
+ });
1861
+ }
1862
+ function loadTemplateFile(filename, required = true) {
1863
+ 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);
1864
+ const __dirname = path.dirname(__filename2);
1865
+ let templatePath2 = path.join(__dirname, "templates", "config", filename);
1866
+ try {
1867
+ return fs.readFileSync(templatePath2, "utf-8");
1868
+ } catch {
1869
+ templatePath2 = path.join(
1870
+ __dirname,
1871
+ "..",
1872
+ "..",
1873
+ "templates",
1874
+ "config",
1875
+ filename
1876
+ );
1877
+ try {
1878
+ return fs.readFileSync(templatePath2, "utf-8");
1879
+ } catch (error) {
1880
+ if (required) {
1881
+ throw new Error(
1882
+ `Failed to load template "${filename}": ${error}`
1883
+ );
1884
+ }
1885
+ return null;
1886
+ }
1887
+ }
1888
+ }
1889
+ function loadTemplate$1() {
1890
+ return loadTemplateFile("config.mustache", true);
1891
+ }
1892
+ function loadPartials() {
1893
+ const partials = {};
1894
+ const generationAlgorithm = loadTemplateFile(
1895
+ "generation-algorithm.mustache",
1896
+ false
1897
+ );
1898
+ if (generationAlgorithm) {
1899
+ partials["generation-algorithm"] = generationAlgorithm;
1900
+ }
1901
+ return partials;
1902
+ }
1903
+ function buildIncludesPattern(directories) {
1904
+ return directories.map((directory) => `'${directory}/**/*.{js,ts,jsx,tsx}'`).join(", ");
1905
+ }
1906
+ function buildExcludesPattern() {
1907
+ const excludes = [
1908
+ "node_modules",
1909
+ "dist",
1910
+ "build",
1911
+ "**/*.test.ts",
1912
+ "**/*.spec.ts"
1913
+ ];
1914
+ return excludes.map((e) => `'${e}'`).join(", ");
1915
+ }
1916
+ function prepareTemplateData$1(options) {
1917
+ const { answers, moduleSystem } = options;
1918
+ const needsPathBasedImport = answers.configGeneration.enabled && answers.configGeneration.useAlgorithm === "path-based";
1919
+ const hasConfigGeneration = answers.configGeneration.enabled;
1920
+ const usePathBased = hasConfigGeneration && answers.configGeneration.useAlgorithm === "path-based";
1921
+ const useCustom = hasConfigGeneration && answers.configGeneration.useAlgorithm === "custom";
1922
+ const needsTagName = answers.tagName !== "lang";
1923
+ const useKeeper = answers.configGeneration.keepVariables || false;
1924
+ const isDictionaryCollector = answers.collectorType === "dictionary";
1925
+ const isModifiedNamespaceCollector = answers.collectorType === "namespace" && !!answers.namespaceOptions?.modifyNamespaceOptions;
1926
+ const importLibraries = answers.importLibraries;
1927
+ const defaultNamespace = answers.namespaceOptions?.defaultNamespace || "common";
1928
+ const isDefaultNamespace = defaultNamespace === "common";
1929
+ const hasCollectContent = isDictionaryCollector || isModifiedNamespaceCollector || answers.interfereWithCollection || !isDefaultNamespace;
1930
+ const needsAlgorithms = needsPathBasedImport || useKeeper || isDictionaryCollector || isModifiedNamespaceCollector || importLibraries;
1931
+ return {
1932
+ isCJS: moduleSystem === "cjs",
1933
+ addComments: answers.addCommentGuides,
1934
+ needsAlgorithms,
1935
+ needsPathBasedImport,
1936
+ useKeeper,
1937
+ isDictionaryCollector,
1938
+ isModifiedNamespaceCollector,
1939
+ importLibraries,
1940
+ needsTagName,
1941
+ tagName: answers.tagName,
1942
+ isLibrary: answers.projectType === "library",
1943
+ includes: buildIncludesPattern(answers.includeDirectories),
1944
+ excludes: buildExcludesPattern(),
1945
+ localesDirectory: answers.localesDirectory,
1946
+ baseLanguageCode: answers.baseLanguageCode,
1947
+ hasConfigGeneration,
1948
+ usePathBased,
1949
+ useCustom,
1950
+ defaultNamespace,
1951
+ isDefaultNamespace,
1952
+ interfereWithCollection: answers.interfereWithCollection,
1953
+ hasCollectContent
1954
+ };
1955
+ }
1956
+ function renderConfigTemplate(options) {
1957
+ const template2 = loadTemplate$1();
1958
+ const templateData = prepareTemplateData$1(options);
1959
+ const partials = loadPartials();
1960
+ return renderTemplate$1(template2, templateData, partials);
1961
+ }
1670
1962
  async function detectModuleSystem() {
1671
1963
  const packageJsonPath = path.join(process.cwd(), "package.json");
1672
1964
  if (!fs.existsSync(packageJsonPath)) {
@@ -1683,90 +1975,57 @@ async function detectModuleSystem() {
1683
1975
  return "cjs";
1684
1976
  }
1685
1977
  }
1686
- async function generateDefaultConfig() {
1687
- const moduleSystem = await detectModuleSystem();
1688
- const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
1689
- const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
1690
- return `${importStatement}
1691
-
1692
- const generationAlgorithm = pathBasedConfigGenerator({
1693
- ignoreIncludesRootDirectories: true,
1694
- removeBracketedDirectories: true,
1695
- namespaceCase: 'kebab',
1696
- pathCase: 'camel',
1697
- clearOnDefaultNamespace: true,
1698
- ignoreDirectories: ['core', 'utils', 'helpers'],
1699
- // Advanced: Use pathRules for hierarchical transformations with ignore and rename
1700
- // pathRules: {
1701
- // app: {
1702
- // dashboard: {
1703
- // _: false, // ignore "dashboard" but continue with nested rules
1704
- // modules: false // also ignore "modules"
1705
- // },
1706
- // admin: {
1707
- // '>': 'management', // rename "admin" to "management"
1708
- // users: false // ignore "users",
1709
- // ui: {
1710
- // '>>': { // 'redirect' - ignore everything, jump to 'ui' namespace and prefix all paths with 'admin'
1711
- // namespace: 'ui',
1712
- // pathPrefix: 'admin'
1713
- // }
1714
- // }
1715
- // }
1716
- // }
1717
- // }
1718
- });
1719
- const keeper = configKeeper({ propertyName: 'keep' });
1720
-
1721
- /** @type {import('@lang-tag/cli/type').LangTagCLIConfig} */
1722
- const config = {
1723
- tagName: 'lang',
1724
- isLibrary: false,
1725
- includes: ['src/**/*.{js,ts,jsx,tsx}'],
1726
- excludes: ['node_modules', 'dist', 'build', '**/*.test.ts'],
1727
- localesDirectory: 'public/locales',
1728
- baseLanguageCode: 'en',
1729
- onConfigGeneration: async event => {
1730
- // We do not modify imported configurations
1731
- if (event.isImportedLibrary) return;
1732
-
1733
- if (event.config?.keep === 'both') return;
1734
-
1735
- await generationAlgorithm(event);
1736
-
1737
- await keeper(event);
1738
- },
1739
- collect: {
1740
- defaultNamespace: 'common',
1741
- onConflictResolution: async event => {
1742
- await event.logger.conflict(event.conflict, true);
1743
- // By default, continue processing even if conflicts occur
1744
- // Call event.exit(); to terminate the process upon the first conflict
1745
- },
1746
- onCollectFinish: event => {
1747
- if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
1748
- }
1749
- },
1750
- translationArgPosition: 1,
1751
- debug: false,
1752
- };
1753
-
1754
- ${exportStatement}`;
1755
- }
1756
1978
  async function $LT_CMD_InitConfig() {
1757
1979
  const logger = $LT_CreateDefaultLogger();
1758
1980
  if (fs.existsSync(CONFIG_FILE_NAME)) {
1759
- logger.success(
1760
- "Configuration file already exists. Please remove the existing configuration file before creating a new default one"
1981
+ logger.error(
1982
+ "Configuration file already exists. Please remove the existing configuration file before creating a new one"
1761
1983
  );
1762
1984
  return;
1763
1985
  }
1986
+ console.log("");
1987
+ logger.info("Welcome to Lang Tag CLI Setup!");
1988
+ console.log("");
1764
1989
  try {
1765
- const configContent = await generateDefaultConfig();
1990
+ const answers = await askProjectSetupQuestions();
1991
+ const moduleSystem = await detectModuleSystem();
1992
+ const configContent = renderConfigTemplate({
1993
+ answers,
1994
+ moduleSystem
1995
+ });
1766
1996
  await promises.writeFile(CONFIG_FILE_NAME, configContent, "utf-8");
1767
- logger.success("Configuration file created successfully");
1997
+ logger.success("Configuration file created successfully!");
1998
+ logger.success("Created {configFile}", {
1999
+ configFile: CONFIG_FILE_NAME
2000
+ });
2001
+ logger.info("Next steps:");
2002
+ logger.info(" 1. Review and customize {configFile}", {
2003
+ configFile: CONFIG_FILE_NAME
2004
+ });
2005
+ logger.info(
2006
+ " 2. Since you have installed all basic libraries (React, TypeScript, etc.)"
2007
+ );
2008
+ logger.info(
2009
+ " and the initialized basic tag is based on what you use in your project,"
2010
+ );
2011
+ logger.info(
2012
+ ' we recommend using "npx lang-tag init-tag" to generate an initial version of the tag'
2013
+ );
2014
+ logger.info(
2015
+ " 3. Use your tag in the project under the include directories"
2016
+ );
2017
+ logger.info(' 4. Run "npx lang-tag collect" to collect translations');
2018
+ if (answers.projectType === "project") {
2019
+ logger.info(" 5. Your translations will be in {dir}", {
2020
+ dir: answers.localesDirectory
2021
+ });
2022
+ }
1768
2023
  } catch (error) {
1769
- logger.error(error?.message);
2024
+ if (error.name === "ExitPromptError") {
2025
+ logger.warn("Setup cancelled");
2026
+ return;
2027
+ }
2028
+ logger.error(error?.message || "An error occurred during setup");
1770
2029
  }
1771
2030
  }
1772
2031
  async function readPackageJson() {
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { program } from "commander";
3
3
  import * as process$1 from "node:process";
4
4
  import process__default from "node:process";
5
- import fs, { readFileSync, existsSync } from "fs";
5
+ import fs, { readFileSync, existsSync, readdirSync, statSync } from "fs";
6
6
  import { globby } from "globby";
7
7
  import * as path from "path";
8
8
  import path__default, { dirname, resolve, join, sep } from "path";
@@ -15,6 +15,10 @@ import { N as NamespaceCollector } from "./chunks/namespace-collector.js";
15
15
  import micromatch from "micromatch";
16
16
  import * as acorn from "acorn";
17
17
  import mustache from "mustache";
18
+ import checkbox from "@inquirer/checkbox";
19
+ import confirm from "@inquirer/confirm";
20
+ import input from "@inquirer/input";
21
+ import select from "@inquirer/select";
18
22
  import chokidar from "chokidar";
19
23
  function $LT_FilterInvalidTags(tags, config, logger) {
20
24
  return tags.filter((tag) => {
@@ -1525,7 +1529,7 @@ const templatePath = join(
1525
1529
  "imported-tag.mustache"
1526
1530
  );
1527
1531
  const template = readFileSync(templatePath, "utf-8");
1528
- function renderTemplate$1(data) {
1532
+ function renderTemplate$2(data) {
1529
1533
  return mustache.render(template, data, {}, { escape: (text) => text });
1530
1534
  }
1531
1535
  async function generateImportFiles(config, logger, importManager) {
@@ -1554,7 +1558,7 @@ async function generateImportFiles(config, logger, importManager) {
1554
1558
  tagImportPath: config.import.tagImportPath,
1555
1559
  exports: processedExports
1556
1560
  };
1557
- const content = renderTemplate$1(templateData);
1561
+ const content = renderTemplate$2(templateData);
1558
1562
  await $LT_EnsureDirectoryExists(dirname(filePath));
1559
1563
  await writeFile(filePath, content, "utf-8");
1560
1564
  logger.success('Created tag file: "{file}"', {
@@ -1647,6 +1651,294 @@ async function $LT_ImportTranslations() {
1647
1651
  await $LT_ImportLibraries(config, logger);
1648
1652
  logger.success("Successfully imported translations from libraries.");
1649
1653
  }
1654
+ function parseGitignore(cwd) {
1655
+ const gitignorePath = join(cwd, ".gitignore");
1656
+ try {
1657
+ const content = readFileSync(gitignorePath, "utf-8");
1658
+ return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter((line) => !line.startsWith("!")).map((line) => {
1659
+ if (line.endsWith("/")) line = line.slice(0, -1);
1660
+ if (line.startsWith("/")) line = line.slice(1);
1661
+ return line;
1662
+ }).filter((line) => {
1663
+ if (line.startsWith("*.")) return false;
1664
+ if (line.includes(".") && line.split(".")[1].length <= 4)
1665
+ return false;
1666
+ return true;
1667
+ });
1668
+ } catch {
1669
+ return [];
1670
+ }
1671
+ }
1672
+ function isIgnored(entry, ignorePatterns) {
1673
+ if (entry.startsWith(".")) {
1674
+ return true;
1675
+ }
1676
+ if (ignorePatterns.length === 0) {
1677
+ return false;
1678
+ }
1679
+ return micromatch.isMatch(entry, ignorePatterns);
1680
+ }
1681
+ function detectProjectDirectories() {
1682
+ const cwd = process.cwd();
1683
+ const ignorePatterns = parseGitignore(cwd);
1684
+ const detectedFolders = [];
1685
+ try {
1686
+ const entries = readdirSync(cwd);
1687
+ for (const entry of entries) {
1688
+ if (isIgnored(entry, ignorePatterns)) {
1689
+ continue;
1690
+ }
1691
+ try {
1692
+ const fullPath = join(cwd, entry);
1693
+ const stat = statSync(fullPath);
1694
+ if (stat.isDirectory()) {
1695
+ detectedFolders.push(entry);
1696
+ }
1697
+ } catch {
1698
+ }
1699
+ }
1700
+ } catch {
1701
+ return ["src", "app"];
1702
+ }
1703
+ return detectedFolders.length > 0 ? detectedFolders.sort() : ["src", "app"];
1704
+ }
1705
+ async function askProjectSetupQuestions() {
1706
+ const projectType = await select({
1707
+ message: "Is this a project or a library?",
1708
+ choices: [
1709
+ {
1710
+ name: "Project (application that consumes translations)",
1711
+ value: "project",
1712
+ description: "For applications that use translations"
1713
+ },
1714
+ {
1715
+ name: "Library (exports translations for other projects)",
1716
+ value: "library",
1717
+ description: "For packages that provide translations"
1718
+ }
1719
+ ]
1720
+ });
1721
+ const tagName = await input({
1722
+ message: "What name would you like for your translation tag function?",
1723
+ default: "lang"
1724
+ });
1725
+ let collectorType = "namespace";
1726
+ let namespaceOptions;
1727
+ let localesDirectory = "locales";
1728
+ const modifyNamespaceOptions = false;
1729
+ if (projectType === "project") {
1730
+ collectorType = await select({
1731
+ message: "How would you like to collect translations?",
1732
+ choices: [
1733
+ {
1734
+ name: "Namespace (organized by modules/features)",
1735
+ value: "namespace",
1736
+ description: "Organized structure with namespaces"
1737
+ },
1738
+ {
1739
+ name: "Dictionary (flat structure, all translations in one file)",
1740
+ value: "dictionary",
1741
+ description: "Simple flat dictionary structure"
1742
+ }
1743
+ ]
1744
+ });
1745
+ localesDirectory = await input({
1746
+ message: "Where should the translation files be stored?",
1747
+ default: "public/locales"
1748
+ });
1749
+ }
1750
+ const defaultNamespace = await input({
1751
+ message: "Default namespace for tags without explicit namespace:",
1752
+ default: "common"
1753
+ });
1754
+ namespaceOptions = {
1755
+ modifyNamespaceOptions,
1756
+ defaultNamespace
1757
+ };
1758
+ const enableConfigGeneration = await confirm({
1759
+ message: "Do you want to script config generation for tags?",
1760
+ default: projectType === "project"
1761
+ });
1762
+ let configGeneration = {
1763
+ enabled: enableConfigGeneration
1764
+ };
1765
+ if (enableConfigGeneration) {
1766
+ const algorithmChoice = await select({
1767
+ message: "Which config generation approach would you like?",
1768
+ choices: [
1769
+ {
1770
+ name: "Path-based (automatic based on file structure)",
1771
+ value: "path-based",
1772
+ description: "Generates namespace and path from file location"
1773
+ },
1774
+ {
1775
+ name: "Custom (write your own algorithm)",
1776
+ value: "custom",
1777
+ description: "Implement custom config generation logic"
1778
+ }
1779
+ ]
1780
+ });
1781
+ const keepVariables = await confirm({
1782
+ message: "Add a keeper mechanism that locks parts of the configuration from being overwritten?",
1783
+ default: true
1784
+ });
1785
+ configGeneration = {
1786
+ enabled: true,
1787
+ useAlgorithm: algorithmChoice,
1788
+ keepVariables
1789
+ };
1790
+ }
1791
+ const importLibraries = await confirm({
1792
+ message: "Do you plan to import translation tags from external libraries?",
1793
+ default: projectType === "project"
1794
+ });
1795
+ const interfereWithCollection = await confirm({
1796
+ message: "Do you want to interfere with collection mechanisms (conflict resolution, collection finish)?",
1797
+ default: false
1798
+ });
1799
+ const detectedDirectories = detectProjectDirectories();
1800
+ const includeDirectories = await checkbox({
1801
+ message: "Select directories where lang tags will be used (you can add more later):",
1802
+ choices: detectedDirectories.map((directory) => ({
1803
+ name: directory,
1804
+ value: directory,
1805
+ checked: directory === "src" || detectedDirectories.length === 1
1806
+ })),
1807
+ required: true
1808
+ });
1809
+ const baseLanguageCode = await input({
1810
+ message: "Base language code:",
1811
+ default: "en",
1812
+ validate: (value) => {
1813
+ if (!value || value.length < 2) {
1814
+ return "Please enter a valid language code (e.g., en, pl, fr, de, es)";
1815
+ }
1816
+ return true;
1817
+ }
1818
+ });
1819
+ const addCommentGuides = await confirm({
1820
+ message: "Would you like guides in comments?",
1821
+ default: true
1822
+ });
1823
+ return {
1824
+ projectType,
1825
+ tagName,
1826
+ collectorType,
1827
+ namespaceOptions,
1828
+ localesDirectory,
1829
+ configGeneration,
1830
+ importLibraries,
1831
+ interfereWithCollection,
1832
+ includeDirectories,
1833
+ baseLanguageCode,
1834
+ addCommentGuides
1835
+ };
1836
+ }
1837
+ function renderTemplate$1(template2, data, partials) {
1838
+ return mustache.render(template2, data, partials, {
1839
+ escape: (text) => text
1840
+ });
1841
+ }
1842
+ function loadTemplateFile(filename, required = true) {
1843
+ const __filename2 = fileURLToPath(import.meta.url);
1844
+ const __dirname2 = dirname(__filename2);
1845
+ let templatePath2 = join(__dirname2, "templates", "config", filename);
1846
+ try {
1847
+ return readFileSync(templatePath2, "utf-8");
1848
+ } catch {
1849
+ templatePath2 = join(
1850
+ __dirname2,
1851
+ "..",
1852
+ "..",
1853
+ "templates",
1854
+ "config",
1855
+ filename
1856
+ );
1857
+ try {
1858
+ return readFileSync(templatePath2, "utf-8");
1859
+ } catch (error) {
1860
+ if (required) {
1861
+ throw new Error(
1862
+ `Failed to load template "${filename}": ${error}`
1863
+ );
1864
+ }
1865
+ return null;
1866
+ }
1867
+ }
1868
+ }
1869
+ function loadTemplate$1() {
1870
+ return loadTemplateFile("config.mustache", true);
1871
+ }
1872
+ function loadPartials() {
1873
+ const partials = {};
1874
+ const generationAlgorithm = loadTemplateFile(
1875
+ "generation-algorithm.mustache",
1876
+ false
1877
+ );
1878
+ if (generationAlgorithm) {
1879
+ partials["generation-algorithm"] = generationAlgorithm;
1880
+ }
1881
+ return partials;
1882
+ }
1883
+ function buildIncludesPattern(directories) {
1884
+ return directories.map((directory) => `'${directory}/**/*.{js,ts,jsx,tsx}'`).join(", ");
1885
+ }
1886
+ function buildExcludesPattern() {
1887
+ const excludes = [
1888
+ "node_modules",
1889
+ "dist",
1890
+ "build",
1891
+ "**/*.test.ts",
1892
+ "**/*.spec.ts"
1893
+ ];
1894
+ return excludes.map((e) => `'${e}'`).join(", ");
1895
+ }
1896
+ function prepareTemplateData$1(options) {
1897
+ const { answers, moduleSystem } = options;
1898
+ const needsPathBasedImport = answers.configGeneration.enabled && answers.configGeneration.useAlgorithm === "path-based";
1899
+ const hasConfigGeneration = answers.configGeneration.enabled;
1900
+ const usePathBased = hasConfigGeneration && answers.configGeneration.useAlgorithm === "path-based";
1901
+ const useCustom = hasConfigGeneration && answers.configGeneration.useAlgorithm === "custom";
1902
+ const needsTagName = answers.tagName !== "lang";
1903
+ const useKeeper = answers.configGeneration.keepVariables || false;
1904
+ const isDictionaryCollector = answers.collectorType === "dictionary";
1905
+ const isModifiedNamespaceCollector = answers.collectorType === "namespace" && !!answers.namespaceOptions?.modifyNamespaceOptions;
1906
+ const importLibraries = answers.importLibraries;
1907
+ const defaultNamespace = answers.namespaceOptions?.defaultNamespace || "common";
1908
+ const isDefaultNamespace = defaultNamespace === "common";
1909
+ const hasCollectContent = isDictionaryCollector || isModifiedNamespaceCollector || answers.interfereWithCollection || !isDefaultNamespace;
1910
+ const needsAlgorithms = needsPathBasedImport || useKeeper || isDictionaryCollector || isModifiedNamespaceCollector || importLibraries;
1911
+ return {
1912
+ isCJS: moduleSystem === "cjs",
1913
+ addComments: answers.addCommentGuides,
1914
+ needsAlgorithms,
1915
+ needsPathBasedImport,
1916
+ useKeeper,
1917
+ isDictionaryCollector,
1918
+ isModifiedNamespaceCollector,
1919
+ importLibraries,
1920
+ needsTagName,
1921
+ tagName: answers.tagName,
1922
+ isLibrary: answers.projectType === "library",
1923
+ includes: buildIncludesPattern(answers.includeDirectories),
1924
+ excludes: buildExcludesPattern(),
1925
+ localesDirectory: answers.localesDirectory,
1926
+ baseLanguageCode: answers.baseLanguageCode,
1927
+ hasConfigGeneration,
1928
+ usePathBased,
1929
+ useCustom,
1930
+ defaultNamespace,
1931
+ isDefaultNamespace,
1932
+ interfereWithCollection: answers.interfereWithCollection,
1933
+ hasCollectContent
1934
+ };
1935
+ }
1936
+ function renderConfigTemplate(options) {
1937
+ const template2 = loadTemplate$1();
1938
+ const templateData = prepareTemplateData$1(options);
1939
+ const partials = loadPartials();
1940
+ return renderTemplate$1(template2, templateData, partials);
1941
+ }
1650
1942
  async function detectModuleSystem() {
1651
1943
  const packageJsonPath = join(process.cwd(), "package.json");
1652
1944
  if (!existsSync(packageJsonPath)) {
@@ -1663,90 +1955,57 @@ async function detectModuleSystem() {
1663
1955
  return "cjs";
1664
1956
  }
1665
1957
  }
1666
- async function generateDefaultConfig() {
1667
- const moduleSystem = await detectModuleSystem();
1668
- const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
1669
- const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
1670
- return `${importStatement}
1671
-
1672
- const generationAlgorithm = pathBasedConfigGenerator({
1673
- ignoreIncludesRootDirectories: true,
1674
- removeBracketedDirectories: true,
1675
- namespaceCase: 'kebab',
1676
- pathCase: 'camel',
1677
- clearOnDefaultNamespace: true,
1678
- ignoreDirectories: ['core', 'utils', 'helpers'],
1679
- // Advanced: Use pathRules for hierarchical transformations with ignore and rename
1680
- // pathRules: {
1681
- // app: {
1682
- // dashboard: {
1683
- // _: false, // ignore "dashboard" but continue with nested rules
1684
- // modules: false // also ignore "modules"
1685
- // },
1686
- // admin: {
1687
- // '>': 'management', // rename "admin" to "management"
1688
- // users: false // ignore "users",
1689
- // ui: {
1690
- // '>>': { // 'redirect' - ignore everything, jump to 'ui' namespace and prefix all paths with 'admin'
1691
- // namespace: 'ui',
1692
- // pathPrefix: 'admin'
1693
- // }
1694
- // }
1695
- // }
1696
- // }
1697
- // }
1698
- });
1699
- const keeper = configKeeper({ propertyName: 'keep' });
1700
-
1701
- /** @type {import('@lang-tag/cli/type').LangTagCLIConfig} */
1702
- const config = {
1703
- tagName: 'lang',
1704
- isLibrary: false,
1705
- includes: ['src/**/*.{js,ts,jsx,tsx}'],
1706
- excludes: ['node_modules', 'dist', 'build', '**/*.test.ts'],
1707
- localesDirectory: 'public/locales',
1708
- baseLanguageCode: 'en',
1709
- onConfigGeneration: async event => {
1710
- // We do not modify imported configurations
1711
- if (event.isImportedLibrary) return;
1712
-
1713
- if (event.config?.keep === 'both') return;
1714
-
1715
- await generationAlgorithm(event);
1716
-
1717
- await keeper(event);
1718
- },
1719
- collect: {
1720
- defaultNamespace: 'common',
1721
- onConflictResolution: async event => {
1722
- await event.logger.conflict(event.conflict, true);
1723
- // By default, continue processing even if conflicts occur
1724
- // Call event.exit(); to terminate the process upon the first conflict
1725
- },
1726
- onCollectFinish: event => {
1727
- if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
1728
- }
1729
- },
1730
- translationArgPosition: 1,
1731
- debug: false,
1732
- };
1733
-
1734
- ${exportStatement}`;
1735
- }
1736
1958
  async function $LT_CMD_InitConfig() {
1737
1959
  const logger = $LT_CreateDefaultLogger();
1738
1960
  if (existsSync(CONFIG_FILE_NAME)) {
1739
- logger.success(
1740
- "Configuration file already exists. Please remove the existing configuration file before creating a new default one"
1961
+ logger.error(
1962
+ "Configuration file already exists. Please remove the existing configuration file before creating a new one"
1741
1963
  );
1742
1964
  return;
1743
1965
  }
1966
+ console.log("");
1967
+ logger.info("Welcome to Lang Tag CLI Setup!");
1968
+ console.log("");
1744
1969
  try {
1745
- const configContent = await generateDefaultConfig();
1970
+ const answers = await askProjectSetupQuestions();
1971
+ const moduleSystem = await detectModuleSystem();
1972
+ const configContent = renderConfigTemplate({
1973
+ answers,
1974
+ moduleSystem
1975
+ });
1746
1976
  await writeFile(CONFIG_FILE_NAME, configContent, "utf-8");
1747
- logger.success("Configuration file created successfully");
1977
+ logger.success("Configuration file created successfully!");
1978
+ logger.success("Created {configFile}", {
1979
+ configFile: CONFIG_FILE_NAME
1980
+ });
1981
+ logger.info("Next steps:");
1982
+ logger.info(" 1. Review and customize {configFile}", {
1983
+ configFile: CONFIG_FILE_NAME
1984
+ });
1985
+ logger.info(
1986
+ " 2. Since you have installed all basic libraries (React, TypeScript, etc.)"
1987
+ );
1988
+ logger.info(
1989
+ " and the initialized basic tag is based on what you use in your project,"
1990
+ );
1991
+ logger.info(
1992
+ ' we recommend using "npx lang-tag init-tag" to generate an initial version of the tag'
1993
+ );
1994
+ logger.info(
1995
+ " 3. Use your tag in the project under the include directories"
1996
+ );
1997
+ logger.info(' 4. Run "npx lang-tag collect" to collect translations');
1998
+ if (answers.projectType === "project") {
1999
+ logger.info(" 5. Your translations will be in {dir}", {
2000
+ dir: answers.localesDirectory
2001
+ });
2002
+ }
1748
2003
  } catch (error) {
1749
- logger.error(error?.message);
2004
+ if (error.name === "ExitPromptError") {
2005
+ logger.warn("Setup cancelled");
2006
+ return;
2007
+ }
2008
+ logger.error(error?.message || "An error occurred during setup");
1750
2009
  }
1751
2010
  }
1752
2011
  async function readPackageJson() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lang-tag/cli",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -33,6 +33,10 @@
33
33
  "lang-tag": "*"
34
34
  },
35
35
  "dependencies": {
36
+ "@inquirer/checkbox": "^4.3.0",
37
+ "@inquirer/confirm": "^5.1.19",
38
+ "@inquirer/prompts": "^7.9.0",
39
+ "@inquirer/select": "^4.4.0",
36
40
  "acorn": "^8.15.0",
37
41
  "case": "^1.6.3",
38
42
  "chokidar": "^4.0.3",
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Lang Tag CLI Configuration
3
+ *
4
+ * This file was generated. You can modify it to customize your translation workflow.
5
+ * Documentation: https://github.com/TheTonsOfCode/lang-tag-cli
6
+ */
7
+
8
+ {{#needsAlgorithms}}
9
+ {{#isCJS}}const{{/isCJS}}{{^isCJS}}import{{/isCJS}} {
10
+ {{#needsPathBasedImport}}
11
+ pathBasedConfigGenerator,
12
+ {{/needsPathBasedImport}}
13
+ {{#useKeeper}}
14
+ configKeeper,
15
+ {{/useKeeper}}
16
+ {{#isDictionaryCollector}}
17
+ DictionaryCollector,
18
+ {{/isDictionaryCollector}}
19
+ {{#isModifiedNamespaceCollector}}
20
+ NamespaceCollector,
21
+ {{/isModifiedNamespaceCollector}}
22
+ {{#importLibraries}}
23
+ flexibleImportAlgorithm,
24
+ {{/importLibraries}}
25
+ } {{#isCJS}}= require('@lang-tag/cli/algorithms');{{/isCJS}}{{^isCJS}}from '@lang-tag/cli/algorithms';{{/isCJS}}
26
+ {{/needsAlgorithms}}
27
+
28
+ {{#needsPathBasedImport}}
29
+ {{>generation-algorithm}}
30
+ {{/needsPathBasedImport}}
31
+ {{#useKeeper}}
32
+ {{#addComments}}
33
+ // Preserves tags marked with keep property. Example: {{tagName}}({ text: 'text'}, { namespace: 'custom', keep: 'namespace' })
34
+ {{/addComments}}
35
+ const keeper = configKeeper({ propertyName: 'keep' });
36
+
37
+ {{/useKeeper}}
38
+ /** @type {import('@lang-tag/cli/type').LangTagCLIConfig} */
39
+ const config = {
40
+ {{#needsTagName}}
41
+ tagName: '{{tagName}}',
42
+ {{/needsTagName}}
43
+ {{#isLibrary}}
44
+ isLibrary: true,
45
+ {{/isLibrary}}
46
+ {{#addComments}}
47
+ // Directories where we’re going to look for language tags
48
+ {{/addComments}}
49
+ includes: [{{includes}}],
50
+ excludes: [{{excludes}}],
51
+ {{^isLibrary}}
52
+ localesDirectory: '{{localesDirectory}}',
53
+ {{/isLibrary}}
54
+ {{#addComments}}
55
+ // The source translations language used in your code (e.g., 'en', 'pl')
56
+ {{/addComments}}
57
+ baseLanguageCode: '{{baseLanguageCode}}',
58
+ {{#hasConfigGeneration}}
59
+ {{#usePathBased}}
60
+ {{#addComments}}
61
+ /**
62
+ * Automatically generates namespace/path from file location.
63
+ * Example: src/features/auth/LoginForm.tsx → { namespace: 'auth', path: 'loginForm' }
64
+ *
65
+ * To customize, modify generationAlgorithm options above or add custom logic:
66
+ * - Change case formatting (namespaceCase, pathCase)
67
+ * - Ignore specific directories (ignoreDirectories)
68
+ * - Add path transformation rules (pathRules)
69
+ */
70
+ {{/addComments}}
71
+ onConfigGeneration: async event => {
72
+ // We do not modify imported configurations
73
+ if (event.isImportedLibrary) return;
74
+ {{#useKeeper}}
75
+ if (event.config?.keep === 'both') return;
76
+ {{/useKeeper}}
77
+
78
+ await generationAlgorithm(event);
79
+ {{#useKeeper}}
80
+ await keeper(event);
81
+ {{/useKeeper}}
82
+ },
83
+ {{/usePathBased}}
84
+ {{#useCustom}}
85
+ {{#addComments}}
86
+ /**
87
+ * Custom hook to generate namespace/path for each tag based on file location.
88
+ *
89
+ * Sample properties:
90
+ * - event.relativePath: File path relative to project root (e.g., 'src/auth/Login.tsx')
91
+ * - event.config: Current tag config (may be undefined or contain user-provided values)
92
+ * - event.save(newConfig): Save new config. Example: event.save({ namespace: 'auth', path: 'login' })
93
+ *
94
+ * Example - generate from directory structure:
95
+ * const segments = event.relativePath.split('/').slice(1, -1);
96
+ * event.save({ namespace: segments[0], path: segments.slice(1).join('.') });
97
+ */
98
+ {{/addComments}}
99
+ onConfigGeneration: async event => {
100
+ // We do not modify imported configurations
101
+ if (event.isImportedLibrary) return;
102
+
103
+ {{#useKeeper}}
104
+ if (event.config?.keep === 'both') return;
105
+ {{/useKeeper}}
106
+ // TODO: Implement your custom config generation logic here
107
+ // event.save({
108
+ // namespace: 'your-namespace',
109
+ // path: 'your.path'
110
+ // });
111
+ {{#useKeeper}}
112
+ await keeper(event);
113
+ {{/useKeeper}}
114
+ },
115
+ {{/useCustom}}
116
+ {{/hasConfigGeneration}}
117
+ {{#hasCollectContent}}
118
+ collect: {
119
+ {{#isDictionaryCollector}}
120
+ {{#addComments}}
121
+ // All translations in one file per language. Change to NamespaceCollector() for separate files per namespace.
122
+ {{/addComments}}
123
+ collector: new DictionaryCollector(),
124
+ {{/isDictionaryCollector}}
125
+ {{#isModifiedNamespaceCollector}}
126
+ {{#addComments}}
127
+ // Separate file per namespace (e.g., locales/en/auth.json, locales/en/dashboard.json)
128
+ {{/addComments}}
129
+ collector: new NamespaceCollector(),
130
+ {{/isModifiedNamespaceCollector}}
131
+ {{^isDictionary}}
132
+ {{^isDefaultNamespace}}
133
+ {{#addComments}}
134
+ // Tags without config or namespace in config will use this
135
+ {{/addComments}}
136
+ defaultNamespace: '{{defaultNamespace}}',
137
+ {{/isDefaultNamespace}}
138
+ {{/isDictionary}}
139
+ {{#interfereWithCollection}}
140
+ {{#addComments}}
141
+ /**
142
+ * Called for each duplicate key conflict (same namespace + path, different values).
143
+ * To stop on first conflict, call event.exit() here instead of in onCollectFinish.
144
+ */
145
+ {{/addComments}}
146
+ onConflictResolution: async event => {
147
+ await event.logger.conflict(event.conflict, true);
148
+ // By default, continue processing even if conflicts occur
149
+ // Call event.exit(); to terminate the process upon the first conflict
150
+ },
151
+ {{#addComments}}
152
+ /**
153
+ * Called after collection completes. Check event.conflicts array to handle all conflicts.
154
+ * Remove this hook to allow merging despite conflicts.
155
+ */
156
+ {{/addComments}}
157
+ onCollectFinish: event => {
158
+ if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
159
+ }
160
+ {{/interfereWithCollection}}
161
+ },
162
+ {{/hasCollectContent}}
163
+ {{#importLibraries}}
164
+ {{#addComments}}
165
+ /**
166
+ * Imports translations from external libraries (node modules packages containing exported 'lang-tags.json').
167
+ * - dir: Where to generate import files
168
+ * - tagImportPath: Update path to your tag function
169
+ * - onImport: Controls file naming/structure (see flexibleImportAlgorithm options)
170
+ */
171
+ {{/addComments}}
172
+ import: {
173
+ dir: 'src/lang-libraries',
174
+ tagImportPath: 'import { {{tagName}} } from "@/my-lang-tag-path"',
175
+ onImport: flexibleImportAlgorithm({ filePath: { includePackageInPath: true } })
176
+ },
177
+ {{/importLibraries}}
178
+ };
179
+
180
+ {{#isCJS}}module.exports = config;{{/isCJS}}{{^isCJS}}export default config;{{/isCJS}}
@@ -0,0 +1,73 @@
1
+ {{#addComments}}
2
+ /**
3
+ * Generates namespace/path from directory structure (filename is excluded).
4
+ *
5
+ * Example with current settings (assuming includes: ['src/**']):
6
+ * File: src/(admin)/features/auth/utils/LoginForm.tsx
7
+ * 1. Remove filename → src/(admin)/features/auth/utils
8
+ * 2. Remove root 'src' → (admin)/features/auth/utils
9
+ * 3. Remove bracketed '(admin)' → features/auth/utils
10
+ * 4. Remove 'utils' (in ignoreDirectories) → features/auth
11
+ * 5. First segment = namespace, rest = path → namespace: 'features', path: 'auth'
12
+ */
13
+ {{/addComments}}
14
+ const generationAlgorithm = pathBasedConfigGenerator({
15
+ {{#addComments}}
16
+ // Removes root directory from includes patterns (e.g., 'src' if includes: ['src/**'])
17
+ {{/addComments}}
18
+ ignoreIncludesRootDirectories: true,
19
+ {{#addComments}}
20
+ // Directories with () or [] are NOT used for namespace/path generation
21
+ // true: app/(admin)/[id]/page.tsx → app/page (completely removed)
22
+ // false: app/(admin)/[id]/page.tsx → app/admin/id/page (brackets removed, names kept)
23
+ {{/addComments}}
24
+ removeBracketedDirectories: true,
25
+ {{#addComments}}
26
+ // Case for namespace. Options: 'kebab', 'camel', 'pascal', 'snake'
27
+ {{/addComments}}
28
+ namespaceCase: 'kebab',
29
+ {{#addComments}}
30
+ // Case for path. Options: 'kebab', 'camel', 'pascal', 'snake'
31
+ {{/addComments}}
32
+ pathCase: 'camel',
33
+ {{#addComments}}
34
+ // If namespace equals defaultNamespace, omit it from config (namespace becomes undefined)
35
+ {{/addComments}}
36
+ clearOnDefaultNamespace: true,
37
+ {{#addComments}}
38
+ // Skip these directory names globally when building namespace/path
39
+ {{/addComments}}
40
+ ignoreDirectories: ['core', 'utils', 'helpers'],{{#addComments}}
41
+ /**
42
+ * Advanced: pathRules for hierarchical transformations
43
+ *
44
+ * Special operators:
45
+ * - _: false → ignore current directory, continue with nested rules
46
+ * - '>': 'newName' → rename directory in namespace
47
+ * - '>>': redirect → jump to different namespace
48
+ * - '>>': 'namespace' → use specified namespace, remaining segments become path
49
+ * - '>>': { namespace: 'ui', pathPrefix: 'admin' } → jump to 'ui' namespace with 'admin.' prefix
50
+ * - '>>': '' or null → use current directory as namespace
51
+ *
52
+ * Example usage:
53
+ */
54
+ // pathRules: {
55
+ // app: {
56
+ // dashboard: {
57
+ // _: false, // ignore "dashboard" but continue with nested rules
58
+ // modules: false // also ignore "modules"
59
+ // },
60
+ // admin: {
61
+ // '>': 'management', // rename "admin" to "management"
62
+ // users: false // ignore "users"
63
+ // },
64
+ // components: {
65
+ // '>>': { // jump to 'ui' namespace, prefix all paths with 'components'
66
+ // namespace: 'ui',
67
+ // pathPrefix: 'components'
68
+ // }
69
+ // }
70
+ // }
71
+ // }{{/addComments}}
72
+ });
73
+
@@ -1 +0,0 @@
1
- -