@colrealpro/react-luau-doctor 0.18.1 → 0.18.3
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/dist/cli.js +381 -37
- package/dist/cli.js.map +28 -26
- package/dist/scan-worker.js +68 -2
- package/dist/scan-worker.js.map +25 -25
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import
|
|
5
|
+
import fs11 from "fs";
|
|
6
6
|
import os2 from "os";
|
|
7
|
-
import
|
|
7
|
+
import path13 from "path";
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@colrealpro/react-luau-doctor",
|
|
11
11
|
publishConfig: {
|
|
12
12
|
access: "public"
|
|
13
13
|
},
|
|
14
|
-
version: "0.18.
|
|
14
|
+
version: "0.18.3",
|
|
15
15
|
description: "Static analysis for React-Luau hooks, effects, rendering, and performance.",
|
|
16
16
|
license: "MIT",
|
|
17
17
|
type: "module",
|
|
@@ -2141,6 +2141,71 @@ function applyMasks(source, ranges) {
|
|
|
2141
2141
|
}
|
|
2142
2142
|
return output;
|
|
2143
2143
|
}
|
|
2144
|
+
function genericFunctionParameterRanges(source, mask) {
|
|
2145
|
+
const ranges = [];
|
|
2146
|
+
const functionPattern = /\bfunction\b/g;
|
|
2147
|
+
for (const match of source.matchAll(functionPattern)) {
|
|
2148
|
+
const start = match.index;
|
|
2149
|
+
if (!isCode(mask, start, start + match[0].length))
|
|
2150
|
+
continue;
|
|
2151
|
+
let cursor = start + match[0].length;
|
|
2152
|
+
while (cursor < source.length) {
|
|
2153
|
+
const char = source[cursor];
|
|
2154
|
+
if (/\s/.test(char)) {
|
|
2155
|
+
cursor += 1;
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
if (char === "(")
|
|
2159
|
+
break;
|
|
2160
|
+
if (char === "<") {
|
|
2161
|
+
let depth = 0;
|
|
2162
|
+
const rangeStart = cursor;
|
|
2163
|
+
while (cursor < source.length) {
|
|
2164
|
+
if (isCode(mask, cursor)) {
|
|
2165
|
+
if (source[cursor] === "<")
|
|
2166
|
+
depth += 1;
|
|
2167
|
+
else if (source[cursor] === ">") {
|
|
2168
|
+
depth -= 1;
|
|
2169
|
+
if (depth === 0) {
|
|
2170
|
+
cursor += 1;
|
|
2171
|
+
ranges.push({ start: rangeStart, end: cursor });
|
|
2172
|
+
break;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
cursor += 1;
|
|
2177
|
+
}
|
|
2178
|
+
break;
|
|
2179
|
+
}
|
|
2180
|
+
if (/[A-Za-z0-9_.:]/.test(char)) {
|
|
2181
|
+
cursor += 1;
|
|
2182
|
+
continue;
|
|
2183
|
+
}
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return ranges;
|
|
2188
|
+
}
|
|
2189
|
+
function normalizeGenericTypePackUses(source, mask) {
|
|
2190
|
+
const declarationRanges = genericFunctionParameterRanges(source, mask);
|
|
2191
|
+
const replacements = [];
|
|
2192
|
+
const packPattern = /\b[A-Za-z_][A-Za-z0-9_]*\.\.\./g;
|
|
2193
|
+
for (const match of source.matchAll(packPattern)) {
|
|
2194
|
+
const start = match.index;
|
|
2195
|
+
const end = start + match[0].length;
|
|
2196
|
+
if (!isCode(mask, start, end))
|
|
2197
|
+
continue;
|
|
2198
|
+
if (declarationRanges.some((range) => start >= range.start && end <= range.end))
|
|
2199
|
+
continue;
|
|
2200
|
+
const name = match[0].slice(0, -3);
|
|
2201
|
+
replacements.push({ start, end, value: `...${name}` });
|
|
2202
|
+
}
|
|
2203
|
+
let output = source;
|
|
2204
|
+
for (const replacement of replacements.sort((a, b) => b.start - a.start)) {
|
|
2205
|
+
output = `${output.slice(0, replacement.start)}${replacement.value}${output.slice(replacement.end)}`;
|
|
2206
|
+
}
|
|
2207
|
+
return output;
|
|
2208
|
+
}
|
|
2144
2209
|
function normalizeIntegerLiteralSuffixes(source, mask) {
|
|
2145
2210
|
const suffixes = [];
|
|
2146
2211
|
const integerPattern = /\b(?:0[xX][0-9A-Fa-f_]+|0[bB][01_]+|[0-9][0-9_]*)i\b/g;
|
|
@@ -2180,13 +2245,14 @@ function parserCompatibleSource(source) {
|
|
|
2180
2245
|
const mask = codeMask(source);
|
|
2181
2246
|
const keywordCompatible = replaceContextualKeywords(source, mask);
|
|
2182
2247
|
const literalCompatible = normalizeIntegerLiteralSuffixes(keywordCompatible, mask);
|
|
2248
|
+
const packCompatible = normalizeGenericTypePackUses(literalCompatible, mask);
|
|
2183
2249
|
const ranges = [
|
|
2184
2250
|
...maskTypeAliases(source, mask),
|
|
2185
2251
|
...maskTypeLevelBlocks(source, mask),
|
|
2186
2252
|
...attributeRanges(source, mask),
|
|
2187
2253
|
...explicitTypeArgumentRanges(source, mask)
|
|
2188
2254
|
];
|
|
2189
|
-
const masked = applyMasks(
|
|
2255
|
+
const masked = applyMasks(packCompatible, ranges);
|
|
2190
2256
|
return masked.replace(/^(\s*)type(?=\s*(?:\+=|-=|\*=|\/=|%=|\^=|\.\.=|=(?!=)))/gm, "$1_typ");
|
|
2191
2257
|
}
|
|
2192
2258
|
|
|
@@ -10698,6 +10764,210 @@ async function runCiCommand(argv) {
|
|
|
10698
10764
|
throw new Error("ci requires install, config, or upgrade");
|
|
10699
10765
|
}
|
|
10700
10766
|
|
|
10767
|
+
// src/update-check.ts
|
|
10768
|
+
import { spawn } from "child_process";
|
|
10769
|
+
import fs10 from "fs";
|
|
10770
|
+
import path12 from "path";
|
|
10771
|
+
var UPDATE_CHECK_INTERVAL_MS = 2 * 60 * 60 * 1000;
|
|
10772
|
+
var UPDATE_REQUEST_TIMEOUT_MS = 5000;
|
|
10773
|
+
var UPDATE_CACHE_FILENAME = "update-check.json";
|
|
10774
|
+
function updateCacheFilename() {
|
|
10775
|
+
return path12.join(cacheBaseDirectory(), UPDATE_CACHE_FILENAME);
|
|
10776
|
+
}
|
|
10777
|
+
function readUpdateCache() {
|
|
10778
|
+
try {
|
|
10779
|
+
const parsed = JSON.parse(fs10.readFileSync(updateCacheFilename(), "utf8"));
|
|
10780
|
+
if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt))
|
|
10781
|
+
return null;
|
|
10782
|
+
if (parsed.latest !== undefined && typeof parsed.latest !== "string")
|
|
10783
|
+
return null;
|
|
10784
|
+
return {
|
|
10785
|
+
checkedAt: parsed.checkedAt,
|
|
10786
|
+
latest: parsed.latest
|
|
10787
|
+
};
|
|
10788
|
+
} catch {
|
|
10789
|
+
return null;
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
function writeUpdateCache(cache) {
|
|
10793
|
+
try {
|
|
10794
|
+
const filename = updateCacheFilename();
|
|
10795
|
+
fs10.mkdirSync(path12.dirname(filename), { recursive: true });
|
|
10796
|
+
fs10.writeFileSync(filename, `${JSON.stringify(cache)}
|
|
10797
|
+
`);
|
|
10798
|
+
} catch {}
|
|
10799
|
+
}
|
|
10800
|
+
function parseVersion(value) {
|
|
10801
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value.trim());
|
|
10802
|
+
if (!match)
|
|
10803
|
+
return null;
|
|
10804
|
+
return {
|
|
10805
|
+
major: Number(match[1]),
|
|
10806
|
+
minor: Number(match[2]),
|
|
10807
|
+
patch: Number(match[3]),
|
|
10808
|
+
prerelease: match[4]?.split(".") ?? []
|
|
10809
|
+
};
|
|
10810
|
+
}
|
|
10811
|
+
function comparePrerelease(left, right) {
|
|
10812
|
+
if (left.length === 0 || right.length === 0) {
|
|
10813
|
+
if (left.length === right.length)
|
|
10814
|
+
return 0;
|
|
10815
|
+
return left.length === 0 ? 1 : -1;
|
|
10816
|
+
}
|
|
10817
|
+
const count = Math.max(left.length, right.length);
|
|
10818
|
+
for (let index = 0;index < count; index += 1) {
|
|
10819
|
+
const leftPart = left[index];
|
|
10820
|
+
const rightPart = right[index];
|
|
10821
|
+
if (leftPart === undefined)
|
|
10822
|
+
return -1;
|
|
10823
|
+
if (rightPart === undefined)
|
|
10824
|
+
return 1;
|
|
10825
|
+
if (leftPart === rightPart)
|
|
10826
|
+
continue;
|
|
10827
|
+
const leftNumber = /^\d+$/.test(leftPart) ? Number(leftPart) : null;
|
|
10828
|
+
const rightNumber = /^\d+$/.test(rightPart) ? Number(rightPart) : null;
|
|
10829
|
+
if (leftNumber !== null && rightNumber !== null)
|
|
10830
|
+
return leftNumber < rightNumber ? -1 : 1;
|
|
10831
|
+
if (leftNumber !== null)
|
|
10832
|
+
return -1;
|
|
10833
|
+
if (rightNumber !== null)
|
|
10834
|
+
return 1;
|
|
10835
|
+
return leftPart < rightPart ? -1 : 1;
|
|
10836
|
+
}
|
|
10837
|
+
return 0;
|
|
10838
|
+
}
|
|
10839
|
+
function compareVersions(left, right) {
|
|
10840
|
+
const a = parseVersion(left);
|
|
10841
|
+
const b = parseVersion(right);
|
|
10842
|
+
if (!a || !b)
|
|
10843
|
+
return null;
|
|
10844
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
10845
|
+
if (a[key] !== b[key])
|
|
10846
|
+
return a[key] < b[key] ? -1 : 1;
|
|
10847
|
+
}
|
|
10848
|
+
return comparePrerelease(a.prerelease, b.prerelease);
|
|
10849
|
+
}
|
|
10850
|
+
function updateRegistryUrl() {
|
|
10851
|
+
const registry = process.env.REACT_LUAU_DOCTOR_UPDATE_REGISTRY ?? process.env.npm_config_registry ?? process.env.NPM_CONFIG_REGISTRY ?? "https://registry.npmjs.org/";
|
|
10852
|
+
const base = registry.endsWith("/") ? registry : `${registry}/`;
|
|
10853
|
+
return new URL(`${encodeURIComponent(package_default.name)}/latest`, base).toString();
|
|
10854
|
+
}
|
|
10855
|
+
async function fetchLatestVersion() {
|
|
10856
|
+
const controller = new AbortController;
|
|
10857
|
+
const timer = setTimeout(() => controller.abort(), UPDATE_REQUEST_TIMEOUT_MS);
|
|
10858
|
+
timer.unref?.();
|
|
10859
|
+
try {
|
|
10860
|
+
const response = await fetch(updateRegistryUrl(), {
|
|
10861
|
+
headers: { accept: "application/json" },
|
|
10862
|
+
signal: controller.signal
|
|
10863
|
+
});
|
|
10864
|
+
if (!response.ok)
|
|
10865
|
+
throw new Error(`npm registry returned HTTP ${response.status}`);
|
|
10866
|
+
const body = await response.json();
|
|
10867
|
+
if (typeof body.version !== "string" || compareVersions(body.version, body.version) === null) {
|
|
10868
|
+
throw new Error("npm registry returned an invalid package version");
|
|
10869
|
+
}
|
|
10870
|
+
return body.version;
|
|
10871
|
+
} finally {
|
|
10872
|
+
clearTimeout(timer);
|
|
10873
|
+
}
|
|
10874
|
+
}
|
|
10875
|
+
function updateCacheIsStale(now = Date.now()) {
|
|
10876
|
+
const cache = readUpdateCache();
|
|
10877
|
+
return !cache || now - cache.checkedAt >= UPDATE_CHECK_INTERVAL_MS;
|
|
10878
|
+
}
|
|
10879
|
+
async function refreshUpdateCache(options = {}) {
|
|
10880
|
+
const now = options.now ?? Date.now();
|
|
10881
|
+
try {
|
|
10882
|
+
const latest = await fetchLatestVersion();
|
|
10883
|
+
writeUpdateCache({ checkedAt: now, latest });
|
|
10884
|
+
return latest;
|
|
10885
|
+
} catch (error) {
|
|
10886
|
+
const previous = readUpdateCache();
|
|
10887
|
+
writeUpdateCache({ checkedAt: now, latest: previous?.latest });
|
|
10888
|
+
if (options.silent)
|
|
10889
|
+
return null;
|
|
10890
|
+
throw error;
|
|
10891
|
+
}
|
|
10892
|
+
}
|
|
10893
|
+
function startBackgroundUpdateRefresh() {
|
|
10894
|
+
if (!updateCacheIsStale())
|
|
10895
|
+
return;
|
|
10896
|
+
const script = process.argv[1];
|
|
10897
|
+
if (!script)
|
|
10898
|
+
return;
|
|
10899
|
+
try {
|
|
10900
|
+
const child2 = spawn(process.execPath, [script, "__update-cache"], {
|
|
10901
|
+
detached: true,
|
|
10902
|
+
stdio: "ignore",
|
|
10903
|
+
windowsHide: true,
|
|
10904
|
+
env: process.env
|
|
10905
|
+
});
|
|
10906
|
+
child2.unref();
|
|
10907
|
+
} catch {}
|
|
10908
|
+
}
|
|
10909
|
+
function getCachedUpdateNotice(currentVersion) {
|
|
10910
|
+
const cache = readUpdateCache();
|
|
10911
|
+
if (!cache?.latest)
|
|
10912
|
+
return null;
|
|
10913
|
+
const comparison = compareVersions(cache.latest, currentVersion);
|
|
10914
|
+
if (comparison === null || comparison <= 0)
|
|
10915
|
+
return null;
|
|
10916
|
+
return { current: currentVersion, latest: cache.latest };
|
|
10917
|
+
}
|
|
10918
|
+
async function checkForUpdatesNow(currentVersion) {
|
|
10919
|
+
const latest = await refreshUpdateCache();
|
|
10920
|
+
if (!latest)
|
|
10921
|
+
throw new Error("Could not check npm for updates");
|
|
10922
|
+
const comparison = compareVersions(latest, currentVersion);
|
|
10923
|
+
if (comparison === null)
|
|
10924
|
+
throw new Error(`Could not compare installed version ${currentVersion} with ${latest}`);
|
|
10925
|
+
return { latest, updateAvailable: comparison > 0 };
|
|
10926
|
+
}
|
|
10927
|
+
|
|
10928
|
+
// src/update-install.ts
|
|
10929
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
10930
|
+
import { fileURLToPath } from "url";
|
|
10931
|
+
function normalizedPath(filename) {
|
|
10932
|
+
return filename.replaceAll("\\", "/");
|
|
10933
|
+
}
|
|
10934
|
+
function updateInstallCommandForPath(filename) {
|
|
10935
|
+
const normalized = normalizedPath(filename);
|
|
10936
|
+
const spec = `${package_default.name}@latest`;
|
|
10937
|
+
if (normalized.includes("/.bun/install/global/node_modules/")) {
|
|
10938
|
+
return {
|
|
10939
|
+
manager: "bun",
|
|
10940
|
+
command: "bun",
|
|
10941
|
+
args: ["add", "-g", spec],
|
|
10942
|
+
display: `bun add -g ${spec}`
|
|
10943
|
+
};
|
|
10944
|
+
}
|
|
10945
|
+
if (normalized.includes("/.bun/install/cache/") || normalized.includes("/.npm/_npx/"))
|
|
10946
|
+
return null;
|
|
10947
|
+
if (normalized.includes("/node_modules/")) {
|
|
10948
|
+
return {
|
|
10949
|
+
manager: "npm",
|
|
10950
|
+
command: "npm",
|
|
10951
|
+
args: ["install", "-g", spec],
|
|
10952
|
+
display: `npm install -g ${spec}`
|
|
10953
|
+
};
|
|
10954
|
+
}
|
|
10955
|
+
return null;
|
|
10956
|
+
}
|
|
10957
|
+
function currentUpdateInstallCommand() {
|
|
10958
|
+
return updateInstallCommandForPath(fileURLToPath(import.meta.url));
|
|
10959
|
+
}
|
|
10960
|
+
function installLatestVersion(command) {
|
|
10961
|
+
const result = spawnSync3(command.command, command.args, {
|
|
10962
|
+
stdio: "inherit",
|
|
10963
|
+
windowsHide: true
|
|
10964
|
+
});
|
|
10965
|
+
if (result.error)
|
|
10966
|
+
throw result.error;
|
|
10967
|
+
if (result.status !== 0)
|
|
10968
|
+
throw new Error(`${command.display} exited with code ${result.status ?? "unknown"}`);
|
|
10969
|
+
}
|
|
10970
|
+
|
|
10701
10971
|
// src/fix-examples.ts
|
|
10702
10972
|
var examples = {
|
|
10703
10973
|
"react-luau/parse-error": {
|
|
@@ -11184,6 +11454,7 @@ Usage:
|
|
|
11184
11454
|
react-luau-doctor [directory] [options]
|
|
11185
11455
|
react-luau-doctor ci <install|config|upgrade>
|
|
11186
11456
|
react-luau-doctor why <file:line>
|
|
11457
|
+
react-luau-doctor update [--check]
|
|
11187
11458
|
react-luau-doctor rules <command>
|
|
11188
11459
|
|
|
11189
11460
|
Scan options:
|
|
@@ -11212,6 +11483,7 @@ Scan options:
|
|
|
11212
11483
|
--no-color Disable automatic ANSI colors
|
|
11213
11484
|
--no-cache Disable the persistent OS-level analysis cache
|
|
11214
11485
|
--no-parallel Disable parallel file analysis
|
|
11486
|
+
--no-update-check Disable the automatic update notice
|
|
11215
11487
|
|
|
11216
11488
|
React-Luau Doctor options:
|
|
11217
11489
|
--min-severity <level> suggestion, warning, or error
|
|
@@ -11224,6 +11496,10 @@ CI commands:
|
|
|
11224
11496
|
ci upgrade [--provider github|gitlab] [--pr] [-y] [--cwd <cwd>]
|
|
11225
11497
|
Reporting toggles: --comment/--no-comment, --review-comments/--no-review-comments, --commit-status/--no-commit-status
|
|
11226
11498
|
|
|
11499
|
+
Update commands:
|
|
11500
|
+
update Update the global installation to the latest release
|
|
11501
|
+
update --check Check npm for a newer release without updating
|
|
11502
|
+
|
|
11227
11503
|
Rules commands:
|
|
11228
11504
|
rules list [--category <name>] [--configured] [--json]
|
|
11229
11505
|
rules explain <rule> [--json]
|
|
@@ -11236,6 +11512,53 @@ Config:
|
|
|
11236
11512
|
react-luau-doctor.config.json
|
|
11237
11513
|
`;
|
|
11238
11514
|
}
|
|
11515
|
+
function automaticUpdateNoticeEnabled(options, machineReadable) {
|
|
11516
|
+
return !options.noUpdateCheck && !machineReadable && Boolean(process.stdout.isTTY) && !process.env.CI && process.env.NO_UPDATE_NOTIFIER === undefined && process.env.REACT_LUAU_DOCTOR_NO_UPDATE_CHECK === undefined;
|
|
11517
|
+
}
|
|
11518
|
+
function renderUpdateNotice(current, latest, colorized) {
|
|
11519
|
+
const label = whyPaint(colorized, "Update available:", WHY_ANSI.bold, WHY_ANSI.yellow);
|
|
11520
|
+
const oldVersion = whyPaint(colorized, `v${current}`, WHY_ANSI.dim);
|
|
11521
|
+
const newVersion = whyPaint(colorized, `v${latest}`, WHY_ANSI.bold);
|
|
11522
|
+
return `${label} ${oldVersion} \u2192 ${newVersion}
|
|
11523
|
+
Run \`react-luau-doctor update\` to update.`;
|
|
11524
|
+
}
|
|
11525
|
+
async function runUpdateCommand(argv) {
|
|
11526
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
11527
|
+
process.stdout.write(`Usage: react-luau-doctor update [--check]
|
|
11528
|
+
`);
|
|
11529
|
+
return;
|
|
11530
|
+
}
|
|
11531
|
+
if (argv.length > 1 || argv.length === 1 && argv[0] !== "--check") {
|
|
11532
|
+
throw new Error("Usage: react-luau-doctor update [--check]");
|
|
11533
|
+
}
|
|
11534
|
+
const result = await checkForUpdatesNow(VERSION2);
|
|
11535
|
+
if (!result.updateAvailable) {
|
|
11536
|
+
process.stdout.write(`React-Luau Doctor v${VERSION2} is up to date.
|
|
11537
|
+
`);
|
|
11538
|
+
return;
|
|
11539
|
+
}
|
|
11540
|
+
const colorized = shouldUseColor(false, false);
|
|
11541
|
+
if (argv[0] === "--check") {
|
|
11542
|
+
process.stdout.write(`${renderUpdateNotice(VERSION2, result.latest, colorized)}
|
|
11543
|
+
`);
|
|
11544
|
+
return;
|
|
11545
|
+
}
|
|
11546
|
+
const command = currentUpdateInstallCommand();
|
|
11547
|
+
if (!command) {
|
|
11548
|
+
throw new Error(`Could not determine the global package manager for this installation. Run \`npm install -g ${package_default.name}@latest\` manually.`);
|
|
11549
|
+
}
|
|
11550
|
+
const label = whyPaint(colorized, "Updating React-Luau Doctor:", WHY_ANSI.bold, WHY_ANSI.yellow);
|
|
11551
|
+
const oldVersion = whyPaint(colorized, `v${VERSION2}`, WHY_ANSI.dim);
|
|
11552
|
+
const newVersion = whyPaint(colorized, `v${result.latest}`, WHY_ANSI.bold);
|
|
11553
|
+
process.stdout.write(`${label} ${oldVersion} \u2192 ${newVersion}
|
|
11554
|
+
Using \`${command.display}\`
|
|
11555
|
+
|
|
11556
|
+
`);
|
|
11557
|
+
installLatestVersion(command);
|
|
11558
|
+
process.stdout.write(`
|
|
11559
|
+
Updated React-Luau Doctor to v${result.latest}.
|
|
11560
|
+
`);
|
|
11561
|
+
}
|
|
11239
11562
|
function splitLongOption(arg) {
|
|
11240
11563
|
if (!arg.startsWith("--"))
|
|
11241
11564
|
return { name: arg };
|
|
@@ -11641,6 +11964,7 @@ function parseArgs(argv) {
|
|
|
11641
11964
|
noColor: false,
|
|
11642
11965
|
noCache: false,
|
|
11643
11966
|
noParallel: false,
|
|
11967
|
+
noUpdateCheck: false,
|
|
11644
11968
|
help: false,
|
|
11645
11969
|
version: false
|
|
11646
11970
|
};
|
|
@@ -11678,6 +12002,8 @@ function parseArgs(argv) {
|
|
|
11678
12002
|
options.noCache = true;
|
|
11679
12003
|
else if (arg === "--no-parallel")
|
|
11680
12004
|
options.noParallel = true;
|
|
12005
|
+
else if (arg === "--no-update-check")
|
|
12006
|
+
options.noUpdateCheck = true;
|
|
11681
12007
|
else if (arg === "--annotations")
|
|
11682
12008
|
options.annotations = true;
|
|
11683
12009
|
else if (arg === "--help" || arg === "-h")
|
|
@@ -11795,8 +12121,8 @@ function validateModeFlags(options, scope) {
|
|
|
11795
12121
|
throw new Error("--annotations cannot be combined with --json or --score");
|
|
11796
12122
|
}
|
|
11797
12123
|
function findProjectByName(root, name) {
|
|
11798
|
-
const direct =
|
|
11799
|
-
if (
|
|
12124
|
+
const direct = path13.resolve(root, name);
|
|
12125
|
+
if (fs11.existsSync(direct) && fs11.statSync(direct).isDirectory())
|
|
11800
12126
|
return direct;
|
|
11801
12127
|
const ignored = new Set([".git", "node_modules", "Packages", "DevPackages", "ServerPackages", "dist", "vendor"]);
|
|
11802
12128
|
const queue = [{ directory: root, depth: 0 }];
|
|
@@ -11805,17 +12131,17 @@ function findProjectByName(root, name) {
|
|
|
11805
12131
|
const current = queue.shift();
|
|
11806
12132
|
if (current.depth >= 3)
|
|
11807
12133
|
continue;
|
|
11808
|
-
for (const entry of
|
|
12134
|
+
for (const entry of fs11.readdirSync(current.directory, { withFileTypes: true })) {
|
|
11809
12135
|
if (!entry.isDirectory() || ignored.has(entry.name))
|
|
11810
12136
|
continue;
|
|
11811
|
-
const absolute =
|
|
12137
|
+
const absolute = path13.join(current.directory, entry.name);
|
|
11812
12138
|
if (entry.name === name)
|
|
11813
12139
|
matches.push(absolute);
|
|
11814
12140
|
queue.push({ directory: absolute, depth: current.depth + 1 });
|
|
11815
12141
|
}
|
|
11816
12142
|
}
|
|
11817
12143
|
if (matches.length > 1)
|
|
11818
|
-
throw new Error(`Project selector "${name}" is ambiguous: ${matches.map((match) =>
|
|
12144
|
+
throw new Error(`Project selector "${name}" is ambiguous: ${matches.map((match) => path13.relative(root, match)).join(", ")}`);
|
|
11819
12145
|
return matches[0] ?? null;
|
|
11820
12146
|
}
|
|
11821
12147
|
function resolveProjectRoots(root, projectFlag, config) {
|
|
@@ -11833,15 +12159,15 @@ function serializeReport(report, compact) {
|
|
|
11833
12159
|
return compact ? JSON.stringify(report) : JSON.stringify(report, null, 2);
|
|
11834
12160
|
}
|
|
11835
12161
|
function writeJsonFile(filename, value, compact = false) {
|
|
11836
|
-
|
|
11837
|
-
|
|
12162
|
+
fs11.mkdirSync(path13.dirname(filename), { recursive: true });
|
|
12163
|
+
fs11.writeFileSync(filename, `${compact ? JSON.stringify(value) : JSON.stringify(value, null, 2)}
|
|
11838
12164
|
`);
|
|
11839
12165
|
}
|
|
11840
12166
|
function writeDiagnosticsDump(directory, report) {
|
|
11841
|
-
|
|
11842
|
-
writeJsonFile(
|
|
11843
|
-
writeJsonFile(
|
|
11844
|
-
writeJsonFile(
|
|
12167
|
+
fs11.mkdirSync(directory, { recursive: true });
|
|
12168
|
+
writeJsonFile(path13.join(directory, "report.json"), report);
|
|
12169
|
+
writeJsonFile(path13.join(directory, "diagnostics.json"), report.diagnostics);
|
|
12170
|
+
writeJsonFile(path13.join(directory, "summary.json"), {
|
|
11845
12171
|
schemaVersion: report.schemaVersion,
|
|
11846
12172
|
root: report.root,
|
|
11847
12173
|
scope: report.scope ?? "full",
|
|
@@ -11877,7 +12203,7 @@ function parseCommandCwd(argv) {
|
|
|
11877
12203
|
const value = argv[++index];
|
|
11878
12204
|
if (!value)
|
|
11879
12205
|
throw new Error(`${arg} requires a path`);
|
|
11880
|
-
cwd =
|
|
12206
|
+
cwd = path13.resolve(value);
|
|
11881
12207
|
} else
|
|
11882
12208
|
remaining.push(arg);
|
|
11883
12209
|
}
|
|
@@ -11986,7 +12312,7 @@ function runRulesCommand(argv) {
|
|
|
11986
12312
|
if (!severity)
|
|
11987
12313
|
throw new Error("Rule severity must be off, suggestion, warning/warn, or error");
|
|
11988
12314
|
const filename = writeConfig(cwd, (current) => ({ ...current, rules: { ...current.rules, [rule.id]: severity } }));
|
|
11989
|
-
process.stdout.write(`Set ${rule.id} to ${severity} in ${
|
|
12315
|
+
process.stdout.write(`Set ${rule.id} to ${severity} in ${path13.relative(cwd, filename)}
|
|
11990
12316
|
`);
|
|
11991
12317
|
return;
|
|
11992
12318
|
}
|
|
@@ -12005,7 +12331,7 @@ function runRulesCommand(argv) {
|
|
|
12005
12331
|
severity = normalized;
|
|
12006
12332
|
}
|
|
12007
12333
|
const filename = writeConfig(cwd, (current) => ({ ...current, rules: { ...current.rules, [rule.id]: severity } }));
|
|
12008
|
-
process.stdout.write(`Enabled ${rule.id} at ${severity} in ${
|
|
12334
|
+
process.stdout.write(`Enabled ${rule.id} at ${severity} in ${path13.relative(cwd, filename)}
|
|
12009
12335
|
`);
|
|
12010
12336
|
return;
|
|
12011
12337
|
}
|
|
@@ -12015,7 +12341,7 @@ function runRulesCommand(argv) {
|
|
|
12015
12341
|
throw new Error("rules disable requires a rule id");
|
|
12016
12342
|
const rule = findRule(requested);
|
|
12017
12343
|
const filename = writeConfig(cwd, (current) => ({ ...current, rules: { ...current.rules, [rule.id]: "off" } }));
|
|
12018
|
-
process.stdout.write(`Disabled ${rule.id} in ${
|
|
12344
|
+
process.stdout.write(`Disabled ${rule.id} in ${path13.relative(cwd, filename)}
|
|
12019
12345
|
`);
|
|
12020
12346
|
return;
|
|
12021
12347
|
}
|
|
@@ -12038,7 +12364,7 @@ function runRulesCommand(argv) {
|
|
|
12038
12364
|
...categoryRules.map((rule) => [rule.id, severity])
|
|
12039
12365
|
])
|
|
12040
12366
|
}));
|
|
12041
|
-
process.stdout.write(`Set ${categoryRules.length} ${category} rules to ${severity} in ${
|
|
12367
|
+
process.stdout.write(`Set ${categoryRules.length} ${category} rules to ${severity} in ${path13.relative(cwd, filename)}
|
|
12042
12368
|
`);
|
|
12043
12369
|
return;
|
|
12044
12370
|
}
|
|
@@ -12122,7 +12448,7 @@ function whyCaretForLine(sourceLine, line, ranges) {
|
|
|
12122
12448
|
return value;
|
|
12123
12449
|
}
|
|
12124
12450
|
function renderWhyCodeFrame(filename, diagnostic, colorized) {
|
|
12125
|
-
const source =
|
|
12451
|
+
const source = fs11.readFileSync(filename, "utf8").split(/\r?\n/);
|
|
12126
12452
|
const ranges = whyDiagnosticRanges(diagnostic);
|
|
12127
12453
|
const intervals = whyFrameIntervals(ranges, source.length);
|
|
12128
12454
|
const width = String(Math.max(...intervals.map((interval) => interval.end), 1)).length;
|
|
@@ -12230,13 +12556,13 @@ async function runWhy(location, cwd, noColor = false, cache = true, onProgress,
|
|
|
12230
12556
|
const match = location.match(/^(.*):(\d+)(?::(\d+))?$/);
|
|
12231
12557
|
if (!match)
|
|
12232
12558
|
throw new Error("Location must be file:line or file:line:column");
|
|
12233
|
-
const filename =
|
|
12559
|
+
const filename = path13.resolve(cwd, match[1]);
|
|
12234
12560
|
const line = Number(match[2]);
|
|
12235
12561
|
const column = match[3] === undefined ? undefined : Number(match[3]);
|
|
12236
|
-
if (!
|
|
12562
|
+
if (!fs11.existsSync(filename))
|
|
12237
12563
|
throw new Error(`File does not exist: ${match[1]}`);
|
|
12238
12564
|
const colorized = shouldUseColor(noColor, false);
|
|
12239
|
-
const source =
|
|
12565
|
+
const source = fs11.readFileSync(filename, "utf8");
|
|
12240
12566
|
const isSuppressed = createInlineSuppressionChecker(source);
|
|
12241
12567
|
const auditReport = await scanWhyFile(filename, cwd, cache, onProgress);
|
|
12242
12568
|
beforeOutput?.();
|
|
@@ -12277,16 +12603,16 @@ async function runWhy(location, cwd, noColor = false, cache = true, onProgress,
|
|
|
12277
12603
|
}
|
|
12278
12604
|
}
|
|
12279
12605
|
function pathIsInside(parent, child2) {
|
|
12280
|
-
const relative =
|
|
12281
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
12606
|
+
const relative = path13.relative(parent, child2);
|
|
12607
|
+
return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
|
|
12282
12608
|
}
|
|
12283
12609
|
async function runScan(options, onProgress) {
|
|
12284
12610
|
const commandRoot = process.cwd();
|
|
12285
|
-
const target =
|
|
12286
|
-
if (!
|
|
12611
|
+
const target = path13.resolve(commandRoot, options.target);
|
|
12612
|
+
if (!fs11.existsSync(target))
|
|
12287
12613
|
throw new Error(`Scan path does not exist: ${options.target}`);
|
|
12288
|
-
const targetStat =
|
|
12289
|
-
const scanRoot = targetStat.isFile() ?
|
|
12614
|
+
const targetStat = fs11.statSync(target);
|
|
12615
|
+
const scanRoot = targetStat.isFile() ? path13.dirname(target) : target;
|
|
12290
12616
|
const loaded = loadConfigWithSource(commandRoot);
|
|
12291
12617
|
const config = loaded.config;
|
|
12292
12618
|
const resolvedScope = resolveScope(options, config);
|
|
@@ -12316,7 +12642,7 @@ async function runScan(options, onProgress) {
|
|
|
12316
12642
|
const reports = [];
|
|
12317
12643
|
for (const { projectRoot, targetRoot } of projectTargets) {
|
|
12318
12644
|
let report2;
|
|
12319
|
-
const projectName = projectTargets.length > 1 ?
|
|
12645
|
+
const projectName = projectTargets.length > 1 ? path13.relative(displayRoot, projectRoot) || "." : undefined;
|
|
12320
12646
|
const projectProgress = onProgress ? (progress) => onProgress({
|
|
12321
12647
|
...progress,
|
|
12322
12648
|
phase: [projectName, progress.phase].filter(Boolean).join(":") || undefined,
|
|
@@ -12363,7 +12689,7 @@ async function runScan(options, onProgress) {
|
|
|
12363
12689
|
`[debug] target=${target}`,
|
|
12364
12690
|
`[debug] config=${loaded.filename ?? "none"}`,
|
|
12365
12691
|
`[debug] scope=${report.scope ?? scope} base=${report.base ?? resolvedScope.base ?? "auto"}`,
|
|
12366
|
-
`[debug] projects=${projectTargets.map(({ projectRoot }) =>
|
|
12692
|
+
`[debug] projects=${projectTargets.map(({ projectRoot }) => path13.relative(displayRoot, projectRoot) || ".").join(",")}`,
|
|
12367
12693
|
`[debug] candidates=${report.candidateFiles ?? 0} scanned=${report.scannedFiles} partial=${Boolean(report.partial)}`,
|
|
12368
12694
|
`[debug] parallel=${!options.noParallel}`
|
|
12369
12695
|
];
|
|
@@ -12376,6 +12702,14 @@ async function runScan(options, onProgress) {
|
|
|
12376
12702
|
async function main() {
|
|
12377
12703
|
try {
|
|
12378
12704
|
const argv = process.argv.slice(2);
|
|
12705
|
+
if (argv[0] === "__update-cache") {
|
|
12706
|
+
await refreshUpdateCache({ silent: true });
|
|
12707
|
+
return;
|
|
12708
|
+
}
|
|
12709
|
+
if (argv[0] === "update") {
|
|
12710
|
+
await runUpdateCommand(argv.slice(1));
|
|
12711
|
+
return;
|
|
12712
|
+
}
|
|
12379
12713
|
if (argv[0] === "ci") {
|
|
12380
12714
|
await runCiCommand(argv.slice(1));
|
|
12381
12715
|
return;
|
|
@@ -12426,6 +12760,9 @@ ${os2.release()}
|
|
|
12426
12760
|
}
|
|
12427
12761
|
const machineReadable = options.scoreOnly || options.json || options.annotations;
|
|
12428
12762
|
const colorized = shouldUseColor(options.noColor, machineReadable);
|
|
12763
|
+
const updateNoticeEnabled = automaticUpdateNoticeEnabled(options, machineReadable);
|
|
12764
|
+
if (updateNoticeEnabled)
|
|
12765
|
+
startBackgroundUpdateRefresh();
|
|
12429
12766
|
const progress = createProgressRenderer({
|
|
12430
12767
|
enabled: Boolean(process.stdout.isTTY && !process.env.CI && !machineReadable),
|
|
12431
12768
|
colorized
|
|
@@ -12440,12 +12777,12 @@ ${os2.release()}
|
|
|
12440
12777
|
const json = `${serializeReport(report, compactJson)}
|
|
12441
12778
|
`;
|
|
12442
12779
|
if (options.jsonOut) {
|
|
12443
|
-
const outputPath =
|
|
12444
|
-
|
|
12445
|
-
|
|
12780
|
+
const outputPath = path13.resolve(process.cwd(), options.jsonOut);
|
|
12781
|
+
fs11.mkdirSync(path13.dirname(outputPath), { recursive: true });
|
|
12782
|
+
fs11.writeFileSync(outputPath, json);
|
|
12446
12783
|
}
|
|
12447
12784
|
if (options.outputDir)
|
|
12448
|
-
writeDiagnosticsDump(
|
|
12785
|
+
writeDiagnosticsDump(path13.resolve(process.cwd(), options.outputDir), report);
|
|
12449
12786
|
const config = loadConfigWithSource(process.cwd()).config;
|
|
12450
12787
|
const showScore = options.showScore ?? true;
|
|
12451
12788
|
const verbose = options.verbose ?? config.verbose ?? false;
|
|
@@ -12462,6 +12799,13 @@ ${os2.release()}
|
|
|
12462
12799
|
} else
|
|
12463
12800
|
process.stdout.write(`${renderTextReport(report, showScore, colorized, verbose, process.stdout.columns ?? 120)}
|
|
12464
12801
|
`);
|
|
12802
|
+
if (updateNoticeEnabled) {
|
|
12803
|
+
const update = getCachedUpdateNotice(VERSION2);
|
|
12804
|
+
if (update)
|
|
12805
|
+
process.stdout.write(`
|
|
12806
|
+
${renderUpdateNotice(update.current, update.latest, colorized)}
|
|
12807
|
+
`);
|
|
12808
|
+
}
|
|
12465
12809
|
const blocking = options.blocking ?? config.blocking ?? "error";
|
|
12466
12810
|
if (shouldBlock2(report, blocking))
|
|
12467
12811
|
process.exitCode = 1;
|
|
@@ -12474,5 +12818,5 @@ ${os2.release()}
|
|
|
12474
12818
|
}
|
|
12475
12819
|
main();
|
|
12476
12820
|
|
|
12477
|
-
//# debugId=
|
|
12821
|
+
//# debugId=32BB85E7FBDD457564756E2164756E21
|
|
12478
12822
|
//# sourceMappingURL=cli.js.map
|