@johnfaleke/fathom 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 +285 -0
- package/dist/ai/provider.d.ts +20 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +2 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/checks/dependencies.d.ts +3 -0
- package/dist/checks/dependencies.d.ts.map +1 -0
- package/dist/checks/dependencies.js +106 -0
- package/dist/checks/dependencies.js.map +1 -0
- package/dist/checks/env-vars.d.ts +3 -0
- package/dist/checks/env-vars.d.ts.map +1 -0
- package/dist/checks/env-vars.js +76 -0
- package/dist/checks/env-vars.js.map +1 -0
- package/dist/checks/index.d.ts +4 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +5 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.d.ts +4 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +32 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +30 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +16 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +74 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/core/git.d.ts +4 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +40 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/project.d.ts +3 -0
- package/dist/core/project.d.ts.map +1 -0
- package/dist/core/project.js +59 -0
- package/dist/core/project.js.map +1 -0
- package/dist/core/runner.d.ts +3 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +8 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/core/storage.d.ts +17 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +84 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/render/terminal.d.ts +5 -0
- package/dist/render/terminal.d.ts.map +1 -0
- package/dist/render/terminal.js +120 -0
- package/dist/render/terminal.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Faleke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Fathom
|
|
2
|
+
|
|
3
|
+
> AI can generate the work. Fathom helps you understand the work.
|
|
4
|
+
|
|
5
|
+
Fathom is a local-first CLI for project Work State. It gives developers a deterministic, evidence-backed view of what is happening in a repo: what is in progress, what has been completed, what changed, and what may be silently broken.
|
|
6
|
+
|
|
7
|
+
## Why Fathom exists
|
|
8
|
+
|
|
9
|
+
Most project tooling answers either:
|
|
10
|
+
|
|
11
|
+
- which files changed, or
|
|
12
|
+
- what an AI thinks is happening.
|
|
13
|
+
|
|
14
|
+
Fathom does neither blindly. It focuses on observable evidence and clearly labelled findings:
|
|
15
|
+
|
|
16
|
+
- missing environment variables
|
|
17
|
+
- undeclared dependencies
|
|
18
|
+
- stale project state
|
|
19
|
+
- changed files grouped by project area
|
|
20
|
+
- a machine-readable snapshot of project health
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
Install Fathom globally:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --global @johnfaleke/fathom
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then run it from any project workspace:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd /path/to/your-project
|
|
34
|
+
fathom init
|
|
35
|
+
fathom check
|
|
36
|
+
fathom status
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The package installs the `fathom` command. You do not need to clone this
|
|
40
|
+
repository into the project being inspected.
|
|
41
|
+
|
|
42
|
+
For repository development, install dependencies locally instead:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Initialize a project:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run fathom -- init
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Set your current work and task state:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run fathom -- set --current "Ship OAuth integration" --complete "Login API, API docs" --incomplete "Webhook retry handling, Missing README"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Check the project for inconsistencies:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run fathom -- check
|
|
64
|
+
npm run fathom -- check --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Review project state:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm run fathom -- status
|
|
71
|
+
npm run fathom -- status --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
See what meaningfully changed:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm run fathom -- diff
|
|
78
|
+
npm run fathom -- diff --json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
After building:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm run build
|
|
85
|
+
node dist/cli.js init
|
|
86
|
+
node dist/cli.js status
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Use Fathom in another project
|
|
90
|
+
|
|
91
|
+
Fathom operates on the current working directory. Build it once, then run the
|
|
92
|
+
CLI while your other project is the working directory.
|
|
93
|
+
|
|
94
|
+
From PowerShell on Windows:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
Set-Location C:\path\to\Fathom
|
|
98
|
+
npm run build
|
|
99
|
+
Set-Location C:\path\to\your-project
|
|
100
|
+
node C:\path\to\Fathom\dist\cli.js init
|
|
101
|
+
node C:\path\to\Fathom\dist\cli.js check
|
|
102
|
+
node C:\path\to\Fathom\dist\cli.js status
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
From macOS or Linux:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd /path/to/Fathom
|
|
109
|
+
npm run build
|
|
110
|
+
cd /path/to/your-project
|
|
111
|
+
node /path/to/Fathom/dist/cli.js init
|
|
112
|
+
node /path/to/Fathom/dist/cli.js check
|
|
113
|
+
node /path/to/Fathom/dist/cli.js status
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Initialization creates `.fathom/` and `.env.example` in the target project.
|
|
117
|
+
The generated `.fathom/` directory is local project state; commit it only if
|
|
118
|
+
you want to share that state with collaborators. Use `--json` for automation:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
node /path/to/Fathom/dist/cli.js check --json
|
|
122
|
+
node /path/to/Fathom/dist/cli.js status --json
|
|
123
|
+
node /path/to/Fathom/dist/cli.js diff --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
To use the `fathom` command directly during local development, run `npm link`
|
|
127
|
+
once from the Fathom directory. Then open another project and run:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
fathom init
|
|
131
|
+
fathom check
|
|
132
|
+
fathom status
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The direct command works because Fathom always inspects the directory where
|
|
136
|
+
the command is run, not the directory where Fathom itself is installed.
|
|
137
|
+
|
|
138
|
+
## Open-source project site
|
|
139
|
+
|
|
140
|
+
The static project site lives in [`site/`](./site/index.html). Open
|
|
141
|
+
`site/index.html` directly in a browser, or serve the repository with any static
|
|
142
|
+
file server while developing. It includes the product overview, installation
|
|
143
|
+
guide, command reference, check interface, Work State schema, contribution links,
|
|
144
|
+
and support link.
|
|
145
|
+
|
|
146
|
+
The machine-readable [`llms.txt`](./llms.txt) file is a concise text reference for
|
|
147
|
+
AI tools, search systems, and integrations that need to understand Fathom without
|
|
148
|
+
parsing the full README or landing page.
|
|
149
|
+
|
|
150
|
+
## Package releases
|
|
151
|
+
|
|
152
|
+
The published package name is `@johnfaleke/fathom`; the installed command remains
|
|
153
|
+
`fathom`. Releases are published automatically by GitHub Actions when a maintainer
|
|
154
|
+
pushes a semantic version tag:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
git tag v0.1.0
|
|
158
|
+
git push origin v0.1.0
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Before the first release, the npm package owner must configure GitHub Actions as a
|
|
162
|
+
trusted publisher for `@johnfaleke/fathom` on npm. Future v0.2 and later tags will
|
|
163
|
+
then build, test, and publish automatically. A version tag does not happen by
|
|
164
|
+
itself; it is the deliberate release switch.
|
|
165
|
+
|
|
166
|
+
## Commands
|
|
167
|
+
|
|
168
|
+
| Command | Purpose |
|
|
169
|
+
|---------|---------|
|
|
170
|
+
| `fathom init` | Create `.fathom/` for state, config, and event history |
|
|
171
|
+
| `fathom set --current ...` | Update current work and task lists |
|
|
172
|
+
| `fathom status` | Show the current project state |
|
|
173
|
+
| `fathom diff` | Show semantic file changes and project impact |
|
|
174
|
+
| `fathom check` | Detect inconsistent or forgotten wiring |
|
|
175
|
+
|
|
176
|
+
Add `--json` to any of the reporting commands for machine-readable output.
|
|
177
|
+
|
|
178
|
+
## Example status output
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
FATHOM STATUS
|
|
182
|
+
|
|
183
|
+
Current work: Ship OAuth integration
|
|
184
|
+
Progress: 1 complete, 1 remaining
|
|
185
|
+
|
|
186
|
+
Completed:
|
|
187
|
+
✓ Login API
|
|
188
|
+
|
|
189
|
+
Remaining:
|
|
190
|
+
• Webhook retry handling
|
|
191
|
+
|
|
192
|
+
Findings:
|
|
193
|
+
⚠ left-pad is imported but not declared in package.json
|
|
194
|
+
|
|
195
|
+
Changes:
|
|
196
|
+
1 file(s) changed
|
|
197
|
+
~ src/app.ts
|
|
198
|
+
|
|
199
|
+
Attention:
|
|
200
|
+
1 thing(s) are worth reviewing.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Example JSON output
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"version": 1,
|
|
208
|
+
"updatedAt": "2026-09-20T00:00:00.000Z",
|
|
209
|
+
"root": "/path/to/project",
|
|
210
|
+
"currentWork": "Ship OAuth integration",
|
|
211
|
+
"completed": [
|
|
212
|
+
{ "id": "completed-login-api", "title": "Login API", "status": "completed" }
|
|
213
|
+
],
|
|
214
|
+
"incomplete": [
|
|
215
|
+
{ "id": "incomplete-webhook-retry-handling", "title": "Webhook retry handling", "status": "incomplete" }
|
|
216
|
+
],
|
|
217
|
+
"findings": [
|
|
218
|
+
{
|
|
219
|
+
"id": "deps.undeclared-import",
|
|
220
|
+
"category": "dependencies",
|
|
221
|
+
"severity": "warning",
|
|
222
|
+
"message": "left-pad is imported but not declared in package.json",
|
|
223
|
+
"evidence": []
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
"changes": {
|
|
227
|
+
"filesChanged": 1,
|
|
228
|
+
"summary": ["~ src/app.ts"]
|
|
229
|
+
},
|
|
230
|
+
"attention": 1
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Built-in checks
|
|
235
|
+
|
|
236
|
+
The current v0.1 checks are deterministic and evidence-based:
|
|
237
|
+
|
|
238
|
+
- **config.env-var-missing** — `process.env.X` used but not present in `.env.example`
|
|
239
|
+
- **deps.undeclared-import** — package imported but not declared in `package.json`
|
|
240
|
+
|
|
241
|
+
## Project configuration guidance
|
|
242
|
+
|
|
243
|
+
Fathom uses a local `.fathom/config.json` file and respects ignore rules. A good default config is deliberately conservative:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"version": 1,
|
|
248
|
+
"ignore": [
|
|
249
|
+
"node_modules",
|
|
250
|
+
"dist",
|
|
251
|
+
".git",
|
|
252
|
+
".fathom",
|
|
253
|
+
"coverage",
|
|
254
|
+
".next"
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For more guidance, see [docs/configuration.md](./docs/configuration.md).
|
|
260
|
+
|
|
261
|
+
## AI interpretation layer (optional)
|
|
262
|
+
|
|
263
|
+
Fathom intentionally keeps deterministic checks separate from interpretation. If you want AI assistance later, it should be opt-in and use a clean provider interface instead of hard coding a single model provider.
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
export interface AIProvider {
|
|
267
|
+
id: string;
|
|
268
|
+
name: string;
|
|
269
|
+
interpret(input: AIInterpretationRequest): Promise<AIInterpretationResult>;
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
This keeps the core product honest: evidence stays evidence, and model output remains clearly labelled as interpretation.
|
|
274
|
+
|
|
275
|
+
See [docs/ai-provider.md](./docs/ai-provider.md) for the provider surface and integration conventions.
|
|
276
|
+
|
|
277
|
+
## Docs and roadmap
|
|
278
|
+
|
|
279
|
+
- [FATHOM.md](./FATHOM.md) — vision, principles, and roadmap
|
|
280
|
+
- [docs/configuration.md](./docs/configuration.md) — config and ignore recommendations
|
|
281
|
+
- [docs/ai-provider.md](./docs/ai-provider.md) — optional provider abstraction for interpretation
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ProviderType = "openai" | "anthropic" | "google" | "custom";
|
|
2
|
+
export interface AIInterpretationRequest {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
workState: unknown;
|
|
5
|
+
prompt: string;
|
|
6
|
+
context?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface AIInterpretationResult {
|
|
9
|
+
summary: string;
|
|
10
|
+
confidence: number;
|
|
11
|
+
notes: string[];
|
|
12
|
+
provider: ProviderType;
|
|
13
|
+
}
|
|
14
|
+
export interface AIProvider {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
type: ProviderType;
|
|
18
|
+
interpret(input: AIInterpretationRequest): Promise<AIInterpretationResult>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC5E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../src/checks/dependencies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAA2B,MAAM,aAAa,CAAC;AAalE,eAAO,MAAM,eAAe,EAAE,KA+E7B,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { builtinModules } from "node:module";
|
|
2
|
+
const NODE_BUILTINS = new Set(builtinModules.flatMap((m) => (m.startsWith("node:") ? [m.slice(5)] : [m])));
|
|
3
|
+
const IMPORT_FROM = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?["']([^"']+)["']/g;
|
|
4
|
+
const REQUIRE_CALL = /require\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
5
|
+
const DYNAMIC_IMPORT = /import\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
6
|
+
const CODE_EXT = /\.(?:[cm]?[jt]sx?|mjs|cjs)$/;
|
|
7
|
+
export const dependencyCheck = {
|
|
8
|
+
id: "deps.undeclared-import",
|
|
9
|
+
async run(ctx) {
|
|
10
|
+
const pkgRaw = await ctx.readText("package.json");
|
|
11
|
+
if (!pkgRaw)
|
|
12
|
+
return [];
|
|
13
|
+
let pkg;
|
|
14
|
+
try {
|
|
15
|
+
pkg = JSON.parse(pkgRaw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const declared = new Set([
|
|
21
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
22
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
23
|
+
...Object.keys(pkg.peerDependencies ?? {}),
|
|
24
|
+
...Object.keys(pkg.optionalDependencies ?? {}),
|
|
25
|
+
]);
|
|
26
|
+
const used = new Map();
|
|
27
|
+
for (const file of ctx.files) {
|
|
28
|
+
if (!CODE_EXT.test(file))
|
|
29
|
+
continue;
|
|
30
|
+
const text = await ctx.readText(file);
|
|
31
|
+
if (!text)
|
|
32
|
+
continue;
|
|
33
|
+
const specs = [
|
|
34
|
+
...matchAll(text, IMPORT_FROM),
|
|
35
|
+
...matchAll(text, REQUIRE_CALL),
|
|
36
|
+
...matchAll(text, DYNAMIC_IMPORT),
|
|
37
|
+
];
|
|
38
|
+
for (const spec of specs) {
|
|
39
|
+
const name = packageName(spec);
|
|
40
|
+
if (!name)
|
|
41
|
+
continue;
|
|
42
|
+
if (!used.has(name))
|
|
43
|
+
used.set(name, new Set());
|
|
44
|
+
used.get(name).add(file);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const findings = [];
|
|
48
|
+
for (const [name, files] of used) {
|
|
49
|
+
if (declared.has(name))
|
|
50
|
+
continue;
|
|
51
|
+
const fileList = [...files].sort();
|
|
52
|
+
findings.push({
|
|
53
|
+
id: "deps.undeclared-import",
|
|
54
|
+
category: "dependencies",
|
|
55
|
+
severity: "warning",
|
|
56
|
+
message: `${name} is imported but not declared in package.json`,
|
|
57
|
+
evidence: [
|
|
58
|
+
{
|
|
59
|
+
kind: "import",
|
|
60
|
+
detail: `Imported in ${fileList.length} file(s)`,
|
|
61
|
+
path: fileList[0],
|
|
62
|
+
},
|
|
63
|
+
...fileList.slice(0, 5).map((path) => ({
|
|
64
|
+
kind: "source",
|
|
65
|
+
path,
|
|
66
|
+
detail: `import of "${name}"`,
|
|
67
|
+
})),
|
|
68
|
+
{
|
|
69
|
+
kind: "manifest",
|
|
70
|
+
path: "package.json",
|
|
71
|
+
detail: `${name} is not in dependencies / devDependencies`,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return findings;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
function matchAll(text, re) {
|
|
80
|
+
const out = [];
|
|
81
|
+
const copy = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
|
|
82
|
+
for (const m of text.matchAll(copy)) {
|
|
83
|
+
if (m[1])
|
|
84
|
+
out.push(m[1]);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
function packageName(spec) {
|
|
89
|
+
if (spec.startsWith(".") ||
|
|
90
|
+
spec.startsWith("/") ||
|
|
91
|
+
spec.startsWith("node:") ||
|
|
92
|
+
spec.startsWith("#")) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (spec.startsWith("@")) {
|
|
96
|
+
const parts = spec.split("/");
|
|
97
|
+
if (parts.length < 2)
|
|
98
|
+
return null;
|
|
99
|
+
return `${parts[0]}/${parts[1]}`;
|
|
100
|
+
}
|
|
101
|
+
const name = spec.split("/")[0] ?? null;
|
|
102
|
+
if (!name || NODE_BUILTINS.has(name))
|
|
103
|
+
return null;
|
|
104
|
+
return name;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=dependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/checks/dependencies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-vars.d.ts","sourceRoot":"","sources":["../../src/checks/env-vars.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA2B,MAAM,aAAa,CAAC;AAQlE,eAAO,MAAM,WAAW,EAAE,KA4DzB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Detects env reads: dot form and bracket form on process env. */
|
|
2
|
+
const ENV_ACCESS = /process\.env(?:\.([A-Z][A-Z0-9_]*)|\[["']([A-Z][A-Z0-9_]*)["']\])/g;
|
|
3
|
+
const CODE_EXT = /\.(?:[cm]?[jt]sx?|mjs|cjs)$/;
|
|
4
|
+
export const envVarCheck = {
|
|
5
|
+
id: "config.env-var-missing",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const used = new Map();
|
|
8
|
+
for (const file of ctx.files) {
|
|
9
|
+
if (!CODE_EXT.test(file))
|
|
10
|
+
continue;
|
|
11
|
+
if (file.includes(".test.") || file.includes(".spec."))
|
|
12
|
+
continue;
|
|
13
|
+
const text = await ctx.readText(file);
|
|
14
|
+
if (!text)
|
|
15
|
+
continue;
|
|
16
|
+
for (const match of text.matchAll(ENV_ACCESS)) {
|
|
17
|
+
const name = match[1] ?? match[2];
|
|
18
|
+
if (!name)
|
|
19
|
+
continue;
|
|
20
|
+
if (!used.has(name))
|
|
21
|
+
used.set(name, new Set());
|
|
22
|
+
used.get(name).add(file);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (used.size === 0)
|
|
26
|
+
return [];
|
|
27
|
+
const example = await ctx.readText(".env.example");
|
|
28
|
+
const documented = parseEnvKeys(example ?? "");
|
|
29
|
+
const findings = [];
|
|
30
|
+
for (const [name, files] of used) {
|
|
31
|
+
if (documented.has(name))
|
|
32
|
+
continue;
|
|
33
|
+
const fileList = [...files].sort();
|
|
34
|
+
findings.push({
|
|
35
|
+
id: "config.env-var-missing",
|
|
36
|
+
category: "configuration",
|
|
37
|
+
severity: "warning",
|
|
38
|
+
message: `${name} is used in code but missing from .env.example`,
|
|
39
|
+
evidence: [
|
|
40
|
+
{
|
|
41
|
+
kind: "env-usage",
|
|
42
|
+
detail: `Referenced in ${fileList.length} file(s)`,
|
|
43
|
+
path: fileList[0],
|
|
44
|
+
},
|
|
45
|
+
...fileList.slice(0, 5).map((path) => ({
|
|
46
|
+
kind: "source",
|
|
47
|
+
path,
|
|
48
|
+
detail: `process.env.${name}`,
|
|
49
|
+
})),
|
|
50
|
+
{
|
|
51
|
+
kind: "missing-doc",
|
|
52
|
+
path: ".env.example",
|
|
53
|
+
detail: example === null
|
|
54
|
+
? ".env.example does not exist"
|
|
55
|
+
: `${name} is not listed in .env.example`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return findings;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
function parseEnvKeys(contents) {
|
|
64
|
+
const keys = new Set();
|
|
65
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
68
|
+
continue;
|
|
69
|
+
const eq = trimmed.indexOf("=");
|
|
70
|
+
const key = (eq === -1 ? trimmed : trimmed.slice(0, eq)).trim();
|
|
71
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
72
|
+
keys.add(key);
|
|
73
|
+
}
|
|
74
|
+
return keys;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=env-vars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../src/checks/env-vars.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAIzC,8CAA8C;AAC9C,eAAO,MAAM,aAAa,EAAE,KAAK,EAAmC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,8CAA8C;AAC9C,MAAM,CAAC,MAAM,aAAa,GAAY,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|