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.
- package/CHANGELOG.md +12 -0
- package/dist/api/checkFileTranslations.d.ts +1 -1
- package/dist/api/checkFileTranslations.js +11 -5
- package/dist/api/downloadFileBatch.d.ts +3 -1
- package/dist/api/downloadFileBatch.js +41 -1
- package/dist/cli/commands/translate.js +3 -3
- package/dist/cli/flags.js +1 -0
- package/dist/fs/config/downloadedVersions.d.ts +12 -0
- package/dist/fs/config/downloadedVersions.js +30 -0
- package/dist/next/parse/handleInitGT.js +9 -60
- package/dist/react/parse/addVitePlugin/index.d.ts +22 -0
- package/dist/react/parse/addVitePlugin/index.js +40 -0
- package/dist/react/parse/addVitePlugin/installCompiler.d.ts +8 -0
- package/dist/react/parse/addVitePlugin/installCompiler.js +22 -0
- package/dist/react/parse/addVitePlugin/updateViteConfig.d.ts +19 -0
- package/dist/react/parse/addVitePlugin/updateViteConfig.js +123 -0
- package/dist/react/parse/addVitePlugin/utils/addCompilerImport.d.ts +9 -0
- package/dist/react/parse/addVitePlugin/utils/addCompilerImport.js +34 -0
- package/dist/react/parse/addVitePlugin/utils/addPluginInvocation.d.ts +11 -0
- package/dist/react/parse/addVitePlugin/utils/addPluginInvocation.js +48 -0
- package/dist/react/parse/addVitePlugin/utils/checkCompilerImport.d.ts +15 -0
- package/dist/react/parse/addVitePlugin/utils/checkCompilerImport.js +113 -0
- package/dist/react/parse/addVitePlugin/utils/checkPluginInvocation.d.ts +12 -0
- package/dist/react/parse/addVitePlugin/utils/checkPluginInvocation.js +32 -0
- package/dist/setup/wizard.js +14 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/parse/needsCJS.d.ts +20 -0
- package/dist/utils/parse/needsCJS.js +72 -0
- 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
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
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,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
|
+
}
|
package/dist/setup/wizard.js
CHANGED
|
@@ -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
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
+
}
|