aislop 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4349 -0
- package/dist/engine-info-DBG3uXLc.js +39 -0
- package/dist/expo-doctor-CGXGLgMJ.js +126 -0
- package/dist/expo-doctor-vDz4kh9-.js +127 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +3880 -0
- package/dist/json-DkpW9UQj.js +30 -0
- package/dist/json-L5x3hQdy.js +31 -0
- package/dist/subprocess-99puEEGl.js +59 -0
- package/package.json +81 -0
- package/scripts/postinstall-tools.mjs +235 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/version.ts
|
|
2
|
+
/**
|
|
3
|
+
* Application version — injected at build time by tsdown from package.json.
|
|
4
|
+
* The fallback should always match the "version" field in package.json.
|
|
5
|
+
*/
|
|
6
|
+
const APP_VERSION = "0.1.0";
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/output/engine-info.ts
|
|
10
|
+
const ENGINE_INFO = {
|
|
11
|
+
format: {
|
|
12
|
+
label: "Formatting",
|
|
13
|
+
description: "Whitespace, indentation, line wrapping, and import ordering"
|
|
14
|
+
},
|
|
15
|
+
lint: {
|
|
16
|
+
label: "Linting",
|
|
17
|
+
description: "Static analysis for likely bugs and bad patterns"
|
|
18
|
+
},
|
|
19
|
+
"code-quality": {
|
|
20
|
+
label: "Code Quality",
|
|
21
|
+
description: "Complexity limits, dead code detection, and duplication checks"
|
|
22
|
+
},
|
|
23
|
+
"ai-slop": {
|
|
24
|
+
label: "Maintainability",
|
|
25
|
+
description: "Over-abstraction, swallowed errors, and low-signal code patterns"
|
|
26
|
+
},
|
|
27
|
+
architecture: {
|
|
28
|
+
label: "Architecture",
|
|
29
|
+
description: "Project-specific import and layering rules"
|
|
30
|
+
},
|
|
31
|
+
security: {
|
|
32
|
+
label: "Security",
|
|
33
|
+
description: "Secret leaks, risky APIs, and dependency vulnerabilities"
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const getEngineLabel = (engine) => ENGINE_INFO[engine].label;
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { getEngineLabel as n, APP_VERSION as r, ENGINE_INFO as t };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { n as runSubprocess } from "./subprocess-99puEEGl.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/engines/lint/expo-doctor.ts
|
|
7
|
+
const esmRequire = createRequire(import.meta.url);
|
|
8
|
+
const ISSUE_PREFIX = "✖ ";
|
|
9
|
+
const resolveExpoDoctorScript = () => {
|
|
10
|
+
try {
|
|
11
|
+
const packageJsonPath = esmRequire.resolve("expo-doctor/package.json");
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
13
|
+
const binRelativePath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["expo-doctor"];
|
|
14
|
+
if (!binRelativePath) return null;
|
|
15
|
+
return path.join(path.dirname(packageJsonPath), binRelativePath);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const toRuleSuffix = (title) => {
|
|
21
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
22
|
+
return slug.length > 0 ? slug : "issue";
|
|
23
|
+
};
|
|
24
|
+
const parseIssues = (output) => {
|
|
25
|
+
const lines = output.split("\n").map((line) => line.trimEnd());
|
|
26
|
+
const startIndex = lines.findIndex((line) => line.includes("Possible issues detected:"));
|
|
27
|
+
if (startIndex < 0) return [];
|
|
28
|
+
const issues = [];
|
|
29
|
+
let current = null;
|
|
30
|
+
let inAdvice = false;
|
|
31
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
32
|
+
const line = lines[i].trim();
|
|
33
|
+
if (/^\d+\s+checks failed/.test(line)) break;
|
|
34
|
+
if (line.length === 0) continue;
|
|
35
|
+
if (line.startsWith(ISSUE_PREFIX)) {
|
|
36
|
+
if (current) issues.push(current);
|
|
37
|
+
current = {
|
|
38
|
+
title: line.slice(2).trim(),
|
|
39
|
+
details: [],
|
|
40
|
+
advice: []
|
|
41
|
+
};
|
|
42
|
+
inAdvice = false;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!current) continue;
|
|
46
|
+
if (line === "Advice:") {
|
|
47
|
+
inAdvice = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inAdvice) current.advice.push(line);
|
|
51
|
+
else current.details.push(line);
|
|
52
|
+
}
|
|
53
|
+
if (current) issues.push(current);
|
|
54
|
+
return issues;
|
|
55
|
+
};
|
|
56
|
+
const parseConfigError = (output) => {
|
|
57
|
+
const line = output.split("\n").find((candidate) => candidate.trim().startsWith("ConfigError:"));
|
|
58
|
+
return line ? line.trim() : null;
|
|
59
|
+
};
|
|
60
|
+
const toDiagnostics = (issues) => issues.map((issue) => {
|
|
61
|
+
const helpParts = [issue.details.join(" ").trim(), issue.advice.join(" ").trim()].filter((part) => part.length > 0);
|
|
62
|
+
return {
|
|
63
|
+
filePath: "package.json",
|
|
64
|
+
engine: "lint",
|
|
65
|
+
rule: `expo-doctor/${toRuleSuffix(issue.title)}`,
|
|
66
|
+
severity: "warning",
|
|
67
|
+
message: `Expo Doctor: ${issue.title}`,
|
|
68
|
+
help: helpParts.join(" "),
|
|
69
|
+
line: 0,
|
|
70
|
+
column: 0,
|
|
71
|
+
category: "Expo",
|
|
72
|
+
fixable: false
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
const runExpoDoctor = async (context) => {
|
|
76
|
+
const scriptPath = resolveExpoDoctorScript();
|
|
77
|
+
let stdout = "";
|
|
78
|
+
let stderr = "";
|
|
79
|
+
try {
|
|
80
|
+
if (scriptPath) {
|
|
81
|
+
const result = await runSubprocess(process.execPath, [
|
|
82
|
+
scriptPath,
|
|
83
|
+
context.rootDirectory,
|
|
84
|
+
"--verbose"
|
|
85
|
+
], {
|
|
86
|
+
cwd: context.rootDirectory,
|
|
87
|
+
timeout: 12e4
|
|
88
|
+
});
|
|
89
|
+
stdout = result.stdout;
|
|
90
|
+
stderr = result.stderr;
|
|
91
|
+
} else {
|
|
92
|
+
const result = await runSubprocess("npx", [
|
|
93
|
+
"--yes",
|
|
94
|
+
"expo-doctor",
|
|
95
|
+
context.rootDirectory,
|
|
96
|
+
"--verbose"
|
|
97
|
+
], {
|
|
98
|
+
cwd: context.rootDirectory,
|
|
99
|
+
timeout: 12e4
|
|
100
|
+
});
|
|
101
|
+
stdout = result.stdout;
|
|
102
|
+
stderr = result.stderr;
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
108
|
+
if (!output) return [];
|
|
109
|
+
const configError = parseConfigError(output);
|
|
110
|
+
if (configError) return [{
|
|
111
|
+
filePath: "package.json",
|
|
112
|
+
engine: "lint",
|
|
113
|
+
rule: "expo-doctor/config-error",
|
|
114
|
+
severity: "warning",
|
|
115
|
+
message: configError,
|
|
116
|
+
help: "Install project dependencies, then re-run `aislop scan`.",
|
|
117
|
+
line: 0,
|
|
118
|
+
column: 0,
|
|
119
|
+
category: "Expo",
|
|
120
|
+
fixable: false
|
|
121
|
+
}];
|
|
122
|
+
return toDiagnostics(parseIssues(output));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
export { runExpoDoctor };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as runSubprocess } from "./cli.js";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
|
|
7
|
+
//#region src/engines/lint/expo-doctor.ts
|
|
8
|
+
const esmRequire = createRequire(import.meta.url);
|
|
9
|
+
const ISSUE_PREFIX = "✖ ";
|
|
10
|
+
const resolveExpoDoctorScript = () => {
|
|
11
|
+
try {
|
|
12
|
+
const packageJsonPath = esmRequire.resolve("expo-doctor/package.json");
|
|
13
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
14
|
+
const binRelativePath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["expo-doctor"];
|
|
15
|
+
if (!binRelativePath) return null;
|
|
16
|
+
return path.join(path.dirname(packageJsonPath), binRelativePath);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const toRuleSuffix = (title) => {
|
|
22
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
23
|
+
return slug.length > 0 ? slug : "issue";
|
|
24
|
+
};
|
|
25
|
+
const parseIssues = (output) => {
|
|
26
|
+
const lines = output.split("\n").map((line) => line.trimEnd());
|
|
27
|
+
const startIndex = lines.findIndex((line) => line.includes("Possible issues detected:"));
|
|
28
|
+
if (startIndex < 0) return [];
|
|
29
|
+
const issues = [];
|
|
30
|
+
let current = null;
|
|
31
|
+
let inAdvice = false;
|
|
32
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
33
|
+
const line = lines[i].trim();
|
|
34
|
+
if (/^\d+\s+checks failed/.test(line)) break;
|
|
35
|
+
if (line.length === 0) continue;
|
|
36
|
+
if (line.startsWith(ISSUE_PREFIX)) {
|
|
37
|
+
if (current) issues.push(current);
|
|
38
|
+
current = {
|
|
39
|
+
title: line.slice(2).trim(),
|
|
40
|
+
details: [],
|
|
41
|
+
advice: []
|
|
42
|
+
};
|
|
43
|
+
inAdvice = false;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!current) continue;
|
|
47
|
+
if (line === "Advice:") {
|
|
48
|
+
inAdvice = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inAdvice) current.advice.push(line);
|
|
52
|
+
else current.details.push(line);
|
|
53
|
+
}
|
|
54
|
+
if (current) issues.push(current);
|
|
55
|
+
return issues;
|
|
56
|
+
};
|
|
57
|
+
const parseConfigError = (output) => {
|
|
58
|
+
const line = output.split("\n").find((candidate) => candidate.trim().startsWith("ConfigError:"));
|
|
59
|
+
return line ? line.trim() : null;
|
|
60
|
+
};
|
|
61
|
+
const toDiagnostics = (issues) => issues.map((issue) => {
|
|
62
|
+
const helpParts = [issue.details.join(" ").trim(), issue.advice.join(" ").trim()].filter((part) => part.length > 0);
|
|
63
|
+
return {
|
|
64
|
+
filePath: "package.json",
|
|
65
|
+
engine: "lint",
|
|
66
|
+
rule: `expo-doctor/${toRuleSuffix(issue.title)}`,
|
|
67
|
+
severity: "warning",
|
|
68
|
+
message: `Expo Doctor: ${issue.title}`,
|
|
69
|
+
help: helpParts.join(" "),
|
|
70
|
+
line: 0,
|
|
71
|
+
column: 0,
|
|
72
|
+
category: "Expo",
|
|
73
|
+
fixable: false
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
const runExpoDoctor = async (context) => {
|
|
77
|
+
const scriptPath = resolveExpoDoctorScript();
|
|
78
|
+
let stdout = "";
|
|
79
|
+
let stderr = "";
|
|
80
|
+
try {
|
|
81
|
+
if (scriptPath) {
|
|
82
|
+
const result = await runSubprocess(process.execPath, [
|
|
83
|
+
scriptPath,
|
|
84
|
+
context.rootDirectory,
|
|
85
|
+
"--verbose"
|
|
86
|
+
], {
|
|
87
|
+
cwd: context.rootDirectory,
|
|
88
|
+
timeout: 12e4
|
|
89
|
+
});
|
|
90
|
+
stdout = result.stdout;
|
|
91
|
+
stderr = result.stderr;
|
|
92
|
+
} else {
|
|
93
|
+
const result = await runSubprocess("npx", [
|
|
94
|
+
"--yes",
|
|
95
|
+
"expo-doctor",
|
|
96
|
+
context.rootDirectory,
|
|
97
|
+
"--verbose"
|
|
98
|
+
], {
|
|
99
|
+
cwd: context.rootDirectory,
|
|
100
|
+
timeout: 12e4
|
|
101
|
+
});
|
|
102
|
+
stdout = result.stdout;
|
|
103
|
+
stderr = result.stderr;
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
109
|
+
if (!output) return [];
|
|
110
|
+
const configError = parseConfigError(output);
|
|
111
|
+
if (configError) return [{
|
|
112
|
+
filePath: "package.json",
|
|
113
|
+
engine: "lint",
|
|
114
|
+
rule: "expo-doctor/config-error",
|
|
115
|
+
severity: "warning",
|
|
116
|
+
message: configError,
|
|
117
|
+
help: "Install project dependencies, then re-run `aislop scan`.",
|
|
118
|
+
line: 0,
|
|
119
|
+
column: 0,
|
|
120
|
+
category: "Expo",
|
|
121
|
+
fixable: false
|
|
122
|
+
}];
|
|
123
|
+
return toDiagnostics(parseIssues(output));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { runExpoDoctor };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/doctor.d.ts
|
|
4
|
+
declare const doctorCommand: (directory: string) => Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/config/schema.d.ts
|
|
7
|
+
declare const AislopConfigSchema: z.ZodObject<{
|
|
8
|
+
version: z.ZodDefault<z.ZodNumber>;
|
|
9
|
+
engines: z.ZodDefault<z.ZodObject<{
|
|
10
|
+
format: z.ZodDefault<z.ZodBoolean>;
|
|
11
|
+
lint: z.ZodDefault<z.ZodBoolean>;
|
|
12
|
+
"code-quality": z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
"ai-slop": z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
architecture: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
security: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
}, z.core.$strip>>;
|
|
17
|
+
quality: z.ZodDefault<z.ZodObject<{
|
|
18
|
+
maxFunctionLoc: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
maxFileLoc: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
maxNesting: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
maxParams: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
security: z.ZodDefault<z.ZodObject<{
|
|
24
|
+
audit: z.ZodDefault<z.ZodBoolean>;
|
|
25
|
+
auditTimeout: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
|
+
scoring: z.ZodDefault<z.ZodObject<{
|
|
28
|
+
weights: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
29
|
+
thresholds: z.ZodDefault<z.ZodObject<{
|
|
30
|
+
good: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
ok: z.ZodDefault<z.ZodNumber>;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
}, z.core.$strip>>;
|
|
34
|
+
ci: z.ZodDefault<z.ZodObject<{
|
|
35
|
+
failBelow: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
format: z.ZodDefault<z.ZodEnum<{
|
|
37
|
+
json: "json";
|
|
38
|
+
}>>;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
type AislopConfig = z.infer<typeof AislopConfigSchema>;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/config/index.d.ts
|
|
44
|
+
declare const loadConfig: (directory: string) => AislopConfig;
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/commands/fix.d.ts
|
|
47
|
+
interface FixOptions {
|
|
48
|
+
verbose: boolean;
|
|
49
|
+
showHeader?: boolean;
|
|
50
|
+
}
|
|
51
|
+
declare const fixCommand: (directory: string, config: AislopConfig, options?: FixOptions) => Promise<void>;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/commands/init.d.ts
|
|
54
|
+
declare const initCommand: (directory: string) => Promise<void>;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/commands/scan.d.ts
|
|
57
|
+
interface ScanOptions {
|
|
58
|
+
changes: boolean;
|
|
59
|
+
staged: boolean;
|
|
60
|
+
verbose: boolean;
|
|
61
|
+
json: boolean;
|
|
62
|
+
showHeader?: boolean;
|
|
63
|
+
}
|
|
64
|
+
declare const scanCommand: (directory: string, config: AislopConfig, options: ScanOptions) => Promise<{
|
|
65
|
+
exitCode: number;
|
|
66
|
+
}>;
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/utils/discover.d.ts
|
|
69
|
+
type Language = "typescript" | "javascript" | "python" | "go" | "rust" | "java" | "ruby" | "php";
|
|
70
|
+
type Framework = "nextjs" | "react" | "vite" | "remix" | "expo" | "django" | "flask" | "fastapi" | "none";
|
|
71
|
+
interface ProjectInfo {
|
|
72
|
+
rootDirectory: string;
|
|
73
|
+
projectName: string;
|
|
74
|
+
languages: Language[];
|
|
75
|
+
frameworks: Framework[];
|
|
76
|
+
sourceFileCount: number;
|
|
77
|
+
installedTools: Record<string, boolean>;
|
|
78
|
+
}
|
|
79
|
+
declare const discoverProject: (directory: string) => Promise<ProjectInfo>;
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/engines/types.d.ts
|
|
82
|
+
type Severity = "error" | "warning" | "info";
|
|
83
|
+
type EngineName = "format" | "lint" | "code-quality" | "ai-slop" | "architecture" | "security";
|
|
84
|
+
interface Diagnostic {
|
|
85
|
+
filePath: string;
|
|
86
|
+
engine: EngineName;
|
|
87
|
+
rule: string;
|
|
88
|
+
severity: Severity;
|
|
89
|
+
message: string;
|
|
90
|
+
help: string;
|
|
91
|
+
line: number;
|
|
92
|
+
column: number;
|
|
93
|
+
category: string;
|
|
94
|
+
fixable: boolean;
|
|
95
|
+
}
|
|
96
|
+
interface EngineResult {
|
|
97
|
+
engine: EngineName;
|
|
98
|
+
diagnostics: Diagnostic[];
|
|
99
|
+
elapsed: number;
|
|
100
|
+
skipped: boolean;
|
|
101
|
+
skipReason?: string;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/scoring/index.d.ts
|
|
105
|
+
interface ScoreResult {
|
|
106
|
+
score: number;
|
|
107
|
+
label: string;
|
|
108
|
+
}
|
|
109
|
+
declare const calculateScore: (diagnostics: Diagnostic[], weights: Record<string, number>, thresholds: {
|
|
110
|
+
good: number;
|
|
111
|
+
ok: number;
|
|
112
|
+
}) => ScoreResult;
|
|
113
|
+
//#endregion
|
|
114
|
+
export { type AislopConfig, type Diagnostic, type EngineName, type EngineResult, type Framework, type Language, type ProjectInfo, type ScoreResult, type Severity, calculateScore, discoverProject, doctorCommand, fixCommand, initCommand, loadConfig, scanCommand };
|