depvitals 0.0.0-stage → 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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -2
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +31 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/cli/parseCliArgs.d.ts +37 -0
  8. package/dist/cli/parseCliArgs.d.ts.map +1 -0
  9. package/dist/cli/parseCliArgs.js +85 -0
  10. package/dist/cli/parseCliArgs.js.map +1 -0
  11. package/dist/cli/runCli.d.ts +44 -0
  12. package/dist/cli/runCli.d.ts.map +1 -0
  13. package/dist/cli/runCli.js +103 -0
  14. package/dist/cli/runCli.js.map +1 -0
  15. package/dist/core/analyzeProject.d.ts +92 -0
  16. package/dist/core/analyzeProject.d.ts.map +1 -0
  17. package/dist/core/analyzeProject.js +175 -0
  18. package/dist/core/analyzeProject.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +9 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/reporters/terminalReporter.d.ts +49 -0
  24. package/dist/reporters/terminalReporter.d.ts.map +1 -0
  25. package/dist/reporters/terminalReporter.js +98 -0
  26. package/dist/reporters/terminalReporter.js.map +1 -0
  27. package/dist/scanner/moduleSpecifiers.d.ts +60 -0
  28. package/dist/scanner/moduleSpecifiers.d.ts.map +1 -0
  29. package/dist/scanner/moduleSpecifiers.js +824 -0
  30. package/dist/scanner/moduleSpecifiers.js.map +1 -0
  31. package/dist/scanner/packageSpecifier.d.ts +45 -0
  32. package/dist/scanner/packageSpecifier.d.ts.map +1 -0
  33. package/dist/scanner/packageSpecifier.js +98 -0
  34. package/dist/scanner/packageSpecifier.js.map +1 -0
  35. package/dist/scanner/projectManifest.d.ts +72 -0
  36. package/dist/scanner/projectManifest.d.ts.map +1 -0
  37. package/dist/scanner/projectManifest.js +176 -0
  38. package/dist/scanner/projectManifest.js.map +1 -0
  39. package/dist/scanner/scriptUsage.d.ts +50 -0
  40. package/dist/scanner/scriptUsage.d.ts.map +1 -0
  41. package/dist/scanner/scriptUsage.js +461 -0
  42. package/dist/scanner/scriptUsage.js.map +1 -0
  43. package/dist/scanner/sourceFiles.d.ts +52 -0
  44. package/dist/scanner/sourceFiles.d.ts.map +1 -0
  45. package/dist/scanner/sourceFiles.js +190 -0
  46. package/dist/scanner/sourceFiles.js.map +1 -0
  47. package/package.json +68 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matias Ricardi
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 CHANGED
@@ -1,3 +1,87 @@
1
- # Temporary Holding Version
1
+ # DepVitals
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > A simple, lightweight dependency health checker for Node.js projects.
4
+
5
+ DepVitals scans a Node.js project to identify declared dependencies that appear unused and imported packages that are not declared in `package.json`.
6
+
7
+ Inspired by [depcheck](https://github.com/depcheck/depcheck), DepVitals stays deliberately small. It performs lightweight static analysis locally, without making network requests, modifying the analyzed project, or executing its code.
8
+
9
+ ## Usage
10
+
11
+ ```bash
12
+ npx depvitals
13
+ npx depvitals ./path/to/project
14
+ npx depvitals --help
15
+ npx depvitals --version
16
+ ```
17
+
18
+ Run it from a project directory to scan the current directory, or provide a path to analyze another project.
19
+
20
+ **Requirements:** Node.js `>=22.13.0`.
21
+
22
+ **Version note:** Dependency scanning was introduced in v0.1.0. The earlier v0.0.1 release contains only the initial CLI bootstrap.
23
+
24
+ **Exit status:**
25
+
26
+ ```text
27
+ 0 = successful clean scan, help, or version
28
+ 1 = dependency findings or an operational/CLI error
29
+ ```
30
+
31
+ Scan results, including dependency findings, are printed to `stdout`. Argument errors and operational errors are printed to `stderr`.
32
+
33
+ ## What it detects
34
+
35
+ - **Unused dependencies** — packages declared in `dependencies` for which no supported source file or `package.json` script provides usage evidence.
36
+ - **Unused devDependencies** — the same analysis applied to packages declared in `devDependencies`.
37
+ - **Missing dependencies** — external packages referenced by supported source imports but not declared in the project's `package.json`.
38
+
39
+ DepVitals does not inspect `node_modules`, so **"missing" means "not declared", not "not installed"**. An installed but undeclared package can still be reported as missing, and package installation state is never validated.
40
+
41
+ Similarly, a dependency is reported as unused only when no supported usage evidence is found. Indirect or unsupported usage may still exist.
42
+
43
+ `peerDependencies` and `optionalDependencies` are considered when checking whether imported packages are declared, but are never reported as unused.
44
+
45
+ **Example output:**
46
+
47
+ ```text
48
+ DepVitals
49
+
50
+ Unused dependencies (2)
51
+ dotenv
52
+ lodash
53
+
54
+ Unused devDependencies (1)
55
+ eslint
56
+
57
+ Missing dependencies (1)
58
+ zod
59
+ ```
60
+
61
+ ## Supported in v0.1.0
62
+
63
+ - **JavaScript and TypeScript:** `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`, including `.d.ts`-style declaration files.
64
+ - **Static ESM imports and exports:** regular imports, side-effect imports, `import type`, and `export ... from`.
65
+ - **Literal dynamic imports:** expressions such as `import("express")`.
66
+ - **Literal CommonJS requires:** expressions such as `require("express")`.
67
+ - **Basic `package.json` script awareness:** a declared dependency invoked by a supported script command counts as used, for example `eslint` in `"lint": "eslint ."`. Script usage can prevent unused findings but does not generate missing-dependency findings.
68
+ - **Node.js builtins:** built-in modules, with or without the `node:` prefix, are excluded from missing-dependency findings.
69
+ - **Scoped packages and subpaths:** package specifiers are normalized to their package root. For example, `@scope/ui/button` counts as `@scope/ui`, and `lodash/chunk` counts as `lodash`.
70
+ - **Ignored directories:** generated and vendor trees are skipped, including `node_modules`, `dist`, `build`, `coverage`, `out`, `.next`, `.nuxt`, `.cache`, `.turbo`, and `.git`.
71
+ - **Deterministic output:** findings are grouped into sections in a fixed order, with package names sorted alphabetically within each section.
72
+
73
+ Detection uses a lightweight static lexical scanner rather than a full JavaScript/TypeScript parser. Comments, ordinary strings, template text, and regular expressions that merely resemble imports are not counted as dependency usage.
74
+
75
+ ## Current limitations
76
+
77
+ - **Single-project analysis:** only one `package.json` is analyzed. Workspaces, monorepos, and parent-manifest lookup are not supported.
78
+ - **No configuration-aware resolution:** framework-specific configurations, plugins, TypeScript `paths`, and bundler aliases are not resolved.
79
+ - **Static specifiers only:** non-literal dynamic `import()`, template-literal specifiers, `require.resolve()`, and property calls such as `obj.require()` are not recognized as usage evidence.
80
+ - **Limited script detection:** script usage relies on supported command patterns and exact package-name matching. Executable names that differ from their package names are not automatically mapped; for example, `tsc` does not mark `typescript` as used.
81
+ - **Fixed directory exclusions:** source discovery uses the built-in ignored-directory list above. `.gitignore` and custom ignore rules are not interpreted.
82
+ - **Conservative error handling:** unreadable source files or source content the lexical scanner cannot safely finish scanning, such as unterminated strings, comments, templates, or regular expressions, abort the scan rather than producing partial results.
83
+ - **No remediation features yet:** automatic fixes, custom configuration files, and per-dependency explanations are not available.
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { runCli } from './runCli.js';
4
+ /**
5
+ * Thin executable adapter for the `v0.1.0` CLI.
6
+ *
7
+ * Its only responsibilities are:
8
+ * 1. resolve the package version from the single source of truth (`package.json`);
9
+ * 2. collect `process.argv`, `process.cwd()` and the version;
10
+ * 3. await the pure orchestration in `runCli`;
11
+ * 4. render the returned stdout/stderr;
12
+ * 5. assign `process.exitCode` (never call `process.exit()`).
13
+ *
14
+ * All argument handling, scanning orchestration and error mapping live in `runCli`, so this file
15
+ * only wires process I/O to that orchestration. The relative path works both from the TypeScript
16
+ * source (`src/cli/index.ts`) and from the built output (`dist/cli/index.js`), because both resolve
17
+ * to the repository root `package.json`.
18
+ */
19
+ const version = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
20
+ const result = await runCli(process.argv.slice(2), {
21
+ version: version.version,
22
+ cwd: process.cwd(),
23
+ });
24
+ if (result.stdout.length > 0) {
25
+ process.stdout.write(result.stdout);
26
+ }
27
+ if (result.stderr.length > 0) {
28
+ process.stderr.write(result.stderr);
29
+ }
30
+ process.exitCode = result.exitCode;
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACxB,YAAY,CAAC,IAAI,GAAG,CAAC,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC9C,CAAC;AAEzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACjD,OAAO,EAAE,OAAO,CAAC,OAAO;IACxB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;CACnB,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Pure, deterministic argument parsing for the `v0.1.0` CLI.
3
+ *
4
+ * This module only validates the argument shape and resolves the selected project path. It never
5
+ * touches the filesystem, never calls the analyzer, and never accesses `process`, so the parsing
6
+ * rules are fully unit-testable without any I/O.
7
+ *
8
+ * The supported shapes are intentionally small:
9
+ * - no arguments -> scan the injected `cwd`
10
+ * - exactly one positional -> scan the resolved project directory
11
+ * - exactly one meta flag -> help or version (exit 0, no scan)
12
+ * - anything else -> a deterministic error message
13
+ *
14
+ * `--help`/`-h`/`--version`/`-v` must stay standalone: combining them with any other token, or
15
+ * combining them with each other, is rejected rather than given precedence.
16
+ */
17
+ /** The command selected after parsing the arguments. */
18
+ export type ParsedCommand = {
19
+ kind: 'help';
20
+ } | {
21
+ kind: 'version';
22
+ } | {
23
+ kind: 'scan';
24
+ projectPath: string;
25
+ } | {
26
+ kind: 'error';
27
+ message: string;
28
+ };
29
+ /**
30
+ * Parse the raw arguments (already stripped of `node` and the script path) into a single command.
31
+ *
32
+ * @param args - The raw arguments after `node` and the script path.
33
+ * @param cwd - The current working directory used as the base for relative project paths and as
34
+ * the default project when no argument is supplied.
35
+ */
36
+ export declare function parseCliArgs(args: readonly string[], cwd: string): ParsedCommand;
37
+ //# sourceMappingURL=parseCliArgs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseCliArgs.d.ts","sourceRoot":"","sources":["../../src/cli/parseCliArgs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,wDAAwD;AACxD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAiBvC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CA8ChF"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Pure, deterministic argument parsing for the `v0.1.0` CLI.
3
+ *
4
+ * This module only validates the argument shape and resolves the selected project path. It never
5
+ * touches the filesystem, never calls the analyzer, and never accesses `process`, so the parsing
6
+ * rules are fully unit-testable without any I/O.
7
+ *
8
+ * The supported shapes are intentionally small:
9
+ * - no arguments -> scan the injected `cwd`
10
+ * - exactly one positional -> scan the resolved project directory
11
+ * - exactly one meta flag -> help or version (exit 0, no scan)
12
+ * - anything else -> a deterministic error message
13
+ *
14
+ * `--help`/`-h`/`--version`/`-v` must stay standalone: combining them with any other token, or
15
+ * combining them with each other, is rejected rather than given precedence.
16
+ */
17
+ import { resolve } from 'node:path';
18
+ /** Help/version flags that must be used alone. */
19
+ const HELP_FLAGS = new Set(['--help', '-h']);
20
+ /** Version flags that must be used alone. */
21
+ const VERSION_FLAGS = new Set(['--version', '-v']);
22
+ /** Hint appended to every argument error, so a failing run always points back to `--help`. */
23
+ const HINT = "Run 'depvitals --help' for available commands.";
24
+ /** Error returned when an empty string is passed as the project path. */
25
+ const INVALID_EMPTY_PROJECT_PATH = 'Invalid project path: project path must not be empty.\n' + HINT;
26
+ /** Error returned when more than one positional path is supplied. */
27
+ const TOO_MANY_ARGUMENTS = 'Too many arguments: expected at most one project path.\n' + HINT;
28
+ /**
29
+ * Parse the raw arguments (already stripped of `node` and the script path) into a single command.
30
+ *
31
+ * @param args - The raw arguments after `node` and the script path.
32
+ * @param cwd - The current working directory used as the base for relative project paths and as
33
+ * the default project when no argument is supplied.
34
+ */
35
+ export function parseCliArgs(args, cwd) {
36
+ if (args.length === 0) {
37
+ return { kind: 'scan', projectPath: cwd };
38
+ }
39
+ if (args.length === 1) {
40
+ const arg = args[0];
41
+ // An empty string (or the unreachable undefined) is an explicit, invalid positional: it must
42
+ // not silently become `cwd`.
43
+ if (arg === undefined || arg === '') {
44
+ return { kind: 'error', message: INVALID_EMPTY_PROJECT_PATH };
45
+ }
46
+ if (HELP_FLAGS.has(arg)) {
47
+ return { kind: 'help' };
48
+ }
49
+ if (VERSION_FLAGS.has(arg)) {
50
+ return { kind: 'version' };
51
+ }
52
+ // Anything else that is not an option is treated as a project path.
53
+ if (!arg.startsWith('-')) {
54
+ return { kind: 'scan', projectPath: resolve(cwd, arg) };
55
+ }
56
+ return { kind: 'error', message: unknownOptionMessage(arg) };
57
+ }
58
+ // Two or more tokens: help/version must remain standalone, so any meta flag combined with
59
+ // additional tokens (including other meta flags) is rejected as used alone.
60
+ const metaFlag = args.find((arg) => HELP_FLAGS.has(arg) || VERSION_FLAGS.has(arg));
61
+ if (metaFlag !== undefined) {
62
+ return { kind: 'error', message: metaFlagMustBeUsedAlone(metaFlag) };
63
+ }
64
+ // No meta flag: reject unknown options before falling back to the positional rule, so an unknown
65
+ // flag is reported even when it appears alongside a positional path.
66
+ const unknownOption = args.find((arg) => arg.startsWith('-'));
67
+ if (unknownOption !== undefined) {
68
+ return { kind: 'error', message: unknownOptionMessage(unknownOption) };
69
+ }
70
+ // Remaining tokens are all positionals, which is more than one.
71
+ return { kind: 'error', message: TOO_MANY_ARGUMENTS };
72
+ }
73
+ /**
74
+ * The error message for an unrecognized option.
75
+ */
76
+ function unknownOptionMessage(option) {
77
+ return `Unknown option: ${option}\n${HINT}`;
78
+ }
79
+ /**
80
+ * The error message when a meta flag is combined with additional tokens.
81
+ */
82
+ function metaFlagMustBeUsedAlone(flag) {
83
+ return `Option ${flag} must be used alone.\n${HINT}`;
84
+ }
85
+ //# sourceMappingURL=parseCliArgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseCliArgs.js","sourceRoot":"","sources":["../../src/cli/parseCliArgs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,kDAAkD;AAClD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;AAE7C,6CAA6C;AAC7C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC;AAEnD,8FAA8F;AAC9F,MAAM,IAAI,GAAG,gDAAgD,CAAC;AAE9D,yEAAyE;AACzE,MAAM,0BAA0B,GAAG,yDAAyD,GAAG,IAAI,CAAC;AAEpG,qEAAqE;AACrE,MAAM,kBAAkB,GAAG,0DAA0D,GAAG,IAAI,CAAC;AAE7F;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAuB,EAAE,GAAW;IAC/D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpB,6FAA6F;QAC7F,6BAA6B;QAC7B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;QAChE,CAAC;QAED,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;QAED,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7B,CAAC;QAED,oEAAoE;QACpE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QAC1D,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/D,CAAC;IAED,0FAA0F;IAC1F,4EAA4E;IAC5E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,uBAAuB,CAAC,QAAQ,CAAC,EAAE,CAAC;IACvE,CAAC;IAED,iGAAiG;IACjG,qEAAqE;IACrE,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;IACzE,CAAC;IAED,gEAAgE;IAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,MAAc;IAC1C,OAAO,mBAAmB,MAAM,KAAK,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,SAAS,uBAAuB,CAAC,IAAY;IAC3C,OAAO,UAAU,IAAI,yBAAyB,IAAI,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Asynchronous CLI orchestration for `v0.1.0`.
3
+ *
4
+ * `runCli` turns parsed arguments into a plain {@link CliResult}. It performs no process I/O of its
5
+ * own: it never writes to stdout/stderr and never touches `process`. The thin adapter in
6
+ * `index.ts` is the only code that renders the result.
7
+ *
8
+ * The responsibilities here are:
9
+ * - delegate argument validation to the pure {@link parseCliArgs};
10
+ * - run the static dependency scan with `analyzeProject`;
11
+ * - render the report with the pure terminal reporter;
12
+ * - decide the scan exit code from `hasDependencyIssues`, not from the report string;
13
+ * - convert operational failures into a concise CLI error result.
14
+ *
15
+ * It deliberately does not implement any scanning or dependency-analysis logic: that stays in
16
+ * `src/core`, and the reporter stays in `src/reporters`.
17
+ */
18
+ /**
19
+ * The rendered CLI result: an exit code and the text to write to each stream. Exactly one of
20
+ * stdout/stderr is non-empty for every command, so a successful scan never mixes a report with an
21
+ * error and a failure never prints a misleading clean report.
22
+ */
23
+ export type CliResult = {
24
+ exitCode: number;
25
+ stdout: string;
26
+ stderr: string;
27
+ };
28
+ /**
29
+ * Options supplied by the process adapter. Injecting `cwd` (rather than reading `process.cwd()`)
30
+ * keeps the orchestration pure and fully testable without mutating the working directory.
31
+ */
32
+ export type CliOptions = {
33
+ version: string;
34
+ cwd: string;
35
+ };
36
+ /**
37
+ * Orchestrate the CLI for the given arguments.
38
+ *
39
+ * @param args - The raw arguments after `node` and the script path.
40
+ * @param options - The package version and the current working directory.
41
+ * @returns The exit code and the stdout/stderr text to render.
42
+ */
43
+ export declare function runCli(args: readonly string[], options: CliOptions): Promise<CliResult>;
44
+ //# sourceMappingURL=runCli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runCli.d.ts","sourceRoot":"","sources":["../../src/cli/runCli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAkBF;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAa7F"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Asynchronous CLI orchestration for `v0.1.0`.
3
+ *
4
+ * `runCli` turns parsed arguments into a plain {@link CliResult}. It performs no process I/O of its
5
+ * own: it never writes to stdout/stderr and never touches `process`. The thin adapter in
6
+ * `index.ts` is the only code that renders the result.
7
+ *
8
+ * The responsibilities here are:
9
+ * - delegate argument validation to the pure {@link parseCliArgs};
10
+ * - run the static dependency scan with `analyzeProject`;
11
+ * - render the report with the pure terminal reporter;
12
+ * - decide the scan exit code from `hasDependencyIssues`, not from the report string;
13
+ * - convert operational failures into a concise CLI error result.
14
+ *
15
+ * It deliberately does not implement any scanning or dependency-analysis logic: that stays in
16
+ * `src/core`, and the reporter stays in `src/reporters`.
17
+ */
18
+ import { AnalysisError, analyzeProject } from '../core/analyzeProject.js';
19
+ import { formatTerminalReport, hasDependencyIssues } from '../reporters/terminalReporter.js';
20
+ import { ProjectManifestError } from '../scanner/projectManifest.js';
21
+ import { SourceFilesError } from '../scanner/sourceFiles.js';
22
+ import { parseCliArgs } from './parseCliArgs.js';
23
+ const HELP_MESSAGE = [
24
+ 'DepVitals',
25
+ '',
26
+ 'A simple, lightweight dependency health checker for Node.js projects.',
27
+ '',
28
+ 'Usage:',
29
+ ' depvitals [project]',
30
+ '',
31
+ 'Arguments:',
32
+ ' project Project directory to analyze (default: current directory)',
33
+ '',
34
+ 'Options:',
35
+ ' -h, --help Show help',
36
+ ' -v, --version Show version',
37
+ ].join('\n');
38
+ /**
39
+ * Orchestrate the CLI for the given arguments.
40
+ *
41
+ * @param args - The raw arguments after `node` and the script path.
42
+ * @param options - The package version and the current working directory.
43
+ * @returns The exit code and the stdout/stderr text to render.
44
+ */
45
+ export async function runCli(args, options) {
46
+ const command = parseCliArgs(args, options.cwd);
47
+ switch (command.kind) {
48
+ case 'help':
49
+ return { exitCode: 0, stdout: `${HELP_MESSAGE}\n`, stderr: '' };
50
+ case 'version':
51
+ return { exitCode: 0, stdout: `${options.version}\n`, stderr: '' };
52
+ case 'error':
53
+ return { exitCode: 1, stdout: '', stderr: `${command.message}\n` };
54
+ case 'scan':
55
+ return await runScan(command.projectPath);
56
+ }
57
+ }
58
+ /**
59
+ * Run the scan for a single project and render its report.
60
+ *
61
+ * Operational failures from the scanner are converted into a concise CLI error result (exit 1,
62
+ * empty stdout). Any other thrown value is surfaced through a generic fallback message so the CLI
63
+ * never leaks internal error objects and never leaves an unhandled rejection.
64
+ */
65
+ async function runScan(projectPath) {
66
+ let result;
67
+ try {
68
+ result = await analyzeProject(projectPath);
69
+ }
70
+ catch (error) {
71
+ if (isOperationalError(error)) {
72
+ return { exitCode: 1, stdout: '', stderr: `${error.message}\n` };
73
+ }
74
+ return {
75
+ exitCode: 1,
76
+ stdout: '',
77
+ stderr: `${unexpectedErrorMessage(projectPath, error)}\n`,
78
+ };
79
+ }
80
+ const hasIssues = hasDependencyIssues(result);
81
+ return { exitCode: hasIssues ? 1 : 0, stdout: formatTerminalReport(result), stderr: '' };
82
+ }
83
+ /**
84
+ * Whether the thrown value is a scanner error the CLI knows how to render. Only the existing typed
85
+ * error classes count; everything else is treated as an unexpected internal failure.
86
+ */
87
+ function isOperationalError(error) {
88
+ return (error instanceof ProjectManifestError ||
89
+ error instanceof SourceFilesError ||
90
+ error instanceof AnalysisError);
91
+ }
92
+ /**
93
+ * The fallback message for an unexpected thrown value. The path is always included so the failure
94
+ * is traceable; the underlying message is only appended when it is a non-empty, useful `Error`
95
+ * message, so unknown throwables never leak serialized internals.
96
+ */
97
+ function unexpectedErrorMessage(projectPath, error) {
98
+ if (error instanceof Error && error.message.trim().length > 0) {
99
+ return `Unexpected error while analyzing project ${projectPath}: ${error.message}`;
100
+ }
101
+ return `Unexpected error while analyzing project ${projectPath}.`;
102
+ }
103
+ //# sourceMappingURL=runCli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runCli.js","sourceRoot":"","sources":["../../src/cli/runCli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAuB,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAsBjD,MAAM,YAAY,GAAG;IACnB,WAAW;IACX,EAAE;IACF,uEAAuE;IACvE,EAAE;IACF,QAAQ;IACR,uBAAuB;IACvB,EAAE;IACF,YAAY;IACZ,8EAA8E;IAC9E,EAAE;IACF,UAAU;IACV,8BAA8B;IAC9B,iCAAiC;CAClC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAuB,EAAE,OAAmB;IACvE,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,YAAY,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAClE,KAAK,SAAS;YACZ,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QACrE,KAAK,OAAO;YACV,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QACrE,KAAK,MAAM;YACT,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,OAAO,CAAC,WAAmB;IACxC,IAAI,MAAsB,CAAC;IAE3B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QACnE,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,CAAC;YACX,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,GAAG,sBAAsB,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI;SAC1D,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE9C,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AAC3F,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CACzB,KAAc;IAEd,OAAO,CACL,KAAK,YAAY,oBAAoB;QACrC,KAAK,YAAY,gBAAgB;QACjC,KAAK,YAAY,aAAa,CAC/B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,WAAmB,EAAE,KAAc;IACjE,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9D,OAAO,4CAA4C,WAAW,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IACrF,CAAC;IAED,OAAO,4CAA4C,WAAW,GAAG,CAAC;AACpE,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Dependency analysis.
3
+ *
4
+ * This module combines the outputs of the manifest loader, source discovery, module-specifier
5
+ * extraction, package-specifier classification and script-usage detection into the first real
6
+ * dependency-analysis engine.
7
+ *
8
+ * It answers exactly three questions about a single Node.js project:
9
+ * - which `dependencies` are never referenced (neither by source files nor by `package.json`
10
+ * scripts)?
11
+ * - which `devDependencies` are never referenced?
12
+ * - which external packages are referenced by source files but are not declared anywhere?
13
+ *
14
+ * It deliberately does not:
15
+ * - decide whether a dependency is misplaced, a misconfigured peer/optional, or a scoped
16
+ * dependency (that belongs to later releases);
17
+ * - resolve specifiers against `node_modules`, `tsconfig` `paths`, `package.json#imports` or
18
+ * bundler aliases (Step 10 only classifies a raw specifier);
19
+ * - render terminal output, JSON, evidence or confidence (that belongs to later steps);
20
+ * - execute or import any analyzed source, run package scripts, or mutate the analyzed project;
21
+ * - consult the network or the npm registry.
22
+ *
23
+ * The pipeline is intentionally sequential and single-pass: each discovered source file is read
24
+ * once and parsed once. No concurrency pool, cache or index is introduced before a measured need
25
+ * appears.
26
+ *
27
+ * Usage/missing formulas (`usedNames` is the union of source-external package roots and script-used
28
+ * package names; `declaredDependencyNames` is the union of every declared dependency section):
29
+ *
30
+ * ```text
31
+ * sourceUsedNames = external package roots extracted from source files
32
+ * scriptUsedNames = findPackageScriptUsage(scripts, dependencies ∪ devDependencies)
33
+ * usedNames = sourceUsedNames ∪ scriptUsedNames
34
+ * unusedDependencies = dependencies − usedNames
35
+ * unusedDevDependencies = devDependencies − usedNames
36
+ * missingDependencies = sourceUsedNames − declaredDependencyNames
37
+ * ```
38
+ *
39
+ * Scripts never generate missing-dependency findings, and peer/optional declarations suppress false
40
+ * missing results through `declaredDependencyNames`.
41
+ */
42
+ /**
43
+ * The structured outcome of a dependency analysis.
44
+ *
45
+ * Every package-name list is sorted alphabetically and contains no duplicates. The result carries
46
+ * only the facts needed by the `v0.1.0` report: the project root and the three unused/missing
47
+ * categories. Confidence, explanations, evidence, severity and JSON schema version are deliberately
48
+ * absent: they are future roadmap concepts.
49
+ */
50
+ export type AnalysisResult = {
51
+ /** Absolute, normalized project root (from the manifest). */
52
+ projectRoot: string;
53
+ /** Unused `dependencies`, sorted alphabetically. */
54
+ unusedDependencies: readonly string[];
55
+ /** Unused `devDependencies`, sorted alphabetically. */
56
+ unusedDevDependencies: readonly string[];
57
+ /** External packages referenced by source but not declared anywhere, sorted alphabetically. */
58
+ missingDependencies: readonly string[];
59
+ };
60
+ /**
61
+ * Why the analysis could not complete.
62
+ *
63
+ * - `unreadable-source`: a discovered source file could not be read from disk.
64
+ * - `invalid-source`: a discovered source file reached a lexical state from which the specifier
65
+ * extractor cannot safely continue (an unterminated string/comment/template/regex, or a malformed
66
+ * string escape).
67
+ */
68
+ export type AnalysisErrorCode = 'unreadable-source' | 'invalid-source';
69
+ /**
70
+ * Operational error for the dependency-analysis engine.
71
+ *
72
+ * The `code` lets callers (the CLI in a later step) render a clean message without matching on error
73
+ * strings, and the preserved `cause` keeps the original filesystem or parsing failure. The affected
74
+ * source-file path is always included in the message so an unreadable or invalid file is never
75
+ * silently skipped.
76
+ */
77
+ export declare class AnalysisError extends Error {
78
+ readonly code: AnalysisErrorCode;
79
+ constructor(message: string, code: AnalysisErrorCode, cause?: unknown);
80
+ }
81
+ /**
82
+ * Analyze the single project rooted at `projectRoot` and return the structured unused/missing result.
83
+ *
84
+ * The supplied root is resolved to an absolute path; `process.cwd()` is never changed and the target
85
+ * project is never written to.
86
+ *
87
+ * @throws {AnalysisError} when a discovered source file cannot be read or cannot be safely analyzed.
88
+ * @throws {ProjectManifestError} when the manifest is missing, unreadable, invalid or malformed.
89
+ * @throws {SourceFilesError} when the root is missing, is not a directory, or cannot be read.
90
+ */
91
+ export declare function analyzeProject(projectRoot: string): Promise<AnalysisResult>;
92
+ //# sourceMappingURL=analyzeProject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzeProject.d.ts","sourceRoot":"","sources":["../../src/core/analyzeProject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAUH;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,uDAAuD;IACvD,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,+FAA+F;IAC/F,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAEvE;;;;;;;GAOG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,CAAC,EAAE,OAAO;CAKtE;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6CjF"}