nanos-lint 2.2.0 → 2.3.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 vugi99
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vugi99
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![LuaLS Version](https://img.shields.io/badge/LuaLS-3.19.1-brightgreen.svg)](https://github.com/LuaLS/lua-language-server)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
8
 
9
- A dedicated, fast linter and type-checker for **[nanos world](https://nanos.world)** Lua scripts powered by the **[Lua Language Server (LuaLS)](https://github.com/LuaLS/lua-language-server)**.
9
+ A dedicated, fast linter and type-checker for **[nanos world](https://nanos-world.com/)** Lua scripts powered by the **[Lua Language Server (LuaLS)](https://github.com/LuaLS/lua-language-server)**.
10
10
 
11
11
  ---
12
12
 
@@ -2,7 +2,7 @@ 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 childProcess, { execFile } from "node:child_process";
5
+ import childProcess, { execFile, execFileSync } from "node:child_process";
6
6
  import { promisify, stripVTControlCharacters } from "node:util";
7
7
  import { EventEmitter } from "node:events";
8
8
  import process$1 from "node:process";
@@ -161,6 +161,7 @@ function mergeConfigs(base, override = {}, definitionsDir = getDefinitionsDir(),
161
161
  const defaultIgnore = [
162
162
  ".git",
163
163
  ".vscode",
164
+ ".nanos-lint",
164
165
  "node_modules",
165
166
  "dist",
166
167
  "bin",
@@ -290,12 +291,18 @@ function initWorkspace(workspacePath, options) {
290
291
  const template = loadConfigFile(getDefaultTemplatePath());
291
292
  const definitionsDir = getDefinitionsDir();
292
293
  const sourceAnnotations = path.join(definitionsDir, "annotations.lua");
294
+ if (!fs.existsSync(sourceAnnotations)) throw new Error(`Definitions file not found at ${sourceAnnotations}. Make sure submodules are initialized.`);
293
295
  const targetNanosDir = path.join(workspacePath, ".nanos-lint");
294
296
  fs.mkdirSync(targetNanosDir, { recursive: true });
295
297
  const targetAnnotations = path.join(targetNanosDir, "annotations.lua");
296
- if (fs.existsSync(sourceAnnotations)) fs.copyFileSync(sourceAnnotations, targetAnnotations);
298
+ fs.copyFileSync(sourceAnnotations, targetAnnotations);
297
299
  template.workspace = template.workspace ?? {};
298
300
  template.workspace.library = [".nanos-lint/annotations.lua"];
301
+ const existingIgnore = template.workspace.ignoreDir ?? [];
302
+ if (!existingIgnore.includes(".nanos-lint")) template.workspace.ignoreDir = [".nanos-lint", ...existingIgnore];
303
+ template.files = template.files ?? {};
304
+ const existingExclude = template.files.exclude ?? [];
305
+ if (!existingExclude.includes(".nanos-lint/**")) template.files.exclude = [".nanos-lint/**", ...existingExclude];
299
306
  fs.writeFileSync(targetFile, JSON.stringify(template, null, 2), "utf-8");
300
307
  return targetFile;
301
308
  }
@@ -374,15 +381,50 @@ function getCacheDir(version = FALLBACK_LUALS_VERSION) {
374
381
  const base = process.platform === "win32" ? process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
375
382
  return path.join(base, "nanos-lint", "luals", version);
376
383
  }
384
+ /**
385
+ * Verifies that a LuaLS binary exists, has non-trivial size, and is executable.
386
+ */
387
+ function isBinaryValid(binaryPath) {
388
+ if (!fs.existsSync(binaryPath)) return false;
389
+ try {
390
+ const stats = fs.statSync(binaryPath);
391
+ if (!stats.isFile() || stats.size < 1e5) return false;
392
+ const output = execFileSync(binaryPath, ["--version"], {
393
+ timeout: 5e3,
394
+ stdio: "pipe",
395
+ encoding: "utf-8"
396
+ });
397
+ return /^\d+\.\d+\.\d+/.test(output.trim());
398
+ } catch {
399
+ return false;
400
+ }
401
+ }
377
402
  async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDir, options) {
378
403
  const resolvedVersion = await resolveLuaLSVersion(version);
379
404
  const info = getPlatformInfo(resolvedVersion);
380
405
  const destDir = targetDir || getCacheDir(resolvedVersion);
381
406
  const binaryPath = path.join(destDir, info.binaryRelativePath);
382
- if (fs.existsSync(binaryPath)) return binaryPath;
383
- fs.mkdirSync(destDir, { recursive: true });
407
+ const completeMarker = path.join(destDir, ".complete");
408
+ if (fs.existsSync(destDir)) {
409
+ if (fs.existsSync(binaryPath) && isBinaryValid(binaryPath)) {
410
+ if (!fs.existsSync(completeMarker)) try {
411
+ fs.writeFileSync(completeMarker, resolvedVersion, "utf-8");
412
+ } catch {}
413
+ return binaryPath;
414
+ }
415
+ try {
416
+ fs.rmSync(destDir, {
417
+ recursive: true,
418
+ force: true
419
+ });
420
+ } catch {}
421
+ }
422
+ const parentDir = path.dirname(destDir);
423
+ fs.mkdirSync(parentDir, { recursive: true });
424
+ const tempDir = path.join(parentDir, `.${path.basename(destDir)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
425
+ fs.mkdirSync(tempDir, { recursive: true });
384
426
  const url = `https://github.com/LuaLS/lua-language-server/releases/download/${resolvedVersion}/${info.assetName}`;
385
- const archivePath = path.join(destDir, info.assetName);
427
+ const archivePath = path.join(tempDir, info.assetName);
386
428
  if (!options?.quiet) console.log(`[luals] Downloading LuaLS ${resolvedVersion} from ${url}...`);
387
429
  let response = null;
388
430
  let lastErr = null;
@@ -393,54 +435,108 @@ async function downloadAndExtractLuaLS(version = DEFAULT_LUALS_VERSION, targetDi
393
435
  response = res;
394
436
  break;
395
437
  }
438
+ await res.body?.cancel();
396
439
  lastErr = /* @__PURE__ */ new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
397
440
  } catch (err) {
398
441
  lastErr = err;
399
442
  }
400
443
  if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
401
444
  }
402
- if (!response || !response.body) throw lastErr || /* @__PURE__ */ new Error(`Failed to download ${url}`);
403
- const arrayBuffer = await response.arrayBuffer();
404
- fs.writeFileSync(archivePath, Buffer.from(arrayBuffer));
405
- if (!options?.quiet) console.log(`[luals] Extracting to ${destDir}...`);
406
- try {
407
- await execFileAsync("tar", [
408
- "-xf",
409
- archivePath,
410
- "-C",
411
- destDir
412
- ]);
413
- } catch (tarErr) {
414
- if (process.platform === "win32" && info.assetName.endsWith(".zip")) await execFileAsync("powershell.exe", [
415
- "-NoProfile",
416
- "-Command",
417
- `Expand-Archive -Path '${escapePowerShellSingleQuote(archivePath)}' -DestinationPath '${escapePowerShellSingleQuote(destDir)}' -Force`
418
- ]);
419
- else throw tarErr;
445
+ if (!response || !response.body) {
446
+ try {
447
+ fs.rmSync(tempDir, {
448
+ recursive: true,
449
+ force: true
450
+ });
451
+ } catch {}
452
+ throw lastErr || /* @__PURE__ */ new Error(`Failed to download ${url}`);
420
453
  }
421
454
  try {
422
- fs.unlinkSync(archivePath);
423
- } catch {}
424
- if (process.platform !== "win32") try {
425
- fs.chmodSync(binaryPath, 493);
426
- } catch {}
427
- if (!fs.existsSync(binaryPath)) throw new Error(`Failed to extract LuaLS binary to expected path: ${binaryPath}`);
428
- if (!options?.quiet) console.log(`[luals] Ready: ${binaryPath}`);
429
- return binaryPath;
455
+ const arrayBuffer = await response.arrayBuffer();
456
+ fs.writeFileSync(archivePath, Buffer.from(arrayBuffer));
457
+ if (!options?.quiet) console.log(`[luals] Extracting to ${destDir}...`);
458
+ try {
459
+ await execFileAsync("tar", [
460
+ "-xf",
461
+ archivePath,
462
+ "-C",
463
+ tempDir
464
+ ]);
465
+ } catch (tarErr) {
466
+ if (process.platform === "win32" && info.assetName.endsWith(".zip")) await execFileAsync("powershell.exe", [
467
+ "-NoProfile",
468
+ "-Command",
469
+ `Expand-Archive -Path '${escapePowerShellSingleQuote(archivePath)}' -DestinationPath '${escapePowerShellSingleQuote(tempDir)}' -Force`
470
+ ]);
471
+ else throw tarErr;
472
+ }
473
+ try {
474
+ fs.unlinkSync(archivePath);
475
+ } catch {}
476
+ const tempBinaryPath = path.join(tempDir, info.binaryRelativePath);
477
+ if (process.platform !== "win32") try {
478
+ fs.chmodSync(tempBinaryPath, 493);
479
+ } catch {}
480
+ if (!fs.existsSync(tempBinaryPath) || fs.statSync(tempBinaryPath).size < 1e5) throw new Error(`Failed to extract valid LuaLS binary to expected path: ${tempBinaryPath}`);
481
+ fs.writeFileSync(path.join(tempDir, ".complete"), resolvedVersion, "utf-8");
482
+ for (let attempt = 0; attempt < 5; attempt++) try {
483
+ fs.renameSync(tempDir, destDir);
484
+ break;
485
+ } catch (renameErr) {
486
+ if (fs.existsSync(binaryPath) && isBinaryValid(binaryPath)) {
487
+ try {
488
+ fs.rmSync(tempDir, {
489
+ recursive: true,
490
+ force: true
491
+ });
492
+ } catch {}
493
+ if (!options?.quiet) console.log(`[luals] Ready: ${binaryPath}`);
494
+ return binaryPath;
495
+ }
496
+ if (attempt < 4) await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
497
+ else throw renameErr;
498
+ }
499
+ if (!isBinaryValid(binaryPath)) throw new Error(`Extracted LuaLS binary at ${binaryPath} is invalid or non-functional.`);
500
+ if (!options?.quiet) console.log(`[luals] Ready: ${binaryPath}`);
501
+ return binaryPath;
502
+ } finally {
503
+ if (fs.existsSync(tempDir)) try {
504
+ fs.rmSync(tempDir, {
505
+ recursive: true,
506
+ force: true
507
+ });
508
+ } catch {}
509
+ }
430
510
  }
431
511
  async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
432
512
  if (process.env.LUALS_BIN && fs.existsSync(process.env.LUALS_BIN)) return process.env.LUALS_BIN;
433
513
  const resolvedVersion = await resolveLuaLSVersion(version);
434
514
  const info = getPlatformInfo(resolvedVersion);
435
515
  const bundledPath = path.join(getPackageRoot(), info.binaryRelativePath);
436
- if (fs.existsSync(bundledPath)) return bundledPath;
437
- const cachedPath = path.join(getCacheDir(resolvedVersion), info.binaryRelativePath);
438
- if (fs.existsSync(cachedPath)) return cachedPath;
516
+ if (fs.existsSync(bundledPath) && isBinaryValid(bundledPath)) return bundledPath;
517
+ const cachedDir = getCacheDir(resolvedVersion);
518
+ const cachedPath = path.join(cachedDir, info.binaryRelativePath);
519
+ const completeMarker = path.join(cachedDir, ".complete");
520
+ if (fs.existsSync(cachedPath)) {
521
+ if (isBinaryValid(cachedPath)) {
522
+ if (!fs.existsSync(completeMarker)) try {
523
+ fs.writeFileSync(completeMarker, resolvedVersion, "utf-8");
524
+ } catch {}
525
+ return cachedPath;
526
+ }
527
+ if (!options?.quiet) console.warn(`[luals] Cached LuaLS binary at ${cachedPath} is corrupted or incomplete. Repairing...`);
528
+ try {
529
+ fs.rmSync(cachedDir, {
530
+ recursive: true,
531
+ force: true
532
+ });
533
+ } catch {}
534
+ }
439
535
  try {
440
536
  const cmd = process.platform === "win32" ? "where.exe" : "which";
441
537
  const { stdout } = await execFileAsync(cmd, ["lua-language-server"]);
442
538
  const found = stdout.trim().split(/\r?\n/)[0];
443
- if (found && fs.existsSync(found)) return found;
539
+ if (found && fs.existsSync(found) && isBinaryValid(found)) return found;
444
540
  } catch {}
445
541
  return await downloadAndExtractLuaLS(resolvedVersion, void 0, options);
446
542
  }
@@ -485,8 +581,9 @@ async function runLuaLSCheck(targetPath, configPath, options) {
485
581
  } catch {}
486
582
  }
487
583
  if (!parseSucceeded) {
488
- if (execError) throw new Error(`LuaLS check failed to execute or produce diagnostic output: ${execError instanceof Error ? execError.message : String(execError)}`);
489
- throw new Error(`LuaLS check failed to produce diagnostic output at: ${checkOutPath}`);
584
+ const cacheHint = `(Cache location: ${getCacheDir()})`;
585
+ if (execError) throw new Error(`LuaLS check failed to execute or produce diagnostic output: ${execError instanceof Error ? execError.message : String(execError)}. ${cacheHint}`);
586
+ throw new Error(`LuaLS check failed to produce diagnostic output at: ${checkOutPath}. ${cacheHint}`);
490
587
  }
491
588
  if (targetFileOnly) {
492
589
  const filtered = {};
@@ -529,6 +626,7 @@ function countCheckedFiles(targetPath, configPath) {
529
626
  let ignoreDirs = [
530
627
  ".git",
531
628
  ".vscode",
629
+ ".nanos-lint",
532
630
  "node_modules"
533
631
  ];
534
632
  let excludePatterns = [];
@@ -3832,6 +3930,6 @@ if (isDirectExecution()) runCLI().then((code) => {
3832
3930
  process.exit(1);
3833
3931
  });
3834
3932
  //#endregion
3835
- export { parseJsonc as A, runLuaLSCheck as C, initWorkspace as D, getPackageRoot as E, stripJsonComments as M, fileUriToPath as N, loadConfigFile as O, resolveLuaLSVersion as S, getDefinitionsDir as T, escapePowerShellSingleQuote as _, formatGitHubAnnotations as a, resolveLatestLuaLSVersion as b, formatReport as c, pluralize as d, shouldEnableColor as f, downloadAndExtractLuaLS as g, countCheckedFiles as h, runCLI as i, resolveWorkspaceConfig as j, mergeConfigs as k, formatSeverityBadge as l, FALLBACK_LUALS_VERSION as m, createProgram as n, formatPretty as o, DEFAULT_LUALS_VERSION as p, isDirectExecution as r, formatProblemSummary as s, collectIgnorePatterns as t, getColors as u, getCacheDir as v, getDefaultTemplatePath as w, resolveLuaLSBinary as x, getPlatformInfo as y };
3933
+ export { mergeConfigs as A, resolveLuaLSVersion as C, getPackageRoot as D, getDefinitionsDir as E, resolveWorkspaceConfig as M, stripJsonComments as N, initWorkspace as O, fileUriToPath as P, resolveLuaLSBinary as S, getDefaultTemplatePath as T, escapePowerShellSingleQuote as _, formatGitHubAnnotations as a, isBinaryValid as b, formatReport as c, pluralize as d, shouldEnableColor as f, downloadAndExtractLuaLS as g, countCheckedFiles as h, runCLI as i, parseJsonc as j, loadConfigFile as k, formatSeverityBadge as l, FALLBACK_LUALS_VERSION as m, createProgram as n, formatPretty as o, DEFAULT_LUALS_VERSION as p, isDirectExecution as r, formatProblemSummary as s, collectIgnorePatterns as t, getColors as u, getCacheDir as v, runLuaLSCheck as w, resolveLatestLuaLSVersion as x, getPlatformInfo as y };
3836
3934
 
3837
- //# sourceMappingURL=cli-Dx-1zSOk.js.map
3935
+ //# sourceMappingURL=cli-DnmSA08r.js.map