@westbayberry/dg 2.1.0 → 2.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 +216 -226
- package/dist/agents/claude-code.js +123 -0
- package/dist/agents/codex.js +65 -0
- package/dist/agents/copilot-cli.js +115 -0
- package/dist/agents/cursor.js +118 -0
- package/dist/agents/gate-posture.js +21 -0
- package/dist/agents/gemini.js +107 -0
- package/dist/agents/persistence.js +351 -0
- package/dist/agents/registry.js +128 -0
- package/dist/agents/routing.js +118 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/windsurf.js +93 -0
- package/dist/api/analyze.js +1 -1
- package/dist/audit/detectors.js +0 -11
- package/dist/audit/events.js +5 -21
- package/dist/audit/rules.js +6 -2
- package/dist/audit-ui/AuditApp.js +2 -0
- package/dist/audit-ui/components/AuditResultsView.js +55 -92
- package/dist/audit-ui/export.js +0 -4
- package/dist/audit-ui/format.js +0 -3
- package/dist/audit-ui/launch.js +10 -1
- package/dist/auth/device-login.js +4 -5
- package/dist/auth/login-app.js +7 -7
- package/dist/auth/store.js +17 -5
- package/dist/bin/dg.js +59 -52
- package/dist/commands/agents.js +275 -0
- package/dist/commands/audit.js +28 -3
- package/dist/commands/config.js +15 -7
- package/dist/commands/cooldown.js +389 -0
- package/dist/commands/decisions.js +23 -9
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/help.js +1 -1
- package/dist/commands/licenses.js +10 -22
- package/dist/commands/logout.js +4 -11
- package/dist/commands/router.js +6 -4
- package/dist/commands/sbom.js +206 -0
- package/dist/commands/scan.js +1 -1
- package/dist/commands/service.js +32 -13
- package/dist/commands/setup.js +196 -27
- package/dist/commands/types.js +1 -0
- package/dist/commands/uninstall.js +2 -1
- package/dist/commands/update.js +17 -8
- package/dist/commands/verify.js +8 -5
- package/dist/config/settings.js +49 -48
- package/dist/decisions/remember-prompt.js +5 -10
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +5 -4
- package/dist/install-ui/block-render.js +18 -14
- package/dist/install-ui/prep-spinner.js +32 -0
- package/dist/launcher/agent-check.js +896 -0
- package/dist/launcher/agent-hook-exec.js +70 -0
- package/dist/launcher/agent-hook-io.js +39 -0
- package/dist/launcher/cargo-cache.js +40 -0
- package/dist/launcher/classify.js +44 -7
- package/dist/launcher/env.js +105 -26
- package/dist/launcher/install-preflight.js +64 -18
- package/dist/launcher/live-install.js +29 -8
- package/dist/launcher/manifest-screen.js +171 -0
- package/dist/launcher/output-redaction.js +10 -4
- package/dist/launcher/preflight-prompt.js +18 -4
- package/dist/launcher/run.js +178 -103
- package/dist/launcher/spawn-invocation.js +21 -0
- package/dist/policy/cooldown.js +18 -5
- package/dist/policy/evaluate.js +5 -6
- package/dist/policy/pypi-name.js +17 -0
- package/dist/presentation/mode.js +3 -2
- package/dist/presentation/package-page.js +9 -0
- package/dist/presentation/theme.js +7 -7
- package/dist/project/dgfile.js +146 -6
- package/dist/project/override-trust.js +0 -0
- package/dist/proxy/auth.js +42 -0
- package/dist/proxy/ca.js +29 -9
- package/dist/proxy/cooldown-exemptions-file.js +33 -0
- package/dist/proxy/enforcement.js +55 -16
- package/dist/proxy/metadata-map.js +66 -12
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +568 -53
- package/dist/proxy/worker.js +16 -1
- package/dist/publish-set/collect.js +1 -4
- package/dist/publish-set/npm.js +8 -5
- package/dist/publish-set/pack.js +9 -3
- package/dist/runtime/cli.js +0 -4
- package/dist/runtime/fatal.js +31 -0
- package/dist/runtime/first-run.js +14 -12
- package/dist/runtime/node-version.js +43 -6
- package/dist/runtime/nudges.js +35 -2
- package/dist/sbom/cyclonedx.js +211 -0
- package/dist/sbom-ui/SbomApp.js +158 -0
- package/dist/sbom-ui/components/SbomHeader.js +32 -0
- package/dist/sbom-ui/components/SbomList.js +52 -0
- package/dist/sbom-ui/inventory.js +132 -0
- package/dist/sbom-ui/launch.js +51 -0
- package/dist/sbom-ui/run.js +55 -0
- package/dist/sbom-ui/store.js +26 -0
- package/dist/scan/collect.js +10 -6
- package/dist/scan/command.js +17 -10
- package/dist/scan/discovery.js +11 -2
- package/dist/scan/render.js +28 -4
- package/dist/scan/scanner-report.js +15 -9
- package/dist/scan/staged.js +33 -9
- package/dist/scan-ui/LegacyApp.js +9 -13
- package/dist/scan-ui/alt-screen.js +5 -8
- package/dist/scan-ui/components/InteractiveResultsView.js +133 -120
- package/dist/scan-ui/components/ProgressBar.js +3 -14
- package/dist/scan-ui/components/ProjectSelector.js +1 -1
- package/dist/scan-ui/components/ScoreHeader.js +2 -3
- package/dist/scan-ui/components/SetupBanner.js +0 -6
- package/dist/scan-ui/format-helpers.js +61 -5
- package/dist/scan-ui/hooks/useResizeRepaint.js +25 -0
- package/dist/scan-ui/hooks/useScan.js +19 -2
- package/dist/scan-ui/launch.js +7 -4
- package/dist/scan-ui/shims.js +11 -4
- package/dist/scripts/detect.js +7 -2
- package/dist/scripts/gate.js +1 -1
- package/dist/security/sanitize.js +8 -4
- package/dist/service/state.js +28 -9
- package/dist/service/trust-refresh.js +92 -0
- package/dist/service/trust-store.js +5 -9
- package/dist/service/worker.js +23 -1
- package/dist/setup/activate-shell.js +28 -0
- package/dist/setup/git-hook.js +49 -4
- package/dist/setup/plan.js +227 -49
- package/dist/setup/uninstall-standalone.js +25 -0
- package/dist/setup-ui/gate.js +39 -0
- package/dist/setup-ui/offer.js +42 -0
- package/dist/setup-ui/selector.js +27 -0
- package/dist/setup-ui/tasks.js +56 -0
- package/dist/setup-ui/wizard.js +233 -0
- package/dist/standalone/uninstall.mjs +2123 -0
- package/dist/state/cooldown-held.js +66 -0
- package/dist/state/index.js +1 -0
- package/dist/state/locks.js +4 -2
- package/dist/state/store.js +2 -1
- package/dist/util/external-tool.js +25 -0
- package/dist/util/git.js +10 -3
- package/dist/util/report-writer.js +57 -0
- package/dist/verify/local.js +242 -44
- package/dist/verify/package-check.js +87 -29
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +7 -8
- package/NOTICE +0 -5
- package/dist/commands/completion.js +0 -117
- package/dist/commands/explain.js +0 -236
- package/dist/commands/unavailable.js +0 -11
- package/dist/scripts/rebuild.js +0 -28
- package/dist/telemetry/events.js +0 -40
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
// A bare `npm install` resolves the whole dependency tree, but enumerating every
|
|
4
|
+
// transitive package in the hook would blow its time budget — that breadth is
|
|
5
|
+
// the runtime network gate's job. The static hook screens the DIRECT
|
|
6
|
+
// dependencies declared in the manifest, which is where a cloned hostile repo
|
|
7
|
+
// plants a malicious package, and caps the count so a giant manifest defers
|
|
8
|
+
// instead of silently passing.
|
|
9
|
+
const MAX_MANIFEST_SPECS = 100;
|
|
10
|
+
const MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
11
|
+
function readBoundedFile(path) {
|
|
12
|
+
try {
|
|
13
|
+
if (statSync(path).size > MAX_MANIFEST_BYTES) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return readFileSync(path, "utf8");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function asRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
24
|
+
}
|
|
25
|
+
// package-lock v2/v3 keys direct deps as "node_modules/<name>"; v1 nests them
|
|
26
|
+
// under `dependencies`. Either way, pinning to the lock means the version we
|
|
27
|
+
// screen is the version that installs.
|
|
28
|
+
function lockfileVersions(cwd) {
|
|
29
|
+
const out = new Map();
|
|
30
|
+
for (const file of ["package-lock.json", "npm-shrinkwrap.json"]) {
|
|
31
|
+
const raw = readBoundedFile(join(cwd, file));
|
|
32
|
+
if (!raw) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
const packages = asRecord(parsed.packages);
|
|
38
|
+
for (const [key, meta] of Object.entries(packages)) {
|
|
39
|
+
if (key.startsWith("node_modules/")) {
|
|
40
|
+
const version = asRecord(meta).version;
|
|
41
|
+
if (typeof version === "string") {
|
|
42
|
+
out.set(key.slice("node_modules/".length), version);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const deps = asRecord(parsed.dependencies);
|
|
47
|
+
for (const [name, meta] of Object.entries(deps)) {
|
|
48
|
+
const version = asRecord(meta).version;
|
|
49
|
+
if (typeof version === "string" && !out.has(name)) {
|
|
50
|
+
out.set(name, version);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Unparsable lockfile: fall back to range resolution, don't crash.
|
|
56
|
+
}
|
|
57
|
+
if (out.size > 0) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
export function readNpmManifestSpecs(cwd) {
|
|
64
|
+
const raw = readBoundedFile(join(cwd, "package.json"));
|
|
65
|
+
if (!raw) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(raw);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const names = [];
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
78
|
+
for (const name of Object.keys(asRecord(parsed[field]))) {
|
|
79
|
+
if (!seen.has(name)) {
|
|
80
|
+
seen.add(name);
|
|
81
|
+
names.push(name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (names.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const truncated = names.length > MAX_MANIFEST_SPECS;
|
|
89
|
+
const locks = lockfileVersions(cwd);
|
|
90
|
+
const specs = names.slice(0, MAX_MANIFEST_SPECS).map((name) => ({ name, version: locks.get(name) ?? null }));
|
|
91
|
+
return { specs, truncated };
|
|
92
|
+
}
|
|
93
|
+
function parseRequirementLine(line) {
|
|
94
|
+
const stripped = line.split(/(?<!\\)#/)[0]?.trim() ?? "";
|
|
95
|
+
if (stripped.length === 0 || stripped.startsWith("-")) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
// Drop environment markers (`; python_version < "3.9"`) and inline options.
|
|
99
|
+
const core = (stripped.split(";")[0] ?? "").trim();
|
|
100
|
+
if (core.length === 0 || core.includes("://") || core.startsWith("git+")) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const exact = /^([A-Za-z0-9._-]+)(?:\[[^\]]*\])?==([^,;\s]+)$/.exec(core);
|
|
104
|
+
if (exact && exact[1] && exact[2]) {
|
|
105
|
+
return { name: exact[1], version: exact[2] };
|
|
106
|
+
}
|
|
107
|
+
const ranged = /^([A-Za-z0-9._-]+)(?:\[[^\]]*\])?\s*(?:===|>=|<=|~=|!=|<|>|$)/.exec(core);
|
|
108
|
+
if (ranged && ranged[1]) {
|
|
109
|
+
return { name: ranged[1], version: null };
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
export function readPipRequirementSpecs(args, cwd) {
|
|
114
|
+
const files = [];
|
|
115
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
116
|
+
const a = args[i];
|
|
117
|
+
if (a === undefined) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (a === "-r" || a === "--requirement") {
|
|
121
|
+
const v = args[i + 1];
|
|
122
|
+
i += 1;
|
|
123
|
+
if (v !== undefined) {
|
|
124
|
+
files.push(v);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (a.startsWith("--requirement=")) {
|
|
128
|
+
files.push(a.slice("--requirement=".length));
|
|
129
|
+
}
|
|
130
|
+
else if (a.startsWith("-r=")) {
|
|
131
|
+
files.push(a.slice("-r=".length));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (files.length === 0) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const specs = [];
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
let truncated = false;
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
const path = isAbsolute(file) ? file : join(cwd, file);
|
|
142
|
+
if (!existsSync(path)) {
|
|
143
|
+
// A missing requirements file means the install will fail anyway — nothing
|
|
144
|
+
// to screen, and no reason to nag the user. Leave it alone.
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const raw = readBoundedFile(path);
|
|
148
|
+
if (!raw) {
|
|
149
|
+
// The file exists but we could not read it (oversized / permissions) — that
|
|
150
|
+
// is a real unscreened install, so defer rather than pass it clean.
|
|
151
|
+
truncated = true;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
155
|
+
const spec = parseRequirementLine(line);
|
|
156
|
+
if (!spec || seen.has(spec.name)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (specs.length >= MAX_MANIFEST_SPECS) {
|
|
160
|
+
truncated = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
seen.add(spec.name);
|
|
164
|
+
specs.push(spec);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (specs.length === 0) {
|
|
168
|
+
return truncated ? { specs, truncated } : null;
|
|
169
|
+
}
|
|
170
|
+
return { specs, truncated };
|
|
171
|
+
}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
const credentialUrlPattern = /\b([a-z][a-z0-9+.-]{0,31}:\/\/)([^/\s:@]+):([^/\s@]+)@/gi;
|
|
2
2
|
const authHeaderPattern = /\b(proxy-authorization|authorization):\s*[^\r\n]+/gi;
|
|
3
|
-
|
|
3
|
+
// Matches both KEY=value and colon-form (KEY: value, YAML) secret assignments;
|
|
4
|
+
// the separator and any opening quote are preserved in the replacement.
|
|
5
|
+
const tokenAssignmentPattern = /(?<![A-Za-z0-9])([A-Za-z0-9_-]*(?:secret[_-]key|access[_-]key|api[_-]key|token|password|secret))(\s*[:=]\s*)("?)([^\s"]+)/gi;
|
|
4
6
|
const npmrcAuthPattern = /(_authToken|_auth|_password)\s*=\s*("[^"]*"|[^\s;,]+)/gi;
|
|
5
7
|
const jsonSecretPattern = /("[A-Za-z0-9_.$-]*(?:secret[_-]key|access[_-]key|api[_-]key|token|password|secret)"\s*:\s*)"(?:[^"\\]|\\.)*"/gi;
|
|
6
8
|
const bearerPattern = /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/g;
|
|
7
|
-
|
|
9
|
+
// Bare tokens (not in URL userinfo or KEY=value form), by their published shape.
|
|
10
|
+
// Alnum-only look-around instead of \b so a fixed-length token immediately
|
|
11
|
+
// adjacent to an underscore-word (e.g. npm_<36>_ci) is still redacted — \b fails
|
|
12
|
+
// there because the boundary would fall between two word characters.
|
|
13
|
+
const knownTokenShapePattern = /(?<![A-Za-z0-9])(npm_[A-Za-z0-9]{36}|gh[pousr]_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{22,255}|pypi-[A-Za-z0-9_-]{20,}|glpat-[A-Za-z0-9_-]{20,}|hf_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|sk_(?:live|test)_[A-Za-z0-9]{20,}|rk_live_[A-Za-z0-9]{20,}|sk-(?:proj-)?[A-Za-z0-9_-]{20,}|xox[abdeprs]-[A-Za-z0-9-]{10,})(?![A-Za-z0-9])/g;
|
|
8
14
|
export function redactSecrets(text) {
|
|
9
15
|
return text
|
|
10
16
|
.replace(credentialUrlPattern, "$1<redacted>@")
|
|
11
17
|
.replace(authHeaderPattern, (_match, header) => `${header}: <redacted>`)
|
|
12
18
|
.replace(npmrcAuthPattern, "$1=<redacted>")
|
|
13
19
|
.replace(jsonSecretPattern, '$1"<redacted>"')
|
|
14
|
-
.replace(tokenAssignmentPattern, "$1
|
|
20
|
+
.replace(tokenAssignmentPattern, "$1$2$3<redacted>")
|
|
15
21
|
.replace(bearerPattern, "Bearer <redacted>")
|
|
16
22
|
.replace(knownTokenShapePattern, "<redacted>");
|
|
17
23
|
}
|
|
18
24
|
const STREAM_FLUSH_QUIET_MS = 80;
|
|
19
25
|
const SECRET_TAIL_SCAN_CHARS = 80;
|
|
20
|
-
const secretTailPattern = /(Bearer\s+[\w.~+/=-]*|npm_[A-Za-z0-9]*|gh[pousr]_[A-Za-z0-9]*|pypi-[\w-]*|glpat-[\w-]*|xox[
|
|
26
|
+
const secretTailPattern = /(Bearer\s+[\w.~+/=-]*|npm_[A-Za-z0-9]*|gh[pousr]_[A-Za-z0-9]*|github_pat_[A-Za-z0-9_]*|pypi-[\w-]*|glpat-[\w-]*|hf_[A-Za-z0-9]*|AKIA[0-9A-Z]*|ASIA[0-9A-Z]*|AIza[\w-]*|sk_(?:live|test)_[A-Za-z0-9]*|rk_live_[A-Za-z0-9]*|sk-(?:proj-)?[\w-]*|xox[abdeprs]-[\w-]*|[\w-]*(?:_authToken|_auth|_password|secret[_-]key|access[_-]key|api[_-]key|token|password|secret)\s*[:=]\s*"?\S*|"[\w.$-]*(?:secret[_-]key|access[_-]key|api[_-]key|token|password|secret)"\s*:\s*"?[^"\n]*)$/i;
|
|
21
27
|
export function createStreamRedactor(emit) {
|
|
22
28
|
let pending = "";
|
|
23
29
|
let timer;
|
|
@@ -4,11 +4,13 @@ import { matchDecision } from "../decisions/apply.js";
|
|
|
4
4
|
import { offerRememberOnIo } from "../decisions/remember-prompt.js";
|
|
5
5
|
import { renderInstallDecision } from "../install-ui/block-render.js";
|
|
6
6
|
import { defaultPromptIo } from "../install-ui/prompt.js";
|
|
7
|
+
import { normalizePypiName } from "../policy/pypi-name.js";
|
|
7
8
|
import { resolveAcceptedBy } from "../project/dgfile.js";
|
|
8
9
|
import { enforceProtectedInstall } from "../proxy/enforcement.js";
|
|
9
|
-
import { classifyPackageManagerInvocation, isSupportedPackageManager } from "./classify.js";
|
|
10
|
+
import { classifyPackageManagerInvocation, isSupportedPackageManager, normalizeManagerName } from "./classify.js";
|
|
10
11
|
import { actionRank, promptPreflightYesNo, recordPreflightApprovals, renderCoveredWarns, renderProvenanceDowngrades, resolvePreflightDecisions } from "./install-preflight.js";
|
|
11
12
|
import { redactSecrets } from "./output-redaction.js";
|
|
13
|
+
import { startPrepSpinner } from "../install-ui/prep-spinner.js";
|
|
12
14
|
const ECOSYSTEM_BY_MANAGER = {
|
|
13
15
|
npm: "npm",
|
|
14
16
|
pnpm: "npm",
|
|
@@ -24,8 +26,9 @@ export async function maybePreflightInstallPrompt(args, options = {}) {
|
|
|
24
26
|
if (!io.isTTY || isCiEnv(env)) {
|
|
25
27
|
return FALL_THROUGH;
|
|
26
28
|
}
|
|
27
|
-
const [
|
|
28
|
-
|
|
29
|
+
const [rawManager, ...rest] = args;
|
|
30
|
+
const manager = normalizeManagerName(rawManager ?? "");
|
|
31
|
+
if (!rawManager || !isSupportedPackageManager(manager)) {
|
|
29
32
|
return FALL_THROUGH;
|
|
30
33
|
}
|
|
31
34
|
const ecosystem = ECOSYSTEM_BY_MANAGER[manager];
|
|
@@ -44,11 +47,13 @@ export async function maybePreflightInstallPrompt(args, options = {}) {
|
|
|
44
47
|
const decisions = options.decisionsCwd ? resolvePreflightDecisions(ecosystem, options.decisionsCwd, env) : null;
|
|
45
48
|
const flagged = [];
|
|
46
49
|
const covered = [];
|
|
50
|
+
const spinner = startPrepSpinner("DG preparing…");
|
|
47
51
|
try {
|
|
48
52
|
const response = await (options.analyze ?? analyzePackages)(specs.map((spec) => ({ name: spec.name, version: spec.version })), { ecosystem, env });
|
|
53
|
+
spinner.stop();
|
|
49
54
|
renderProvenanceDowngrades(response.packages, io.output);
|
|
50
55
|
for (const spec of specs) {
|
|
51
|
-
const pkg = response.packages.find((entry) => entry.name
|
|
56
|
+
const pkg = response.packages.find((entry) => sameSpecName(entry.name, spec.name, ecosystem) && entry.version === spec.version);
|
|
52
57
|
const action = pkg?.action ?? "pass";
|
|
53
58
|
if (action === "pass") {
|
|
54
59
|
continue;
|
|
@@ -68,6 +73,9 @@ export async function maybePreflightInstallPrompt(args, options = {}) {
|
|
|
68
73
|
catch {
|
|
69
74
|
return FALL_THROUGH;
|
|
70
75
|
}
|
|
76
|
+
finally {
|
|
77
|
+
spinner.stop();
|
|
78
|
+
}
|
|
71
79
|
if (covered.length > 0) {
|
|
72
80
|
renderCoveredWarns(covered, decisions, io.output);
|
|
73
81
|
recordPreflightApprovals(covered.map((pkg) => ({ name: pkg.name, version: pkg.version, action: "warn" })));
|
|
@@ -131,6 +139,12 @@ export async function maybePreflightInstallPrompt(args, options = {}) {
|
|
|
131
139
|
result: { exitCode: 1, stdout: "", stderr: "Declined. Nothing was installed.\n" }
|
|
132
140
|
};
|
|
133
141
|
}
|
|
142
|
+
function sameSpecName(entryName, specName, ecosystem) {
|
|
143
|
+
if (ecosystem === "pypi") {
|
|
144
|
+
return normalizePypiName(entryName) === normalizePypiName(specName);
|
|
145
|
+
}
|
|
146
|
+
return entryName === specName;
|
|
147
|
+
}
|
|
134
148
|
function asFlaggedPackage(entry) {
|
|
135
149
|
return {
|
|
136
150
|
name: entry.spec.name,
|