@mmmbuto/qwen-code-termux 0.6.4-termux → 0.6.402
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/README.md +4 -5
- package/dist/cli.js +4 -4
- package/package.json +4 -3
- package/scripts/build.js +55 -0
- package/scripts/build_package.js +37 -0
- package/scripts/build_sandbox.js +196 -0
- package/scripts/build_vscode_companion.js +30 -0
- package/scripts/check-build-status.js +148 -0
- package/scripts/check-i18n.ts +462 -0
- package/scripts/check-lockfile.js +74 -0
- package/scripts/clean.js +59 -0
- package/scripts/copy_bundle_assets.js +128 -0
- package/scripts/copy_files.js +86 -0
- package/scripts/create_alias.sh +39 -0
- package/scripts/esbuild-shims.js +29 -0
- package/scripts/generate-git-commit-info.js +71 -0
- package/scripts/get-release-version.js +411 -0
- package/scripts/lint.js +205 -0
- package/scripts/local_telemetry.js +219 -0
- package/scripts/postinstall.cjs +13 -0
- package/scripts/pre-commit.js +22 -0
- package/scripts/prepare-package.js +162 -0
- package/scripts/sandbox_command.js +126 -0
- package/scripts/start.js +83 -0
- package/scripts/telemetry.js +85 -0
- package/scripts/telemetry_gcp.js +188 -0
- package/scripts/telemetry_utils.js +439 -0
- package/scripts/termux-tools/call.sh +206 -0
- package/scripts/termux-tools/discovery.sh +382 -0
- package/scripts/test-windows-paths.js +51 -0
- package/scripts/tests/get-release-version.test.js +186 -0
- package/scripts/tests/test-setup.ts +12 -0
- package/scripts/tests/vitest.config.ts +26 -0
- package/scripts/unused-keys-only-in-locales.json +61 -0
- package/scripts/version.js +112 -0
|
@@ -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
|
+
}
|
package/scripts/clean.js
ADDED
|
@@ -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
|
+
}
|