nanos-lint 2.5.0 → 2.6.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 +14 -11
- package/action.yml +9 -0
- package/dist/{cli-BVXk8XmH.js → cli-5LLXRlN7.js} +422 -34
- package/dist/cli-5LLXRlN7.js.map +1 -0
- package/dist/cli-r8LDolDk.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +137 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +6 -3
- package/dist/cli-BVXk8XmH.js.map +0 -1
- package/vendor/nanos-world-vscode-extension/annotations.lua +0 -11831
|
@@ -2,10 +2,10 @@ import { fileURLToPath } from "node:url";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
|
+
import process$1 from "node:process";
|
|
5
6
|
import childProcess, { execFile, execFileSync } from "node:child_process";
|
|
6
7
|
import { promisify, stripVTControlCharacters } from "node:util";
|
|
7
8
|
import { EventEmitter } from "node:events";
|
|
8
|
-
import process$1 from "node:process";
|
|
9
9
|
//#region src/types.ts
|
|
10
10
|
/**
|
|
11
11
|
* 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`)
|
|
@@ -778,6 +778,93 @@ function printParseErrorCode(code) {
|
|
|
778
778
|
}
|
|
779
779
|
return "<unknown ParseErrorCode>";
|
|
780
780
|
}
|
|
781
|
+
Object.freeze([
|
|
782
|
+
"",
|
|
783
|
+
" ",
|
|
784
|
+
".",
|
|
785
|
+
"..",
|
|
786
|
+
" .",
|
|
787
|
+
". ",
|
|
788
|
+
" ..",
|
|
789
|
+
".. ",
|
|
790
|
+
"../",
|
|
791
|
+
"../foo",
|
|
792
|
+
"foo/../bar",
|
|
793
|
+
"foo/bar",
|
|
794
|
+
"foo\\bar",
|
|
795
|
+
"foo\0bar"
|
|
796
|
+
]);
|
|
797
|
+
function isSafeFilename(filename) {
|
|
798
|
+
if (typeof filename !== "string") return false;
|
|
799
|
+
const trimmed = filename.trim();
|
|
800
|
+
return trimmed !== "" && trimmed !== "." && trimmed !== ".." && !filename.includes("/") && !filename.includes("\\") && !filename.includes("\0");
|
|
801
|
+
}
|
|
802
|
+
function assertSafeFilename(filename) {
|
|
803
|
+
if (typeof filename !== "string") throw new TypeError("Expected a string");
|
|
804
|
+
if (!isSafeFilename(filename)) throw new Error(`Unsafe filename: ${JSON.stringify(filename)}`);
|
|
805
|
+
}
|
|
806
|
+
//#endregion
|
|
807
|
+
//#region node_modules/env-paths/index.js
|
|
808
|
+
const homedir = os.homedir();
|
|
809
|
+
const tmpdir = os.tmpdir();
|
|
810
|
+
const { env } = process$1;
|
|
811
|
+
const macos = (name) => {
|
|
812
|
+
const library = path.join(homedir, "Library");
|
|
813
|
+
return {
|
|
814
|
+
data: path.join(library, "Application Support", name),
|
|
815
|
+
config: path.join(library, "Preferences", name),
|
|
816
|
+
cache: path.join(library, "Caches", name),
|
|
817
|
+
log: path.join(library, "Logs", name),
|
|
818
|
+
temp: path.join(tmpdir, name)
|
|
819
|
+
};
|
|
820
|
+
};
|
|
821
|
+
const windows = (name) => {
|
|
822
|
+
const appData = env.APPDATA || path.join(homedir, "AppData", "Roaming");
|
|
823
|
+
const localAppData = env.LOCALAPPDATA || path.join(homedir, "AppData", "Local");
|
|
824
|
+
return {
|
|
825
|
+
data: path.join(localAppData, name, "Data"),
|
|
826
|
+
config: path.join(appData, name, "Config"),
|
|
827
|
+
cache: path.join(localAppData, name, "Cache"),
|
|
828
|
+
log: path.join(localAppData, name, "Log"),
|
|
829
|
+
temp: path.join(tmpdir, name)
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
const linux = (name) => {
|
|
833
|
+
const username = path.basename(homedir);
|
|
834
|
+
return {
|
|
835
|
+
data: path.join(env.XDG_DATA_HOME || path.join(homedir, ".local", "share"), name),
|
|
836
|
+
config: path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), name),
|
|
837
|
+
cache: path.join(env.XDG_CACHE_HOME || path.join(homedir, ".cache"), name),
|
|
838
|
+
log: path.join(env.XDG_STATE_HOME || path.join(homedir, ".local", "state"), name),
|
|
839
|
+
temp: path.join(tmpdir, username, name)
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
function envPaths(name, { suffix = "nodejs" } = {}) {
|
|
843
|
+
assertSafeFilename(name);
|
|
844
|
+
if (suffix) name += `-${suffix}`;
|
|
845
|
+
assertSafeFilename(name);
|
|
846
|
+
if (process$1.platform === "darwin") return macos(name);
|
|
847
|
+
if (process$1.platform === "win32") return windows(name);
|
|
848
|
+
return linux(name);
|
|
849
|
+
}
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/paths.ts
|
|
852
|
+
/**
|
|
853
|
+
* System paths resolved for nanos-lint using cross-platform conventions
|
|
854
|
+
* provided by env-paths (XDG on Linux, %LOCALAPPDATA%/%APPDATA% on Windows, ~/Library on macOS).
|
|
855
|
+
*/
|
|
856
|
+
const systemPaths = envPaths("nanos-lint", { suffix: "" });
|
|
857
|
+
/**
|
|
858
|
+
* Clears the nanos-lint cache directory.
|
|
859
|
+
* Returns the path of the cleared cache directory, or null if it did not exist.
|
|
860
|
+
*/
|
|
861
|
+
function cleanCache(targetDir = systemPaths.cache) {
|
|
862
|
+
if (fs.existsSync(targetDir)) {
|
|
863
|
+
fs.rmSync(targetDir, { recursive: true });
|
|
864
|
+
return targetDir;
|
|
865
|
+
}
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
781
868
|
//#endregion
|
|
782
869
|
//#region src/config.ts
|
|
783
870
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -793,9 +880,19 @@ function getPackageRoot() {
|
|
|
793
880
|
}
|
|
794
881
|
return path.resolve(__dirname, "..");
|
|
795
882
|
}
|
|
883
|
+
function getDefaultAnnotationsPath() {
|
|
884
|
+
const envPath = process.env.NANOS_ANNOTATIONS_PATH || process.env.NANOS_ANNOTATIONS;
|
|
885
|
+
if (envPath && fs.existsSync(envPath)) return path.resolve(envPath);
|
|
886
|
+
const bundled = path.join(getPackageRoot(), "annotations.lua");
|
|
887
|
+
if (fs.existsSync(bundled)) return bundled;
|
|
888
|
+
return path.join(systemPaths.cache, "annotations", "annotations.lua");
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* @deprecated Use getDefaultAnnotationsPath() instead.
|
|
892
|
+
* Returns the directory containing the resolved annotations.lua file.
|
|
893
|
+
*/
|
|
796
894
|
function getDefinitionsDir() {
|
|
797
|
-
|
|
798
|
-
return path.join(root, "vendor", "nanos-world-vscode-extension");
|
|
895
|
+
return path.dirname(getDefaultAnnotationsPath());
|
|
799
896
|
}
|
|
800
897
|
function getDefaultTemplatePath() {
|
|
801
898
|
const root = getPackageRoot();
|
|
@@ -837,15 +934,27 @@ function stripTrailingSlashes(value) {
|
|
|
837
934
|
}
|
|
838
935
|
/**
|
|
839
936
|
* Merges a base nanos configuration with a workspace override configuration.
|
|
840
|
-
* Guarantees that
|
|
937
|
+
* Guarantees that nanos API annotations are included in workspace.library,
|
|
841
938
|
* and standardizes paths for LuaLS.
|
|
939
|
+
*
|
|
940
|
+
* @param base Base configuration template
|
|
941
|
+
* @param override Workspace override configuration
|
|
942
|
+
* @param definitionsPath Path to annotations.lua file or directory containing it
|
|
943
|
+
* @param options Additional merge options
|
|
842
944
|
*/
|
|
843
|
-
function mergeConfigs(base, override = {},
|
|
844
|
-
|
|
945
|
+
function mergeConfigs(base, override = {}, definitionsPath = getDefaultAnnotationsPath(), options) {
|
|
946
|
+
let resolvedPath = definitionsPath;
|
|
947
|
+
if (fs.existsSync(definitionsPath)) try {
|
|
948
|
+
if (fs.statSync(definitionsPath).isDirectory()) {
|
|
949
|
+
const candidate = path.join(definitionsPath, "annotations.lua");
|
|
950
|
+
if (fs.existsSync(candidate)) resolvedPath = candidate;
|
|
951
|
+
}
|
|
952
|
+
} catch {}
|
|
953
|
+
const normalizedDefPath = resolvedPath.split(path.sep).join("/");
|
|
845
954
|
const baseLibraries = base.workspace?.library ?? [];
|
|
846
955
|
const overrideLibraries = override.workspace?.library ?? [];
|
|
847
956
|
const librarySet = /* @__PURE__ */ new Set([
|
|
848
|
-
|
|
957
|
+
normalizedDefPath,
|
|
849
958
|
...baseLibraries,
|
|
850
959
|
...overrideLibraries
|
|
851
960
|
]);
|
|
@@ -938,7 +1047,7 @@ function mergeConfigs(base, override = {}, definitionsDir = getDefinitionsDir(),
|
|
|
938
1047
|
*/
|
|
939
1048
|
function resolveWorkspaceConfig(workspacePath, customConfigPath, options) {
|
|
940
1049
|
const defaultTemplate = loadConfigFile(getDefaultTemplatePath());
|
|
941
|
-
const
|
|
1050
|
+
const activeAnnotationsPath = options?.annotationsPath || getDefaultAnnotationsPath();
|
|
942
1051
|
let userConfig = {};
|
|
943
1052
|
if (customConfigPath) userConfig = loadConfigFile(path.resolve(customConfigPath));
|
|
944
1053
|
else {
|
|
@@ -961,7 +1070,7 @@ function resolveWorkspaceConfig(workspacePath, customConfigPath, options) {
|
|
|
961
1070
|
}
|
|
962
1071
|
cliIgnore = expanded;
|
|
963
1072
|
}
|
|
964
|
-
const merged = mergeConfigs(defaultTemplate, userConfig,
|
|
1073
|
+
const merged = mergeConfigs(defaultTemplate, userConfig, activeAnnotationsPath, { cliIgnore });
|
|
965
1074
|
if (!hasCliIgnore) {
|
|
966
1075
|
const resolvedTarget = path.resolve(workspacePath);
|
|
967
1076
|
if (fs.existsSync(path.join(resolvedTarget, "main.lua")) && (fs.existsSync(path.join(resolvedTarget, "bin", "lua-language-server.exe")) || fs.existsSync(path.join(resolvedTarget, "bin", "lua-language-server")))) {
|
|
@@ -976,7 +1085,7 @@ function resolveWorkspaceConfig(workspacePath, customConfigPath, options) {
|
|
|
976
1085
|
])];
|
|
977
1086
|
}
|
|
978
1087
|
}
|
|
979
|
-
const tempDir =
|
|
1088
|
+
const tempDir = systemPaths.temp;
|
|
980
1089
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
981
1090
|
const tempConfigFile = path.join(tempDir, `luarc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
982
1091
|
fs.writeFileSync(tempConfigFile, JSON.stringify(merged, null, 2), "utf-8");
|
|
@@ -992,9 +1101,8 @@ function initWorkspace(workspacePath, options) {
|
|
|
992
1101
|
const targetFile = path.join(workspacePath, ".luarc.json");
|
|
993
1102
|
if (fs.existsSync(targetFile) && !options?.force) throw new Error(`.luarc.json already exists at ${targetFile}. Use --force to overwrite.`);
|
|
994
1103
|
const template = loadConfigFile(getDefaultTemplatePath());
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
if (!fs.existsSync(sourceAnnotations)) throw new Error(`Definitions file not found at ${sourceAnnotations}. Make sure submodules are initialized.`);
|
|
1104
|
+
const sourceAnnotations = options?.annotationsPath || getDefaultAnnotationsPath();
|
|
1105
|
+
if (!fs.existsSync(sourceAnnotations)) throw new Error(`Definitions file not found at ${sourceAnnotations}. Make sure annotations have been downloaded or pass a valid file with --annotations.`);
|
|
998
1106
|
const targetNanosDir = path.join(workspacePath, ".nanos-lint");
|
|
999
1107
|
fs.mkdirSync(targetNanosDir, { recursive: true });
|
|
1000
1108
|
const targetAnnotations = path.join(targetNanosDir, "annotations.lua");
|
|
@@ -1119,6 +1227,12 @@ function getPlatformInfo(version = FALLBACK_LUALS_VERSION) {
|
|
|
1119
1227
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
1120
1228
|
}
|
|
1121
1229
|
function getCacheDir(version = FALLBACK_LUALS_VERSION) {
|
|
1230
|
+
return path.join(systemPaths.cache, "luals", version);
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Returns the legacy cache directory used in nanos-lint <= 2.2.1.
|
|
1234
|
+
*/
|
|
1235
|
+
function getLegacyCacheDir(version = FALLBACK_LUALS_VERSION) {
|
|
1122
1236
|
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
1123
1237
|
return path.join(base, "nanos-lint", "luals", version);
|
|
1124
1238
|
}
|
|
@@ -1147,12 +1261,9 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1147
1261
|
const binaryPath = path.join(destDir, info.binaryRelativePath);
|
|
1148
1262
|
const completeMarker = path.join(destDir, ".complete");
|
|
1149
1263
|
if (fs.existsSync(destDir)) {
|
|
1150
|
-
if (fs.existsSync(binaryPath) &&
|
|
1151
|
-
if (
|
|
1152
|
-
|
|
1153
|
-
} catch {}
|
|
1154
|
-
return binaryPath;
|
|
1155
|
-
}
|
|
1264
|
+
if (fs.existsSync(binaryPath) && fs.existsSync(completeMarker)) try {
|
|
1265
|
+
if (fs.readFileSync(completeMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(binaryPath)) return binaryPath;
|
|
1266
|
+
} catch {}
|
|
1156
1267
|
try {
|
|
1157
1268
|
fs.rmSync(destDir, {
|
|
1158
1269
|
recursive: true,
|
|
@@ -1219,6 +1330,7 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1219
1330
|
fs.chmodSync(tempBinaryPath, 493);
|
|
1220
1331
|
} catch {}
|
|
1221
1332
|
if (!fs.existsSync(tempBinaryPath) || fs.statSync(tempBinaryPath).size < 1e5) throw new Error(`Failed to extract valid LuaLS binary to expected path: ${tempBinaryPath}`);
|
|
1333
|
+
if (!isBinaryValid(tempBinaryPath)) throw new Error(`Extracted LuaLS binary at ${tempBinaryPath} is invalid or non-functional.`);
|
|
1222
1334
|
fs.writeFileSync(path.join(tempDir, ".complete"), resolvedVersion, "utf-8");
|
|
1223
1335
|
for (let attempt = 0; attempt < 5; attempt++) try {
|
|
1224
1336
|
fs.renameSync(tempDir, destDir);
|
|
@@ -1235,9 +1347,16 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1235
1347
|
return binaryPath;
|
|
1236
1348
|
}
|
|
1237
1349
|
if (attempt < 4) await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
1238
|
-
else
|
|
1350
|
+
else {
|
|
1351
|
+
if (fs.existsSync(destDir) && (!fs.existsSync(binaryPath) || !fs.existsSync(completeMarker))) try {
|
|
1352
|
+
fs.rmSync(destDir, {
|
|
1353
|
+
recursive: true,
|
|
1354
|
+
force: true
|
|
1355
|
+
});
|
|
1356
|
+
} catch {}
|
|
1357
|
+
throw renameErr;
|
|
1358
|
+
}
|
|
1239
1359
|
}
|
|
1240
|
-
if (!isBinaryValid(binaryPath)) throw new Error(`Extracted LuaLS binary at ${binaryPath} is invalid or non-functional.`);
|
|
1241
1360
|
if (!options?.quiet) console.log(`[luals] Ready: ${binaryPath}`);
|
|
1242
1361
|
return binaryPath;
|
|
1243
1362
|
} finally {
|
|
@@ -1251,6 +1370,11 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
|
|
|
1251
1370
|
}
|
|
1252
1371
|
async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
1253
1372
|
if (process.env.LUALS_BIN && fs.existsSync(process.env.LUALS_BIN)) return process.env.LUALS_BIN;
|
|
1373
|
+
if (!version || version === "latest") {
|
|
1374
|
+
const defaultInfo = getPlatformInfo(FALLBACK_LUALS_VERSION);
|
|
1375
|
+
const defaultBundledPath = path.join(getPackageRoot(), defaultInfo.binaryRelativePath);
|
|
1376
|
+
if (fs.existsSync(defaultBundledPath) && isBinaryValid(defaultBundledPath)) return defaultBundledPath;
|
|
1377
|
+
}
|
|
1254
1378
|
const resolvedVersion = await resolveLuaLSVersion(version);
|
|
1255
1379
|
const info = getPlatformInfo(resolvedVersion);
|
|
1256
1380
|
const bundledPath = path.join(getPackageRoot(), info.binaryRelativePath);
|
|
@@ -1259,12 +1383,9 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
|
1259
1383
|
const cachedPath = path.join(cachedDir, info.binaryRelativePath);
|
|
1260
1384
|
const completeMarker = path.join(cachedDir, ".complete");
|
|
1261
1385
|
if (fs.existsSync(cachedPath)) {
|
|
1262
|
-
if (
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1265
|
-
} catch {}
|
|
1266
|
-
return cachedPath;
|
|
1267
|
-
}
|
|
1386
|
+
if (fs.existsSync(completeMarker)) try {
|
|
1387
|
+
if (fs.readFileSync(completeMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(cachedPath)) return cachedPath;
|
|
1388
|
+
} catch {}
|
|
1268
1389
|
if (!options?.quiet) console.warn(`[luals] Cached LuaLS binary at ${cachedPath} is corrupted or incomplete. Repairing...`);
|
|
1269
1390
|
try {
|
|
1270
1391
|
fs.rmSync(cachedDir, {
|
|
@@ -1273,6 +1394,24 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
|
|
|
1273
1394
|
});
|
|
1274
1395
|
} catch {}
|
|
1275
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;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1276
1415
|
try {
|
|
1277
1416
|
const cmd = process.platform === "win32" ? "where.exe" : "which";
|
|
1278
1417
|
const { stdout } = await execFileAsync(cmd, ["lua-language-server"]);
|
|
@@ -1291,7 +1430,7 @@ async function runLuaLSCheck(targetPath, configPath, options) {
|
|
|
1291
1430
|
checkDir = path.dirname(absoluteTarget);
|
|
1292
1431
|
targetFileOnly = absoluteTarget;
|
|
1293
1432
|
}
|
|
1294
|
-
const tempOutputDir =
|
|
1433
|
+
const tempOutputDir = systemPaths.temp;
|
|
1295
1434
|
fs.mkdirSync(tempOutputDir, { recursive: true });
|
|
1296
1435
|
const checkOutPath = path.join(tempOutputDir, `check-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
1297
1436
|
const args = [
|
|
@@ -4539,6 +4678,228 @@ function useColor() {
|
|
|
4539
4678
|
}
|
|
4540
4679
|
new Command();
|
|
4541
4680
|
//#endregion
|
|
4681
|
+
//#region src/annotations.ts
|
|
4682
|
+
const DOCGEN_REPO = "nanos-world/vscode-extension";
|
|
4683
|
+
const DOCGEN_BRANCH = "docgen-output";
|
|
4684
|
+
const RAW_ANNOTATIONS_URL = "https://raw.githubusercontent.com/nanos-world/vscode-extension/refs/heads/docgen-output/annotations.lua";
|
|
4685
|
+
const GITHUB_COMMITS_API = "https://api.github.com/repos/nanos-world/vscode-extension/commits/docgen-output";
|
|
4686
|
+
const ANNOTATIONS_FILENAME = "annotations.lua";
|
|
4687
|
+
const METADATA_FILENAME = "metadata.json";
|
|
4688
|
+
function getTodayDateString(d = /* @__PURE__ */ new Date()) {
|
|
4689
|
+
const year = d.getFullYear();
|
|
4690
|
+
const month = d.getMonth() + 1;
|
|
4691
|
+
const day = d.getDate();
|
|
4692
|
+
return {
|
|
4693
|
+
dateStr: `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
|
|
4694
|
+
dateObj: {
|
|
4695
|
+
year,
|
|
4696
|
+
month,
|
|
4697
|
+
day
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
function getAnnotationsCacheDir() {
|
|
4702
|
+
return path.join(systemPaths.cache, "annotations");
|
|
4703
|
+
}
|
|
4704
|
+
function getCachedAnnotationsFilePath() {
|
|
4705
|
+
return path.join(getAnnotationsCacheDir(), ANNOTATIONS_FILENAME);
|
|
4706
|
+
}
|
|
4707
|
+
function getAnnotationsMetadataFilePath() {
|
|
4708
|
+
return path.join(getAnnotationsCacheDir(), METADATA_FILENAME);
|
|
4709
|
+
}
|
|
4710
|
+
function readAnnotationsMetadata(cacheDir = getAnnotationsCacheDir()) {
|
|
4711
|
+
const metaPath = path.join(cacheDir, "metadata.json");
|
|
4712
|
+
if (!fs.existsSync(metaPath)) return null;
|
|
4713
|
+
try {
|
|
4714
|
+
const content = fs.readFileSync(metaPath, "utf-8");
|
|
4715
|
+
const parsed = JSON.parse(content);
|
|
4716
|
+
if (typeof parsed?.commitId === "string" && typeof parsed?.lastChecked === "string") return parsed;
|
|
4717
|
+
} catch {}
|
|
4718
|
+
return null;
|
|
4719
|
+
}
|
|
4720
|
+
async function fetchLatestCommitId() {
|
|
4721
|
+
try {
|
|
4722
|
+
const headers = { "User-Agent": "nanos-lint" };
|
|
4723
|
+
if (process.env.GITHUB_TOKEN) headers["Authorization"] = `token ${process.env.GITHUB_TOKEN}`;
|
|
4724
|
+
const res = await fetch(GITHUB_COMMITS_API, {
|
|
4725
|
+
headers,
|
|
4726
|
+
signal: AbortSignal.timeout(5e3)
|
|
4727
|
+
});
|
|
4728
|
+
if (res.ok) {
|
|
4729
|
+
const data = await res.json();
|
|
4730
|
+
if (typeof data.sha === "string" && /^[0-9a-fA-F]{7,40}$/.test(data.sha)) return data.sha;
|
|
4731
|
+
}
|
|
4732
|
+
} catch {}
|
|
4733
|
+
return null;
|
|
4734
|
+
}
|
|
4735
|
+
async function fetchRawAnnotationsContent() {
|
|
4736
|
+
const res = await fetch(RAW_ANNOTATIONS_URL, {
|
|
4737
|
+
headers: { "User-Agent": "nanos-lint" },
|
|
4738
|
+
signal: AbortSignal.timeout(15e3)
|
|
4739
|
+
});
|
|
4740
|
+
if (!res.ok) throw new Error(`Failed to download annotations.lua: ${res.status} ${res.statusText}`);
|
|
4741
|
+
const text = await res.text();
|
|
4742
|
+
if (!text || text.length < 1e3) throw new Error("Downloaded annotations.lua appears truncated or invalid");
|
|
4743
|
+
return text;
|
|
4744
|
+
}
|
|
4745
|
+
async function copyFileWithRetry(src, dest, maxRetries = 10) {
|
|
4746
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) try {
|
|
4747
|
+
fs.copyFileSync(src, dest);
|
|
4748
|
+
return;
|
|
4749
|
+
} catch (err) {
|
|
4750
|
+
const code = err?.code;
|
|
4751
|
+
if ((code === "EBUSY" || code === "EPERM") && attempt < maxRetries - 1) {
|
|
4752
|
+
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
4753
|
+
continue;
|
|
4754
|
+
}
|
|
4755
|
+
throw err;
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
/**
|
|
4759
|
+
* Updates the metadata lastChecked date atomically without modifying annotations.lua.
|
|
4760
|
+
*/
|
|
4761
|
+
function updateLastCheckedDate(commitId, cacheDir = getAnnotationsCacheDir()) {
|
|
4762
|
+
const { dateStr, dateObj } = getTodayDateString();
|
|
4763
|
+
const metadata = {
|
|
4764
|
+
commitId,
|
|
4765
|
+
lastChecked: dateStr,
|
|
4766
|
+
date: dateObj
|
|
4767
|
+
};
|
|
4768
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
4769
|
+
const metaPath = path.join(cacheDir, METADATA_FILENAME);
|
|
4770
|
+
const tempMetaPath = path.join(os.tmpdir(), `.metadata-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.tmp`);
|
|
4771
|
+
try {
|
|
4772
|
+
fs.writeFileSync(tempMetaPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
4773
|
+
fs.copyFileSync(tempMetaPath, metaPath);
|
|
4774
|
+
} finally {
|
|
4775
|
+
try {
|
|
4776
|
+
if (fs.existsSync(tempMetaPath)) fs.unlinkSync(tempMetaPath);
|
|
4777
|
+
} catch {}
|
|
4778
|
+
}
|
|
4779
|
+
return metadata;
|
|
4780
|
+
}
|
|
4781
|
+
/**
|
|
4782
|
+
* Downloads annotations.lua, writes it and the new metadata in a chained atomic transaction.
|
|
4783
|
+
* If any step fails, the previous cache state is reverted.
|
|
4784
|
+
*/
|
|
4785
|
+
async function downloadAndCacheAnnotations(commitId, cacheDir = getAnnotationsCacheDir(), options) {
|
|
4786
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
4787
|
+
const finalAnnotationsPath = path.join(cacheDir, ANNOTATIONS_FILENAME);
|
|
4788
|
+
const finalMetaPath = path.join(cacheDir, METADATA_FILENAME);
|
|
4789
|
+
const tempDir = path.join(os.tmpdir(), `nanos-ann-tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
4790
|
+
const backupDir = path.join(os.tmpdir(), `nanos-ann-bak-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
4791
|
+
let hasBackup = false;
|
|
4792
|
+
try {
|
|
4793
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
4794
|
+
if (!options?.quiet) {
|
|
4795
|
+
if (commitId && commitId !== "unknown") console.log(`[annotations] Downloading nanos world API annotations (${commitId.slice(0, 7)})...`);
|
|
4796
|
+
else console.log("[annotations] Downloading nanos world API annotations...");
|
|
4797
|
+
}
|
|
4798
|
+
const content = await fetchRawAnnotationsContent();
|
|
4799
|
+
const tempAnnotationsPath = path.join(tempDir, ANNOTATIONS_FILENAME);
|
|
4800
|
+
fs.writeFileSync(tempAnnotationsPath, content, "utf-8");
|
|
4801
|
+
const { dateStr, dateObj } = getTodayDateString();
|
|
4802
|
+
const metadata = {
|
|
4803
|
+
commitId,
|
|
4804
|
+
lastChecked: dateStr,
|
|
4805
|
+
date: dateObj
|
|
4806
|
+
};
|
|
4807
|
+
const tempMetaPath = path.join(tempDir, METADATA_FILENAME);
|
|
4808
|
+
fs.writeFileSync(tempMetaPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
4809
|
+
const existingMeta = readAnnotationsMetadata(cacheDir);
|
|
4810
|
+
if (existingMeta && existingMeta.commitId === commitId && fs.existsSync(finalAnnotationsPath) && fs.statSync(finalAnnotationsPath).size >= 1e3) return finalAnnotationsPath;
|
|
4811
|
+
const oldAnnotationsExists = fs.existsSync(finalAnnotationsPath);
|
|
4812
|
+
const oldMetaExists = fs.existsSync(finalMetaPath);
|
|
4813
|
+
if (oldAnnotationsExists || oldMetaExists) {
|
|
4814
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
4815
|
+
if (oldAnnotationsExists) await copyFileWithRetry(finalAnnotationsPath, path.join(backupDir, ANNOTATIONS_FILENAME));
|
|
4816
|
+
if (oldMetaExists) await copyFileWithRetry(finalMetaPath, path.join(backupDir, METADATA_FILENAME));
|
|
4817
|
+
hasBackup = true;
|
|
4818
|
+
}
|
|
4819
|
+
await copyFileWithRetry(tempAnnotationsPath, finalAnnotationsPath);
|
|
4820
|
+
await copyFileWithRetry(tempMetaPath, finalMetaPath);
|
|
4821
|
+
if (!options?.quiet) {
|
|
4822
|
+
if (commitId && commitId !== "unknown") console.log(`[annotations] Updated annotations.lua to commit ${commitId.slice(0, 7)}.`);
|
|
4823
|
+
else console.log("[annotations] Updated annotations.lua.");
|
|
4824
|
+
}
|
|
4825
|
+
return finalAnnotationsPath;
|
|
4826
|
+
} catch (err) {
|
|
4827
|
+
if (hasBackup && fs.existsSync(backupDir)) try {
|
|
4828
|
+
const backupAnnotations = path.join(backupDir, ANNOTATIONS_FILENAME);
|
|
4829
|
+
const backupMeta = path.join(backupDir, METADATA_FILENAME);
|
|
4830
|
+
if (fs.existsSync(backupAnnotations)) await copyFileWithRetry(backupAnnotations, finalAnnotationsPath);
|
|
4831
|
+
if (fs.existsSync(backupMeta)) await copyFileWithRetry(backupMeta, finalMetaPath);
|
|
4832
|
+
} catch {}
|
|
4833
|
+
throw err;
|
|
4834
|
+
} finally {
|
|
4835
|
+
try {
|
|
4836
|
+
if (fs.existsSync(tempDir)) fs.rmSync(tempDir, {
|
|
4837
|
+
recursive: true,
|
|
4838
|
+
force: true
|
|
4839
|
+
});
|
|
4840
|
+
if (fs.existsSync(backupDir)) fs.rmSync(backupDir, {
|
|
4841
|
+
recursive: true,
|
|
4842
|
+
force: true
|
|
4843
|
+
});
|
|
4844
|
+
} catch {}
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
/**
|
|
4848
|
+
* Resolves the nanos world annotations.lua definitions file path.
|
|
4849
|
+
* Resolution precedence:
|
|
4850
|
+
* 1. CLI option (`customPath`)
|
|
4851
|
+
* 2. Environment variable (`NANOS_ANNOTATIONS_PATH` or `NANOS_ANNOTATIONS`)
|
|
4852
|
+
* 3. Bundled inside package root (`annotations.lua`)
|
|
4853
|
+
* 4. User cache (checked once per day from upstream GitHub)
|
|
4854
|
+
*/
|
|
4855
|
+
async function resolveAnnotations(options = {}) {
|
|
4856
|
+
if (options.customPath) {
|
|
4857
|
+
const resolved = path.resolve(options.customPath);
|
|
4858
|
+
if (!fs.existsSync(resolved)) throw new Error(`Custom annotations file not found: ${resolved}`);
|
|
4859
|
+
return resolved;
|
|
4860
|
+
}
|
|
4861
|
+
const envPath = process.env.NANOS_ANNOTATIONS_PATH || process.env.NANOS_ANNOTATIONS;
|
|
4862
|
+
if (envPath) {
|
|
4863
|
+
const resolved = path.resolve(envPath);
|
|
4864
|
+
if (!fs.existsSync(resolved)) throw new Error(`Annotations file specified in environment not found: ${resolved}`);
|
|
4865
|
+
return resolved;
|
|
4866
|
+
}
|
|
4867
|
+
const bundled = path.join(getPackageRoot(), "annotations.lua");
|
|
4868
|
+
if (fs.existsSync(bundled)) return bundled;
|
|
4869
|
+
const cacheDir = options.cacheDir ?? getAnnotationsCacheDir();
|
|
4870
|
+
const cachedAnnotationsFile = path.join(cacheDir, ANNOTATIONS_FILENAME);
|
|
4871
|
+
const metadata = readAnnotationsMetadata(cacheDir);
|
|
4872
|
+
const { dateStr } = getTodayDateString();
|
|
4873
|
+
if (metadata && metadata.lastChecked === dateStr && fs.existsSync(cachedAnnotationsFile)) return cachedAnnotationsFile;
|
|
4874
|
+
const latestCommitId = await fetchLatestCommitId();
|
|
4875
|
+
if (latestCommitId) {
|
|
4876
|
+
if (metadata && metadata.commitId === latestCommitId && fs.existsSync(cachedAnnotationsFile)) {
|
|
4877
|
+
updateLastCheckedDate(latestCommitId, cacheDir);
|
|
4878
|
+
return cachedAnnotationsFile;
|
|
4879
|
+
}
|
|
4880
|
+
return await downloadAndCacheAnnotations(latestCommitId, cacheDir, options);
|
|
4881
|
+
}
|
|
4882
|
+
if (fs.existsSync(cachedAnnotationsFile)) {
|
|
4883
|
+
try {
|
|
4884
|
+
updateLastCheckedDate(metadata?.commitId || "unknown", cacheDir);
|
|
4885
|
+
} catch {}
|
|
4886
|
+
return cachedAnnotationsFile;
|
|
4887
|
+
}
|
|
4888
|
+
try {
|
|
4889
|
+
return await downloadAndCacheAnnotations("unknown", cacheDir, options);
|
|
4890
|
+
} catch (err) {
|
|
4891
|
+
if (Boolean(err && typeof err === "object" && "code" in err && typeof err.code === "string" && [
|
|
4892
|
+
"EACCES",
|
|
4893
|
+
"EPERM",
|
|
4894
|
+
"ENOSPC",
|
|
4895
|
+
"EROFS",
|
|
4896
|
+
"EEXIST",
|
|
4897
|
+
"ENOENT"
|
|
4898
|
+
].includes(err.code)) || err instanceof Error && /permission denied|read-only|no space left/i.test(err.message)) throw new Error(`Failed to resolve nanos world API annotations due to a filesystem error: ${err instanceof Error ? err.message : String(err)}. Please check directory permissions or pass a custom file with --annotations.`, { cause: err });
|
|
4899
|
+
throw new Error(`Failed to resolve nanos world API annotations. Please check your network connection or pass a custom file with --annotations. (${err instanceof Error ? err.message : String(err)})`, { cause: err });
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
//#endregion
|
|
4542
4903
|
//#region src/cli.ts
|
|
4543
4904
|
function getVersionString() {
|
|
4544
4905
|
const root = getPackageRoot();
|
|
@@ -4564,7 +4925,7 @@ function createProgram(options) {
|
|
|
4564
4925
|
"Warning",
|
|
4565
4926
|
"Information",
|
|
4566
4927
|
"Hint"
|
|
4567
|
-
]).default("Warning")).option("--config <path>", "Path to custom .luarc.json configuration file").addOption(new Option("--format <format>", "Output format: pretty, json, github (default: pretty, auto-detects GitHub Actions)").choices([
|
|
4928
|
+
]).default("Warning")).option("--config <path>", "Path to custom .luarc.json configuration file").option("--annotations <path>", "Path to custom annotations.lua file").addOption(new Option("--format <format>", "Output format: pretty, json, github (default: pretty, auto-detects GitHub Actions)").choices([
|
|
4568
4929
|
"pretty",
|
|
4569
4930
|
"json",
|
|
4570
4931
|
"github"
|
|
@@ -4580,7 +4941,18 @@ function createProgram(options) {
|
|
|
4580
4941
|
quiet: opts.quiet,
|
|
4581
4942
|
ignore: opts.ignore
|
|
4582
4943
|
};
|
|
4583
|
-
|
|
4944
|
+
if (opts.config) {
|
|
4945
|
+
const resolvedConfig = path.resolve(opts.config);
|
|
4946
|
+
if (!fs.existsSync(resolvedConfig)) throw new Error(`Configuration file not found: ${resolvedConfig}`);
|
|
4947
|
+
}
|
|
4948
|
+
const annotationsPath = await resolveAnnotations({
|
|
4949
|
+
customPath: opts.annotations,
|
|
4950
|
+
quiet: opts.quiet
|
|
4951
|
+
});
|
|
4952
|
+
const resolved = resolveWorkspaceConfig(targetPath, checkOptions.configpath, {
|
|
4953
|
+
ignore: checkOptions.ignore,
|
|
4954
|
+
annotationsPath
|
|
4955
|
+
});
|
|
4584
4956
|
let result;
|
|
4585
4957
|
try {
|
|
4586
4958
|
result = await runLuaLSCheck(targetPath, resolved.configPath, checkOptions);
|
|
@@ -4594,8 +4966,12 @@ function createProgram(options) {
|
|
|
4594
4966
|
if (!result.passed && checkOptions.failOnError) setExitCode(1);
|
|
4595
4967
|
else setExitCode(0);
|
|
4596
4968
|
});
|
|
4597
|
-
program.command("init [path]").description("Scaffold a .luarc.json configuration in the workspace").option("-f, --force", "Overwrite existing .luarc.json configuration").action((targetPath = ".", opts) => {
|
|
4598
|
-
const
|
|
4969
|
+
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
|
+
const annotationsPath = await resolveAnnotations({ customPath: opts.annotations });
|
|
4971
|
+
const created = initWorkspace(path.resolve(targetPath), {
|
|
4972
|
+
force: opts.force,
|
|
4973
|
+
annotationsPath
|
|
4974
|
+
});
|
|
4599
4975
|
console.log(`[init] Initialized nanos world LuaLS configuration: ${created}`);
|
|
4600
4976
|
setExitCode(0);
|
|
4601
4977
|
});
|
|
@@ -4606,6 +4982,17 @@ function createProgram(options) {
|
|
|
4606
4982
|
console.log(`[luals] Ready at: ${bin}`);
|
|
4607
4983
|
setExitCode(0);
|
|
4608
4984
|
});
|
|
4985
|
+
program.command("clean-cache").alias("clean").description("Clear the nanos-lint cache").action(() => {
|
|
4986
|
+
try {
|
|
4987
|
+
const cleared = cleanCache();
|
|
4988
|
+
if (cleared) console.log(`[cache] Cleared cache at: ${cleared}`);
|
|
4989
|
+
else console.log(`[cache] Cache is already empty (${systemPaths.cache})`);
|
|
4990
|
+
setExitCode(0);
|
|
4991
|
+
} catch (err) {
|
|
4992
|
+
console.error(`[cache] Failed to clear cache: ${err instanceof Error ? err.message : String(err)}`);
|
|
4993
|
+
setExitCode(1);
|
|
4994
|
+
}
|
|
4995
|
+
});
|
|
4609
4996
|
program.command("version").description("Show version information").action(() => {
|
|
4610
4997
|
console.log(getVersionString());
|
|
4611
4998
|
setExitCode(0);
|
|
@@ -4618,6 +5005,7 @@ Examples:
|
|
|
4618
5005
|
$ npx nanos-lint check . --checklevel=Error
|
|
4619
5006
|
$ npx nanos-lint check . --ignore "myfolder/hello-*.lua"
|
|
4620
5007
|
$ npx nanos-lint init
|
|
5008
|
+
$ npx nanos-lint clean-cache
|
|
4621
5009
|
`);
|
|
4622
5010
|
return program;
|
|
4623
5011
|
}
|
|
@@ -4671,6 +5059,6 @@ if (isDirectExecution()) runCLI().then((code) => {
|
|
|
4671
5059
|
process.exit(1);
|
|
4672
5060
|
});
|
|
4673
5061
|
//#endregion
|
|
4674
|
-
export {
|
|
5062
|
+
export { stripTrailingSlashes as $, FALLBACK_LUALS_VERSION as A, resolveLuaLSVersion as B, formatProblemSummary as C, pluralize as D, getColors as E, getLegacyCacheDir as F, getDefinitionsDir as G, sanitizeLuaLSVersion as H, getPlatformInfo as I, loadConfigFile as J, getPackageRoot as K, isBinaryValid as L, downloadAndExtractLuaLS as M, escapePowerShellSingleQuote as N, shouldEnableColor as O, getCacheDir as P, stripJsonComments as Q, resolveLatestLuaLSVersion as R, formatPretty as S, formatSeverityBadge as T, getDefaultAnnotationsPath as U, runLuaLSCheck as V, getDefaultTemplatePath as W, parseJsonc as X, mergeConfigs as Y, resolveWorkspaceConfig as Z, getTodayDateString as _, ANNOTATIONS_FILENAME as a, updateLastCheckedDate as b, GITHUB_COMMITS_API as c, downloadAndCacheAnnotations as d, cleanCache as et, fetchLatestCommitId as f, getCachedAnnotationsFilePath as g, getAnnotationsMetadataFilePath as h, runCLI as i, countCheckedFiles as j, DEFAULT_LUALS_VERSION as k, METADATA_FILENAME as l, getAnnotationsCacheDir as m, createProgram as n, fileUriToPath as nt, DOCGEN_BRANCH as o, fetchRawAnnotationsContent as p, initWorkspace as q, isDirectExecution as r, DOCGEN_REPO as s, collectIgnorePatterns as t, systemPaths as tt, RAW_ANNOTATIONS_URL as u, readAnnotationsMetadata as v, formatReport as w, formatGitHubAnnotations as x, resolveAnnotations as y, resolveLuaLSBinary as z };
|
|
4675
5063
|
|
|
4676
|
-
//# sourceMappingURL=cli-
|
|
5064
|
+
//# sourceMappingURL=cli-5LLXRlN7.js.map
|