nanos-lint 2.6.1 → 2.7.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/README.md +39 -8
- package/action.yml +15 -6
- package/dist/cli-r8LDolDk.d.ts.map +1 -1
- package/dist/{cli-5LLXRlN7.js → cli-sEO_MZ-K.js} +489 -143
- package/dist/cli-sEO_MZ-K.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +70 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/cli-5LLXRlN7.js.map +0 -1
|
@@ -6,6 +6,67 @@ import process$1 from "node:process";
|
|
|
6
6
|
import childProcess, { execFile, execFileSync } from "node:child_process";
|
|
7
7
|
import { promisify, stripVTControlCharacters } from "node:util";
|
|
8
8
|
import { EventEmitter } from "node:events";
|
|
9
|
+
//#region src/logger.ts
|
|
10
|
+
const LOG_LEVELS = [
|
|
11
|
+
"silent",
|
|
12
|
+
"error",
|
|
13
|
+
"warn",
|
|
14
|
+
"info",
|
|
15
|
+
"debug"
|
|
16
|
+
];
|
|
17
|
+
const LOG_LEVEL_PRIORITY = {
|
|
18
|
+
silent: 0,
|
|
19
|
+
error: 1,
|
|
20
|
+
warn: 2,
|
|
21
|
+
info: 3,
|
|
22
|
+
debug: 4
|
|
23
|
+
};
|
|
24
|
+
const DEFAULT_LOG_LEVEL = "warn";
|
|
25
|
+
function isValidLogLevel(level) {
|
|
26
|
+
return level in LOG_LEVEL_PRIORITY;
|
|
27
|
+
}
|
|
28
|
+
function parseLogLevel(raw) {
|
|
29
|
+
if (!raw) return DEFAULT_LOG_LEVEL;
|
|
30
|
+
const lower = raw.trim().toLowerCase();
|
|
31
|
+
return isValidLogLevel(lower) ? lower : DEFAULT_LOG_LEVEL;
|
|
32
|
+
}
|
|
33
|
+
var Logger = class {
|
|
34
|
+
level = DEFAULT_LOG_LEVEL;
|
|
35
|
+
constructor(initialLevel) {
|
|
36
|
+
if (initialLevel && isValidLogLevel(initialLevel)) this.level = initialLevel;
|
|
37
|
+
else {
|
|
38
|
+
const envLevel = process.env.NANOS_LOG_LEVEL;
|
|
39
|
+
if (envLevel && isValidLogLevel(envLevel.trim().toLowerCase())) this.level = envLevel.trim().toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
setLevel(level) {
|
|
43
|
+
if (isValidLogLevel(level)) this.level = level;
|
|
44
|
+
}
|
|
45
|
+
getLevel() {
|
|
46
|
+
return this.level;
|
|
47
|
+
}
|
|
48
|
+
isEnabledFor(level) {
|
|
49
|
+
return LOG_LEVEL_PRIORITY[this.level] >= LOG_LEVEL_PRIORITY[level];
|
|
50
|
+
}
|
|
51
|
+
/** Whether command results should be written; only `silent` disables them. */
|
|
52
|
+
isOutputEnabled() {
|
|
53
|
+
return this.level !== "silent";
|
|
54
|
+
}
|
|
55
|
+
error(...args) {
|
|
56
|
+
if (this.isEnabledFor("error")) console.error(...args);
|
|
57
|
+
}
|
|
58
|
+
warn(...args) {
|
|
59
|
+
if (this.isEnabledFor("warn")) console.warn(...args);
|
|
60
|
+
}
|
|
61
|
+
info(...args) {
|
|
62
|
+
if (this.isEnabledFor("info")) console.log(...args);
|
|
63
|
+
}
|
|
64
|
+
debug(...args) {
|
|
65
|
+
if (this.isEnabledFor("debug")) console.debug(...args);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const logger = new Logger();
|
|
69
|
+
//#endregion
|
|
9
70
|
//#region src/types.ts
|
|
10
71
|
/**
|
|
11
72
|
* Converts a URI (e.g. `file:///path/to/file` or `file:///C:/path/to/file` or `file:///c%3A/path` or `file://server/share/file`)
|
|
@@ -18,11 +79,14 @@ function fileUriToPath(uri) {
|
|
|
18
79
|
if (/^\/[a-zA-Z]:/.test(res)) res = res.slice(1);
|
|
19
80
|
if (/^[a-zA-Z]:/.test(res)) res = res.charAt(0).toUpperCase() + res.slice(1);
|
|
20
81
|
return res;
|
|
21
|
-
} catch {
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.debug(`fileURLToPath fallback for URI "${uri}": ${err instanceof Error ? err.message : String(err)}`);
|
|
22
84
|
let decoded = uri.slice(7);
|
|
23
85
|
try {
|
|
24
86
|
decoded = decodeURIComponent(decoded);
|
|
25
|
-
} catch {
|
|
87
|
+
} catch (decodeErr) {
|
|
88
|
+
logger.warn(`decodeURIComponent failed for path "${decoded}": ${decodeErr instanceof Error ? decodeErr.message : String(decodeErr)}`);
|
|
89
|
+
}
|
|
26
90
|
if (decoded.startsWith("//")) return decoded;
|
|
27
91
|
if (!decoded.startsWith("/") && uri.startsWith("file://") && !uri.startsWith("file:///")) return `//${decoded}`;
|
|
28
92
|
if (/^\/[a-zA-Z]:/.test(decoded)) decoded = decoded.slice(1);
|
|
@@ -949,7 +1013,9 @@ function mergeConfigs(base, override = {}, definitionsPath = getDefaultAnnotatio
|
|
|
949
1013
|
const candidate = path.join(definitionsPath, "annotations.lua");
|
|
950
1014
|
if (fs.existsSync(candidate)) resolvedPath = candidate;
|
|
951
1015
|
}
|
|
952
|
-
} catch {
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
logger.debug(`Error checking annotations definitionsPath directory (${definitionsPath}): ${err instanceof Error ? err.message : String(err)}`);
|
|
1018
|
+
}
|
|
953
1019
|
const normalizedDefPath = resolvedPath.split(path.sep).join("/");
|
|
954
1020
|
const baseLibraries = base.workspace?.library ?? [];
|
|
955
1021
|
const overrideLibraries = override.workspace?.library ?? [];
|
|
@@ -1162,9 +1228,10 @@ function escapePowerShellSingleQuote(str) {
|
|
|
1162
1228
|
return str.replace(/'/g, "''");
|
|
1163
1229
|
}
|
|
1164
1230
|
/**
|
|
1165
|
-
*
|
|
1231
|
+
* Fetches the latest available LuaLS release tag from the GitHub API.
|
|
1232
|
+
* Returns null if the request fails, times out, or receives an invalid tag.
|
|
1166
1233
|
*/
|
|
1167
|
-
async function
|
|
1234
|
+
async function fetchLatestLuaLSVersionFromGitHub() {
|
|
1168
1235
|
try {
|
|
1169
1236
|
const headers = { "User-Agent": "nanos-lint" };
|
|
1170
1237
|
if (process.env.GITHUB_TOKEN) headers["Authorization"] = `token ${process.env.GITHUB_TOKEN}`;
|
|
@@ -1177,8 +1244,17 @@ async function resolveLatestLuaLSVersion() {
|
|
|
1177
1244
|
const version = typeof data.tag_name === "string" ? sanitizeLuaLSVersion(data.tag_name) : null;
|
|
1178
1245
|
if (version) return version;
|
|
1179
1246
|
}
|
|
1180
|
-
} catch {
|
|
1181
|
-
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
logger.debug(`[luals] Failed to resolve latest LuaLS version from GitHub API: ${err instanceof Error ? err.message : String(err)}`);
|
|
1249
|
+
}
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Resolves the latest available LuaLS release tag from the GitHub API,
|
|
1254
|
+
* falling back to FALLBACK_LUALS_VERSION if offline or unreachable.
|
|
1255
|
+
*/
|
|
1256
|
+
async function resolveLatestLuaLSVersion() {
|
|
1257
|
+
return await fetchLatestLuaLSVersionFromGitHub() || "3.19.1";
|
|
1182
1258
|
}
|
|
1183
1259
|
/**
|
|
1184
1260
|
* Resolves a version string ("latest" -> actual tag).
|
|
@@ -1226,8 +1302,15 @@ function getPlatformInfo(version = FALLBACK_LUALS_VERSION) {
|
|
|
1226
1302
|
};
|
|
1227
1303
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
1228
1304
|
}
|
|
1229
|
-
function
|
|
1230
|
-
return path.join(systemPaths.cache, "luals"
|
|
1305
|
+
function getBaseLuaLSCacheDir() {
|
|
1306
|
+
return path.join(systemPaths.cache, "luals");
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Cache directory of a LuaLS version. `baseCacheDir` defaults to the system
|
|
1310
|
+
* cache and can be overridden to keep a caller isolated from it.
|
|
1311
|
+
*/
|
|
1312
|
+
function getCacheDir(version = FALLBACK_LUALS_VERSION, baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1313
|
+
return path.join(baseCacheDir, version);
|
|
1231
1314
|
}
|
|
1232
1315
|
/**
|
|
1233
1316
|
* Returns the legacy cache directory used in nanos-lint <= 2.2.1.
|
|
@@ -1236,6 +1319,131 @@ function getLegacyCacheDir(version = FALLBACK_LUALS_VERSION) {
|
|
|
1236
1319
|
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
1237
1320
|
return path.join(base, "nanos-lint", "luals", version);
|
|
1238
1321
|
}
|
|
1322
|
+
const LUALS_METADATA_FILENAME = "metadata.json";
|
|
1323
|
+
/**
|
|
1324
|
+
* Calculates the ISO 8601 week string for a given date in the format 'YYYY-Www' (e.g. '2026-W39').
|
|
1325
|
+
*/
|
|
1326
|
+
function getIsoWeek(d = /* @__PURE__ */ new Date()) {
|
|
1327
|
+
const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
1328
|
+
const dayNum = date.getUTCDay() || 7;
|
|
1329
|
+
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
|
|
1330
|
+
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
|
|
1331
|
+
const weekNo = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
1332
|
+
return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
|
|
1333
|
+
}
|
|
1334
|
+
function getLuaLSMetadataPath(baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1335
|
+
return path.join(baseCacheDir, LUALS_METADATA_FILENAME);
|
|
1336
|
+
}
|
|
1337
|
+
function readLuaLSMetadata(baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1338
|
+
const metaPath = getLuaLSMetadataPath(baseCacheDir);
|
|
1339
|
+
if (!fs.existsSync(metaPath)) return null;
|
|
1340
|
+
try {
|
|
1341
|
+
const content = fs.readFileSync(metaPath, "utf-8");
|
|
1342
|
+
const parsed = JSON.parse(content);
|
|
1343
|
+
if (typeof parsed?.lastCheckedWeek === "string" && typeof parsed?.latestVersion === "string") return parsed;
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
logger.debug(`[luals] Failed to parse LuaLS metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
1346
|
+
}
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
function writeLuaLSMetadata(metadata, baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1350
|
+
try {
|
|
1351
|
+
fs.mkdirSync(baseCacheDir, { recursive: true });
|
|
1352
|
+
const metaPath = getLuaLSMetadataPath(baseCacheDir);
|
|
1353
|
+
fs.writeFileSync(metaPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
logger.warn(`[luals] Failed to write LuaLS metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Lists all cached LuaLS versions in baseCacheDir that have a valid .complete marker
|
|
1360
|
+
* and a functional executable binary.
|
|
1361
|
+
*/
|
|
1362
|
+
function listCachedLuaLSVersions(baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1363
|
+
if (!fs.existsSync(baseCacheDir)) return [];
|
|
1364
|
+
try {
|
|
1365
|
+
const entries = fs.readdirSync(baseCacheDir, { withFileTypes: true });
|
|
1366
|
+
const versions = [];
|
|
1367
|
+
for (const entry of entries) {
|
|
1368
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1369
|
+
const version = sanitizeLuaLSVersion(entry.name);
|
|
1370
|
+
if (!version) continue;
|
|
1371
|
+
const dirPath = path.join(baseCacheDir, entry.name);
|
|
1372
|
+
const marker = path.join(dirPath, ".complete");
|
|
1373
|
+
const info = getPlatformInfo(version);
|
|
1374
|
+
const bin = path.join(dirPath, info.binaryRelativePath);
|
|
1375
|
+
if (fs.existsSync(marker) && fs.existsSync(bin)) try {
|
|
1376
|
+
if (fs.readFileSync(marker, "utf-8").trim() === version && isBinaryValid(bin)) versions.push(version);
|
|
1377
|
+
} catch (err) {
|
|
1378
|
+
logger.debug(`[luals] Error validating cached version ${version}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return versions;
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
logger.debug(`[luals] Failed to list cached LuaLS versions: ${err instanceof Error ? err.message : String(err)}`);
|
|
1384
|
+
return [];
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Removes older cached LuaLS version directories, preserving keepVersion,
|
|
1389
|
+
* hidden/temporary directories, and metadata files.
|
|
1390
|
+
*/
|
|
1391
|
+
function cleanupOldCachedLuaLSVersions(keepVersion, baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1392
|
+
if (!fs.existsSync(baseCacheDir)) return [];
|
|
1393
|
+
const removed = [];
|
|
1394
|
+
try {
|
|
1395
|
+
const entries = fs.readdirSync(baseCacheDir, { withFileTypes: true });
|
|
1396
|
+
for (const entry of entries) {
|
|
1397
|
+
if (!entry.isDirectory()) continue;
|
|
1398
|
+
if (entry.name.startsWith(".")) continue;
|
|
1399
|
+
if (entry.name !== keepVersion) {
|
|
1400
|
+
const dirPath = path.join(baseCacheDir, entry.name);
|
|
1401
|
+
try {
|
|
1402
|
+
fs.rmSync(dirPath, {
|
|
1403
|
+
recursive: true,
|
|
1404
|
+
force: true,
|
|
1405
|
+
maxRetries: 5,
|
|
1406
|
+
retryDelay: 100
|
|
1407
|
+
});
|
|
1408
|
+
removed.push(entry.name);
|
|
1409
|
+
logger.info(`[luals] Cleaned up older cached LuaLS version: ${entry.name}`);
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
logger.warn(`[luals] Failed to remove older cached LuaLS version at ${dirPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
logger.debug(`[luals] Failed to clean up old LuaLS versions: ${err instanceof Error ? err.message : String(err)}`);
|
|
1417
|
+
}
|
|
1418
|
+
return removed;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Locates an existing, valid LuaLS directory for the specified version in the
|
|
1422
|
+
* primary cache (`baseCacheDir`), the legacy cache, or the package root.
|
|
1423
|
+
*/
|
|
1424
|
+
function findExistingLuaLSDir(version, baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1425
|
+
const info = getPlatformInfo(version);
|
|
1426
|
+
const primaryCache = getCacheDir(version, baseCacheDir);
|
|
1427
|
+
const primaryBin = path.join(primaryCache, info.binaryRelativePath);
|
|
1428
|
+
const primaryMarker = path.join(primaryCache, ".complete");
|
|
1429
|
+
if (fs.existsSync(primaryMarker)) try {
|
|
1430
|
+
if (fs.readFileSync(primaryMarker, "utf-8").trim() === version && isBinaryValid(primaryBin)) return primaryCache;
|
|
1431
|
+
} catch (err) {
|
|
1432
|
+
logger.debug(`[luals] Error checking primary LuaLS cache marker at ${primaryMarker}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1433
|
+
}
|
|
1434
|
+
const legacyCache = getLegacyCacheDir(version);
|
|
1435
|
+
const legacyBin = path.join(legacyCache, info.binaryRelativePath);
|
|
1436
|
+
const legacyMarker = path.join(legacyCache, ".complete");
|
|
1437
|
+
if (fs.existsSync(legacyMarker)) try {
|
|
1438
|
+
if (fs.readFileSync(legacyMarker, "utf-8").trim() === version && isBinaryValid(legacyBin)) return legacyCache;
|
|
1439
|
+
} catch (err) {
|
|
1440
|
+
logger.debug(`[luals] Error checking legacy LuaLS cache marker at ${legacyMarker}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1441
|
+
}
|
|
1442
|
+
const pkgRoot = getPackageRoot();
|
|
1443
|
+
const pkgBin = path.join(pkgRoot, info.binaryRelativePath);
|
|
1444
|
+
if (fs.existsSync(pkgBin) && isBinaryValid(pkgBin)) return pkgRoot;
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1239
1447
|
/**
|
|
1240
1448
|
* Verifies that a LuaLS binary exists, has non-trivial size, and is executable.
|
|
1241
1449
|
*/
|
|
@@ -1250,7 +1458,8 @@ function isBinaryValid(binaryPath) {
|
|
|
1250
1458
|
encoding: "utf-8"
|
|
1251
1459
|
});
|
|
1252
1460
|
return /^\d+\.\d+\.\d+/.test(output.trim());
|
|
1253
|
-
} catch {
|
|
1461
|
+
} catch (err) {
|
|
1462
|
+
logger.debug(`[luals] Binary validation check failed for ${binaryPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1254
1463
|
return false;
|
|
1255
1464
|
}
|
|
1256
1465
|
}
|
|
@@ -1263,72 +1472,79 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1263
1472
|
if (fs.existsSync(destDir)) {
|
|
1264
1473
|
if (fs.existsSync(binaryPath) && fs.existsSync(completeMarker)) try {
|
|
1265
1474
|
if (fs.readFileSync(completeMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(binaryPath)) return binaryPath;
|
|
1266
|
-
} catch {
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
logger.debug(`[luals] Failed to read complete marker at ${completeMarker}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1477
|
+
}
|
|
1267
1478
|
try {
|
|
1268
1479
|
fs.rmSync(destDir, {
|
|
1269
1480
|
recursive: true,
|
|
1270
1481
|
force: true
|
|
1271
1482
|
});
|
|
1272
|
-
} catch {
|
|
1483
|
+
} catch (err) {
|
|
1484
|
+
logger.warn(`[luals] Failed to remove stale or invalid cache dir ${destDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1485
|
+
}
|
|
1273
1486
|
}
|
|
1274
1487
|
const parentDir = path.dirname(destDir);
|
|
1275
1488
|
fs.mkdirSync(parentDir, { recursive: true });
|
|
1276
1489
|
const tempDir = path.join(parentDir, `.${path.basename(destDir)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1277
1490
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
1491
|
+
const existingSourceDir = options?.reuseExisting !== false ? findExistingLuaLSDir(resolvedVersion, options?.cacheDir ?? getBaseLuaLSCacheDir()) : null;
|
|
1492
|
+
const shouldCopyFromExisting = existingSourceDir !== null && path.resolve(existingSourceDir) !== path.resolve(destDir);
|
|
1278
1493
|
const url = `https://github.com/LuaLS/lua-language-server/releases/download/${resolvedVersion}/${info.assetName}`;
|
|
1279
1494
|
const archivePath = path.join(tempDir, info.assetName);
|
|
1280
|
-
if (!options?.quiet) console.log(`[luals] Downloading LuaLS ${resolvedVersion} from ${url}...`);
|
|
1281
|
-
let response = null;
|
|
1282
|
-
let lastErr = null;
|
|
1283
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1284
|
-
try {
|
|
1285
|
-
const res = await fetch(url);
|
|
1286
|
-
if (res.ok && res.body) {
|
|
1287
|
-
response = res;
|
|
1288
|
-
break;
|
|
1289
|
-
}
|
|
1290
|
-
await res.body?.cancel();
|
|
1291
|
-
lastErr = /* @__PURE__ */ new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
|
|
1292
|
-
} catch (err) {
|
|
1293
|
-
lastErr = err;
|
|
1294
|
-
}
|
|
1295
|
-
if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
|
|
1296
|
-
}
|
|
1297
|
-
if (!response || !response.body) {
|
|
1298
|
-
try {
|
|
1299
|
-
fs.rmSync(tempDir, {
|
|
1300
|
-
recursive: true,
|
|
1301
|
-
force: true
|
|
1302
|
-
});
|
|
1303
|
-
} catch {}
|
|
1304
|
-
throw lastErr || /* @__PURE__ */ new Error(`Failed to download ${url}`);
|
|
1305
|
-
}
|
|
1306
1495
|
try {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1496
|
+
if (shouldCopyFromExisting) {
|
|
1497
|
+
if (!options?.quiet) logger.info(`[luals] Reusing existing LuaLS ${resolvedVersion} installation from ${existingSourceDir}...`);
|
|
1498
|
+
fs.cpSync(existingSourceDir, tempDir, { recursive: true });
|
|
1499
|
+
} else {
|
|
1500
|
+
if (!options?.quiet) logger.info(`[luals] Downloading LuaLS ${resolvedVersion} from ${url}...`);
|
|
1501
|
+
let response = null;
|
|
1502
|
+
let lastErr = null;
|
|
1503
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1504
|
+
try {
|
|
1505
|
+
const res = await fetch(url);
|
|
1506
|
+
if (res.ok && res.body) {
|
|
1507
|
+
response = res;
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
await res.body?.cancel();
|
|
1511
|
+
lastErr = /* @__PURE__ */ new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
|
|
1512
|
+
} catch (err) {
|
|
1513
|
+
lastErr = err;
|
|
1514
|
+
}
|
|
1515
|
+
if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
|
|
1516
|
+
}
|
|
1517
|
+
if (!response || !response.body) throw lastErr || /* @__PURE__ */ new Error(`Failed to download ${url}`);
|
|
1518
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1519
|
+
fs.writeFileSync(archivePath, Buffer.from(arrayBuffer));
|
|
1520
|
+
if (!options?.quiet) logger.info(`[luals] Extracting to ${destDir}...`);
|
|
1521
|
+
try {
|
|
1522
|
+
await execFileAsync("tar", [
|
|
1523
|
+
"-xf",
|
|
1524
|
+
archivePath,
|
|
1525
|
+
"-C",
|
|
1526
|
+
tempDir
|
|
1527
|
+
]);
|
|
1528
|
+
} catch (tarErr) {
|
|
1529
|
+
if (process.platform === "win32" && info.assetName.endsWith(".zip")) await execFileAsync("powershell.exe", [
|
|
1530
|
+
"-NoProfile",
|
|
1531
|
+
"-Command",
|
|
1532
|
+
`Expand-Archive -Path '${escapePowerShellSingleQuote(archivePath)}' -DestinationPath '${escapePowerShellSingleQuote(tempDir)}' -Force`
|
|
1533
|
+
]);
|
|
1534
|
+
else throw tarErr;
|
|
1535
|
+
}
|
|
1536
|
+
try {
|
|
1537
|
+
fs.unlinkSync(archivePath);
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
logger.debug(`[luals] Failed to delete temporary archive ${archivePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1540
|
+
}
|
|
1324
1541
|
}
|
|
1325
|
-
try {
|
|
1326
|
-
fs.unlinkSync(archivePath);
|
|
1327
|
-
} catch {}
|
|
1328
1542
|
const tempBinaryPath = path.join(tempDir, info.binaryRelativePath);
|
|
1329
1543
|
if (process.platform !== "win32") try {
|
|
1330
1544
|
fs.chmodSync(tempBinaryPath, 493);
|
|
1331
|
-
} catch {
|
|
1545
|
+
} catch (err) {
|
|
1546
|
+
logger.warn(`[luals] Failed to chmod binary at ${tempBinaryPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1547
|
+
}
|
|
1332
1548
|
if (!fs.existsSync(tempBinaryPath) || fs.statSync(tempBinaryPath).size < 1e5) throw new Error(`Failed to extract valid LuaLS binary to expected path: ${tempBinaryPath}`);
|
|
1333
1549
|
if (!isBinaryValid(tempBinaryPath)) throw new Error(`Extracted LuaLS binary at ${tempBinaryPath} is invalid or non-functional.`);
|
|
1334
1550
|
fs.writeFileSync(path.join(tempDir, ".complete"), resolvedVersion, "utf-8");
|
|
@@ -1342,8 +1558,10 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1342
1558
|
recursive: true,
|
|
1343
1559
|
force: true
|
|
1344
1560
|
});
|
|
1345
|
-
} catch {
|
|
1346
|
-
|
|
1561
|
+
} catch (err) {
|
|
1562
|
+
logger.debug(`[luals] Failed to remove temp directory after concurrent promotion: ${err instanceof Error ? err.message : String(err)}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (!options?.quiet) logger.info(`[luals] Ready: ${binaryPath}`);
|
|
1347
1565
|
return binaryPath;
|
|
1348
1566
|
}
|
|
1349
1567
|
if (attempt < 4) await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
@@ -1353,11 +1571,13 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1353
1571
|
recursive: true,
|
|
1354
1572
|
force: true
|
|
1355
1573
|
});
|
|
1356
|
-
} catch {
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
logger.warn(`[luals] Failed to clean up broken destination directory ${destDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1576
|
+
}
|
|
1357
1577
|
throw renameErr;
|
|
1358
1578
|
}
|
|
1359
1579
|
}
|
|
1360
|
-
if (!options?.quiet)
|
|
1580
|
+
if (!options?.quiet) logger.info(`[luals] Ready: ${binaryPath}`);
|
|
1361
1581
|
return binaryPath;
|
|
1362
1582
|
} finally {
|
|
1363
1583
|
if (fs.existsSync(tempDir)) try {
|
|
@@ -1365,7 +1585,9 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1365
1585
|
recursive: true,
|
|
1366
1586
|
force: true
|
|
1367
1587
|
});
|
|
1368
|
-
} catch {
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
logger.debug(`[luals] Failed to clean up temporary directory ${tempDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1590
|
+
}
|
|
1369
1591
|
}
|
|
1370
1592
|
}
|
|
1371
1593
|
async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
@@ -1375,50 +1597,112 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
|
1375
1597
|
const defaultBundledPath = path.join(getPackageRoot(), defaultInfo.binaryRelativePath);
|
|
1376
1598
|
if (fs.existsSync(defaultBundledPath) && isBinaryValid(defaultBundledPath)) return defaultBundledPath;
|
|
1377
1599
|
}
|
|
1378
|
-
const
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1600
|
+
const isDefaultOrLatest = !version || version === "latest";
|
|
1601
|
+
const baseCacheDir = options?.cacheDir ?? getBaseLuaLSCacheDir();
|
|
1602
|
+
if (!isDefaultOrLatest) {
|
|
1603
|
+
const resolvedVersion = await resolveLuaLSVersion(version);
|
|
1604
|
+
const info = getPlatformInfo(resolvedVersion);
|
|
1605
|
+
const bundledPath = path.join(getPackageRoot(), info.binaryRelativePath);
|
|
1606
|
+
if (fs.existsSync(bundledPath) && isBinaryValid(bundledPath)) return bundledPath;
|
|
1607
|
+
const cachedDir = getCacheDir(resolvedVersion, baseCacheDir);
|
|
1608
|
+
const cachedPath = path.join(cachedDir, info.binaryRelativePath);
|
|
1609
|
+
const completeMarker = path.join(cachedDir, ".complete");
|
|
1610
|
+
if (fs.existsSync(cachedPath)) {
|
|
1611
|
+
if (fs.existsSync(completeMarker)) try {
|
|
1612
|
+
if (fs.readFileSync(completeMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(cachedPath)) return cachedPath;
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
logger.debug(`[luals] Failed to read complete marker at ${completeMarker}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1615
|
+
}
|
|
1616
|
+
if (!options?.quiet) logger.warn(`[luals] Cached LuaLS binary at ${cachedPath} is corrupted or incomplete. Repairing...`);
|
|
1617
|
+
try {
|
|
1618
|
+
fs.rmSync(cachedDir, {
|
|
1619
|
+
recursive: true,
|
|
1620
|
+
force: true
|
|
1621
|
+
});
|
|
1622
|
+
} catch (err) {
|
|
1623
|
+
logger.warn(`[luals] Failed to remove corrupted cache directory ${cachedDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
const legacyDir = getLegacyCacheDir(resolvedVersion);
|
|
1627
|
+
const legacyPath = path.join(legacyDir, info.binaryRelativePath);
|
|
1628
|
+
const legacyMarker = path.join(legacyDir, ".complete");
|
|
1629
|
+
if (fs.existsSync(legacyPath)) {
|
|
1630
|
+
let validLegacy = false;
|
|
1631
|
+
if (fs.existsSync(legacyMarker)) try {
|
|
1632
|
+
if (fs.readFileSync(legacyMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(legacyPath)) validLegacy = true;
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
logger.debug(`[luals] Failed to read legacy complete marker at ${legacyMarker}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1635
|
+
}
|
|
1636
|
+
else if (isBinaryValid(legacyPath)) validLegacy = true;
|
|
1637
|
+
if (validLegacy) {
|
|
1638
|
+
if (path.resolve(legacyDir) !== path.resolve(cachedDir)) try {
|
|
1639
|
+
fs.mkdirSync(path.dirname(cachedDir), { recursive: true });
|
|
1640
|
+
fs.cpSync(legacyDir, cachedDir, { recursive: true });
|
|
1641
|
+
if (fs.existsSync(cachedPath) && isBinaryValid(cachedPath)) return cachedPath;
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
logger.warn(`[luals] Failed to migrate legacy cache from ${legacyDir} to ${cachedDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1644
|
+
}
|
|
1645
|
+
return legacyPath;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1390
1648
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
} catch {
|
|
1396
|
-
|
|
1397
|
-
const legacyDir = getLegacyCacheDir(resolvedVersion);
|
|
1398
|
-
const legacyPath = path.join(legacyDir, info.binaryRelativePath);
|
|
1399
|
-
const legacyMarker = path.join(legacyDir, ".complete");
|
|
1400
|
-
if (fs.existsSync(legacyPath)) {
|
|
1401
|
-
let validLegacy = false;
|
|
1402
|
-
if (fs.existsSync(legacyMarker)) try {
|
|
1403
|
-
if (fs.readFileSync(legacyMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(legacyPath)) validLegacy = true;
|
|
1404
|
-
} catch {}
|
|
1405
|
-
else if (isBinaryValid(legacyPath)) validLegacy = true;
|
|
1406
|
-
if (validLegacy) {
|
|
1407
|
-
if (path.resolve(legacyDir) !== path.resolve(cachedDir)) try {
|
|
1408
|
-
fs.mkdirSync(path.dirname(cachedDir), { recursive: true });
|
|
1409
|
-
fs.cpSync(legacyDir, cachedDir, { recursive: true });
|
|
1410
|
-
if (fs.existsSync(cachedPath) && isBinaryValid(cachedPath)) return cachedPath;
|
|
1411
|
-
} catch {}
|
|
1412
|
-
return legacyPath;
|
|
1649
|
+
const cmd = process.platform === "win32" ? "where.exe" : "which";
|
|
1650
|
+
const { stdout } = await execFileAsync(cmd, ["lua-language-server"]);
|
|
1651
|
+
const found = stdout.trim().split(/\r?\n/)[0];
|
|
1652
|
+
if (found && fs.existsSync(found) && isBinaryValid(found)) return found;
|
|
1653
|
+
} catch (err) {
|
|
1654
|
+
logger.debug(`[luals] LuaLS binary not found in PATH: ${err instanceof Error ? err.message : String(err)}`);
|
|
1413
1655
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1656
|
+
return await downloadAndExtractLuaLS(resolvedVersion, getCacheDir(resolvedVersion, baseCacheDir), options);
|
|
1657
|
+
}
|
|
1658
|
+
const currentWeek = getIsoWeek();
|
|
1659
|
+
const metadata = readLuaLSMetadata(baseCacheDir);
|
|
1660
|
+
if (metadata && metadata.lastCheckedWeek === currentWeek && metadata.latestVersion) {
|
|
1661
|
+
const info = getPlatformInfo(metadata.latestVersion);
|
|
1662
|
+
const cachedPath = path.join(getCacheDir(metadata.latestVersion, baseCacheDir), info.binaryRelativePath);
|
|
1663
|
+
if (isBinaryValid(cachedPath)) return cachedPath;
|
|
1664
|
+
}
|
|
1665
|
+
const cachedVersions = listCachedLuaLSVersions(baseCacheDir);
|
|
1666
|
+
if (metadata && metadata.lastCheckedWeek === currentWeek && cachedVersions.length > 0) {
|
|
1667
|
+
const fallbackVersion = cachedVersions[0];
|
|
1668
|
+
const info = getPlatformInfo(fallbackVersion);
|
|
1669
|
+
return path.join(getCacheDir(fallbackVersion, baseCacheDir), info.binaryRelativePath);
|
|
1670
|
+
}
|
|
1671
|
+
const onlineTag = await fetchLatestLuaLSVersionFromGitHub();
|
|
1672
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1673
|
+
let targetVersion;
|
|
1674
|
+
if (onlineTag) targetVersion = onlineTag;
|
|
1675
|
+
else if (metadata?.latestVersion && cachedVersions.includes(metadata.latestVersion)) {
|
|
1676
|
+
logger.info(`[luals] Network unreachable or rate limited; using cached LuaLS ${metadata.latestVersion}.`);
|
|
1677
|
+
targetVersion = metadata.latestVersion;
|
|
1678
|
+
} else if (cachedVersions.length > 0) {
|
|
1679
|
+
logger.info(`[luals] Network unreachable or rate limited; using cached LuaLS ${cachedVersions[0]}.`);
|
|
1680
|
+
targetVersion = cachedVersions[0];
|
|
1681
|
+
} else targetVersion = FALLBACK_LUALS_VERSION;
|
|
1682
|
+
writeLuaLSMetadata({
|
|
1683
|
+
lastCheckedWeek: currentWeek,
|
|
1684
|
+
latestVersion: targetVersion,
|
|
1685
|
+
lastCheckedDate: today
|
|
1686
|
+
}, baseCacheDir);
|
|
1687
|
+
const info = getPlatformInfo(targetVersion);
|
|
1688
|
+
const targetCacheDir = getCacheDir(targetVersion, baseCacheDir);
|
|
1689
|
+
const targetBinaryPath = path.join(targetCacheDir, info.binaryRelativePath);
|
|
1690
|
+
const completeMarker = path.join(targetCacheDir, ".complete");
|
|
1691
|
+
if (fs.existsSync(completeMarker) && isBinaryValid(targetBinaryPath)) {
|
|
1692
|
+
cleanupOldCachedLuaLSVersions(targetVersion, baseCacheDir);
|
|
1693
|
+
return targetBinaryPath;
|
|
1694
|
+
}
|
|
1695
|
+
if (!onlineTag) try {
|
|
1416
1696
|
const cmd = process.platform === "win32" ? "where.exe" : "which";
|
|
1417
1697
|
const { stdout } = await execFileAsync(cmd, ["lua-language-server"]);
|
|
1418
1698
|
const found = stdout.trim().split(/\r?\n/)[0];
|
|
1419
1699
|
if (found && fs.existsSync(found) && isBinaryValid(found)) return found;
|
|
1420
|
-
} catch {
|
|
1421
|
-
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
logger.debug(`[luals] LuaLS binary not found in PATH: ${err instanceof Error ? err.message : String(err)}`);
|
|
1702
|
+
}
|
|
1703
|
+
const downloadedBinary = await downloadAndExtractLuaLS(targetVersion, targetCacheDir, options);
|
|
1704
|
+
cleanupOldCachedLuaLSVersions(targetVersion, baseCacheDir);
|
|
1705
|
+
return downloadedBinary;
|
|
1422
1706
|
}
|
|
1423
1707
|
async function runLuaLSCheck(targetPath, configPath, options) {
|
|
1424
1708
|
const absoluteTarget = path.resolve(targetPath);
|
|
@@ -1448,6 +1732,7 @@ async function runLuaLSCheck(targetPath, configPath, options) {
|
|
|
1448
1732
|
});
|
|
1449
1733
|
} catch (err) {
|
|
1450
1734
|
execError = err;
|
|
1735
|
+
logger.debug(`[luals] LuaLS process exited with error or non-zero status: ${err instanceof Error ? err.message : String(err)}`);
|
|
1451
1736
|
}
|
|
1452
1737
|
let diagnostics = {};
|
|
1453
1738
|
let parseSucceeded = false;
|
|
@@ -1455,10 +1740,14 @@ async function runLuaLSCheck(targetPath, configPath, options) {
|
|
|
1455
1740
|
const content = fs.readFileSync(checkOutPath, "utf-8");
|
|
1456
1741
|
diagnostics = JSON.parse(content);
|
|
1457
1742
|
parseSucceeded = true;
|
|
1458
|
-
} catch
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
logger.error(`[luals] Failed to read or parse diagnostic output from ${checkOutPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1745
|
+
} finally {
|
|
1459
1746
|
try {
|
|
1460
1747
|
fs.unlinkSync(checkOutPath);
|
|
1461
|
-
} catch {
|
|
1748
|
+
} catch (err) {
|
|
1749
|
+
logger.debug(`[luals] Failed to delete check output file ${checkOutPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1750
|
+
}
|
|
1462
1751
|
}
|
|
1463
1752
|
if (!parseSucceeded) {
|
|
1464
1753
|
const cacheHint = `(Cache location: ${getCacheDir()})`;
|
|
@@ -1514,7 +1803,9 @@ function countCheckedFiles(targetPath, configPath) {
|
|
|
1514
1803
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1515
1804
|
if (cfg.workspace?.ignoreDir) ignoreDirs = cfg.workspace.ignoreDir;
|
|
1516
1805
|
if (cfg.files?.exclude) excludePatterns = cfg.files.exclude;
|
|
1517
|
-
} catch {
|
|
1806
|
+
} catch (err) {
|
|
1807
|
+
logger.warn(`[luals] Failed to parse config file for file counting at ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1808
|
+
}
|
|
1518
1809
|
const normIgnoreDirs = new Set(ignoreDirs.map((d) => d.replace(/\\/g, "/").toLowerCase()));
|
|
1519
1810
|
function isExcluded(relPath) {
|
|
1520
1811
|
const norm = relPath.replace(/\\/g, "/");
|
|
@@ -1532,7 +1823,9 @@ function countCheckedFiles(targetPath, configPath) {
|
|
|
1532
1823
|
const baseRegexStr = "^" + normPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$";
|
|
1533
1824
|
try {
|
|
1534
1825
|
if (new RegExp(baseRegexStr, "i").test(baseName)) return true;
|
|
1535
|
-
} catch {
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
logger.debug(`[luals] Invalid regex for pattern "${normPat}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1828
|
+
}
|
|
1536
1829
|
}
|
|
1537
1830
|
let regexStr = normPat;
|
|
1538
1831
|
const hasLeadingDoubleStar = regexStr.startsWith("**/");
|
|
@@ -1544,7 +1837,9 @@ function countCheckedFiles(targetPath, configPath) {
|
|
|
1544
1837
|
if (hasTrailingDoubleStar) escaped = `${escaped}(?:/.*)?`;
|
|
1545
1838
|
try {
|
|
1546
1839
|
if (new RegExp(`^${escaped}$`, "i").test(norm)) return true;
|
|
1547
|
-
} catch {
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
logger.debug(`[luals] Invalid glob regex for pattern "${normPat}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1842
|
+
}
|
|
1548
1843
|
}
|
|
1549
1844
|
}
|
|
1550
1845
|
return false;
|
|
@@ -1554,7 +1849,8 @@ function countCheckedFiles(targetPath, configPath) {
|
|
|
1554
1849
|
let entries;
|
|
1555
1850
|
try {
|
|
1556
1851
|
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1557
|
-
} catch {
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
logger.debug(`[luals] Failed to read directory ${currentDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1558
1854
|
return;
|
|
1559
1855
|
}
|
|
1560
1856
|
for (const entry of entries) {
|
|
@@ -1564,7 +1860,8 @@ function countCheckedFiles(targetPath, configPath) {
|
|
|
1564
1860
|
if (entry.isDirectory() || entry.isSymbolicLink() && (() => {
|
|
1565
1861
|
try {
|
|
1566
1862
|
return fs.statSync(fullPath).isDirectory();
|
|
1567
|
-
} catch {
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
logger.debug(`[luals] Failed to stat symlink target ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1568
1865
|
return false;
|
|
1569
1866
|
}
|
|
1570
1867
|
})()) {
|
|
@@ -1661,7 +1958,9 @@ function formatPretty(result, cwd = process.cwd(), useColor = shouldEnableColor(
|
|
|
1661
1958
|
let fileContent = [];
|
|
1662
1959
|
try {
|
|
1663
1960
|
if (fs.existsSync(filePath)) fileContent = fs.readFileSync(filePath, "utf-8").split(/\r?\n/);
|
|
1664
|
-
} catch {
|
|
1961
|
+
} catch (err) {
|
|
1962
|
+
logger.debug(`Failed to read file snippet for ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1963
|
+
}
|
|
1665
1964
|
for (const d of diags) {
|
|
1666
1965
|
const line = d.range.start.line + 1;
|
|
1667
1966
|
const col = d.range.start.character + 1;
|
|
@@ -4714,7 +5013,9 @@ function readAnnotationsMetadata(cacheDir = getAnnotationsCacheDir()) {
|
|
|
4714
5013
|
const content = fs.readFileSync(metaPath, "utf-8");
|
|
4715
5014
|
const parsed = JSON.parse(content);
|
|
4716
5015
|
if (typeof parsed?.commitId === "string" && typeof parsed?.lastChecked === "string") return parsed;
|
|
4717
|
-
} catch {
|
|
5016
|
+
} catch (err) {
|
|
5017
|
+
logger.debug(`Failed to parse annotations metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
5018
|
+
}
|
|
4718
5019
|
return null;
|
|
4719
5020
|
}
|
|
4720
5021
|
async function fetchLatestCommitId() {
|
|
@@ -4729,7 +5030,9 @@ async function fetchLatestCommitId() {
|
|
|
4729
5030
|
const data = await res.json();
|
|
4730
5031
|
if (typeof data.sha === "string" && /^[0-9a-fA-F]{7,40}$/.test(data.sha)) return data.sha;
|
|
4731
5032
|
}
|
|
4732
|
-
} catch {
|
|
5033
|
+
} catch (err) {
|
|
5034
|
+
logger.warn(`Failed to resolve latest annotations commit from GitHub: ${err instanceof Error ? err.message : String(err)}`);
|
|
5035
|
+
}
|
|
4733
5036
|
return null;
|
|
4734
5037
|
}
|
|
4735
5038
|
async function fetchRawAnnotationsContent() {
|
|
@@ -4774,7 +5077,9 @@ function updateLastCheckedDate(commitId, cacheDir = getAnnotationsCacheDir()) {
|
|
|
4774
5077
|
} finally {
|
|
4775
5078
|
try {
|
|
4776
5079
|
if (fs.existsSync(tempMetaPath)) fs.unlinkSync(tempMetaPath);
|
|
4777
|
-
} catch {
|
|
5080
|
+
} catch (err) {
|
|
5081
|
+
logger.warn(`Failed to clean up temporary metadata file ${tempMetaPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5082
|
+
}
|
|
4778
5083
|
}
|
|
4779
5084
|
return metadata;
|
|
4780
5085
|
}
|
|
@@ -4792,8 +5097,8 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
4792
5097
|
try {
|
|
4793
5098
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
4794
5099
|
if (!options?.quiet) {
|
|
4795
|
-
if (commitId && commitId !== "unknown")
|
|
4796
|
-
else
|
|
5100
|
+
if (commitId && commitId !== "unknown") logger.info(`[annotations] Downloading nanos world API annotations (${commitId.slice(0, 7)})...`);
|
|
5101
|
+
else logger.info("[annotations] Downloading nanos world API annotations...");
|
|
4797
5102
|
}
|
|
4798
5103
|
const content = await fetchRawAnnotationsContent();
|
|
4799
5104
|
const tempAnnotationsPath = path.join(tempDir, ANNOTATIONS_FILENAME);
|
|
@@ -4819,8 +5124,8 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
4819
5124
|
await copyFileWithRetry(tempAnnotationsPath, finalAnnotationsPath);
|
|
4820
5125
|
await copyFileWithRetry(tempMetaPath, finalMetaPath);
|
|
4821
5126
|
if (!options?.quiet) {
|
|
4822
|
-
if (commitId && commitId !== "unknown")
|
|
4823
|
-
else
|
|
5127
|
+
if (commitId && commitId !== "unknown") logger.info(`[annotations] Updated annotations.lua to commit ${commitId.slice(0, 7)}.`);
|
|
5128
|
+
else logger.info("[annotations] Updated annotations.lua.");
|
|
4824
5129
|
}
|
|
4825
5130
|
return finalAnnotationsPath;
|
|
4826
5131
|
} catch (err) {
|
|
@@ -4829,7 +5134,9 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
4829
5134
|
const backupMeta = path.join(backupDir, METADATA_FILENAME);
|
|
4830
5135
|
if (fs.existsSync(backupAnnotations)) await copyFileWithRetry(backupAnnotations, finalAnnotationsPath);
|
|
4831
5136
|
if (fs.existsSync(backupMeta)) await copyFileWithRetry(backupMeta, finalMetaPath);
|
|
4832
|
-
} catch {
|
|
5137
|
+
} catch (rollbackErr) {
|
|
5138
|
+
logger.error(`Failed to restore annotations from backup during rollback: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
|
|
5139
|
+
}
|
|
4833
5140
|
throw err;
|
|
4834
5141
|
} finally {
|
|
4835
5142
|
try {
|
|
@@ -4841,7 +5148,9 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
4841
5148
|
recursive: true,
|
|
4842
5149
|
force: true
|
|
4843
5150
|
});
|
|
4844
|
-
} catch {
|
|
5151
|
+
} catch (err) {
|
|
5152
|
+
logger.warn(`Failed to clean up annotations temporary or backup directory: ${err instanceof Error ? err.message : String(err)}`);
|
|
5153
|
+
}
|
|
4845
5154
|
}
|
|
4846
5155
|
}
|
|
4847
5156
|
/**
|
|
@@ -4882,7 +5191,9 @@ async function resolveAnnotations(options = {}) {
|
|
|
4882
5191
|
if (fs.existsSync(cachedAnnotationsFile)) {
|
|
4883
5192
|
try {
|
|
4884
5193
|
updateLastCheckedDate(metadata?.commitId || "unknown", cacheDir);
|
|
4885
|
-
} catch {
|
|
5194
|
+
} catch (err) {
|
|
5195
|
+
logger.warn(`Failed to update lastChecked date for cached annotations: ${err instanceof Error ? err.message : String(err)}`);
|
|
5196
|
+
}
|
|
4886
5197
|
return cachedAnnotationsFile;
|
|
4887
5198
|
}
|
|
4888
5199
|
try {
|
|
@@ -4905,10 +5216,15 @@ function getVersionString() {
|
|
|
4905
5216
|
const root = getPackageRoot();
|
|
4906
5217
|
try {
|
|
4907
5218
|
return `nanos-lint v${JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8")).version}`;
|
|
4908
|
-
} catch {
|
|
5219
|
+
} catch (err) {
|
|
5220
|
+
logger.debug(`Could not read version from package.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
4909
5221
|
return "nanos-lint v1.0.0";
|
|
4910
5222
|
}
|
|
4911
5223
|
}
|
|
5224
|
+
/** Writes command results to stdout unless the level is `silent`. */
|
|
5225
|
+
function writeOutput(message) {
|
|
5226
|
+
if (logger.isOutputEnabled()) console.log(message);
|
|
5227
|
+
}
|
|
4912
5228
|
function collectIgnorePatterns(val, prev) {
|
|
4913
5229
|
const parts = val.split(/[\r\n,]+/).map((s) => s.trim()).filter(Boolean);
|
|
4914
5230
|
return (prev ?? []).concat(parts);
|
|
@@ -4916,9 +5232,20 @@ function collectIgnorePatterns(val, prev) {
|
|
|
4916
5232
|
function createProgram(options) {
|
|
4917
5233
|
const setExitCode = options?.setExitCode ?? (() => {});
|
|
4918
5234
|
const program = new Command("nanos-lint");
|
|
4919
|
-
program.description("Linter and type-checker for nanos world Lua scripts").version(getVersionString(), "-v, --version", "Show version information").
|
|
5235
|
+
program.description("Linter and type-checker for nanos world Lua scripts").version(getVersionString(), "-v, --version", "Show version information").addOption(new Option("-l, --log-level <level>", "Logging level: error, warn, info, debug, silent").choices([
|
|
5236
|
+
"error",
|
|
5237
|
+
"warn",
|
|
5238
|
+
"info",
|
|
5239
|
+
"debug",
|
|
5240
|
+
"silent"
|
|
5241
|
+
]).default(DEFAULT_LOG_LEVEL)).hook("preAction", (thisCommand, actionCommand) => {
|
|
5242
|
+
const target = actionCommand || thisCommand;
|
|
5243
|
+
const opts = target.optsWithGlobals ? target.optsWithGlobals() : target.opts();
|
|
5244
|
+
if (opts.quiet) logger.setLevel("error");
|
|
5245
|
+
else if (opts.logLevel && isValidLogLevel(opts.logLevel)) logger.setLevel(opts.logLevel);
|
|
5246
|
+
}).exitOverride().configureOutput({
|
|
4920
5247
|
writeOut: (str) => console.log(str.trimEnd()),
|
|
4921
|
-
writeErr: (str) =>
|
|
5248
|
+
writeErr: (str) => logger.error(str.trimEnd())
|
|
4922
5249
|
});
|
|
4923
5250
|
program.command("check [path]", { isDefault: true }).description("Check a workspace or Lua file (default command)").addOption(new Option("--checklevel <level>", "Minimum diagnostic level: Error, Warning, Information, Hint").choices([
|
|
4924
5251
|
"Error",
|
|
@@ -4929,7 +5256,15 @@ function createProgram(options) {
|
|
|
4929
5256
|
"pretty",
|
|
4930
5257
|
"json",
|
|
4931
5258
|
"github"
|
|
4932
|
-
])).option("-i, --ignore <pattern>", "Files or directories to ignore (supports glob patterns, repeatable)", collectIgnorePatterns).option("--luals-version <ver>", `Version of LuaLS to use (default: ${DEFAULT_LUALS_VERSION})`, DEFAULT_LUALS_VERSION).option("--no-fail", "Do not exit with code 1 if diagnostics are found").option("--quiet", "Suppress progress output").
|
|
5259
|
+
])).option("-i, --ignore <pattern>", "Files or directories to ignore (supports glob patterns, repeatable)", collectIgnorePatterns).option("--luals-version <ver>", `Version of LuaLS to use (default: ${DEFAULT_LUALS_VERSION})`, DEFAULT_LUALS_VERSION).option("--no-fail", "Do not exit with code 1 if diagnostics are found").option("--quiet", "Suppress progress output").addOption(new Option("-l, --log-level <level>", "Logging level: error, warn, info, debug, silent").choices([
|
|
5260
|
+
"error",
|
|
5261
|
+
"warn",
|
|
5262
|
+
"info",
|
|
5263
|
+
"debug",
|
|
5264
|
+
"silent"
|
|
5265
|
+
])).option("--github", "Output in GitHub Actions format (shortcut for --format=github)").action(async (targetPath = ".", opts) => {
|
|
5266
|
+
if (opts.quiet) logger.setLevel("error");
|
|
5267
|
+
else if (opts.logLevel && isValidLogLevel(opts.logLevel)) logger.setLevel(opts.logLevel);
|
|
4933
5268
|
const format = opts.github ? "github" : opts.format || (process.env.GITHUB_ACTIONS ? "github" : "pretty");
|
|
4934
5269
|
const checkOptions = {
|
|
4935
5270
|
path: targetPath,
|
|
@@ -4959,42 +5294,42 @@ function createProgram(options) {
|
|
|
4959
5294
|
} finally {
|
|
4960
5295
|
if (resolved.isTemp && fs.existsSync(resolved.configPath)) try {
|
|
4961
5296
|
fs.unlinkSync(resolved.configPath);
|
|
4962
|
-
} catch {
|
|
5297
|
+
} catch (err) {
|
|
5298
|
+
logger.warn(`Failed to clean up temporary config file ${resolved.configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5299
|
+
}
|
|
4963
5300
|
}
|
|
4964
5301
|
const output = formatReport(result, checkOptions.format, process.cwd());
|
|
4965
|
-
if (output)
|
|
5302
|
+
if (output) writeOutput(output);
|
|
4966
5303
|
if (!result.passed && checkOptions.failOnError) setExitCode(1);
|
|
4967
5304
|
else setExitCode(0);
|
|
4968
5305
|
});
|
|
4969
5306
|
program.command("init [path]").description("Scaffold a .luarc.json configuration in the workspace").option("-f, --force", "Overwrite existing .luarc.json configuration").option("--annotations <path>", "Path to custom annotations.lua file").action(async (targetPath = ".", opts) => {
|
|
4970
5307
|
const annotationsPath = await resolveAnnotations({ customPath: opts.annotations });
|
|
4971
|
-
|
|
5308
|
+
writeOutput(`[init] Initialized nanos world LuaLS configuration: ${initWorkspace(path.resolve(targetPath), {
|
|
4972
5309
|
force: opts.force,
|
|
4973
5310
|
annotationsPath
|
|
4974
|
-
});
|
|
4975
|
-
console.log(`[init] Initialized nanos world LuaLS configuration: ${created}`);
|
|
5311
|
+
})}`);
|
|
4976
5312
|
setExitCode(0);
|
|
4977
5313
|
});
|
|
4978
5314
|
program.command("download-luals [version]").description("Download and cache the LuaLS binary").option("--luals-version <ver>", `Version of LuaLS to use (default: ${DEFAULT_LUALS_VERSION})`).action(async (version, opts) => {
|
|
4979
5315
|
const ver = version || opts?.lualsVersion || "latest";
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
console.log(`[luals] Ready at: ${bin}`);
|
|
5316
|
+
writeOutput(`[luals] Downloading LuaLS ${ver}...`);
|
|
5317
|
+
writeOutput(`[luals] Ready at: ${await resolveLuaLSBinary(ver)}`);
|
|
4983
5318
|
setExitCode(0);
|
|
4984
5319
|
});
|
|
4985
5320
|
program.command("clean-cache").alias("clean").description("Clear the nanos-lint cache").action(() => {
|
|
4986
5321
|
try {
|
|
4987
5322
|
const cleared = cleanCache();
|
|
4988
|
-
if (cleared)
|
|
4989
|
-
else
|
|
5323
|
+
if (cleared) writeOutput(`[cache] Cleared cache at: ${cleared}`);
|
|
5324
|
+
else writeOutput(`[cache] Cache is already empty (${systemPaths.cache})`);
|
|
4990
5325
|
setExitCode(0);
|
|
4991
5326
|
} catch (err) {
|
|
4992
|
-
|
|
5327
|
+
logger.error(`[cache] Failed to clear cache: ${err instanceof Error ? err.message : String(err)}`);
|
|
4993
5328
|
setExitCode(1);
|
|
4994
5329
|
}
|
|
4995
5330
|
});
|
|
4996
5331
|
program.command("version").description("Show version information").action(() => {
|
|
4997
|
-
|
|
5332
|
+
writeOutput(getVersionString());
|
|
4998
5333
|
setExitCode(0);
|
|
4999
5334
|
});
|
|
5000
5335
|
program.helpCommand("help [command]", "Show this help message");
|
|
@@ -5010,6 +5345,15 @@ Examples:
|
|
|
5010
5345
|
return program;
|
|
5011
5346
|
}
|
|
5012
5347
|
async function runCLI(args = process.argv.slice(2)) {
|
|
5348
|
+
for (let i = 0; i < args.length; i++) {
|
|
5349
|
+
const arg = args[i];
|
|
5350
|
+
if (arg === "-l" || arg === "--log-level") {
|
|
5351
|
+
if (i + 1 < args.length && isValidLogLevel(args[i + 1])) logger.setLevel(args[i + 1]);
|
|
5352
|
+
} else if (arg.startsWith("--log-level=")) {
|
|
5353
|
+
const val = arg.split("=")[1];
|
|
5354
|
+
if (val && isValidLogLevel(val)) logger.setLevel(val);
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5013
5357
|
let exitCode = 0;
|
|
5014
5358
|
const program = createProgram({ setExitCode: (code) => {
|
|
5015
5359
|
exitCode = code;
|
|
@@ -5020,8 +5364,8 @@ async function runCLI(args = process.argv.slice(2)) {
|
|
|
5020
5364
|
} catch (err) {
|
|
5021
5365
|
if (err instanceof CommanderError) return err.exitCode;
|
|
5022
5366
|
const message = err instanceof Error ? err.message : String(err);
|
|
5023
|
-
|
|
5024
|
-
if (process.env.DEBUG && err instanceof Error && err.stack)
|
|
5367
|
+
logger.error(`error: ${message}`);
|
|
5368
|
+
if (process.env.DEBUG && err instanceof Error && err.stack) logger.error(err.stack);
|
|
5025
5369
|
return 1;
|
|
5026
5370
|
}
|
|
5027
5371
|
}
|
|
@@ -5031,7 +5375,8 @@ function isDirectExecution(importMetaUrl = import.meta.url, argv1 = process.argv
|
|
|
5031
5375
|
try {
|
|
5032
5376
|
if (urlStr.startsWith("file:")) return fileURLToPath(urlStr);
|
|
5033
5377
|
return urlStr;
|
|
5034
|
-
} catch {
|
|
5378
|
+
} catch (err) {
|
|
5379
|
+
logger.debug(`[cli] Failed to convert URL "${urlStr}" using fileURLToPath: ${err instanceof Error ? err.message : String(err)}`);
|
|
5035
5380
|
return urlStr.replace(/^file:\/\/\/?/, "");
|
|
5036
5381
|
}
|
|
5037
5382
|
};
|
|
@@ -5043,7 +5388,8 @@ function isDirectExecution(importMetaUrl = import.meta.url, argv1 = process.argv
|
|
|
5043
5388
|
const cliJsPath = path.join(moduleDir, "cli.js").toLowerCase();
|
|
5044
5389
|
const cliTsPath = path.join(moduleDir, "cli.ts").toLowerCase();
|
|
5045
5390
|
if (scriptPath === cliJsPath || scriptPath === cliTsPath) return true;
|
|
5046
|
-
} catch {
|
|
5391
|
+
} catch (err) {
|
|
5392
|
+
logger.debug(`[cli] Failed to resolve realpath for entry detection: ${err instanceof Error ? err.message : String(err)}`);
|
|
5047
5393
|
const normArgv = path.resolve(argv1).toLowerCase();
|
|
5048
5394
|
const normMeta = toPath(importMetaUrl).toLowerCase();
|
|
5049
5395
|
if (normArgv === normMeta) return true;
|
|
@@ -5055,10 +5401,10 @@ function isDirectExecution(importMetaUrl = import.meta.url, argv1 = process.argv
|
|
|
5055
5401
|
if (isDirectExecution()) runCLI().then((code) => {
|
|
5056
5402
|
process.exit(code);
|
|
5057
5403
|
}).catch((err) => {
|
|
5058
|
-
|
|
5404
|
+
logger.error(err);
|
|
5059
5405
|
process.exit(1);
|
|
5060
5406
|
});
|
|
5061
5407
|
//#endregion
|
|
5062
|
-
export {
|
|
5408
|
+
export { getDefaultAnnotationsPath as $, FALLBACK_LUALS_VERSION as A, getIsoWeek as B, formatProblemSummary as C, pluralize as D, getColors as E, escapePowerShellSingleQuote as F, listCachedLuaLSVersions as G, getLuaLSMetadataPath as H, fetchLatestLuaLSVersionFromGitHub as I, resolveLuaLSBinary as J, readLuaLSMetadata as K, findExistingLuaLSDir as L, cleanupOldCachedLuaLSVersions as M, countCheckedFiles as N, shouldEnableColor as O, downloadAndExtractLuaLS as P, writeLuaLSMetadata as Q, getBaseLuaLSCacheDir as R, formatPretty as S, formatSeverityBadge as T, getPlatformInfo as U, getLegacyCacheDir as V, isBinaryValid as W, runLuaLSCheck as X, resolveLuaLSVersion as Y, sanitizeLuaLSVersion as Z, getTodayDateString as _, isValidLogLevel as _t, ANNOTATIONS_FILENAME as a, mergeConfigs as at, updateLastCheckedDate as b, GITHUB_COMMITS_API as c, stripJsonComments as ct, downloadAndCacheAnnotations as d, systemPaths as dt, getDefaultTemplatePath as et, fetchLatestCommitId as f, fileUriToPath as ft, getCachedAnnotationsFilePath as g, Logger as gt, getAnnotationsMetadataFilePath as h, LOG_LEVEL_PRIORITY as ht, runCLI as i, loadConfigFile as it, LUALS_METADATA_FILENAME as j, DEFAULT_LUALS_VERSION as k, METADATA_FILENAME as l, stripTrailingSlashes as lt, getAnnotationsCacheDir as m, LOG_LEVELS as mt, createProgram as n, getPackageRoot as nt, DOCGEN_BRANCH as o, parseJsonc as ot, fetchRawAnnotationsContent as p, DEFAULT_LOG_LEVEL as pt, resolveLatestLuaLSVersion as q, isDirectExecution as r, initWorkspace as rt, DOCGEN_REPO as s, resolveWorkspaceConfig as st, collectIgnorePatterns as t, getDefinitionsDir as tt, RAW_ANNOTATIONS_URL as u, cleanCache as ut, readAnnotationsMetadata as v, logger as vt, formatReport as w, formatGitHubAnnotations as x, resolveAnnotations as y, parseLogLevel as yt, getCacheDir as z };
|
|
5063
5409
|
|
|
5064
|
-
//# sourceMappingURL=cli-
|
|
5410
|
+
//# sourceMappingURL=cli-sEO_MZ-K.js.map
|