nanos-lint 2.8.1 → 2.8.2
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 +49 -44
- package/action.yml +25 -25
- package/bin/nanos-lint.js +0 -2
- package/dist/{cli-BAy_Ztvh.js → cli-BTHx4yp_.js} +344 -161
- package/dist/cli-BTHx4yp_.js.map +1 -0
- package/dist/{cli-r8LDolDk.d.ts → cli-dxsaVVVq.d.ts} +5 -1
- package/dist/{cli-r8LDolDk.d.ts.map → cli-dxsaVVVq.d.ts.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +90 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +13 -3
- package/templates/.luarc.json +2 -9
- package/dist/cli-BAy_Ztvh.js.map +0 -1
|
@@ -5,8 +5,8 @@ import path, { posix, win32 } from "node:path";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import process$1 from "node:process";
|
|
7
7
|
import childProcess, { execFile, execFileSync } from "node:child_process";
|
|
8
|
-
import crypto from "node:crypto";
|
|
9
8
|
import { promisify, stripVTControlCharacters } from "node:util";
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
10
|
import Pe, { Readable } from "node:stream";
|
|
11
11
|
import { pipeline } from "node:stream/promises";
|
|
12
12
|
import { lstatSync, readdir, readdirSync, readlinkSync, realpathSync } from "fs";
|
|
@@ -29,9 +29,11 @@ const LOG_LEVEL_PRIORITY = {
|
|
|
29
29
|
debug: 4
|
|
30
30
|
};
|
|
31
31
|
const DEFAULT_LOG_LEVEL = "warn";
|
|
32
|
+
/** Validates whether a string corresponds to a recognized log level. */
|
|
32
33
|
function isValidLogLevel(level) {
|
|
33
34
|
return level in LOG_LEVEL_PRIORITY;
|
|
34
35
|
}
|
|
36
|
+
/** Parses a string into a valid LogLevel, defaulting to 'warn'. */
|
|
35
37
|
function parseLogLevel(raw) {
|
|
36
38
|
if (!raw) return DEFAULT_LOG_LEVEL;
|
|
37
39
|
const lower = raw.trim().toLowerCase();
|
|
@@ -46,12 +48,15 @@ var Logger = class {
|
|
|
46
48
|
if (envLevel && isValidLogLevel(envLevel.trim().toLowerCase())) this.level = envLevel.trim().toLowerCase();
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
/** Sets the active minimum logging level. */
|
|
49
52
|
setLevel(level) {
|
|
50
53
|
if (isValidLogLevel(level)) this.level = level;
|
|
51
54
|
}
|
|
55
|
+
/** Returns the current active log level. */
|
|
52
56
|
getLevel() {
|
|
53
57
|
return this.level;
|
|
54
58
|
}
|
|
59
|
+
/** Checks if a message at the specified level should be emitted. */
|
|
55
60
|
isEnabledFor(level) {
|
|
56
61
|
return LOG_LEVEL_PRIORITY[this.level] >= LOG_LEVEL_PRIORITY[level];
|
|
57
62
|
}
|
|
@@ -59,15 +64,19 @@ var Logger = class {
|
|
|
59
64
|
isOutputEnabled() {
|
|
60
65
|
return this.level !== "silent";
|
|
61
66
|
}
|
|
67
|
+
/** Logs an error-level message to stderr. */
|
|
62
68
|
error(...args) {
|
|
63
69
|
if (this.isEnabledFor("error")) console.error(...args);
|
|
64
70
|
}
|
|
71
|
+
/** Logs a warning-level message to stderr. */
|
|
65
72
|
warn(...args) {
|
|
66
73
|
if (this.isEnabledFor("warn")) console.warn(...args);
|
|
67
74
|
}
|
|
75
|
+
/** Logs an informational message to stdout. */
|
|
68
76
|
info(...args) {
|
|
69
77
|
if (this.isEnabledFor("info")) console.log(...args);
|
|
70
78
|
}
|
|
79
|
+
/** Logs a debug-level diagnostic message to stdout. */
|
|
71
80
|
debug(...args) {
|
|
72
81
|
if (this.isEnabledFor("debug")) console.debug(...args);
|
|
73
82
|
}
|
|
@@ -1022,6 +1031,7 @@ function getPackageRoot() {
|
|
|
1022
1031
|
}
|
|
1023
1032
|
return path.resolve(__dirname, "..");
|
|
1024
1033
|
}
|
|
1034
|
+
/** Resolves the default annotations path from environment variable, bundled file, or user cache. */
|
|
1025
1035
|
function getDefaultAnnotationsPath() {
|
|
1026
1036
|
const envPath = process.env.NANOS_ANNOTATIONS_PATH || process.env.NANOS_ANNOTATIONS;
|
|
1027
1037
|
if (envPath && fs.existsSync(envPath)) return path.resolve(envPath);
|
|
@@ -1036,6 +1046,7 @@ function getDefaultAnnotationsPath() {
|
|
|
1036
1046
|
function getDefinitionsDir() {
|
|
1037
1047
|
return path.dirname(getDefaultAnnotationsPath());
|
|
1038
1048
|
}
|
|
1049
|
+
/** Resolves the absolute path to the default .luarc.json template shipped with the package. */
|
|
1039
1050
|
function getDefaultTemplatePath() {
|
|
1040
1051
|
const root = getPackageRoot();
|
|
1041
1052
|
return path.join(root, "templates", ".luarc.json");
|
|
@@ -1047,6 +1058,7 @@ function stripJsonComments(text) {
|
|
|
1047
1058
|
const cleanText = text.replace(/^\uFEFF/, "");
|
|
1048
1059
|
return stripComments(cleanText);
|
|
1049
1060
|
}
|
|
1061
|
+
/** Parses JSONC text with support for comments and trailing commas. */
|
|
1050
1062
|
function parseJsonc(text) {
|
|
1051
1063
|
const cleanText = text.replace(/^\uFEFF/, "");
|
|
1052
1064
|
const errors = [];
|
|
@@ -1057,6 +1069,7 @@ function parseJsonc(text) {
|
|
|
1057
1069
|
}
|
|
1058
1070
|
return result;
|
|
1059
1071
|
}
|
|
1072
|
+
/** Reads and parses a .luarc.json configuration file from disk. */
|
|
1060
1073
|
function loadConfigFile(filePath) {
|
|
1061
1074
|
if (!fs.existsSync(filePath)) throw new ConfigError(`Configuration file not found: ${filePath}`, "ERR_CONFIG_NOT_FOUND", "Ensure the configuration file path is correct or run 'nanos-lint init' to generate a default config.");
|
|
1062
1075
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -1352,25 +1365,12 @@ function initWorkspace(workspacePath, options) {
|
|
|
1352
1365
|
//#region src/luals/version.ts
|
|
1353
1366
|
const FALLBACK_LUALS_VERSION = "3.19.1";
|
|
1354
1367
|
const DEFAULT_LUALS_VERSION = "latest";
|
|
1355
|
-
/**
|
|
1356
|
-
* Characters accepted in a LuaLS version/tag. Only ASCII letters, digits, dots,
|
|
1357
|
-
* dashes and underscores are allowed, so a version can never contain a path
|
|
1358
|
-
* separator, a drive letter or a traversal segment.
|
|
1359
|
-
*/
|
|
1368
|
+
/** Characters accepted in a safe LuaLS version tag. */
|
|
1360
1369
|
const SAFE_VERSION_CHARS = new Map([..."0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-"].map((ch) => [ch, ch]));
|
|
1361
1370
|
const MAX_VERSION_LENGTH = 64;
|
|
1362
1371
|
/**
|
|
1363
|
-
* Validates a LuaLS version
|
|
1364
|
-
*
|
|
1365
|
-
* Version strings originate from untrusted sources: the GitHub releases API
|
|
1366
|
-
* response and user supplied `--luals-version` arguments. They are interpolated
|
|
1367
|
-
* into cache directory paths, download URLs, and the path of the binary that is
|
|
1368
|
-
* eventually executed, so they must be constrained to a single safe path
|
|
1369
|
-
* segment. Rebuilding the value character by character guarantees the returned
|
|
1370
|
-
* string only ever contains allow-listed characters (CodeQL: js/command-line-injection).
|
|
1371
|
-
*
|
|
1372
|
-
* @returns the normalized version (a single leading `v` is dropped), or `null`
|
|
1373
|
-
* when the input cannot be used as a version tag.
|
|
1372
|
+
* Validates and normalizes a LuaLS version string to prevent path traversal or injection.
|
|
1373
|
+
* Drops a single leading 'v' and returns null on invalid characters.
|
|
1374
1374
|
*/
|
|
1375
1375
|
function sanitizeLuaLSVersion(raw) {
|
|
1376
1376
|
const trimmed = raw.trim();
|
|
@@ -1427,6 +1427,7 @@ async function resolveLuaLSVersion(version) {
|
|
|
1427
1427
|
}
|
|
1428
1428
|
//#endregion
|
|
1429
1429
|
//#region src/luals/platform.ts
|
|
1430
|
+
/** Resolves platform-specific binary naming and relative paths for the host operating system. */
|
|
1430
1431
|
function getPlatformInfo(version = FALLBACK_LUALS_VERSION) {
|
|
1431
1432
|
const platform = process.platform;
|
|
1432
1433
|
const arch = process.arch;
|
|
@@ -1464,6 +1465,158 @@ function getPlatformInfo(version = FALLBACK_LUALS_VERSION) {
|
|
|
1464
1465
|
}
|
|
1465
1466
|
//#endregion
|
|
1466
1467
|
//#region src/luals/validation.ts
|
|
1468
|
+
const execFileAsync$2 = promisify(execFile);
|
|
1469
|
+
/**
|
|
1470
|
+
* Escapes single quotes for safe PowerShell single-quoted string interpolation.
|
|
1471
|
+
*/
|
|
1472
|
+
function escapePowerShellSingleQuote(str) {
|
|
1473
|
+
return str.replace(/'/g, "''");
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Maximum total decompressed size of a LuaLS release archive (500 MB).
|
|
1477
|
+
* Protects against zip bombs and maliciously inflated archives (#31).
|
|
1478
|
+
*/
|
|
1479
|
+
const MAX_DECOMPRESSED_SIZE_BYTES = 524288e3;
|
|
1480
|
+
/**
|
|
1481
|
+
* Maximum number of members allowed in a LuaLS release archive (10,000).
|
|
1482
|
+
* Protects against inode exhaustion and archive recursion attacks (#31).
|
|
1483
|
+
*/
|
|
1484
|
+
const MAX_ARCHIVE_MEMBER_COUNT = 1e4;
|
|
1485
|
+
/** Parses the file size column from a 'tar -tvf' listing output line. */
|
|
1486
|
+
function parseTarTvSize(line) {
|
|
1487
|
+
const parts = line.trim().split(/\s+/);
|
|
1488
|
+
if (parts.length < 5) return 0;
|
|
1489
|
+
const p1 = parts[1];
|
|
1490
|
+
const p2 = parts[2];
|
|
1491
|
+
const p4 = parts[4];
|
|
1492
|
+
if (p1 && p2 && p1.includes("/") && /^\d+$/.test(p2)) return parseInt(p2, 10) || 0;
|
|
1493
|
+
if (p4 && /^\d+$/.test(p4)) return parseInt(p4, 10) || 0;
|
|
1494
|
+
for (let i = 2; i < Math.min(parts.length - 1, 6); i++) {
|
|
1495
|
+
const p = parts[i];
|
|
1496
|
+
if (p && /^\d+$/.test(p)) return parseInt(p, 10) || 0;
|
|
1497
|
+
}
|
|
1498
|
+
return 0;
|
|
1499
|
+
}
|
|
1500
|
+
/** Asserts that an archive entry name does not escape the destination directory. */
|
|
1501
|
+
function checkEscapedMember(member) {
|
|
1502
|
+
if (member.startsWith("/") || member.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(member) || member.split(/[/\\]/).includes("..")) throw new LuaLSError(`Archive member path escapes extraction directory: ${member}`, "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1503
|
+
}
|
|
1504
|
+
/** Validates that archive entry count and decompressed size do not exceed safety limits. */
|
|
1505
|
+
function checkArchiveLimits(count, size) {
|
|
1506
|
+
if (count > 1e4) throw new LuaLSError(`Archive member count (${count}) exceeds maximum limit (${MAX_ARCHIVE_MEMBER_COUNT})`, "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1507
|
+
if (size > 524288e3) throw new LuaLSError(`Archive declared decompressed size (${size} bytes) exceeds maximum limit (${MAX_DECOMPRESSED_SIZE_BYTES} bytes)`, "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and ensure there is sufficient disk space.");
|
|
1508
|
+
}
|
|
1509
|
+
/** Locates the platform tar executable path, checking System32 on Windows. */
|
|
1510
|
+
function getTarBinary() {
|
|
1511
|
+
if (process.platform === "win32") {
|
|
1512
|
+
const sysTar = path.join(process.env.SystemRoot || "C:\\Windows", "System32", "tar.exe");
|
|
1513
|
+
if (fs.existsSync(sysTar)) return sysTar;
|
|
1514
|
+
}
|
|
1515
|
+
return "tar";
|
|
1516
|
+
}
|
|
1517
|
+
/** Pre-inspects a ZIP archive's central directory to enforce extraction safety constraints (#31). */
|
|
1518
|
+
function inspectZipMembers(archivePath) {
|
|
1519
|
+
const buf = fs.readFileSync(archivePath);
|
|
1520
|
+
if (buf.length < 22) throw new LuaLSError("Failed to inspect release archive before extraction: archive is too small to be a valid zip", "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1521
|
+
let eocdOffset = -1;
|
|
1522
|
+
const minOffset = Math.max(0, buf.length - 22 - 65535);
|
|
1523
|
+
for (let i = buf.length - 22; i >= minOffset; i--) if (buf.readUInt32LE(i) === 101010256) {
|
|
1524
|
+
const commentLen = buf.readUInt16LE(i + 20);
|
|
1525
|
+
if (i + 22 + commentLen === buf.length) {
|
|
1526
|
+
eocdOffset = i;
|
|
1527
|
+
break;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (eocdOffset === -1) {
|
|
1531
|
+
for (let i = buf.length - 22; i >= minOffset; i--) if (buf.readUInt32LE(i) === 101010256) {
|
|
1532
|
+
eocdOffset = i;
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (eocdOffset === -1) throw new LuaLSError("Failed to inspect release archive before extraction: corrupt or invalid zip archive (missing EOCD)", "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1537
|
+
const entryCount = buf.readUInt16LE(eocdOffset + 10);
|
|
1538
|
+
let offset = buf.readUInt32LE(eocdOffset + 16);
|
|
1539
|
+
let memberCount = 0;
|
|
1540
|
+
let totalDeclaredSize = 0;
|
|
1541
|
+
for (let i = 0; i < entryCount && offset + 46 <= buf.length; i++) {
|
|
1542
|
+
if (buf.readUInt32LE(offset) !== 33639248) throw new LuaLSError("Failed to inspect release archive before extraction: invalid central directory header", "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1543
|
+
const uncompressedSize = buf.readUInt32LE(offset + 24);
|
|
1544
|
+
const fileNameLen = buf.readUInt16LE(offset + 28);
|
|
1545
|
+
const extraLen = buf.readUInt16LE(offset + 30);
|
|
1546
|
+
const commentLen = buf.readUInt16LE(offset + 32);
|
|
1547
|
+
if ((buf.readUInt32LE(offset + 38) >>> 16 & 61440) === 40960) throw new LuaLSError("Archive member is a symbolic or hard link. Refusing to extract archive-planted links.", "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1548
|
+
const fileNameStart = offset + 46;
|
|
1549
|
+
const fileNameEnd = fileNameStart + fileNameLen;
|
|
1550
|
+
if (fileNameEnd > buf.length) throw new LuaLSError("Failed to inspect release archive before extraction: corrupt file name length in zip", "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1551
|
+
const fileName = buf.toString("utf-8", fileNameStart, fileNameEnd);
|
|
1552
|
+
memberCount++;
|
|
1553
|
+
totalDeclaredSize += uncompressedSize;
|
|
1554
|
+
checkEscapedMember(fileName);
|
|
1555
|
+
offset += 46 + fileNameLen + extraLen + commentLen;
|
|
1556
|
+
}
|
|
1557
|
+
checkArchiveLimits(memberCount, totalDeclaredSize);
|
|
1558
|
+
return {
|
|
1559
|
+
memberCount,
|
|
1560
|
+
totalDeclaredSize
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
/** Detects whether an archive file starts with ZIP magic bytes. */
|
|
1564
|
+
function isZipArchive(archivePath) {
|
|
1565
|
+
try {
|
|
1566
|
+
const fd = fs.openSync(archivePath, "r");
|
|
1567
|
+
const buf = Buffer.alloc(4);
|
|
1568
|
+
const bytesRead = fs.readSync(fd, buf, 0, 4, 0);
|
|
1569
|
+
fs.closeSync(fd);
|
|
1570
|
+
if (bytesRead < 4) return false;
|
|
1571
|
+
return buf[0] === 80 && buf[1] === 75 && (buf[2] === 3 && buf[3] === 4 || buf[2] === 5 && buf[3] === 6 || buf[2] === 7 && buf[3] === 8);
|
|
1572
|
+
} catch {
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/** Detects whether an archive file starts with GZIP magic bytes. */
|
|
1577
|
+
function isGzipArchive(archivePath) {
|
|
1578
|
+
try {
|
|
1579
|
+
const fd = fs.openSync(archivePath, "r");
|
|
1580
|
+
const buf = Buffer.alloc(2);
|
|
1581
|
+
const bytesRead = fs.readSync(fd, buf, 0, 2, 0);
|
|
1582
|
+
fs.closeSync(fd);
|
|
1583
|
+
return bytesRead >= 2 && buf[0] === 31 && buf[1] === 139;
|
|
1584
|
+
} catch {
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/** Pre-inspects archive members using tar listing or zip directory parsing to enforce extraction safety constraints (#31). */
|
|
1589
|
+
async function validateArchiveMembers(archivePath) {
|
|
1590
|
+
if (isZipArchive(archivePath) || archivePath.toLowerCase().endsWith(".zip") && !isGzipArchive(archivePath)) return inspectZipMembers(archivePath);
|
|
1591
|
+
let memberCount = 0;
|
|
1592
|
+
let totalDeclaredSize = 0;
|
|
1593
|
+
const tarBin = getTarBinary();
|
|
1594
|
+
const archiveDir = path.dirname(archivePath);
|
|
1595
|
+
const archiveFile = path.basename(archivePath);
|
|
1596
|
+
try {
|
|
1597
|
+
const [namesOutput, verboseOutput] = await Promise.all([execFileAsync$2(tarBin, ["-tf", archiveFile], { cwd: archiveDir }), execFileAsync$2(tarBin, ["-tvf", archiveFile], { cwd: archiveDir })]);
|
|
1598
|
+
for (const rawName of namesOutput.stdout.split(/\r?\n/)) {
|
|
1599
|
+
const member = rawName.trim();
|
|
1600
|
+
if (!member) continue;
|
|
1601
|
+
memberCount++;
|
|
1602
|
+
checkEscapedMember(member);
|
|
1603
|
+
}
|
|
1604
|
+
for (const rawLine of verboseOutput.stdout.split(/\r?\n/)) {
|
|
1605
|
+
const line = rawLine.trim();
|
|
1606
|
+
if (!line) continue;
|
|
1607
|
+
if (/^[lh]/.test(line) || line.includes(" -> ") || line.includes(" link to ")) throw new LuaLSError("Archive member is a symbolic or hard link. Refusing to extract archive-planted links.", "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and verify the LuaLS release integrity.");
|
|
1608
|
+
totalDeclaredSize += parseTarTvSize(line);
|
|
1609
|
+
}
|
|
1610
|
+
checkArchiveLimits(memberCount, totalDeclaredSize);
|
|
1611
|
+
} catch (err) {
|
|
1612
|
+
if (err instanceof LuaLSError) throw err;
|
|
1613
|
+
throw new LuaLSError(`Failed to inspect release archive before extraction: ${err instanceof Error ? err.message : String(err)}`, "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and ensure there is sufficient disk space.", { cause: err });
|
|
1614
|
+
}
|
|
1615
|
+
return {
|
|
1616
|
+
memberCount,
|
|
1617
|
+
totalDeclaredSize
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1467
1620
|
/**
|
|
1468
1621
|
* Minimum plausible size of a downloaded LuaLS executable. Release assets are
|
|
1469
1622
|
* several megabytes, so anything smaller is a truncated download or a stray
|
|
@@ -1545,6 +1698,7 @@ function assertValidLuaLSBinary(binaryPath, source) {
|
|
|
1545
1698
|
}
|
|
1546
1699
|
//#endregion
|
|
1547
1700
|
//#region src/luals/cache.ts
|
|
1701
|
+
/** Returns the base directory in the system cache where LuaLS versions and metadata are stored. */
|
|
1548
1702
|
function getBaseLuaLSCacheDir() {
|
|
1549
1703
|
return path.join(systemPaths.cache, "luals");
|
|
1550
1704
|
}
|
|
@@ -1574,9 +1728,11 @@ function getIsoWeek(d = /* @__PURE__ */ new Date()) {
|
|
|
1574
1728
|
const weekNo = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
1575
1729
|
return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
|
|
1576
1730
|
}
|
|
1731
|
+
/** Returns the path to the LuaLS metadata.json file within the specified cache directory. */
|
|
1577
1732
|
function getLuaLSMetadataPath(baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1578
1733
|
return path.join(baseCacheDir, LUALS_METADATA_FILENAME);
|
|
1579
1734
|
}
|
|
1735
|
+
/** Reads and parses the LuaLS metadata.json file, purging corrupted or invalid files automatically. */
|
|
1580
1736
|
function readLuaLSMetadata(baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1581
1737
|
const metaPath = getLuaLSMetadataPath(baseCacheDir);
|
|
1582
1738
|
if (!fs.existsSync(metaPath)) return null;
|
|
@@ -1606,6 +1762,7 @@ function readLuaLSMetadata(baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
|
1606
1762
|
}
|
|
1607
1763
|
return null;
|
|
1608
1764
|
}
|
|
1765
|
+
/** Writes metadata.json containing the latest checked version and timestamp. */
|
|
1609
1766
|
function writeLuaLSMetadata(metadata, baseCacheDir = getBaseLuaLSCacheDir()) {
|
|
1610
1767
|
try {
|
|
1611
1768
|
fs.mkdirSync(baseCacheDir, { recursive: true });
|
|
@@ -1768,12 +1925,6 @@ function computeFileSha256(filePath) {
|
|
|
1768
1925
|
}
|
|
1769
1926
|
const execFileAsync$1 = promisify(execFile);
|
|
1770
1927
|
/**
|
|
1771
|
-
* Escapes single quotes for safe PowerShell single-quoted string interpolation.
|
|
1772
|
-
*/
|
|
1773
|
-
function escapePowerShellSingleQuote(str) {
|
|
1774
|
-
return str.replace(/'/g, "''");
|
|
1775
|
-
}
|
|
1776
|
-
/**
|
|
1777
1928
|
* Enforces a maximum byte count on an asynchronous download stream.
|
|
1778
1929
|
*/
|
|
1779
1930
|
async function* limitDownloadStream(source) {
|
|
@@ -1784,6 +1935,7 @@ async function* limitDownloadStream(source) {
|
|
|
1784
1935
|
yield chunk;
|
|
1785
1936
|
}
|
|
1786
1937
|
}
|
|
1938
|
+
/** Downloads, verifies, and extracts the LuaLS release archive for the current platform. */
|
|
1787
1939
|
async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDir, options) {
|
|
1788
1940
|
const resolvedVersion = await resolveLuaLSVersion(version);
|
|
1789
1941
|
const info = getPlatformInfo(resolvedVersion);
|
|
@@ -1866,21 +2018,22 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1866
2018
|
const archiveSha256 = computeFileSha256(archivePath);
|
|
1867
2019
|
logger.info(`[luals] Downloaded ${info.assetName} (SHA-256 ${archiveSha256})`);
|
|
1868
2020
|
if (!options?.quiet) logger.info(`[luals] Extracting to ${destDir}...`);
|
|
2021
|
+
await validateArchiveMembers(archivePath);
|
|
1869
2022
|
try {
|
|
1870
2023
|
const tarArgs = process.platform === "win32" ? [
|
|
1871
2024
|
"-xf",
|
|
1872
|
-
archivePath,
|
|
2025
|
+
path.basename(archivePath),
|
|
1873
2026
|
"-C",
|
|
1874
2027
|
tempDir
|
|
1875
2028
|
] : [
|
|
1876
2029
|
"-xf",
|
|
1877
|
-
archivePath,
|
|
2030
|
+
path.basename(archivePath),
|
|
1878
2031
|
"--no-same-owner",
|
|
1879
2032
|
"--no-same-permissions",
|
|
1880
2033
|
"-C",
|
|
1881
2034
|
tempDir
|
|
1882
2035
|
];
|
|
1883
|
-
await execFileAsync$1(
|
|
2036
|
+
await execFileAsync$1(getTarBinary(), tarArgs, { cwd: path.dirname(archivePath) });
|
|
1884
2037
|
} catch (tarErr) {
|
|
1885
2038
|
if (process.platform === "win32" && info.assetName.endsWith(".zip")) await execFileAsync$1("powershell.exe", [
|
|
1886
2039
|
"-NoProfile",
|
|
@@ -1889,6 +2042,8 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1889
2042
|
]);
|
|
1890
2043
|
else throw tarErr;
|
|
1891
2044
|
}
|
|
2045
|
+
const extractedSize = getDirectorySize(tempDir);
|
|
2046
|
+
if (extractedSize > 524288e3) throw new LuaLSError(`Extracted archive size (${extractedSize} bytes) exceeds maximum limit (${MAX_DECOMPRESSED_SIZE_BYTES} bytes)`, "ERR_LUALS_EXTRACT", "Run 'nanos-lint clean-cache' and ensure there is sufficient disk space.");
|
|
1892
2047
|
try {
|
|
1893
2048
|
fs.unlinkSync(archivePath);
|
|
1894
2049
|
} catch (err) {
|
|
@@ -5256,21 +5411,8 @@ const LUA_FILE_PATTERN = "**/*.lua";
|
|
|
5256
5411
|
/** `glob` rejects patterns longer than this, so they are dropped before matching. */
|
|
5257
5412
|
const MAX_PATTERN_LENGTH = 65536;
|
|
5258
5413
|
/**
|
|
5259
|
-
* Complexity budget per pattern
|
|
5260
|
-
*
|
|
5261
|
-
* one path segment can backtrack, and brace groups expand combinatorially
|
|
5262
|
-
* (`{a,b}` repeated 16 times is 65 536 alternatives). A hostile or accidental
|
|
5263
|
-
* `.luarc.json` could otherwise stall the file walk, so patterns beyond the
|
|
5264
|
-
* budget are skipped with a warning instead of being matched.
|
|
5265
|
-
*
|
|
5266
|
-
* The ambiguity of one segment grows like `C(segment length, wildcards)`, so the
|
|
5267
|
-
* per-segment cap is what keeps matching cheap. `**\/*a*a*a*a\/*a*a*a*a\/*a*a*a*a\/*a*a*a*z.lua`
|
|
5268
|
-
* (4 wildcards per segment) stays within a 4-wildcard cap yet costs ~5 ms per
|
|
5269
|
-
* candidate file — ~100 s on a 20 000-file tree — while two wildcards per
|
|
5270
|
-
* segment cost `C(60, 2) = 1 770` splits, hundreds of times less. The budget is
|
|
5271
|
-
* deliberately far below what minimatch can parse: every realistic pattern
|
|
5272
|
-
* (`**\/*.{bak,tmp}`, `**\/node_modules\/**`, `**\/item-[0-9].lua`,
|
|
5273
|
-
* `**\/*.min.*`) needs at most two wildcards in any one segment.
|
|
5414
|
+
* Complexity budget per pattern to bound backtracking and combinatorial brace expansion.
|
|
5415
|
+
* Patterns exceeding limits are skipped with a warning to protect against ReDoS (#27).
|
|
5274
5416
|
*/
|
|
5275
5417
|
const MAX_WILDCARDS_PER_SEGMENT = 2;
|
|
5276
5418
|
const MAX_TOTAL_WILDCARDS = 12;
|
|
@@ -5303,15 +5445,7 @@ function isPatternWithinBudget(pattern) {
|
|
|
5303
5445
|
if (countWildcards(pattern) > MAX_TOTAL_WILDCARDS) return false;
|
|
5304
5446
|
return pattern.split("/").every((segment) => countWildcards(segment) <= MAX_WILDCARDS_PER_SEGMENT);
|
|
5305
5447
|
}
|
|
5306
|
-
/**
|
|
5307
|
-
* Normalizes one user-supplied pattern (`workspace.ignoreDir`, `files.exclude`
|
|
5308
|
-
* or a built-in default) into the slash-separated form `glob` expects.
|
|
5309
|
-
* Backslashes are accepted as separators so Windows-authored `.luarc.json`
|
|
5310
|
-
* files keep working, and trailing slashes are dropped so `vendor/` behaves
|
|
5311
|
-
* exactly like `vendor`. Patterns that cannot be matched (`""`, `"."`, NUL
|
|
5312
|
-
* bytes, oversized input, absolute paths, or non-string JSON values) are
|
|
5313
|
-
* rejected instead of being handed to `glob`.
|
|
5314
|
-
*/
|
|
5448
|
+
/** Normalizes a user pattern to slash-separated form, dropping trailing slashes and invalid inputs. */
|
|
5315
5449
|
function normalizePattern(pattern) {
|
|
5316
5450
|
if (typeof pattern !== "string") return null;
|
|
5317
5451
|
const normalized = pattern.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -5320,27 +5454,11 @@ function normalizePattern(pattern) {
|
|
|
5320
5454
|
}
|
|
5321
5455
|
/** Drive-letter prefixes, recognized on every platform (not just Windows). */
|
|
5322
5456
|
const WINDOWS_ABSOLUTE_PATTERN = /^[A-Za-z]:\//;
|
|
5323
|
-
/**
|
|
5324
|
-
* `workspace.ignoreDir` / `files.exclude` entries are workspace-relative in
|
|
5325
|
-
* LuaLS, and `glob` matches ignore patterns against walk-relative paths. An
|
|
5326
|
-
* absolute entry is therefore unusable, and `glob` anchors it on some platforms
|
|
5327
|
-
* but not others (a macOS `/var` -> `/private/var` walk root or a Windows drive
|
|
5328
|
-
* letter behave differently again), so it is skipped to keep one `.luarc.json`
|
|
5329
|
-
* behaving identically on Linux, macOS and Windows.
|
|
5330
|
-
*/
|
|
5457
|
+
/** Rejects absolute patterns to maintain cross-platform consistency with LuaLS relative paths. */
|
|
5331
5458
|
function isAbsolutePattern(normalized) {
|
|
5332
5459
|
return path.posix.isAbsolute(normalized) || WINDOWS_ABSOLUTE_PATTERN.test(normalized);
|
|
5333
5460
|
}
|
|
5334
|
-
/**
|
|
5335
|
-
* Expands one LuaLS pattern into the equivalent `glob` ignore patterns:
|
|
5336
|
-
*
|
|
5337
|
-
* - basename-only patterns (`*.lua`, `vendor`) match at any depth, so the
|
|
5338
|
-
* `**\/`-prefixed variants are added (mirrors LuaLS `matchBase` semantics);
|
|
5339
|
-
* - a pattern also excludes everything below a matching directory, which is
|
|
5340
|
-
* what the trailing `/**` variant expresses;
|
|
5341
|
-
* - the pattern itself is always kept so an exact file or directory match
|
|
5342
|
-
* works as written.
|
|
5343
|
-
*/
|
|
5461
|
+
/** Expands a LuaLS pattern to include basename and sub-directory variants for glob matching. */
|
|
5344
5462
|
function expandIgnorePattern(pattern) {
|
|
5345
5463
|
return pattern.includes("/") ? [pattern, `${pattern}/**`] : [`**/${pattern}`, `**/${pattern}/**`];
|
|
5346
5464
|
}
|
|
@@ -5361,17 +5479,7 @@ function toIgnorePatterns(patterns) {
|
|
|
5361
5479
|
}
|
|
5362
5480
|
return ignore;
|
|
5363
5481
|
}
|
|
5364
|
-
/**
|
|
5365
|
-
* Counts candidate Lua files within targetPath, taking `workspace.ignoreDir`
|
|
5366
|
-
* and `files.exclude` into account.
|
|
5367
|
-
*
|
|
5368
|
-
* Traversal and matching are delegated to the bundled `glob` package, whose
|
|
5369
|
-
* `minimatch` matcher understands the full glob syntax LuaLS accepts (brace
|
|
5370
|
-
* expansion, character classes, `**`, `?`) instead of the hand-rolled
|
|
5371
|
-
* glob-to-regex translation used before. Combined with the pattern budgets
|
|
5372
|
-
* above, this keeps the count predictable for adversarial configurations
|
|
5373
|
-
* while remaining consistent with LuaLS exclude semantics (#27).
|
|
5374
|
-
*/
|
|
5482
|
+
/** Counts candidate Lua files within targetPath, taking workspace ignoreDir and files.exclude into account (#27). */
|
|
5375
5483
|
function countCheckedFiles(targetPath, configPath) {
|
|
5376
5484
|
let absPath = path.resolve(targetPath);
|
|
5377
5485
|
if (!fs.existsSync(absPath)) return 0;
|
|
@@ -5410,11 +5518,13 @@ function countCheckedFiles(targetPath, configPath) {
|
|
|
5410
5518
|
//#endregion
|
|
5411
5519
|
//#region src/luals/runner.ts
|
|
5412
5520
|
const execFileAsync = promisify(execFile);
|
|
5521
|
+
/** Determines if an error represents an offline network condition. */
|
|
5413
5522
|
function isOfflineError(err) {
|
|
5414
5523
|
if (!err) return false;
|
|
5415
5524
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5416
5525
|
return /offline|enotfound|eai_again|econnrefused|etimedout|fetch failed/i.test(msg);
|
|
5417
5526
|
}
|
|
5527
|
+
/** Resolves the executable path of a LuaLS binary from env, bundle, cache, or network download. */
|
|
5418
5528
|
async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
5419
5529
|
if (process.env.LUALS_BIN) return assertValidLuaLSBinary(process.env.LUALS_BIN, "LUALS_BIN");
|
|
5420
5530
|
if (options?.reuseExisting !== false && (!version || version === "latest")) {
|
|
@@ -5572,6 +5682,7 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
|
5572
5682
|
cleanupOldCachedLuaLSVersions(targetVersion, baseCacheDir);
|
|
5573
5683
|
return downloadedBinary;
|
|
5574
5684
|
}
|
|
5685
|
+
/** Executes the LuaLS diagnostics check on the target path using merged configurations. */
|
|
5575
5686
|
async function runLuaLSCheck(targetPath, configPath, options) {
|
|
5576
5687
|
let absoluteTarget = path.resolve(targetPath);
|
|
5577
5688
|
try {
|
|
@@ -5680,6 +5791,7 @@ function shouldEnableColor() {
|
|
|
5680
5791
|
if (process.env.FORCE_COLOR !== void 0 && process.env.FORCE_COLOR !== "0") return true;
|
|
5681
5792
|
return process.stdout ? Boolean(process.stdout.isTTY) : true;
|
|
5682
5793
|
}
|
|
5794
|
+
/** Returns ANSI color escape code helpers or empty strings when colors are disabled. */
|
|
5683
5795
|
function getColors(useColor = shouldEnableColor()) {
|
|
5684
5796
|
if (!useColor) return {
|
|
5685
5797
|
reset: "",
|
|
@@ -5706,6 +5818,7 @@ function getColors(useColor = shouldEnableColor()) {
|
|
|
5706
5818
|
gray: "\x1B[90m"
|
|
5707
5819
|
};
|
|
5708
5820
|
}
|
|
5821
|
+
/** Formats a numeric diagnostic severity level into a colored badge. */
|
|
5709
5822
|
function formatSeverityBadge(severity, useColor = shouldEnableColor()) {
|
|
5710
5823
|
const c = getColors(useColor);
|
|
5711
5824
|
const name = SEVERITY_NAMES[severity] || "Warning";
|
|
@@ -5716,9 +5829,11 @@ function formatSeverityBadge(severity, useColor = shouldEnableColor()) {
|
|
|
5716
5829
|
default: return `${c.gray}[${name}]${c.reset}`;
|
|
5717
5830
|
}
|
|
5718
5831
|
}
|
|
5832
|
+
/** Formats a noun with its count and pluralizes if necessary. */
|
|
5719
5833
|
function pluralize(count, singular, plural = `${singular}s`) {
|
|
5720
5834
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
5721
5835
|
}
|
|
5836
|
+
/** Formats the summary text detailing diagnostic counts and affected files. */
|
|
5722
5837
|
function formatProblemSummary(totalProblems, errors, warnings, files) {
|
|
5723
5838
|
const parts = [];
|
|
5724
5839
|
if (errors > 0) parts.push(pluralize(errors, "error"));
|
|
@@ -5728,6 +5843,7 @@ function formatProblemSummary(totalProblems, errors, warnings, files) {
|
|
|
5728
5843
|
const breakdown = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
5729
5844
|
return `Diagnosis complete: ${pluralize(totalProblems, "problem")}${breakdown} found across ${pluralize(files, "file")}.`;
|
|
5730
5845
|
}
|
|
5846
|
+
/** Formats check results into pretty human-readable terminal output. */
|
|
5731
5847
|
function formatPretty(result, cwd = process.cwd(), useColor = shouldEnableColor()) {
|
|
5732
5848
|
const c = getColors(useColor);
|
|
5733
5849
|
const symCross = "✖ ";
|
|
@@ -5777,6 +5893,7 @@ function formatPretty(result, cwd = process.cwd(), useColor = shouldEnableColor(
|
|
|
5777
5893
|
lines.push(`${c.red}${c.bold}${symCross}${summary}${c.reset}`);
|
|
5778
5894
|
return lines.join("\n");
|
|
5779
5895
|
}
|
|
5896
|
+
/** Formats check diagnostics into GitHub Actions workflow annotation commands. */
|
|
5780
5897
|
function formatGitHubAnnotations(result, cwd = process.cwd()) {
|
|
5781
5898
|
const commands = [];
|
|
5782
5899
|
for (const [rawUri, diags] of Object.entries(result.diagnostics)) {
|
|
@@ -5796,6 +5913,7 @@ function formatGitHubAnnotations(result, cwd = process.cwd()) {
|
|
|
5796
5913
|
}
|
|
5797
5914
|
return commands.join("\n");
|
|
5798
5915
|
}
|
|
5916
|
+
/** Formats check results according to the chosen format (pretty, json, github). */
|
|
5799
5917
|
function formatReport(result, format = "pretty", cwd = process.cwd(), useColor = shouldEnableColor()) {
|
|
5800
5918
|
switch (format) {
|
|
5801
5919
|
case "json": return JSON.stringify(result, null, 2);
|
|
@@ -8763,13 +8881,13 @@ function useColor() {
|
|
|
8763
8881
|
}
|
|
8764
8882
|
new Command();
|
|
8765
8883
|
//#endregion
|
|
8766
|
-
//#region src/annotations.ts
|
|
8767
|
-
const DOCGEN_REPO = "nanos-world/vscode-extension";
|
|
8768
|
-
const DOCGEN_BRANCH = "docgen-output";
|
|
8769
|
-
const RAW_ANNOTATIONS_URL = "https://raw.githubusercontent.com/nanos-world/vscode-extension/refs/heads/docgen-output/annotations.lua";
|
|
8770
|
-
const GITHUB_COMMITS_API = "https://api.github.com/repos/nanos-world/vscode-extension/commits/docgen-output";
|
|
8884
|
+
//#region src/annotations-metadata.ts
|
|
8771
8885
|
const ANNOTATIONS_FILENAME = "annotations.lua";
|
|
8772
8886
|
const METADATA_FILENAME = "metadata.json";
|
|
8887
|
+
const MIN_ANNOTATIONS_SIZE_BYTES = 1e3;
|
|
8888
|
+
const MAX_ANNOTATIONS_SIZE_BYTES = 10485760;
|
|
8889
|
+
const MAX_COMMIT_JSON_SIZE_BYTES = 1048576;
|
|
8890
|
+
/** Returns current date string formatted as YYYY-MM-DD alongside a date components object. */
|
|
8773
8891
|
function getTodayDateString(d = /* @__PURE__ */ new Date()) {
|
|
8774
8892
|
const year = d.getFullYear();
|
|
8775
8893
|
const month = d.getMonth() + 1;
|
|
@@ -8783,60 +8901,127 @@ function getTodayDateString(d = /* @__PURE__ */ new Date()) {
|
|
|
8783
8901
|
}
|
|
8784
8902
|
};
|
|
8785
8903
|
}
|
|
8904
|
+
/** Returns the system cache directory used for nanos world annotations. */
|
|
8786
8905
|
function getAnnotationsCacheDir() {
|
|
8787
8906
|
return path.join(systemPaths.cache, "annotations");
|
|
8788
8907
|
}
|
|
8908
|
+
/** Returns the full path to the cached annotations.lua file. */
|
|
8789
8909
|
function getCachedAnnotationsFilePath() {
|
|
8790
8910
|
return path.join(getAnnotationsCacheDir(), ANNOTATIONS_FILENAME);
|
|
8791
8911
|
}
|
|
8912
|
+
/** Returns the full path to the annotations metadata.json file. */
|
|
8792
8913
|
function getAnnotationsMetadataFilePath() {
|
|
8793
8914
|
return path.join(getAnnotationsCacheDir(), METADATA_FILENAME);
|
|
8794
8915
|
}
|
|
8916
|
+
/** Reads and parses annotations metadata.json, purging corrupt files automatically. */
|
|
8795
8917
|
function readAnnotationsMetadata(cacheDir = getAnnotationsCacheDir()) {
|
|
8796
8918
|
const metaPath = path.join(cacheDir, "metadata.json");
|
|
8797
8919
|
if (!fs.existsSync(metaPath)) return null;
|
|
8798
|
-
|
|
8799
|
-
const content = fs.readFileSync(metaPath, "utf-8");
|
|
8800
|
-
const parsed = JSON.parse(content);
|
|
8801
|
-
if (typeof parsed?.commitId === "string" && typeof parsed?.lastChecked === "string") return parsed;
|
|
8920
|
+
const purge = (reason) => {
|
|
8802
8921
|
try {
|
|
8803
8922
|
fs.unlinkSync(metaPath);
|
|
8804
|
-
logger.warn(`[annotations]
|
|
8805
|
-
} catch (
|
|
8806
|
-
logger.debug(`[annotations] Failed to unlink
|
|
8923
|
+
logger.warn(`[annotations] ${reason} annotations metadata at ${metaPath} purged.`);
|
|
8924
|
+
} catch (err) {
|
|
8925
|
+
logger.debug(`[annotations] Failed to unlink metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
8807
8926
|
}
|
|
8927
|
+
};
|
|
8928
|
+
try {
|
|
8929
|
+
const parsed = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
8930
|
+
if (typeof parsed?.commitId === "string" && typeof parsed?.lastChecked === "string") return parsed;
|
|
8931
|
+
purge("Stale or invalid");
|
|
8808
8932
|
} catch (err) {
|
|
8809
8933
|
logger.debug(`Failed to parse annotations metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
8810
|
-
|
|
8811
|
-
fs.unlinkSync(metaPath);
|
|
8812
|
-
logger.warn(`[annotations] Corrupted annotations metadata at ${metaPath} purged.`);
|
|
8813
|
-
} catch (unlinkErr) {
|
|
8814
|
-
logger.debug(`[annotations] Failed to unlink corrupted metadata: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`);
|
|
8815
|
-
}
|
|
8934
|
+
purge("Corrupted");
|
|
8816
8935
|
}
|
|
8817
8936
|
return null;
|
|
8818
8937
|
}
|
|
8819
|
-
|
|
8820
|
-
/**
|
|
8821
|
-
* Verifies that an annotations file exists, is a regular file, is of non-trivial size (>= MIN_ANNOTATIONS_SIZE_BYTES),
|
|
8822
|
-
* and begins with valid LuaLS metadata or nanos world definitions.
|
|
8823
|
-
*/
|
|
8938
|
+
/** Verifies that an annotations file exists, is non-trivial in size, and contains valid header. */
|
|
8824
8939
|
function isAnnotationsValid(filePath) {
|
|
8825
8940
|
if (!fs.existsSync(filePath)) return false;
|
|
8826
8941
|
try {
|
|
8827
8942
|
const stat = fs.statSync(filePath);
|
|
8828
8943
|
if (!stat.isFile() || stat.size < 1e3) return false;
|
|
8829
8944
|
const fd = fs.openSync(filePath, "r");
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8945
|
+
try {
|
|
8946
|
+
const buffer = Buffer.alloc(512);
|
|
8947
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
8948
|
+
const header = buffer.toString("utf-8", 0, bytesRead).trimStart();
|
|
8949
|
+
return header.startsWith("---@meta") || header.includes("nanos world") || header.includes("nanos-world");
|
|
8950
|
+
} finally {
|
|
8951
|
+
fs.closeSync(fd);
|
|
8952
|
+
}
|
|
8835
8953
|
} catch (err) {
|
|
8836
8954
|
logger.debug(`[annotations] Annotation validation failed for ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8837
8955
|
return false;
|
|
8838
8956
|
}
|
|
8839
8957
|
}
|
|
8958
|
+
/** Updates metadata lastChecked date atomically without modifying annotations.lua. */
|
|
8959
|
+
function updateLastCheckedDate(commitId, cacheDir = getAnnotationsCacheDir()) {
|
|
8960
|
+
const { dateStr, dateObj } = getTodayDateString();
|
|
8961
|
+
const metadata = {
|
|
8962
|
+
commitId,
|
|
8963
|
+
lastChecked: dateStr,
|
|
8964
|
+
date: dateObj
|
|
8965
|
+
};
|
|
8966
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
8967
|
+
const metaPath = path.join(cacheDir, METADATA_FILENAME);
|
|
8968
|
+
const tempMetaPath = path.join(os.tmpdir(), `.metadata-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.tmp`);
|
|
8969
|
+
try {
|
|
8970
|
+
fs.writeFileSync(tempMetaPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
8971
|
+
fs.copyFileSync(tempMetaPath, metaPath);
|
|
8972
|
+
} finally {
|
|
8973
|
+
try {
|
|
8974
|
+
if (fs.existsSync(tempMetaPath)) fs.unlinkSync(tempMetaPath);
|
|
8975
|
+
} catch (err) {
|
|
8976
|
+
logger.warn(`Failed to clean up temporary metadata file ${tempMetaPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
return metadata;
|
|
8980
|
+
}
|
|
8981
|
+
//#endregion
|
|
8982
|
+
//#region src/annotations-download.ts
|
|
8983
|
+
const DOCGEN_REPO = "nanos-world/vscode-extension";
|
|
8984
|
+
const DOCGEN_BRANCH = "docgen-output";
|
|
8985
|
+
const RAW_ANNOTATIONS_URL = "https://raw.githubusercontent.com/nanos-world/vscode-extension/refs/heads/docgen-output/annotations.lua";
|
|
8986
|
+
const GITHUB_COMMITS_API = "https://api.github.com/repos/nanos-world/vscode-extension/commits/docgen-output";
|
|
8987
|
+
/** Reads an HTTP response body with an upper byte limit to guard against memory exhaustion. */
|
|
8988
|
+
async function readBoundedResponseBody(res, maxBytes, onExceeded) {
|
|
8989
|
+
const lengthHeader = res.headers?.get?.("content-length");
|
|
8990
|
+
if (lengthHeader) {
|
|
8991
|
+
const declared = parseInt(lengthHeader, 10);
|
|
8992
|
+
if (!isNaN(declared) && declared > maxBytes) {
|
|
8993
|
+
if (typeof res.body?.cancel === "function") await res.body.cancel().catch(() => {});
|
|
8994
|
+
onExceeded(declared, "header");
|
|
8995
|
+
return null;
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
if (res.body && typeof res.body[Symbol.asyncIterator] === "function") {
|
|
8999
|
+
let total = 0;
|
|
9000
|
+
const chunks = [];
|
|
9001
|
+
for await (const chunk of res.body) {
|
|
9002
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
9003
|
+
total += buf.length;
|
|
9004
|
+
if (total > maxBytes) {
|
|
9005
|
+
if (typeof res.body.cancel === "function") await res.body.cancel().catch(() => {});
|
|
9006
|
+
onExceeded(total, "stream");
|
|
9007
|
+
return null;
|
|
9008
|
+
}
|
|
9009
|
+
chunks.push(buf);
|
|
9010
|
+
}
|
|
9011
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
9012
|
+
}
|
|
9013
|
+
if (typeof res.text === "function") {
|
|
9014
|
+
const text = await res.text();
|
|
9015
|
+
const len = Buffer.byteLength(text, "utf-8");
|
|
9016
|
+
if (len > maxBytes) {
|
|
9017
|
+
onExceeded(len, "stream");
|
|
9018
|
+
return null;
|
|
9019
|
+
}
|
|
9020
|
+
return text;
|
|
9021
|
+
}
|
|
9022
|
+
return "";
|
|
9023
|
+
}
|
|
9024
|
+
/** Fetches the latest commit SHA for the annotations branch from the GitHub API. */
|
|
8840
9025
|
async function fetchLatestCommitId() {
|
|
8841
9026
|
try {
|
|
8842
9027
|
const headers = { "User-Agent": "nanos-lint" };
|
|
@@ -8846,7 +9031,17 @@ async function fetchLatestCommitId() {
|
|
|
8846
9031
|
signal: AbortSignal.timeout(5e3)
|
|
8847
9032
|
});
|
|
8848
9033
|
if (res.ok) {
|
|
8849
|
-
const
|
|
9034
|
+
const rawJson = await readBoundedResponseBody(res, MAX_COMMIT_JSON_SIZE_BYTES, (bytes) => {
|
|
9035
|
+
logger.warn(`GitHub commits response exceeded size limit of ${MAX_COMMIT_JSON_SIZE_BYTES} bytes (${bytes} bytes)`);
|
|
9036
|
+
});
|
|
9037
|
+
if (rawJson === null) return null;
|
|
9038
|
+
if (!rawJson && !res.bodyUsed && typeof res.json === "function") {
|
|
9039
|
+
const data = await res.json();
|
|
9040
|
+
if (typeof data.sha === "string" && /^[0-9a-fA-F]{7,40}$/.test(data.sha)) return data.sha;
|
|
9041
|
+
return null;
|
|
9042
|
+
}
|
|
9043
|
+
if (!rawJson) return null;
|
|
9044
|
+
const data = JSON.parse(rawJson);
|
|
8850
9045
|
if (typeof data.sha === "string" && /^[0-9a-fA-F]{7,40}$/.test(data.sha)) return data.sha;
|
|
8851
9046
|
}
|
|
8852
9047
|
} catch (err) {
|
|
@@ -8854,13 +9049,12 @@ async function fetchLatestCommitId() {
|
|
|
8854
9049
|
}
|
|
8855
9050
|
return null;
|
|
8856
9051
|
}
|
|
8857
|
-
/**
|
|
8858
|
-
* Returns raw annotations URL, pinned to commit SHA if provided and valid.
|
|
8859
|
-
*/
|
|
9052
|
+
/** Returns raw annotations URL, pinned to commit SHA if provided and valid. */
|
|
8860
9053
|
function getRawAnnotationsUrl(commitSha) {
|
|
8861
9054
|
if (commitSha && /^[0-9a-fA-F]{7,40}$/.test(commitSha)) return `https://raw.githubusercontent.com/${DOCGEN_REPO}/${commitSha}/annotations.lua`;
|
|
8862
9055
|
return RAW_ANNOTATIONS_URL;
|
|
8863
9056
|
}
|
|
9057
|
+
/** Downloads raw annotations.lua content from GitHub raw content endpoints. */
|
|
8864
9058
|
async function fetchRawAnnotationsContent(commitSha) {
|
|
8865
9059
|
const url = getRawAnnotationsUrl(commitSha);
|
|
8866
9060
|
const res = await fetch(url, {
|
|
@@ -8868,10 +9062,13 @@ async function fetchRawAnnotationsContent(commitSha) {
|
|
|
8868
9062
|
signal: AbortSignal.timeout(15e3)
|
|
8869
9063
|
});
|
|
8870
9064
|
if (!res.ok) throw new AnnotationsError(`Failed to download annotations.lua: ${res.status} ${res.statusText}`, "ERR_ANNOTATIONS_DOWNLOAD", "Verify your internet connection and that GitHub raw endpoints are accessible.");
|
|
8871
|
-
const text = await res
|
|
9065
|
+
const text = await readBoundedResponseBody(res, MAX_ANNOTATIONS_SIZE_BYTES, (bytes, reason) => {
|
|
9066
|
+
throw new AnnotationsError(reason === "header" ? `Annotations file size (${bytes} bytes) exceeds maximum limit (${MAX_ANNOTATIONS_SIZE_BYTES} bytes)` : `Annotations file download exceeded maximum allowed size of ${MAX_ANNOTATIONS_SIZE_BYTES} bytes`, "ERR_ANNOTATIONS_TOO_LARGE", "Specify a local annotations file via --annotations <path>.");
|
|
9067
|
+
});
|
|
8872
9068
|
if (!text || text.length < 1e3) throw new AnnotationsError("Downloaded annotations.lua appears truncated or invalid", "ERR_ANNOTATIONS_INVALID", "Retry downloading or pass a local annotations file via --annotations <path>.");
|
|
8873
9069
|
return text;
|
|
8874
9070
|
}
|
|
9071
|
+
/** Copies a file with exponential backoff retries to handle transient file lock contention. */
|
|
8875
9072
|
async function copyFileWithRetry(src, dest, maxRetries = 10) {
|
|
8876
9073
|
for (let attempt = 0; attempt < maxRetries; attempt++) try {
|
|
8877
9074
|
fs.copyFileSync(src, dest);
|
|
@@ -8885,35 +9082,7 @@ async function copyFileWithRetry(src, dest, maxRetries = 10) {
|
|
|
8885
9082
|
throw err;
|
|
8886
9083
|
}
|
|
8887
9084
|
}
|
|
8888
|
-
/**
|
|
8889
|
-
* Updates the metadata lastChecked date atomically without modifying annotations.lua.
|
|
8890
|
-
*/
|
|
8891
|
-
function updateLastCheckedDate(commitId, cacheDir = getAnnotationsCacheDir()) {
|
|
8892
|
-
const { dateStr, dateObj } = getTodayDateString();
|
|
8893
|
-
const metadata = {
|
|
8894
|
-
commitId,
|
|
8895
|
-
lastChecked: dateStr,
|
|
8896
|
-
date: dateObj
|
|
8897
|
-
};
|
|
8898
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
8899
|
-
const metaPath = path.join(cacheDir, METADATA_FILENAME);
|
|
8900
|
-
const tempMetaPath = path.join(os.tmpdir(), `.metadata-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.tmp`);
|
|
8901
|
-
try {
|
|
8902
|
-
fs.writeFileSync(tempMetaPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
8903
|
-
fs.copyFileSync(tempMetaPath, metaPath);
|
|
8904
|
-
} finally {
|
|
8905
|
-
try {
|
|
8906
|
-
if (fs.existsSync(tempMetaPath)) fs.unlinkSync(tempMetaPath);
|
|
8907
|
-
} catch (err) {
|
|
8908
|
-
logger.warn(`Failed to clean up temporary metadata file ${tempMetaPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8909
|
-
}
|
|
8910
|
-
}
|
|
8911
|
-
return metadata;
|
|
8912
|
-
}
|
|
8913
|
-
/**
|
|
8914
|
-
* Downloads annotations.lua, writes it and the new metadata in a chained atomic transaction.
|
|
8915
|
-
* If any step fails, the previous cache state is reverted.
|
|
8916
|
-
*/
|
|
9085
|
+
/** Downloads annotations.lua and writes metadata in a chained atomic transaction with rollback. */
|
|
8917
9086
|
async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCacheDir(), options) {
|
|
8918
9087
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
8919
9088
|
const finalAnnotationsPath = path.join(cacheDir, ANNOTATIONS_FILENAME);
|
|
@@ -8924,8 +9093,8 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
8924
9093
|
try {
|
|
8925
9094
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
8926
9095
|
if (!options?.quiet) {
|
|
8927
|
-
|
|
8928
|
-
|
|
9096
|
+
const label = commitId && commitId !== "unknown" ? ` (${commitId.slice(0, 7)})` : "";
|
|
9097
|
+
logger.info(`[annotations] Downloading nanos world API annotations${label}...`);
|
|
8929
9098
|
}
|
|
8930
9099
|
const content = await fetchRawAnnotationsContent(commitId);
|
|
8931
9100
|
const tempAnnotationsPath = path.join(tempDir, ANNOTATIONS_FILENAME);
|
|
@@ -8951,16 +9120,16 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
8951
9120
|
await copyFileWithRetry(tempAnnotationsPath, finalAnnotationsPath);
|
|
8952
9121
|
await copyFileWithRetry(tempMetaPath, finalMetaPath);
|
|
8953
9122
|
if (!options?.quiet) {
|
|
8954
|
-
|
|
8955
|
-
|
|
9123
|
+
const label = commitId && commitId !== "unknown" ? ` to commit ${commitId.slice(0, 7)}` : "";
|
|
9124
|
+
logger.info(`[annotations] Updated annotations.lua${label}.`);
|
|
8956
9125
|
}
|
|
8957
9126
|
return finalAnnotationsPath;
|
|
8958
9127
|
} catch (err) {
|
|
8959
9128
|
if (hasBackup && fs.existsSync(backupDir)) try {
|
|
8960
|
-
const
|
|
8961
|
-
const
|
|
8962
|
-
if (fs.existsSync(
|
|
8963
|
-
if (fs.existsSync(
|
|
9129
|
+
const bAnn = path.join(backupDir, ANNOTATIONS_FILENAME);
|
|
9130
|
+
const bMeta = path.join(backupDir, METADATA_FILENAME);
|
|
9131
|
+
if (fs.existsSync(bAnn)) await copyFileWithRetry(bAnn, finalAnnotationsPath);
|
|
9132
|
+
if (fs.existsSync(bMeta)) await copyFileWithRetry(bMeta, finalMetaPath);
|
|
8964
9133
|
} catch (rollbackErr) {
|
|
8965
9134
|
logger.error(`Failed to restore annotations from backup during rollback: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
|
|
8966
9135
|
}
|
|
@@ -8980,21 +9149,28 @@ async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCa
|
|
|
8980
9149
|
}
|
|
8981
9150
|
}
|
|
8982
9151
|
}
|
|
9152
|
+
//#endregion
|
|
9153
|
+
//#region src/annotations.ts
|
|
9154
|
+
/** Validates that a user-supplied or environment-specified annotations path exists and is a valid file. */
|
|
8983
9155
|
function validateCustomAnnotationsPath(filePath, source) {
|
|
8984
9156
|
const resolved = path.resolve(filePath);
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
}
|
|
9157
|
+
const isCustom = source === "custom";
|
|
9158
|
+
const targetDesc = isCustom ? "path specified in --annotations" : "path specified in NANOS_ANNOTATIONS_PATH or NANOS_ANNOTATIONS";
|
|
9159
|
+
if (!fs.existsSync(resolved)) throw new AnnotationsError(isCustom ? `Custom annotations file not found: ${resolved}` : `Annotations file specified in environment not found: ${resolved}`, isCustom ? "ERR_ANNOTATIONS_NOT_FOUND" : "ERR_ANNOTATIONS_ENV_NOT_FOUND", `Verify that the ${targetDesc} exists and is accessible.`);
|
|
8989
9160
|
const stat = fs.statSync(resolved);
|
|
8990
|
-
if (!stat.isFile()) throw new AnnotationsError(
|
|
8991
|
-
if (stat.size === 0) throw new AnnotationsError(
|
|
9161
|
+
if (!stat.isFile()) throw new AnnotationsError(isCustom ? `Custom annotations path is not a file: ${resolved}` : `Annotations path specified in environment is not a file: ${resolved}`, "ERR_ANNOTATIONS_NOT_A_FILE", `Verify that the ${targetDesc} points to a regular file, not a directory.`);
|
|
9162
|
+
if (stat.size === 0) throw new AnnotationsError(isCustom ? `Custom annotations file is empty: ${resolved}` : `Annotations file specified in environment is empty: ${resolved}`, "ERR_ANNOTATIONS_INVALID", `Verify that the ${targetDesc} is a valid non-empty Lua annotations file.`);
|
|
8992
9163
|
try {
|
|
8993
9164
|
const fd = fs.openSync(resolved, "r");
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
9165
|
+
let isBinary = false;
|
|
9166
|
+
try {
|
|
9167
|
+
const buffer = Buffer.alloc(Math.min(stat.size, 512));
|
|
9168
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
9169
|
+
isBinary = buffer.subarray(0, bytesRead).includes(0);
|
|
9170
|
+
} finally {
|
|
9171
|
+
fs.closeSync(fd);
|
|
9172
|
+
}
|
|
9173
|
+
if (isBinary) throw new AnnotationsError(isCustom ? `Custom annotations file appears to be a binary file: ${resolved}` : `Annotations file specified in environment appears to be a binary file: ${resolved}`, "ERR_ANNOTATIONS_INVALID", "Verify that the annotations file is a valid Lua text annotations file.");
|
|
8998
9174
|
} catch (err) {
|
|
8999
9175
|
if (err instanceof AnnotationsError) throw err;
|
|
9000
9176
|
}
|
|
@@ -9063,6 +9239,7 @@ async function resolveAnnotations(options = {}) {
|
|
|
9063
9239
|
}
|
|
9064
9240
|
//#endregion
|
|
9065
9241
|
//#region src/cache-status.ts
|
|
9242
|
+
/** Collects detailed status and disk usage information for LuaLS and annotations caches. */
|
|
9066
9243
|
function getCacheStatus(baseCacheDir = systemPaths.cache) {
|
|
9067
9244
|
const totalSize = getDirectorySize(baseCacheDir);
|
|
9068
9245
|
const totalSizeFormatted = formatBytes(totalSize);
|
|
@@ -9120,6 +9297,7 @@ function getCacheStatus(baseCacheDir = systemPaths.cache) {
|
|
|
9120
9297
|
}
|
|
9121
9298
|
};
|
|
9122
9299
|
}
|
|
9300
|
+
/** Formats a cache status report into human-readable terminal output. */
|
|
9123
9301
|
function formatCacheStatusPretty(report) {
|
|
9124
9302
|
const lines = [];
|
|
9125
9303
|
lines.push("nanos-lint Cache Status");
|
|
@@ -9154,6 +9332,7 @@ function formatCacheStatusPretty(report) {
|
|
|
9154
9332
|
}
|
|
9155
9333
|
//#endregion
|
|
9156
9334
|
//#region src/cli.ts
|
|
9335
|
+
/** Retrieves the formatted package name and version string from package.json. */
|
|
9157
9336
|
function getVersionString() {
|
|
9158
9337
|
const root = getPackageRoot();
|
|
9159
9338
|
try {
|
|
@@ -9167,10 +9346,12 @@ function getVersionString() {
|
|
|
9167
9346
|
function writeOutput(message) {
|
|
9168
9347
|
if (logger.isOutputEnabled()) console.log(message);
|
|
9169
9348
|
}
|
|
9349
|
+
/** Accumulates repeatable --ignore pattern arguments, splitting by comma and newline. */
|
|
9170
9350
|
function collectIgnorePatterns(val, prev) {
|
|
9171
9351
|
const parts = val.split(/[\r\n,]+/).map((s) => s.trim()).filter(Boolean);
|
|
9172
9352
|
return (prev ?? []).concat(parts);
|
|
9173
9353
|
}
|
|
9354
|
+
/** Constructs and configures the top-level Commander program and its CLI subcommands. */
|
|
9174
9355
|
function createProgram(options) {
|
|
9175
9356
|
const setExitCode = options?.setExitCode ?? (() => {});
|
|
9176
9357
|
const program = new Command("nanos-lint");
|
|
@@ -9319,6 +9500,7 @@ Examples:
|
|
|
9319
9500
|
`);
|
|
9320
9501
|
return program;
|
|
9321
9502
|
}
|
|
9503
|
+
/** Parses command-line arguments and executes the requested CLI action. */
|
|
9322
9504
|
async function runCLI(args = process.argv.slice(2)) {
|
|
9323
9505
|
for (let i = 0; i < args.length; i++) {
|
|
9324
9506
|
const arg = args[i];
|
|
@@ -9355,6 +9537,7 @@ async function runCLI(args = process.argv.slice(2)) {
|
|
|
9355
9537
|
return 1;
|
|
9356
9538
|
}
|
|
9357
9539
|
}
|
|
9540
|
+
/** Determines whether the current module is being directly executed as an application entrypoint. */
|
|
9358
9541
|
function isDirectExecution(importMetaUrl = import.meta.url, argv1 = process.argv[1]) {
|
|
9359
9542
|
if (!argv1) return false;
|
|
9360
9543
|
const toPath = (urlStr) => {
|
|
@@ -9391,6 +9574,6 @@ if (isDirectExecution()) runCLI().then((code) => {
|
|
|
9391
9574
|
process.exit(1);
|
|
9392
9575
|
});
|
|
9393
9576
|
//#endregion
|
|
9394
|
-
export {
|
|
9577
|
+
export { getLuaLSMetadataPath as $, formatProblemSummary as A, loadConfigFile as At, DOWNLOAD_TIMEOUT_MS as B, NanosLintError as Bt, getCachedAnnotationsFilePath as C, sanitizeLuaLSVersion as Ct, updateLastCheckedDate as D, getDefinitionsDir as Dt, readAnnotationsMetadata as E, getDefaultTemplatePath as Et, shouldEnableColor as F, stripTrailingSlashes as Ft, limitDownloadStream as G, fileUriToPath as Gt, computeFileSha256 as H, formatBytes as Ht, resolveLuaLSBinary as I, AnnotationsError as It, findExistingLuaLSDir as J, LOG_LEVEL_PRIORITY as Jt, LUALS_METADATA_FILENAME as K, DEFAULT_LOG_LEVEL as Kt, runLuaLSCheck as L, CacheError as Lt, formatSeverityBadge as M, parseJsonc as Mt, getColors as N, resolveWorkspaceConfig as Nt, formatGitHubAnnotations as O, getPackageRoot as Ot, pluralize as P, stripJsonComments as Pt, getLegacyCacheDir as Q, parseLogLevel as Qt, countCheckedFiles as R, ConfigError as Rt, getAnnotationsMetadataFilePath as S, resolveLuaLSVersion as St, isAnnotationsValid as T, getDefaultAnnotationsPath as Tt, downloadAndExtractLuaLS as U, getDirectorySize as Ut, MAX_ARCHIVE_SIZE_BYTES as V, cleanCache as Vt, isAllowedDownloadUrl as W, systemPaths as Wt, getCacheDir as X, isValidLogLevel as Xt, getBaseLuaLSCacheDir as Y, Logger as Yt, getIsoWeek as Z, logger as Zt, MAX_ANNOTATIONS_SIZE_BYTES as _, getPlatformInfo as _t, formatCacheStatusPretty as a, MAX_DECOMPRESSED_SIZE_BYTES as at, MIN_ANNOTATIONS_SIZE_BYTES as b, fetchLatestLuaLSVersionFromGitHub as bt, DOCGEN_BRANCH as c, escapePowerShellSingleQuote as ct, RAW_ANNOTATIONS_URL as d, isBinaryRunnable as dt, listCachedLuaLSVersionDirs as et, downloadAndCacheAnnotations as f, isBinaryValid as ft, ANNOTATIONS_FILENAME as g, validateArchiveMembers as gt, getRawAnnotationsUrl as h, parseTarTvSize as ht, runCLI as i, MAX_ARCHIVE_MEMBER_COUNT as it, formatReport as j, mergeConfigs as jt, formatPretty as k, initWorkspace as kt, DOCGEN_REPO as l, getTarBinary as lt, fetchRawAnnotationsContent as m, isZipArchive as mt, createProgram as n, readLuaLSMetadata as nt, getCacheStatus as o, MIN_DOWNLOADED_BINARY_SIZE_BYTES as ot, fetchLatestCommitId as p, isGzipArchive as pt, cleanupOldCachedLuaLSVersions as q, LOG_LEVELS as qt, isDirectExecution as r, writeLuaLSMetadata as rt, resolveAnnotations as s, assertValidLuaLSBinary as st, collectIgnorePatterns as t, listCachedLuaLSVersions as tt, GITHUB_COMMITS_API as u, inspectZipMembers as ut, MAX_COMMIT_JSON_SIZE_BYTES as v, DEFAULT_LUALS_VERSION as vt, getTodayDateString as w, VALID_LUALS_DIAGNOSTIC_CODES as wt, getAnnotationsCacheDir as x, resolveLatestLuaLSVersion as xt, METADATA_FILENAME as y, FALLBACK_LUALS_VERSION as yt, ALLOWED_DOWNLOAD_DOMAINS as z, LuaLSError as zt };
|
|
9395
9578
|
|
|
9396
|
-
//# sourceMappingURL=cli-
|
|
9579
|
+
//# sourceMappingURL=cli-BTHx4yp_.js.map
|