gtx-cli 2.3.6 → 2.3.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.3.8
4
+
5
+ ### Patch Changes
6
+
7
+ - [#713](https://github.com/generaltranslation/gt/pull/713) [`b8feb26`](https://github.com/generaltranslation/gt/commit/b8feb2638613f54b76b5f3768edc6039db512c53) Thanks [@SamEggert](https://github.com/SamEggert)! - rename clearLocaleFolders to clearLocaleDirs
8
+
9
+ ## 2.3.7
10
+
11
+ ### Patch Changes
12
+
13
+ - [#710](https://github.com/generaltranslation/gt/pull/710) [`8325bae`](https://github.com/generaltranslation/gt/commit/8325bae9a8661a0b269131ac6dadefab327c5b2c) Thanks [@SamEggert](https://github.com/SamEggert)! - add clearLocaleFolders option
14
+
3
15
  ## 2.3.6
4
16
 
5
17
  ### Patch Changes
@@ -4,6 +4,8 @@ import { getLocaleProperties } from 'generaltranslation';
4
4
  import { downloadFileBatch } from './downloadFileBatch.js';
5
5
  import { gt } from '../utils/gt.js';
6
6
  import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
7
+ import { clearLocaleDirs } from '../fs/clearLocaleDirs.js';
8
+ import path from 'node:path';
7
9
  /**
8
10
  * Checks the status of translations for a given version ID
9
11
  * @param apiKey - The API key for the General Translation API
@@ -24,6 +26,22 @@ export async function checkFileTranslations(data, locales, timeoutDuration, reso
24
26
  spinner.start(spinnerMessage);
25
27
  // Initialize the query data
26
28
  const fileQueryData = prepareFileQueryData(data, locales);
29
+ // Clear translated files before any downloads (if enabled)
30
+ if (options.options?.experimentalClearLocaleDirs === true &&
31
+ fileQueryData.length > 0) {
32
+ const translatedFiles = new Set(fileQueryData
33
+ .map((file) => {
34
+ const outputPath = resolveOutputPath(file.fileName, file.locale);
35
+ // Only clear if the output path is different from the source (i.e., there's a transform)
36
+ return outputPath !== null && outputPath !== file.fileName
37
+ ? outputPath
38
+ : null;
39
+ })
40
+ .filter((path) => path !== null));
41
+ // Derive cwd from config path
42
+ const cwd = path.dirname(options.config);
43
+ await clearLocaleDirs(translatedFiles, locales, options.options?.clearLocaleDirsExclude, cwd);
44
+ }
27
45
  const downloadStatus = {
28
46
  downloaded: new Set(),
29
47
  failed: new Set(),
package/dist/cli/flags.js CHANGED
@@ -24,7 +24,8 @@ export function attachTranslateFlags(command) {
24
24
  .option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
25
25
  .option('--experimental-flatten-json-files', 'Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.', false)
26
26
  .option('--experimental-localize-static-imports', 'Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.', false)
27
- .option('--force', 'Force a retranslation, invalidating all existing cached translations if they exist.', false);
27
+ .option('--force', 'Force a retranslation, invalidating all existing cached translations if they exist.', false)
28
+ .option('--experimental-clear-locale-dirs', 'Clear locale directories before downloading new translations', false);
28
29
  return command;
29
30
  }
30
31
  export function attachAdditionalReactTranslateFlags(command) {
@@ -126,6 +126,10 @@ export async function generateSettings(options, cwd = process.cwd()) {
126
126
  options.experimentalHideDefaultLocale,
127
127
  experimentalFlattenJsonFiles: gtConfig.options?.experimentalFlattenJsonFiles ||
128
128
  options.experimentalFlattenJsonFiles,
129
+ experimentalClearLocaleDirs: gtConfig.options?.experimentalClearLocaleDirs ||
130
+ options.experimentalClearLocaleDirs,
131
+ clearLocaleDirsExclude: gtConfig.options?.clearLocaleDirsExclude ||
132
+ options.clearLocaleDirsExclude,
129
133
  };
130
134
  // Add additional options if provided
131
135
  if (mergedOptions.options) {
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Clears translated files before writing new translations
3
+ * @param filePaths - Set of translated file paths to clear
4
+ * @param locales - Array of locale codes to match against
5
+ * @param excludePatterns - Optional array of glob patterns to exclude from clearing (supports [locale] and [locales])
6
+ * @param cwd - Current working directory for resolving relative exclude patterns (defaults to process.cwd())
7
+ */
8
+ export declare function clearLocaleDirs(filePaths: Set<string>, locales: string[], excludePatterns?: string[], cwd?: string): Promise<void>;
@@ -0,0 +1,126 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'node:path';
3
+ import { logSuccess, logWarning } from '../console/logging.js';
4
+ import fg from 'fast-glob';
5
+ import micromatch from 'micromatch';
6
+ /**
7
+ * Extracts locale directories from translated file paths.
8
+ * Groups files by their immediate parent containing a locale code.
9
+ * For example: "snippets/es/api-test/file.mdx" -> "snippets/es"
10
+ */
11
+ function extractLocaleDirectories(filePaths, locales) {
12
+ const localeDirs = new Set();
13
+ const localeSet = new Set(locales);
14
+ for (const filePath of filePaths) {
15
+ const parts = filePath.split(path.sep);
16
+ // Find directory segments that match the provided locales
17
+ for (let i = 0; i < parts.length - 1; i++) {
18
+ const segment = parts[i];
19
+ if (localeSet.has(segment)) {
20
+ // Found a locale directory, capture up to and including this segment
21
+ const localeDir = parts.slice(0, i + 1).join(path.sep);
22
+ localeDirs.add(localeDir);
23
+ break;
24
+ }
25
+ }
26
+ }
27
+ return localeDirs;
28
+ }
29
+ async function getAllFiles(dirPath) {
30
+ return await fg(path.join(dirPath, '**/*'), {
31
+ absolute: true,
32
+ onlyFiles: true,
33
+ });
34
+ }
35
+ async function getFilesToDelete(dirPath, excludePatterns, currentLocale, cwd) {
36
+ const allFiles = await getAllFiles(dirPath);
37
+ const absoluteCwd = path.resolve(cwd);
38
+ const expandedExcludePatterns = excludePatterns.map((p) => {
39
+ const resolvedPattern = path.isAbsolute(p) ? p : path.join(absoluteCwd, p);
40
+ return resolvedPattern
41
+ .replace(/\[locale\]/g, currentLocale)
42
+ .replace(/\[locales\]/g, currentLocale);
43
+ });
44
+ const filesToKeep = micromatch(allFiles, expandedExcludePatterns, {
45
+ dot: true,
46
+ });
47
+ const filesToKeepSet = new Set(filesToKeep);
48
+ return allFiles.filter((file) => !filesToKeepSet.has(file));
49
+ }
50
+ /**
51
+ * Clears translated files before writing new translations
52
+ * @param filePaths - Set of translated file paths to clear
53
+ * @param locales - Array of locale codes to match against
54
+ * @param excludePatterns - Optional array of glob patterns to exclude from clearing (supports [locale] and [locales])
55
+ * @param cwd - Current working directory for resolving relative exclude patterns (defaults to process.cwd())
56
+ */
57
+ export async function clearLocaleDirs(filePaths, locales, excludePatterns, cwd = process.cwd()) {
58
+ // Extract locale directories
59
+ const localeDirs = extractLocaleDirectories(filePaths, locales);
60
+ for (const dir of localeDirs) {
61
+ try {
62
+ await fs.stat(dir);
63
+ // Extract locale from directory path
64
+ const dirParts = dir.split(path.sep);
65
+ const locale = locales.find((loc) => dirParts.includes(loc));
66
+ if (!locale) {
67
+ continue;
68
+ }
69
+ if (!excludePatterns?.length) {
70
+ await fs.rm(dir, { recursive: true, force: true });
71
+ logSuccess(`Cleared locale directory: ${dir}`);
72
+ continue;
73
+ }
74
+ const filesToDelete = await getFilesToDelete(dir, excludePatterns, locale, cwd);
75
+ // Get all files for count comparison
76
+ const allFiles = await getAllFiles(dir);
77
+ for (const file of filesToDelete) {
78
+ try {
79
+ await fs.unlink(file);
80
+ }
81
+ catch (error) {
82
+ if (error.code !== 'ENOENT') {
83
+ logWarning(`Failed to delete file ${file}: ${error}`);
84
+ }
85
+ }
86
+ }
87
+ // Clean up empty directories
88
+ await cleanupEmptyDirs(dir);
89
+ const excludedCount = allFiles.length - filesToDelete.length;
90
+ if (excludedCount > 0) {
91
+ logSuccess(`Cleared locale directory: ${dir} (excluded ${excludedCount} file${excludedCount > 1 ? 's' : ''})`);
92
+ }
93
+ else {
94
+ logSuccess(`Cleared locale directory: ${dir}`);
95
+ }
96
+ }
97
+ catch (error) {
98
+ if (error.code !== 'ENOENT') {
99
+ logWarning(`Failed to clear locale directory ${dir}: ${error}`);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Recursively removes empty directories
106
+ * @param dirPath - The directory to clean up
107
+ */
108
+ async function cleanupEmptyDirs(dirPath) {
109
+ try {
110
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
111
+ // Recursively clean subdirectories first
112
+ for (const entry of entries) {
113
+ if (entry.isDirectory()) {
114
+ await cleanupEmptyDirs(path.join(dirPath, entry.name));
115
+ }
116
+ }
117
+ // Check if directory is now empty
118
+ const remainingEntries = await fs.readdir(dirPath);
119
+ if (remainingEntries.length === 0) {
120
+ await fs.rmdir(dirPath);
121
+ }
122
+ }
123
+ catch (error) {
124
+ // Ignore errors - directory might not exist or might not be empty
125
+ }
126
+ }
@@ -143,6 +143,8 @@ export type AdditionalOptions = {
143
143
  excludeStaticImports?: string[];
144
144
  docsHideDefaultLocaleImport?: boolean;
145
145
  copyFiles?: string[];
146
+ experimentalClearLocaleDirs?: boolean;
147
+ clearLocaleDirsExclude?: string[];
146
148
  experimentalLocalizeStaticImports?: boolean;
147
149
  experimentalLocalizeStaticUrls?: boolean;
148
150
  experimentalAddHeaderAnchorIds?: 'mintlify';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.3.6",
3
+ "version": "2.3.8",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [