ai-localize-scanner 1.0.0

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,73 @@
1
+ import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from '@ai-localize/shared';
2
+
3
+ interface AstScanOptions {
4
+ filePath: string;
5
+ content: string;
6
+ sourceRoot?: string;
7
+ }
8
+ /**
9
+ * Scans a JS/TS/JSX/TSX file using Babel AST to find hardcoded text.
10
+ */
11
+ declare class AstScanner {
12
+ private options;
13
+ private detectedTexts;
14
+ private translationFunctionNames;
15
+ constructor(options: AstScanOptions);
16
+ scan(): DetectedText[];
17
+ private collectTranslationImports;
18
+ private isInsideTranslationCall;
19
+ private addDetected;
20
+ private mapAttrToContext;
21
+ private regexFallbackScan;
22
+ }
23
+
24
+ declare class AssetScanner {
25
+ private legacyCdnPattern;
26
+ constructor(legacyCdnPattern?: string);
27
+ scanFile(filePath: string): {
28
+ assets: AssetReference[];
29
+ legacyCdnUrls: LegacyCdnUrl[];
30
+ };
31
+ private getLineNumber;
32
+ private getAssetType;
33
+ private extractPathFromUrl;
34
+ }
35
+
36
+ declare class IncrementalScanCache {
37
+ private cachePath;
38
+ private cache;
39
+ constructor(cacheDir: string);
40
+ private load;
41
+ isFileChanged(filePath: string): boolean;
42
+ getCachedResult(filePath: string): DetectedText[] | null;
43
+ setCachedResult(filePath: string, texts: DetectedText[]): void;
44
+ persist(): void;
45
+ private hashFile;
46
+ clear(): void;
47
+ }
48
+
49
+ interface ScanOptions {
50
+ files?: string[];
51
+ incremental?: boolean;
52
+ }
53
+ declare class ProjectScanner {
54
+ private config;
55
+ private sourceRoot;
56
+ private cache?;
57
+ private assetScanner;
58
+ constructor(config: LocalizationConfig);
59
+ scan(options?: ScanOptions): Promise<ScanResult>;
60
+ private scanFile;
61
+ private chunkArray;
62
+ }
63
+
64
+ declare class GitScanner {
65
+ private cwd;
66
+ constructor(cwd?: string);
67
+ getStagedFiles(extensions?: string[]): string[];
68
+ getChangedFiles(base?: string, extensions?: string[]): string[];
69
+ getRecentlyChangedFiles(commits?: number, extensions?: string[]): string[];
70
+ private filter;
71
+ }
72
+
73
+ export { AssetScanner, type AstScanOptions, AstScanner, GitScanner, IncrementalScanCache, ProjectScanner, type ScanOptions };
@@ -0,0 +1,73 @@
1
+ import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from '@ai-localize/shared';
2
+
3
+ interface AstScanOptions {
4
+ filePath: string;
5
+ content: string;
6
+ sourceRoot?: string;
7
+ }
8
+ /**
9
+ * Scans a JS/TS/JSX/TSX file using Babel AST to find hardcoded text.
10
+ */
11
+ declare class AstScanner {
12
+ private options;
13
+ private detectedTexts;
14
+ private translationFunctionNames;
15
+ constructor(options: AstScanOptions);
16
+ scan(): DetectedText[];
17
+ private collectTranslationImports;
18
+ private isInsideTranslationCall;
19
+ private addDetected;
20
+ private mapAttrToContext;
21
+ private regexFallbackScan;
22
+ }
23
+
24
+ declare class AssetScanner {
25
+ private legacyCdnPattern;
26
+ constructor(legacyCdnPattern?: string);
27
+ scanFile(filePath: string): {
28
+ assets: AssetReference[];
29
+ legacyCdnUrls: LegacyCdnUrl[];
30
+ };
31
+ private getLineNumber;
32
+ private getAssetType;
33
+ private extractPathFromUrl;
34
+ }
35
+
36
+ declare class IncrementalScanCache {
37
+ private cachePath;
38
+ private cache;
39
+ constructor(cacheDir: string);
40
+ private load;
41
+ isFileChanged(filePath: string): boolean;
42
+ getCachedResult(filePath: string): DetectedText[] | null;
43
+ setCachedResult(filePath: string, texts: DetectedText[]): void;
44
+ persist(): void;
45
+ private hashFile;
46
+ clear(): void;
47
+ }
48
+
49
+ interface ScanOptions {
50
+ files?: string[];
51
+ incremental?: boolean;
52
+ }
53
+ declare class ProjectScanner {
54
+ private config;
55
+ private sourceRoot;
56
+ private cache?;
57
+ private assetScanner;
58
+ constructor(config: LocalizationConfig);
59
+ scan(options?: ScanOptions): Promise<ScanResult>;
60
+ private scanFile;
61
+ private chunkArray;
62
+ }
63
+
64
+ declare class GitScanner {
65
+ private cwd;
66
+ constructor(cwd?: string);
67
+ getStagedFiles(extensions?: string[]): string[];
68
+ getChangedFiles(base?: string, extensions?: string[]): string[];
69
+ getRecentlyChangedFiles(commits?: number, extensions?: string[]): string[];
70
+ private filter;
71
+ }
72
+
73
+ export { AssetScanner, type AstScanOptions, AstScanner, GitScanner, IncrementalScanCache, ProjectScanner, type ScanOptions };
package/dist/index.js ADDED
@@ -0,0 +1,504 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AssetScanner: () => AssetScanner,
34
+ AstScanner: () => AstScanner,
35
+ GitScanner: () => GitScanner,
36
+ IncrementalScanCache: () => IncrementalScanCache,
37
+ ProjectScanner: () => ProjectScanner
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/ast-scanner.ts
42
+ var parser = __toESM(require("@babel/parser"));
43
+ var import_traverse = __toESM(require("@babel/traverse"));
44
+ var t = __toESM(require("@babel/types"));
45
+ var import_shared = require("@ai-localize/shared");
46
+ var TRANSLATION_IMPORT_SOURCES = /* @__PURE__ */ new Set([
47
+ "react-i18next",
48
+ "i18next",
49
+ "vue-i18n",
50
+ "@ngx-translate/core"
51
+ ]);
52
+ var AstScanner = class {
53
+ options;
54
+ detectedTexts = [];
55
+ translationFunctionNames = /* @__PURE__ */ new Set(["t", "$t", "i18n", "translate"]);
56
+ constructor(options) {
57
+ this.options = options;
58
+ }
59
+ scan() {
60
+ const { content } = this.options;
61
+ let ast;
62
+ try {
63
+ ast = parser.parse(content, {
64
+ sourceType: "module",
65
+ plugins: [
66
+ "jsx",
67
+ "typescript",
68
+ "decorators-legacy",
69
+ "classProperties",
70
+ "optionalChaining",
71
+ "nullishCoalescingOperator",
72
+ "dynamicImport",
73
+ "exportDefaultFrom"
74
+ ],
75
+ errorRecovery: true
76
+ });
77
+ } catch {
78
+ return this.regexFallbackScan();
79
+ }
80
+ this.collectTranslationImports(ast);
81
+ (0, import_traverse.default)(ast, {
82
+ JSXText: (nodePath) => {
83
+ const text = (0, import_shared.normalizeText)(nodePath.node.value);
84
+ if (!(0, import_shared.isHumanReadableText)(text)) return;
85
+ if (this.isInsideTranslationCall(nodePath)) return;
86
+ this.addDetected(
87
+ text,
88
+ nodePath.node.loc?.start.line ?? 0,
89
+ nodePath.node.loc?.start.column ?? 0,
90
+ "jsx-text",
91
+ "JSXText"
92
+ );
93
+ },
94
+ JSXAttribute: (nodePath) => {
95
+ const attrName = t.isJSXIdentifier(nodePath.node.name) ? nodePath.node.name.name : "";
96
+ if (!import_shared.TEXT_ATTRIBUTE_NAMES.has(attrName.toLowerCase())) return;
97
+ const valueNode = nodePath.node.value;
98
+ if (!t.isStringLiteral(valueNode)) return;
99
+ const text = (0, import_shared.normalizeText)(valueNode.value);
100
+ if (!(0, import_shared.isHumanReadableText)(text)) return;
101
+ if (this.isInsideTranslationCall(nodePath)) return;
102
+ const context = this.mapAttrToContext(attrName);
103
+ this.addDetected(
104
+ text,
105
+ valueNode.loc?.start.line ?? 0,
106
+ valueNode.loc?.start.column ?? 0,
107
+ context,
108
+ "JSXAttribute"
109
+ );
110
+ },
111
+ StringLiteral: (nodePath) => {
112
+ if (t.isImportDeclaration(nodePath.parent)) return;
113
+ if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
114
+ if (t.isJSXAttribute(nodePath.parent)) return;
115
+ if (this.isInsideTranslationCall(nodePath)) return;
116
+ if (/^[a-z][a-z0-9_.]+$/.test(nodePath.node.value)) return;
117
+ const text = (0, import_shared.normalizeText)(nodePath.node.value);
118
+ if (!(0, import_shared.isHumanReadableText)(text)) return;
119
+ this.addDetected(
120
+ text,
121
+ nodePath.node.loc?.start.line ?? 0,
122
+ nodePath.node.loc?.start.column ?? 0,
123
+ "string-literal",
124
+ "StringLiteral"
125
+ );
126
+ },
127
+ TemplateLiteral: (nodePath) => {
128
+ if (nodePath.node.expressions.length > 0) return;
129
+ if (this.isInsideTranslationCall(nodePath)) return;
130
+ const text = (0, import_shared.normalizeText)(nodePath.node.quasis[0]?.value.cooked ?? "");
131
+ if (!(0, import_shared.isHumanReadableText)(text)) return;
132
+ this.addDetected(
133
+ text,
134
+ nodePath.node.loc?.start.line ?? 0,
135
+ nodePath.node.loc?.start.column ?? 0,
136
+ "template-literal",
137
+ "TemplateLiteral"
138
+ );
139
+ }
140
+ });
141
+ return this.detectedTexts;
142
+ }
143
+ collectTranslationImports(ast) {
144
+ for (const node of ast.program.body) {
145
+ if (!t.isImportDeclaration(node)) continue;
146
+ if (!TRANSLATION_IMPORT_SOURCES.has(node.source.value)) continue;
147
+ for (const specifier of node.specifiers) {
148
+ if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.local)) {
149
+ this.translationFunctionNames.add(specifier.local.name);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ isInsideTranslationCall(nodePath) {
156
+ let current = nodePath.parentPath;
157
+ while (current) {
158
+ const node = current.node;
159
+ if (t.isCallExpression(node)) {
160
+ const callee = node.callee;
161
+ if (t.isIdentifier(callee) && this.translationFunctionNames.has(callee.name)) {
162
+ return true;
163
+ }
164
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property) && this.translationFunctionNames.has(callee.property.name)) {
165
+ return true;
166
+ }
167
+ }
168
+ current = current.parentPath;
169
+ }
170
+ return false;
171
+ }
172
+ addDetected(text, line, column, context, nodeType) {
173
+ const key = (0, import_shared.generateLocaleKey)(
174
+ this.options.filePath,
175
+ text,
176
+ this.options.sourceRoot || "src"
177
+ );
178
+ this.detectedTexts.push({
179
+ filePath: this.options.filePath,
180
+ line,
181
+ column,
182
+ text,
183
+ suggestedKey: key,
184
+ context,
185
+ nodeType,
186
+ alreadyTranslated: false
187
+ });
188
+ }
189
+ mapAttrToContext(attrName) {
190
+ const lower = attrName.toLowerCase();
191
+ if (lower === "placeholder") return "placeholder";
192
+ if (lower === "aria-label" || lower === "aria-placeholder") return "aria-label";
193
+ if (lower === "title") return "title";
194
+ if (lower === "alt") return "alt";
195
+ return "jsx-attribute";
196
+ }
197
+ regexFallbackScan() {
198
+ const results = [];
199
+ const jsxTextRegex = />([^<>{}\n]+)</g;
200
+ const lines = this.options.content.split("\n");
201
+ lines.forEach((line, idx) => {
202
+ let m;
203
+ jsxTextRegex.lastIndex = 0;
204
+ while ((m = jsxTextRegex.exec(line)) !== null) {
205
+ const text = (0, import_shared.normalizeText)(m[1]);
206
+ if (!(0, import_shared.isHumanReadableText)(text)) continue;
207
+ const key = (0, import_shared.generateLocaleKey)(this.options.filePath, text, this.options.sourceRoot || "src");
208
+ results.push({
209
+ filePath: this.options.filePath,
210
+ line: idx + 1,
211
+ column: m.index,
212
+ text,
213
+ suggestedKey: key,
214
+ context: "jsx-text",
215
+ nodeType: "regex-fallback",
216
+ alreadyTranslated: false
217
+ });
218
+ }
219
+ });
220
+ return results;
221
+ }
222
+ };
223
+
224
+ // src/asset-scanner.ts
225
+ var fs = __toESM(require("fs"));
226
+ var path = __toESM(require("path"));
227
+ var import_shared2 = require("@ai-localize/shared");
228
+ var CDN_URL_PATTERN = /https?:\/\/[a-zA-Z0-9\-.]+\.[a-zA-Z]{2,}\/[^\s"'`\)\]>]+/g;
229
+ var CSS_URL_PATTERN = /url\(['"\s]?([^'")]+)['"\s]?\)/g;
230
+ var IMPORT_ASSET_PATTERN = /import\s+\w+\s+from\s+['"]([^'"]+\.(png|jpg|jpeg|svg|webp|gif|ico|woff|woff2|ttf|eot|mp4))['"];?/gi;
231
+ var SRC_ATTR_PATTERN = /(?:src|href)=["']([^"']+\.(png|jpg|jpeg|svg|webp|gif|ico|mp4))["']/gi;
232
+ var AssetScanner = class {
233
+ legacyCdnPattern = null;
234
+ constructor(legacyCdnPattern) {
235
+ if (legacyCdnPattern) {
236
+ try {
237
+ this.legacyCdnPattern = new RegExp(legacyCdnPattern, "g");
238
+ } catch {
239
+ }
240
+ }
241
+ }
242
+ scanFile(filePath) {
243
+ const assets = [];
244
+ const legacyCdnUrls = [];
245
+ let content;
246
+ try {
247
+ content = fs.readFileSync(filePath, "utf-8");
248
+ } catch {
249
+ return { assets, legacyCdnUrls };
250
+ }
251
+ let m;
252
+ IMPORT_ASSET_PATTERN.lastIndex = 0;
253
+ while ((m = IMPORT_ASSET_PATTERN.exec(content)) !== null) {
254
+ const assetPath = m[1];
255
+ assets.push({
256
+ filePath,
257
+ line: this.getLineNumber(content, m.index),
258
+ assetPath,
259
+ assetType: this.getAssetType(assetPath),
260
+ referenceType: "import"
261
+ });
262
+ }
263
+ CSS_URL_PATTERN.lastIndex = 0;
264
+ while ((m = CSS_URL_PATTERN.exec(content)) !== null) {
265
+ const assetPath = m[1];
266
+ if (assetPath.startsWith("data:")) continue;
267
+ assets.push({
268
+ filePath,
269
+ line: this.getLineNumber(content, m.index),
270
+ assetPath,
271
+ assetType: this.getAssetType(assetPath),
272
+ referenceType: "css-url"
273
+ });
274
+ }
275
+ SRC_ATTR_PATTERN.lastIndex = 0;
276
+ while ((m = SRC_ATTR_PATTERN.exec(content)) !== null) {
277
+ assets.push({
278
+ filePath,
279
+ line: this.getLineNumber(content, m.index),
280
+ assetPath: m[1],
281
+ assetType: this.getAssetType(m[1]),
282
+ referenceType: "src-attr"
283
+ });
284
+ }
285
+ if (this.legacyCdnPattern) {
286
+ this.legacyCdnPattern.lastIndex = 0;
287
+ while ((m = this.legacyCdnPattern.exec(content)) !== null) {
288
+ const url = m[0];
289
+ legacyCdnUrls.push({
290
+ filePath,
291
+ line: this.getLineNumber(content, m.index),
292
+ url,
293
+ assetPath: this.extractPathFromUrl(url)
294
+ });
295
+ }
296
+ }
297
+ CDN_URL_PATTERN.lastIndex = 0;
298
+ while ((m = CDN_URL_PATTERN.exec(content)) !== null) {
299
+ const url = m[0];
300
+ if (!import_shared2.ASSET_EXTENSIONS.some((ext) => url.includes(`.${ext}`))) continue;
301
+ const line = this.getLineNumber(content, m.index);
302
+ if (!legacyCdnUrls.find((u) => u.url === url && u.line === line)) {
303
+ legacyCdnUrls.push({ filePath, line, url, assetPath: this.extractPathFromUrl(url) });
304
+ }
305
+ }
306
+ return { assets, legacyCdnUrls };
307
+ }
308
+ getLineNumber(content, index) {
309
+ return content.slice(0, index).split("\n").length;
310
+ }
311
+ getAssetType(assetPath) {
312
+ const ext = path.extname(assetPath).toLowerCase().replace(".", "");
313
+ return import_shared2.ASSET_EXTENSIONS.includes(ext) ? ext : "other";
314
+ }
315
+ extractPathFromUrl(url) {
316
+ try {
317
+ return new URL(url).pathname;
318
+ } catch {
319
+ return url;
320
+ }
321
+ }
322
+ };
323
+
324
+ // src/incremental-scanner.ts
325
+ var fs2 = __toESM(require("fs"));
326
+ var path2 = __toESM(require("path"));
327
+ var crypto = __toESM(require("crypto"));
328
+ var import_shared3 = require("@ai-localize/shared");
329
+ var IncrementalScanCache = class {
330
+ cachePath;
331
+ cache;
332
+ constructor(cacheDir) {
333
+ (0, import_shared3.ensureDir)(cacheDir);
334
+ this.cachePath = path2.join(cacheDir, "scan-cache.json");
335
+ this.cache = this.load();
336
+ }
337
+ load() {
338
+ const existing = (0, import_shared3.readJsonSafe)(this.cachePath);
339
+ if (existing?.version === "1") return existing;
340
+ return { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), fileHashes: {}, processedFiles: {} };
341
+ }
342
+ isFileChanged(filePath) {
343
+ return this.hashFile(filePath) !== this.cache.fileHashes[filePath];
344
+ }
345
+ getCachedResult(filePath) {
346
+ const entry = this.cache.processedFiles[filePath];
347
+ if (!entry) return null;
348
+ if (entry.hash !== this.hashFile(filePath)) return null;
349
+ return entry.detectedTexts;
350
+ }
351
+ setCachedResult(filePath, texts) {
352
+ const hash = this.hashFile(filePath);
353
+ this.cache.fileHashes[filePath] = hash;
354
+ this.cache.processedFiles[filePath] = { hash, detectedTexts: texts, lastModified: Date.now() };
355
+ }
356
+ persist() {
357
+ this.cache.lastRun = (/* @__PURE__ */ new Date()).toISOString();
358
+ (0, import_shared3.writeJson)(this.cachePath, this.cache);
359
+ }
360
+ hashFile(filePath) {
361
+ try {
362
+ return crypto.createHash("sha256").update(fs2.readFileSync(filePath)).digest("hex");
363
+ } catch {
364
+ return "";
365
+ }
366
+ }
367
+ clear() {
368
+ this.cache = { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), fileHashes: {}, processedFiles: {} };
369
+ this.persist();
370
+ }
371
+ };
372
+
373
+ // src/project-scanner.ts
374
+ var path3 = __toESM(require("path"));
375
+ var os = __toESM(require("os"));
376
+ var import_shared4 = require("@ai-localize/shared");
377
+ var ProjectScanner = class {
378
+ config;
379
+ sourceRoot;
380
+ cache;
381
+ assetScanner;
382
+ constructor(config) {
383
+ this.config = config;
384
+ this.sourceRoot = path3.join(process.cwd(), config.sourceDir);
385
+ this.assetScanner = new AssetScanner(config.aws?.legacyCdnPattern);
386
+ if (config.incrementalCache) {
387
+ this.cache = new IncrementalScanCache(
388
+ path3.join(process.cwd(), config.cacheDir || ".ai-localize-cache")
389
+ );
390
+ }
391
+ }
392
+ async scan(options = {}) {
393
+ const startTime = Date.now();
394
+ const filesToScan = options.files?.length ? options.files : (0, import_shared4.collectFiles)(this.sourceRoot, import_shared4.SOURCE_EXTENSIONS, [
395
+ ...import_shared4.DEFAULT_IGNORE_DIRS,
396
+ ...this.config.ignorePatterns || []
397
+ ]);
398
+ const allTexts = [];
399
+ const allAssets = [];
400
+ const allLegacyUrls = [];
401
+ const chunkSize = Math.max(
402
+ 1,
403
+ Math.min(50, Math.ceil(filesToScan.length / (os.cpus().length || 4)))
404
+ );
405
+ const chunks = this.chunkArray(filesToScan, chunkSize);
406
+ for (const chunk of chunks) {
407
+ const results = await Promise.all(chunk.map((f) => this.scanFile(f)));
408
+ for (const r of results) {
409
+ allTexts.push(...r.texts);
410
+ allAssets.push(...r.assets);
411
+ allLegacyUrls.push(...r.legacyUrls);
412
+ }
413
+ }
414
+ this.cache?.persist();
415
+ return {
416
+ framework: this.config.framework,
417
+ scannedFiles: filesToScan.length,
418
+ detectedTexts: allTexts,
419
+ assets: allAssets,
420
+ legacyCdnUrls: allLegacyUrls,
421
+ duration: Date.now() - startTime,
422
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
423
+ };
424
+ }
425
+ async scanFile(filePath) {
426
+ if (this.cache && !this.cache.isFileChanged(filePath)) {
427
+ const cached = this.cache.getCachedResult(filePath);
428
+ if (cached) return { texts: cached, assets: [], legacyUrls: [] };
429
+ }
430
+ let content;
431
+ try {
432
+ const { readFileSync: readFileSync3 } = await import("fs");
433
+ content = readFileSync3(filePath, "utf-8");
434
+ } catch {
435
+ return { texts: [], assets: [], legacyUrls: [] };
436
+ }
437
+ const scanner = new AstScanner({ filePath, content, sourceRoot: this.config.sourceDir });
438
+ const texts = scanner.scan();
439
+ const { assets, legacyCdnUrls } = this.assetScanner.scanFile(filePath);
440
+ this.cache?.setCachedResult(filePath, texts);
441
+ return { texts, assets, legacyUrls: legacyCdnUrls };
442
+ }
443
+ chunkArray(array, size) {
444
+ const chunks = [];
445
+ for (let i = 0; i < array.length; i += size) {
446
+ chunks.push(array.slice(i, i + size));
447
+ }
448
+ return chunks;
449
+ }
450
+ };
451
+
452
+ // src/git-scanner.ts
453
+ var import_child_process = require("child_process");
454
+ var path4 = __toESM(require("path"));
455
+ var GitScanner = class {
456
+ cwd;
457
+ constructor(cwd = process.cwd()) {
458
+ this.cwd = cwd;
459
+ }
460
+ getStagedFiles(extensions = ["ts", "tsx", "js", "jsx", "vue"]) {
461
+ try {
462
+ const out = (0, import_child_process.execSync)("git diff --cached --name-only --diff-filter=ACM", {
463
+ cwd: this.cwd,
464
+ encoding: "utf-8"
465
+ });
466
+ return this.filter(out.trim().split("\n"), extensions);
467
+ } catch {
468
+ return [];
469
+ }
470
+ }
471
+ getChangedFiles(base = "main", extensions = ["ts", "tsx", "js", "jsx", "vue"]) {
472
+ try {
473
+ const out = (0, import_child_process.execSync)(`git diff --name-only --diff-filter=ACM ${base}...HEAD`, {
474
+ cwd: this.cwd,
475
+ encoding: "utf-8"
476
+ });
477
+ return this.filter(out.trim().split("\n"), extensions);
478
+ } catch {
479
+ return [];
480
+ }
481
+ }
482
+ getRecentlyChangedFiles(commits = 1, extensions = ["ts", "tsx", "js", "jsx", "vue"]) {
483
+ try {
484
+ const out = (0, import_child_process.execSync)(
485
+ `git diff --name-only --diff-filter=ACM HEAD~${commits}...HEAD`,
486
+ { cwd: this.cwd, encoding: "utf-8" }
487
+ );
488
+ return this.filter(out.trim().split("\n"), extensions);
489
+ } catch {
490
+ return [];
491
+ }
492
+ }
493
+ filter(files, extensions) {
494
+ return files.filter((f) => f && extensions.some((e) => f.endsWith(`.${e}`))).map((f) => path4.join(this.cwd, f));
495
+ }
496
+ };
497
+ // Annotate the CommonJS export names for ESM import in node:
498
+ 0 && (module.exports = {
499
+ AssetScanner,
500
+ AstScanner,
501
+ GitScanner,
502
+ IncrementalScanCache,
503
+ ProjectScanner
504
+ });