@mmmbuto/qwen-code-termux 0.6.4-termux → 0.6.401

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.
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2025 Qwen
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import { glob } from 'glob';
12
+ import { fileURLToPath } from 'url';
13
+ import { dirname } from 'path';
14
+
15
+ // Get __dirname for ESM modules
16
+ // @ts-expect-error - import.meta is supported in NodeNext module system at runtime
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+
19
+ interface CheckResult {
20
+ success: boolean;
21
+ errors: string[];
22
+ warnings: string[];
23
+ stats: {
24
+ totalKeys: number;
25
+ translatedKeys: number;
26
+ unusedKeys: string[];
27
+ unusedKeysOnlyInLocales?: string[]; // 新增:只在 locales 中存在的未使用键
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Load translations from JS file
33
+ */
34
+ async function loadTranslationsFile(
35
+ filePath: string,
36
+ ): Promise<Record<string, string | string[]>> {
37
+ try {
38
+ // Dynamic import for ES modules
39
+ const module = await import(filePath);
40
+ return module.default || module;
41
+ } catch (error) {
42
+ // Fallback: try reading as JSON if JS import fails
43
+ try {
44
+ const content = fs.readFileSync(
45
+ filePath.replace('.js', '.json'),
46
+ 'utf-8',
47
+ );
48
+ return JSON.parse(content);
49
+ } catch {
50
+ throw error;
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Extract string literal from code, handling escaped quotes
57
+ */
58
+ function extractStringLiteral(
59
+ content: string,
60
+ startPos: number,
61
+ quote: string,
62
+ ): { value: string; endPos: number } | null {
63
+ let pos = startPos + 1; // Skip opening quote
64
+ let value = '';
65
+ let escaped = false;
66
+
67
+ while (pos < content.length) {
68
+ const char = content[pos];
69
+
70
+ if (escaped) {
71
+ if (char === '\\') {
72
+ value += '\\';
73
+ } else if (char === quote) {
74
+ value += quote;
75
+ } else if (char === 'n') {
76
+ value += '\n';
77
+ } else if (char === 't') {
78
+ value += '\t';
79
+ } else if (char === 'r') {
80
+ value += '\r';
81
+ } else {
82
+ value += char;
83
+ }
84
+ escaped = false;
85
+ } else if (char === '\\') {
86
+ escaped = true;
87
+ } else if (char === quote) {
88
+ return { value, endPos: pos };
89
+ } else {
90
+ value += char;
91
+ }
92
+
93
+ pos++;
94
+ }
95
+
96
+ return null; // String not closed
97
+ }
98
+
99
+ /**
100
+ * Extract all t() calls from source files
101
+ */
102
+ async function extractUsedKeys(sourceDir: string): Promise<Set<string>> {
103
+ const usedKeys = new Set<string>();
104
+
105
+ // Find all TypeScript/TSX files
106
+ const files = await glob('**/*.{ts,tsx}', {
107
+ cwd: sourceDir,
108
+ ignore: [
109
+ '**/node_modules/**',
110
+ '**/dist/**',
111
+ '**/*.test.ts',
112
+ '**/*.test.tsx',
113
+ ],
114
+ });
115
+
116
+ for (const file of files) {
117
+ const filePath = path.join(sourceDir, file);
118
+ try {
119
+ const content = fs.readFileSync(filePath, 'utf-8');
120
+
121
+ // Find all t( or ta( calls
122
+ const tCallRegex = /\bta?\s*\(/g;
123
+ let match;
124
+ while ((match = tCallRegex.exec(content)) !== null) {
125
+ const startPos = match.index + match[0].length;
126
+ let pos = startPos;
127
+
128
+ // Skip whitespace
129
+ while (pos < content.length && /\s/.test(content[pos])) {
130
+ pos++;
131
+ }
132
+
133
+ if (pos >= content.length) continue;
134
+
135
+ const char = content[pos];
136
+ if (char === "'" || char === '"') {
137
+ const result = extractStringLiteral(content, pos, char);
138
+ if (result) {
139
+ usedKeys.add(result.value);
140
+ }
141
+ }
142
+ }
143
+ } catch {
144
+ // Skip files that can't be read
145
+ continue;
146
+ }
147
+ }
148
+
149
+ return usedKeys;
150
+ }
151
+
152
+ /**
153
+ * Check key-value consistency in en.js
154
+ */
155
+ function checkKeyValueConsistency(
156
+ enTranslations: Record<string, string | string[]>,
157
+ ): string[] {
158
+ const errors: string[] = [];
159
+
160
+ for (const [key, value] of Object.entries(enTranslations)) {
161
+ // Skip array values as they don't follow the key=value rule (e.g., WITTY_LOADING_PHRASES)
162
+ if (Array.isArray(value)) {
163
+ continue;
164
+ }
165
+
166
+ if (key !== value) {
167
+ errors.push(`Key-value mismatch: "${key}" !== "${value}"`);
168
+ }
169
+ }
170
+
171
+ return errors;
172
+ }
173
+
174
+ /**
175
+ * Check if en.js and zh.js have matching keys
176
+ */
177
+ function checkKeyMatching(
178
+ enTranslations: Record<string, string | string[]>,
179
+ zhTranslations: Record<string, string | string[]>,
180
+ ): string[] {
181
+ const errors: string[] = [];
182
+ const enKeys = new Set(Object.keys(enTranslations));
183
+ const zhKeys = new Set(Object.keys(zhTranslations));
184
+
185
+ // Check for keys in en but not in zh
186
+ for (const key of enKeys) {
187
+ if (!zhKeys.has(key)) {
188
+ errors.push(`Missing translation in zh.js: "${key}"`);
189
+ }
190
+ }
191
+
192
+ // Check for keys in zh but not in en
193
+ for (const key of zhKeys) {
194
+ if (!enKeys.has(key)) {
195
+ errors.push(`Extra key in zh.js (not in en.js): "${key}"`);
196
+ }
197
+ }
198
+
199
+ return errors;
200
+ }
201
+
202
+ /**
203
+ * Find unused translation keys
204
+ */
205
+ function findUnusedKeys(allKeys: Set<string>, usedKeys: Set<string>): string[] {
206
+ return Array.from(allKeys)
207
+ .filter((key) => !usedKeys.has(key))
208
+ .sort();
209
+ }
210
+
211
+ /**
212
+ * Save keys that exist only in locale files to a JSON file
213
+ * @param keysOnlyInLocales Array of keys that exist only in locale files
214
+ * @param outputPath Path to save the JSON file
215
+ */
216
+ function saveKeysOnlyInLocalesToJson(
217
+ keysOnlyInLocales: string[],
218
+ outputPath: string,
219
+ ): void {
220
+ try {
221
+ const data = {
222
+ generatedAt: new Date().toISOString(),
223
+ keys: keysOnlyInLocales,
224
+ count: keysOnlyInLocales.length,
225
+ };
226
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2));
227
+ console.log(`Keys that exist only in locale files saved to: ${outputPath}`);
228
+ } catch (error) {
229
+ console.error(`Failed to save keys to JSON file: ${error}`);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Check if unused keys exist only in locale files and nowhere else in the codebase
235
+ * Optimized to search all keys in a single pass instead of multiple grep calls
236
+ * @param unusedKeys The list of unused keys to check
237
+ * @param sourceDir The source directory to search in
238
+ * @param localesDir The locales directory to exclude from search
239
+ * @returns Array of keys that exist only in locale files
240
+ */
241
+ async function findKeysOnlyInLocales(
242
+ unusedKeys: string[],
243
+ sourceDir: string,
244
+ localesDir: string,
245
+ ): Promise<string[]> {
246
+ if (unusedKeys.length === 0) {
247
+ return [];
248
+ }
249
+
250
+ const keysOnlyInLocales: string[] = [];
251
+ const localesDirName = path.basename(localesDir);
252
+
253
+ // Find all TypeScript/TSX files (excluding locales, node_modules, dist, and test files)
254
+ const files = await glob('**/*.{ts,tsx}', {
255
+ cwd: sourceDir,
256
+ ignore: [
257
+ '**/node_modules/**',
258
+ '**/dist/**',
259
+ '**/*.test.ts',
260
+ '**/*.test.tsx',
261
+ `**/${localesDirName}/**`,
262
+ ],
263
+ });
264
+
265
+ // Read all files and check for key usage
266
+ const foundKeys = new Set<string>();
267
+
268
+ for (const file of files) {
269
+ const filePath = path.join(sourceDir, file);
270
+ try {
271
+ const content = fs.readFileSync(filePath, 'utf-8');
272
+
273
+ // Check each unused key in the file content
274
+ for (const key of unusedKeys) {
275
+ if (!foundKeys.has(key) && content.includes(key)) {
276
+ foundKeys.add(key);
277
+ }
278
+ }
279
+ } catch {
280
+ // Skip files that can't be read
281
+ continue;
282
+ }
283
+ }
284
+
285
+ // Keys that were not found in any source files exist only in locales
286
+ for (const key of unusedKeys) {
287
+ if (!foundKeys.has(key)) {
288
+ keysOnlyInLocales.push(key);
289
+ }
290
+ }
291
+
292
+ return keysOnlyInLocales;
293
+ }
294
+
295
+ /**
296
+ * Main check function
297
+ */
298
+ async function checkI18n(): Promise<CheckResult> {
299
+ const errors: string[] = [];
300
+ const warnings: string[] = [];
301
+
302
+ const localesDir = path.join(__dirname, '../packages/cli/src/i18n/locales');
303
+ const sourceDir = path.join(__dirname, '../packages/cli/src');
304
+
305
+ const enPath = path.join(localesDir, 'en.js');
306
+ const zhPath = path.join(localesDir, 'zh.js');
307
+
308
+ // Load translation files
309
+ let enTranslations: Record<string, string | string[]>;
310
+ let zhTranslations: Record<string, string | string[]>;
311
+
312
+ try {
313
+ enTranslations = await loadTranslationsFile(enPath);
314
+ } catch (error) {
315
+ errors.push(
316
+ `Failed to load en.js: ${error instanceof Error ? error.message : String(error)}`,
317
+ );
318
+ return {
319
+ success: false,
320
+ errors,
321
+ warnings,
322
+ stats: { totalKeys: 0, translatedKeys: 0, unusedKeys: [] },
323
+ };
324
+ }
325
+
326
+ try {
327
+ zhTranslations = await loadTranslationsFile(zhPath);
328
+ } catch (error) {
329
+ errors.push(
330
+ `Failed to load zh.js: ${error instanceof Error ? error.message : String(error)}`,
331
+ );
332
+ return {
333
+ success: false,
334
+ errors,
335
+ warnings,
336
+ stats: { totalKeys: 0, translatedKeys: 0, unusedKeys: [] },
337
+ };
338
+ }
339
+
340
+ // Check key-value consistency in en.js
341
+ const consistencyErrors = checkKeyValueConsistency(enTranslations);
342
+ errors.push(...consistencyErrors);
343
+
344
+ // Check key matching between en and zh
345
+ const matchingErrors = checkKeyMatching(enTranslations, zhTranslations);
346
+ errors.push(...matchingErrors);
347
+
348
+ // Extract used keys from source code
349
+ const usedKeys = await extractUsedKeys(sourceDir);
350
+
351
+ // Find unused keys
352
+ const enKeys = new Set(Object.keys(enTranslations));
353
+ const unusedKeys = findUnusedKeys(enKeys, usedKeys);
354
+
355
+ // Find keys that exist only in locales (and nowhere else in the codebase)
356
+ const unusedKeysOnlyInLocales =
357
+ unusedKeys.length > 0
358
+ ? await findKeysOnlyInLocales(unusedKeys, sourceDir, localesDir)
359
+ : [];
360
+
361
+ if (unusedKeys.length > 0) {
362
+ warnings.push(`Found ${unusedKeys.length} unused translation keys`);
363
+ }
364
+
365
+ const totalKeys = Object.keys(enTranslations).length;
366
+ const translatedKeys = Object.keys(zhTranslations).length;
367
+
368
+ return {
369
+ success: errors.length === 0,
370
+ errors,
371
+ warnings,
372
+ stats: {
373
+ totalKeys,
374
+ translatedKeys,
375
+ unusedKeys,
376
+ unusedKeysOnlyInLocales,
377
+ },
378
+ };
379
+ }
380
+
381
+ // Run checks
382
+ async function main() {
383
+ const result = await checkI18n();
384
+
385
+ console.log('\n=== i18n Check Results ===\n');
386
+
387
+ console.log(`Total keys: ${result.stats.totalKeys}`);
388
+ console.log(`Translated keys: ${result.stats.translatedKeys}`);
389
+ const coverage =
390
+ result.stats.totalKeys > 0
391
+ ? ((result.stats.translatedKeys / result.stats.totalKeys) * 100).toFixed(
392
+ 1,
393
+ )
394
+ : '0.0';
395
+ console.log(`Translation coverage: ${coverage}%\n`);
396
+
397
+ if (result.warnings.length > 0) {
398
+ console.log('⚠️ Warnings:');
399
+ result.warnings.forEach((warning) => console.log(` - ${warning}`));
400
+
401
+ // Show unused keys
402
+ if (
403
+ result.stats.unusedKeys.length > 0 &&
404
+ result.stats.unusedKeys.length <= 10
405
+ ) {
406
+ console.log('\nUnused keys:');
407
+ result.stats.unusedKeys.forEach((key) => console.log(` - "${key}"`));
408
+ } else if (result.stats.unusedKeys.length > 10) {
409
+ console.log(
410
+ `\nUnused keys (showing first 10 of ${result.stats.unusedKeys.length}):`,
411
+ );
412
+ result.stats.unusedKeys
413
+ .slice(0, 10)
414
+ .forEach((key) => console.log(` - "${key}"`));
415
+ }
416
+
417
+ // Show keys that exist only in locales files
418
+ if (
419
+ result.stats.unusedKeysOnlyInLocales &&
420
+ result.stats.unusedKeysOnlyInLocales.length > 0
421
+ ) {
422
+ console.log(
423
+ '\n⚠️ The following keys exist ONLY in locale files and nowhere else in the codebase:',
424
+ );
425
+ console.log(
426
+ ' Please review these keys - they might be safe to remove.',
427
+ );
428
+ result.stats.unusedKeysOnlyInLocales.forEach((key) =>
429
+ console.log(` - "${key}"`),
430
+ );
431
+
432
+ // Save these keys to a JSON file
433
+ const outputPath = path.join(
434
+ __dirname,
435
+ 'unused-keys-only-in-locales.json',
436
+ );
437
+ saveKeysOnlyInLocalesToJson(
438
+ result.stats.unusedKeysOnlyInLocales,
439
+ outputPath,
440
+ );
441
+ }
442
+
443
+ console.log();
444
+ }
445
+
446
+ if (result.errors.length > 0) {
447
+ console.log('❌ Errors:');
448
+ result.errors.forEach((error) => console.log(` - ${error}`));
449
+ console.log();
450
+ process.exit(1);
451
+ }
452
+
453
+ if (result.success) {
454
+ console.log('✅ All checks passed!\n');
455
+ process.exit(0);
456
+ }
457
+ }
458
+
459
+ main().catch((error) => {
460
+ console.error('❌ Fatal error:', error);
461
+ process.exit(1);
462
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import fs from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const root = join(__dirname, '..');
13
+ const lockfilePath = join(root, 'package-lock.json');
14
+
15
+ function readJsonFile(filePath) {
16
+ try {
17
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
18
+ return JSON.parse(fileContent);
19
+ } catch (error) {
20
+ console.error(`Error reading or parsing ${filePath}:`, error);
21
+ return null;
22
+ }
23
+ }
24
+
25
+ console.log('Checking lockfile...');
26
+
27
+ const lockfile = readJsonFile(lockfilePath);
28
+ if (lockfile === null) {
29
+ process.exit(1);
30
+ }
31
+ const packages = lockfile.packages || {};
32
+ const invalidPackages = [];
33
+
34
+ for (const [location, details] of Object.entries(packages)) {
35
+ // 1. Skip the root package itself.
36
+ if (location === '') {
37
+ continue;
38
+ }
39
+
40
+ // 2. Skip local workspace packages.
41
+ // They are identifiable in two ways:
42
+ // a) As a symlink within node_modules.
43
+ // b) As the source package definition, whose path is not in node_modules.
44
+ if (details.link === true || !location.includes('node_modules')) {
45
+ continue;
46
+ }
47
+
48
+ // 3. Any remaining package should be a third-party dependency.
49
+ // 1) Registry package with both "resolved" and "integrity" fields is valid.
50
+ if (details.resolved && details.integrity) {
51
+ continue;
52
+ }
53
+ // 2) Git and file dependencies only need a "resolved" field.
54
+ const isGitOrFileDep =
55
+ details.resolved?.startsWith('git') ||
56
+ details.resolved?.startsWith('file:');
57
+ if (isGitOrFileDep) {
58
+ continue;
59
+ }
60
+
61
+ // Mark the left dependency as invalid.
62
+ invalidPackages.push(location);
63
+ }
64
+
65
+ if (invalidPackages.length > 0) {
66
+ console.error(
67
+ '\nError: The following dependencies in package-lock.json are missing the "resolved" or "integrity" field:',
68
+ );
69
+ invalidPackages.forEach((pkg) => console.error(`- ${pkg}`));
70
+ process.exitCode = 1;
71
+ } else {
72
+ console.log('Lockfile check passed.');
73
+ process.exitCode = 0;
74
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ //
8
+ // Licensed under the Apache License, Version 2.0 (the "License");
9
+ // you may not use this file except in compliance with the License.
10
+ // You may obtain a copy of the License at
11
+ //
12
+ // http://www.apache.org/licenses/LICENSE-2.0
13
+ //
14
+ // Unless required by applicable law or agreed to in writing, software
15
+ // distributed under the License is distributed on an "AS IS" BASIS,
16
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ // See the License for the specific language governing permissions and
18
+ // limitations under the License.
19
+
20
+ import { rmSync, readFileSync } from 'node:fs';
21
+ import { dirname, join } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { globSync } from 'glob';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const root = join(__dirname, '..');
27
+
28
+ // remove npm install/build artifacts
29
+ rmSync(join(root, 'node_modules'), { recursive: true, force: true });
30
+ rmSync(join(root, 'bundle'), { recursive: true, force: true });
31
+ rmSync(join(root, 'packages/cli/src/generated/'), {
32
+ recursive: true,
33
+ force: true,
34
+ });
35
+ const RMRF_OPTIONS = { recursive: true, force: true };
36
+ rmSync(join(root, 'bundle'), RMRF_OPTIONS);
37
+ // Dynamically clean dist directories in all workspaces
38
+ const rootPackageJson = JSON.parse(
39
+ readFileSync(join(root, 'package.json'), 'utf-8'),
40
+ );
41
+ for (const workspace of rootPackageJson.workspaces) {
42
+ const packages = globSync(join(workspace, 'package.json'), { cwd: root });
43
+ for (const pkgPath of packages) {
44
+ const pkgDir = dirname(join(root, pkgPath));
45
+ rmSync(join(pkgDir, 'dist'), RMRF_OPTIONS);
46
+ }
47
+ }
48
+
49
+ // Clean up vscode-ide-companion package
50
+ rmSync(join(root, 'packages/vscode-ide-companion/node_modules'), {
51
+ recursive: true,
52
+ force: true,
53
+ });
54
+ const vsixFiles = globSync('packages/vscode-ide-companion/*.vsix', {
55
+ cwd: root,
56
+ });
57
+ for (const vsixFile of vsixFiles) {
58
+ rmSync(join(root, vsixFile), RMRF_OPTIONS);
59
+ }