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.
- package/README.md +52 -14
- package/action.yml +47 -2
- package/dist/{cli-DUsLr0tU.js → cli-Cb3wLBCr.js} +568 -55
- package/dist/cli-Cb3wLBCr.js.map +1 -0
- package/dist/cli-dxsaVVVq.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +65 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +3 -3
- package/dist/cli-DUsLr0tU.js.map +0 -1
|
@@ -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, {
|
|
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 =
|
|
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
|
|
4792
|
-
function
|
|
4793
|
-
const
|
|
4794
|
-
|
|
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
|
-
|
|
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
|
|
4835
|
-
if (
|
|
4836
|
-
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
|
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
|
|
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 [
|
|
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 (
|
|
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:
|
|
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
|
|
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,
|
|
10946
|
+
result = await runRealmAwareCheck(realmPlan, rootPath, checkOptions);
|
|
10439
10947
|
} finally {
|
|
10440
10948
|
realmPlan.cleanup();
|
|
10441
10949
|
}
|
|
10442
10950
|
else {
|
|
10443
|
-
const
|
|
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(
|
|
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 {
|
|
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-
|
|
11123
|
+
//# sourceMappingURL=cli-Cb3wLBCr.js.map
|