nanos-lint 3.0.0 → 3.1.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.
@@ -1186,10 +1186,12 @@ function mergeConfigs(base, override = {}, definitionsPath = getDefaultAnnotatio
1186
1186
  const normalizedDefPath = resolvedPath.split(path.sep).join("/");
1187
1187
  const baseLibraries = base.workspace?.library ?? [];
1188
1188
  const overrideLibraries = override.workspace?.library ?? [];
1189
+ const depLibraries = (options?.dependencyLibraries ?? []).map((p) => p.replace(/\\/g, "/"));
1189
1190
  const librarySet = /* @__PURE__ */ new Set([
1190
1191
  normalizedDefPath,
1191
1192
  ...baseLibraries,
1192
- ...overrideLibraries
1193
+ ...overrideLibraries,
1194
+ ...depLibraries
1193
1195
  ]);
1194
1196
  const baseGlobals = base.diagnostics?.globals ?? [];
1195
1197
  const overrideGlobals = override.diagnostics?.globals ?? [];
@@ -1328,7 +1330,10 @@ function buildWorkspaceConfig(workspacePath, customConfigPath, options) {
1328
1330
  }
1329
1331
  cliIgnore = expanded;
1330
1332
  }
1331
- const merged = mergeConfigs(defaultTemplate, userConfig, activeAnnotationsPath, { cliIgnore });
1333
+ const merged = mergeConfigs(defaultTemplate, userConfig, activeAnnotationsPath, {
1334
+ cliIgnore,
1335
+ dependencyLibraries: options?.dependencyLibraries
1336
+ });
1332
1337
  if (!hasCliIgnore) {
1333
1338
  const resolvedTarget = path.resolve(workspacePath);
1334
1339
  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")))) {
@@ -1343,6 +1348,11 @@ function buildWorkspaceConfig(workspacePath, customConfigPath, options) {
1343
1348
  ])];
1344
1349
  }
1345
1350
  }
1351
+ if (options?.unrequestedExclusions && options.unrequestedExclusions.length > 0) {
1352
+ merged.files = merged.files ?? {};
1353
+ const existingExclude = merged.files.exclude ?? [];
1354
+ merged.files.exclude = [.../* @__PURE__ */ new Set([...existingExclude, ...options.unrequestedExclusions])];
1355
+ }
1346
1356
  return merged;
1347
1357
  }
1348
1358
  /**
@@ -4740,9 +4750,11 @@ var Ze = Object.assign(Je, {
4740
4750
  });
4741
4751
  Ze.glob = Ze;
4742
4752
  /** A lock file older than this, or owned by a dead process, is treated as abandoned. */
4743
- const DEFAULT_LOCK_STALE_MS = 12e4;
4753
+ const DEFAULT_LOCK_STALE_MS = 3e4;
4744
4754
  /** Upper bound for the polling interval, so a long wait stays responsive. */
4745
4755
  const MAX_LOCK_POLL_INTERVAL_MS = 500;
4756
+ /** Upper bound for the automatic heartbeat interval, keeping it responsive. */
4757
+ const MAX_LOCK_HEARTBEAT_INTERVAL_MS = 15e3;
4746
4758
  /** Default rename attempts used to absorb transient Windows file-lock errors. */
4747
4759
  const DEFAULT_RENAME_RETRIES = 5;
4748
4760
  /** Create failures that mean "the lock is there / being swapped", not "cannot lock". */
@@ -4788,18 +4800,26 @@ function readLockMetadata(lockPath) {
4788
4800
  return null;
4789
4801
  }
4790
4802
  }
4791
- /** Returns true when a lock was left behind by a dead owner or has expired. */
4792
- function isLockStale(lockPath, staleMs = DEFAULT_LOCK_STALE_MS) {
4793
- const metadata = readLockMetadata(lockPath);
4794
- if (typeof metadata?.pid === "number" && !isProcessAlive(metadata.pid)) return true;
4795
- const createdAt = typeof metadata?.createdAt === "number" ? metadata.createdAt : null;
4796
- if (createdAt !== null) return Date.now() - createdAt > staleMs;
4803
+ /** Returns the most recent activity timestamp (createdAt or mtime) for a lock file. */
4804
+ function readLockActiveTime(lockPath, metadata) {
4805
+ const meta = metadata !== void 0 ? metadata : readLockMetadata(lockPath);
4806
+ let mtimeMs = null;
4797
4807
  try {
4798
- return Date.now() - fs.statSync(lockPath).mtimeMs > staleMs;
4808
+ mtimeMs = fs.statSync(lockPath).mtimeMs;
4799
4809
  } catch (err) {
4800
4810
  logger.debug(`[lock] Could not stat lock file ${lockPath}: ${err instanceof Error ? err.message : String(err)}`);
4801
- return false;
4802
4811
  }
4812
+ const createdAt = typeof meta?.createdAt === "number" ? meta.createdAt : null;
4813
+ if (createdAt !== null && mtimeMs !== null) return Math.max(createdAt, mtimeMs);
4814
+ return createdAt ?? mtimeMs;
4815
+ }
4816
+ /** Returns true when a lock was left behind by a dead owner or has expired. */
4817
+ function isLockStale(lockPath, staleMs = DEFAULT_LOCK_STALE_MS) {
4818
+ const metadata = readLockMetadata(lockPath);
4819
+ if (typeof metadata?.pid === "number" && !isProcessAlive(metadata.pid)) return true;
4820
+ const activeTime = readLockActiveTime(lockPath, metadata);
4821
+ if (activeTime !== null) return Date.now() - activeTime > staleMs;
4822
+ return false;
4803
4823
  }
4804
4824
  /**
4805
4825
  * Atomically creates the lock file with `O_CREAT | O_EXCL` semantics, so exactly one
@@ -4831,14 +4851,9 @@ function tryCreateLockFile(lockPath, token) {
4831
4851
  }
4832
4852
  /** Returns the age of a lock file in milliseconds, from its metadata or its mtime. */
4833
4853
  function lockAgeMs(lockPath) {
4834
- const metadata = readLockMetadata(lockPath);
4835
- if (typeof metadata?.createdAt === "number") return Math.max(0, Date.now() - metadata.createdAt);
4836
- try {
4837
- return Math.max(0, Date.now() - fs.statSync(lockPath).mtimeMs);
4838
- } catch (err) {
4839
- logger.debug(`[lock] Could not read the age of ${lockPath}: ${err instanceof Error ? err.message : String(err)}`);
4840
- return Number.POSITIVE_INFINITY;
4841
- }
4854
+ const activeTime = readLockActiveTime(lockPath);
4855
+ if (activeTime !== null) return Math.max(0, Date.now() - activeTime);
4856
+ return Number.POSITIVE_INFINITY;
4842
4857
  }
4843
4858
  /** Confirms that a lock file still carries the token written by this process. */
4844
4859
  function ownsLockFile(lockPath, token) {
@@ -4878,7 +4893,7 @@ function nextPollDelay(attempt, baseMs) {
4878
4893
  */
4879
4894
  async function acquireFileLock(lockPath, options) {
4880
4895
  const timeoutMs = options.timeoutMs ?? 6e4;
4881
- const staleMs = options.staleMs ?? 12e4;
4896
+ const staleMs = options.staleMs ?? 3e4;
4882
4897
  const reclaimGraceMs = options.reclaimGraceMs ?? 1e3;
4883
4898
  const pollIntervalMs = options.pollIntervalMs ?? 25;
4884
4899
  const label = options.label ?? path.basename(lockPath);
@@ -4910,15 +4925,70 @@ function releaseFileLock(lockPath, token) {
4910
4925
  }
4911
4926
  }
4912
4927
  /**
4928
+ * Refreshes the active timestamp of a lock file if the caller still owns it.
4929
+ * Uses `fs.utimesSync` non-destructively so it never overwrites another owner's lock metadata.
4930
+ */
4931
+ function touchLockFile(lockPath, token) {
4932
+ if (readLockMetadata(lockPath)?.token !== token) {
4933
+ logger.debug(`[lock] Cannot touch lock at ${lockPath}: token mismatch or missing lock.`);
4934
+ return false;
4935
+ }
4936
+ try {
4937
+ const now = /* @__PURE__ */ new Date();
4938
+ fs.utimesSync(lockPath, now, now);
4939
+ return true;
4940
+ } catch (err) {
4941
+ logger.debug(`[lock] Could not touch lock at ${lockPath}: ${err instanceof Error ? err.message : String(err)}`);
4942
+ return false;
4943
+ }
4944
+ }
4945
+ /** Calculates the default heartbeat interval for a given staleness threshold. */
4946
+ function defaultHeartbeatInterval(staleMs) {
4947
+ if (staleMs <= 0) return 0;
4948
+ return Math.min(Math.max(1, Math.floor(staleMs / 4)), MAX_LOCK_HEARTBEAT_INTERVAL_MS);
4949
+ }
4950
+ /**
4913
4951
  * Runs `task` while holding an exclusive cross-process lock at `lockPath`, releasing it
4914
4952
  * in a `finally` block. Concurrent callers queue with jittered exponential backoff, so
4915
4953
  * parallel CI jobs and monorepo linters sharing one cache serialize instead of racing.
4916
4954
  */
4917
4955
  async function withFileLock(lockPath, task, options = {}) {
4918
- const token = await acquireFileLock(lockPath, options);
4956
+ const staleMs = options.staleMs ?? 3e4;
4957
+ const label = options.label ?? path.basename(lockPath);
4958
+ const token = await acquireFileLock(lockPath, {
4959
+ ...options,
4960
+ staleMs,
4961
+ label
4962
+ });
4963
+ const heartbeatIntervalMs = options.heartbeatIntervalMs ?? defaultHeartbeatInterval(staleMs);
4964
+ let heartbeatTimer;
4965
+ let hasWarnedFailure = false;
4966
+ if (heartbeatIntervalMs > 0) {
4967
+ heartbeatTimer = setInterval(() => {
4968
+ if (readLockMetadata(lockPath)?.token !== token) {
4969
+ if (heartbeatTimer !== void 0) {
4970
+ clearInterval(heartbeatTimer);
4971
+ heartbeatTimer = void 0;
4972
+ }
4973
+ logger.warn(`[lock] Lost ownership of ${label} lock at ${lockPath}; stopping heartbeat.`);
4974
+ return;
4975
+ }
4976
+ if (!touchLockFile(lockPath, token)) {
4977
+ if (!hasWarnedFailure) {
4978
+ hasWarnedFailure = true;
4979
+ logger.warn(`[lock] Failed to touch ${label} lock at ${lockPath}; heartbeat could not update timestamp.`);
4980
+ }
4981
+ }
4982
+ }, heartbeatIntervalMs);
4983
+ heartbeatTimer.unref();
4984
+ }
4919
4985
  try {
4920
4986
  return await task();
4921
4987
  } finally {
4988
+ if (heartbeatTimer !== void 0) {
4989
+ clearInterval(heartbeatTimer);
4990
+ heartbeatTimer = void 0;
4991
+ }
4922
4992
  releaseFileLock(lockPath, token);
4923
4993
  }
4924
4994
  }
@@ -6146,24 +6216,27 @@ async function downloadAndPromoteLuaLS(resolvedVersion, info, destDir, binaryPat
6146
6216
  try {
6147
6217
  const res = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
6148
6218
  if (res.url && !isAllowedDownloadUrl(res.url)) {
6149
- await res.body?.cancel();
6219
+ if (typeof res.body?.cancel === "function") try {
6220
+ await res.body.cancel();
6221
+ } catch (cancelErr) {}
6222
+ logger.warn(`[luals] Redirect to untrusted URL blocked: ${res.url}`);
6150
6223
  throw new LuaLSError(`Redirect to untrusted URL blocked: ${res.url}`, "ERR_LUALS_DOWNLOAD", "Download redirects must stay on allowlisted HTTPS GitHub hosts.");
6151
6224
  }
6152
6225
  if (res.ok && res.body) {
6153
6226
  response = res;
6154
6227
  break;
6155
6228
  }
6156
- await res.body?.cancel();
6229
+ if (typeof res.body?.cancel === "function") try {
6230
+ await res.body.cancel();
6231
+ } catch (cancelErr) {}
6157
6232
  lastErr = /* @__PURE__ */ new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
6158
6233
  } catch (err) {
6234
+ if (err instanceof LuaLSError) throw err;
6159
6235
  lastErr = err;
6160
6236
  }
6161
6237
  if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
6162
6238
  }
6163
- if (!response || !response.body) {
6164
- if (lastErr instanceof LuaLSError) throw lastErr;
6165
- throw new LuaLSError(`Failed to download LuaLS from ${url}${lastErr ? `: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}` : ""}`, "ERR_LUALS_DOWNLOAD", "Check your network connection or specify a custom binary with LUALS_BIN.", { cause: lastErr });
6166
- }
6239
+ if (!response || !response.body) throw new LuaLSError(`Failed to download LuaLS from ${url}${lastErr ? `: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}` : ""}`, "ERR_LUALS_DOWNLOAD", "Check your network connection or specify a custom binary with LUALS_BIN.", { cause: lastErr });
6167
6240
  const contentLengthHeader = response.headers?.get?.("content-length");
6168
6241
  if (contentLengthHeader) {
6169
6242
  const contentLength = parseInt(contentLengthHeader, 10);
@@ -6277,6 +6350,236 @@ async function downloadAndPromoteLuaLS(resolvedVersion, info, destDir, binaryPat
6277
6350
  }
6278
6351
  }
6279
6352
  //#endregion
6353
+ //#region src/target-resolver.ts
6354
+ /** Resolves the canonical filesystem path, falling back to original path on failure. */
6355
+ function getCanonicalPath(p) {
6356
+ try {
6357
+ return fs.realpathSync.native ? fs.realpathSync.native(p) : fs.realpathSync(p);
6358
+ } catch {
6359
+ return p;
6360
+ }
6361
+ }
6362
+ /** Determines whether a path points to the root of a filesystem volume. */
6363
+ function isFilesystemRoot(dir) {
6364
+ const stripped = dir.trim();
6365
+ if (stripped === "/" || stripped === "\\" || stripped === "") return true;
6366
+ if (/^[a-zA-Z]:[\\/]?$/.test(stripped)) return true;
6367
+ const norm = path.resolve(stripped);
6368
+ if (norm === "/" || norm === "\\") return true;
6369
+ return /^[a-zA-Z]:[\\/]?$/.test(norm);
6370
+ }
6371
+ /**
6372
+ * Computes the lowest common ancestor directory for a set of paths.
6373
+ * All paths must reside on the same filesystem root or volume.
6374
+ */
6375
+ function findCommonAncestorDirectory(paths) {
6376
+ if (paths.length === 0) return path.resolve(".");
6377
+ const resolved = paths.map((p) => {
6378
+ const abs = path.resolve(p);
6379
+ try {
6380
+ if (fs.existsSync(abs)) return fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);
6381
+ } catch (err) {}
6382
+ return path.dirname(abs);
6383
+ });
6384
+ if (resolved.length === 1 && resolved[0]) return resolved[0];
6385
+ const isWin = process.platform === "win32";
6386
+ const splitPaths = resolved.map((p) => path.resolve(p).split(path.sep));
6387
+ const first = splitPaths[0];
6388
+ if (!first || first.length === 0) return path.resolve(".");
6389
+ const firstRoot = first[0];
6390
+ if (!splitPaths.every((p) => {
6391
+ const rootSeg = p[0];
6392
+ if (rootSeg === void 0) return false;
6393
+ return isWin ? rootSeg.toLowerCase() === firstRoot?.toLowerCase() : rootSeg === firstRoot;
6394
+ }) || firstRoot === void 0) throw new ConfigError(`Cannot check paths across different root drives or volumes: ${paths.join(", ")}`, "ERR_MULTIPLE_ROOTS", "Ensure all checked paths are within the same workspace or project root.");
6395
+ const minLen = Math.min(...splitPaths.map((p) => p.length));
6396
+ let commonLen = 0;
6397
+ for (let i = 0; i < minLen; i++) {
6398
+ const segment = first[i];
6399
+ if (segment === void 0) break;
6400
+ if (!splitPaths.every((p) => {
6401
+ const seg = p[i];
6402
+ if (seg === void 0) return false;
6403
+ return isWin ? seg.toLowerCase() === segment.toLowerCase() : seg === segment;
6404
+ })) break;
6405
+ commonLen++;
6406
+ }
6407
+ if (commonLen === 0) throw new ConfigError(`Cannot check paths across different root drives or volumes: ${paths.join(", ")}`, "ERR_MULTIPLE_ROOTS", "Ensure all checked paths are within the same workspace or project root.");
6408
+ let commonPath = first.slice(0, commonLen).join(path.sep);
6409
+ if (!commonPath) commonPath = path.sep;
6410
+ if (isWin && /^[a-zA-Z]:$/.test(commonPath)) commonPath = `${commonPath}\\`;
6411
+ return path.resolve(commonPath);
6412
+ }
6413
+ /**
6414
+ * Searches upward from a starting directory to locate an enclosing project root
6415
+ * containing `.luarc.json`. Returns the start directory if not found.
6416
+ */
6417
+ function findProjectRoot(startDir) {
6418
+ let curr = path.resolve(startDir);
6419
+ while (true) {
6420
+ if (fs.existsSync(path.join(curr, ".luarc.json"))) return curr;
6421
+ if (fs.existsSync(path.join(curr, ".git"))) break;
6422
+ const parent = path.dirname(curr);
6423
+ if (parent === curr) break;
6424
+ curr = parent;
6425
+ }
6426
+ return startDir;
6427
+ }
6428
+ /**
6429
+ * Characters the LuaLS glob matcher treats as syntax.
6430
+ *
6431
+ * LuaLS compiles `files.exclude` with `glob.gitignore` (see its
6432
+ * `script/glob/glob.lua`), whose grammar only understands backslash escapes:
6433
+ * bracket expressions such as `[[]` are parsed as character ranges and match
6434
+ * nothing useful. Escaping is therefore done with `\`.
6435
+ */
6436
+ const GLOB_METACHARACTERS = /* @__PURE__ */ new Set([
6437
+ "*",
6438
+ "?",
6439
+ "[",
6440
+ "]",
6441
+ "{",
6442
+ "}",
6443
+ ",",
6444
+ "\\"
6445
+ ]);
6446
+ /**
6447
+ * Escapes the glob metacharacters of a single path segment so it matches literally.
6448
+ *
6449
+ * Note: `normalizePattern()` in `src/luals/files.ts` rewrites backslashes to path
6450
+ * separators when counting checked files, so these escapes only reach LuaLS. That is
6451
+ * harmless: the counted file set is filtered by the requested target paths anyway.
6452
+ */
6453
+ function escapeGlobSegment(segment) {
6454
+ let escaped = "";
6455
+ for (const char of segment) escaped += GLOB_METACHARACTERS.has(char) ? `\\${char}` : char;
6456
+ return escaped;
6457
+ }
6458
+ /** Escapes glob metacharacters in every segment of a slash-separated relative path. */
6459
+ function escapeGlobPath(relativePath) {
6460
+ return relativePath.split("/").map(escapeGlobSegment).join("/");
6461
+ }
6462
+ /**
6463
+ * Computes glob exclusion patterns for directories and files under workspace root
6464
+ * that are completely outside the requested target paths.
6465
+ */
6466
+ function computeUnrequestedExclusions(root, targetPaths) {
6467
+ const resolvedRoot = path.resolve(root);
6468
+ if (targetPaths.length === 0 || targetPaths.some((t) => t === "." || t === "" || path.resolve(resolvedRoot, t) === resolvedRoot)) return [];
6469
+ const isWin = process.platform === "win32";
6470
+ const canonicalRoot = getCanonicalPath(resolvedRoot);
6471
+ const relTargets = targetPaths.map((t) => {
6472
+ const canonical = getCanonicalPath(path.isAbsolute(t) ? t : path.resolve(canonicalRoot, t));
6473
+ const rel = path.relative(canonicalRoot, canonical).replace(/\\/g, "/");
6474
+ return isWin ? rel.toLowerCase() : rel;
6475
+ }).filter((rel) => !rel.startsWith("../") && rel !== "..");
6476
+ if (relTargets.length === 0 || relTargets.includes("") || relTargets.includes(".")) return [];
6477
+ const exclusions = [];
6478
+ /** Recursively scans directory entries to identify unrequested paths. */
6479
+ function scanDir(dir, relPrefix) {
6480
+ let entries;
6481
+ try {
6482
+ entries = fs.readdirSync(dir, { withFileTypes: true });
6483
+ } catch {
6484
+ return;
6485
+ }
6486
+ for (const entry of entries) {
6487
+ const entryRel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
6488
+ const normEntry = isWin ? entryRel.toLowerCase() : entryRel;
6489
+ let isExactTarget = false;
6490
+ let isAncestorOfTarget = false;
6491
+ let isDescendantOfTarget = false;
6492
+ for (const target of relTargets) {
6493
+ if (normEntry === target) {
6494
+ isExactTarget = true;
6495
+ break;
6496
+ }
6497
+ if (target.startsWith(`${normEntry}/`)) {
6498
+ isAncestorOfTarget = true;
6499
+ break;
6500
+ }
6501
+ if (normEntry.startsWith(`${target}/`)) {
6502
+ isDescendantOfTarget = true;
6503
+ break;
6504
+ }
6505
+ }
6506
+ if (isExactTarget || isDescendantOfTarget) continue;
6507
+ if (isAncestorOfTarget) {
6508
+ if (entry.isDirectory()) scanDir(path.join(dir, entry.name), entryRel);
6509
+ continue;
6510
+ }
6511
+ if (entry.isDirectory()) exclusions.push(`${escapeGlobPath(entryRel)}/**`);
6512
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".lua")) exclusions.push(escapeGlobPath(entryRel));
6513
+ }
6514
+ }
6515
+ scanDir(canonicalRoot, "");
6516
+ return exclusions;
6517
+ }
6518
+ /** Determines whether a workspace-relative file path matches any of the target paths. */
6519
+ function matchesTargetPaths(relativePath, root, targetPaths) {
6520
+ if (targetPaths.length === 0 || targetPaths.length === 1 && (targetPaths[0] === "." || targetPaths[0] === "")) return true;
6521
+ const isWin = process.platform === "win32";
6522
+ const canonicalRoot = getCanonicalPath(root);
6523
+ const normRel = isWin ? relativePath.replace(/\\/g, "/").toLowerCase() : relativePath.replace(/\\/g, "/");
6524
+ for (const rawTarget of targetPaths) {
6525
+ const fromCwd = path.resolve(rawTarget);
6526
+ const canonicalTarget = getCanonicalPath(fs.existsSync(fromCwd) ? fromCwd : path.isAbsolute(rawTarget) ? rawTarget : path.resolve(canonicalRoot, rawTarget));
6527
+ let relTarget = path.relative(canonicalRoot, canonicalTarget).replace(/\\/g, "/");
6528
+ relTarget = stripTrailingSlashes(relTarget).replace(/^\.\//, "");
6529
+ const normTarget = isWin ? relTarget.toLowerCase() : relTarget;
6530
+ if (normTarget === "" || normTarget === ".") return true;
6531
+ if (normRel === normTarget || normRel.startsWith(`${normTarget}/`)) return true;
6532
+ }
6533
+ return false;
6534
+ }
6535
+ /** Keeps only diagnostics for files that match the requested target paths. */
6536
+ function filterReportByTargetPaths(report, root, targetPaths) {
6537
+ if (targetPaths.length === 0 || targetPaths.length === 1 && (targetPaths[0] === "." || targetPaths[0] === "")) return report;
6538
+ const filtered = {};
6539
+ const canonicalRoot = getCanonicalPath(root);
6540
+ for (const [uri, diags] of Object.entries(report)) {
6541
+ const filePath = fileUriToPath(uri);
6542
+ const canonicalFile = getCanonicalPath(path.resolve(filePath));
6543
+ if (matchesTargetPaths(path.relative(canonicalRoot, canonicalFile).replace(/\\/g, "/"), canonicalRoot, targetPaths)) filtered[uri] = diags;
6544
+ }
6545
+ return filtered;
6546
+ }
6547
+ /** Validates and resolves CLI target paths into a common root directory and target list. */
6548
+ function resolveCheckTargets(rawPaths) {
6549
+ const paths = rawPaths && rawPaths.length > 0 ? rawPaths : ["."];
6550
+ const canonicalTargets = paths.map((targetPath) => {
6551
+ const resolved = path.resolve(targetPath);
6552
+ if (!fs.existsSync(resolved)) throw new LuaLSError(`Target path does not exist: ${targetPath}`, "ERR_TARGET_NOT_FOUND", "Verify that the target path exists and is accessible.");
6553
+ return getCanonicalPath(resolved);
6554
+ });
6555
+ const first = paths[0];
6556
+ if (paths.length === 1 && first) {
6557
+ const canonicalFirst = canonicalTargets[0];
6558
+ const startDir = fs.statSync(canonicalFirst).isFile() ? path.dirname(canonicalFirst) : canonicalFirst;
6559
+ const projRoot = findProjectRoot(startDir);
6560
+ if (projRoot !== startDir) {
6561
+ if (isFilesystemRoot(projRoot)) throw new ConfigError(`Cannot check targets across the root filesystem (${projRoot}): checked paths must share a common project directory.`, "ERR_ROOT_ANCESTOR", "Ensure all checked paths reside within a common project directory.");
6562
+ logger.debug(`[targets] Using discovered project root "${projRoot}" for "${first}".`);
6563
+ return {
6564
+ rootPath: projRoot,
6565
+ targetPaths: canonicalTargets
6566
+ };
6567
+ }
6568
+ return {
6569
+ rootPath: first,
6570
+ targetPaths: canonicalTargets
6571
+ };
6572
+ }
6573
+ const ancestor = findCommonAncestorDirectory(canonicalTargets);
6574
+ if (isFilesystemRoot(ancestor)) throw new ConfigError(`Cannot check targets across the root filesystem (${ancestor}): checked paths must share a common project directory.`, "ERR_ROOT_ANCESTOR", "Ensure all checked paths reside within a common project directory.");
6575
+ const rootPath = findProjectRoot(ancestor);
6576
+ if (rootPath !== ancestor) logger.debug(`[targets] Using discovered project root "${rootPath}" for "${ancestor}".`);
6577
+ return {
6578
+ rootPath,
6579
+ targetPaths: canonicalTargets
6580
+ };
6581
+ }
6582
+ //#endregion
6280
6583
  //#region src/luals/files.ts
6281
6584
  /** Directories skipped unless the workspace config overrides `workspace.ignoreDir`. */
6282
6585
  const DEFAULT_IGNORE_DIRS = [
@@ -6400,7 +6703,7 @@ function resolveTargetPath(targetPath) {
6400
6703
  * honoring `workspace.ignoreDir` and `files.exclude` (#27). Absolute paths are returned
6401
6704
  * for single-file targets, and an empty list is returned when the walk fails.
6402
6705
  */
6403
- function listCheckedFiles(targetPath, configPath) {
6706
+ function listCheckedFiles(targetPath, configPath, targetPaths) {
6404
6707
  const absPath = resolveTargetPath(targetPath);
6405
6708
  if (!absPath) return [];
6406
6709
  if (fs.statSync(absPath).isFile()) return absPath.toLowerCase().endsWith(".lua") ? [absPath] : [];
@@ -6415,7 +6718,7 @@ function listCheckedFiles(targetPath, configPath) {
6415
6718
  }
6416
6719
  const ignore = [...toIgnorePatterns(ignoreDirs), ...toIgnorePatterns(excludePatterns)];
6417
6720
  try {
6418
- return ts(LUA_FILE_PATTERN, {
6721
+ const files = ts(LUA_FILE_PATTERN, {
6419
6722
  cwd: absPath,
6420
6723
  ignore,
6421
6724
  dot: true,
@@ -6423,14 +6726,16 @@ function listCheckedFiles(targetPath, configPath) {
6423
6726
  follow: false,
6424
6727
  withFileTypes: true
6425
6728
  }).filter((entry) => entry.isFile()).map((entry) => entry.relativePosix());
6729
+ if (targetPaths && targetPaths.length > 0) return files.filter((f) => matchesTargetPaths(f, absPath, targetPaths));
6730
+ return files;
6426
6731
  } catch (err) {
6427
6732
  logger.warn(`[luals] Failed to walk ${absPath} while listing checked files: ${err instanceof Error ? err.message : String(err)}`);
6428
6733
  return [];
6429
6734
  }
6430
6735
  }
6431
6736
  /** Counts candidate Lua files within targetPath, taking workspace ignoreDir and files.exclude into account (#27). */
6432
- function countCheckedFiles(targetPath, configPath) {
6433
- return listCheckedFiles(targetPath, configPath).length;
6737
+ function countCheckedFiles(targetPath, configPath, targetPaths) {
6738
+ return listCheckedFiles(targetPath, configPath, targetPaths).length;
6434
6739
  }
6435
6740
  //#endregion
6436
6741
  //#region src/luals/runner.ts
@@ -6642,7 +6947,7 @@ async function runLuaLSCheck(targetPath, configPath, options) {
6642
6947
  const normTarget = normalizeComparable(targetFileOnly);
6643
6948
  for (const [rawUri, diags] of Object.entries(diagnostics)) if (normalizeComparable(fileUriToPath(rawUri)) === normTarget) filtered[rawUri] = diags;
6644
6949
  diagnostics = filtered;
6645
- }
6950
+ } else if (options.paths && options.paths.length > 0) diagnostics = filterReportByTargetPaths(diagnostics, checkDir, options.paths);
6646
6951
  let totalProblems = 0;
6647
6952
  let totalErrors = 0;
6648
6953
  let totalWarnings = 0;
@@ -6654,7 +6959,7 @@ async function runLuaLSCheck(targetPath, configPath, options) {
6654
6959
  else if (d.severity === 2) totalWarnings += 1;
6655
6960
  }
6656
6961
  const passed = totalProblems === 0;
6657
- const filesChecked = countCheckedFiles(targetPath, configPath);
6962
+ const filesChecked = countCheckedFiles(targetPath, configPath, options.paths);
6658
6963
  return {
6659
6964
  passed,
6660
6965
  totalProblems,
@@ -6666,6 +6971,179 @@ async function runLuaLSCheck(targetPath, configPath, options) {
6666
6971
  };
6667
6972
  }
6668
6973
  //#endregion
6974
+ //#region src/deps.ts
6975
+ /** Accumulates repeatable --dep arguments. */
6976
+ function collectDeps(val, prev) {
6977
+ const trimmed = val.trim();
6978
+ if (!trimmed) return prev ?? [];
6979
+ return (prev ?? []).concat(trimmed);
6980
+ }
6981
+ /** Normalizes a filesystem path to forward slashes for LuaLS compatibility. */
6982
+ function normalizeSlash(p) {
6983
+ return p.replace(/\\/g, "/");
6984
+ }
6985
+ /**
6986
+ * Resolves package dependencies declared in .luarc.json (nanos.deps) or via CLI (--dep),
6987
+ * resolving transitive dependencies with cycle detection and partitioning them by realm.
6988
+ */
6989
+ function resolvePackageDependencies(root, userConfig, cliDeps) {
6990
+ const visited = /* @__PURE__ */ new Set();
6991
+ const queue = [];
6992
+ if (cliDeps && cliDeps.length > 0) {
6993
+ for (const dep of cliDeps) if (typeof dep === "string" && dep.trim()) queue.push({
6994
+ rawPath: dep.trim(),
6995
+ fromDir: process.cwd()
6996
+ });
6997
+ }
6998
+ const rawConfigDeps = userConfig.nanos?.deps;
6999
+ if (rawConfigDeps !== void 0) {
7000
+ if (Array.isArray(rawConfigDeps)) for (const dep of rawConfigDeps) if (typeof dep === "string" && dep.trim()) queue.push({
7001
+ rawPath: dep.trim(),
7002
+ fromDir: root
7003
+ });
7004
+ else logger.warn(`[deps] Skipping invalid nanos.deps entry: expected a string path.`);
7005
+ else logger.warn(`[deps] Ignoring nanos.deps: expected an array of path strings.`);
7006
+ }
7007
+ const resolvedFiles = [];
7008
+ const resolvedDirs = [];
7009
+ const depConfigMap = /* @__PURE__ */ new Map();
7010
+ const isWin = process.platform === "win32";
7011
+ while (queue.length > 0) {
7012
+ const item = queue.shift();
7013
+ if (!item) continue;
7014
+ const { rawPath, fromDir } = item;
7015
+ const absPath = path.resolve(fromDir, rawPath);
7016
+ if (!fs.existsSync(absPath)) {
7017
+ logger.warn(`[deps] Dependency path not found: "${rawPath}" (skipping).`);
7018
+ continue;
7019
+ }
7020
+ let canonical;
7021
+ try {
7022
+ canonical = fs.realpathSync.native(absPath);
7023
+ } catch (err) {
7024
+ try {
7025
+ canonical = fs.realpathSync(absPath);
7026
+ } catch (innerErr) {
7027
+ canonical = absPath;
7028
+ }
7029
+ }
7030
+ const cycleKey = isWin ? canonical.toLowerCase() : canonical;
7031
+ if (visited.has(cycleKey)) continue;
7032
+ visited.add(cycleKey);
7033
+ let stat;
7034
+ try {
7035
+ stat = fs.statSync(canonical);
7036
+ } catch (err) {
7037
+ continue;
7038
+ }
7039
+ if (stat.isFile()) {
7040
+ if (canonical.toLowerCase().endsWith(".lua")) resolvedFiles.push(normalizeSlash(canonical));
7041
+ else logger.warn(`[deps] Skipping non-Lua dependency file: "${rawPath}".`);
7042
+ continue;
7043
+ }
7044
+ if (stat.isDirectory()) {
7045
+ resolvedDirs.push(canonical);
7046
+ const candidateLuarc = path.join(canonical, ".luarc.json");
7047
+ if (fs.existsSync(candidateLuarc)) try {
7048
+ const depConfig = loadUserConfig(canonical);
7049
+ depConfigMap.set(canonical, depConfig);
7050
+ const transDeps = depConfig.nanos?.deps;
7051
+ if (transDeps !== void 0) {
7052
+ if (Array.isArray(transDeps)) for (const trans of transDeps) if (typeof trans === "string" && trans.trim()) queue.push({
7053
+ rawPath: trans.trim(),
7054
+ fromDir: canonical
7055
+ });
7056
+ else logger.warn(`[deps] Skipping invalid nanos.deps entry in "${canonical}": expected a string path.`);
7057
+ else logger.warn(`[deps] Ignoring nanos.deps in "${canonical}": expected an array of path strings.`);
7058
+ }
7059
+ } catch (err) {
7060
+ logger.warn(`[deps] Failed to parse transitive dependency configuration at "${candidateLuarc}": ${err instanceof Error ? err.message : String(err)}`);
7061
+ }
7062
+ }
7063
+ }
7064
+ const serverSet = /* @__PURE__ */ new Set();
7065
+ const clientSet = /* @__PURE__ */ new Set();
7066
+ const sharedSet = /* @__PURE__ */ new Set();
7067
+ const allSet = /* @__PURE__ */ new Set();
7068
+ for (const file of resolvedFiles) {
7069
+ serverSet.add(file);
7070
+ clientSet.add(file);
7071
+ sharedSet.add(file);
7072
+ allSet.add(file);
7073
+ }
7074
+ for (const depDir of resolvedDirs) {
7075
+ const normDepDir = normalizeSlash(depDir);
7076
+ allSet.add(normDepDir);
7077
+ let depConfig = depConfigMap.get(depDir);
7078
+ if (!depConfig) try {
7079
+ depConfig = loadUserConfig(depDir);
7080
+ } catch (err) {
7081
+ depConfig = {};
7082
+ }
7083
+ const { enabled, mappings } = resolveRealmMappings(depConfig);
7084
+ if (!enabled) {
7085
+ serverSet.add(normDepDir);
7086
+ clientSet.add(normDepDir);
7087
+ sharedSet.add(normDepDir);
7088
+ continue;
7089
+ }
7090
+ const serverFolder = path.join(depDir, "Server");
7091
+ const clientFolder = path.join(depDir, "Client");
7092
+ const sharedFolder = path.join(depDir, "Shared");
7093
+ const hasServer = fs.existsSync(serverFolder);
7094
+ const hasClient = fs.existsSync(clientFolder);
7095
+ const hasShared = fs.existsSync(sharedFolder);
7096
+ if (hasServer || hasClient || hasShared) {
7097
+ if (hasServer) serverSet.add(normalizeSlash(serverFolder));
7098
+ if (hasClient) clientSet.add(normalizeSlash(clientFolder));
7099
+ if (hasShared) {
7100
+ const normShared = normalizeSlash(sharedFolder);
7101
+ serverSet.add(normShared);
7102
+ clientSet.add(normShared);
7103
+ sharedSet.add(normShared);
7104
+ }
7105
+ try {
7106
+ const entries = fs.readdirSync(depDir, { withFileTypes: true });
7107
+ for (const entry of entries) if (entry.isFile() && entry.name.toLowerCase().endsWith(".lua")) {
7108
+ const rootFile = normalizeSlash(path.join(depDir, entry.name));
7109
+ serverSet.add(rootFile);
7110
+ clientSet.add(rootFile);
7111
+ sharedSet.add(rootFile);
7112
+ }
7113
+ } catch (err) {}
7114
+ continue;
7115
+ }
7116
+ if (depConfig.nanos?.realms) {
7117
+ let matchedAnyCustom = false;
7118
+ for (const { pattern, realm } of mappings) {
7119
+ const baseFolder = pattern.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "");
7120
+ const targetPath = path.join(depDir, baseFolder);
7121
+ if (fs.existsSync(targetPath)) {
7122
+ matchedAnyCustom = true;
7123
+ const norm = normalizeSlash(targetPath);
7124
+ if (realm === "server") serverSet.add(norm);
7125
+ else if (realm === "client") clientSet.add(norm);
7126
+ else {
7127
+ serverSet.add(norm);
7128
+ clientSet.add(norm);
7129
+ sharedSet.add(norm);
7130
+ }
7131
+ }
7132
+ }
7133
+ if (matchedAnyCustom) continue;
7134
+ }
7135
+ serverSet.add(normDepDir);
7136
+ clientSet.add(normDepDir);
7137
+ sharedSet.add(normDepDir);
7138
+ }
7139
+ return {
7140
+ server: Array.from(serverSet),
7141
+ client: Array.from(clientSet),
7142
+ shared: Array.from(sharedSet),
7143
+ all: Array.from(allSet)
7144
+ };
7145
+ }
7146
+ //#endregion
6669
7147
  //#region src/realms.ts
6670
7148
  /** Normalizes a workspace-relative path for case-insensitive comparison on Windows. */
6671
7149
  function normalizeRelative(candidate) {
@@ -6723,13 +7201,17 @@ function selectedRealms(selection) {
6723
7201
  return ["shared", selection];
6724
7202
  }
6725
7203
  /** Builds a pass config from the merged base config by swapping library and exclusions. */
6726
- function buildPassConfig(baseConfig, realm, annotationsPath, realmAnnotationPath, excludedPatterns) {
7204
+ function buildPassConfig(baseConfig, realm, annotationsPath, realmAnnotationPath, excludedPatterns, dependencyLibraries = []) {
6727
7205
  const config = JSON.parse(JSON.stringify(baseConfig));
6728
7206
  const normalizedFull = annotationsPath.replace(/\\/g, "/");
6729
7207
  const libraries = (config.workspace?.library ?? []).filter((entry) => entry.replace(/\\/g, "/") !== normalizedFull);
6730
7208
  config.workspace = {
6731
7209
  ...config.workspace ?? {},
6732
- library: [realmAnnotationPath, ...libraries]
7210
+ library: Array.from(/* @__PURE__ */ new Set([
7211
+ realmAnnotationPath,
7212
+ ...libraries,
7213
+ ...dependencyLibraries
7214
+ ]))
6733
7215
  };
6734
7216
  if (realm !== "shared" && excludedPatterns.length > 0) config.files = {
6735
7217
  ...config.files ?? {},
@@ -6751,19 +7233,25 @@ function planRealmCheck(options) {
6751
7233
  logger.debug("[realms] Realm mapping disabled; running a single standard pass.");
6752
7234
  return null;
6753
7235
  }
7236
+ const unrequestedExclusions = options.targetPaths ? computeUnrequestedExclusions(resolvedTarget, options.targetPaths) : [];
6754
7237
  const baseConfig = buildWorkspaceConfig(resolvedTarget, options.customConfigPath, {
6755
7238
  ignore: options.ignore,
6756
- annotationsPath
7239
+ annotationsPath,
7240
+ unrequestedExclusions
6757
7241
  });
6758
7242
  const tempConfigs = [];
6759
7243
  const baseConfigPath = writeTempConfig(baseConfig);
6760
7244
  tempConfigs.push(baseConfigPath);
6761
7245
  try {
6762
7246
  const realmFiles = collectRealmFiles(resolvedTarget, mappings, listCheckedFiles(resolvedTarget, baseConfigPath));
7247
+ const isTargetFile = (file) => {
7248
+ if (!options.targetPaths || options.targetPaths.length === 0) return true;
7249
+ return matchesTargetPaths(file, resolvedTarget, options.targetPaths);
7250
+ };
6763
7251
  const realmReportFiles = {
6764
- client: new Set(realmFiles.client.map(normalizeRelative)),
6765
- server: new Set(realmFiles.server.map(normalizeRelative)),
6766
- shared: new Set([...realmFiles.shared, ...realmFiles.unmatched].map(normalizeRelative))
7252
+ client: new Set(realmFiles.client.filter(isTargetFile).map(normalizeRelative)),
7253
+ server: new Set(realmFiles.server.filter(isTargetFile).map(normalizeRelative)),
7254
+ shared: new Set([...realmFiles.shared, ...realmFiles.unmatched].filter(isTargetFile).map(normalizeRelative))
6767
7255
  };
6768
7256
  const hasRealmFolders = realmFiles.client.length > 0 || realmFiles.server.length > 0 || realmFiles.shared.length > 0;
6769
7257
  if (selection === "all" && !hasRealmFolders) {
@@ -6772,14 +7260,22 @@ function planRealmCheck(options) {
6772
7260
  return null;
6773
7261
  }
6774
7262
  const wanted = selectedRealms(selection).filter((realm) => realmReportFiles[realm].size > 0);
7263
+ if (wanted.length === 0) {
7264
+ if (selection !== "all") logger.warn(`[realms] No target files match the requested realm "${selection}"; falling back to a standard check.`);
7265
+ else logger.debug("[realms] No files to report for selected realm pass; running a single standard pass.");
7266
+ for (const tempConfig of tempConfigs) removeTempConfig(tempConfig);
7267
+ return null;
7268
+ }
6775
7269
  const derived = wanted.some((realm) => realm !== "shared") ? deriveRealmAnnotationFiles(annotationsPath) : {
6776
7270
  client: annotationsPath,
6777
7271
  server: annotationsPath
6778
7272
  };
7273
+ const resolvedDeps = resolvePackageDependencies(resolvedTarget, userConfig, options.cliDeps);
6779
7274
  const passes = [];
6780
7275
  for (const realm of wanted) {
6781
7276
  const excluded = mappings.filter((mapping) => mapping.realm !== realm && mapping.realm !== "shared").map((mapping) => mapping.pattern);
6782
- const configPath = writeTempConfig(buildPassConfig(baseConfig, realm, annotationsPath, realm === "client" ? derived.client : realm === "server" ? derived.server : annotationsPath, excluded));
7277
+ const depLibraries = realm === "server" ? resolvedDeps.server : realm === "client" ? resolvedDeps.client : resolvedDeps.shared;
7278
+ const configPath = writeTempConfig(buildPassConfig(baseConfig, realm, annotationsPath, realm === "client" ? derived.client : realm === "server" ? derived.server : annotationsPath, excluded, depLibraries));
6783
7279
  tempConfigs.push(configPath);
6784
7280
  passes.push({
6785
7281
  realm,
@@ -6810,9 +7306,15 @@ function removeTempConfig(configPath) {
6810
7306
  /** Keeps only the diagnostics of files owned by the pass. */
6811
7307
  function filterReportByFiles(report, root, reportFiles) {
6812
7308
  const filtered = {};
7309
+ const canonicalRoot = getCanonicalPath(root);
6813
7310
  for (const [uri, diagnostics] of Object.entries(report)) {
6814
7311
  const filePath = fileUriToPath(uri);
6815
- const relative = normalizeRelative(path.relative(root, path.resolve(filePath)));
7312
+ const resolvedFile = path.resolve(filePath);
7313
+ let relative = normalizeRelative(path.relative(root, resolvedFile));
7314
+ if (!reportFiles.has(relative)) {
7315
+ const canonicalFile = getCanonicalPath(resolvedFile);
7316
+ relative = normalizeRelative(path.relative(canonicalRoot, canonicalFile));
7317
+ }
6816
7318
  if (reportFiles.has(relative)) filtered[uri] = diagnostics;
6817
7319
  }
6818
7320
  return filtered;
@@ -10388,7 +10890,7 @@ function createProgram(options) {
10388
10890
  writeOut: (str) => console.log(str.trimEnd()),
10389
10891
  writeErr: (str) => logger.error(str.trimEnd())
10390
10892
  });
10391
- program.command("check [path]", { isDefault: true }).description("Check a workspace or Lua file (default command)").addOption(new Option("--checklevel <level>", "Minimum diagnostic level: Error, Warning, Information, Hint").choices([
10893
+ program.command("check [paths...]", { isDefault: true }).description("Check workspace files or directories (default command)").addOption(new Option("--checklevel <level>", "Minimum diagnostic level: Error, Warning, Information, Hint").choices([
10392
10894
  "Error",
10393
10895
  "Warning",
10394
10896
  "Information",
@@ -10397,7 +10899,7 @@ function createProgram(options) {
10397
10899
  "pretty",
10398
10900
  "json",
10399
10901
  "github"
10400
- ])).option("-i, --ignore <pattern>", "Files or directories to ignore (supports glob patterns, repeatable)", collectIgnorePatterns).option("--luals-version <ver>", `Version of LuaLS to use (default: ${DEFAULT_LUALS_VERSION})`, DEFAULT_LUALS_VERSION).option("--no-fail", "Do not exit with code 1 if diagnostics are found").addOption(new Option("-l, --log-level <level>", "Logging level: error, warn, info, debug, silent").choices([
10902
+ ])).option("-i, --ignore <pattern>", "Files or directories to ignore (supports glob patterns, repeatable)", collectIgnorePatterns).option("-d, --dep <path>", "Path to package dependency or Lua definition file (repeatable)", collectDeps).option("--luals-version <ver>", `Version of LuaLS to use (default: ${DEFAULT_LUALS_VERSION})`, DEFAULT_LUALS_VERSION).option("--no-fail", "Do not exit with code 1 if diagnostics are found").addOption(new Option("-l, --log-level <level>", "Logging level: error, warn, info, debug, silent").choices([
10401
10903
  "error",
10402
10904
  "warn",
10403
10905
  "info",
@@ -10408,44 +10910,54 @@ function createProgram(options) {
10408
10910
  "client",
10409
10911
  "server",
10410
10912
  "shared"
10411
- ]).default("all")).action(async (targetPath = ".", opts) => {
10913
+ ]).default("all")).action(async (targetPaths = ["."], opts) => {
10412
10914
  if (opts.logLevel && isValidLogLevel(opts.logLevel)) logger.setLevel(opts.logLevel);
10915
+ const { rootPath, targetPaths: canonicalTargets } = resolveCheckTargets(targetPaths && targetPaths.length > 0 ? targetPaths : ["."]);
10413
10916
  const format = opts.github ? "github" : opts.format || (process.env.GITHUB_ACTIONS ? "github" : "pretty");
10414
10917
  const checkOptions = {
10415
- path: targetPath,
10918
+ path: rootPath,
10919
+ paths: canonicalTargets,
10416
10920
  configpath: opts.config,
10417
10921
  checklevel: opts.checklevel,
10418
10922
  format,
10419
10923
  lualsVersion: opts.lualsVersion,
10420
10924
  failOnError: opts.fail !== false,
10421
- ignore: opts.ignore
10925
+ ignore: opts.ignore,
10926
+ deps: opts.dep
10422
10927
  };
10423
10928
  if (opts.config) {
10424
10929
  const resolvedConfig = path.resolve(opts.config);
10425
10930
  if (!fs.existsSync(resolvedConfig)) throw new ConfigError(`Configuration file not found: ${resolvedConfig}`, "ERR_CONFIG_NOT_FOUND", "Verify the path passed to --config exists and is readable.");
10426
10931
  }
10427
10932
  const annotationsPath = await resolveAnnotations({ customPath: opts.annotations });
10933
+ const userConfig = loadUserConfig(rootPath, checkOptions.configpath);
10428
10934
  const realmPlan = planRealmCheck({
10429
- targetPath,
10430
- userConfig: loadUserConfig(targetPath, checkOptions.configpath),
10935
+ targetPath: rootPath,
10936
+ userConfig,
10431
10937
  selection: opts.realm ?? "all",
10432
10938
  annotationsPath,
10433
10939
  customConfigPath: checkOptions.configpath,
10434
- ignore: checkOptions.ignore
10940
+ ignore: checkOptions.ignore,
10941
+ targetPaths: canonicalTargets,
10942
+ cliDeps: checkOptions.deps
10435
10943
  });
10436
10944
  let result;
10437
10945
  if (realmPlan) try {
10438
- result = await runRealmAwareCheck(realmPlan, targetPath, checkOptions);
10946
+ result = await runRealmAwareCheck(realmPlan, rootPath, checkOptions);
10439
10947
  } finally {
10440
10948
  realmPlan.cleanup();
10441
10949
  }
10442
10950
  else {
10443
- const resolved = resolveWorkspaceConfig(targetPath, checkOptions.configpath, {
10951
+ const resolvedDeps = resolvePackageDependencies(rootPath, userConfig, checkOptions.deps);
10952
+ const unrequestedExclusions = computeUnrequestedExclusions(rootPath, canonicalTargets);
10953
+ const resolved = resolveWorkspaceConfig(rootPath, checkOptions.configpath, {
10444
10954
  ignore: checkOptions.ignore,
10445
- annotationsPath
10955
+ annotationsPath,
10956
+ dependencyLibraries: resolvedDeps.all,
10957
+ unrequestedExclusions
10446
10958
  });
10447
10959
  try {
10448
- result = await runLuaLSCheck(targetPath, resolved.configPath, checkOptions);
10960
+ result = await runLuaLSCheck(rootPath, resolved.configPath, checkOptions);
10449
10961
  } finally {
10450
10962
  if (resolved.isTemp && fs.existsSync(resolved.configPath)) try {
10451
10963
  fs.unlinkSync(resolved.configPath);
@@ -10526,6 +11038,7 @@ Examples:
10526
11038
  $ npx nanos-lint check . --checklevel=Error
10527
11039
  $ npx nanos-lint check . --ignore "myfolder/hello-*.lua"
10528
11040
  $ npx nanos-lint check . --realm server
11041
+ $ npx nanos-lint check Shared/ Server/ --realm server
10529
11042
  $ npx nanos-lint init
10530
11043
  $ npx nanos-lint clean-cache
10531
11044
  `);
@@ -10605,6 +11118,6 @@ if (isDirectExecution()) runCLI().then((code) => {
10605
11118
  process.exit(1);
10606
11119
  });
10607
11120
  //#endregion
10608
- export { getBaseLuaLSCacheDir as $, getDirectorySize as $t, formatProblemSummary as A, VALID_LUALS_DIAGNOSTIC_CODES as At, runLuaLSCheck as B, parseJsonc as Bt, getCachedAnnotationsFilePath as C, fetchLatestLuaLSVersionFromGitHub as Ct, updateLastCheckedDate as D, deriveRealmAnnotationFiles as Dt, readAnnotationsMetadata as E, sanitizeLuaLSVersion as Et, shouldEnableColor as F, getPackageRoot as Ft, MAX_ARCHIVE_SIZE_BYTES as G, writeTempConfig as Gt, listCheckedFiles as H, resolveWorkspaceConfig as Ht, collectRealmFiles as I, initWorkspace as It, isAllowedDownloadUrl as J, ConfigError as Jt, computeFileSha256 as K, AnnotationsError as Kt, planRealmCheck as L, loadConfigFile as Lt, formatSeverityBadge as M, getDefaultAnnotationsPath as Mt, getColors as N, getDefaultTemplatePath as Nt, formatGitHubAnnotations as O, splitAnnotationsByRealm as Ot, pluralize as P, getDefinitionsDir as Pt, findExistingLuaLSDir as Q, formatBytes as Qt, runRealmAwareCheck as R, loadUserConfig as Rt, getAnnotationsMetadataFilePath as S, FALLBACK_LUALS_VERSION as St, isAnnotationsValid as T, resolveLuaLSVersion as Tt, ALLOWED_DOWNLOAD_DOMAINS as U, stripJsonComments as Ut, countCheckedFiles as V, resolveRealmMappings as Vt, DOWNLOAD_TIMEOUT_MS as W, stripTrailingSlashes as Wt, LUALS_METADATA_FILENAME as X, NanosLintError as Xt, limitDownloadStream as Y, LuaLSError as Yt, cleanupOldCachedLuaLSVersions as Z, cleanCache as Zt, MAX_ANNOTATIONS_SIZE_BYTES as _, isZipArchive as _t, formatCacheStatusPretty as a, Logger as an, readLuaLSMetadata as at, MIN_ANNOTATIONS_SIZE_BYTES as b, getPlatformInfo as bt, DOCGEN_BRANCH as c, parseLogLevel as cn, MAX_DECOMPRESSED_SIZE_BYTES as ct, RAW_ANNOTATIONS_URL as d, escapePowerShellSingleQuote as dt, systemPaths as en, getCacheDir as et, downloadAndCacheAnnotations as f, getTarBinary as ft, ANNOTATIONS_FILENAME as g, isGzipArchive as gt, getRawAnnotationsUrl as h, isBinaryValid as ht, runCLI as i, LOG_LEVEL_PRIORITY as in, listCachedLuaLSVersions as it, formatReport as j, buildWorkspaceConfig as jt, formatPretty as k, DEFAULT_REALM_MAPPINGS as kt, DOCGEN_REPO as l, MIN_DOWNLOADED_BINARY_SIZE_BYTES as lt, fetchRawAnnotationsContent as m, isBinaryRunnable as mt, createProgram as n, DEFAULT_LOG_LEVEL as nn, getLuaLSMetadataPath as nt, getCacheStatus as o, isValidLogLevel as on, writeLuaLSMetadata as ot, fetchLatestCommitId as p, inspectZipMembers as pt, downloadAndExtractLuaLS as q, CacheError as qt, isDirectExecution as r, LOG_LEVELS as rn, listCachedLuaLSVersionDirs as rt, resolveAnnotations as s, logger as sn, MAX_ARCHIVE_MEMBER_COUNT as st, collectIgnorePatterns as t, fileUriToPath as tn, getIsoWeek as tt, GITHUB_COMMITS_API as u, assertValidLuaLSBinary as ut, MAX_COMMIT_JSON_SIZE_BYTES as v, parseTarTvSize as vt, getTodayDateString as w, resolveLatestLuaLSVersion as wt, getAnnotationsCacheDir as x, DEFAULT_LUALS_VERSION as xt, METADATA_FILENAME as y, validateArchiveMembers as yt, resolveLuaLSBinary as z, mergeConfigs as zt };
11121
+ export { ALLOWED_DOWNLOAD_DOMAINS as $, stripJsonComments as $t, formatProblemSummary as A, getPlatformInfo as At, resolvePackageDependencies as B, VALID_LUALS_DIAGNOSTIC_CODES as Bt, getCachedAnnotationsFilePath as C, inspectZipMembers as Ct, updateLastCheckedDate as D, isZipArchive as Dt, readAnnotationsMetadata as E, isGzipArchive as Et, shouldEnableColor as F, resolveLuaLSVersion as Ft, computeUnrequestedExclusions as G, getPackageRoot as Gt, runLuaLSCheck as H, getDefaultAnnotationsPath as Ht, collectRealmFiles as I, sanitizeLuaLSVersion as It, findProjectRoot as J, loadUserConfig as Jt, filterReportByTargetPaths as K, initWorkspace as Kt, planRealmCheck as L, deriveRealmAnnotationFiles as Lt, formatSeverityBadge as M, FALLBACK_LUALS_VERSION as Mt, getColors as N, fetchLatestLuaLSVersionFromGitHub as Nt, formatGitHubAnnotations as O, parseTarTvSize as Ot, pluralize as P, resolveLatestLuaLSVersion as Pt, resolveCheckTargets as Q, resolveWorkspaceConfig as Qt, runRealmAwareCheck as R, splitAnnotationsByRealm as Rt, getAnnotationsMetadataFilePath as S, getTarBinary as St, isAnnotationsValid as T, isBinaryValid as Tt, countCheckedFiles as U, getDefaultTemplatePath as Ut, resolveLuaLSBinary as V, buildWorkspaceConfig as Vt, listCheckedFiles as W, getDefinitionsDir as Wt, isFilesystemRoot as X, parseJsonc as Xt, getCanonicalPath as Y, mergeConfigs as Yt, matchesTargetPaths as Z, resolveRealmMappings as Zt, MAX_ANNOTATIONS_SIZE_BYTES as _, logger as _n, MAX_ARCHIVE_MEMBER_COUNT as _t, formatCacheStatusPretty as a, LuaLSError as an, limitDownloadStream as at, MIN_ANNOTATIONS_SIZE_BYTES as b, assertValidLuaLSBinary as bt, DOCGEN_BRANCH as c, formatBytes as cn, findExistingLuaLSDir as ct, RAW_ANNOTATIONS_URL as d, fileUriToPath as dn, getIsoWeek as dt, stripTrailingSlashes as en, DOWNLOAD_TIMEOUT_MS as et, downloadAndCacheAnnotations as f, DEFAULT_LOG_LEVEL as fn, getLuaLSMetadataPath as ft, ANNOTATIONS_FILENAME as g, isValidLogLevel as gn, writeLuaLSMetadata as gt, getRawAnnotationsUrl as h, Logger as hn, readLuaLSMetadata as ht, runCLI as i, ConfigError as in, isAllowedDownloadUrl as it, formatReport as j, DEFAULT_LUALS_VERSION as jt, formatPretty as k, validateArchiveMembers as kt, DOCGEN_REPO as l, getDirectorySize as ln, getBaseLuaLSCacheDir as lt, fetchRawAnnotationsContent as m, LOG_LEVEL_PRIORITY as mn, listCachedLuaLSVersions as mt, createProgram as n, AnnotationsError as nn, computeFileSha256 as nt, getCacheStatus as o, NanosLintError as on, LUALS_METADATA_FILENAME as ot, fetchLatestCommitId as p, LOG_LEVELS as pn, listCachedLuaLSVersionDirs as pt, findCommonAncestorDirectory as q, loadConfigFile as qt, isDirectExecution as r, CacheError as rn, downloadAndExtractLuaLS as rt, resolveAnnotations as s, cleanCache as sn, cleanupOldCachedLuaLSVersions as st, collectIgnorePatterns as t, writeTempConfig as tn, MAX_ARCHIVE_SIZE_BYTES as tt, GITHUB_COMMITS_API as u, systemPaths as un, getCacheDir as ut, MAX_COMMIT_JSON_SIZE_BYTES as v, parseLogLevel as vn, MAX_DECOMPRESSED_SIZE_BYTES as vt, getTodayDateString as w, isBinaryRunnable as wt, getAnnotationsCacheDir as x, escapePowerShellSingleQuote as xt, METADATA_FILENAME as y, MIN_DOWNLOADED_BINARY_SIZE_BYTES as yt, collectDeps as z, DEFAULT_REALM_MAPPINGS as zt };
10609
11122
 
10610
- //# sourceMappingURL=cli-DUsLr0tU.js.map
11123
+ //# sourceMappingURL=cli-Cb3wLBCr.js.map