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 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
@@ -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.2";
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';
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
  */
@@ -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.2",
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
- }