@westbayberry/dg 2.0.0 → 2.0.1

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.
@@ -12,6 +12,7 @@ import { loadUserConfig, saveUserConfig, setConfigValue } from "../config/settin
12
12
  import { promptYesNo } from "../util/tty-prompt.js";
13
13
  import { authStatus } from "../auth/store.js";
14
14
  import { shouldLaunchAuditTui, launchAuditTui } from "../audit-ui/launch.js";
15
+ import { renderCommandHelp } from "./help.js";
15
16
  export const auditCommand = {
16
17
  name: "audit",
17
18
  summary: "Audit the package you're about to publish for leaked secrets and risky files.",
@@ -95,7 +96,11 @@ export async function maybeAudit(args) {
95
96
  if (args[0] !== "audit") {
96
97
  return { handled: false, result: { exitCode: 0, stdout: "", stderr: "" } };
97
98
  }
98
- const gathered = gather(args.slice(1));
99
+ const sub = args.slice(1);
100
+ if (sub[0] === "--help" || sub[0] === "-h" || sub[0] === "help") {
101
+ return { handled: true, result: { exitCode: 0, stdout: renderCommandHelp(auditCommand), stderr: "" } };
102
+ }
103
+ const gathered = gather(sub);
99
104
  if ("error" in gathered) {
100
105
  return { handled: true, result: gatherError(gathered) };
101
106
  }
@@ -13,6 +13,7 @@ const HEADLINES = {
13
13
  license: "license policy",
14
14
  "hash-mismatch": "artifact integrity mismatch",
15
15
  "private-upload-disabled": "private artifact not scanned",
16
+ "needs-login": "sign-in required",
16
17
  "api-unavailable": "scanner unavailable",
17
18
  "quota-exceeded": "monthly scan limit reached",
18
19
  "api-timeout": "scanner timed out",
@@ -27,6 +28,7 @@ const NEXT_STEP = {
27
28
  license: "Replace the dependency or update your license policy.",
28
29
  "hash-mismatch": "Clear your package cache and retry. If it persists, do not install.",
29
30
  "private-upload-disabled": "Enable private artifact scanning to verify this package.",
31
+ "needs-login": "Run 'dg login' (free) to check packages from the registry before they install.",
30
32
  "quota-exceeded": "Upgrade your plan or wait for your monthly limit to reset. See westbayberry.com/pricing."
31
33
  };
32
34
  export function describeBlockedInstall(decision) {
@@ -34,7 +36,7 @@ export function describeBlockedInstall(decision) {
34
36
  const override = decision.forceOverride && !decision.forceOverride.allowed
35
37
  ? "not allowed by your policy"
36
38
  : "re-run with --dg-force-install";
37
- const nextStep = verifiedBad
39
+ const nextStep = verifiedBad || decision.cause === "needs-login"
38
40
  ? NEXT_STEP[decision.cause]
39
41
  : "Re-check later with 'dg verify', or override if you accept the risk.";
40
42
  return {
@@ -67,13 +69,13 @@ export function renderInstallDecision(decision) {
67
69
  if (decision.dashboardUrl) {
68
70
  lines.push(` Evidence: ${decision.dashboardUrl}`);
69
71
  }
70
- if (decision.unauthenticated) {
72
+ if (decision.unauthenticated && decision.cause !== "needs-login") {
71
73
  lines.push(" Auth: local policy only (run 'dg login' for full coverage)");
72
74
  }
73
75
  lines.push(decision.forceOverride && !decision.forceOverride.allowed
74
76
  ? " Override: not allowed by your policy"
75
77
  : " Override: re-run with --dg-force-install");
76
- const next = verifiedBad
78
+ const next = verifiedBad || decision.cause === "needs-login"
77
79
  ? NEXT_STEP[decision.cause]
78
80
  : "Re-check later with 'dg verify', or override if you accept the risk.";
79
81
  if (next) {
@@ -655,6 +655,15 @@ async function lookupVerdict(options, target, sha256, upstream, identity) {
655
655
  reason: "You've reached your monthly scan limit. Upgrade at westbayberry.com/pricing or wait for it to reset."
656
656
  };
657
657
  }
658
+ if (response.status === 401) {
659
+ return {
660
+ verdict: "block",
661
+ packageName: artifactDisplayName(identity),
662
+ cause: "needs-login",
663
+ unauthenticated: true,
664
+ reason: "Checking a package from the registry before it installs requires sign-in."
665
+ };
666
+ }
658
667
  if (!response.ok) {
659
668
  return {
660
669
  verdict: "block",
@@ -898,6 +907,7 @@ function isProxyCause(value) {
898
907
  "license",
899
908
  "hash-mismatch",
900
909
  "private-upload-disabled",
910
+ "needs-login",
901
911
  "api-unavailable",
902
912
  "quota-exceeded",
903
913
  "api-timeout",
@@ -37,6 +37,19 @@ function actionBadge(action) {
37
37
  return { label: "Unknown", color: chalk.cyan };
38
38
  return { label: "Pass", color: chalk.green };
39
39
  }
40
+ function isYankedIncomplete(pkg) {
41
+ if (pkg.action !== "analysis_incomplete")
42
+ return false;
43
+ const haystack = [...(pkg.reasons ?? []), ...pkg.findings.map((f) => f.title ?? "")]
44
+ .join(" ")
45
+ .toLowerCase();
46
+ return haystack.includes("unpublish") || haystack.includes("yank") || haystack.includes("removed from the registr");
47
+ }
48
+ export function packageBadge(pkg) {
49
+ if (isYankedIncomplete(pkg))
50
+ return { label: "Removed", color: chalk.yellow };
51
+ return actionBadge(pkg.action);
52
+ }
40
53
  const EVIDENCE_LIMIT = 2;
41
54
  // Fixed lines outside the scrollable group area:
42
55
  // 5 ScoreHeader box | 2 Flagged box top | 2 scroll indicators
@@ -1066,7 +1079,7 @@ export const InteractiveResultsView = ({ result, config: _config, durationMs, on
1066
1079
  const dpGroup = groups[detailPane.groupIndex];
1067
1080
  if (dpGroup) {
1068
1081
  const dpRep = firstPackage(dpGroup);
1069
- const { color: dpColor } = actionBadge(dpRep.action);
1082
+ const { color: dpColor } = packageBadge(dpRep);
1070
1083
  const dpScroll = detailPane.scroll;
1071
1084
  const dpAbove = dpScroll;
1072
1085
  const dpBelow = Math.max(0, detailLines.length - dpScroll - detailContentRows);
@@ -1080,7 +1093,7 @@ export const InteractiveResultsView = ({ result, config: _config, durationMs, on
1080
1093
  const isCursor = globalIdx === clampedCursor;
1081
1094
  const level = getLevel(globalIdx);
1082
1095
  const rep = firstPackage(group);
1083
- const { label, color } = actionBadge(rep.action);
1096
+ const { label, color } = packageBadge(rep);
1084
1097
  const names = groupNames(group);
1085
1098
  const scoreStr = String(rep.score);
1086
1099
  const lcInfo = rep.license;
@@ -17,6 +17,10 @@ export const RC_SENTINEL = "dg-shell-rc-v1";
17
17
  export const GUARD_HOOK_SENTINEL = "dg-git-hook-v1";
18
18
  export const RC_BEGIN = "# >>> dg setup >>>";
19
19
  export const RC_END = "# <<< dg setup <<<";
20
+ const LEGACY_RC_MARKERS = [
21
+ { begin: "# >>> dg-managed >>>", end: "# <<< dg-managed <<<" }
22
+ ];
23
+ const LEGACY_RC_CANDIDATES = [".zshrc", ".bashrc", ".bash_profile", ".profile", join(".config", "fish", "config.fish")];
20
24
  export const SETUP_UNINSTALL_LOCK = "setup-uninstall";
21
25
  export const SETUP_UNINSTALL_LOCK_STALE_MS = 30 * 60 * 1000;
22
26
  export const STALE_SESSION_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
@@ -191,6 +195,7 @@ export function uninstallSetup(options) {
191
195
  reverseGitHookEntry(entry, removed, missing, warnings);
192
196
  }
193
197
  }
198
+ sweepLegacyRcBlocks(paths.homeDir, removed, warnings);
194
199
  if (!options.all && !registryRead.malformed && options.keepConfig) {
195
200
  writeRegistryWithLock(paths, {
196
201
  version: 1,
@@ -481,6 +486,37 @@ function stripRcBlock(existing) {
481
486
  const unterminatedPattern = new RegExp(`${escapeRegex(RC_BEGIN)}\\n[\\s\\S]*$`, "g");
482
487
  return existing.replace(pattern, "").replace(unterminatedPattern, "");
483
488
  }
489
+ function sweepLegacyRcBlocks(homeDir, removed, warnings) {
490
+ for (const rel of LEGACY_RC_CANDIDATES) {
491
+ const rcPath = join(homeDir, rel);
492
+ const existing = readText(rcPath);
493
+ if (!existing) {
494
+ continue;
495
+ }
496
+ let next = existing;
497
+ for (const marker of LEGACY_RC_MARKERS) {
498
+ if (!next.includes(marker.begin)) {
499
+ continue;
500
+ }
501
+ if (!next.includes(marker.end)) {
502
+ warnings.push(`legacy dg block in ${rcPath} is missing its end marker; left untouched`);
503
+ continue;
504
+ }
505
+ const pattern = new RegExp(`${escapeRegex(marker.begin)}\\n[\\s\\S]*?${escapeRegex(marker.end)}\\n?`, "g");
506
+ next = next.replace(pattern, "");
507
+ }
508
+ if (next === existing) {
509
+ continue;
510
+ }
511
+ try {
512
+ writeFileSync(rcPath, next, "utf8");
513
+ removed.push(`${rcPath} (legacy dg block)`);
514
+ }
515
+ catch (error) {
516
+ warnings.push(`could not strip legacy dg block from ${rcPath}: ${error instanceof Error ? error.message : "write error"}`);
517
+ }
518
+ }
519
+ }
484
520
  export function cleanupEntry(kind, path, mode, now, sentinel) {
485
521
  return {
486
522
  kind,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Dependency Guardian supply-chain firewall CLI",
5
5
  "type": "module",
6
6
  "bin": {