@colrealpro/react-luau-doctor 0.17.0 → 0.17.1
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 +20 -12
- package/dist/cli.js +93 -344
- package/dist/cli.js.map +13 -13
- package/docs/ci.md +11 -15
- package/docs/cli.md +4 -4
- package/docs/development.md +18 -17
- package/docs/rules.md +1 -1
- package/package.json +6 -7
- package/action.yml +0 -71
- package/dist/runtime-lock.json +0 -29
package/dist/cli.js
CHANGED
|
@@ -11,8 +11,8 @@ var package_default = {
|
|
|
11
11
|
publishConfig: {
|
|
12
12
|
access: "public"
|
|
13
13
|
},
|
|
14
|
-
version: "0.17.
|
|
15
|
-
description: "
|
|
14
|
+
version: "0.17.1",
|
|
15
|
+
description: "Static analysis for React-Luau hooks, effects, rendering, and performance.",
|
|
16
16
|
license: "MIT",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
@@ -24,8 +24,7 @@ var package_default = {
|
|
|
24
24
|
"README.md",
|
|
25
25
|
"LICENSE",
|
|
26
26
|
"THIRD_PARTY_NOTICES.md",
|
|
27
|
-
"docs"
|
|
28
|
-
"action.yml"
|
|
27
|
+
"docs"
|
|
29
28
|
],
|
|
30
29
|
scripts: {
|
|
31
30
|
build: "bun run scripts/build.ts",
|
|
@@ -37,14 +36,14 @@ var package_default = {
|
|
|
37
36
|
"verify:package": "bun run build && bun run scripts/verify-package.ts"
|
|
38
37
|
},
|
|
39
38
|
engines: {
|
|
40
|
-
bun: ">=1.
|
|
39
|
+
bun: ">=1.4.0"
|
|
41
40
|
},
|
|
42
|
-
packageManager: "bun@1.
|
|
41
|
+
packageManager: "bun@1.4.0",
|
|
43
42
|
dependencies: {
|
|
44
43
|
"web-tree-sitter": "0.26.11"
|
|
45
44
|
},
|
|
46
45
|
devDependencies: {
|
|
47
|
-
"@types/bun": "1.
|
|
46
|
+
"@types/bun": "1.4.0",
|
|
48
47
|
typescript: "5.8.3"
|
|
49
48
|
},
|
|
50
49
|
keywords: [
|
|
@@ -67,21 +66,18 @@ var package_default = {
|
|
|
67
66
|
// src/config.ts
|
|
68
67
|
import fs from "fs";
|
|
69
68
|
import path from "path";
|
|
70
|
-
var
|
|
69
|
+
var CONFIG_NAME = "react-luau-doctor.config.json";
|
|
71
70
|
var SEVERITIES = new Set(["off", "error", "warning", "suggestion"]);
|
|
72
71
|
var SCOPES = new Set(["full", "files", "changed", "lines"]);
|
|
73
72
|
var BLOCKING_LEVELS = new Set(["error", "warning", "none"]);
|
|
74
73
|
var CATEGORIES = new Set(["Correctness", "Hooks", "Effects", "Performance", "Roblox", "Architecture"]);
|
|
75
74
|
function loadConfigWithSource(root) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return { config: parsed, filename: candidate };
|
|
83
|
-
}
|
|
84
|
-
return { config: {}, filename: null };
|
|
75
|
+
const candidate = path.join(root, CONFIG_NAME);
|
|
76
|
+
if (!fs.existsSync(candidate))
|
|
77
|
+
return { config: {}, filename: null };
|
|
78
|
+
const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
79
|
+
validateConfig(parsed, candidate);
|
|
80
|
+
return { config: parsed, filename: candidate };
|
|
85
81
|
}
|
|
86
82
|
function loadConfig(root) {
|
|
87
83
|
return loadConfigWithSource(root).config;
|
|
@@ -96,8 +92,6 @@ function validateConfig(config, filename) {
|
|
|
96
92
|
validateStringArray(config.include, "include", filename);
|
|
97
93
|
if (config.ignore !== undefined)
|
|
98
94
|
validateStringArray(config.ignore, "ignore", filename);
|
|
99
|
-
if (config.ignoredTags !== undefined)
|
|
100
|
-
validateStringArray(config.ignoredTags, "ignoredTags", filename);
|
|
101
95
|
if (config.projects !== undefined)
|
|
102
96
|
validateStringArray(config.projects, "projects", filename);
|
|
103
97
|
if (config.categories !== undefined) {
|
|
@@ -129,22 +123,8 @@ function validateConfig(config, filename) {
|
|
|
129
123
|
if (config.blocking !== undefined && !BLOCKING_LEVELS.has(config.blocking)) {
|
|
130
124
|
throw new Error(`${filename}: blocking must be error, warning, or none`);
|
|
131
125
|
}
|
|
132
|
-
if (config.failOn !== undefined && !BLOCKING_LEVELS.has(config.failOn)) {
|
|
133
|
-
throw new Error(`${filename}: failOn must be error, warning, or none`);
|
|
134
|
-
}
|
|
135
126
|
}
|
|
136
|
-
function
|
|
137
|
-
return [...new Set([rule.category.toLowerCase(), ...(rule.tags ?? []).map((tag) => tag.toLowerCase())])];
|
|
138
|
-
}
|
|
139
|
-
function ruleFrameworks(rule) {
|
|
140
|
-
if (rule.frameworks && rule.frameworks.length > 0)
|
|
141
|
-
return rule.frameworks.map((framework) => framework.toLowerCase());
|
|
142
|
-
return rule.category === "Roblox" ? ["react-luau", "roblox"] : ["react-luau", "global"];
|
|
143
|
-
}
|
|
144
|
-
function effectiveSeverity(defaultSeverity, ruleId, config, tags = []) {
|
|
145
|
-
const ignoredTags = new Set((config.ignoredTags ?? []).map((tag) => tag.toLowerCase()));
|
|
146
|
-
if (tags.some((tag) => ignoredTags.has(tag.toLowerCase())))
|
|
147
|
-
return null;
|
|
127
|
+
function effectiveSeverity(defaultSeverity, ruleId, config) {
|
|
148
128
|
const configured = config.rules?.[ruleId];
|
|
149
129
|
if (!configured)
|
|
150
130
|
return defaultSeverity;
|
|
@@ -152,12 +132,12 @@ function effectiveSeverity(defaultSeverity, ruleId, config, tags = []) {
|
|
|
152
132
|
}
|
|
153
133
|
function configPathForWrite(root) {
|
|
154
134
|
const loaded = loadConfigWithSource(root);
|
|
155
|
-
return loaded.filename ?? path.join(root,
|
|
135
|
+
return loaded.filename ?? path.join(root, CONFIG_NAME);
|
|
156
136
|
}
|
|
157
137
|
function writeConfig(root, update) {
|
|
158
138
|
const loaded = loadConfigWithSource(root);
|
|
159
139
|
const next = update(structuredClone(loaded.config));
|
|
160
|
-
validateConfig(next, loaded.filename ??
|
|
140
|
+
validateConfig(next, loaded.filename ?? CONFIG_NAME);
|
|
161
141
|
const filename = loaded.filename ?? configPathForWrite(root);
|
|
162
142
|
fs.mkdirSync(path.dirname(filename), { recursive: true });
|
|
163
143
|
fs.writeFileSync(filename, `${JSON.stringify(next, null, 2)}
|
|
@@ -582,6 +562,7 @@ function createGitScopePlan(scanRootInput, options, relativeRootInput = scanRoot
|
|
|
582
562
|
}
|
|
583
563
|
|
|
584
564
|
// src/reporter.ts
|
|
565
|
+
var VERSION = package_default.version;
|
|
585
566
|
var SYMBOLS = {
|
|
586
567
|
error: "x",
|
|
587
568
|
warning: "!",
|
|
@@ -744,7 +725,7 @@ function renderCounts(report, colorized) {
|
|
|
744
725
|
].join(", ");
|
|
745
726
|
}
|
|
746
727
|
function renderHeader(report, colorized) {
|
|
747
|
-
const title = colorized ? paint("React-Luau Doctor", ANSI.bold, ANSI.magenta) :
|
|
728
|
+
const title = colorized ? `${paint("React-Luau Doctor", ANSI.bold, ANSI.magenta)} ${paint(`v${VERSION}`, ANSI.dim)}` : `React-Luau Doctor v${VERSION}`;
|
|
748
729
|
const scopeSuffix = report.scope && report.scope !== "full" ? ` (${report.scope} scope)` : "";
|
|
749
730
|
const lines2 = [title, `Scanned ${report.scannedFiles} React-Luau files in ${report.durationMs.toFixed(2)}ms${scopeSuffix}`];
|
|
750
731
|
if (report.partial) {
|
|
@@ -3237,7 +3218,7 @@ function directNodes(body) {
|
|
|
3237
3218
|
function topLevelFunctionRecords(record) {
|
|
3238
3219
|
const functions = [];
|
|
3239
3220
|
let anonymousExportIndex = 0;
|
|
3240
|
-
const add = (node, body,
|
|
3221
|
+
const add = (node, body, localName, memberName, method, exported) => {
|
|
3241
3222
|
if (!body)
|
|
3242
3223
|
return;
|
|
3243
3224
|
const key = memberName ? `member:${memberName}` : localName ? `local:${localName}` : `export:${anonymousExportIndex++}`;
|
|
@@ -3262,9 +3243,9 @@ function topLevelFunctionRecords(record) {
|
|
|
3262
3243
|
const text = nameNode?.text.replace(/\s+/g, "") ?? "";
|
|
3263
3244
|
const methodMatch = text.match(/^([A-Za-z_][A-Za-z0-9_]*)[:.]([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
3264
3245
|
if (methodMatch && methodMatch[1] === record.exportName) {
|
|
3265
|
-
add(node, body,
|
|
3246
|
+
add(node, body, null, methodMatch[2], nameNode?.type === "method_index_expression", false);
|
|
3266
3247
|
} else if (nameNode?.type === "identifier") {
|
|
3267
|
-
add(node, body, text,
|
|
3248
|
+
add(node, body, text, null, false, record.exportName === text);
|
|
3268
3249
|
}
|
|
3269
3250
|
continue;
|
|
3270
3251
|
}
|
|
@@ -3274,7 +3255,7 @@ function topLevelFunctionRecords(record) {
|
|
|
3274
3255
|
const expression = expressions[index] ?? expressions[0];
|
|
3275
3256
|
if (expression?.type !== "function_definition")
|
|
3276
3257
|
continue;
|
|
3277
|
-
add(expression, child(expression, "block"), names[index],
|
|
3258
|
+
add(expression, child(expression, "block"), names[index], null, false, record.exportName === names[index]);
|
|
3278
3259
|
}
|
|
3279
3260
|
continue;
|
|
3280
3261
|
}
|
|
@@ -3287,7 +3268,7 @@ function topLevelFunctionRecords(record) {
|
|
|
3287
3268
|
const text = left[index].text.replace(/\s+/g, "");
|
|
3288
3269
|
const memberMatch = text.match(/^([A-Za-z_][A-Za-z0-9_]*)[.:]([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
3289
3270
|
if (memberMatch && memberMatch[1] === record.exportName) {
|
|
3290
|
-
add(expression, child(expression, "block"),
|
|
3271
|
+
add(expression, child(expression, "block"), null, memberMatch[2], text.includes(":"), false);
|
|
3291
3272
|
}
|
|
3292
3273
|
}
|
|
3293
3274
|
continue;
|
|
@@ -3296,7 +3277,7 @@ function topLevelFunctionRecords(record) {
|
|
|
3296
3277
|
const expressions = child(node, "expression_list");
|
|
3297
3278
|
const expression = expressions?.namedChildren[0];
|
|
3298
3279
|
if (expression?.type === "function_definition")
|
|
3299
|
-
add(expression, child(expression, "block"), null, null,
|
|
3280
|
+
add(expression, child(expression, "block"), null, null, false, true);
|
|
3300
3281
|
}
|
|
3301
3282
|
}
|
|
3302
3283
|
return functions;
|
|
@@ -5161,7 +5142,7 @@ function declarationExpressions3(node) {
|
|
|
5161
5142
|
const expressionList = assignment?.namedChildren.find((child2) => child2.type === "expression_list");
|
|
5162
5143
|
return expressionList?.namedChildren ?? [];
|
|
5163
5144
|
}
|
|
5164
|
-
function directCallsInExpression(node
|
|
5145
|
+
function directCallsInExpression(node) {
|
|
5165
5146
|
const calls = [];
|
|
5166
5147
|
const pending = [node];
|
|
5167
5148
|
while (pending.length > 0) {
|
|
@@ -5213,7 +5194,7 @@ function customHookDependencyHints(owner, context) {
|
|
|
5213
5194
|
const expression = expressions[index] ?? expressions[0];
|
|
5214
5195
|
if (!expression)
|
|
5215
5196
|
continue;
|
|
5216
|
-
const calls = directCallsInExpression(expression
|
|
5197
|
+
const calls = directCallsInExpression(expression);
|
|
5217
5198
|
if (calls.length === 0)
|
|
5218
5199
|
continue;
|
|
5219
5200
|
const directHook = calls.find((call) => isCustomHookCall(call, context));
|
|
@@ -6339,7 +6320,7 @@ function isIdentifierPropertyName2(node) {
|
|
|
6339
6320
|
return sameNode(parent.childForFieldName("method"), node) || sameNode(parent.namedChildren.at(-1), node);
|
|
6340
6321
|
return false;
|
|
6341
6322
|
}
|
|
6342
|
-
function isReadNode(node, valueName, owner, declaration
|
|
6323
|
+
function isReadNode(node, valueName, owner, declaration) {
|
|
6343
6324
|
if (node.type !== "identifier" || node.text !== valueName)
|
|
6344
6325
|
return false;
|
|
6345
6326
|
if (isInside2(node, declaration))
|
|
@@ -6359,7 +6340,7 @@ function isReadNode(node, valueName, owner, declaration, context) {
|
|
|
6359
6340
|
function hasAnyRead(valueName, owner, declaration, context) {
|
|
6360
6341
|
if (!owner.body)
|
|
6361
6342
|
return false;
|
|
6362
|
-
return [...context.walk(owner.body)].some((node) => isReadNode(node, valueName, owner, declaration
|
|
6343
|
+
return [...context.walk(owner.body)].some((node) => isReadNode(node, valueName, owner, declaration));
|
|
6363
6344
|
}
|
|
6364
6345
|
function isCreateElementCall2(call, context) {
|
|
6365
6346
|
return call.type === "function_call" && context.resolveCallPath(context.getCallPath(call) ?? "") === "React.createElement";
|
|
@@ -6436,7 +6417,7 @@ function stateReadsAreBindingCompatible(valueName, owner, declaration, context,
|
|
|
6436
6417
|
visited.add(visitKey);
|
|
6437
6418
|
let reads = 0;
|
|
6438
6419
|
for (const node of context.walk(owner.body)) {
|
|
6439
|
-
if (!isReadNode(node, valueName, owner, declaration
|
|
6420
|
+
if (!isReadNode(node, valueName, owner, declaration))
|
|
6440
6421
|
continue;
|
|
6441
6422
|
reads += 1;
|
|
6442
6423
|
if (isTransparentUsage(node, context, compatibleComponentProps))
|
|
@@ -6721,7 +6702,7 @@ function bindingUsageSummary(valueName, owner, declaration, context, compatibleC
|
|
|
6721
6702
|
if (!owner.body)
|
|
6722
6703
|
return summary;
|
|
6723
6704
|
for (const node of context.walk(owner.body)) {
|
|
6724
|
-
if (!isReadNode(node, valueName, owner, declaration
|
|
6705
|
+
if (!isReadNode(node, valueName, owner, declaration))
|
|
6725
6706
|
continue;
|
|
6726
6707
|
summary.reads += 1;
|
|
6727
6708
|
const customComponentUsage = createElementPropUsage(node, context);
|
|
@@ -7519,7 +7500,7 @@ function stableShapeVariablesByFunction(context) {
|
|
|
7519
7500
|
}
|
|
7520
7501
|
return stable;
|
|
7521
7502
|
}
|
|
7522
|
-
function loopIterationIsProvablyStable(loop,
|
|
7503
|
+
function loopIterationIsProvablyStable(loop, imports, owner, stableShapes) {
|
|
7523
7504
|
const numeric = loop.namedChildren.find((child2) => child2.type === "for_numeric_clause");
|
|
7524
7505
|
if (numeric) {
|
|
7525
7506
|
const text = numeric.text;
|
|
@@ -7723,7 +7704,7 @@ end`,
|
|
|
7723
7704
|
if (controlFlow) {
|
|
7724
7705
|
if (currentModeSummary && currentModeSummary.knownCallSites > 0 && controlledParameterIndex(controlFlow, fn, currentModeSummary) !== null)
|
|
7725
7706
|
continue;
|
|
7726
|
-
if (controlFlow.type === "for_statement" && loopIterationIsProvablyStable(controlFlow,
|
|
7707
|
+
if (controlFlow.type === "for_statement" && loopIterationIsProvablyStable(controlFlow, staticImports, fn, stableShapes))
|
|
7727
7708
|
continue;
|
|
7728
7709
|
const kind = controlFlow.type.replaceAll("_", " ");
|
|
7729
7710
|
const loopLike = controlFlow.type === "for_statement" || controlFlow.type === "while_statement" || controlFlow.type === "repeat_statement";
|
|
@@ -8319,8 +8300,7 @@ async function scanPath(target = ".", options = {}) {
|
|
|
8319
8300
|
minSeverity,
|
|
8320
8301
|
categories: options.categories ? [...options.categories].sort() : [],
|
|
8321
8302
|
respectInlineDisables,
|
|
8322
|
-
rules: config.rules ?? {}
|
|
8323
|
-
ignoredTags: [...config.ignoredTags ?? []].sort()
|
|
8303
|
+
rules: config.rules ?? {}
|
|
8324
8304
|
});
|
|
8325
8305
|
const canCacheWholeReport = canPersistCache && targetIsProjectRoot && options.files === undefined && deadlineAt === undefined;
|
|
8326
8306
|
const previousReport = canCacheWholeReport ? previousCachedReport(cacheSession, reportCacheKey) : null;
|
|
@@ -8442,7 +8422,7 @@ async function scanPath(target = ".", options = {}) {
|
|
|
8442
8422
|
for (const rule of rules) {
|
|
8443
8423
|
if (categorySet && !categorySet.has(rule.category))
|
|
8444
8424
|
continue;
|
|
8445
|
-
const severity = effectiveSeverity(rule.severity, rule.id, config
|
|
8425
|
+
const severity = effectiveSeverity(rule.severity, rule.id, config);
|
|
8446
8426
|
if (!severity)
|
|
8447
8427
|
continue;
|
|
8448
8428
|
const findings = rule.run(context);
|
|
@@ -8648,12 +8628,11 @@ var DEFAULT_SETTINGS = {
|
|
|
8648
8628
|
project: "*"
|
|
8649
8629
|
};
|
|
8650
8630
|
var GITHUB_WORKFLOW = path11.join(".github", "workflows", "react-luau-doctor.yml");
|
|
8651
|
-
var GITHUB_LOCAL_ACTION = path11.join(".github", "actions", "react-luau-doctor", "action.yml");
|
|
8652
8631
|
var GITLAB_WORKFLOW = ".gitlab-ci.yml";
|
|
8653
8632
|
var SUMMARY_MARKER = "<!-- react-luau-doctor:summary -->";
|
|
8654
8633
|
var REVIEW_MARKER = "<!-- react-luau-doctor:review -->";
|
|
8655
8634
|
var MAX_REVIEW_COMMENTS = 20;
|
|
8656
|
-
var
|
|
8635
|
+
var PACKAGE_SPEC = `${package_default.name}@${package_default.version}`;
|
|
8657
8636
|
function yamlString(value) {
|
|
8658
8637
|
return JSON.stringify(value);
|
|
8659
8638
|
}
|
|
@@ -8682,92 +8661,25 @@ jobs:
|
|
|
8682
8661
|
- uses: actions/checkout@v5
|
|
8683
8662
|
with:
|
|
8684
8663
|
fetch-depth: 0
|
|
8685
|
-
- uses:
|
|
8664
|
+
- uses: oven-sh/setup-bun@v2
|
|
8686
8665
|
with:
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
description: "Severity that fails the workflow: error, warning, or none."
|
|
8705
|
-
default: none
|
|
8706
|
-
scope:
|
|
8707
|
-
description: "Pull request scan scope: changed, files, lines, or full."
|
|
8708
|
-
default: changed
|
|
8709
|
-
comment:
|
|
8710
|
-
description: "Create or update a sticky pull request summary comment."
|
|
8711
|
-
default: "true"
|
|
8712
|
-
review-comments:
|
|
8713
|
-
description: "Post inline review comments on changed lines."
|
|
8714
|
-
default: "true"
|
|
8715
|
-
commit-status:
|
|
8716
|
-
description: "Publish score and issue counts as a commit status."
|
|
8717
|
-
default: "true"
|
|
8718
|
-
project:
|
|
8719
|
-
description: "Project directories to scan, comma-separated. Asterisk scans the whole directory."
|
|
8720
|
-
default: "*"
|
|
8721
|
-
directory:
|
|
8722
|
-
description: "Project directory to scan."
|
|
8723
|
-
default: "."
|
|
8724
|
-
outputs:
|
|
8725
|
-
score:
|
|
8726
|
-
description: "Health score from 0 to 100."
|
|
8727
|
-
value: \${{ steps.doctor.outputs.score }}
|
|
8728
|
-
total-issues:
|
|
8729
|
-
description: "Total diagnostics reported by this run."
|
|
8730
|
-
value: \${{ steps.doctor.outputs.total-issues }}
|
|
8731
|
-
fixed-issues:
|
|
8732
|
-
description: "Number of diagnostics resolved by the pull request."
|
|
8733
|
-
value: \${{ steps.doctor.outputs.fixed-issues }}
|
|
8734
|
-
error-count:
|
|
8735
|
-
description: "Error diagnostic count."
|
|
8736
|
-
value: \${{ steps.doctor.outputs.error-count }}
|
|
8737
|
-
warning-count:
|
|
8738
|
-
description: "Warning diagnostic count."
|
|
8739
|
-
value: \${{ steps.doctor.outputs.warning-count }}
|
|
8740
|
-
affected-files:
|
|
8741
|
-
description: "Number of files with diagnostics."
|
|
8742
|
-
value: \${{ steps.doctor.outputs.affected-files }}
|
|
8743
|
-
runs:
|
|
8744
|
-
using: composite
|
|
8745
|
-
steps:
|
|
8746
|
-
- uses: oven-sh/setup-bun@v2
|
|
8747
|
-
with:
|
|
8748
|
-
bun-version: "1.3.14"
|
|
8749
|
-
- name: Install parser runtime
|
|
8750
|
-
shell: bash
|
|
8751
|
-
run: |
|
|
8752
|
-
cd "$GITHUB_ACTION_PATH"
|
|
8753
|
-
bun install --production --frozen-lockfile
|
|
8754
|
-
- id: doctor
|
|
8755
|
-
shell: bash
|
|
8756
|
-
env:
|
|
8757
|
-
GITHUB_TOKEN: \${{ github.token }}
|
|
8758
|
-
DOCTOR_DIRECTORY: \${{ inputs.directory }}
|
|
8759
|
-
DOCTOR_PROJECT: \${{ inputs.project }}
|
|
8760
|
-
DOCTOR_BLOCKING: \${{ inputs.blocking }}
|
|
8761
|
-
DOCTOR_SCOPE: \${{ inputs.scope }}
|
|
8762
|
-
run: >-
|
|
8763
|
-
bun "$GITHUB_ACTION_PATH/dist/cli.js" ci run
|
|
8764
|
-
--directory "$DOCTOR_DIRECTORY"
|
|
8765
|
-
--project "$DOCTOR_PROJECT"
|
|
8766
|
-
--blocking "$DOCTOR_BLOCKING"
|
|
8767
|
-
--scope "$DOCTOR_SCOPE"
|
|
8768
|
-
\${{ inputs.comment == 'true' && '--comment' || '--no-comment' }}
|
|
8769
|
-
\${{ inputs.review-comments == 'true' && '--review-comments' || '--no-review-comments' }}
|
|
8770
|
-
\${{ inputs.commit-status == 'true' && '--commit-status' || '--no-commit-status' }}
|
|
8666
|
+
bun-version: "1.4.0"
|
|
8667
|
+
- id: doctor
|
|
8668
|
+
env:
|
|
8669
|
+
GITHUB_TOKEN: \${{ github.token }}
|
|
8670
|
+
DOCTOR_DIRECTORY: ${yamlString(settings.directory)}
|
|
8671
|
+
DOCTOR_PROJECT: ${yamlString(settings.project)}
|
|
8672
|
+
DOCTOR_BLOCKING: ${settings.blocking}
|
|
8673
|
+
DOCTOR_SCOPE: ${settings.scope}
|
|
8674
|
+
run: >-
|
|
8675
|
+
bunx --bun ${PACKAGE_SPEC} ci run
|
|
8676
|
+
--directory "$DOCTOR_DIRECTORY"
|
|
8677
|
+
--project "$DOCTOR_PROJECT"
|
|
8678
|
+
--blocking "$DOCTOR_BLOCKING"
|
|
8679
|
+
--scope "$DOCTOR_SCOPE"
|
|
8680
|
+
${settings.comment ? "--comment" : "--no-comment"}
|
|
8681
|
+
${settings.reviewComments ? "--review-comments" : "--no-review-comments"}
|
|
8682
|
+
${settings.commitStatus ? "--commit-status" : "--no-commit-status"}
|
|
8771
8683
|
`;
|
|
8772
8684
|
}
|
|
8773
8685
|
function renderGitLabWorkflow(settings) {
|
|
@@ -8776,41 +8688,16 @@ function renderGitLabWorkflow(settings) {
|
|
|
8776
8688
|
|
|
8777
8689
|
react-luau-doctor:
|
|
8778
8690
|
stage: test
|
|
8779
|
-
image: oven/bun:1.
|
|
8691
|
+
image: oven/bun:1.4.0
|
|
8780
8692
|
variables:
|
|
8781
8693
|
GIT_DEPTH: "0"
|
|
8782
8694
|
DOCTOR_DIRECTORY: ${yamlString(settings.directory)}
|
|
8783
8695
|
DOCTOR_SCOPE: ${yamlString(settings.scope)}
|
|
8784
8696
|
DOCTOR_BLOCKING: ${yamlString(settings.blocking)}
|
|
8785
8697
|
script:
|
|
8786
|
-
- '
|
|
8787
|
-
- 'bun .gitlab/react-luau-doctor/dist/cli.js "$DOCTOR_DIRECTORY" --scope "$DOCTOR_SCOPE" --blocking "$DOCTOR_BLOCKING" --no-color'
|
|
8698
|
+
- 'bunx --bun ${PACKAGE_SPEC} "$DOCTOR_DIRECTORY" --scope "$DOCTOR_SCOPE" --blocking "$DOCTOR_BLOCKING" --no-color'
|
|
8788
8699
|
`;
|
|
8789
8700
|
}
|
|
8790
|
-
function copyCiRuntime(destination) {
|
|
8791
|
-
const root = path11.resolve(import.meta.dir, "..");
|
|
8792
|
-
const entries = ["dist", "vendor", "package.json", "LICENSE", "THIRD_PARTY_NOTICES.md"];
|
|
8793
|
-
for (const entry of entries) {
|
|
8794
|
-
if (!fs9.existsSync(path11.join(root, entry)))
|
|
8795
|
-
throw new Error(`Missing ${entry}; run bun run build before installing CI`);
|
|
8796
|
-
}
|
|
8797
|
-
const lockfile = fs9.existsSync(path11.join(root, "bun.lock")) ? path11.join(root, "bun.lock") : path11.join(root, "dist/runtime-lock.json");
|
|
8798
|
-
if (!fs9.existsSync(lockfile))
|
|
8799
|
-
throw new Error("Missing runtime lockfile; rebuild or reinstall Doctor");
|
|
8800
|
-
fs9.mkdirSync(destination, { recursive: true });
|
|
8801
|
-
const copiedLock = path11.join(destination, "bun.lock");
|
|
8802
|
-
if (path11.resolve(lockfile) !== path11.resolve(copiedLock))
|
|
8803
|
-
fs9.copyFileSync(lockfile, copiedLock);
|
|
8804
|
-
const files = [copiedLock];
|
|
8805
|
-
for (const entry of entries) {
|
|
8806
|
-
const target = path11.join(destination, entry);
|
|
8807
|
-
if (path11.resolve(root, entry) === path11.resolve(target))
|
|
8808
|
-
continue;
|
|
8809
|
-
fs9.cpSync(path11.join(root, entry), target, { recursive: true });
|
|
8810
|
-
files.push(target);
|
|
8811
|
-
}
|
|
8812
|
-
return files;
|
|
8813
|
-
}
|
|
8814
8701
|
function parseBoolean(value, name) {
|
|
8815
8702
|
if (value === "true")
|
|
8816
8703
|
return true;
|
|
@@ -8887,24 +8774,22 @@ function parseManagedWorkflowSettings(filename) {
|
|
|
8887
8774
|
if (!fs9.existsSync(filename))
|
|
8888
8775
|
return {};
|
|
8889
8776
|
const text = fs9.readFileSync(filename, "utf8");
|
|
8890
|
-
if (filename.endsWith(".gitlab-ci.yml")) {
|
|
8891
|
-
const scope2 = text.match(/--scope\s+(full|files|changed|lines)/)?.[1];
|
|
8892
|
-
const blocking2 = text.match(/--blocking\s+(error|warning|none)/)?.[1];
|
|
8893
|
-
return {
|
|
8894
|
-
provider: "gitlab",
|
|
8895
|
-
scope: scope2 ? parseScope(scope2) : DEFAULT_SETTINGS.scope,
|
|
8896
|
-
blocking: blocking2 ? parseBlocking(blocking2) : "none"
|
|
8897
|
-
};
|
|
8898
|
-
}
|
|
8899
8777
|
const match = (name) => text.match(new RegExp(`^\\s*${name}:\\s*(.+?)\\s*$`, "m"))?.[1]?.replace(/^['"]|['"]$/g, "");
|
|
8900
|
-
const result = {};
|
|
8901
|
-
const blocking = match("blocking");
|
|
8902
|
-
const scope = match("scope");
|
|
8903
|
-
const
|
|
8904
|
-
const
|
|
8905
|
-
const
|
|
8906
|
-
|
|
8907
|
-
|
|
8778
|
+
const result = { provider: filename.endsWith(".gitlab-ci.yml") ? "gitlab" : "github" };
|
|
8779
|
+
const blocking = match("DOCTOR_BLOCKING") ?? match("blocking");
|
|
8780
|
+
const scope = match("DOCTOR_SCOPE") ?? match("scope");
|
|
8781
|
+
const project = match("DOCTOR_PROJECT") ?? match("project");
|
|
8782
|
+
const directory = match("DOCTOR_DIRECTORY") ?? match("directory");
|
|
8783
|
+
const flag = (enabled, disabled) => {
|
|
8784
|
+
if (text.includes(disabled))
|
|
8785
|
+
return false;
|
|
8786
|
+
if (text.includes(enabled))
|
|
8787
|
+
return true;
|
|
8788
|
+
return;
|
|
8789
|
+
};
|
|
8790
|
+
const comment = match("comment") ?? flag("--comment", "--no-comment")?.toString();
|
|
8791
|
+
const reviewComments = match("review-comments") ?? flag("--review-comments", "--no-review-comments")?.toString();
|
|
8792
|
+
const commitStatus = match("commit-status") ?? flag("--commit-status", "--no-commit-status")?.toString();
|
|
8908
8793
|
if (blocking)
|
|
8909
8794
|
result.blocking = parseBlocking(blocking);
|
|
8910
8795
|
if (scope)
|
|
@@ -8954,21 +8839,16 @@ async function completeSettings(options, current, action) {
|
|
|
8954
8839
|
function writeManagedCi(cwd, settings) {
|
|
8955
8840
|
if (settings.provider === "github") {
|
|
8956
8841
|
const workflow2 = path11.join(cwd, GITHUB_WORKFLOW);
|
|
8957
|
-
const action = path11.join(cwd, GITHUB_LOCAL_ACTION);
|
|
8958
|
-
const runtimeFiles2 = copyCiRuntime(path11.dirname(action));
|
|
8959
8842
|
fs9.mkdirSync(path11.dirname(workflow2), { recursive: true });
|
|
8960
|
-
fs9.mkdirSync(path11.dirname(action), { recursive: true });
|
|
8961
8843
|
fs9.writeFileSync(workflow2, renderGitHubWorkflow(settings));
|
|
8962
|
-
|
|
8963
|
-
return [workflow2, action, ...runtimeFiles2];
|
|
8844
|
+
return [workflow2];
|
|
8964
8845
|
}
|
|
8965
8846
|
const workflow = path11.join(cwd, GITLAB_WORKFLOW);
|
|
8966
8847
|
if (fs9.existsSync(workflow) && !fs9.readFileSync(workflow, "utf8").includes("react-luau-doctor")) {
|
|
8967
8848
|
throw new Error(".gitlab-ci.yml already exists and is not managed by React-Luau Doctor; add the generated job manually instead of overwriting it");
|
|
8968
8849
|
}
|
|
8969
|
-
const runtimeFiles = copyCiRuntime(path11.join(cwd, ".gitlab", "react-luau-doctor"));
|
|
8970
8850
|
fs9.writeFileSync(workflow, renderGitLabWorkflow(settings));
|
|
8971
|
-
return [workflow
|
|
8851
|
+
return [workflow];
|
|
8972
8852
|
}
|
|
8973
8853
|
function runCommand(cwd, command, args, allowFailure = false) {
|
|
8974
8854
|
const result = spawnSync2(command, args, { cwd, encoding: "utf8", windowsHide: true });
|
|
@@ -9293,7 +9173,7 @@ function parseCiRunOptions(argv) {
|
|
|
9293
9173
|
}
|
|
9294
9174
|
return settings;
|
|
9295
9175
|
}
|
|
9296
|
-
async function
|
|
9176
|
+
async function runCiJob(argv) {
|
|
9297
9177
|
const settings = parseCiRunOptions(argv);
|
|
9298
9178
|
const eventName = process.env.GITHUB_EVENT_NAME ?? "";
|
|
9299
9179
|
const event = githubEvent();
|
|
@@ -9354,7 +9234,7 @@ async function runCiCommand(argv) {
|
|
|
9354
9234
|
return;
|
|
9355
9235
|
}
|
|
9356
9236
|
if (action === "run") {
|
|
9357
|
-
await
|
|
9237
|
+
await runCiJob(argv.slice(1));
|
|
9358
9238
|
return;
|
|
9359
9239
|
}
|
|
9360
9240
|
throw new Error("ci requires install, config, or upgrade");
|
|
@@ -9830,7 +9710,7 @@ function createProgressRenderer(options = {}) {
|
|
|
9830
9710
|
}
|
|
9831
9711
|
|
|
9832
9712
|
// src/cli.ts
|
|
9833
|
-
var
|
|
9713
|
+
var VERSION2 = package_default.version;
|
|
9834
9714
|
var SCOPES2 = new Set(["full", "files", "changed", "lines"]);
|
|
9835
9715
|
var BLOCKING = new Set(["error", "warning", "none"]);
|
|
9836
9716
|
var SEVERITIES2 = new Set(["suggestion", "warning", "error"]);
|
|
@@ -9840,7 +9720,7 @@ process.stdout.on("error", (error) => {
|
|
|
9840
9720
|
throw error;
|
|
9841
9721
|
});
|
|
9842
9722
|
function usage() {
|
|
9843
|
-
return `React-Luau Doctor ${
|
|
9723
|
+
return `React-Luau Doctor ${VERSION2}
|
|
9844
9724
|
|
|
9845
9725
|
Usage:
|
|
9846
9726
|
react-luau-doctor [directory] [options]
|
|
@@ -9849,9 +9729,6 @@ Usage:
|
|
|
9849
9729
|
react-luau-doctor rules <command>
|
|
9850
9730
|
|
|
9851
9731
|
Scan options:
|
|
9852
|
-
--lint / --no-lint Enable or skip React-Luau analysis
|
|
9853
|
-
--dead-code / --no-dead-code React Doctor compatibility flag; no Luau dead-code pass yet
|
|
9854
|
-
--supply-chain / --no-supply-chain React Doctor compatibility flag; no Roblox supply-chain pass yet
|
|
9855
9732
|
--verbose Show every finding and per-file details
|
|
9856
9733
|
--debug Print resolved local scan details to stderr
|
|
9857
9734
|
--output-dir <dir> Write report.json, diagnostics.json, and summary.json
|
|
@@ -9859,35 +9736,26 @@ Scan options:
|
|
|
9859
9736
|
--json Output one structured JSON report
|
|
9860
9737
|
--json-compact Emit compact JSON instead of indented JSON
|
|
9861
9738
|
--json-out <path> Write the JSON report to a file
|
|
9862
|
-
-y, --yes Skip prompts; accepted for React Doctor CLI compatibility
|
|
9863
|
-
--no-parallel Force serial scanning (the Luau analyzer is currently serial)
|
|
9864
9739
|
--project <name> Select project directories, comma-separated
|
|
9865
9740
|
--scope <value> full, files, changed, or lines
|
|
9866
|
-
--full Legacy alias for --scope full
|
|
9867
9741
|
--base <ref> Git base ref for files/changed/lines scope
|
|
9868
9742
|
--include-untracked Include untracked files in git scopes
|
|
9869
9743
|
--diff [base] Alias for changed scope; false forces full scope
|
|
9870
9744
|
--changed-files-from <file> Read changed paths from a newline-delimited file
|
|
9871
9745
|
--no-score Hide the local score
|
|
9872
9746
|
--category <category> Only report a category; repeatable
|
|
9873
|
-
--no-telemetry React Doctor-compatible alias for --no-score
|
|
9874
|
-
--offline Legacy alias for --no-telemetry
|
|
9875
9747
|
--staged Scan staged git-index content only
|
|
9876
9748
|
--max-duration <seconds> Stop after a shared scan time budget and report partial results
|
|
9877
9749
|
--blocking <level> Exit gate: error, warning, or none
|
|
9878
|
-
--fail-on <level> Legacy alias for --blocking
|
|
9879
9750
|
--respect-inline-disables Respect react-luau-doctor inline suppression comments
|
|
9880
9751
|
--no-respect-inline-disables Ignore inline suppressions for audit scans
|
|
9881
9752
|
--warnings / --no-warnings Show warnings (default) or errors only
|
|
9882
9753
|
--annotations Emit GitHub Actions workflow annotations
|
|
9883
|
-
--explain <file:line> Legacy inline form of the why command
|
|
9884
|
-
--why <file:line> Alias for --explain
|
|
9885
9754
|
--no-color Disable automatic ANSI colors
|
|
9886
9755
|
--no-cache Disable the persistent OS-level analysis cache
|
|
9887
9756
|
|
|
9888
9757
|
React-Luau Doctor options:
|
|
9889
9758
|
--min-severity <level> suggestion, warning, or error
|
|
9890
|
-
--rules List available rules
|
|
9891
9759
|
-v, --version Print the version
|
|
9892
9760
|
-h, --help Show this help
|
|
9893
9761
|
|
|
@@ -9898,17 +9766,15 @@ CI commands:
|
|
|
9898
9766
|
Reporting toggles: --comment/--no-comment, --review-comments/--no-review-comments, --commit-status/--no-commit-status
|
|
9899
9767
|
|
|
9900
9768
|
Rules commands:
|
|
9901
|
-
rules list [--category <name>] [--
|
|
9769
|
+
rules list [--category <name>] [--configured] [--json]
|
|
9902
9770
|
rules explain <rule> [--json]
|
|
9903
9771
|
rules set <rule> <severity>
|
|
9904
9772
|
rules enable <rule> [--severity <level>]
|
|
9905
9773
|
rules disable <rule>
|
|
9906
9774
|
rules category <category> <severity>
|
|
9907
|
-
rules ignore-tag <tag>
|
|
9908
|
-
rules unignore-tag <tag>
|
|
9909
9775
|
|
|
9910
9776
|
Config:
|
|
9911
|
-
react-luau-doctor.config.json
|
|
9777
|
+
react-luau-doctor.config.json
|
|
9912
9778
|
`;
|
|
9913
9779
|
}
|
|
9914
9780
|
function splitLongOption(arg) {
|
|
@@ -10309,17 +10175,12 @@ function parseArgs(argv) {
|
|
|
10309
10175
|
scoreOnly: false,
|
|
10310
10176
|
json: false,
|
|
10311
10177
|
jsonCompact: false,
|
|
10312
|
-
yes: false,
|
|
10313
|
-
noParallel: false,
|
|
10314
|
-
full: false,
|
|
10315
10178
|
includeUntracked: false,
|
|
10316
10179
|
categories: [],
|
|
10317
|
-
noTelemetry: false,
|
|
10318
10180
|
staged: false,
|
|
10319
10181
|
annotations: false,
|
|
10320
10182
|
noColor: false,
|
|
10321
10183
|
noCache: false,
|
|
10322
|
-
listRules: false,
|
|
10323
10184
|
help: false,
|
|
10324
10185
|
version: false
|
|
10325
10186
|
};
|
|
@@ -10327,19 +10188,7 @@ function parseArgs(argv) {
|
|
|
10327
10188
|
for (let index = 0;index < argv.length; index += 1) {
|
|
10328
10189
|
const raw = argv[index];
|
|
10329
10190
|
const { name: arg, inlineValue } = splitLongOption(raw);
|
|
10330
|
-
if (arg === "--
|
|
10331
|
-
options.lint = true;
|
|
10332
|
-
else if (arg === "--no-lint")
|
|
10333
|
-
options.lint = false;
|
|
10334
|
-
else if (arg === "--dead-code")
|
|
10335
|
-
options.deadCode = true;
|
|
10336
|
-
else if (arg === "--no-dead-code")
|
|
10337
|
-
options.deadCode = false;
|
|
10338
|
-
else if (arg === "--supply-chain")
|
|
10339
|
-
options.supplyChain = true;
|
|
10340
|
-
else if (arg === "--no-supply-chain")
|
|
10341
|
-
options.supplyChain = false;
|
|
10342
|
-
else if (arg === "--verbose")
|
|
10191
|
+
if (arg === "--verbose")
|
|
10343
10192
|
options.verbose = true;
|
|
10344
10193
|
else if (arg === "--debug")
|
|
10345
10194
|
options.debug = true;
|
|
@@ -10349,20 +10198,11 @@ function parseArgs(argv) {
|
|
|
10349
10198
|
options.json = true;
|
|
10350
10199
|
else if (arg === "--json-compact")
|
|
10351
10200
|
options.jsonCompact = true;
|
|
10352
|
-
else if (arg === "-y" || arg === "--yes")
|
|
10353
|
-
options.yes = true;
|
|
10354
|
-
else if (arg === "--no-parallel")
|
|
10355
|
-
options.noParallel = true;
|
|
10356
|
-
else if (arg === "--full")
|
|
10357
|
-
options.full = true;
|
|
10358
10201
|
else if (arg === "--include-untracked")
|
|
10359
10202
|
options.includeUntracked = true;
|
|
10360
10203
|
else if (arg === "--no-score")
|
|
10361
10204
|
options.showScore = false;
|
|
10362
|
-
else if (arg === "--
|
|
10363
|
-
options.noTelemetry = true;
|
|
10364
|
-
options.showScore = false;
|
|
10365
|
-
} else if (arg === "--staged")
|
|
10205
|
+
else if (arg === "--staged")
|
|
10366
10206
|
options.staged = true;
|
|
10367
10207
|
else if (arg === "--no-respect-inline-disables")
|
|
10368
10208
|
options.respectInlineDisables = false;
|
|
@@ -10378,8 +10218,6 @@ function parseArgs(argv) {
|
|
|
10378
10218
|
options.noCache = true;
|
|
10379
10219
|
else if (arg === "--annotations")
|
|
10380
10220
|
options.annotations = true;
|
|
10381
|
-
else if (arg === "--rules")
|
|
10382
|
-
options.listRules = true;
|
|
10383
10221
|
else if (arg === "--help" || arg === "-h")
|
|
10384
10222
|
options.help = true;
|
|
10385
10223
|
else if (arg === "--version" || arg === "-v")
|
|
@@ -10439,16 +10277,6 @@ function parseArgs(argv) {
|
|
|
10439
10277
|
const parsed = requiredValue(argv, index, arg, inlineValue);
|
|
10440
10278
|
options.blocking = parseBlocking2(parsed.value, arg);
|
|
10441
10279
|
index = parsed.nextIndex;
|
|
10442
|
-
} else if (arg === "--fail-on") {
|
|
10443
|
-
const parsed = requiredValue(argv, index, arg, inlineValue);
|
|
10444
|
-
options.failOn = parseBlocking2(parsed.value, arg);
|
|
10445
|
-
index = parsed.nextIndex;
|
|
10446
|
-
} else if (arg === "--explain" || arg === "--why") {
|
|
10447
|
-
const parsed = requiredValue(argv, index, arg, inlineValue);
|
|
10448
|
-
if (options.explain !== undefined)
|
|
10449
|
-
throw new Error("Use --explain or --why once; they are aliases");
|
|
10450
|
-
options.explain = parsed.value;
|
|
10451
|
-
index = parsed.nextIndex;
|
|
10452
10280
|
} else if (arg === "--min-severity") {
|
|
10453
10281
|
const parsed = requiredValue(argv, index, arg, inlineValue);
|
|
10454
10282
|
if (!SEVERITIES2.has(parsed.value))
|
|
@@ -10467,8 +10295,6 @@ function parseArgs(argv) {
|
|
|
10467
10295
|
return options;
|
|
10468
10296
|
}
|
|
10469
10297
|
function resolveScope(options, config) {
|
|
10470
|
-
if (options.full)
|
|
10471
|
-
return { scope: "full", base: options.base ?? config.base };
|
|
10472
10298
|
if (options.scope)
|
|
10473
10299
|
return { scope: options.scope, base: options.base ?? config.base };
|
|
10474
10300
|
if (options.diff !== undefined) {
|
|
@@ -10502,12 +10328,9 @@ function validateModeFlags(options, scope) {
|
|
|
10502
10328
|
if (options.scoreOnly && options.json)
|
|
10503
10329
|
throw new Error("Cannot combine --score and --json; pick one output mode");
|
|
10504
10330
|
if (options.scoreOnly && options.showScore === false)
|
|
10505
|
-
throw new Error("Cannot combine --score with --no-score
|
|
10331
|
+
throw new Error("Cannot combine --score with --no-score");
|
|
10506
10332
|
if (options.annotations && (options.json || options.scoreOnly))
|
|
10507
10333
|
throw new Error("--annotations cannot be combined with --json or --score");
|
|
10508
|
-
if (options.explain && (options.json || options.scoreOnly || options.annotations || options.staged)) {
|
|
10509
|
-
throw new Error("--explain cannot be combined with --json, --score, --annotations, or --staged");
|
|
10510
|
-
}
|
|
10511
10334
|
}
|
|
10512
10335
|
function findProjectByName(root, name) {
|
|
10513
10336
|
const direct = path12.resolve(root, name);
|
|
@@ -10599,7 +10422,7 @@ function parseCommandCwd(argv) {
|
|
|
10599
10422
|
return { cwd, remaining };
|
|
10600
10423
|
}
|
|
10601
10424
|
function ruleSeverityMap(config) {
|
|
10602
|
-
return new Map(rules.map((rule) => [rule.id, effectiveSeverity(rule.severity, rule.id, config
|
|
10425
|
+
return new Map(rules.map((rule) => [rule.id, effectiveSeverity(rule.severity, rule.id, config) ?? "off"]));
|
|
10603
10426
|
}
|
|
10604
10427
|
function runRulesCommand(argv) {
|
|
10605
10428
|
const noColor = argv.includes("--no-color");
|
|
@@ -10610,8 +10433,6 @@ function runRulesCommand(argv) {
|
|
|
10610
10433
|
const config = loaded.config;
|
|
10611
10434
|
if (action === "list") {
|
|
10612
10435
|
let category = null;
|
|
10613
|
-
let tag = null;
|
|
10614
|
-
let framework = null;
|
|
10615
10436
|
let configured = false;
|
|
10616
10437
|
let json = false;
|
|
10617
10438
|
for (let index = 0;index < remaining.length; index += 1) {
|
|
@@ -10627,14 +10448,6 @@ function runRulesCommand(argv) {
|
|
|
10627
10448
|
category = normalizeCategory(value);
|
|
10628
10449
|
if (!category)
|
|
10629
10450
|
throw new Error(`Unknown category: ${value}`);
|
|
10630
|
-
} else if (arg === "--tag") {
|
|
10631
|
-
tag = remaining[++index]?.toLowerCase() ?? null;
|
|
10632
|
-
if (!tag)
|
|
10633
|
-
throw new Error("rules list --tag requires a name");
|
|
10634
|
-
} else if (arg === "--framework") {
|
|
10635
|
-
framework = remaining[++index]?.toLowerCase() ?? null;
|
|
10636
|
-
if (!framework)
|
|
10637
|
-
throw new Error("rules list --framework requires a name");
|
|
10638
10451
|
} else
|
|
10639
10452
|
throw new Error(`Unknown rules list option: ${arg}`);
|
|
10640
10453
|
}
|
|
@@ -10642,13 +10455,8 @@ function runRulesCommand(argv) {
|
|
|
10642
10455
|
const filtered = rules.filter((rule) => {
|
|
10643
10456
|
if (category && rule.category !== category)
|
|
10644
10457
|
return false;
|
|
10645
|
-
if (
|
|
10458
|
+
if (configured && config.rules?.[rule.id] === undefined)
|
|
10646
10459
|
return false;
|
|
10647
|
-
if (framework && !ruleFrameworks(rule).includes(framework))
|
|
10648
|
-
return false;
|
|
10649
|
-
if (configured && config.rules?.[rule.id] === undefined && !(config.ignoredTags ?? []).some((ignored) => ruleTags(rule).includes(ignored.toLowerCase()))) {
|
|
10650
|
-
return false;
|
|
10651
|
-
}
|
|
10652
10460
|
return true;
|
|
10653
10461
|
});
|
|
10654
10462
|
if (json) {
|
|
@@ -10657,8 +10465,6 @@ function runRulesCommand(argv) {
|
|
|
10657
10465
|
severity: severityMap.get(rule.id),
|
|
10658
10466
|
defaultSeverity: rule.severity,
|
|
10659
10467
|
category: rule.category,
|
|
10660
|
-
tags: ruleTags(rule),
|
|
10661
|
-
frameworks: ruleFrameworks(rule),
|
|
10662
10468
|
description: rule.description
|
|
10663
10469
|
})), null, 2)}
|
|
10664
10470
|
`);
|
|
@@ -10676,14 +10482,12 @@ function runRulesCommand(argv) {
|
|
|
10676
10482
|
if (extra.length > 0)
|
|
10677
10483
|
throw new Error(`Unknown rules explain option: ${extra[0]}`);
|
|
10678
10484
|
const rule = findRule(requested);
|
|
10679
|
-
const currentSeverity = effectiveSeverity(rule.severity, rule.id, config
|
|
10485
|
+
const currentSeverity = effectiveSeverity(rule.severity, rule.id, config) ?? "off";
|
|
10680
10486
|
const result = {
|
|
10681
10487
|
id: rule.id,
|
|
10682
10488
|
severity: currentSeverity,
|
|
10683
10489
|
defaultSeverity: rule.severity,
|
|
10684
10490
|
category: rule.category,
|
|
10685
|
-
tags: ruleTags(rule),
|
|
10686
|
-
frameworks: ruleFrameworks(rule),
|
|
10687
10491
|
description: rule.description,
|
|
10688
10492
|
example: fixExampleForRule(rule.id)
|
|
10689
10493
|
};
|
|
@@ -10773,22 +10577,6 @@ function runRulesCommand(argv) {
|
|
|
10773
10577
|
])
|
|
10774
10578
|
}));
|
|
10775
10579
|
process.stdout.write(`Set ${categoryRules.length} ${category} rules to ${severity} in ${path12.relative(cwd, filename)}
|
|
10776
|
-
`);
|
|
10777
|
-
return;
|
|
10778
|
-
}
|
|
10779
|
-
if (action === "ignore-tag" || action === "unignore-tag") {
|
|
10780
|
-
const tag = remaining[0]?.toLowerCase();
|
|
10781
|
-
if (!tag)
|
|
10782
|
-
throw new Error(`rules ${action} requires a tag`);
|
|
10783
|
-
const filename = writeConfig(cwd, (current) => {
|
|
10784
|
-
const tags = new Set((current.ignoredTags ?? []).map((entry) => entry.toLowerCase()));
|
|
10785
|
-
if (action === "ignore-tag")
|
|
10786
|
-
tags.add(tag);
|
|
10787
|
-
else
|
|
10788
|
-
tags.delete(tag);
|
|
10789
|
-
return { ...current, ignoredTags: [...tags].sort() };
|
|
10790
|
-
});
|
|
10791
|
-
process.stdout.write(`${action === "ignore-tag" ? "Ignored" : "Unignored"} tag ${tag} in ${path12.relative(cwd, filename)}
|
|
10792
10580
|
`);
|
|
10793
10581
|
return;
|
|
10794
10582
|
}
|
|
@@ -11030,7 +10818,7 @@ function pathIsInside(parent, child2) {
|
|
|
11030
10818
|
const relative = path12.relative(parent, child2);
|
|
11031
10819
|
return relative === "" || !relative.startsWith("..") && !path12.isAbsolute(relative);
|
|
11032
10820
|
}
|
|
11033
|
-
async function runScan(options, onProgress
|
|
10821
|
+
async function runScan(options, onProgress) {
|
|
11034
10822
|
const commandRoot = process.cwd();
|
|
11035
10823
|
const target = path12.resolve(commandRoot, options.target);
|
|
11036
10824
|
if (!fs10.existsSync(target))
|
|
@@ -11042,21 +10830,6 @@ async function runScan(options, onProgress, beforeOutput) {
|
|
|
11042
10830
|
const resolvedScope = resolveScope(options, config);
|
|
11043
10831
|
const scope = options.staged && options.scope === undefined ? "files" : resolvedScope.scope;
|
|
11044
10832
|
validateModeFlags(options, scope);
|
|
11045
|
-
if (options.explain) {
|
|
11046
|
-
await runWhy(options.explain, commandRoot, options.noColor, !options.noCache, onProgress, beforeOutput);
|
|
11047
|
-
return {
|
|
11048
|
-
schemaVersion: 1,
|
|
11049
|
-
root: commandRoot,
|
|
11050
|
-
scannedFiles: 0,
|
|
11051
|
-
candidateFiles: 0,
|
|
11052
|
-
durationMs: 0,
|
|
11053
|
-
score: 100,
|
|
11054
|
-
counts: { error: 0, warning: 0, suggestion: 0 },
|
|
11055
|
-
diagnostics: [],
|
|
11056
|
-
partial: false,
|
|
11057
|
-
skippedFiles: []
|
|
11058
|
-
};
|
|
11059
|
-
}
|
|
11060
10833
|
if (targetStat.isFile() && (scope !== "full" || options.staged || options.changedFilesFrom)) {
|
|
11061
10834
|
throw new Error("Git scopes require a directory target; scan the containing project directory instead");
|
|
11062
10835
|
}
|
|
@@ -11078,13 +10851,6 @@ async function runScan(options, onProgress, beforeOutput) {
|
|
|
11078
10851
|
const categories = options.categories.length > 0 ? options.categories : config.categories;
|
|
11079
10852
|
const respectInlineDisables = options.respectInlineDisables ?? config.respectInlineDisables ?? true;
|
|
11080
10853
|
const deadlineAt = options.maxDurationSeconds !== undefined ? performance.now() + options.maxDurationSeconds * 1000 : undefined;
|
|
11081
|
-
const notes = [];
|
|
11082
|
-
if (options.deadCode === true)
|
|
11083
|
-
notes.push("--dead-code is accepted for React Doctor CLI compatibility, but this port does not yet include a Luau dead-code pass.");
|
|
11084
|
-
if (options.supplyChain === true)
|
|
11085
|
-
notes.push("--supply-chain is accepted for React Doctor CLI compatibility, but this port does not run Socket.dev or Roblox dependency health checks.");
|
|
11086
|
-
if (options.lint === false)
|
|
11087
|
-
notes.push("React-Luau analysis was disabled by --no-lint.");
|
|
11088
10854
|
const reports = [];
|
|
11089
10855
|
for (const { projectRoot, targetRoot } of projectTargets) {
|
|
11090
10856
|
let report2;
|
|
@@ -11094,15 +10860,7 @@ async function runScan(options, onProgress, beforeOutput) {
|
|
|
11094
10860
|
phase: [projectName, progress.phase].filter(Boolean).join(":") || undefined,
|
|
11095
10861
|
label: projectName && progress.label ? `${projectName}: ${progress.label}` : progress.label
|
|
11096
10862
|
}) : undefined;
|
|
11097
|
-
if (
|
|
11098
|
-
report2 = await scanPath(targetRoot, {
|
|
11099
|
-
projectRoot,
|
|
11100
|
-
files: [],
|
|
11101
|
-
config,
|
|
11102
|
-
cache: !options.noCache
|
|
11103
|
-
});
|
|
11104
|
-
report2.scope = options.staged ? "staged" : scope;
|
|
11105
|
-
} else if (targetStat.isFile()) {
|
|
10863
|
+
if (targetStat.isFile()) {
|
|
11106
10864
|
report2 = await scanPath(targetRoot, {
|
|
11107
10865
|
projectRoot,
|
|
11108
10866
|
config,
|
|
@@ -11131,21 +10889,19 @@ async function runScan(options, onProgress, beforeOutput) {
|
|
|
11131
10889
|
cache: !options.noCache
|
|
11132
10890
|
});
|
|
11133
10891
|
}
|
|
11134
|
-
if (notes.length > 0)
|
|
11135
|
-
report2.notes = [...report2.notes ?? [], ...notes];
|
|
11136
10892
|
reports.push({ projectRoot, report: report2 });
|
|
11137
10893
|
}
|
|
11138
10894
|
const report = aggregateReports(displayRoot, reports);
|
|
11139
10895
|
if (options.debug) {
|
|
11140
10896
|
const debugLines = [
|
|
11141
|
-
`[debug] version=${
|
|
10897
|
+
`[debug] version=${VERSION2}`,
|
|
11142
10898
|
`[debug] bun=${Bun.version} platform=${process.platform}/${process.arch}`,
|
|
11143
10899
|
`[debug] target=${target}`,
|
|
11144
10900
|
`[debug] config=${loaded.filename ?? "none"}`,
|
|
11145
10901
|
`[debug] scope=${report.scope ?? scope} base=${report.base ?? resolvedScope.base ?? "auto"}`,
|
|
11146
10902
|
`[debug] projects=${projectTargets.map(({ projectRoot }) => path12.relative(displayRoot, projectRoot) || ".").join(",")}`,
|
|
11147
10903
|
`[debug] candidates=${report.candidateFiles ?? 0} scanned=${report.scannedFiles} partial=${Boolean(report.partial)}`,
|
|
11148
|
-
`[debug] parallel=false
|
|
10904
|
+
`[debug] parallel=false`
|
|
11149
10905
|
];
|
|
11150
10906
|
process.stderr.write(`${debugLines.join(`
|
|
11151
10907
|
`)}
|
|
@@ -11187,7 +10943,7 @@ async function main() {
|
|
|
11187
10943
|
return;
|
|
11188
10944
|
}
|
|
11189
10945
|
if (argv[0] === "version") {
|
|
11190
|
-
process.stdout.write(`React-Luau Doctor ${
|
|
10946
|
+
process.stdout.write(`React-Luau Doctor ${VERSION2}
|
|
11191
10947
|
Bun ${Bun.version}
|
|
11192
10948
|
${process.platform} ${process.arch}
|
|
11193
10949
|
${os2.release()}
|
|
@@ -11200,12 +10956,7 @@ ${os2.release()}
|
|
|
11200
10956
|
return;
|
|
11201
10957
|
}
|
|
11202
10958
|
if (options.version) {
|
|
11203
|
-
process.stdout.write(`${
|
|
11204
|
-
`);
|
|
11205
|
-
return;
|
|
11206
|
-
}
|
|
11207
|
-
if (options.listRules) {
|
|
11208
|
-
process.stdout.write(`${renderRulesList(rules, process.stdout.columns ?? 120, new Map, shouldUseColor(options.noColor))}
|
|
10959
|
+
process.stdout.write(`${VERSION2}
|
|
11209
10960
|
`);
|
|
11210
10961
|
return;
|
|
11211
10962
|
}
|
|
@@ -11217,12 +10968,10 @@ ${os2.release()}
|
|
|
11217
10968
|
});
|
|
11218
10969
|
let report;
|
|
11219
10970
|
try {
|
|
11220
|
-
report = await runScan(options, (scanProgress) => progress.update(scanProgress)
|
|
10971
|
+
report = await runScan(options, (scanProgress) => progress.update(scanProgress));
|
|
11221
10972
|
} finally {
|
|
11222
10973
|
progress.clear();
|
|
11223
10974
|
}
|
|
11224
|
-
if (options.explain)
|
|
11225
|
-
return;
|
|
11226
10975
|
const compactJson = options.jsonCompact;
|
|
11227
10976
|
const json = `${serializeReport(report, compactJson)}
|
|
11228
10977
|
`;
|
|
@@ -11249,7 +10998,7 @@ ${os2.release()}
|
|
|
11249
10998
|
} else
|
|
11250
10999
|
process.stdout.write(`${renderTextReport(report, showScore, colorized, verbose, process.stdout.columns ?? 120)}
|
|
11251
11000
|
`);
|
|
11252
|
-
const blocking = options.blocking ??
|
|
11001
|
+
const blocking = options.blocking ?? config.blocking ?? "error";
|
|
11253
11002
|
if (shouldBlock2(report, blocking))
|
|
11254
11003
|
process.exitCode = 1;
|
|
11255
11004
|
} catch (error) {
|
|
@@ -11261,5 +11010,5 @@ ${os2.release()}
|
|
|
11261
11010
|
}
|
|
11262
11011
|
main();
|
|
11263
11012
|
|
|
11264
|
-
//# debugId=
|
|
11013
|
+
//# debugId=269B394ED6CE708464756E2164756E21
|
|
11265
11014
|
//# sourceMappingURL=cli.js.map
|