@staff0rd/assist 0.301.0 → 0.302.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/README.md +1 -1
- package/claude/commands/add-command.md +1 -1
- package/claude/commands/verify-new.md +2 -2
- package/dist/index.js +1381 -1306
- package/package.json +1 -1
- package/dist/commands/lint/biome.linter.json +0 -66
- package/dist/commands/lint/init.ts +0 -56
- package/dist/commands/lint/lint/applyMoves.ts +0 -50
- package/dist/commands/lint/lint/checkFileNames.ts +0 -81
- package/dist/commands/lint/lint/createLintProject.ts +0 -16
- package/dist/commands/lint/lint/fixFileNameViolations.ts +0 -16
- package/dist/commands/lint/lint/index.ts +0 -17
- package/dist/commands/lint/lint/renameExports.ts +0 -37
- package/dist/commands/lint/lint/runFileNameCheck.ts +0 -45
- package/dist/commands/lint/lint/runImportExtensionCheck.ts +0 -45
- package/dist/commands/lint/lint/runStaticImportCheck.ts +0 -45
- package/dist/commands/lint/shared.ts +0 -29
package/package.json
CHANGED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"overrides": [
|
|
3
|
-
{
|
|
4
|
-
"includes": [
|
|
5
|
-
"**/index.ts",
|
|
6
|
-
"**/shared.ts",
|
|
7
|
-
"**/types.ts",
|
|
8
|
-
"**/constants.ts",
|
|
9
|
-
"tsup.config.ts"
|
|
10
|
-
],
|
|
11
|
-
"linter": {
|
|
12
|
-
"rules": {
|
|
13
|
-
"style": {
|
|
14
|
-
"useFilenamingConvention": {
|
|
15
|
-
"level": "error",
|
|
16
|
-
"options": {
|
|
17
|
-
"filenameCases": ["kebab-case"],
|
|
18
|
-
"requireAscii": true
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"includes": ["**/*.test.ts"],
|
|
27
|
-
"linter": {
|
|
28
|
-
"rules": {
|
|
29
|
-
"style": {
|
|
30
|
-
"useFilenamingConvention": "off"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
"linter": {
|
|
37
|
-
"enabled": true,
|
|
38
|
-
"rules": {
|
|
39
|
-
"recommended": true,
|
|
40
|
-
"style": {
|
|
41
|
-
"useTemplate": {
|
|
42
|
-
"level": "error"
|
|
43
|
-
},
|
|
44
|
-
"useFilenamingConvention": {
|
|
45
|
-
"level": "error",
|
|
46
|
-
"options": {
|
|
47
|
-
"filenameCases": ["export"],
|
|
48
|
-
"requireAscii": true
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
"useConsistentTypeDefinitions": {
|
|
52
|
-
"level": "error",
|
|
53
|
-
"options": {
|
|
54
|
-
"style": "type"
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"useConsistentMemberAccessibility": {
|
|
58
|
-
"level": "error",
|
|
59
|
-
"options": {
|
|
60
|
-
"accessibility": "noPublic"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import { promptConfirm } from "../../shared/promptConfirm";
|
|
7
|
-
import { removeEslint } from "../../shared/removeEslint";
|
|
8
|
-
import { printDiff } from "../../utils/printDiff";
|
|
9
|
-
|
|
10
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
export async function init(): Promise<void> {
|
|
13
|
-
removeEslint();
|
|
14
|
-
|
|
15
|
-
const biomeConfigPath = "biome.json";
|
|
16
|
-
|
|
17
|
-
if (!existsSync(biomeConfigPath)) {
|
|
18
|
-
console.log("Initializing Biome...");
|
|
19
|
-
execSync("npx @biomejs/biome init", { stdio: "inherit" });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!existsSync(biomeConfigPath)) {
|
|
23
|
-
console.log("No biome.json found, skipping linter config");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const linterConfigPath = join(__dirname, "commands/lint/biome.linter.json");
|
|
28
|
-
const linterConfig = JSON.parse(readFileSync(linterConfigPath, "utf8"));
|
|
29
|
-
const biomeConfig = JSON.parse(readFileSync(biomeConfigPath, "utf8"));
|
|
30
|
-
|
|
31
|
-
const oldContent = `${JSON.stringify(biomeConfig, null, 2)}\n`;
|
|
32
|
-
biomeConfig.linter = linterConfig.linter;
|
|
33
|
-
if (linterConfig.overrides) {
|
|
34
|
-
biomeConfig.overrides = linterConfig.overrides;
|
|
35
|
-
}
|
|
36
|
-
const newContent = `${JSON.stringify(biomeConfig, null, 2)}\n`;
|
|
37
|
-
|
|
38
|
-
if (oldContent === newContent) {
|
|
39
|
-
console.log("biome.json already has the correct linter config");
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
console.log(chalk.yellow("\n⚠️ biome.json will be updated:"));
|
|
44
|
-
console.log();
|
|
45
|
-
printDiff(oldContent, newContent);
|
|
46
|
-
|
|
47
|
-
const confirm = await promptConfirm(chalk.red("Update biome.json?"));
|
|
48
|
-
|
|
49
|
-
if (!confirm) {
|
|
50
|
-
console.log("Skipped biome.json update");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
writeFileSync(biomeConfigPath, newContent);
|
|
55
|
-
console.log("Updated biome.json with linter config");
|
|
56
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type { Project } from "ts-morph";
|
|
4
|
-
import { renameExports } from "./renameExports";
|
|
5
|
-
|
|
6
|
-
type Move = { sourcePath: string; destPath: string };
|
|
7
|
-
|
|
8
|
-
function isCaseOnly(a: string, b: string): boolean {
|
|
9
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function moveCaseInsensitive(absSource: string, absDest: string): void {
|
|
13
|
-
const tmp = `${absSource}.tmp`;
|
|
14
|
-
fs.renameSync(absSource, tmp);
|
|
15
|
-
fs.renameSync(tmp, absDest);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function applyMoves(
|
|
19
|
-
project: Project,
|
|
20
|
-
moves: Move[],
|
|
21
|
-
cwd: string,
|
|
22
|
-
emit: (line: string) => void,
|
|
23
|
-
): void {
|
|
24
|
-
for (const { sourcePath, destPath } of moves) {
|
|
25
|
-
const start = performance.now();
|
|
26
|
-
const absSource = path.resolve(sourcePath);
|
|
27
|
-
const absDest = path.resolve(destPath);
|
|
28
|
-
|
|
29
|
-
for (const r of renameExports(project, absSource, absDest)) {
|
|
30
|
-
emit(` Renamed export ${r} in ${sourcePath}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const sourceFile = project.getSourceFile(absSource);
|
|
34
|
-
if (sourceFile) sourceFile.move(absDest);
|
|
35
|
-
|
|
36
|
-
const ms = (performance.now() - start).toFixed(0);
|
|
37
|
-
const rel = `${path.relative(cwd, absSource)} → ${path.relative(cwd, absDest)}`;
|
|
38
|
-
emit(` Renamed ${rel} (${ms}ms)`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
project.saveSync();
|
|
42
|
-
|
|
43
|
-
for (const { sourcePath, destPath } of moves) {
|
|
44
|
-
const absSource = path.resolve(sourcePath);
|
|
45
|
-
const absDest = path.resolve(destPath);
|
|
46
|
-
if (isCaseOnly(absSource, absDest) && fs.existsSync(absSource)) {
|
|
47
|
-
moveCaseInsensitive(absSource, absDest);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { findSourceFiles } from "../../../shared/findSourceFiles";
|
|
4
|
-
|
|
5
|
-
export type FileNameViolation = {
|
|
6
|
-
filePath: string;
|
|
7
|
-
fileName: string;
|
|
8
|
-
suggestedName: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function hasClassOrComponent(content: string): boolean {
|
|
12
|
-
const classPattern = /^(export\s+)?(abstract\s+)?class\s+\w+/m;
|
|
13
|
-
const functionComponentPattern =
|
|
14
|
-
/^(export\s+)?(default\s+)?function\s+[A-Z]\w*\s*\(/m;
|
|
15
|
-
const arrowComponentPattern = /^(export\s+)?(const|let)\s+[A-Z]\w*\s*=.*=>/m;
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
classPattern.test(content) ||
|
|
19
|
-
functionComponentPattern.test(content) ||
|
|
20
|
-
arrowComponentPattern.test(content)
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function hasMatchingTypeExport(
|
|
25
|
-
content: string,
|
|
26
|
-
nameWithoutExt: string,
|
|
27
|
-
): boolean {
|
|
28
|
-
const typePattern = new RegExp(
|
|
29
|
-
`^export\\s+type\\s+${nameWithoutExt}\\b`,
|
|
30
|
-
"m",
|
|
31
|
-
);
|
|
32
|
-
const interfacePattern = new RegExp(
|
|
33
|
-
`^export\\s+interface\\s+${nameWithoutExt}\\b`,
|
|
34
|
-
"m",
|
|
35
|
-
);
|
|
36
|
-
return typePattern.test(content) || interfacePattern.test(content);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function suggestName(fileName: string): string {
|
|
40
|
-
const nameWithoutExt = fileName.replace(/\.(ts|tsx)$/, "");
|
|
41
|
-
const ext = fileName.slice(nameWithoutExt.length);
|
|
42
|
-
|
|
43
|
-
// SCREAMING_SNAKE_CASE → camelCase (e.g. OC_COLORS → ocColors)
|
|
44
|
-
if (/^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/.test(nameWithoutExt)) {
|
|
45
|
-
const camel = nameWithoutExt
|
|
46
|
-
.toLowerCase()
|
|
47
|
-
.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());
|
|
48
|
-
return `${camel}${ext}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return `${nameWithoutExt.charAt(0).toLowerCase()}${nameWithoutExt.slice(1)}${ext}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function checkFileNames(): FileNameViolation[] {
|
|
55
|
-
const sourceFiles = findSourceFiles("src");
|
|
56
|
-
const violations: FileNameViolation[] = [];
|
|
57
|
-
|
|
58
|
-
for (const filePath of sourceFiles) {
|
|
59
|
-
const fileName = path.basename(filePath);
|
|
60
|
-
const nameWithoutExt = fileName.replace(/\.(ts|tsx)$/, "");
|
|
61
|
-
|
|
62
|
-
// Skip .stories and .test files — they mirror the component/module name
|
|
63
|
-
if (/\.(stories|test)\.(ts|tsx)$/.test(fileName)) continue;
|
|
64
|
-
|
|
65
|
-
if (/^[A-Z]/.test(nameWithoutExt)) {
|
|
66
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
67
|
-
if (
|
|
68
|
-
!hasClassOrComponent(content) &&
|
|
69
|
-
!hasMatchingTypeExport(content, nameWithoutExt)
|
|
70
|
-
) {
|
|
71
|
-
violations.push({
|
|
72
|
-
filePath,
|
|
73
|
-
fileName,
|
|
74
|
-
suggestedName: suggestName(fileName),
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return violations;
|
|
81
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { Project } from "ts-morph";
|
|
4
|
-
|
|
5
|
-
export function createLintProject(): Project {
|
|
6
|
-
const tsConfigPath = path.resolve("tsconfig.json");
|
|
7
|
-
const project = fs.existsSync(tsConfigPath)
|
|
8
|
-
? new Project({
|
|
9
|
-
tsConfigFilePath: tsConfigPath,
|
|
10
|
-
skipAddingFilesFromTsConfig: true,
|
|
11
|
-
})
|
|
12
|
-
: new Project();
|
|
13
|
-
|
|
14
|
-
project.addSourceFilesAtPaths("src/**/*.{ts,tsx}");
|
|
15
|
-
return project;
|
|
16
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { applyMoves } from "./applyMoves";
|
|
3
|
-
import { createLintProject } from "./createLintProject";
|
|
4
|
-
|
|
5
|
-
type Move = { sourcePath: string; destPath: string };
|
|
6
|
-
|
|
7
|
-
export function fixFileNameViolations(moves: Move[]): void {
|
|
8
|
-
const start = performance.now();
|
|
9
|
-
const project = createLintProject();
|
|
10
|
-
const cwd = process.cwd();
|
|
11
|
-
|
|
12
|
-
applyMoves(project, moves, cwd, (line) => console.log(chalk.green(line)));
|
|
13
|
-
|
|
14
|
-
const ms = (performance.now() - start).toFixed(0);
|
|
15
|
-
console.log(chalk.dim(` Done in ${ms}ms`));
|
|
16
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { runFileNameCheck } from "./runFileNameCheck";
|
|
2
|
-
import { runImportExtensionCheck } from "./runImportExtensionCheck";
|
|
3
|
-
import { runStaticImportCheck } from "./runStaticImportCheck";
|
|
4
|
-
|
|
5
|
-
type LintOptions = {
|
|
6
|
-
fix?: boolean;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function lint(options: LintOptions = {}): void {
|
|
10
|
-
const fileNamePassed = runFileNameCheck(options.fix);
|
|
11
|
-
const staticImportPassed = runStaticImportCheck();
|
|
12
|
-
const importExtensionPassed = runImportExtensionCheck();
|
|
13
|
-
|
|
14
|
-
if (!fileNamePassed || !staticImportPassed || !importExtensionPassed) {
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import type { Project } from "ts-morph";
|
|
3
|
-
import { SyntaxKind } from "ts-morph";
|
|
4
|
-
|
|
5
|
-
function nameWithoutExtension(filePath: string): string {
|
|
6
|
-
return path.basename(filePath).replace(/\.(ts|tsx)$/, "");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function renameExports(
|
|
10
|
-
project: Project,
|
|
11
|
-
absSource: string,
|
|
12
|
-
absDest: string,
|
|
13
|
-
): string[] {
|
|
14
|
-
const oldName = nameWithoutExtension(absSource);
|
|
15
|
-
const newName = nameWithoutExtension(absDest);
|
|
16
|
-
const sourceFile = project.getSourceFile(absSource);
|
|
17
|
-
if (!sourceFile) return [];
|
|
18
|
-
|
|
19
|
-
const renamed: string[] = [];
|
|
20
|
-
for (const [, declarations] of sourceFile.getExportedDeclarations()) {
|
|
21
|
-
for (const decl of declarations) {
|
|
22
|
-
// Skip type aliases and interfaces — they should stay PascalCase
|
|
23
|
-
const kind = decl.getKind();
|
|
24
|
-
if (
|
|
25
|
-
kind === SyntaxKind.TypeAliasDeclaration ||
|
|
26
|
-
kind === SyntaxKind.InterfaceDeclaration
|
|
27
|
-
) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
const nameNode = decl.getFirstChildByKind(SyntaxKind.Identifier);
|
|
31
|
-
if (!nameNode || nameNode.getText() !== oldName) continue;
|
|
32
|
-
nameNode.rename(newName);
|
|
33
|
-
renamed.push(`${oldName} → ${newName}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return renamed;
|
|
37
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { checkFileNames, type FileNameViolation } from "./checkFileNames";
|
|
4
|
-
import { fixFileNameViolations } from "./fixFileNameViolations";
|
|
5
|
-
|
|
6
|
-
function reportViolations(violations: FileNameViolation[]): void {
|
|
7
|
-
console.error(chalk.red("\nFile name check failed:\n"));
|
|
8
|
-
console.error(
|
|
9
|
-
chalk.red(
|
|
10
|
-
" Files without classes or React components should not start with a capital letter.\n",
|
|
11
|
-
),
|
|
12
|
-
);
|
|
13
|
-
for (const violation of violations) {
|
|
14
|
-
console.error(chalk.red(` ${violation.filePath}`));
|
|
15
|
-
console.error(chalk.gray(` Rename to: ${violation.suggestedName}\n`));
|
|
16
|
-
}
|
|
17
|
-
console.error(chalk.dim(" Run with -f to auto-fix.\n"));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function runFileNameCheck(fix = false): boolean {
|
|
21
|
-
const violations = checkFileNames();
|
|
22
|
-
|
|
23
|
-
if (violations.length === 0) {
|
|
24
|
-
if (!process.env.CLAUDECODE) {
|
|
25
|
-
console.log(
|
|
26
|
-
"File name check passed. All PascalCase files contain classes or components.",
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!fix) {
|
|
33
|
-
reportViolations(violations);
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
fixFileNameViolations(
|
|
38
|
-
violations.map((v) => ({
|
|
39
|
-
sourcePath: v.filePath,
|
|
40
|
-
destPath: path.join(path.dirname(v.filePath), v.suggestedName),
|
|
41
|
-
})),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { findSourceFiles } from "../../../shared/findSourceFiles";
|
|
3
|
-
import { type LintViolation, reportViolations } from "../shared";
|
|
4
|
-
|
|
5
|
-
function checkForImportExtensions(filePath: string): LintViolation[] {
|
|
6
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
7
|
-
const lines = content.split("\n");
|
|
8
|
-
const violations: LintViolation[] = [];
|
|
9
|
-
|
|
10
|
-
// Matches relative imports with .js or .ts extensions
|
|
11
|
-
const importExtensionPattern = /from\s+["']\..*\.(js|ts)["']/;
|
|
12
|
-
|
|
13
|
-
for (let i = 0; i < lines.length; i++) {
|
|
14
|
-
const line = lines[i];
|
|
15
|
-
if (importExtensionPattern.test(line)) {
|
|
16
|
-
violations.push({
|
|
17
|
-
filePath,
|
|
18
|
-
line: i + 1,
|
|
19
|
-
content: line.trim(),
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return violations;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function checkImportExtensions(): LintViolation[] {
|
|
28
|
-
const sourceFiles = findSourceFiles("src");
|
|
29
|
-
const violations: LintViolation[] = [];
|
|
30
|
-
|
|
31
|
-
for (const filePath of sourceFiles) {
|
|
32
|
-
violations.push(...checkForImportExtensions(filePath));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return violations;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function runImportExtensionCheck(): boolean {
|
|
39
|
-
return reportViolations(
|
|
40
|
-
checkImportExtensions(),
|
|
41
|
-
"Import extension check",
|
|
42
|
-
"File extensions in imports are not allowed. Use extensionless imports instead.",
|
|
43
|
-
"Import extension check passed. No file extensions in imports found.",
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { findSourceFiles } from "../../../shared/findSourceFiles";
|
|
3
|
-
import { type LintViolation, reportViolations } from "../shared";
|
|
4
|
-
|
|
5
|
-
function checkForDynamicImports(filePath: string): LintViolation[] {
|
|
6
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
7
|
-
const lines = content.split("\n");
|
|
8
|
-
const violations: LintViolation[] = [];
|
|
9
|
-
|
|
10
|
-
const requirePattern = /\brequire\s*\(/;
|
|
11
|
-
const dynamicImportPattern = /\bimport\s*\(/;
|
|
12
|
-
|
|
13
|
-
for (let i = 0; i < lines.length; i++) {
|
|
14
|
-
const line = lines[i];
|
|
15
|
-
if (requirePattern.test(line) || dynamicImportPattern.test(line)) {
|
|
16
|
-
violations.push({
|
|
17
|
-
filePath,
|
|
18
|
-
line: i + 1,
|
|
19
|
-
content: line.trim(),
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return violations;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function checkStaticImports(): LintViolation[] {
|
|
28
|
-
const sourceFiles = findSourceFiles("src");
|
|
29
|
-
const violations: LintViolation[] = [];
|
|
30
|
-
|
|
31
|
-
for (const filePath of sourceFiles) {
|
|
32
|
-
violations.push(...checkForDynamicImports(filePath));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return violations;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function runStaticImportCheck(): boolean {
|
|
39
|
-
return reportViolations(
|
|
40
|
-
checkStaticImports(),
|
|
41
|
-
"Static import check",
|
|
42
|
-
"Dynamic imports are not allowed. Use static imports instead.",
|
|
43
|
-
"Static import check passed. No dynamic imports found.",
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
|
|
3
|
-
export type LintViolation = {
|
|
4
|
-
filePath: string;
|
|
5
|
-
line: number;
|
|
6
|
-
content: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function reportViolations(
|
|
10
|
-
violations: LintViolation[],
|
|
11
|
-
checkName: string,
|
|
12
|
-
errorMessage: string,
|
|
13
|
-
successMessage: string,
|
|
14
|
-
): boolean {
|
|
15
|
-
if (violations.length > 0) {
|
|
16
|
-
console.error(chalk.red(`\n${checkName} failed:\n`));
|
|
17
|
-
console.error(chalk.red(` ${errorMessage}\n`));
|
|
18
|
-
for (const violation of violations) {
|
|
19
|
-
console.error(chalk.red(` ${violation.filePath}:${violation.line}`));
|
|
20
|
-
console.error(chalk.gray(` ${violation.content}\n`));
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (!process.env.CLAUDECODE) {
|
|
26
|
-
console.log(successMessage);
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|