projectmedic 0.0.0-stage → 1.0.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/LICENSE +8 -0
- package/README.md +61 -2
- package/bin/doctor.js +46 -0
- package/package.json +36 -4
- package/src/finding.js +14 -0
- package/src/index.js +71 -0
- package/src/package.js +19 -0
- package/src/rules/description.js +25 -0
- package/src/rules/gitignore.js +18 -0
- package/src/rules/license.js +18 -0
- package/src/rules/node-version.js +21 -0
- package/src/rules/package-json.js +15 -0
- package/src/rules/package-name.js +27 -0
- package/src/rules/package-version.js +27 -0
- package/src/rules/readme.js +18 -0
- package/src/rules/repository.js +26 -0
- package/src/rules/test-script.js +18 -0
- package/src/scanner.js +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
ISC License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Akhil Pal
|
|
5
|
+
|
|
6
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Project Doctor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Node.js project health checker that identifies missing or misconfigured project metadata and files.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Validates `package.json`
|
|
8
|
+
- Checks package name, version, and description
|
|
9
|
+
- Checks for a README, `.gitignore`, and license
|
|
10
|
+
- Checks for a test script and repository metadata
|
|
11
|
+
- Checks for a Node.js version requirement
|
|
12
|
+
- Provides a JavaScript API and command-line interface
|
|
13
|
+
- Supports JSON output
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Node.js
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Run the checker against the current directory:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node bin/doctor.js .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Check another project:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node bin/doctor.js /path/to/project
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Output a JSON report:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node bin/doctor.js . --json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Library API
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
import { inspectProject } from "project-doctor";
|
|
43
|
+
|
|
44
|
+
const report = await inspectProject("./my-project");
|
|
45
|
+
|
|
46
|
+
console.log(report.summary);
|
|
47
|
+
console.log(report.findings);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The report contains the project path, a summary of check results, and individual findings.
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
Run the test suite:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm test
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
ISC
|
package/bin/doctor.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { inspectProject } from "../src/index.js";
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const jsonMode = args.includes("--json");
|
|
6
|
+
const projectPath = args.find((arg) => !arg.startsWith("--")) || ".";
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const report = await inspectProject(projectPath);
|
|
10
|
+
|
|
11
|
+
if (report.summary.failed > 0) {
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (jsonMode) {
|
|
16
|
+
console.log(JSON.stringify(report, null, 2));
|
|
17
|
+
} else {
|
|
18
|
+
console.log("\nProject Doctor\n");
|
|
19
|
+
|
|
20
|
+
for (const finding of report.findings) {
|
|
21
|
+
const symbol =
|
|
22
|
+
finding.status === "pass"
|
|
23
|
+
? "✓"
|
|
24
|
+
: finding.status === "warn"
|
|
25
|
+
? "⚠"
|
|
26
|
+
: "✗";
|
|
27
|
+
|
|
28
|
+
console.log(`${symbol} ${finding.message}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`\n${report.summary.total} checks completed`);
|
|
32
|
+
console.log(
|
|
33
|
+
`${report.summary.passed} passed, ` +
|
|
34
|
+
`${report.summary.warnings} warnings, ` +
|
|
35
|
+
`${report.summary.failed} failed`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (jsonMode) {
|
|
40
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
41
|
+
} else {
|
|
42
|
+
console.error(`Project Doctor: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectmedic",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Node.js project health checker that identifies missing or misconfigured project metadata and files.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./src/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"project-doctor": "bin/doctor.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src/",
|
|
17
|
+
"bin/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"nodejs",
|
|
23
|
+
"project-health",
|
|
24
|
+
"cli",
|
|
25
|
+
"developer-tools",
|
|
26
|
+
"project-checker"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "node --test"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/amethystakira/project-doctor.git"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/finding.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Create a standardized result for every project check.
|
|
2
|
+
export function createFinding(id, name, status, message) {
|
|
3
|
+
return { id, name, status, message };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Report a check that could not run because package.json is unavailable.
|
|
7
|
+
export function createSkippedFinding(id, name) {
|
|
8
|
+
return createFinding(
|
|
9
|
+
id,
|
|
10
|
+
name,
|
|
11
|
+
"warn",
|
|
12
|
+
"Check skipped because package.json is missing or invalid"
|
|
13
|
+
);
|
|
14
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { scanProject } from "./scanner.js";
|
|
5
|
+
|
|
6
|
+
// Import all project checks.
|
|
7
|
+
import { checkPackageJson } from "./rules/package-json.js";
|
|
8
|
+
import { checkPackageName } from "./rules/package-name.js";
|
|
9
|
+
import { checkPackageVersion } from "./rules/package-version.js";
|
|
10
|
+
import { checkDescription } from "./rules/description.js";
|
|
11
|
+
import { checkReadme } from "./rules/readme.js";
|
|
12
|
+
import { checkGitignore } from "./rules/gitignore.js";
|
|
13
|
+
import { checkLicense } from "./rules/license.js";
|
|
14
|
+
import { checkTestScript } from "./rules/test-script.js";
|
|
15
|
+
import { checkRepository } from "./rules/repository.js";
|
|
16
|
+
import { checkNodeVersion } from "./rules/node-version.js";
|
|
17
|
+
|
|
18
|
+
const rules = [
|
|
19
|
+
checkPackageJson,
|
|
20
|
+
checkPackageName,
|
|
21
|
+
checkPackageVersion,
|
|
22
|
+
checkDescription,
|
|
23
|
+
checkReadme,
|
|
24
|
+
checkGitignore,
|
|
25
|
+
checkLicense,
|
|
26
|
+
checkTestScript,
|
|
27
|
+
checkRepository,
|
|
28
|
+
checkNodeVersion
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// Inspect a project and return its complete health report.
|
|
32
|
+
export async function inspectProject(projectPath = ".") {
|
|
33
|
+
if (typeof projectPath !== "string" || !projectPath.trim()) {
|
|
34
|
+
throw new TypeError("Project path must be a non-empty string");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const absolutePath = path.resolve(projectPath);
|
|
38
|
+
|
|
39
|
+
// Reject missing paths and paths that are not directories.
|
|
40
|
+
let stats;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
stats = await import("node:fs/promises").then(
|
|
44
|
+
({ stat }) => stat(absolutePath)
|
|
45
|
+
);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(`Project path does not exist: ${absolutePath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!stats.isDirectory()) {
|
|
51
|
+
throw new Error(`Project path is not a directory: ${absolutePath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const findings = await scanProject(absolutePath, rules);
|
|
55
|
+
|
|
56
|
+
const summary = {
|
|
57
|
+
total: findings.length,
|
|
58
|
+
passed: findings.filter((item) => item.status === "pass").length,
|
|
59
|
+
warnings: findings.filter((item) => item.status === "warn").length,
|
|
60
|
+
failed: findings.filter((item) => item.status === "fail").length
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
path: absolutePath,
|
|
65
|
+
summary,
|
|
66
|
+
findings
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Allow the package to be imported without running the CLI.
|
|
71
|
+
export { rules };
|
package/src/package.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
// Read and validate the project's package.json.
|
|
5
|
+
export async function readPackageJson(projectPath) {
|
|
6
|
+
const packagePath = path.join(projectPath, "package.json");
|
|
7
|
+
const content = await fs.readFile(packagePath, "utf8");
|
|
8
|
+
const data = JSON.parse(content);
|
|
9
|
+
|
|
10
|
+
if (
|
|
11
|
+
data === null ||
|
|
12
|
+
typeof data !== "object" ||
|
|
13
|
+
Array.isArray(data)
|
|
14
|
+
) {
|
|
15
|
+
throw new Error("package.json must contain a JSON object");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFinding, createSkippedFinding } from "../finding.js";
|
|
3
|
+
|
|
4
|
+
// Check whether package.json has a description.
|
|
5
|
+
export async function checkDescription(context) {
|
|
6
|
+
if (context.packageJsonError) {
|
|
7
|
+
return createSkippedFinding("description", "description");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (context.packageJson.description) {
|
|
11
|
+
return createFinding(
|
|
12
|
+
"description",
|
|
13
|
+
"description",
|
|
14
|
+
"pass",
|
|
15
|
+
"Package description exists"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return createFinding(
|
|
20
|
+
"description",
|
|
21
|
+
"description",
|
|
22
|
+
"warn",
|
|
23
|
+
"Package description is missing"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathExists } from "../scanner.js";
|
|
4
|
+
import { createFinding } from "../finding.js";
|
|
5
|
+
|
|
6
|
+
// Check whether .gitignore exists.
|
|
7
|
+
export async function checkGitignore(context) {
|
|
8
|
+
const exists = await pathExists(
|
|
9
|
+
path.join(context.projectPath, ".gitignore")
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return createFinding(
|
|
13
|
+
"gitignore",
|
|
14
|
+
".gitignore",
|
|
15
|
+
exists ? "pass" : "warn",
|
|
16
|
+
exists ? ".gitignore exists" : ".gitignore is missing"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathExists } from "../scanner.js";
|
|
4
|
+
import { createFinding } from "../finding.js";
|
|
5
|
+
|
|
6
|
+
// Check whether LICENSE exists.
|
|
7
|
+
export async function checkLicense(context) {
|
|
8
|
+
const exists = await pathExists(
|
|
9
|
+
path.join(context.projectPath, "LICENSE")
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return createFinding(
|
|
13
|
+
"license",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
exists ? "pass" : "warn",
|
|
16
|
+
exists ? "LICENSE exists" : "LICENSE is missing"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFinding, createSkippedFinding } from "../finding.js";
|
|
3
|
+
|
|
4
|
+
// Check whether a Node.js version requirement is configured.
|
|
5
|
+
export async function checkNodeVersion(context) {
|
|
6
|
+
if (context.packageJsonError) {
|
|
7
|
+
return createSkippedFinding("node-version", "Node.js version");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const nodeVersion = context.packageJson.engines?.node?.trim();
|
|
11
|
+
const exists = Boolean(nodeVersion);
|
|
12
|
+
|
|
13
|
+
return createFinding(
|
|
14
|
+
"node-version",
|
|
15
|
+
"Node.js version",
|
|
16
|
+
exists ? "pass" : "warn",
|
|
17
|
+
exists
|
|
18
|
+
? `Node.js requirement is "${nodeVersion}"`
|
|
19
|
+
: "Node.js version requirement is missing"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createFinding } from "../finding.js";
|
|
2
|
+
|
|
3
|
+
// Check whether package.json exists and contains valid JSON.
|
|
4
|
+
export async function checkPackageJson(context) {
|
|
5
|
+
const valid = !context.packageJsonError;
|
|
6
|
+
|
|
7
|
+
return createFinding(
|
|
8
|
+
"package-json",
|
|
9
|
+
"package.json",
|
|
10
|
+
valid ? "pass" : "fail",
|
|
11
|
+
valid
|
|
12
|
+
? "package.json exists and is valid"
|
|
13
|
+
: "package.json is missing or invalid"
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFinding, createSkippedFinding } from "../finding.js";
|
|
3
|
+
|
|
4
|
+
// Check whether package.json has a name.
|
|
5
|
+
export async function checkPackageName(context) {
|
|
6
|
+
const { packageJson, packageJsonError } = context;
|
|
7
|
+
|
|
8
|
+
if (context.packageJsonError) {
|
|
9
|
+
return createSkippedFinding("package-name", "package name");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (packageJson.name) {
|
|
13
|
+
return createFinding(
|
|
14
|
+
"package-name",
|
|
15
|
+
"package name",
|
|
16
|
+
"pass",
|
|
17
|
+
`Package name is "${packageJson.name}"`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return createFinding(
|
|
22
|
+
"package-name",
|
|
23
|
+
"package name",
|
|
24
|
+
"fail",
|
|
25
|
+
"Package name is missing"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFinding, createSkippedFinding } from "../finding.js";
|
|
3
|
+
|
|
4
|
+
// Check whether package.json has a version.
|
|
5
|
+
export async function checkPackageVersion(context) {
|
|
6
|
+
const { packageJson, packageJsonError } = context;
|
|
7
|
+
|
|
8
|
+
if (packageJsonError) {
|
|
9
|
+
return createSkippedFinding("package-version", "package version");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (packageJson.version) {
|
|
13
|
+
return createFinding(
|
|
14
|
+
"package-version",
|
|
15
|
+
"package version",
|
|
16
|
+
"pass",
|
|
17
|
+
`Package version is "${packageJson.version}"`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return createFinding(
|
|
22
|
+
"package-version",
|
|
23
|
+
"package version",
|
|
24
|
+
"fail",
|
|
25
|
+
"Package version is missing"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathExists } from "../scanner.js";
|
|
4
|
+
import { createFinding } from "../finding.js";
|
|
5
|
+
|
|
6
|
+
// Check whether README.md exists.
|
|
7
|
+
export async function checkReadme(context) {
|
|
8
|
+
const exists = await pathExists(
|
|
9
|
+
path.join(context.projectPath, "README.md")
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return createFinding(
|
|
13
|
+
"readme",
|
|
14
|
+
"README",
|
|
15
|
+
exists ? "pass" : "warn",
|
|
16
|
+
exists ? "README.md exists" : "README.md is missing"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFinding, createSkippedFinding } from "../finding.js";
|
|
3
|
+
|
|
4
|
+
// Check whether repository metadata is configured.
|
|
5
|
+
export async function checkRepository(context) {
|
|
6
|
+
if (context.packageJsonError) {
|
|
7
|
+
return createSkippedFinding("repository", "repository");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const repository = context.packageJson.repository;
|
|
11
|
+
|
|
12
|
+
const exists = Boolean(
|
|
13
|
+
typeof repository === "string"
|
|
14
|
+
? repository.trim()
|
|
15
|
+
: repository?.url?.trim()
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return createFinding(
|
|
19
|
+
"repository",
|
|
20
|
+
"repository",
|
|
21
|
+
exists ? "pass" : "warn",
|
|
22
|
+
exists
|
|
23
|
+
? "Repository metadata exists"
|
|
24
|
+
: "Repository metadata is missing"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFinding, createSkippedFinding } from "../finding.js";
|
|
3
|
+
|
|
4
|
+
// Check whether a test script is configured.
|
|
5
|
+
export async function checkTestScript(context) {
|
|
6
|
+
if (context.packageJsonError) {
|
|
7
|
+
return createSkippedFinding("test-script", "test script");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const exists = Boolean(context.packageJson.scripts?.test);
|
|
11
|
+
|
|
12
|
+
return createFinding(
|
|
13
|
+
"test-script",
|
|
14
|
+
"test script",
|
|
15
|
+
exists ? "pass" : "warn",
|
|
16
|
+
exists ? "Test script exists" : "Test script is missing"
|
|
17
|
+
);
|
|
18
|
+
}
|
package/src/scanner.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readPackageJson } from "./package.js";
|
|
5
|
+
|
|
6
|
+
// Check whether a path exists.
|
|
7
|
+
export async function pathExists(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
await fs.access(filePath);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Load reusable project data once.
|
|
17
|
+
export async function createProjectContext(projectPath) {
|
|
18
|
+
const absolutePath = path.resolve(projectPath);
|
|
19
|
+
let packageJson = null;
|
|
20
|
+
let packageJsonError = null;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
packageJson = await readPackageJson(absolutePath);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
packageJsonError = error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
projectPath: absolutePath,
|
|
30
|
+
packageJson,
|
|
31
|
+
packageJsonError
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Run all checks using the same project context.
|
|
36
|
+
export async function scanProject(projectPath, rules) {
|
|
37
|
+
const context = await createProjectContext(projectPath);
|
|
38
|
+
const findings = [];
|
|
39
|
+
|
|
40
|
+
for (const rule of rules) {
|
|
41
|
+
findings.push(await rule(context));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return findings;
|
|
45
|
+
}
|