Skip to content

Commit d8aaf30

Browse files
fix(analyzer): add explicit mmpm exception profile and keep CODE_OF_CONDUCT check enabled
1 parent a2b8ee2 commit d8aaf30

3 files changed

Lines changed: 121 additions & 47 deletions

File tree

scripts/check-modules/__tests__/module-analyzer.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,35 @@ test("analyzer keeps .github files and applies only lockfile-specific package-lo
110110

111111
assert.ok(Array.isArray(result.issues));
112112
});
113+
114+
test("analyzer applies classic-module exceptions for mmpm", async () => {
115+
const moduleRoot = await fsPromises.mkdtemp(join(tmpdir(), "module-analyzer-mmpm-test-"));
116+
117+
await fsPromises.writeFile(join(moduleRoot, "README.md"), "# mmpm\n");
118+
await fsPromises.writeFile(join(moduleRoot, "LICENSE.md"), "MIT\n");
119+
await fsPromises.writeFile(
120+
join(moduleRoot, "package.json"),
121+
JSON.stringify({
122+
name: "mmpm",
123+
version: "1.0.0"
124+
})
125+
);
126+
127+
const files = [
128+
join(moduleRoot, "README.md"),
129+
join(moduleRoot, "LICENSE.md"),
130+
join(moduleRoot, "package.json")
131+
];
132+
133+
const result = await analyzeModule(
134+
moduleRoot,
135+
"mmpm",
136+
"https://github.com/Bee-Mar/mmpm",
137+
files
138+
);
139+
140+
assert.equal(result.issues.some(issue => issue.includes("README seems not to have an update section")), false);
141+
assert.equal(result.issues.some(issue => issue.includes("There is no CODE_OF_CONDUCT file")), true);
142+
assert.equal(result.issues.some(issue => issue.includes("There is no dependabot configuration file")), false);
143+
assert.equal(result.issues.some(issue => issue.includes("No ESLint configuration was found")), false);
144+
});

scripts/check-modules/module-analyzer.ts

Lines changed: 88 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,44 @@ interface AnalysisResult {
1616
recommendations: string[];
1717
}
1818

19+
interface ModuleCheckExceptions {
20+
skipCodeOfConductCheck?: boolean;
21+
skipDependabotCheck?: boolean;
22+
skipEslintChecks?: boolean;
23+
skipReadmeChecks?: boolean;
24+
}
25+
26+
const MODULE_CHECK_EXCEPTIONS: Record<string, ModuleCheckExceptions> = {
27+
// mmpm is a standalone management tool, not a classic MM module runtime package.
28+
// Some mirror-module README/community checks are not meaningful for it.
29+
"Bee-Mar/mmpm": {
30+
skipReadmeChecks: true,
31+
skipDependabotCheck: true,
32+
skipEslintChecks: true
33+
}
34+
};
35+
36+
function getRepositoryId(moduleUrl: string): string | null {
37+
try {
38+
const url = new URL(moduleUrl);
39+
const segments = url.pathname.split("/").filter(Boolean);
40+
if (segments.length < 2) {
41+
return null;
42+
}
43+
44+
const owner = segments[0];
45+
const repo = segments[1].replace(/\.git$/u, "");
46+
if (!owner || !repo) {
47+
return null;
48+
}
49+
50+
return `${owner}/${repo}`;
51+
}
52+
catch {
53+
return null;
54+
}
55+
}
56+
1957
// Comprehensive TEXT_RULES with all deprecated APIs, typos, and recommendations
2058
const TEXT_RULES: Record<string, TextRule> = {
2159
"new Buffer(": {
@@ -330,6 +368,8 @@ export async function analyzeModule(
330368
files: string[]
331369
): Promise<AnalysisResult> {
332370
const issues: string[] = [];
371+
const moduleRepoId = getRepositoryId(moduleUrl);
372+
const moduleExceptions = moduleRepoId ? MODULE_CHECK_EXCEPTIONS[moduleRepoId] ?? {} : {};
333373

334374
// Filter out files in node_modules and .git directories.
335375
// Use path segments instead of substring matching so ".github" files are not excluded.
@@ -399,7 +439,7 @@ export async function analyzeModule(
399439
const relativePath = filePath.startsWith(modulePath)
400440
? filePath.slice(modulePath.length).replace(/^\/+/, "")
401441
: filePath;
402-
if (relativePath === "README.md") {
442+
if (relativePath === "README.md" && !moduleExceptions.skipReadmeChecks) {
403443
// Check for update section
404444
if (!content.includes("## Updat")) {
405445
issues.push(
@@ -471,69 +511,71 @@ export async function analyzeModule(
471511
);
472512
}
473513

474-
if (!filenames.has("CODE_OF_CONDUCT") && !filenames.has("CODE_OF_CONDUCT.MD")) {
514+
if (!moduleExceptions.skipCodeOfConductCheck && !filenames.has("CODE_OF_CONDUCT") && !filenames.has("CODE_OF_CONDUCT.MD")) {
475515
issues.push(
476516
"Recommendation: There is no CODE_OF_CONDUCT file. It is recommended to add one ([example CODE_OF_CONDUCT file](https://github.com/KristjanESPERANTO/MMM-ApothekenNotdienst/blob/main/CODE_OF_CONDUCT.md))."
477517
);
478518
}
479519

480-
if (!filenames.has("DEPENDABOT.YAML") && !filenames.has("DEPENDABOT.YML")) {
520+
if (!moduleExceptions.skipDependabotCheck && !filenames.has("DEPENDABOT.YAML") && !filenames.has("DEPENDABOT.YML")) {
481521
issues.push(
482522
"Recommendation: There is no dependabot configuration file. It is recommended to add one ([example dependabot file](https://github.com/KristjanESPERANTO/MMM-ApothekenNotdienst/blob/main/.github/dependabot.yaml))."
483523
);
484524
}
485525

486526
// ESLint checks
487-
const hasOldEslintrc = filenames.has("ESLINTRC") || filenames.has("ESLINTRC.JSON") || filenames.has("ESLINTRC.JS") || filenames.has("ESLINTRC.YML") || filenames.has("ESLINTRC.YAML");
488-
const hasNewEslint = filenames.has("ESLINT.CONFIG.JS") || filenames.has("ESLINT.CONFIG.MJS");
489-
490-
if (hasOldEslintrc) {
491-
issues.push("Recommendation: Replace eslintrc by new flat config.");
492-
} else if (!hasNewEslint) {
493-
issues.push(
494-
"Recommendation: No ESLint configuration was found. ESLint is very helpful, it is worth using it even for small projects ([basic instructions](https://github.com/MagicMirrorOrg/MagicMirror-3rd-Party-Modules/blob/main/guides/eslint.md))."
495-
);
496-
} else {
497-
// Check if ESLint is in package.json dependencies
498-
const packageJsonFiles = relevantFiles.filter((f) => f.endsWith("package.json"));
499-
for (const pkgFile of packageJsonFiles) {
500-
const pkgContent = await readFile(pkgFile, "utf-8").catch(() => "{}");
501-
try {
502-
const pkg = JSON.parse(pkgContent);
503-
if (
504-
!pkg.dependencies?.eslint &&
505-
!pkg.devDependencies?.eslint
506-
) {
507-
issues.push(
508-
"Recommendation: ESLint is not in the dependencies or devDependencies. It is recommended to add it to one of them."
509-
);
510-
}
527+
if (!moduleExceptions.skipEslintChecks) {
528+
const hasOldEslintrc = filenames.has("ESLINTRC") || filenames.has("ESLINTRC.JSON") || filenames.has("ESLINTRC.JS") || filenames.has("ESLINTRC.YML") || filenames.has("ESLINTRC.YAML");
529+
const hasNewEslint = filenames.has("ESLINT.CONFIG.JS") || filenames.has("ESLINT.CONFIG.MJS");
511530

512-
// Check lint script
513-
if (pkg.scripts) {
514-
if (!pkg.scripts.lint) {
515-
issues.push("Recommendation: No lint script found in package.json. It is recommended to add one.");
516-
} else if (!pkg.scripts.lint.includes("eslint")) {
531+
if (hasOldEslintrc) {
532+
issues.push("Recommendation: Replace eslintrc by new flat config.");
533+
} else if (!hasNewEslint) {
534+
issues.push(
535+
"Recommendation: No ESLint configuration was found. ESLint is very helpful, it is worth using it even for small projects ([basic instructions](https://github.com/MagicMirrorOrg/MagicMirror-3rd-Party-Modules/blob/main/guides/eslint.md))."
536+
);
537+
} else {
538+
// Check if ESLint is in package.json dependencies
539+
const packageJsonFiles = relevantFiles.filter((f) => f.endsWith("package.json"));
540+
for (const pkgFile of packageJsonFiles) {
541+
const pkgContent = await readFile(pkgFile, "utf-8").catch(() => "{}");
542+
try {
543+
const pkg = JSON.parse(pkgContent);
544+
if (
545+
!pkg.dependencies?.eslint &&
546+
!pkg.devDependencies?.eslint
547+
) {
517548
issues.push(
518-
"Recommendation: The lint script in package.json does not contain `eslint`. It is recommended to add it."
549+
"Recommendation: ESLint is not in the dependencies or devDependencies. It is recommended to add it to one of them."
519550
);
520551
}
552+
553+
// Check lint script
554+
if (pkg.scripts) {
555+
if (!pkg.scripts.lint) {
556+
issues.push("Recommendation: No lint script found in package.json. It is recommended to add one.");
557+
} else if (!pkg.scripts.lint.includes("eslint")) {
558+
issues.push(
559+
"Recommendation: The lint script in package.json does not contain `eslint`. It is recommended to add it."
560+
);
561+
}
562+
}
563+
} catch {
564+
// Silently ignore JSON parse errors
521565
}
522-
} catch {
523-
// Silently ignore JSON parse errors
524566
}
525-
}
526567

527-
// Check for defineConfig in eslint.config.js
528-
const eslintConfigFiles = relevantFiles.filter(
529-
(f) => f.endsWith("eslint.config.js") || f.endsWith("eslint.config.mjs")
530-
);
531-
for (const configFile of eslintConfigFiles) {
532-
const configContent = await readFile(configFile, "utf-8").catch(() => "");
533-
if (!configContent.includes("defineConfig")) {
534-
issues.push(
535-
`Recommendation: The ESLint configuration file \`${configFile.split("/").pop()}\` does not contain \`defineConfig\`. It is recommended to use it.`
536-
);
568+
// Check for defineConfig in eslint.config.js
569+
const eslintConfigFiles = relevantFiles.filter(
570+
(f) => f.endsWith("eslint.config.js") || f.endsWith("eslint.config.mjs")
571+
);
572+
for (const configFile of eslintConfigFiles) {
573+
const configContent = await readFile(configFile, "utf-8").catch(() => "");
574+
if (!configContent.includes("defineConfig")) {
575+
issues.push(
576+
`Recommendation: The ESLint configuration file \`${configFile.split("/").pop()}\` does not contain \`defineConfig\`. It is recommended to use it.`
577+
);
578+
}
537579
}
538580
}
539581
}

scripts/shared/module-analysis-cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getCurrentCommit } from "./git.ts";
33
import { resolve } from "node:path";
44
import { stringifyDeterministic } from "./deterministic-output.ts";
55

6-
export const MODULE_ANALYSIS_CACHE_SCHEMA_VERSION = 7;
6+
export const MODULE_ANALYSIS_CACHE_SCHEMA_VERSION = 8;
77
export const MODULE_ANALYSIS_CACHE_RELATIVE_PATH = "website/data/moduleCache.json";
88

99
interface ModuleAnalysisCheckGroupsInput {

0 commit comments

Comments
 (0)