depvitals 0.1.2 → 0.3.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 +39 -61
- package/dist/cli/entry.d.ts +59 -0
- package/dist/cli/entry.d.ts.map +1 -0
- package/dist/cli/entry.js +95 -0
- package/dist/cli/entry.js.map +1 -0
- package/dist/cli/index.js +14 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/notifier/eligibility.d.ts +65 -0
- package/dist/cli/notifier/eligibility.d.ts.map +1 -0
- package/dist/cli/notifier/eligibility.js +123 -0
- package/dist/cli/notifier/eligibility.js.map +1 -0
- package/dist/cli/notifier/registryLookup.d.ts +111 -0
- package/dist/cli/notifier/registryLookup.d.ts.map +1 -0
- package/dist/cli/notifier/registryLookup.js +211 -0
- package/dist/cli/notifier/registryLookup.js.map +1 -0
- package/dist/cli/notifier/updateCache.d.ts +101 -0
- package/dist/cli/notifier/updateCache.d.ts.map +1 -0
- package/dist/cli/notifier/updateCache.js +352 -0
- package/dist/cli/notifier/updateCache.js.map +1 -0
- package/dist/cli/notifier/updateCheck.d.ts +88 -0
- package/dist/cli/notifier/updateCheck.d.ts.map +1 -0
- package/dist/cli/notifier/updateCheck.js +166 -0
- package/dist/cli/notifier/updateCheck.js.map +1 -0
- package/dist/cli/notifier/versionCompare.d.ts +48 -0
- package/dist/cli/notifier/versionCompare.d.ts.map +1 -0
- package/dist/cli/notifier/versionCompare.js +89 -0
- package/dist/cli/notifier/versionCompare.js.map +1 -0
- package/dist/cli/runCli.d.ts +2 -0
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +36 -4
- package/dist/cli/runCli.js.map +1 -1
- package/dist/config/ignoreDependencies.d.ts +7 -0
- package/dist/config/ignoreDependencies.d.ts.map +1 -0
- package/dist/config/ignoreDependencies.js +18 -0
- package/dist/config/ignoreDependencies.js.map +1 -0
- package/dist/config/ignorePaths.d.ts +13 -0
- package/dist/config/ignorePaths.d.ts.map +1 -0
- package/dist/config/ignorePaths.js +47 -0
- package/dist/config/ignorePaths.js.map +1 -0
- package/dist/config/projectConfigError.d.ts +7 -0
- package/dist/config/projectConfigError.d.ts.map +1 -0
- package/dist/config/projectConfigError.js +10 -0
- package/dist/config/projectConfigError.js.map +1 -0
- package/dist/config/readProjectConfig.d.ts +23 -0
- package/dist/config/readProjectConfig.d.ts.map +1 -0
- package/dist/config/readProjectConfig.js +106 -0
- package/dist/config/readProjectConfig.js.map +1 -0
- package/dist/core/analyzeProject.d.ts +48 -6
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +123 -16
- package/dist/core/analyzeProject.js.map +1 -1
- package/dist/detectors/eslintManifestConfig.d.ts +111 -0
- package/dist/detectors/eslintManifestConfig.d.ts.map +1 -0
- package/dist/detectors/eslintManifestConfig.js +351 -0
- package/dist/detectors/eslintManifestConfig.js.map +1 -0
- package/dist/detectors/eslintRcJsonConfig.d.ts +85 -0
- package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -0
- package/dist/detectors/eslintRcJsonConfig.js +189 -0
- package/dist/detectors/eslintRcJsonConfig.js.map +1 -0
- package/dist/detectors/jestManifestConfig.d.ts +76 -0
- package/dist/detectors/jestManifestConfig.d.ts.map +1 -0
- package/dist/detectors/jestManifestConfig.js +294 -0
- package/dist/detectors/jestManifestConfig.js.map +1 -0
- package/dist/reporters/terminalReporter.js +27 -1
- package/dist/reporters/terminalReporter.js.map +1 -1
- package/dist/scanner/moduleSpecifiers.d.ts +9 -1
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
- package/dist/scanner/moduleSpecifiers.js +3317 -205
- package/dist/scanner/moduleSpecifiers.js.map +1 -1
- package/dist/scanner/packageSpecifier.d.ts +26 -2
- package/dist/scanner/packageSpecifier.d.ts.map +1 -1
- package/dist/scanner/packageSpecifier.js +70 -2
- package/dist/scanner/packageSpecifier.js.map +1 -1
- package/dist/scanner/projectManifest.d.ts +31 -1
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +83 -4
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +2 -1
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +216 -39
- package/dist/scanner/scriptUsage.js.map +1 -1
- package/dist/scanner/sourceFiles.d.ts +16 -2
- package/dist/scanner/sourceFiles.d.ts.map +1 -1
- package/dist/scanner/sourceFiles.js +12 -6
- package/dist/scanner/sourceFiles.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,88 +1,66 @@
|
|
|
1
1
|
# DepVitals
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/depvitals)
|
|
4
|
+
[](https://github.com/MatiasRicardi/depvitals/blob/main/package.json)
|
|
5
|
+
[](https://github.com/MatiasRicardi/depvitals/actions/workflows/ci.yml)
|
|
6
|
+
[](https://codecov.io/gh/MatiasRicardi/depvitals)
|
|
7
|
+
[](https://github.com/MatiasRicardi/depvitals/actions/workflows/codeql.yml)
|
|
8
|
+
[](https://github.com/MatiasRicardi/depvitals/blob/main/LICENSE)
|
|
4
9
|
|
|
5
|
-
DepVitals
|
|
10
|
+
DepVitals is a lightweight, [Depcheck](https://github.com/depcheck/depcheck)-inspired **npm dependency analyzer for Node.js, JavaScript and TypeScript projects**. It finds **unused dependencies, unused devDependencies and missing (undeclared) dependencies** using a custom static scanner with **zero runtime dependencies**.
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
Designed for fast, zero-config dependency checks during development, maintenance and CI, its CLI can also be used in scripts and AI coding-agent workflows. It analyzes one project root at a time; review findings before changing `package.json`.
|
|
8
13
|
|
|
9
|
-
##
|
|
14
|
+
## Quick start
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
Requires **Node.js 22.13.0 or newer**. Scan the current directory containing `package.json`, or pass a project path:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
12
19
|
npx depvitals
|
|
13
20
|
npx depvitals ./path/to/project
|
|
14
|
-
npx depvitals --help
|
|
15
|
-
npx depvitals --version
|
|
16
21
|
```
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**Requirements:** Node.js `>=22.13.0`.
|
|
21
|
-
|
|
22
|
-
**Version note:** Dependency scanning was introduced in v0.1.0, and v0.1.2 expanded the script executable allowlist. The earlier v0.0.1 release contains only the initial CLI bootstrap.
|
|
23
|
-
|
|
24
|
-
**Exit status:**
|
|
23
|
+
### Install globally
|
|
25
24
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
```sh
|
|
26
|
+
npm i -g depvitals
|
|
27
|
+
depvitals
|
|
28
|
+
depvitals ./path/to/project
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Use `depvitals --help` or `depvitals --version` for standalone help/version; the same flags work with `npx depvitals`.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
These commands use the npm release. Documentation on `main` can describe unreleased changes; see the [changelog](https://github.com/MatiasRicardi/depvitals/blob/main/CHANGELOG.md). To try the development checkout, [build and run the local CLI](https://github.com/MatiasRicardi/depvitals/blob/main/CONTRIBUTING.md#local-setup).
|
|
34
34
|
|
|
35
|
-
|
|
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`.
|
|
35
|
+
## Results
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
| Finding | Meaning |
|
|
38
|
+
| ---------------------- | -------------------------------------------------------------------------------------- |
|
|
39
|
+
| Unused dependencies | Declared in `dependencies`, without supported usage evidence. |
|
|
40
|
+
| Unused devDependencies | The same check for `devDependencies`. |
|
|
41
|
+
| Missing dependencies | Referenced by supported source imports/exports/requires, but undeclared in all groups. |
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
**Missing means undeclared, not uninstalled. Unused is not proof of safe removal.** Review findings before editing your manifest. Peer and optional dependencies suppress missing findings and are never reported unused.
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
Reports use stdout; operational errors use stderr without a partial report. Exit **0** means clean/help/version; **1** means findings/errors. An optional update notice can use stderr without changing the status.
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
## Optional configuration
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
DepVitals
|
|
49
|
+
Requires DepVitals **0.3.0 or newer**. Create `depvitals.config.json` at the scanned project root:
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
eslint
|
|
56
|
-
|
|
57
|
-
Missing dependencies (1)
|
|
58
|
-
zod
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"ignorePaths": ["examples", "src/generated"],
|
|
54
|
+
"ignoreDependencies": ["some-adapter"]
|
|
55
|
+
}
|
|
59
56
|
```
|
|
60
57
|
|
|
61
|
-
|
|
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
|
-
- **Known executable names:** since v0.1.2, a static allowlist of 60 known executable commands resolves to 46 declared packages, so a real CLI executable credits the package that declares it, for example `tsserver` → `typescript`, `ng` → `@angular/cli`, `vue-cli-service` → `@vue/cli-service`, `depcruise` → `dependency-cruiser`, `sls` → `serverless`, and `gulp` → `gulp-cli`. Each alias was verified against published `bin` metadata beforehand: nothing is auto-discovered from `bin` fields or from `node_modules`. 32 of them are collision-sensitive, meaning a same-named declared package wins and the mapped provider gets no evidence.
|
|
69
|
-
- **Node.js builtins:** built-in modules, with or without the `node:` prefix, are excluded from missing-dependency findings.
|
|
70
|
-
- **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`.
|
|
71
|
-
- **Ignored directories:** generated and vendor trees are skipped, including `node_modules`, `dist`, `build`, `coverage`, `out`, `.next`, `.nuxt`, `.cache`, `.turbo`, and `.git`.
|
|
72
|
-
- **Deterministic output:** findings are grouped into sections in a fixed order, with package names sorted alphabetically within each section.
|
|
73
|
-
|
|
74
|
-
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.
|
|
58
|
+
`ignorePaths` excludes literal source paths/subtrees; `ignoreDependencies` hides exact package names from findings. No globs. Exclusions narrow what a clean report means; excluding the only source reference can make a dependency unused. See the [configuration reference](https://github.com/MatiasRicardi/depvitals/blob/main/docs/reference.md#optional-project-configuration) for validation and precedence.
|
|
75
59
|
|
|
76
|
-
##
|
|
60
|
+
## Scope and privacy
|
|
77
61
|
|
|
78
|
-
|
|
79
|
-
- **No configuration-aware resolution:** framework-specific configurations, plugins, TypeScript `paths`, and bundler aliases are not resolved.
|
|
80
|
-
- **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.
|
|
81
|
-
- **Limited script detection:** script usage relies on supported command patterns, and only the executables in the static allowlist above are translated to their declaring package, and only when the executable appears in a supported direct command position. Every other executable name keeps exact-name matching, mappings are never applied to runner arguments such as `npx tsc` or `pnpm exec tsc`, and ambiguous provider families such as `tailwindcss`, `playwright`, and `webpack` are deliberately unmapped, so only an exactly declared same-name package is credited.
|
|
82
|
-
- **Fixed directory exclusions:** source discovery uses the built-in ignored-directory list above. `.gitignore` and custom ignore rules are not interpreted.
|
|
83
|
-
- **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.
|
|
84
|
-
- **No remediation features yet:** automatic fixes, custom configuration files, and per-dependency explanations are not available.
|
|
62
|
+
Scans cover JS/TS, JSX/TSX and declaration files, literal ESM/CommonJS references, supported script commands and bounded ESLint/Jest hints. Only source evidence creates missing findings. There is no workspace aggregation, alias/tsconfig resolution, blanket `@types` inference, JSON output or automatic fix; see [supported patterns and limits](https://github.com/MatiasRicardi/depvitals/blob/main/docs/reference.md).
|
|
85
63
|
|
|
86
|
-
|
|
64
|
+
Core analysis is offline and never executes or modifies the project or inspects `node_modules`. After completed interactive scans, an optional update check requests public metadata for **DepVitals itself**, caches it outside the project and uploads no project data. CI, non-TTY, help/version and failed scans skip it. Set `DEPVITALS_NO_UPDATE_CHECK=1` to disable it; see [privacy and shell examples](https://github.com/MatiasRicardi/depvitals/blob/main/docs/reference.md#update-notice-and-privacy).
|
|
87
65
|
|
|
88
|
-
MIT
|
|
66
|
+
[Technical reference](https://github.com/MatiasRicardi/depvitals/blob/main/docs/reference.md) · [Contributing](https://github.com/MatiasRicardi/depvitals/blob/main/CONTRIBUTING.md) · [Changelog](https://github.com/MatiasRicardi/depvitals/blob/main/CHANGELOG.md) · [MIT license](https://github.com/MatiasRicardi/depvitals/blob/main/LICENSE)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-facing CLI orchestration (Step 67).
|
|
3
|
+
*
|
|
4
|
+
* `runCliEntry` is the layer between the pure {@link runCli} orchestration and the real process
|
|
5
|
+
* streams. It owns exactly three things:
|
|
6
|
+
*
|
|
7
|
+
* 1. render the deterministic result: stdout first, then stderr, byte for byte as `runCli` produced
|
|
8
|
+
* them - the report and the operational diagnostics are untouched by this module;
|
|
9
|
+
* 2. run the *optional* update check **after** that output, and append its notice to stderr only;
|
|
10
|
+
* 3. hand back the original exit code.
|
|
11
|
+
*
|
|
12
|
+
* The ordering above is the contract: a scan always finishes printing before the notifier may write,
|
|
13
|
+
* and the notifier can never rewrite, delay or contradict that output. It also cannot change the exit
|
|
14
|
+
* status, because the returned code comes from `runCli` alone and the notice is only appended text.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here reads `process`: the streams, the environment, the working directory and the notifier
|
|
17
|
+
* are all injected, so the sequencing is testable without spawning anything and without network
|
|
18
|
+
* access. The thin executable adapter in `index.ts` supplies the real process objects and assigns
|
|
19
|
+
* `process.exitCode` **after** this function resolves, which is what keeps the optional asynchronous
|
|
20
|
+
* work from being truncated (and `process.exit()` is never called at all).
|
|
21
|
+
*
|
|
22
|
+
* The command kind handed to the notifier is deliberately only a literal string: no project path,
|
|
23
|
+
* manifest content or dependency name ever crosses into notifier code.
|
|
24
|
+
*/
|
|
25
|
+
import { type UpdateNoticeCollector, type UpdateNoticeRequest } from './notifier/updateCheck.js';
|
|
26
|
+
import { type CliOptions } from './runCli.js';
|
|
27
|
+
/**
|
|
28
|
+
* The minimal stream view this layer needs: a writable text sink plus its interactivity flag.
|
|
29
|
+
* `process.stdout` and `process.stderr` satisfy it, and a test double satisfies it too, which keeps
|
|
30
|
+
* the TTY gate injectable without ever faking a real terminal.
|
|
31
|
+
*/
|
|
32
|
+
export type StreamWriter = {
|
|
33
|
+
readonly isTTY?: boolean | undefined;
|
|
34
|
+
write(chunk: string): unknown;
|
|
35
|
+
};
|
|
36
|
+
/** Everything the entry layer needs from the process, injected. */
|
|
37
|
+
export type CliEntryOptions = CliOptions & {
|
|
38
|
+
/** The environment map used for the notifier eligibility gates, e.g. `process.env`. */
|
|
39
|
+
readonly env: UpdateNoticeRequest['env'];
|
|
40
|
+
/** The standard output stream. */
|
|
41
|
+
readonly stdout: StreamWriter;
|
|
42
|
+
/** The standard error stream. */
|
|
43
|
+
readonly stderr: StreamWriter;
|
|
44
|
+
/**
|
|
45
|
+
* Notifier seam. Defaults to the production {@link collectUpdateNotice}; tests inject a stub so
|
|
46
|
+
* they stay deterministic and offline while still asserting the real sequencing and channels.
|
|
47
|
+
*/
|
|
48
|
+
readonly updateNotice?: UpdateNoticeCollector;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Render one CLI invocation and return the exit code the caller must assign to `process.exitCode`.
|
|
52
|
+
*
|
|
53
|
+
* @param args - The raw arguments after `node` and the script path.
|
|
54
|
+
* @param options - Version, working directory, environment, the two output streams and the
|
|
55
|
+
* optional notifier seam.
|
|
56
|
+
* @returns The unchanged exit code of the scan: 0 for clean/help/version, 1 for findings/errors.
|
|
57
|
+
*/
|
|
58
|
+
export declare function runCliEntry(args: readonly string[], options: CliEntryOptions): Promise<number>;
|
|
59
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/cli/entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,KAAK,UAAU,EAA0B,MAAM,aAAa,CAAC;AAEtE;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG;IACzC,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACzC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,qBAAqB,CAAC;CAC/C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,MAAM,CAAC,CAmBjB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-facing CLI orchestration (Step 67).
|
|
3
|
+
*
|
|
4
|
+
* `runCliEntry` is the layer between the pure {@link runCli} orchestration and the real process
|
|
5
|
+
* streams. It owns exactly three things:
|
|
6
|
+
*
|
|
7
|
+
* 1. render the deterministic result: stdout first, then stderr, byte for byte as `runCli` produced
|
|
8
|
+
* them - the report and the operational diagnostics are untouched by this module;
|
|
9
|
+
* 2. run the *optional* update check **after** that output, and append its notice to stderr only;
|
|
10
|
+
* 3. hand back the original exit code.
|
|
11
|
+
*
|
|
12
|
+
* The ordering above is the contract: a scan always finishes printing before the notifier may write,
|
|
13
|
+
* and the notifier can never rewrite, delay or contradict that output. It also cannot change the exit
|
|
14
|
+
* status, because the returned code comes from `runCli` alone and the notice is only appended text.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here reads `process`: the streams, the environment, the working directory and the notifier
|
|
17
|
+
* are all injected, so the sequencing is testable without spawning anything and without network
|
|
18
|
+
* access. The thin executable adapter in `index.ts` supplies the real process objects and assigns
|
|
19
|
+
* `process.exitCode` **after** this function resolves, which is what keeps the optional asynchronous
|
|
20
|
+
* work from being truncated (and `process.exit()` is never called at all).
|
|
21
|
+
*
|
|
22
|
+
* The command kind handed to the notifier is deliberately only a literal string: no project path,
|
|
23
|
+
* manifest content or dependency name ever crosses into notifier code.
|
|
24
|
+
*/
|
|
25
|
+
import { collectUpdateNotice, } from './notifier/updateCheck.js';
|
|
26
|
+
import { parseCliArgs } from './parseCliArgs.js';
|
|
27
|
+
import { runCli } from './runCli.js';
|
|
28
|
+
/**
|
|
29
|
+
* Render one CLI invocation and return the exit code the caller must assign to `process.exitCode`.
|
|
30
|
+
*
|
|
31
|
+
* @param args - The raw arguments after `node` and the script path.
|
|
32
|
+
* @param options - Version, working directory, environment, the two output streams and the
|
|
33
|
+
* optional notifier seam.
|
|
34
|
+
* @returns The unchanged exit code of the scan: 0 for clean/help/version, 1 for findings/errors.
|
|
35
|
+
*/
|
|
36
|
+
export async function runCliEntry(args, options) {
|
|
37
|
+
const result = await runCli(args, { version: options.version, cwd: options.cwd });
|
|
38
|
+
// The deterministic output goes out before anything optional happens.
|
|
39
|
+
if (result.stdout.length > 0) {
|
|
40
|
+
options.stdout.write(result.stdout);
|
|
41
|
+
}
|
|
42
|
+
if (result.stderr.length > 0) {
|
|
43
|
+
options.stderr.write(result.stderr);
|
|
44
|
+
}
|
|
45
|
+
const notice = await collectNotice(args, result, options);
|
|
46
|
+
if (notice !== undefined && notice.length > 0) {
|
|
47
|
+
options.stderr.write(notice);
|
|
48
|
+
}
|
|
49
|
+
return result.exitCode;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Ask the notifier for its notice, if this invocation has one.
|
|
53
|
+
*
|
|
54
|
+
* The call is wrapped so that a notifier failing in any way - a thrown error, a rejected promise, a
|
|
55
|
+
* malformed return value - behaves exactly like "no notice": the scan's own status and diagnostics
|
|
56
|
+
* stand on their own. An unexpected type is discarded rather than printed.
|
|
57
|
+
*/
|
|
58
|
+
async function collectNotice(args, result, options) {
|
|
59
|
+
try {
|
|
60
|
+
const collect = options.updateNotice ?? collectUpdateNotice;
|
|
61
|
+
const notice = await collect({
|
|
62
|
+
commandKind: commandKindOf(args, options.cwd, result),
|
|
63
|
+
currentVersion: options.version,
|
|
64
|
+
env: options.env,
|
|
65
|
+
stdoutTTY: options.stdout.isTTY === true,
|
|
66
|
+
stderrTTY: options.stderr.isTTY === true,
|
|
67
|
+
});
|
|
68
|
+
return typeof notice === 'string' ? notice : undefined;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// An optional notification never becomes a failure of its own.
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Classify the invocation in the vocabulary the eligibility predicate expects.
|
|
77
|
+
*
|
|
78
|
+
* The argument parser is the single source of truth for `help`, `version` and usage errors, so it is
|
|
79
|
+
* consulted again here (a pure, deterministic function of the same arguments and the same working
|
|
80
|
+
* directory) instead of widening {@link CliResult}, whose three keys are pinned by the contract gate.
|
|
81
|
+
* A parsed `scan` is then split by what the scan produced: {@link runCli} renders a report whenever
|
|
82
|
+
* the scan completed - clean or with findings - and leaves stdout empty for an operational failure,
|
|
83
|
+
* so an empty report means the scan never finished and nothing may be suggested to the user.
|
|
84
|
+
*
|
|
85
|
+
* Only the literal kind crosses the boundary; the parsed project path stays here and is never passed
|
|
86
|
+
* into notifier code.
|
|
87
|
+
*/
|
|
88
|
+
function commandKindOf(args, cwd, result) {
|
|
89
|
+
const command = parseCliArgs(args, cwd);
|
|
90
|
+
if (command.kind !== 'scan') {
|
|
91
|
+
return command.kind;
|
|
92
|
+
}
|
|
93
|
+
return result.stdout.length === 0 ? 'scan-failure' : 'scan';
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/cli/entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,mBAAmB,GAGpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAmC,MAAM,EAAE,MAAM,aAAa,CAAC;AA2BtE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAuB,EACvB,OAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAElF,sEAAsE;IACtE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1D,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,aAAa,CAC1B,IAAuB,EACvB,MAAiB,EACjB,OAAwB;IAExB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAC;QAE5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;YAC3B,WAAW,EAAE,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC;YACrD,cAAc,EAAE,OAAO,CAAC,OAAO;YAC/B,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;YACxC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;SACzC,CAAC,CAAC;QAEH,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;QAC/D,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,aAAa,CACpB,IAAuB,EACvB,GAAW,EACX,MAAiB;IAEjB,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAExC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC,IAAI,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9D,CAAC"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { runCliEntry } from './entry.js';
|
|
4
4
|
/**
|
|
5
|
-
* Thin executable adapter for the
|
|
5
|
+
* Thin executable adapter for the CLI.
|
|
6
6
|
*
|
|
7
7
|
* Its only responsibilities are:
|
|
8
8
|
* 1. resolve the package version from the single source of truth (`package.json`);
|
|
9
|
-
* 2.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* 2. hand `process.argv`, `process.cwd()`, `process.env` and the two output streams to
|
|
10
|
+
* `runCliEntry`, which prints the report, then optionally appends the update notice to stderr;
|
|
11
|
+
* 3. assign `process.exitCode` from the returned status (never call `process.exit()`, so the
|
|
12
|
+
* optional update check is never truncated and can never change the status).
|
|
13
13
|
*
|
|
14
|
-
* All argument handling, scanning orchestration
|
|
15
|
-
* only wires process
|
|
16
|
-
* source (`src/cli/index.ts`) and from the built output
|
|
17
|
-
* to the repository root `package.json`.
|
|
14
|
+
* All argument handling, scanning orchestration, error mapping and the notifier sequencing live in
|
|
15
|
+
* `runCli`/`runCliEntry`, so this file only wires process objects to them. The relative path works
|
|
16
|
+
* both from the TypeScript source (`src/cli/index.ts`) and from the built output
|
|
17
|
+
* (`dist/cli/index.js`), because both resolve to the repository root `package.json`.
|
|
18
18
|
*/
|
|
19
19
|
const version = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
20
|
-
|
|
20
|
+
process.exitCode = await runCliEntry(process.argv.slice(2), {
|
|
21
21
|
version: version.version,
|
|
22
22
|
cwd: process.cwd(),
|
|
23
|
+
env: process.env,
|
|
24
|
+
stdout: process.stdout,
|
|
25
|
+
stderr: process.stderr,
|
|
23
26
|
});
|
|
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
27
|
//# sourceMappingURL=index.js.map
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +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,
|
|
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,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;;;;;;;;;;;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,OAAO,CAAC,QAAQ,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IAC1D,OAAO,EAAE,OAAO,CAAC,OAAO;IACxB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;IAClB,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,MAAM,EAAE,OAAO,CAAC,MAAM;IACtB,MAAM,EAAE,OAAO,CAAC,MAAM;CACvB,CAAC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure eligibility predicate for the optional CLI update check (Step 64).
|
|
3
|
+
*
|
|
4
|
+
* The update-notifier contract allows a *newer DepVitals version* notice to be printed to stderr
|
|
5
|
+
* only after a normal, completed scan in an interactive terminal. Everything before that network
|
|
6
|
+
* convenience is decided here, so the gates are auditable in one place:
|
|
7
|
+
*
|
|
8
|
+
* 1. a completed normal scan invocation (clean **or** with findings) - not help, not version,
|
|
9
|
+
* not an argument error, not a failed scan;
|
|
10
|
+
* 2. both stdout and stderr are interactive TTYs (a pipe or redirection on either stream skips);
|
|
11
|
+
* 3. no reliable CI flag;
|
|
12
|
+
* 4. no explicit `DEPVITALS_NO_UPDATE_CHECK=1` opt-out.
|
|
13
|
+
*
|
|
14
|
+
* An ineligible invocation must trigger **no** registry lookup, **no** cache read and **no**
|
|
15
|
+
* notice, so this module deliberately has no imports: it touches no filesystem, no network, no
|
|
16
|
+
* cache, no clock, no output stream and no ambient global state. Every input is passed in by the
|
|
17
|
+
* caller (an environment map, two booleans and the command kind), which keeps the decision
|
|
18
|
+
* deterministic, offline and unit-testable without any real global state.
|
|
19
|
+
*
|
|
20
|
+
* The decision is safe by default: anything unknown or unrecognized resolves to `false`, and no
|
|
21
|
+
* input can make this function throw.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The environment map to consult, shaped exactly like the ambient environment the CLI entry point
|
|
25
|
+
* already holds. Injecting it (instead of reading globals) is what makes every gate testable with
|
|
26
|
+
* arbitrary, hostile or partially known values.
|
|
27
|
+
*/
|
|
28
|
+
export type NotifierEnv = Readonly<Record<string, string | undefined>>;
|
|
29
|
+
/**
|
|
30
|
+
* What the CLI is about to do, expressed in the same vocabulary as the argument parser plus the
|
|
31
|
+
* one outcome the parser cannot know:
|
|
32
|
+
*
|
|
33
|
+
* - `scan` - a normal scan invocation that completed (clean or with findings; findings alone set
|
|
34
|
+
* exit code 1 and stay eligible);
|
|
35
|
+
* - `help` / `version` - standalone meta options that never scan;
|
|
36
|
+
* - `error` - an argument error from {@link parseCliArgs}, i.e. nothing was scanned;
|
|
37
|
+
* - `scan-failure` - the scan started but failed operationally, so no report was produced.
|
|
38
|
+
*/
|
|
39
|
+
export type NotifierCommandKind = 'scan' | 'help' | 'version' | 'error' | 'scan-failure';
|
|
40
|
+
/** Everything the decision depends on, with no implicit global reads. */
|
|
41
|
+
export type NotifierEligibilityInput = {
|
|
42
|
+
/** Environment map to consult, e.g. the caller's own environment object. */
|
|
43
|
+
readonly env: NotifierEnv;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the standard output stream is an interactive terminal. Node reports a pipe or a
|
|
46
|
+
* redirection as non-interactive (its `isTTY` flag is absent), and any falsy value here is
|
|
47
|
+
* ineligible, so the caller only has to pass the stream's own flag.
|
|
48
|
+
*/
|
|
49
|
+
readonly stdoutTTY: boolean;
|
|
50
|
+
/** Whether the error output stream is an interactive terminal, with the same rule as stdout. */
|
|
51
|
+
readonly stderrTTY: boolean;
|
|
52
|
+
/** Which command the CLI completed. Only a completed scan can ever be eligible. */
|
|
53
|
+
readonly commandKind: NotifierCommandKind;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Decide whether a human interactive invocation may attempt the optional update check.
|
|
57
|
+
*
|
|
58
|
+
* Returns `true` only when every gate in the contract's eligibility order passes. `false` means
|
|
59
|
+
* the caller must not touch the network, must not read the cache and must not print a notice.
|
|
60
|
+
*
|
|
61
|
+
* @param input - The injected environment map, the two stream TTY flags and the command kind.
|
|
62
|
+
* @returns `true` only for an interactive, non-CI, non-opted-out completed normal scan.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isEligibleUpdateCheck(input: NotifierEligibilityInput): boolean;
|
|
65
|
+
//# sourceMappingURL=eligibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../../src/cli/notifier/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAEvE;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;AAEzF,yEAAyE;AACzE,MAAM,MAAM,wBAAwB,GAAG;IACrC,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC3C,CAAC;AA0FF;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAe9E"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure eligibility predicate for the optional CLI update check (Step 64).
|
|
3
|
+
*
|
|
4
|
+
* The update-notifier contract allows a *newer DepVitals version* notice to be printed to stderr
|
|
5
|
+
* only after a normal, completed scan in an interactive terminal. Everything before that network
|
|
6
|
+
* convenience is decided here, so the gates are auditable in one place:
|
|
7
|
+
*
|
|
8
|
+
* 1. a completed normal scan invocation (clean **or** with findings) - not help, not version,
|
|
9
|
+
* not an argument error, not a failed scan;
|
|
10
|
+
* 2. both stdout and stderr are interactive TTYs (a pipe or redirection on either stream skips);
|
|
11
|
+
* 3. no reliable CI flag;
|
|
12
|
+
* 4. no explicit `DEPVITALS_NO_UPDATE_CHECK=1` opt-out.
|
|
13
|
+
*
|
|
14
|
+
* An ineligible invocation must trigger **no** registry lookup, **no** cache read and **no**
|
|
15
|
+
* notice, so this module deliberately has no imports: it touches no filesystem, no network, no
|
|
16
|
+
* cache, no clock, no output stream and no ambient global state. Every input is passed in by the
|
|
17
|
+
* caller (an environment map, two booleans and the command kind), which keeps the decision
|
|
18
|
+
* deterministic, offline and unit-testable without any real global state.
|
|
19
|
+
*
|
|
20
|
+
* The decision is safe by default: anything unknown or unrecognized resolves to `false`, and no
|
|
21
|
+
* input can make this function throw.
|
|
22
|
+
*/
|
|
23
|
+
/** The only supported opt-out variable; see the update-notifier contract. */
|
|
24
|
+
const NO_UPDATE_CHECK_VARIABLE = 'DEPVITALS_NO_UPDATE_CHECK';
|
|
25
|
+
/** The opt-out is honored for exactly this value and for no other. */
|
|
26
|
+
const NO_UPDATE_CHECK_VALUE = '1';
|
|
27
|
+
/**
|
|
28
|
+
* The explicit list of environment variables that reliably mean "this is a non-interactive
|
|
29
|
+
* continuous-integration run". Matching is by exact variable name, never by substring, so
|
|
30
|
+
* unrelated variables such as `MY_CI`, `CIRCLE_CI` or `GITLAB` are ignored.
|
|
31
|
+
*/
|
|
32
|
+
const CI_VARIABLES = [
|
|
33
|
+
'CI',
|
|
34
|
+
'GITHUB_ACTIONS',
|
|
35
|
+
'GITLAB_CI',
|
|
36
|
+
'TF_BUILD',
|
|
37
|
+
'CIRCLECI',
|
|
38
|
+
'TRAVIS',
|
|
39
|
+
'BUILDKITE',
|
|
40
|
+
'JENKINS_URL',
|
|
41
|
+
'TEAMCITY_VERSION',
|
|
42
|
+
'APPVEYOR',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Values that explicitly cancel a CI flag. Providers write real identifiers into these variables
|
|
46
|
+
* (`CI=TeamCity`, `TF_BUILD=True`), so a value that is present, non-empty and not one of these
|
|
47
|
+
* tokens counts as CI. Skipping on an unrecognized value is the conservative direction: it costs a
|
|
48
|
+
* missing notice, never an unexpected request or an extra line in a CI log.
|
|
49
|
+
*/
|
|
50
|
+
const CANCELLED_VALUES = new Set(['', '0', 'false', 'no', 'off']);
|
|
51
|
+
/**
|
|
52
|
+
* Read one variable from the injected environment map.
|
|
53
|
+
*/
|
|
54
|
+
function read(env, name) {
|
|
55
|
+
return env[name];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Whether a CI variable is present with a value that is not an explicit cancellation. Absent and
|
|
59
|
+
* empty values are not CI; comparison of cancellation tokens is case-insensitive.
|
|
60
|
+
*/
|
|
61
|
+
function isSet(value) {
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return !CANCELLED_VALUES.has(value.toLowerCase());
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether any recognized CI variable reports a CI run.
|
|
69
|
+
*/
|
|
70
|
+
function isCiRun(env) {
|
|
71
|
+
return CI_VARIABLES.some((name) => isSet(read(env, name)));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Whether the user opted out. `DEPVITALS_NO_UPDATE_CHECK` disables the update check if and only if
|
|
75
|
+
* it equals exactly `"1"`: `0`, ``, `2`, `true`, `01` and padded values do not opt out, and no
|
|
76
|
+
* broader truthy handling is implemented or implied.
|
|
77
|
+
*/
|
|
78
|
+
function hasOptedOut(env) {
|
|
79
|
+
return read(env, NO_UPDATE_CHECK_VARIABLE) === NO_UPDATE_CHECK_VALUE;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether the invocation is a completed normal scan. `help`, `version`, argument errors and failed
|
|
83
|
+
* scans are excluded; a scan that finished with findings is included, because findings are a
|
|
84
|
+
* successful deterministic result and only change the exit code.
|
|
85
|
+
*/
|
|
86
|
+
function isCompletedScan(commandKind) {
|
|
87
|
+
switch (commandKind) {
|
|
88
|
+
case 'scan':
|
|
89
|
+
return true;
|
|
90
|
+
case 'help':
|
|
91
|
+
case 'version':
|
|
92
|
+
case 'error':
|
|
93
|
+
case 'scan-failure':
|
|
94
|
+
return false;
|
|
95
|
+
default:
|
|
96
|
+
// A value outside the declared union can only come from an untyped caller; unknown work is
|
|
97
|
+
// not a completed scan, so the safe default applies here too.
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Decide whether a human interactive invocation may attempt the optional update check.
|
|
103
|
+
*
|
|
104
|
+
* Returns `true` only when every gate in the contract's eligibility order passes. `false` means
|
|
105
|
+
* the caller must not touch the network, must not read the cache and must not print a notice.
|
|
106
|
+
*
|
|
107
|
+
* @param input - The injected environment map, the two stream TTY flags and the command kind.
|
|
108
|
+
* @returns `true` only for an interactive, non-CI, non-opted-out completed normal scan.
|
|
109
|
+
*/
|
|
110
|
+
export function isEligibleUpdateCheck(input) {
|
|
111
|
+
if (!isCompletedScan(input.commandKind)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Both streams must be interactive; a pipe or redirection on either one suppresses the check.
|
|
115
|
+
if (!input.stdoutTTY || !input.stderrTTY) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (isCiRun(input.env)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return !hasOptedOut(input.env);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=eligibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eligibility.js","sourceRoot":"","sources":["../../../src/cli/notifier/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAqCH,6EAA6E;AAC7E,MAAM,wBAAwB,GAAG,2BAA2B,CAAC;AAE7D,sEAAsE;AACtE,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC;;;;GAIG;AACH,MAAM,YAAY,GAAsB;IACtC,IAAI;IACJ,gBAAgB;IAChB,WAAW;IACX,UAAU;IACV,UAAU;IACV,QAAQ;IACR,WAAW;IACX,aAAa;IACb,kBAAkB;IAClB,UAAU;CACX,CAAC;AAEF;;;;;GAKG;AACH,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAEvF;;GAEG;AACH,SAAS,IAAI,CAAC,GAAgB,EAAE,IAAY;IAC1C,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,SAAS,KAAK,CAAC,KAAyB;IACtC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,SAAS,OAAO,CAAC,GAAgB;IAC/B,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,GAAgB;IACnC,OAAO,IAAI,CAAC,GAAG,EAAE,wBAAwB,CAAC,KAAK,qBAAqB,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,WAAgC;IACvD,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QACd,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS,CAAC;QACf,KAAK,OAAO,CAAC;QACb,KAAK,cAAc;YACjB,OAAO,KAAK,CAAC;QACf;YACE,2FAA2F;YAC3F,8DAA8D;YAC9D,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAA+B;IACnE,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8FAA8F;IAC9F,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC"}
|