lingo-linter-tool 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.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +74 -0
- package/dist/config.d.ts +0 -0
- package/dist/config.js +1 -0
- package/dist/healer/index.d.ts +2 -0
- package/dist/healer/index.js +104 -0
- package/dist/healer/translator.d.ts +3 -0
- package/dist/healer/translator.js +63 -0
- package/dist/linter/engine.d.ts +0 -0
- package/dist/linter/engine.js +1 -0
- package/dist/linter/rules/missing-keys.d.ts +2 -0
- package/dist/linter/rules/missing-keys.js +98 -0
- package/dist/linter/rules/no-string-concat.d.ts +2 -0
- package/dist/linter/rules/no-string-concat.js +38 -0
- package/dist/linter/types.d.ts +13 -0
- package/dist/linter/types.js +2 -0
- package/dist/utils/file-system.d.ts +0 -0
- package/dist/utils/file-system.js +1 -0
- package/dist/utils/logger.d.ts +0 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/reporter.d.ts +1 -0
- package/dist/utils/reporter.js +51 -0
- package/package.json +33 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const glob_1 = require("glob");
|
|
11
|
+
const no_string_concat_1 = require("./linter/rules/no-string-concat");
|
|
12
|
+
const missing_keys_1 = require("./linter/rules/missing-keys");
|
|
13
|
+
const healer_1 = require("./healer");
|
|
14
|
+
const reporter_1 = require("./utils/reporter");
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
const calculateHealth = (errors, warnings) => {
|
|
17
|
+
let score = 100 - errors * 5 - warnings * 2;
|
|
18
|
+
return Math.max(0, score);
|
|
19
|
+
};
|
|
20
|
+
program.name("lingo").description("Localization Linter + Self-Healing Tool");
|
|
21
|
+
program
|
|
22
|
+
.command("lint [dir]")
|
|
23
|
+
.description("Analyze code in a specific directory")
|
|
24
|
+
.option("--fix", "Auto-heal missing keys")
|
|
25
|
+
.option("--locales <dir>", "Locales directory (relative to target)", "./locales")
|
|
26
|
+
.action(async (dir, options) => {
|
|
27
|
+
const targetRoot = dir ? path_1.default.resolve(dir) : process.cwd();
|
|
28
|
+
console.log(targetRoot);
|
|
29
|
+
const localesDir = path_1.default.resolve(targetRoot, options.locales);
|
|
30
|
+
console.log(chalk_1.default.blue(`🔍 Scanning: ${targetRoot}`));
|
|
31
|
+
console.log(chalk_1.default.gray(` Locales: ${localesDir}`));
|
|
32
|
+
const files = glob_1.glob.sync("**/*.{ts,tsx}", {
|
|
33
|
+
cwd: targetRoot,
|
|
34
|
+
absolute: true,
|
|
35
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/*.d.ts"],
|
|
36
|
+
});
|
|
37
|
+
if (files.length === 0) {
|
|
38
|
+
console.log(chalk_1.default.red(`❌ No .ts/.tsx files found in ${targetRoot}`));
|
|
39
|
+
console.log(chalk_1.default.gray(` Debug: Glob ran in ${targetRoot}`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const issues = [
|
|
43
|
+
...no_string_concat_1.NoStringConcatRule.run(files),
|
|
44
|
+
...missing_keys_1.MissingKeysRule.run(files, localesDir),
|
|
45
|
+
];
|
|
46
|
+
let errorCount = 0;
|
|
47
|
+
let warningCount = 0;
|
|
48
|
+
issues.forEach((issue) => {
|
|
49
|
+
const color = issue.severity === "error" ? chalk_1.default.red : chalk_1.default.yellow;
|
|
50
|
+
const icon = issue.severity === "error" ? "❌" : "⚠️";
|
|
51
|
+
if (issue.severity === "error")
|
|
52
|
+
errorCount++;
|
|
53
|
+
else
|
|
54
|
+
warningCount++;
|
|
55
|
+
console.log(`${icon} ${color(issue.message)}`);
|
|
56
|
+
console.log(chalk_1.default.gray(` at ${issue.file}:${issue.line}`));
|
|
57
|
+
if (issue.suggestion) {
|
|
58
|
+
console.log(chalk_1.default.green(` 💡 Suggestion: ${issue.suggestion}`));
|
|
59
|
+
}
|
|
60
|
+
console.log("");
|
|
61
|
+
});
|
|
62
|
+
const score = calculateHealth(errorCount, warningCount);
|
|
63
|
+
await (0, reporter_1.uploadHealthScore)(score, errorCount, warningCount);
|
|
64
|
+
const scoreColor = score > 80 ? chalk_1.default.green : score > 50 ? chalk_1.default.yellow : chalk_1.default.red;
|
|
65
|
+
console.log(chalk_1.default.bold("-----------------------------------"));
|
|
66
|
+
console.log(`Localization Health: ${scoreColor(score + " / 100")}`);
|
|
67
|
+
console.log(chalk_1.default.bold("-----------------------------------"));
|
|
68
|
+
if (options.fix && errorCount > 0) {
|
|
69
|
+
await (0, healer_1.healIssues)(issues, localesDir);
|
|
70
|
+
}
|
|
71
|
+
if (errorCount > 0 && !options.fix)
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
program.parse(process.argv);
|
package/dist/config.d.ts
ADDED
|
File without changes
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.healIssues = void 0;
|
|
30
|
+
const fs = __importStar(require("fs"));
|
|
31
|
+
const path = __importStar(require("path"));
|
|
32
|
+
const translator_1 = require("./translator");
|
|
33
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
34
|
+
function getSourceText(localesDir, key) {
|
|
35
|
+
try {
|
|
36
|
+
const enPath = path.join(localesDir, "en.json");
|
|
37
|
+
if (!fs.existsSync(enPath))
|
|
38
|
+
return null;
|
|
39
|
+
const raw = fs.readFileSync(enPath, "utf-8");
|
|
40
|
+
const json = JSON.parse(raw);
|
|
41
|
+
const keys = key.split(".");
|
|
42
|
+
let current = json;
|
|
43
|
+
for (const k of keys) {
|
|
44
|
+
if (current[k] === undefined)
|
|
45
|
+
return null;
|
|
46
|
+
current = current[k];
|
|
47
|
+
}
|
|
48
|
+
return typeof current === "string" ? current : null;
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function healIssues(issues, localesDir) {
|
|
55
|
+
console.log(chalk_1.default.blue("\n🩹 Initializing Lingo.dev Auto-Healer..."));
|
|
56
|
+
const missingKeyIssues = issues.filter((i) => i.ruleId === "missing-keys" && i.key);
|
|
57
|
+
if (missingKeyIssues.length === 0) {
|
|
58
|
+
console.log("✨ No missing keys to heal.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const updates = {};
|
|
62
|
+
for (const issue of missingKeyIssues) {
|
|
63
|
+
const targetFile = path.basename(issue.file);
|
|
64
|
+
const targetLang = targetFile.replace(".json", "");
|
|
65
|
+
const key = issue.key;
|
|
66
|
+
const sourceText = getSourceText(localesDir, key);
|
|
67
|
+
if (!sourceText) {
|
|
68
|
+
console.warn(chalk_1.default.yellow(`⚠️ Skipping "${key}": Key also missing in en.json (Source)`));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!updates[targetFile])
|
|
72
|
+
updates[targetFile] = {};
|
|
73
|
+
process.stdout.write(` ✨ Translating "${key}" to ${targetLang}... `);
|
|
74
|
+
const translation = await translator_1.Translator.translate(sourceText, targetLang);
|
|
75
|
+
console.log(chalk_1.default.green("Done"));
|
|
76
|
+
updates[targetFile][key] = translation;
|
|
77
|
+
}
|
|
78
|
+
Object.keys(updates).forEach((file) => {
|
|
79
|
+
if (Object.keys(updates[file]).length === 0)
|
|
80
|
+
return;
|
|
81
|
+
const filePath = path.join(localesDir, file);
|
|
82
|
+
let currentContent = {};
|
|
83
|
+
try {
|
|
84
|
+
currentContent = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
}
|
|
88
|
+
const newContent = { ...currentContent };
|
|
89
|
+
Object.entries(updates[file]).forEach(([key, val]) => {
|
|
90
|
+
const parts = key.split(".");
|
|
91
|
+
let current = newContent;
|
|
92
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
93
|
+
const part = parts[i];
|
|
94
|
+
if (!current[part])
|
|
95
|
+
current[part] = {};
|
|
96
|
+
current = current[part];
|
|
97
|
+
}
|
|
98
|
+
current[parts[parts.length - 1]] = val;
|
|
99
|
+
});
|
|
100
|
+
fs.writeFileSync(filePath, JSON.stringify(newContent, null, 2));
|
|
101
|
+
console.log(chalk_1.default.magenta(`💾 Written ${Object.keys(updates[file]).length} new keys to ${file}`));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
exports.healIssues = healIssues;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.Translator = void 0;
|
|
30
|
+
const axios_1 = __importDefault(require("axios"));
|
|
31
|
+
const dotenv = __importStar(require("dotenv"));
|
|
32
|
+
dotenv.config();
|
|
33
|
+
const API_KEY = process.env.LINGO_API_KEY;
|
|
34
|
+
const API_URL = "https://api.lingo.dev/v1/translate";
|
|
35
|
+
class Translator {
|
|
36
|
+
static async translate(text, targetLocale) {
|
|
37
|
+
if (!text)
|
|
38
|
+
return "";
|
|
39
|
+
if (!API_KEY) {
|
|
40
|
+
console.warn("⚠️ No LINGO_API_KEY found. Using mock mode.");
|
|
41
|
+
return `[${targetLocale.toUpperCase()}] ${text}`;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const response = await axios_1.default.post(API_URL, {
|
|
45
|
+
source: "en",
|
|
46
|
+
target: targetLocale,
|
|
47
|
+
content: text,
|
|
48
|
+
}, {
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return (response.data.translation ||
|
|
55
|
+
response.data.choices[0].message.content);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(`❌ API Error translating to ${targetLocale}:`, error);
|
|
59
|
+
return text;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.Translator = Translator;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.MissingKeysRule = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
const path = __importStar(require("path"));
|
|
29
|
+
const ts_morph_1 = require("ts-morph");
|
|
30
|
+
function flattenKeys(obj, prefix = "") {
|
|
31
|
+
let keys = [];
|
|
32
|
+
for (const key in obj) {
|
|
33
|
+
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
34
|
+
keys = keys.concat(flattenKeys(obj[key], prefix + key + "."));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
keys.push(prefix + key);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return keys;
|
|
41
|
+
}
|
|
42
|
+
exports.MissingKeysRule = {
|
|
43
|
+
id: "missing-keys",
|
|
44
|
+
run: (filePaths, localesDir = "./locales") => {
|
|
45
|
+
const issues = [];
|
|
46
|
+
const project = new ts_morph_1.Project();
|
|
47
|
+
const usedKeys = new Set();
|
|
48
|
+
filePaths.forEach((filePath) => {
|
|
49
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
50
|
+
sourceFile
|
|
51
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
|
|
52
|
+
.forEach((call) => {
|
|
53
|
+
const expression = call.getExpression();
|
|
54
|
+
const functionName = ts_morph_1.Node.isPropertyAccessExpression(expression)
|
|
55
|
+
? expression.getName()
|
|
56
|
+
: expression.getText();
|
|
57
|
+
if (functionName === "t") {
|
|
58
|
+
const args = call.getArguments();
|
|
59
|
+
if (args.length > 0) {
|
|
60
|
+
const firstArg = args[0];
|
|
61
|
+
if (ts_morph_1.Node.isStringLiteral(firstArg) ||
|
|
62
|
+
ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
63
|
+
usedKeys.add(firstArg.getLiteralText());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
if (!fs.existsSync(localesDir))
|
|
70
|
+
return [];
|
|
71
|
+
const localeFiles = fs
|
|
72
|
+
.readdirSync(localesDir)
|
|
73
|
+
.filter((f) => f.endsWith(".json"));
|
|
74
|
+
localeFiles.forEach((file) => {
|
|
75
|
+
try {
|
|
76
|
+
const rawContent = fs.readFileSync(path.join(localesDir, file), "utf-8");
|
|
77
|
+
const jsonContent = JSON.parse(rawContent);
|
|
78
|
+
const existingKeys = new Set(flattenKeys(jsonContent));
|
|
79
|
+
usedKeys.forEach((key) => {
|
|
80
|
+
if (!existingKeys.has(key)) {
|
|
81
|
+
issues.push({
|
|
82
|
+
file: path.join(localesDir, file),
|
|
83
|
+
line: 1,
|
|
84
|
+
ruleId: "missing-keys",
|
|
85
|
+
severity: "error",
|
|
86
|
+
message: `Key "${key}" is missing in ${file}`,
|
|
87
|
+
key: key,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
console.error(`Error parsing ${file}:`, e);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return issues;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoStringConcatRule = void 0;
|
|
4
|
+
const ts_morph_1 = require("ts-morph");
|
|
5
|
+
exports.NoStringConcatRule = {
|
|
6
|
+
id: "no-string-concat",
|
|
7
|
+
run: (filePaths) => {
|
|
8
|
+
const project = new ts_morph_1.Project();
|
|
9
|
+
const issues = [];
|
|
10
|
+
filePaths.forEach((filePath) => {
|
|
11
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
12
|
+
sourceFile
|
|
13
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.BinaryExpression)
|
|
14
|
+
.forEach((expr) => {
|
|
15
|
+
if (expr.getOperatorToken().getText() !== "+")
|
|
16
|
+
return;
|
|
17
|
+
const left = expr.getLeft();
|
|
18
|
+
const right = expr.getRight();
|
|
19
|
+
const hasString = left.getKind() === ts_morph_1.SyntaxKind.StringLiteral ||
|
|
20
|
+
right.getKind() === ts_morph_1.SyntaxKind.StringLiteral;
|
|
21
|
+
const parentText = expr.getParent()?.getText() || "";
|
|
22
|
+
const isSafe = parentText.startsWith("console.") ||
|
|
23
|
+
parentText.includes("logger.");
|
|
24
|
+
if (hasString && !isSafe) {
|
|
25
|
+
issues.push({
|
|
26
|
+
file: filePath,
|
|
27
|
+
line: expr.getStartLineNumber(),
|
|
28
|
+
ruleId: "no-string-concat",
|
|
29
|
+
severity: "error",
|
|
30
|
+
message: "Detected string concatenation. Use translation keys.",
|
|
31
|
+
suggestion: `t("your_key", { variable: ... })`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return issues;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface LintIssue {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
ruleId: string;
|
|
5
|
+
message: string;
|
|
6
|
+
severity: "error" | "warning";
|
|
7
|
+
suggestion?: string;
|
|
8
|
+
key?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Rule {
|
|
11
|
+
id: string;
|
|
12
|
+
run: (files: string[], localesDir?: string) => LintIssue[];
|
|
13
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function uploadHealthScore(score: number, errors: number, warnings: number): Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.uploadHealthScore = void 0;
|
|
27
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
28
|
+
const dotenv = __importStar(require("dotenv"));
|
|
29
|
+
dotenv.config();
|
|
30
|
+
const SUPABASE_URL = process.env.SUPABASE_URL;
|
|
31
|
+
const SUPABASE_KEY = process.env.SUPABASE_KEY;
|
|
32
|
+
async function uploadHealthScore(score, errors, warnings) {
|
|
33
|
+
if (!process.env.CI || !SUPABASE_URL || !SUPABASE_KEY) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log("📊 Uploading metrics to Supabase...");
|
|
37
|
+
const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_KEY);
|
|
38
|
+
const { error } = await supabase.from("lingo_runs").insert({
|
|
39
|
+
project_name: process.env.GITHUB_REPOSITORY || "local-test",
|
|
40
|
+
branch: process.env.GITHUB_REF_NAME || "unknown",
|
|
41
|
+
commit_sha: process.env.GITHUB_SHA || "unknown",
|
|
42
|
+
health_score: score,
|
|
43
|
+
error_count: errors,
|
|
44
|
+
warning_count: warnings,
|
|
45
|
+
});
|
|
46
|
+
if (error)
|
|
47
|
+
console.error("❌ Failed to upload metrics:", error.message);
|
|
48
|
+
else
|
|
49
|
+
console.log("✅ Metrics uploaded successfully.");
|
|
50
|
+
}
|
|
51
|
+
exports.uploadHealthScore = uploadHealthScore;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lingo-linter-tool",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Localization linter and self-healing tool",
|
|
5
|
+
"bin": {
|
|
6
|
+
"lingo": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"dev": "ts-node src/cli.ts lint",
|
|
16
|
+
"start": "node dist/cli.js lint"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@supabase/supabase-js": "^2.91.0",
|
|
20
|
+
"axios": "^1.13.2",
|
|
21
|
+
"chalk": "^4.1.2",
|
|
22
|
+
"commander": "^10.0.0",
|
|
23
|
+
"dotenv": "^17.2.3",
|
|
24
|
+
"glob": "^8.1.0",
|
|
25
|
+
"ts-morph": "^17.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/glob": "^9.0.0",
|
|
29
|
+
"@types/node": "^18.0.0",
|
|
30
|
+
"ts-node": "^10.9.1",
|
|
31
|
+
"typescript": "^4.9.0"
|
|
32
|
+
}
|
|
33
|
+
}
|