@nebulord/sickbay-core 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/README.md +136 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +2992 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @nebulord/sickbay-core
|
|
2
|
+
|
|
3
|
+
The analysis engine for Sickbay. Orchestrates all health checks in parallel and returns a structured `SickbayReport`.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@nebulord/sickbay-core` exposes a single `runSickbay()` function that:
|
|
8
|
+
|
|
9
|
+
1. Detects project metadata (framework, package manager, tooling)
|
|
10
|
+
2. Runs all enabled checks concurrently via `Promise.allSettled`
|
|
11
|
+
3. Calculates weighted scores per category
|
|
12
|
+
4. Returns a `SickbayReport` with issues and fix suggestions
|
|
13
|
+
|
|
14
|
+
## API
|
|
15
|
+
|
|
16
|
+
### `runSickbay(options)`
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { runSickbay } from '@nebulord/sickbay-core';
|
|
20
|
+
|
|
21
|
+
const report = await runSickbay({
|
|
22
|
+
projectPath: '/path/to/project',
|
|
23
|
+
checks: ['knip', 'npm-audit'], // optional — runs all if omitted
|
|
24
|
+
verbose: false,
|
|
25
|
+
onCheckStart: (name) => console.log(`Starting ${name}`),
|
|
26
|
+
onCheckComplete: (result) => console.log(`Done: ${result.id}`),
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Options:**
|
|
31
|
+
|
|
32
|
+
| Option | Type | Description |
|
|
33
|
+
|--------|------|-------------|
|
|
34
|
+
| `projectPath` | `string` | Absolute path to the project root |
|
|
35
|
+
| `checks` | `string[]` | Subset of check IDs to run (default: all) |
|
|
36
|
+
| `verbose` | `boolean` | Pass through tool output |
|
|
37
|
+
| `onCheckStart` | `(name: string) => void` | Called when a check begins |
|
|
38
|
+
| `onCheckComplete` | `(result: CheckResult) => void` | Called when a check finishes |
|
|
39
|
+
|
|
40
|
+
### Key Types
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
interface SickbayReport {
|
|
44
|
+
timestamp: string;
|
|
45
|
+
projectPath: string;
|
|
46
|
+
projectInfo: ProjectInfo;
|
|
47
|
+
checks: CheckResult[];
|
|
48
|
+
overallScore: number;
|
|
49
|
+
summary: { critical: number; warnings: number; info: number };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface CheckResult {
|
|
53
|
+
id: string;
|
|
54
|
+
category: 'dependencies' | 'performance' | 'code-quality' | 'security' | 'git';
|
|
55
|
+
name: string;
|
|
56
|
+
score: number; // 0–100
|
|
57
|
+
status: 'pass' | 'warning' | 'fail' | 'skipped';
|
|
58
|
+
issues: Issue[];
|
|
59
|
+
toolsUsed: string[];
|
|
60
|
+
duration: number; // milliseconds
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface Issue {
|
|
65
|
+
severity: 'critical' | 'warning' | 'info';
|
|
66
|
+
message: string;
|
|
67
|
+
file?: string;
|
|
68
|
+
fix?: { description: string; command: string };
|
|
69
|
+
reportedBy: string[];
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Checks
|
|
74
|
+
|
|
75
|
+
Each check lives in `src/integrations/` and extends `BaseRunner`:
|
|
76
|
+
|
|
77
|
+
| File | Check ID | External tool |
|
|
78
|
+
|------|----------|---------------|
|
|
79
|
+
| `knip.ts` | `knip` | `knip` |
|
|
80
|
+
| `depcheck.ts` | `depcheck` | `depcheck` |
|
|
81
|
+
| `npm-check-updates.ts` | `npm-check-updates` | `ncu` |
|
|
82
|
+
| `npm-audit.ts` | `npm-audit` | `npm` |
|
|
83
|
+
| `license-checker.ts` | `license-checker` | `license-checker` |
|
|
84
|
+
| `madge.ts` | `madge` | `madge` |
|
|
85
|
+
| `jscpd.ts` | `jscpd` | `jscpd` |
|
|
86
|
+
| `coverage.ts` | `coverage` | *(auto-runs vitest/jest)* |
|
|
87
|
+
| `source-map-explorer.ts` | `source-map-explorer` | `source-map-explorer` |
|
|
88
|
+
| `git.ts` | `git` | `git` |
|
|
89
|
+
|
|
90
|
+
### Adding a new check
|
|
91
|
+
|
|
92
|
+
1. Create `src/integrations/my-check.ts` extending `BaseRunner`
|
|
93
|
+
2. Implement `async run(projectPath): Promise<CheckResult>`
|
|
94
|
+
3. Optionally implement `async isApplicable(projectPath): Promise<boolean>`
|
|
95
|
+
4. Register it in `src/runner.ts` in the `ALL_RUNNERS` array
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
export class MyCheckRunner extends BaseRunner {
|
|
99
|
+
name = 'my-check';
|
|
100
|
+
category = 'code-quality' as const;
|
|
101
|
+
|
|
102
|
+
async run(projectPath: string): Promise<CheckResult> {
|
|
103
|
+
const elapsed = timer();
|
|
104
|
+
// ... run tool, parse output, build issues array
|
|
105
|
+
return {
|
|
106
|
+
id: 'my-check',
|
|
107
|
+
category: this.category,
|
|
108
|
+
name: 'My Check',
|
|
109
|
+
score: 100,
|
|
110
|
+
status: 'pass',
|
|
111
|
+
issues: [],
|
|
112
|
+
toolsUsed: ['my-tool'],
|
|
113
|
+
duration: elapsed(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Scoring Weights
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const CATEGORY_WEIGHTS = {
|
|
123
|
+
security: 0.30,
|
|
124
|
+
dependencies: 0.25,
|
|
125
|
+
'code-quality': 0.25,
|
|
126
|
+
performance: 0.15,
|
|
127
|
+
git: 0.05,
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Build
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pnpm build # tsup → dist/
|
|
135
|
+
pnpm dev # watch mode
|
|
136
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
interface Quote {
|
|
2
|
+
text: string;
|
|
3
|
+
source: string;
|
|
4
|
+
severity: 'critical' | 'warning' | 'info' | 'allClear';
|
|
5
|
+
}
|
|
6
|
+
interface SickbayReport {
|
|
7
|
+
timestamp: string;
|
|
8
|
+
projectPath: string;
|
|
9
|
+
projectInfo: ProjectInfo;
|
|
10
|
+
checks: CheckResult[];
|
|
11
|
+
overallScore: number;
|
|
12
|
+
summary: {
|
|
13
|
+
critical: number;
|
|
14
|
+
warnings: number;
|
|
15
|
+
info: number;
|
|
16
|
+
};
|
|
17
|
+
quote?: Quote;
|
|
18
|
+
}
|
|
19
|
+
interface ProjectInfo {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
hasTypeScript: boolean;
|
|
23
|
+
hasESLint: boolean;
|
|
24
|
+
hasPrettier: boolean;
|
|
25
|
+
framework: 'react' | 'next' | 'vite' | 'cra' | 'express' | 'fastify' | 'koa' | 'hapi' | 'node' | 'hono' | 'unknown';
|
|
26
|
+
packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
27
|
+
totalDependencies: number;
|
|
28
|
+
dependencies: Record<string, string>;
|
|
29
|
+
devDependencies: Record<string, string>;
|
|
30
|
+
overrides?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
interface CheckResult {
|
|
33
|
+
id: string;
|
|
34
|
+
category: 'dependencies' | 'performance' | 'code-quality' | 'security' | 'git' | 'unknown-category';
|
|
35
|
+
name: string;
|
|
36
|
+
score: number;
|
|
37
|
+
status: 'pass' | 'warning' | 'fail' | 'skipped';
|
|
38
|
+
issues: Issue[];
|
|
39
|
+
toolsUsed: string[];
|
|
40
|
+
duration: number;
|
|
41
|
+
metadata?: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
interface Issue {
|
|
44
|
+
severity: 'critical' | 'warning' | 'info';
|
|
45
|
+
message: string;
|
|
46
|
+
file?: string;
|
|
47
|
+
fix?: FixSuggestion;
|
|
48
|
+
reportedBy: string[];
|
|
49
|
+
}
|
|
50
|
+
interface FixSuggestion {
|
|
51
|
+
command?: string;
|
|
52
|
+
description: string;
|
|
53
|
+
modifiesSource?: boolean;
|
|
54
|
+
nextSteps?: string;
|
|
55
|
+
codeChange?: {
|
|
56
|
+
before: string;
|
|
57
|
+
after: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
type Framework = 'react' | 'next' | 'angular' | 'vue' | 'svelte' | 'remix';
|
|
61
|
+
type Runtime = 'browser' | 'node' | 'edge' | 'unknown';
|
|
62
|
+
type BuildTool = 'vite' | 'webpack' | 'esbuild' | 'rollup' | 'tsc' | 'unknown';
|
|
63
|
+
type TestFramework = 'vitest' | 'jest' | 'mocha' | null;
|
|
64
|
+
interface ProjectContext {
|
|
65
|
+
runtime: Runtime;
|
|
66
|
+
frameworks: Framework[];
|
|
67
|
+
buildTool: BuildTool;
|
|
68
|
+
testFramework: TestFramework;
|
|
69
|
+
}
|
|
70
|
+
interface ToolRunner {
|
|
71
|
+
name: string;
|
|
72
|
+
category: CheckResult['category'];
|
|
73
|
+
run(projectPath: string, options?: RunOptions): Promise<CheckResult>;
|
|
74
|
+
isApplicable(projectPath: string, context: ProjectContext): Promise<boolean>;
|
|
75
|
+
isApplicableToContext(context: ProjectContext): boolean;
|
|
76
|
+
}
|
|
77
|
+
interface RunOptions {
|
|
78
|
+
verbose?: boolean;
|
|
79
|
+
timeout?: number;
|
|
80
|
+
}
|
|
81
|
+
interface ToolResult {
|
|
82
|
+
tool: string;
|
|
83
|
+
status: 'success' | 'error' | 'skipped';
|
|
84
|
+
data: unknown;
|
|
85
|
+
duration: number;
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
88
|
+
interface MonorepoInfo {
|
|
89
|
+
isMonorepo: true;
|
|
90
|
+
type: 'pnpm' | 'npm' | 'yarn' | 'turbo' | 'nx' | 'lerna';
|
|
91
|
+
packageManager: ProjectInfo['packageManager'];
|
|
92
|
+
packagePaths: string[];
|
|
93
|
+
}
|
|
94
|
+
interface PackageReport {
|
|
95
|
+
name: string;
|
|
96
|
+
path: string;
|
|
97
|
+
relativePath: string;
|
|
98
|
+
framework: ProjectInfo['framework'];
|
|
99
|
+
runtime: Runtime;
|
|
100
|
+
checks: CheckResult[];
|
|
101
|
+
score: number;
|
|
102
|
+
summary: {
|
|
103
|
+
critical: number;
|
|
104
|
+
warnings: number;
|
|
105
|
+
info: number;
|
|
106
|
+
};
|
|
107
|
+
dependencies: Record<string, string>;
|
|
108
|
+
devDependencies: Record<string, string>;
|
|
109
|
+
}
|
|
110
|
+
interface MonorepoReport {
|
|
111
|
+
isMonorepo: true;
|
|
112
|
+
timestamp: string;
|
|
113
|
+
rootPath: string;
|
|
114
|
+
monorepoType: MonorepoInfo['type'];
|
|
115
|
+
packageManager: ProjectInfo['packageManager'];
|
|
116
|
+
packages: PackageReport[];
|
|
117
|
+
overallScore: number;
|
|
118
|
+
summary: {
|
|
119
|
+
critical: number;
|
|
120
|
+
warnings: number;
|
|
121
|
+
info: number;
|
|
122
|
+
};
|
|
123
|
+
quote?: Quote;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface RunnerOptions {
|
|
127
|
+
projectPath?: string;
|
|
128
|
+
checks?: string[];
|
|
129
|
+
verbose?: boolean;
|
|
130
|
+
quotes?: boolean;
|
|
131
|
+
onRunnersReady?: (names: string[]) => void;
|
|
132
|
+
onCheckStart?: (name: string) => void;
|
|
133
|
+
onCheckComplete?: (result: CheckResult) => void;
|
|
134
|
+
onPackageStart?: (name: string) => void;
|
|
135
|
+
onPackageComplete?: (report: PackageReport) => void;
|
|
136
|
+
}
|
|
137
|
+
declare function runSickbay(options?: RunnerOptions): Promise<SickbayReport>;
|
|
138
|
+
declare function runSickbayMonorepo(options?: RunnerOptions): Promise<MonorepoReport>;
|
|
139
|
+
|
|
140
|
+
interface DependencyTreeNode {
|
|
141
|
+
name: string;
|
|
142
|
+
version: string;
|
|
143
|
+
dependencies?: Record<string, DependencyTreeNode>;
|
|
144
|
+
}
|
|
145
|
+
interface DependencyTree {
|
|
146
|
+
name: string;
|
|
147
|
+
version: string;
|
|
148
|
+
packageManager: string;
|
|
149
|
+
dependencies: Record<string, DependencyTreeNode>;
|
|
150
|
+
}
|
|
151
|
+
interface MonorepoDependencyTree {
|
|
152
|
+
packages: Record<string, DependencyTree>;
|
|
153
|
+
}
|
|
154
|
+
declare function getDependencyTree(projectPath: string, packageManager: ProjectInfo['packageManager']): Promise<DependencyTree>;
|
|
155
|
+
|
|
156
|
+
declare function calculateOverallScore(checks: CheckResult[]): number;
|
|
157
|
+
declare function buildSummary(checks: CheckResult[]): SickbayReport['summary'];
|
|
158
|
+
declare function getScoreColor(score: number): 'green' | 'yellow' | 'red';
|
|
159
|
+
declare function getScoreEmoji(score: number): string;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* detectProject analyzes the given project directory to extract key information about the project setup.
|
|
163
|
+
* It reads the package.json file to determine the project's name, version, dependencies, and devDependencies.
|
|
164
|
+
* The function checks for the presence of TypeScript configuration and ESLint/Prettier configurations to infer if those tools are being used.
|
|
165
|
+
* It also detects the framework used (e.g., Next.js, Vite, Create React App) based on dependencies and configuration files.
|
|
166
|
+
* Finally, it identifies the package manager in use by checking for lock files (pnpm-lock.yaml, yarn.lock).
|
|
167
|
+
* This information is crucial for tailoring checks and recommendations specific to the project's technology stack and tooling.
|
|
168
|
+
*/
|
|
169
|
+
declare function detectProject(projectPath: string): Promise<ProjectInfo>;
|
|
170
|
+
declare function detectPackageManager(projectPath: string): ProjectInfo["packageManager"];
|
|
171
|
+
declare function detectContext(projectPath: string): Promise<ProjectContext>;
|
|
172
|
+
|
|
173
|
+
type NotMonorepo = {
|
|
174
|
+
isMonorepo: false;
|
|
175
|
+
};
|
|
176
|
+
declare function detectMonorepo(rootPath: string): Promise<MonorepoInfo | NotMonorepo>;
|
|
177
|
+
|
|
178
|
+
declare const WARN_LINES = 400;
|
|
179
|
+
declare const CRITICAL_LINES = 600;
|
|
180
|
+
declare const SCORE_EXCELLENT = 90;
|
|
181
|
+
declare const SCORE_GOOD = 80;
|
|
182
|
+
declare const SCORE_FAIR = 60;
|
|
183
|
+
|
|
184
|
+
export { type BuildTool, CRITICAL_LINES, type CheckResult, type DependencyTree, type DependencyTreeNode, type FixSuggestion, type Framework, type Issue, type MonorepoDependencyTree, type MonorepoInfo, type MonorepoReport, type PackageReport, type ProjectContext, type ProjectInfo, type Quote, type RunOptions, type Runtime, SCORE_EXCELLENT, SCORE_FAIR, SCORE_GOOD, type SickbayReport, type TestFramework, type ToolResult, type ToolRunner, WARN_LINES, buildSummary, calculateOverallScore, detectContext, detectMonorepo, detectPackageManager, detectProject, getDependencyTree, getScoreColor, getScoreEmoji, runSickbay, runSickbayMonorepo };
|