gtx-cli 2.3.10 → 2.3.12

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 (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/api/checkFileTranslations.d.ts +1 -1
  3. package/dist/api/checkFileTranslations.js +11 -5
  4. package/dist/api/downloadFileBatch.d.ts +3 -1
  5. package/dist/api/downloadFileBatch.js +41 -1
  6. package/dist/cli/commands/translate.js +3 -3
  7. package/dist/cli/flags.js +1 -0
  8. package/dist/fs/config/downloadedVersions.d.ts +12 -0
  9. package/dist/fs/config/downloadedVersions.js +30 -0
  10. package/dist/next/parse/handleInitGT.js +9 -60
  11. package/dist/react/parse/addVitePlugin/index.d.ts +22 -0
  12. package/dist/react/parse/addVitePlugin/index.js +40 -0
  13. package/dist/react/parse/addVitePlugin/installCompiler.d.ts +8 -0
  14. package/dist/react/parse/addVitePlugin/installCompiler.js +22 -0
  15. package/dist/react/parse/addVitePlugin/updateViteConfig.d.ts +19 -0
  16. package/dist/react/parse/addVitePlugin/updateViteConfig.js +123 -0
  17. package/dist/react/parse/addVitePlugin/utils/addCompilerImport.d.ts +9 -0
  18. package/dist/react/parse/addVitePlugin/utils/addCompilerImport.js +34 -0
  19. package/dist/react/parse/addVitePlugin/utils/addPluginInvocation.d.ts +11 -0
  20. package/dist/react/parse/addVitePlugin/utils/addPluginInvocation.js +48 -0
  21. package/dist/react/parse/addVitePlugin/utils/checkCompilerImport.d.ts +15 -0
  22. package/dist/react/parse/addVitePlugin/utils/checkCompilerImport.js +113 -0
  23. package/dist/react/parse/addVitePlugin/utils/checkPluginInvocation.d.ts +12 -0
  24. package/dist/react/parse/addVitePlugin/utils/checkPluginInvocation.js +32 -0
  25. package/dist/setup/wizard.js +14 -3
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/utils/parse/needsCJS.d.ts +20 -0
  28. package/dist/utils/parse/needsCJS.js +72 -0
  29. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.3.12
4
+
5
+ ### Patch Changes
6
+
7
+ - [#735](https://github.com/generaltranslation/gt/pull/735) [`8e4612e`](https://github.com/generaltranslation/gt/commit/8e4612e0b2c426d64153b6ca460e619fa29cb8e8) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - fix: auto enable gt compiler in default config
8
+
9
+ ## 2.3.11
10
+
11
+ ### Patch Changes
12
+
13
+ - [#732](https://github.com/generaltranslation/gt/pull/732) [`bcd8272`](https://github.com/generaltranslation/gt/commit/bcd8272576ff02432e39cf1887a48b4f566eb752) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Added freezing when fetching translations for unmodified source files. This will keep any local changes until retranslation is triggered or --force-download is used
14
+
3
15
  ## 2.3.10
4
16
 
5
17
  ### Patch Changes
@@ -20,4 +20,4 @@ export declare function checkFileTranslations(data: {
20
20
  versionId: string;
21
21
  fileName: string;
22
22
  };
23
- }, locales: string[], timeoutDuration: number, resolveOutputPath: (sourcePath: string, locale: string) => string | null, options: Settings, forceRetranslation?: boolean): Promise<boolean>;
23
+ }, locales: string[], timeoutDuration: number, resolveOutputPath: (sourcePath: string, locale: string) => string | null, options: Settings, forceRetranslation?: boolean, forceDownload?: boolean): Promise<boolean>;
@@ -16,7 +16,7 @@ import path from 'node:path';
16
16
  * @param timeoutDuration - The timeout duration for the wait in seconds
17
17
  * @returns True if all translations are deployed, false otherwise
18
18
  */
19
- export async function checkFileTranslations(data, locales, timeoutDuration, resolveOutputPath, options, forceRetranslation) {
19
+ export async function checkFileTranslations(data, locales, timeoutDuration, resolveOutputPath, options, forceRetranslation, forceDownload) {
20
20
  const startTime = Date.now();
21
21
  console.log();
22
22
  const spinner = await createOraSpinner();
@@ -49,7 +49,7 @@ export async function checkFileTranslations(data, locales, timeoutDuration, reso
49
49
  };
50
50
  // Do first check immediately, but skip if force retranslation is enabled
51
51
  if (!forceRetranslation) {
52
- const initialCheck = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options);
52
+ const initialCheck = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options, forceDownload);
53
53
  if (initialCheck) {
54
54
  spinner.succeed(chalk.green('Files translated!'));
55
55
  return true;
@@ -62,7 +62,7 @@ export async function checkFileTranslations(data, locales, timeoutDuration, reso
62
62
  // Start the interval aligned with the original request time
63
63
  setTimeout(() => {
64
64
  intervalCheck = setInterval(async () => {
65
- const isDeployed = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options);
65
+ const isDeployed = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options, forceDownload);
66
66
  const elapsed = Date.now() - startTime;
67
67
  if (isDeployed || elapsed >= timeoutDuration * 1000) {
68
68
  clearInterval(intervalCheck);
@@ -186,7 +186,7 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
186
186
  /**
187
187
  * Checks translation status and downloads ready files
188
188
  */
189
- async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options) {
189
+ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options, forceDownload) {
190
190
  try {
191
191
  // Only query for files that haven't been downloaded yet
192
192
  const currentQueryData = fileQueryData.filter((item) => !downloadStatus.downloaded.has(`${item.fileName}:${item.locale}`) &&
@@ -202,6 +202,11 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
202
202
  // Filter for ready translations
203
203
  const readyTranslations = translations.filter((translation) => translation.isReady && translation.fileName);
204
204
  if (readyTranslations.length > 0) {
205
+ // Build version map by fileName:locale for this batch
206
+ const versionMap = new Map(fileQueryData.map((item) => [
207
+ `${item.fileName}:${gt.resolveAliasLocale(item.locale)}`,
208
+ item.versionId,
209
+ ]));
205
210
  // Prepare batch download data
206
211
  const batchFiles = readyTranslations
207
212
  .map((translation) => {
@@ -220,10 +225,11 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
220
225
  outputPath,
221
226
  locale,
222
227
  fileLocale: `${fileName}:${locale}`,
228
+ versionId: versionMap.get(`${fileName}:${locale}`),
223
229
  };
224
230
  })
225
231
  .filter((file) => file !== null);
226
- const batchResult = await downloadFileBatch(batchFiles, options);
232
+ const batchResult = await downloadFileBatch(batchFiles, options, 3, 1000, Boolean(forceDownload));
227
233
  // Process results
228
234
  batchFiles.forEach((file) => {
229
235
  const { translationId, fileLocale } = file;
@@ -5,6 +5,8 @@ export type BatchedFiles = Array<{
5
5
  inputPath: string;
6
6
  locale: string;
7
7
  fileLocale: string;
8
+ fileId?: string;
9
+ versionId?: string;
8
10
  }>;
9
11
  export type DownloadFileBatchResult = {
10
12
  successful: string[];
@@ -17,4 +19,4 @@ export type DownloadFileBatchResult = {
17
19
  * @param retryDelay - Delay between retries in milliseconds
18
20
  * @returns Object containing successful and failed file IDs
19
21
  */
20
- export declare function downloadFileBatch(files: BatchedFiles, options: Settings, maxRetries?: number, retryDelay?: number): Promise<DownloadFileBatchResult>;
22
+ export declare function downloadFileBatch(files: BatchedFiles, options: Settings, maxRetries?: number, retryDelay?: number, forceDownload?: boolean): Promise<DownloadFileBatchResult>;
@@ -6,6 +6,7 @@ import { validateJsonSchema } from '../formats/json/utils.js';
6
6
  import { validateYamlSchema } from '../formats/yaml/utils.js';
7
7
  import { mergeJson } from '../formats/json/mergeJson.js';
8
8
  import mergeYaml from '../formats/yaml/mergeYaml.js';
9
+ import { getDownloadedVersions, saveDownloadedVersions, } from '../fs/config/downloadedVersions.js';
9
10
  /**
10
11
  * Downloads multiple translation files in a single batch request
11
12
  * @param files - Array of files to download with their output paths
@@ -13,17 +14,22 @@ import mergeYaml from '../formats/yaml/mergeYaml.js';
13
14
  * @param retryDelay - Delay between retries in milliseconds
14
15
  * @returns Object containing successful and failed file IDs
15
16
  */
16
- export async function downloadFileBatch(files, options, maxRetries = 3, retryDelay = 1000) {
17
+ export async function downloadFileBatch(files, options, maxRetries = 3, retryDelay = 1000, forceDownload = false) {
18
+ // Local record of what version was last downloaded for each fileName:locale
19
+ const downloadedVersions = getDownloadedVersions(options.configDirectory);
20
+ let didUpdateDownloadedLock = false;
17
21
  let retries = 0;
18
22
  const fileIds = files.map((file) => file.translationId);
19
23
  const result = { successful: [], failed: [] };
20
24
  // Create a map of translationId to outputPath for easier lookup
21
25
  const outputPathMap = new Map(files.map((file) => [file.translationId, file.outputPath]));
22
26
  const inputPathMap = new Map(files.map((file) => [file.translationId, file.inputPath]));
27
+ const fileIdMap = new Map(files.map((file) => [file.translationId, file.fileId]));
23
28
  const localeMap = new Map(files.map((file) => [
24
29
  file.translationId,
25
30
  gt.resolveAliasLocale(file.locale),
26
31
  ]));
32
+ const versionMap = new Map(files.map((file) => [file.translationId, file.versionId]));
27
33
  while (retries <= maxRetries) {
28
34
  try {
29
35
  // Download the files
@@ -36,6 +42,8 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
36
42
  const outputPath = outputPathMap.get(translationId);
37
43
  const inputPath = inputPathMap.get(translationId);
38
44
  const locale = localeMap.get(translationId);
45
+ const fileId = fileIdMap.get(translationId);
46
+ const versionId = versionMap.get(translationId);
39
47
  if (!outputPath || !inputPath) {
40
48
  logWarning(`No input/output path found for file: ${translationId}`);
41
49
  result.failed.push(translationId);
@@ -46,6 +54,18 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
46
54
  if (!fs.existsSync(dir)) {
47
55
  fs.mkdirSync(dir, { recursive: true });
48
56
  }
57
+ // If a local translation already exists for the same source version, skip overwrite
58
+ const keyId = fileId || inputPath;
59
+ const downloadedKey = `${keyId}:${locale}`;
60
+ const alreadyDownloadedVersion = downloadedVersions.entries[downloadedKey]?.versionId;
61
+ const fileExists = fs.existsSync(outputPath);
62
+ if (!forceDownload &&
63
+ fileExists &&
64
+ versionId &&
65
+ alreadyDownloadedVersion === versionId) {
66
+ result.successful.push(translationId);
67
+ continue;
68
+ }
49
69
  let data = file.data;
50
70
  if (options.options?.jsonSchema && locale) {
51
71
  const jsonSchema = validateJsonSchema(options.options, inputPath);
@@ -78,6 +98,15 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
78
98
  // Write the file to disk
79
99
  await fs.promises.writeFile(outputPath, data);
80
100
  result.successful.push(translationId);
101
+ if (versionId) {
102
+ downloadedVersions.entries[downloadedKey] = {
103
+ versionId,
104
+ fileId: fileId || undefined,
105
+ fileName: inputPath,
106
+ updatedAt: new Date().toISOString(),
107
+ };
108
+ didUpdateDownloadedLock = true;
109
+ }
81
110
  }
82
111
  catch (error) {
83
112
  logError(`Error saving file ${file.id}: ` + error);
@@ -91,6 +120,11 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
91
120
  result.failed.push(fileId);
92
121
  }
93
122
  }
123
+ // Persist any updates to the downloaded map at the end of a successful cycle
124
+ if (didUpdateDownloadedLock) {
125
+ saveDownloadedVersions(options.configDirectory, downloadedVersions);
126
+ didUpdateDownloadedLock = false;
127
+ }
94
128
  return result;
95
129
  }
96
130
  catch (error) {
@@ -100,6 +134,9 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
100
134
  error);
101
135
  // Mark all files as failed
102
136
  result.failed = [...fileIds];
137
+ if (didUpdateDownloadedLock) {
138
+ saveDownloadedVersions(options.configDirectory, downloadedVersions);
139
+ }
103
140
  return result;
104
141
  }
105
142
  // Increment retry counter and wait before next attempt
@@ -109,5 +146,8 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
109
146
  }
110
147
  // Mark all files as failed if we get here
111
148
  result.failed = [...fileIds];
149
+ if (didUpdateDownloadedLock) {
150
+ saveDownloadedVersions(options.configDirectory, downloadedVersions);
151
+ }
112
152
  return result;
113
153
  }
@@ -15,7 +15,7 @@ export async function handleTranslate(options, settings, filesTranslationRespons
15
15
  const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
16
16
  const { data } = filesTranslationResponse;
17
17
  // Check for remaining translations
18
- await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, options.force);
18
+ await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, options.force, options.forceDownload);
19
19
  }
20
20
  }
21
21
  // Downloads translations that were originally staged
@@ -33,8 +33,8 @@ export async function handleDownload(options, settings) {
33
33
  const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
34
34
  const stagedVersionData = await getStagedVersions(settings.configDirectory);
35
35
  // Check for remaining translations
36
- await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false // force is not applicable for downloading staged translations
37
- );
36
+ await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false, // force is not applicable for downloading staged translations
37
+ options.forceDownload);
38
38
  }
39
39
  export async function postProcessTranslations(settings) {
40
40
  // Localize static urls (/docs -> /[locale]/docs) and preserve anchor IDs for non-default locales
package/dist/cli/flags.js CHANGED
@@ -26,6 +26,7 @@ export function attachTranslateFlags(command) {
26
26
  .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)
27
27
  .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)
28
28
  .option('--force', 'Force a retranslation, invalidating all existing cached translations if they exist.', false)
29
+ .option('--force-download', 'Force download and overwrite local files, bypassing downloaded-versions checks.', false)
29
30
  .option('--experimental-clear-locale-dirs', 'Clear locale directories before downloading new translations', false);
30
31
  return command;
31
32
  }
@@ -0,0 +1,12 @@
1
+ export type DownloadedVersionEntry = {
2
+ versionId: string;
3
+ fileId?: string;
4
+ fileName?: string;
5
+ updatedAt?: string;
6
+ };
7
+ export type DownloadedVersions = {
8
+ version: number;
9
+ entries: Record<string, DownloadedVersionEntry>;
10
+ };
11
+ export declare function getDownloadedVersions(configDirectory: string): DownloadedVersions;
12
+ export declare function saveDownloadedVersions(configDirectory: string, lock: DownloadedVersions): void;
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logError } from '../../console/logging.js';
4
+ const DOWNLOADED_VERSIONS_FILE = 'downloaded-versions.json';
5
+ export function getDownloadedVersions(configDirectory) {
6
+ try {
7
+ const filepath = path.join(configDirectory, DOWNLOADED_VERSIONS_FILE);
8
+ if (!fs.existsSync(filepath))
9
+ return { version: 1, entries: {} };
10
+ const raw = JSON.parse(fs.readFileSync(filepath, 'utf8'));
11
+ if (raw && typeof raw === 'object' && raw.version && raw.entries) {
12
+ return raw;
13
+ }
14
+ return { version: 1, entries: {} };
15
+ }
16
+ catch (error) {
17
+ logError(`An error occurred while getting downloaded versions: ${error}`);
18
+ return { version: 1, entries: {} };
19
+ }
20
+ }
21
+ export function saveDownloadedVersions(configDirectory, lock) {
22
+ try {
23
+ const filepath = path.join(configDirectory, DOWNLOADED_VERSIONS_FILE);
24
+ fs.mkdirSync(configDirectory, { recursive: true });
25
+ fs.writeFileSync(filepath, JSON.stringify(lock, null, 2));
26
+ }
27
+ catch (error) {
28
+ logError(`An error occurred while updating ${DOWNLOADED_VERSIONS_FILE}: ${error}`);
29
+ }
30
+ }
@@ -7,6 +7,7 @@ const traverse = traverseModule.default || traverseModule;
7
7
  const generate = generateModule.default || generateModule;
8
8
  import * as t from '@babel/types';
9
9
  import { logError } from '../../console/logging.js';
10
+ import { needsCJS } from '../../utils/parse/needsCJS.js';
10
11
  export async function handleInitGT(filepath, errors, warnings, filesUpdated, packageJson, tsconfigJson) {
11
12
  const code = await fs.promises.readFile(filepath, 'utf8');
12
13
  let ast;
@@ -17,66 +18,14 @@ export async function handleInitGT(filepath, errors, warnings, filesUpdated, pac
17
18
  tokens: true,
18
19
  createParenthesizedExpressions: true,
19
20
  });
20
- // Analyze the actual file content to determine module system
21
- let hasES6Imports = false;
22
- let hasCommonJSRequire = false;
23
- traverse(ast, {
24
- ImportDeclaration() {
25
- hasES6Imports = true;
26
- },
27
- CallExpression(path) {
28
- if (t.isIdentifier(path.node.callee, { name: 'require' })) {
29
- hasCommonJSRequire = true;
30
- }
31
- },
21
+ // Get cjs or esm
22
+ const cjsEnabled = needsCJS({
23
+ ast,
24
+ warnings,
25
+ filepath,
26
+ packageJson,
27
+ tsconfigJson,
32
28
  });
33
- // Determine if we need CommonJS based on actual file content and fallback to config-based logic
34
- let needsCJS = false;
35
- if (hasES6Imports && !hasCommonJSRequire) {
36
- // File uses ES6 imports, so we should use ES6 imports
37
- needsCJS = false;
38
- }
39
- else if (hasCommonJSRequire && !hasES6Imports) {
40
- // File uses CommonJS require, so we should use CommonJS require
41
- needsCJS = true;
42
- }
43
- else if (hasES6Imports && hasCommonJSRequire) {
44
- // Mixed usage - this is unusual but we'll default to ES6 imports
45
- warnings.push(`Mixed ES6 imports and CommonJS require detected in ${filepath}. Defaulting to ES6 imports.`);
46
- needsCJS = false;
47
- }
48
- else {
49
- // No imports/requires found, fall back to configuration-based logic
50
- if (filepath.endsWith('.ts') || filepath.endsWith('.tsx')) {
51
- // For TypeScript files, check tsconfig.json compilerOptions.module
52
- const moduleSetting = tsconfigJson?.compilerOptions?.module;
53
- if (moduleSetting === 'commonjs' || moduleSetting === 'node') {
54
- needsCJS = true;
55
- }
56
- else if (moduleSetting === 'esnext' ||
57
- moduleSetting === 'es2022' ||
58
- moduleSetting === 'es2020' ||
59
- moduleSetting === 'es2015' ||
60
- moduleSetting === 'es6' ||
61
- moduleSetting === 'node16' ||
62
- moduleSetting === 'nodenext') {
63
- needsCJS = false;
64
- }
65
- else {
66
- // Default to ESM for TypeScript files if no module setting is specified
67
- needsCJS = false;
68
- }
69
- }
70
- else if (filepath.endsWith('.js')) {
71
- // For JavaScript files, check package.json type
72
- // If package.json has "type": "module", .js files are treated as ES modules
73
- needsCJS = packageJson?.type !== 'module';
74
- }
75
- else {
76
- // For other file extensions, default to ESM
77
- needsCJS = false;
78
- }
79
- }
80
29
  // Check if withGTConfig or initGT is already imported/required
81
30
  let hasGTConfig = false;
82
31
  let hasInitGT = false;
@@ -145,7 +94,7 @@ export async function handleInitGT(filepath, errors, warnings, filesUpdated, pac
145
94
  if (hasGTConfig || hasInitGT) {
146
95
  return;
147
96
  }
148
- ast.program.body.unshift(needsCJS
97
+ ast.program.body.unshift(cjsEnabled
149
98
  ? t.variableDeclaration('const', [
150
99
  t.variableDeclarator(t.identifier('withGTConfig'), t.memberExpression(t.callExpression(t.identifier('require'), [
151
100
  t.stringLiteral('gt-next/config'),
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Adds the gt compiler plugin to the vite config file
3
+ */
4
+ export declare function addVitePlugin({ errors, warnings, filesUpdated, packageJson, tsconfigJson, }: {
5
+ errors: string[];
6
+ warnings: string[];
7
+ filesUpdated: string[];
8
+ packageJson?: {
9
+ type?: string;
10
+ dependencies?: Record<string, string>;
11
+ devDependencies?: Record<string, string>;
12
+ };
13
+ tsconfigJson?: {
14
+ compilerOptions?: {
15
+ module?: string;
16
+ };
17
+ };
18
+ }): Promise<{
19
+ errors: string[];
20
+ warnings: string[];
21
+ filesUpdated: string[];
22
+ }>;
@@ -0,0 +1,40 @@
1
+ import findFilepath from '../../../fs/findFilepath.js';
2
+ import { logError } from '../../../console/logging.js';
3
+ import { installCompiler } from './installCompiler.js';
4
+ import { updateViteConfig } from './updateViteConfig.js';
5
+ const VITE_CONFIG_PATH_BASE = './vite.config.';
6
+ /**
7
+ * Adds the gt compiler plugin to the vite config file
8
+ */
9
+ export async function addVitePlugin({ errors, warnings, filesUpdated, packageJson, tsconfigJson, }) {
10
+ // Resolve file path
11
+ const viteConfigPath = findFilepath([
12
+ VITE_CONFIG_PATH_BASE + 'js',
13
+ VITE_CONFIG_PATH_BASE + 'ts',
14
+ VITE_CONFIG_PATH_BASE + 'mjs',
15
+ VITE_CONFIG_PATH_BASE + 'mts',
16
+ VITE_CONFIG_PATH_BASE + 'cjs',
17
+ VITE_CONFIG_PATH_BASE + 'cts',
18
+ ]);
19
+ if (!viteConfigPath) {
20
+ logError(`No ${VITE_CONFIG_PATH_BASE}[js|ts|mjs|mts|cjs|cts] file found. Please add the @generaltranslation/compiler plugin to your vite configuration file:
21
+ import { vite as gtCompiler } from '@generaltranslation/compiler';
22
+ export default defineConfig({
23
+ plugins: [gtCompiler()],
24
+ });
25
+ `);
26
+ process.exit(1);
27
+ }
28
+ // Install @generaltranslation/compiler if not installed
29
+ await installCompiler({ packageJson });
30
+ // Update the config file
31
+ await updateViteConfig({
32
+ errors,
33
+ warnings,
34
+ filesUpdated,
35
+ viteConfigPath,
36
+ packageJson,
37
+ tsconfigJson,
38
+ });
39
+ return { errors, warnings, filesUpdated };
40
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Installs @generaltranslation/compiler if not installed
3
+ */
4
+ export declare function installCompiler({ packageJson, }: {
5
+ packageJson?: {
6
+ type?: string;
7
+ };
8
+ }): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { createSpinner } from '../../../console/logging.js';
2
+ import { installPackage } from '../../../utils/installPackage.js';
3
+ import { isPackageInstalled } from '../../../utils/packageJson.js';
4
+ import { getPackageManager } from '../../../utils/packageManager.js';
5
+ import chalk from 'chalk';
6
+ /**
7
+ * Installs @generaltranslation/compiler if not installed
8
+ */
9
+ export async function installCompiler({ packageJson, }) {
10
+ // Check if installed
11
+ if (isPackageInstalled('@generaltranslation/compiler', packageJson || {})) {
12
+ return;
13
+ }
14
+ // Animation
15
+ const spinner = createSpinner();
16
+ spinner.start(`Installing @generaltranslation/compiler...`);
17
+ // Install
18
+ const packageManager = await getPackageManager();
19
+ await installPackage('@generaltranslation/compiler', packageManager, true);
20
+ // Animation
21
+ spinner.stop(chalk.green('Installed @generaltranslation/compiler.'));
22
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * - Reads the vite config file
3
+ * - Updates the ast to add the gt compiler plugin
4
+ * - Writes the file back to the filesystem
5
+ */
6
+ export declare function updateViteConfig({ errors, warnings, filesUpdated, viteConfigPath, packageJson, tsconfigJson, }: {
7
+ errors: string[];
8
+ warnings: string[];
9
+ filesUpdated: string[];
10
+ viteConfigPath: string;
11
+ packageJson?: {
12
+ type?: string;
13
+ };
14
+ tsconfigJson?: {
15
+ compilerOptions?: {
16
+ module?: string;
17
+ };
18
+ };
19
+ }): Promise<void>;
@@ -0,0 +1,123 @@
1
+ import { createSpinner } from '../../../console/logging.js';
2
+ import { logError } from '../../../console/logging.js';
3
+ import fs from 'node:fs';
4
+ import chalk from 'chalk';
5
+ import generateModule from '@babel/generator';
6
+ import { parse } from '@babel/parser';
7
+ import { needsCJS } from '../../../utils/parse/needsCJS.js';
8
+ import { addCompilerImport } from './utils/addCompilerImport.js';
9
+ import { checkCompilerImport } from './utils/checkCompilerImport.js';
10
+ import { checkPluginInvocation } from './utils/checkPluginInvocation.js';
11
+ import { addPluginInvocation } from './utils/addPluginInvocation.js';
12
+ // Handle CommonJS/ESM interop
13
+ const generate = generateModule.default || generateModule;
14
+ /**
15
+ * - Reads the vite config file
16
+ * - Updates the ast to add the gt compiler plugin
17
+ * - Writes the file back to the filesystem
18
+ */
19
+ export async function updateViteConfig({ errors, warnings, filesUpdated, viteConfigPath, packageJson, tsconfigJson, }) {
20
+ // Animation
21
+ const spinner = createSpinner();
22
+ spinner.start(`Adding gt compiler plugin to ${viteConfigPath}...`);
23
+ // Read the file
24
+ let code;
25
+ try {
26
+ code = await fs.promises.readFile(viteConfigPath, 'utf8');
27
+ }
28
+ catch (error) {
29
+ logError(`Error: Failed to read ${viteConfigPath}: ${error}`);
30
+ process.exit(1);
31
+ return;
32
+ }
33
+ // Update the ast
34
+ let updatedCode, success;
35
+ try {
36
+ ({ updatedCode, success } = await updateViteConfigAst({
37
+ code,
38
+ errors,
39
+ warnings,
40
+ viteConfigPath,
41
+ packageJson,
42
+ tsconfigJson,
43
+ }));
44
+ }
45
+ catch (error) {
46
+ logError(`Error: Failed to update ${viteConfigPath}: ${error}`);
47
+ process.exit(1);
48
+ return;
49
+ }
50
+ // Write the file
51
+ try {
52
+ await fs.promises.writeFile(viteConfigPath, updatedCode);
53
+ filesUpdated.push(viteConfigPath);
54
+ }
55
+ catch (error) {
56
+ logError(`Error: Failed to write ${viteConfigPath}: ${error}`);
57
+ process.exit(1);
58
+ return;
59
+ }
60
+ // Animation
61
+ spinner.stop(success
62
+ ? chalk.green(`Success! Added gt compiler plugin to ${viteConfigPath}`)
63
+ : chalk.red(`Failed to add gt compiler plugin to ${viteConfigPath}. Continuing setup...`));
64
+ }
65
+ /**
66
+ * Orchestrates AST manipulation
67
+ * @param code - The code to update
68
+ * @param errors - The errors to update
69
+ * @param warnings - The warnings to update
70
+ * @param viteConfigPath - The path to the vite config file
71
+ * @param packageJson - The package.json file
72
+ * @param tsconfigJson - The tsconfig.json file
73
+ * @returns
74
+ */
75
+ async function updateViteConfigAst({ code, warnings, viteConfigPath, packageJson, tsconfigJson, }) {
76
+ // Parse the code
77
+ const ast = parse(code, {
78
+ sourceType: 'module',
79
+ plugins: ['jsx', 'typescript'],
80
+ });
81
+ // Get cjs or esm
82
+ const cjsEnabled = needsCJS({
83
+ ast,
84
+ warnings,
85
+ filepath: viteConfigPath,
86
+ packageJson,
87
+ tsconfigJson,
88
+ });
89
+ // Check if the compiler import is already present
90
+ let { hasCompilerImport, alias, namespaces } = checkCompilerImport(ast);
91
+ // Add the import declaration
92
+ if (!hasCompilerImport) {
93
+ addCompilerImport({ ast, cjsEnabled });
94
+ hasCompilerImport = true;
95
+ alias = 'gtCompiler';
96
+ namespaces = [];
97
+ }
98
+ // Check if plugin is already present
99
+ let pluginAlreadyPresent = false;
100
+ if (hasCompilerImport) {
101
+ pluginAlreadyPresent = checkPluginInvocation({ ast, alias, namespaces });
102
+ }
103
+ // Insert plugin invocation
104
+ let success = false;
105
+ if (!pluginAlreadyPresent) {
106
+ success = addPluginInvocation({ ast, alias, namespaces });
107
+ if (!success) {
108
+ warnings.push(`Failed to add gt compiler plugin to ${viteConfigPath}. Please add the plugin manually:
109
+ import { vite as gtCompiler } from '@generaltranslation/compiler';
110
+ export default defineConfig({
111
+ plugins: [gtCompiler()],
112
+ });`);
113
+ }
114
+ }
115
+ // Generate the modified code
116
+ const output = generate(ast, {
117
+ retainLines: true,
118
+ retainFunctionParens: true,
119
+ comments: true,
120
+ compact: 'auto',
121
+ }, code);
122
+ return { updatedCode: output.code, success };
123
+ }
@@ -0,0 +1,9 @@
1
+ import { ParseResult } from '@babel/parser';
2
+ import * as t from '@babel/types';
3
+ /**
4
+ * Given the vite config file ast, inserts the import declaration for the @generaltranslation/compiler package
5
+ */
6
+ export declare function addCompilerImport({ ast, cjsEnabled, }: {
7
+ ast: ParseResult<t.File>;
8
+ cjsEnabled: boolean;
9
+ }): void;
@@ -0,0 +1,34 @@
1
+ import * as t from '@babel/types';
2
+ /**
3
+ * Given the vite config file ast, inserts the import declaration for the @generaltranslation/compiler package
4
+ */
5
+ export function addCompilerImport({ ast, cjsEnabled, }) {
6
+ if (cjsEnabled) {
7
+ handleCjsImport(ast);
8
+ }
9
+ else {
10
+ handleEsmImport(ast);
11
+ }
12
+ }
13
+ /**
14
+ * Adds a CJS import declaration for the @generaltranslation/compiler package
15
+ * @param ast - The ast of the file
16
+ * const gtCompiler = require('@generaltranslation/compiler').vite;
17
+ */
18
+ function handleCjsImport(ast) {
19
+ const variableDeclaration = t.variableDeclaration('const', [
20
+ t.variableDeclarator(t.identifier('gtCompiler'), t.memberExpression(t.callExpression(t.identifier('require'), [
21
+ t.stringLiteral('@generaltranslation/compiler'),
22
+ ]), t.identifier('vite'))),
23
+ ]);
24
+ ast.program.body.unshift(variableDeclaration);
25
+ }
26
+ /**
27
+ * Adds an ESM import declaration for the @generaltranslation/compiler package
28
+ * @param ast - The ast of the file
29
+ * import { vite as gtCompiler } from '@generaltranslation/compiler';
30
+ */
31
+ function handleEsmImport(ast) {
32
+ const importDeclaration = t.importDeclaration([t.importSpecifier(t.identifier('gtCompiler'), t.identifier('vite'))], t.stringLiteral('@generaltranslation/compiler'));
33
+ ast.program.body.unshift(importDeclaration);
34
+ }
@@ -0,0 +1,11 @@
1
+ import * as t from '@babel/types';
2
+ import { ParseResult } from '@babel/parser';
3
+ /**
4
+ * Adds the plugin invocation to the vite config file
5
+ * Naive solution: look for an object with a plugins property only inside of a defineConfig call
6
+ */
7
+ export declare function addPluginInvocation({ ast, alias, namespaces, }: {
8
+ ast: ParseResult<t.File>;
9
+ alias: string | null;
10
+ namespaces: string[];
11
+ }): boolean;
@@ -0,0 +1,48 @@
1
+ import * as t from '@babel/types';
2
+ import traverseModule from '@babel/traverse';
3
+ // Handle CommonJS/ESM interop
4
+ const traverse = traverseModule.default || traverseModule;
5
+ /**
6
+ * Adds the plugin invocation to the vite config file
7
+ * Naive solution: look for an object with a plugins property only inside of a defineConfig call
8
+ */
9
+ export function addPluginInvocation({ ast, alias, namespaces, }) {
10
+ let addedPlugin = false;
11
+ if (namespaces.length === 0 && !alias) {
12
+ return false;
13
+ }
14
+ const pluginInvocation = alias
15
+ ? t.callExpression(t.identifier(alias), [])
16
+ : t.callExpression(t.memberExpression(t.identifier(namespaces[0]), t.identifier('vite')), []);
17
+ traverse(ast, {
18
+ CallExpression(path) {
19
+ if (!t.isIdentifier(path.node.callee, { name: 'defineConfig' }) ||
20
+ !path.node.arguments.length ||
21
+ !t.isObjectExpression(path.node.arguments[0])) {
22
+ return;
23
+ }
24
+ for (const property of path.node.arguments[0].properties) {
25
+ if (!t.isObjectProperty(property) || !isPluginsProperty(property))
26
+ continue;
27
+ if (t.isArrayExpression(property.value)) {
28
+ // Add to array: [react()] -> [react(), gtCompiler()]
29
+ property.value.elements.push(pluginInvocation);
30
+ addedPlugin = true;
31
+ }
32
+ else {
33
+ // Spread the array: someList -> [...someList, gtCompiler()]
34
+ property.value = t.arrayExpression([
35
+ t.spreadElement(property.value),
36
+ pluginInvocation,
37
+ ]);
38
+ addedPlugin = true;
39
+ }
40
+ }
41
+ },
42
+ });
43
+ return addedPlugin;
44
+ }
45
+ function isPluginsProperty(node) {
46
+ return (t.isIdentifier(node.key, { name: 'plugins' }) ||
47
+ t.isStringLiteral(node.key, { value: 'plugins' }));
48
+ }
@@ -0,0 +1,15 @@
1
+ import { ParseResult } from '@babel/parser';
2
+ import * as t from '@babel/types';
3
+ export interface CheckCompilerImportResult {
4
+ hasCompilerImport: boolean;
5
+ alias: string | null;
6
+ namespaces: string[];
7
+ }
8
+ /**
9
+ * Given the vite config file ast, checks if the @generaltranslation/compiler package is imported.
10
+ * If it is imported, capture any aliases or namespace.
11
+ *
12
+ * Does not handle:
13
+ * - Nested expressions
14
+ */
15
+ export declare function checkCompilerImport(ast: ParseResult<t.File>): CheckCompilerImportResult;
@@ -0,0 +1,113 @@
1
+ import * as t from '@babel/types';
2
+ import traverseModule from '@babel/traverse';
3
+ // Handle CommonJS/ESM interop
4
+ const traverse = traverseModule.default || traverseModule;
5
+ /**
6
+ * Given the vite config file ast, checks if the @generaltranslation/compiler package is imported.
7
+ * If it is imported, capture any aliases or namespace.
8
+ *
9
+ * Does not handle:
10
+ * - Nested expressions
11
+ */
12
+ export function checkCompilerImport(ast) {
13
+ const result = {
14
+ hasCompilerImport: false,
15
+ alias: null,
16
+ namespaces: [],
17
+ };
18
+ traverse(ast, {
19
+ ImportDeclaration(path) {
20
+ handleImportDeclaration(path, result);
21
+ },
22
+ VariableDeclaration(path) {
23
+ handleVariableDeclaration(path, result);
24
+ },
25
+ });
26
+ return result;
27
+ }
28
+ /* =============================== */
29
+ /* Parsing Functions */
30
+ /* =============================== */
31
+ /**
32
+ * Checks an import declaration for a compiler import
33
+ * @param path - The import declaration path
34
+ * @param result - The result object
35
+ */
36
+ function handleImportDeclaration(path, result) {
37
+ if (path.node.source.value !== '@generaltranslation/compiler')
38
+ return;
39
+ for (const spec of path.node.specifiers) {
40
+ if (t.isImportSpecifier(spec)) {
41
+ // Handle named import: import { vite as gtCompiler } from '@generaltranslation/compiler'
42
+ if (t.isIdentifier(spec.imported) && spec.imported.name === 'vite') {
43
+ result.hasCompilerImport = true;
44
+ result.alias = spec.local.name;
45
+ }
46
+ }
47
+ else {
48
+ // Handle default import: import gtCompiler from '@generaltranslation/compiler'
49
+ // Handle namespace import: import * as gtCompiler from '@generaltranslation/compiler'
50
+ result.hasCompilerImport = true;
51
+ result.namespaces.push(spec.local.name);
52
+ }
53
+ }
54
+ }
55
+ /**
56
+ * Handles a variable declaration for a compiler import
57
+ * @param path - The variable declaration path
58
+ * @param result - The result object
59
+ */
60
+ function handleVariableDeclaration(path, result) {
61
+ path.node.declarations.forEach((dec) => {
62
+ // Handle destructuring: const { withGTConfig } = require('@generaltranslation/compiler')
63
+ if (t.isCallExpression(dec.init) &&
64
+ t.isIdentifier(dec.init.callee, { name: 'require' }) &&
65
+ t.isStringLiteral(dec.init.arguments[0], {
66
+ value: '@generaltranslation/compiler',
67
+ })) {
68
+ if (t.isIdentifier(dec.id)) {
69
+ // Handle namespace assignment: const gtCompiler = require('@generaltranslation/compiler')
70
+ result.hasCompilerImport = true;
71
+ result.namespaces.push(dec.id.name);
72
+ }
73
+ else if (t.isObjectPattern(dec.id)) {
74
+ // Handle destructuring: const { vite: gtCompiler } = require('@generaltranslation/compiler')
75
+ let foundVite = false;
76
+ const restElements = [];
77
+ for (const prop of dec.id.properties) {
78
+ if (t.isObjectProperty(prop) &&
79
+ t.isIdentifier(prop.key) &&
80
+ t.isIdentifier(prop.value) &&
81
+ prop.key.name === 'vite') {
82
+ // Handle destructing alias assignment: const { vite: gtCompiler } = require('@generaltranslation/compiler')
83
+ result.hasCompilerImport = true;
84
+ result.alias = prop.value.name;
85
+ foundVite = true;
86
+ break;
87
+ }
88
+ else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
89
+ // Track list of rest elements
90
+ restElements.push(prop.argument.name);
91
+ }
92
+ }
93
+ // Handle destructuring rest elements: const { ...some, b, ...others, d } = require('@generaltranslation/compiler')
94
+ if (!foundVite && restElements.length > 0) {
95
+ result.hasCompilerImport = true;
96
+ result.namespaces.push(...restElements);
97
+ }
98
+ }
99
+ }
100
+ else if (t.isMemberExpression(dec.init) &&
101
+ t.isCallExpression(dec.init.object) &&
102
+ t.isIdentifier(dec.init.object.callee, { name: 'require' }) &&
103
+ t.isStringLiteral(dec.init.object.arguments[0], {
104
+ value: '@generaltranslation/compiler',
105
+ }) &&
106
+ t.isIdentifier(dec.init.property, { name: 'vite' }) &&
107
+ t.isIdentifier(dec.id)) {
108
+ // Handle member access: const gtCompiler = require('@generaltranslation/compiler').vite
109
+ result.hasCompilerImport = true;
110
+ result.alias = dec.id.name;
111
+ }
112
+ });
113
+ }
@@ -0,0 +1,12 @@
1
+ import { ParseResult } from '@babel/parser';
2
+ import * as t from '@babel/types';
3
+ /**
4
+ * Checks if the given AST is exporting the gtCompiler plugin
5
+ *
6
+ * Naive check: see if the plugin has been invoked anywhere in the file
7
+ */
8
+ export declare function checkPluginInvocation({ ast, alias, namespaces, }: {
9
+ ast: ParseResult<t.File>;
10
+ alias: string | null;
11
+ namespaces: string[];
12
+ }): boolean;
@@ -0,0 +1,32 @@
1
+ import * as t from '@babel/types';
2
+ import traverseModule from '@babel/traverse';
3
+ // Handle CommonJS/ESM interop
4
+ const traverse = traverseModule.default || traverseModule;
5
+ /**
6
+ * Checks if the given AST is exporting the gtCompiler plugin
7
+ *
8
+ * Naive check: see if the plugin has been invoked anywhere in the file
9
+ */
10
+ export function checkPluginInvocation({ ast, alias, namespaces, }) {
11
+ let result = false;
12
+ traverse(ast, {
13
+ CallExpression(path) {
14
+ // Handle: gtCompiler()
15
+ if (!alias)
16
+ return;
17
+ if (t.isIdentifier(path.node.callee, { name: alias })) {
18
+ result = true;
19
+ }
20
+ },
21
+ MemberExpression(path) {
22
+ // Handle: gtCompiler.vite()
23
+ if (t.isIdentifier(path.node.object) &&
24
+ namespaces.includes(path.node.object.name) &&
25
+ t.isCallExpression(path.parent) &&
26
+ t.isIdentifier(path.node.property, { name: 'vite' })) {
27
+ result = true;
28
+ }
29
+ },
30
+ });
31
+ return result;
32
+ }
@@ -12,6 +12,7 @@ import { getPackageManager } from '../utils/packageManager.js';
12
12
  import { installPackage } from '../utils/installPackage.js';
13
13
  import { createOrUpdateConfig } from '../fs/config/setupConfig.js';
14
14
  import { loadConfig } from '../fs/config/loadConfig.js';
15
+ import { addVitePlugin } from '../react/parse/addVitePlugin/index.js';
15
16
  export async function handleSetupReactCommand(options) {
16
17
  // Ask user for confirmation using inquirer
17
18
  const answer = await promptConfirm({
@@ -73,6 +74,9 @@ Please let us know what you would like to see supported at https://github.com/ge
73
74
  const errors = [];
74
75
  const warnings = [];
75
76
  let filesUpdated = [];
77
+ // Read tsconfig.json if it exists
78
+ const tsconfigPath = findFilepath(['tsconfig.json']);
79
+ const tsconfigJson = tsconfigPath ? loadConfig(tsconfigPath) : undefined;
76
80
  if (frameworkType === 'next-app') {
77
81
  // Check if they have a next.config.js file
78
82
  const nextConfigPath = findFilepath([
@@ -98,13 +102,20 @@ Please let us know what you would like to see supported at https://github.com/ge
98
102
  const { filesUpdated: filesUpdatedNext } = await wrapContentNext(mergeOptions, 'gt-next', errors, warnings);
99
103
  filesUpdated = [...filesUpdated, ...filesUpdatedNext];
100
104
  spinner.stop(chalk.green(`Success! Updated ${chalk.bold.cyan(filesUpdated.length)} files:\n`) + filesUpdated.map((file) => `${chalk.green('-')} ${file}`).join('\n'));
101
- // Read tsconfig.json if it exists
102
- const tsconfigPath = findFilepath(['tsconfig.json']);
103
- const tsconfigJson = tsconfigPath ? loadConfig(tsconfigPath) : undefined;
104
105
  // Add the withGTConfig() function to the next.config.js file
105
106
  await handleInitGT(nextConfigPath, errors, warnings, filesUpdated, packageJson, tsconfigJson);
106
107
  logStep(chalk.green(`Added withGTConfig() to your ${nextConfigPath} file.`));
107
108
  }
109
+ // Add gt compiler plugin
110
+ if (frameworkType === 'vite') {
111
+ await addVitePlugin({
112
+ errors,
113
+ warnings,
114
+ filesUpdated,
115
+ packageJson,
116
+ tsconfigJson,
117
+ });
118
+ }
108
119
  if (errors.length > 0) {
109
120
  logError(chalk.red('Failed to write files:\n') + errors.join('\n'));
110
121
  }
@@ -41,6 +41,7 @@ export type TranslateFlags = {
41
41
  stageTranslations?: boolean;
42
42
  publish?: boolean;
43
43
  force?: boolean;
44
+ forceDownload?: boolean;
44
45
  experimentalLocalizeStaticUrls?: boolean;
45
46
  experimentalHideDefaultLocale?: boolean;
46
47
  experimentalFlattenJsonFiles?: boolean;
@@ -0,0 +1,20 @@
1
+ import { ParseResult } from '@babel/parser';
2
+ import * as t from '@babel/types';
3
+ /**
4
+ * Given an AST determines if the file needs to be compiled as CommonJS or ESM.
5
+ * @param ast - The AST to analyze
6
+ * @returns True if the file needs to be compiled as CommonJS, false if it needs to be compiled as ESM
7
+ */
8
+ export declare function needsCJS({ ast, warnings, filepath, packageJson, tsconfigJson, }: {
9
+ ast: ParseResult<t.File>;
10
+ warnings: string[];
11
+ filepath: string;
12
+ packageJson?: {
13
+ type?: string;
14
+ };
15
+ tsconfigJson?: {
16
+ compilerOptions?: {
17
+ module?: string;
18
+ };
19
+ };
20
+ }): boolean;
@@ -0,0 +1,72 @@
1
+ import traverseModule from '@babel/traverse';
2
+ import * as t from '@babel/types';
3
+ // Handle CommonJS/ESM interop
4
+ const traverse = traverseModule.default || traverseModule;
5
+ /**
6
+ * Given an AST determines if the file needs to be compiled as CommonJS or ESM.
7
+ * @param ast - The AST to analyze
8
+ * @returns True if the file needs to be compiled as CommonJS, false if it needs to be compiled as ESM
9
+ */
10
+ export function needsCJS({ ast, warnings, filepath, packageJson, tsconfigJson, }) {
11
+ // Analyze the actual file content to determine module system
12
+ let hasES6Imports = false;
13
+ let hasCommonJSRequire = false;
14
+ traverse(ast, {
15
+ ImportDeclaration() {
16
+ hasES6Imports = true;
17
+ },
18
+ CallExpression(path) {
19
+ if (t.isIdentifier(path.node.callee, { name: 'require' })) {
20
+ hasCommonJSRequire = true;
21
+ }
22
+ },
23
+ });
24
+ // Determine if we need CommonJS based on actual file content and fallback to config-based logic
25
+ let needsCJS = false;
26
+ if (hasES6Imports && !hasCommonJSRequire) {
27
+ // File uses ES6 imports, so we should use ES6 imports
28
+ needsCJS = false;
29
+ }
30
+ else if (hasCommonJSRequire && !hasES6Imports) {
31
+ // File uses CommonJS require, so we should use CommonJS require
32
+ needsCJS = true;
33
+ }
34
+ else if (hasES6Imports && hasCommonJSRequire) {
35
+ // Mixed usage - this is unusual but we'll default to ES6 imports
36
+ warnings.push(`Mixed ES6 imports and CommonJS require detected in ${filepath}. Defaulting to ES6 imports.`);
37
+ needsCJS = false;
38
+ }
39
+ else {
40
+ // No imports/requires found, fall back to configuration-based logic
41
+ if (filepath.endsWith('.ts') || filepath.endsWith('.tsx')) {
42
+ // For TypeScript files, check tsconfig.json compilerOptions.module
43
+ const moduleSetting = tsconfigJson?.compilerOptions?.module;
44
+ if (moduleSetting === 'commonjs' || moduleSetting === 'node') {
45
+ needsCJS = true;
46
+ }
47
+ else if (moduleSetting === 'esnext' ||
48
+ moduleSetting === 'es2022' ||
49
+ moduleSetting === 'es2020' ||
50
+ moduleSetting === 'es2015' ||
51
+ moduleSetting === 'es6' ||
52
+ moduleSetting === 'node16' ||
53
+ moduleSetting === 'nodenext') {
54
+ needsCJS = false;
55
+ }
56
+ else {
57
+ // Default to ESM for TypeScript files if no module setting is specified
58
+ needsCJS = false;
59
+ }
60
+ }
61
+ else if (filepath.endsWith('.js')) {
62
+ // For JavaScript files, check package.json type
63
+ // If package.json has "type": "module", .js files are treated as ES modules
64
+ needsCJS = packageJson?.type !== 'module';
65
+ }
66
+ else {
67
+ // For other file extensions, default to ESM
68
+ needsCJS = false;
69
+ }
70
+ }
71
+ return needsCJS;
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.3.10",
3
+ "version": "2.3.12",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [