gt 2.10.2 → 2.10.3
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 +6 -0
- package/dist/cli/base.d.ts +0 -2
- package/dist/cli/base.js +1 -70
- package/dist/cli/node.js +7 -0
- package/dist/cli/react.js +7 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/types/libraries.d.ts +9 -3
- package/dist/types/libraries.js +14 -0
- package/dist/utils/monorepoVersionCheck.d.ts +8 -0
- package/dist/utils/monorepoVersionCheck.js +176 -0
- package/package.json +3 -3
- package/dist/setup/agentInstructions.d.ts +0 -24
- package/dist/setup/agentInstructions.js +0 -138
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.10.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1110](https://github.com/generaltranslation/gt/pull/1110) [`38ecda0`](https://github.com/generaltranslation/gt/commit/38ecda003b6873464e350aff0463a8dc64030565) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Adding monorepo GT package version check, remove writing to agent files
|
|
8
|
+
|
|
3
9
|
## 2.10.2
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/cli/base.d.ts
CHANGED
|
@@ -55,6 +55,4 @@ export declare class BaseCLI {
|
|
|
55
55
|
protected handleUploadCommand(settings: Settings & UploadOptions): Promise<void>;
|
|
56
56
|
protected handleInitCommand(ranReactSetup: boolean, useDefaults?: boolean): Promise<void>;
|
|
57
57
|
protected handleLoginCommand(options: LoginOptions): Promise<void>;
|
|
58
|
-
protected setupUpdateInstructionsCommand(): void;
|
|
59
|
-
protected promptAgentInstructions(useDefaults?: boolean): Promise<void>;
|
|
60
58
|
}
|
package/dist/cli/base.js
CHANGED
|
@@ -30,8 +30,6 @@ import processSharedStaticAssets, { mirrorAssetsToLocales, } from '../utils/shar
|
|
|
30
30
|
import { setupLocadex } from '../locadex/setupFlow.js';
|
|
31
31
|
import { detectFramework } from '../setup/detectFramework.js';
|
|
32
32
|
import { getFrameworkDisplayName, getReactFrameworkLibrary, } from '../setup/frameworkUtils.js';
|
|
33
|
-
import { findAgentFiles, findAgentFilesWithInstructions, hasCursorRulesDir, CURSOR_GT_RULES_FILE, getAgentInstructions, appendAgentInstructions, } from '../setup/agentInstructions.js';
|
|
34
|
-
import { determineLibrary } from '../fs/determineFramework/index.js';
|
|
35
33
|
import { INLINE_LIBRARIES } from '../types/libraries.js';
|
|
36
34
|
import { handleEnqueue } from './commands/enqueue.js';
|
|
37
35
|
export class BaseCLI {
|
|
@@ -43,12 +41,12 @@ export class BaseCLI {
|
|
|
43
41
|
this.program = program;
|
|
44
42
|
this.library = library;
|
|
45
43
|
this.additionalModules = additionalModules || [];
|
|
44
|
+
this.program.option('--skip-version-check', 'Skip the monorepo GT package version consistency check');
|
|
46
45
|
this.setupInitCommand();
|
|
47
46
|
this.setupConfigureCommand();
|
|
48
47
|
this.setupUploadCommand();
|
|
49
48
|
this.setupLoginCommand();
|
|
50
49
|
this.setupSendDiffsCommand();
|
|
51
|
-
this.setupUpdateInstructionsCommand();
|
|
52
50
|
}
|
|
53
51
|
// Init is never called in a child class
|
|
54
52
|
init() {
|
|
@@ -260,7 +258,6 @@ export class BaseCLI {
|
|
|
260
258
|
})();
|
|
261
259
|
if (useAgent) {
|
|
262
260
|
await setupLocadex(settings);
|
|
263
|
-
await this.promptAgentInstructions();
|
|
264
261
|
logger.endCommand('Once installed, Locadex will open a PR to your repository. See the docs for more information: https://generaltranslation.com/docs/locadex');
|
|
265
262
|
}
|
|
266
263
|
else {
|
|
@@ -302,7 +299,6 @@ export class BaseCLI {
|
|
|
302
299
|
}
|
|
303
300
|
// Configure gt.config.json
|
|
304
301
|
await this.handleInitCommand(ranReactSetup, useDefaults);
|
|
305
|
-
await this.promptAgentInstructions(useDefaults);
|
|
306
302
|
logger.endCommand('Done! Check out our docs for more information on how to use General Translation: https://generaltranslation.com/docs');
|
|
307
303
|
}
|
|
308
304
|
});
|
|
@@ -474,69 +470,4 @@ See https://generaltranslation.com/en/docs/next/guides/local-tx`);
|
|
|
474
470
|
const credentials = await retrieveCredentials(settings, keyType);
|
|
475
471
|
await setCredentials(credentials, settings.framework);
|
|
476
472
|
}
|
|
477
|
-
setupUpdateInstructionsCommand() {
|
|
478
|
-
this.program
|
|
479
|
-
.command('update-instructions')
|
|
480
|
-
.description('Update GT usage instructions in AI agent files')
|
|
481
|
-
.option('--new', 'Add instructions to all agent files, even those without existing GT instructions')
|
|
482
|
-
.action(async (options) => {
|
|
483
|
-
const agentFiles = options.new
|
|
484
|
-
? findAgentFiles()
|
|
485
|
-
: findAgentFilesWithInstructions();
|
|
486
|
-
if (options.new &&
|
|
487
|
-
hasCursorRulesDir() &&
|
|
488
|
-
!agentFiles.includes(CURSOR_GT_RULES_FILE)) {
|
|
489
|
-
agentFiles.push(CURSOR_GT_RULES_FILE);
|
|
490
|
-
}
|
|
491
|
-
if (agentFiles.length === 0) {
|
|
492
|
-
logger.warn(options.new
|
|
493
|
-
? 'No agent files found. Create a CLAUDE.md or similar agent file first.'
|
|
494
|
-
: 'No agent files with GT instructions found. Use --new to add instructions to existing agent files.');
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
const { library } = determineLibrary();
|
|
498
|
-
const instructions = getAgentInstructions(library);
|
|
499
|
-
let updatedCount = 0;
|
|
500
|
-
for (const file of agentFiles) {
|
|
501
|
-
if (appendAgentInstructions(file, instructions)) {
|
|
502
|
-
updatedCount++;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (updatedCount > 0) {
|
|
506
|
-
logger.success(`Updated GT instructions in ${updatedCount} file${updatedCount > 1 ? 's' : ''}.`);
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
logger.info('All agent instruction files are already up to date.');
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
async promptAgentInstructions(useDefaults = false) {
|
|
514
|
-
const agentFiles = findAgentFiles();
|
|
515
|
-
// Include .cursor/rules/gt-i18n.mdc if the directory exists but the file doesn't yet
|
|
516
|
-
if (hasCursorRulesDir() && !agentFiles.includes(CURSOR_GT_RULES_FILE)) {
|
|
517
|
-
agentFiles.push(CURSOR_GT_RULES_FILE);
|
|
518
|
-
}
|
|
519
|
-
if (agentFiles.length === 0)
|
|
520
|
-
return;
|
|
521
|
-
const addInstructions = useDefaults
|
|
522
|
-
? true
|
|
523
|
-
: await promptConfirm({
|
|
524
|
-
message: `Found AI agent instruction files (${agentFiles.map((f) => path.basename(f)).join(', ')}). Would you like to add GT usage instructions?`,
|
|
525
|
-
defaultValue: true,
|
|
526
|
-
});
|
|
527
|
-
if (addInstructions) {
|
|
528
|
-
// Re-detect library since packages may have been installed during init
|
|
529
|
-
const { library } = determineLibrary();
|
|
530
|
-
const instructions = getAgentInstructions(library);
|
|
531
|
-
let updatedCount = 0;
|
|
532
|
-
for (const file of agentFiles) {
|
|
533
|
-
if (appendAgentInstructions(file, instructions)) {
|
|
534
|
-
updatedCount++;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (updatedCount > 0) {
|
|
538
|
-
logger.success('Added GT instructions to agent files.');
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
473
|
}
|
package/dist/cli/node.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { InlineCLI } from './inline.js';
|
|
2
|
+
import { NODE_LIBRARIES } from '../types/libraries.js';
|
|
3
|
+
import { checkMonorepoVersionConsistency } from '../utils/monorepoVersionCheck.js';
|
|
2
4
|
/**
|
|
3
5
|
* CLI tool for managing translations with gt-node
|
|
4
6
|
*/
|
|
5
7
|
export class NodeCLI extends InlineCLI {
|
|
6
8
|
constructor(command, library, additionalModules) {
|
|
7
9
|
super(command, library, additionalModules);
|
|
10
|
+
this.program.hook('preAction', () => {
|
|
11
|
+
if (this.program.opts().skipVersionCheck)
|
|
12
|
+
return;
|
|
13
|
+
checkMonorepoVersionConsistency(NODE_LIBRARIES);
|
|
14
|
+
});
|
|
8
15
|
}
|
|
9
16
|
}
|
package/dist/cli/react.js
CHANGED
|
@@ -6,11 +6,17 @@ import { wrapContentReact } from '../react/parse/wrapContent.js';
|
|
|
6
6
|
import { generateSettings } from '../config/generateSettings.js';
|
|
7
7
|
import { attachInlineTranslateFlags, attachTranslateFlags } from './flags.js';
|
|
8
8
|
import { InlineCLI } from './inline.js';
|
|
9
|
-
import { Libraries } from '../types/libraries.js';
|
|
9
|
+
import { Libraries, REACT_LIBRARIES } from '../types/libraries.js';
|
|
10
|
+
import { checkMonorepoVersionConsistency } from '../utils/monorepoVersionCheck.js';
|
|
10
11
|
const pkg = Libraries.GT_REACT;
|
|
11
12
|
export class ReactCLI extends InlineCLI {
|
|
12
13
|
constructor(command, library, additionalModules) {
|
|
13
14
|
super(command, library, additionalModules);
|
|
15
|
+
this.program.hook('preAction', () => {
|
|
16
|
+
if (this.program.opts().skipVersionCheck)
|
|
17
|
+
return;
|
|
18
|
+
checkMonorepoVersionConsistency([...REACT_LIBRARIES, Libraries.GT_I18N]);
|
|
19
|
+
});
|
|
14
20
|
}
|
|
15
21
|
init() {
|
|
16
22
|
super.init();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "2.10.
|
|
1
|
+
export declare const PACKAGE_VERSION = "2.10.3";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
|
-
export const PACKAGE_VERSION = '2.10.
|
|
2
|
+
export const PACKAGE_VERSION = '2.10.3';
|
|
@@ -8,25 +8,31 @@ export declare enum Libraries {
|
|
|
8
8
|
GT_NODE = "gt-node",
|
|
9
9
|
GT_I18N = "gt-i18n",
|
|
10
10
|
GT_REACT_CORE = "@generaltranslation/react-core",
|
|
11
|
+
GT_TANSTACK_START = "gt-tanstack-start",
|
|
11
12
|
GT_FLASK = "gt-flask",
|
|
12
13
|
GT_FASTAPI = "gt-fastapi"
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* A list of all the libraries that support the CLI
|
|
16
17
|
*/
|
|
17
|
-
export declare const GT_LIBRARIES: readonly [Libraries.GT_REACT, Libraries.GT_NEXT, Libraries.GT_REACT_NATIVE, Libraries.GT_NODE, Libraries.GT_I18N, Libraries.GT_REACT_CORE, Libraries.GT_FLASK, Libraries.GT_FASTAPI];
|
|
18
|
+
export declare const GT_LIBRARIES: readonly [Libraries.GT_REACT, Libraries.GT_NEXT, Libraries.GT_REACT_NATIVE, Libraries.GT_NODE, Libraries.GT_I18N, Libraries.GT_REACT_CORE, Libraries.GT_TANSTACK_START, Libraries.GT_FLASK, Libraries.GT_FASTAPI];
|
|
18
19
|
export type GTLibrary = (typeof GT_LIBRARIES)[number];
|
|
19
20
|
/**
|
|
20
21
|
* Libraries that support inline translation
|
|
21
22
|
*/
|
|
22
|
-
export declare const INLINE_LIBRARIES: readonly [Libraries.GT_REACT, Libraries.GT_NEXT, Libraries.GT_NODE, Libraries.GT_REACT_NATIVE, Libraries.GT_REACT_CORE, Libraries.GT_I18N, Libraries.GT_FLASK, Libraries.GT_FASTAPI];
|
|
23
|
+
export declare const INLINE_LIBRARIES: readonly [Libraries.GT_REACT, Libraries.GT_NEXT, Libraries.GT_NODE, Libraries.GT_REACT_NATIVE, Libraries.GT_REACT_CORE, Libraries.GT_TANSTACK_START, Libraries.GT_I18N, Libraries.GT_FLASK, Libraries.GT_FASTAPI];
|
|
23
24
|
export type InlineLibrary = (typeof INLINE_LIBRARIES)[number];
|
|
24
25
|
export declare function isInlineLibrary(lib: string): lib is InlineLibrary;
|
|
25
26
|
/**
|
|
26
27
|
* Libraries that support react primitives
|
|
27
28
|
*/
|
|
28
|
-
export declare const REACT_LIBRARIES: readonly [Libraries.GT_NEXT, Libraries.GT_REACT, Libraries.GT_REACT_NATIVE, Libraries.GT_REACT_CORE];
|
|
29
|
+
export declare const REACT_LIBRARIES: readonly [Libraries.GT_NEXT, Libraries.GT_REACT, Libraries.GT_REACT_NATIVE, Libraries.GT_REACT_CORE, Libraries.GT_TANSTACK_START];
|
|
29
30
|
export type ReactLibrary = (typeof REACT_LIBRARIES)[number];
|
|
31
|
+
/**
|
|
32
|
+
* Node/server-side libraries
|
|
33
|
+
*/
|
|
34
|
+
export declare const NODE_LIBRARIES: readonly [Libraries.GT_NODE, Libraries.GT_I18N];
|
|
35
|
+
export type NodeLibrary = (typeof NODE_LIBRARIES)[number];
|
|
30
36
|
/**
|
|
31
37
|
* Python libraries
|
|
32
38
|
*/
|
package/dist/types/libraries.js
CHANGED
|
@@ -9,6 +9,7 @@ export var Libraries;
|
|
|
9
9
|
Libraries["GT_NODE"] = "gt-node";
|
|
10
10
|
Libraries["GT_I18N"] = "gt-i18n";
|
|
11
11
|
Libraries["GT_REACT_CORE"] = "@generaltranslation/react-core";
|
|
12
|
+
Libraries["GT_TANSTACK_START"] = "gt-tanstack-start";
|
|
12
13
|
Libraries["GT_FLASK"] = "gt-flask";
|
|
13
14
|
Libraries["GT_FASTAPI"] = "gt-fastapi";
|
|
14
15
|
})(Libraries || (Libraries = {}));
|
|
@@ -22,6 +23,7 @@ export const GT_LIBRARIES = [
|
|
|
22
23
|
Libraries.GT_NODE,
|
|
23
24
|
Libraries.GT_I18N,
|
|
24
25
|
Libraries.GT_REACT_CORE,
|
|
26
|
+
Libraries.GT_TANSTACK_START,
|
|
25
27
|
Libraries.GT_FLASK,
|
|
26
28
|
Libraries.GT_FASTAPI,
|
|
27
29
|
];
|
|
@@ -34,6 +36,7 @@ export const INLINE_LIBRARIES = [
|
|
|
34
36
|
Libraries.GT_NODE,
|
|
35
37
|
Libraries.GT_REACT_NATIVE,
|
|
36
38
|
Libraries.GT_REACT_CORE,
|
|
39
|
+
Libraries.GT_TANSTACK_START,
|
|
37
40
|
Libraries.GT_I18N,
|
|
38
41
|
Libraries.GT_FLASK,
|
|
39
42
|
Libraries.GT_FASTAPI,
|
|
@@ -49,7 +52,12 @@ export const REACT_LIBRARIES = [
|
|
|
49
52
|
Libraries.GT_REACT,
|
|
50
53
|
Libraries.GT_REACT_NATIVE,
|
|
51
54
|
Libraries.GT_REACT_CORE,
|
|
55
|
+
Libraries.GT_TANSTACK_START,
|
|
52
56
|
];
|
|
57
|
+
/**
|
|
58
|
+
* Node/server-side libraries
|
|
59
|
+
*/
|
|
60
|
+
export const NODE_LIBRARIES = [Libraries.GT_NODE, Libraries.GT_I18N];
|
|
53
61
|
/**
|
|
54
62
|
* Python libraries
|
|
55
63
|
*/
|
|
@@ -84,6 +92,12 @@ export const GT_LIBRARIES_UPSTREAM = {
|
|
|
84
92
|
],
|
|
85
93
|
[Libraries.GT_NODE]: [Libraries.GT_I18N, Libraries.GT_NODE],
|
|
86
94
|
[Libraries.GT_REACT_CORE]: [Libraries.GT_I18N, Libraries.GT_REACT_CORE],
|
|
95
|
+
[Libraries.GT_TANSTACK_START]: [
|
|
96
|
+
Libraries.GT_I18N,
|
|
97
|
+
Libraries.GT_REACT_CORE,
|
|
98
|
+
Libraries.GT_REACT,
|
|
99
|
+
Libraries.GT_TANSTACK_START,
|
|
100
|
+
],
|
|
87
101
|
[Libraries.GT_I18N]: [Libraries.GT_I18N],
|
|
88
102
|
[Libraries.GT_FLASK]: [Libraries.GT_FLASK],
|
|
89
103
|
[Libraries.GT_FASTAPI]: [Libraries.GT_FASTAPI],
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GTLibrary } from '../types/libraries.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run the monorepo version consistency check.
|
|
4
|
+
* If mismatched GT package versions are found, logs an error and exits with code 1.
|
|
5
|
+
* Silently returns if not in a monorepo or if all versions are consistent.
|
|
6
|
+
* Can be skipped via the --skip-version-check flag or "skipVersionCheck": true in gt.config.json.
|
|
7
|
+
*/
|
|
8
|
+
export declare function checkMonorepoVersionConsistency(libraries: readonly GTLibrary[]): void;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { logger } from '../console/logger.js';
|
|
6
|
+
import { resolveConfig } from '../config/resolveConfig.js';
|
|
7
|
+
const LOCKFILES = [
|
|
8
|
+
'pnpm-lock.yaml',
|
|
9
|
+
'package-lock.json',
|
|
10
|
+
'yarn.lock',
|
|
11
|
+
'bun.lock',
|
|
12
|
+
'bun.lockb',
|
|
13
|
+
'deno.lock',
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Walk up from startDir to find the monorepo root by looking for a lockfile.
|
|
17
|
+
*/
|
|
18
|
+
function findMonorepoRoot(startDir) {
|
|
19
|
+
let dir = startDir;
|
|
20
|
+
while (true) {
|
|
21
|
+
if (LOCKFILES.some((lf) => fs.existsSync(path.join(dir, lf)))) {
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
const parent = path.dirname(dir);
|
|
25
|
+
if (parent === dir)
|
|
26
|
+
return null;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Scan for all directories containing package.json under the root.
|
|
32
|
+
*/
|
|
33
|
+
function scanForPackageDirs(rootDir) {
|
|
34
|
+
const matches = fg.sync('**/package.json', {
|
|
35
|
+
cwd: rootDir,
|
|
36
|
+
absolute: true,
|
|
37
|
+
onlyFiles: true,
|
|
38
|
+
ignore: ['**/node_modules/**'],
|
|
39
|
+
});
|
|
40
|
+
return matches.map((m) => path.dirname(m)).filter((dir) => dir !== rootDir);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a cached reader for workspace package.json files.
|
|
44
|
+
* Cache is scoped to a single check invocation to avoid stale data.
|
|
45
|
+
*/
|
|
46
|
+
function createPackageJsonReader() {
|
|
47
|
+
const cache = new Map();
|
|
48
|
+
return (workspaceDir) => {
|
|
49
|
+
if (cache.has(workspaceDir))
|
|
50
|
+
return cache.get(workspaceDir) ?? null;
|
|
51
|
+
const pkgPath = path.join(workspaceDir, 'package.json');
|
|
52
|
+
if (!fs.existsSync(pkgPath)) {
|
|
53
|
+
cache.set(workspaceDir, null);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
58
|
+
cache.set(workspaceDir, pkg);
|
|
59
|
+
return pkg;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
cache.set(workspaceDir, null);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if a version specifier is a real semver version
|
|
69
|
+
*/
|
|
70
|
+
function isSemverSpecifier(version) {
|
|
71
|
+
return /^[\^~>=<*]?\d/.test(version);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the declared version specifier for a GT package from a workspace's package.json.
|
|
75
|
+
* Returns null if the package is not found or uses a non-semver protocol
|
|
76
|
+
* (e.g. workspace:*, link:, file:, etc.)
|
|
77
|
+
*/
|
|
78
|
+
function getDeclaredVersion(packageName, workspaceDir, readPkgJson) {
|
|
79
|
+
const pkg = readPkgJson(workspaceDir);
|
|
80
|
+
if (!pkg)
|
|
81
|
+
return null;
|
|
82
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
83
|
+
const version = deps[packageName];
|
|
84
|
+
if (!version || !isSemverSpecifier(version))
|
|
85
|
+
return null;
|
|
86
|
+
return version;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Scan all packages for mismatched GT package version specifiers.
|
|
90
|
+
* Compares the declared versions in each package.json directly.
|
|
91
|
+
*/
|
|
92
|
+
function findVersionMismatches(workspaceDirs, readPkgJson, libraries) {
|
|
93
|
+
// Map: packageName -> Map<versionSpecifier, workspaceNames[]>
|
|
94
|
+
const packageVersions = new Map();
|
|
95
|
+
for (const wsDir of workspaceDirs) {
|
|
96
|
+
const wsName = getWorkspaceName(wsDir, readPkgJson);
|
|
97
|
+
for (const pkg of libraries) {
|
|
98
|
+
const version = getDeclaredVersion(pkg, wsDir, readPkgJson);
|
|
99
|
+
if (!version)
|
|
100
|
+
continue;
|
|
101
|
+
if (!packageVersions.has(pkg)) {
|
|
102
|
+
packageVersions.set(pkg, new Map());
|
|
103
|
+
}
|
|
104
|
+
const versionMap = packageVersions.get(pkg);
|
|
105
|
+
if (!versionMap.has(version)) {
|
|
106
|
+
versionMap.set(version, []);
|
|
107
|
+
}
|
|
108
|
+
versionMap.get(version).push(wsName);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const mismatches = [];
|
|
112
|
+
for (const [packageName, versionMap] of packageVersions) {
|
|
113
|
+
if (versionMap.size > 1) {
|
|
114
|
+
const versions = [];
|
|
115
|
+
for (const [version, workspaces] of versionMap) {
|
|
116
|
+
versions.push({ version, workspaces });
|
|
117
|
+
}
|
|
118
|
+
// Sort by version descending so the latest appears first
|
|
119
|
+
versions.sort((a, b) => b.version.localeCompare(a.version, undefined, { numeric: true }));
|
|
120
|
+
mismatches.push({ packageName, versions });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return mismatches;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get a human-readable name for a workspace directory.
|
|
127
|
+
*/
|
|
128
|
+
function getWorkspaceName(wsDir, readPkgJson) {
|
|
129
|
+
const pkg = readPkgJson(wsDir);
|
|
130
|
+
return pkg?.name ?? path.basename(wsDir);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Format version mismatches into a human-readable error message.
|
|
134
|
+
*/
|
|
135
|
+
function formatMismatchError(mismatches) {
|
|
136
|
+
const lines = [
|
|
137
|
+
chalk.red.bold('Mismatched GT package versions detected across your monorepo!'),
|
|
138
|
+
'',
|
|
139
|
+
chalk.yellow('Please update all workspaces to use the same version of each GT package, then reinstall.'),
|
|
140
|
+
'',
|
|
141
|
+
];
|
|
142
|
+
for (const mismatch of mismatches) {
|
|
143
|
+
lines.push(chalk.white.bold(` ${mismatch.packageName}:`));
|
|
144
|
+
for (const { version, workspaces } of mismatch.versions) {
|
|
145
|
+
lines.push(` ${chalk.cyan(version)} ${chalk.dim('←')} ${workspaces.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
lines.push('');
|
|
148
|
+
}
|
|
149
|
+
lines.push(chalk.dim('To skip this check, use --skip-version-check or set "skipVersionCheck": true in gt.config.json.\n'));
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Run the monorepo version consistency check.
|
|
154
|
+
* If mismatched GT package versions are found, logs an error and exits with code 1.
|
|
155
|
+
* Silently returns if not in a monorepo or if all versions are consistent.
|
|
156
|
+
* Can be skipped via the --skip-version-check flag or "skipVersionCheck": true in gt.config.json.
|
|
157
|
+
*/
|
|
158
|
+
export function checkMonorepoVersionConsistency(libraries) {
|
|
159
|
+
const cwd = process.cwd();
|
|
160
|
+
// Check if skipped via config
|
|
161
|
+
const resolved = resolveConfig(cwd);
|
|
162
|
+
if (resolved?.config?.skipVersionCheck)
|
|
163
|
+
return;
|
|
164
|
+
const rootDir = findMonorepoRoot(cwd);
|
|
165
|
+
if (!rootDir)
|
|
166
|
+
return; // No lockfile found — nothing to check
|
|
167
|
+
const workspaceDirs = scanForPackageDirs(rootDir);
|
|
168
|
+
if (workspaceDirs.length <= 1)
|
|
169
|
+
return; // Single package — no mismatches possible
|
|
170
|
+
const readPkgJson = createPackageJsonReader();
|
|
171
|
+
const mismatches = findVersionMismatches(workspaceDirs, readPkgJson, libraries);
|
|
172
|
+
if (mismatches.length === 0)
|
|
173
|
+
return; // All consistent
|
|
174
|
+
logger.error(formatMismatchError(mismatches));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gt",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.3",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -110,9 +110,9 @@
|
|
|
110
110
|
"unified": "^11.0.5",
|
|
111
111
|
"unist-util-visit": "^5.0.0",
|
|
112
112
|
"yaml": "^2.8.0",
|
|
113
|
-
"@generaltranslation/python-extractor": "0.1.2",
|
|
114
113
|
"generaltranslation": "8.1.16",
|
|
115
|
-
"gt-remark": "1.0.5"
|
|
114
|
+
"gt-remark": "1.0.5",
|
|
115
|
+
"@generaltranslation/python-extractor": "0.1.2"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@babel/types": "^7.28.4",
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { SupportedLibraries } from '../types/index.js';
|
|
2
|
-
export declare const CURSOR_GT_RULES_FILE = ".cursor/rules/gt-i18n.mdc";
|
|
3
|
-
/**
|
|
4
|
-
* Detect existing AI agent instruction files in the project.
|
|
5
|
-
*/
|
|
6
|
-
export declare function findAgentFiles(): string[];
|
|
7
|
-
/**
|
|
8
|
-
* Find agent files that already contain GT instructions.
|
|
9
|
-
*/
|
|
10
|
-
export declare function findAgentFilesWithInstructions(): string[];
|
|
11
|
-
/**
|
|
12
|
-
* Check if the .cursor/rules/ directory exists (for offering to create gt-i18n.mdc).
|
|
13
|
-
*/
|
|
14
|
-
export declare function hasCursorRulesDir(): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Generate GT agent instructions content based on the detected library.
|
|
17
|
-
*/
|
|
18
|
-
export declare function getAgentInstructions(library: SupportedLibraries): string;
|
|
19
|
-
/**
|
|
20
|
-
* Append or replace GT instructions in an agent file.
|
|
21
|
-
* Skips writing if the file already contains identical instructions.
|
|
22
|
-
* For .cursor/rules/gt.md, writes the file fresh (dedicated GT rules file).
|
|
23
|
-
*/
|
|
24
|
-
export declare function appendAgentInstructions(filePath: string, instructions: string): boolean;
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { getCLIVersion, getPackageVersion } from '../utils/packageJson.js';
|
|
5
|
-
import { Libraries } from '../types/libraries.js';
|
|
6
|
-
const INSTRUCTIONS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'instructions');
|
|
7
|
-
const AGENT_FILE_PATHS = [
|
|
8
|
-
'CLAUDE.md',
|
|
9
|
-
'AGENTS.md',
|
|
10
|
-
'GPT.md',
|
|
11
|
-
'CHATGPT.md',
|
|
12
|
-
'.cursorrules',
|
|
13
|
-
];
|
|
14
|
-
const CURSOR_RULES_DIR = '.cursor/rules';
|
|
15
|
-
export const CURSOR_GT_RULES_FILE = '.cursor/rules/gt-i18n.mdc';
|
|
16
|
-
const GT_SECTION_START = '<!-- GT I18N RULES START -->';
|
|
17
|
-
const GT_SECTION_END = '<!-- GT I18N RULES END -->';
|
|
18
|
-
function getLibraryVersion(library) {
|
|
19
|
-
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
20
|
-
if (!fs.existsSync(packageJsonPath))
|
|
21
|
-
return undefined;
|
|
22
|
-
try {
|
|
23
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
24
|
-
return getPackageVersion(library, packageJson);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Detect existing AI agent instruction files in the project.
|
|
32
|
-
*/
|
|
33
|
-
export function findAgentFiles() {
|
|
34
|
-
const cwd = process.cwd();
|
|
35
|
-
const found = [];
|
|
36
|
-
for (const filePath of [...AGENT_FILE_PATHS, CURSOR_GT_RULES_FILE]) {
|
|
37
|
-
const fullPath = path.resolve(cwd, filePath);
|
|
38
|
-
if (fs.existsSync(fullPath)) {
|
|
39
|
-
found.push(filePath);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return found;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Find agent files that already contain GT instructions.
|
|
46
|
-
*/
|
|
47
|
-
export function findAgentFilesWithInstructions() {
|
|
48
|
-
const cwd = process.cwd();
|
|
49
|
-
const found = [];
|
|
50
|
-
for (const filePath of [...AGENT_FILE_PATHS, CURSOR_GT_RULES_FILE]) {
|
|
51
|
-
const fullPath = path.resolve(cwd, filePath);
|
|
52
|
-
if (fs.existsSync(fullPath)) {
|
|
53
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
54
|
-
if (content.includes(GT_SECTION_START)) {
|
|
55
|
-
found.push(filePath);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return found;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Check if the .cursor/rules/ directory exists (for offering to create gt-i18n.mdc).
|
|
63
|
-
*/
|
|
64
|
-
export function hasCursorRulesDir() {
|
|
65
|
-
const cursorRulesDir = path.resolve(process.cwd(), CURSOR_RULES_DIR);
|
|
66
|
-
return (fs.existsSync(cursorRulesDir) && fs.statSync(cursorRulesDir).isDirectory());
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Generate GT agent instructions content based on the detected library.
|
|
70
|
-
*/
|
|
71
|
-
export function getAgentInstructions(library) {
|
|
72
|
-
const libraryVersion = getLibraryVersion(library);
|
|
73
|
-
const versionLine = libraryVersion
|
|
74
|
-
? `- **${library}**: ${libraryVersion}\n- **gt**: v${getCLIVersion()}`
|
|
75
|
-
: `- **gt**: v${getCLIVersion()}`;
|
|
76
|
-
const base = fs.readFileSync(path.join(INSTRUCTIONS_DIR, 'base.md'), 'utf8');
|
|
77
|
-
let body = '';
|
|
78
|
-
const libToFile = {
|
|
79
|
-
[Libraries.GT_NEXT]: 'gt-next.md',
|
|
80
|
-
[Libraries.GT_REACT]: 'gt-react.md',
|
|
81
|
-
// TODO: add gt-react-native.md
|
|
82
|
-
[Libraries.GT_REACT_NATIVE]: 'gt-react.md',
|
|
83
|
-
};
|
|
84
|
-
const instructionFile = libToFile[library];
|
|
85
|
-
if (instructionFile) {
|
|
86
|
-
body += '\n\n'; // add two newlines between the base and the specific instructions
|
|
87
|
-
body += fs.readFileSync(path.join(INSTRUCTIONS_DIR, instructionFile), 'utf8');
|
|
88
|
-
}
|
|
89
|
-
return `${GT_SECTION_START}
|
|
90
|
-
|
|
91
|
-
${versionLine}
|
|
92
|
-
|
|
93
|
-
${base}${body}
|
|
94
|
-
${GT_SECTION_END}`;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Append or replace GT instructions in an agent file.
|
|
98
|
-
* Skips writing if the file already contains identical instructions.
|
|
99
|
-
* For .cursor/rules/gt.md, writes the file fresh (dedicated GT rules file).
|
|
100
|
-
*/
|
|
101
|
-
export function appendAgentInstructions(filePath, instructions) {
|
|
102
|
-
const fullPath = path.resolve(process.cwd(), filePath);
|
|
103
|
-
// For .cursor/rules/gt.md, write as a standalone file with frontmatter
|
|
104
|
-
if (filePath === CURSOR_GT_RULES_FILE) {
|
|
105
|
-
const cursorContent = `---\ndescription: GT internationalization instructions\nalwaysApply: true\n---\n${instructions}\n`;
|
|
106
|
-
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
107
|
-
if (fs.existsSync(fullPath)) {
|
|
108
|
-
const existing = fs.readFileSync(fullPath, 'utf8');
|
|
109
|
-
if (existing === cursorContent)
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
fs.writeFileSync(fullPath, cursorContent, 'utf8');
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
// For other files, read existing content and append/replace
|
|
116
|
-
let content = '';
|
|
117
|
-
if (fs.existsSync(fullPath)) {
|
|
118
|
-
content = fs.readFileSync(fullPath, 'utf8');
|
|
119
|
-
}
|
|
120
|
-
// Already has identical instructions — skip
|
|
121
|
-
if (content.includes(instructions))
|
|
122
|
-
return false;
|
|
123
|
-
const startIdx = content.indexOf(GT_SECTION_START);
|
|
124
|
-
const endIdx = startIdx !== -1 ? content.indexOf(GT_SECTION_END, startIdx) : -1;
|
|
125
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
126
|
-
// Replace existing section
|
|
127
|
-
const before = content.substring(0, startIdx);
|
|
128
|
-
const after = content.substring(endIdx + GT_SECTION_END.length);
|
|
129
|
-
content = before + instructions + after;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
// Append to end
|
|
133
|
-
const separator = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
|
|
134
|
-
content = content + separator + '\n' + instructions + '\n';
|
|
135
|
-
}
|
|
136
|
-
fs.writeFileSync(fullPath, content, 'utf8');
|
|
137
|
-
return true;
|
|
138
|
-
}
|