ng-health 0.1.0 → 0.2.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 CHANGED
@@ -155,6 +155,7 @@ All types are exported: `AuditResult`, `AuditOptions`, `Finding`, `Check`, `Chec
155
155
  | `dependencies/outdated` | dependencies | Major / minor / patch gaps of direct dependencies; TypeScript and Angular-lockstep packages (`@ngrx/*`, `@angular-architects/*`, angular-eslint) are judged against your Angular version | |
156
156
  | `dependencies/vulnerabilities` | dependencies | `npm`/`pnpm audit` results by severity, with the fix for each | |
157
157
  | `dependencies/deprecated` | dependencies | Deprecated packages (npm registry plus a built-in list: tslint, protractor…) | |
158
+ | `dependencies/peer-conflicts` | dependencies | Direct dependencies whose peer dependencies are missing or out of range (the cause of npm `ERESOLVE` / `--legacy-peer-deps`), with the newest compatible version when online | |
158
159
  | `bundle/size` | bundle | Initial/lazy sizes vs `angular.json` budgets, the 10 largest modules | `build`, `maxLazyChunkKb` (250), `project`, `outputPath` |
159
160
  | `angular/onpush` | angular | Components without OnPush (or opting out with `Eager` on Angular 22+) | |
160
161
  | `angular/template-calls` | angular | Method calls in template bindings (zero-argument signal reads are fine) | |
@@ -52,7 +52,7 @@ function errorMessage(err) {
52
52
  // package.json
53
53
  var package_default = {
54
54
  name: "ng-health",
55
- version: "0.1.0",
55
+ version: "0.2.0",
56
56
  description: "Audit Angular codebases and produce a scored, actionable health report. CLI and library.",
57
57
  keywords: [
58
58
  "angular",
@@ -109,7 +109,7 @@ var package_default = {
109
109
  prepublishOnly: "npm run build",
110
110
  changeset: "changeset",
111
111
  "version-packages": "changeset version",
112
- release: "npm run build && changeset publish",
112
+ release: "npm run build && node scripts/stage-release.mjs",
113
113
  smoke: "node scripts/smoke.mjs"
114
114
  },
115
115
  dependencies: {
@@ -197,7 +197,7 @@ function buildReport(input) {
197
197
  }
198
198
 
199
199
  // src/core/audit.ts
200
- import path20 from "path";
200
+ import path21 from "path";
201
201
 
202
202
  // src/runtime/session.ts
203
203
  import path3 from "path";
@@ -1838,6 +1838,127 @@ async function installedOrDeclared(ctx, name, pkg) {
1838
1838
  return min?.version;
1839
1839
  }
1840
1840
 
1841
+ // src/checks/dependencies/peer-conflicts.ts
1842
+ import { realpath } from "fs/promises";
1843
+ import path10 from "path";
1844
+ import semver3 from "semver";
1845
+ var satisfies = (version, range) => semver3.satisfies(version, range, { includePrerelease: true, loose: true });
1846
+ async function resolveInstalled(fromDir, name) {
1847
+ for (let dir = fromDir; ; dir = path10.dirname(dir)) {
1848
+ if (path10.basename(dir) !== "node_modules") {
1849
+ const manifest = await readJsonFile(
1850
+ path10.join(dir, "node_modules", name, "package.json")
1851
+ ).catch(() => void 0);
1852
+ if (manifest?.version) return manifest;
1853
+ }
1854
+ if (path10.dirname(dir) === dir) return void 0;
1855
+ }
1856
+ }
1857
+ async function findPeerConflicts(ctx) {
1858
+ const conflicts = [];
1859
+ for (const dep of directDependencies(await readPackageJson(ctx))) {
1860
+ const pkgDir = path10.join(ctx.root, "node_modules", dep.name);
1861
+ const manifest = await readJsonFile(path10.join(pkgDir, "package.json")).catch(
1862
+ () => void 0
1863
+ );
1864
+ if (!manifest?.version || !manifest.peerDependencies) continue;
1865
+ const realDir = await realpath(pkgDir).catch(() => pkgDir);
1866
+ for (const [peer, range] of Object.entries(manifest.peerDependencies)) {
1867
+ if (!semver3.validRange(range, { loose: true })) continue;
1868
+ const optional = manifest.peerDependenciesMeta?.[peer]?.optional === true;
1869
+ const installed = await resolveInstalled(realDir, peer);
1870
+ if (!installed?.version) {
1871
+ if (!optional)
1872
+ conflicts.push({
1873
+ name: dep.name,
1874
+ version: manifest.version,
1875
+ peer,
1876
+ range,
1877
+ installed: void 0,
1878
+ optional
1879
+ });
1880
+ continue;
1881
+ }
1882
+ if (!satisfies(installed.version, range)) {
1883
+ conflicts.push({
1884
+ name: dep.name,
1885
+ version: manifest.version,
1886
+ peer,
1887
+ range,
1888
+ installed: installed.version,
1889
+ optional
1890
+ });
1891
+ }
1892
+ }
1893
+ }
1894
+ return conflicts.sort((a, b) => a.name.localeCompare(b.name) || a.peer.localeCompare(b.peer));
1895
+ }
1896
+ function compatibleVersion(versions, installedPeers) {
1897
+ return versions.filter((v) => semver3.valid(v.version) && !semver3.prerelease(v.version)).filter(
1898
+ (v) => Object.entries(v.peerDependencies ?? {}).every(([peer, range]) => {
1899
+ const have = installedPeers[peer];
1900
+ return have === void 0 || !semver3.validRange(range, { loose: true }) || satisfies(have, range);
1901
+ })
1902
+ ).map((v) => v.version).sort(semver3.rcompare)[0];
1903
+ }
1904
+ async function suggestVersion(ctx, conflict, installedPeers) {
1905
+ if (ctx.offline) return void 0;
1906
+ try {
1907
+ const r = await ctx.exec("npm", [
1908
+ "view",
1909
+ `${conflict.name}@>=0.0.0`,
1910
+ "version",
1911
+ "peerDependencies",
1912
+ "--json"
1913
+ ]);
1914
+ const parsed = r.exitCode === 0 ? parseJsonOutput(r.stdout) : void 0;
1915
+ const list = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
1916
+ return compatibleVersion(
1917
+ list,
1918
+ installedPeers
1919
+ );
1920
+ } catch (err) {
1921
+ ctx.logger.debug(`npm view ${conflict.name} failed: ${String(err)}`);
1922
+ return void 0;
1923
+ }
1924
+ }
1925
+ var severityFor = (c) => {
1926
+ if (c.optional) return "low";
1927
+ return /^(@angular\/|rxjs$|typescript$|zone\.js$)/.test(c.peer) ? "high" : "medium";
1928
+ };
1929
+ var peer_conflicts_default = defineCheck({
1930
+ id: "dependencies/peer-conflicts",
1931
+ name: "Peer dependency conflicts",
1932
+ category: "dependencies",
1933
+ description: "Direct dependencies whose peer dependencies are missing or outside the required range (the cause of npm ERESOLVE errors and --legacy-peer-deps installs).",
1934
+ timeoutMs: 12e4,
1935
+ async run(ctx) {
1936
+ if (!await pathExists(path10.join(ctx.root, "node_modules"))) {
1937
+ skipCheck(
1938
+ "Dependencies are not installed (run npm install first): peer ranges are read from node_modules"
1939
+ );
1940
+ }
1941
+ const conflicts = await findPeerConflicts(ctx);
1942
+ const installedPeers = {};
1943
+ for (const c of conflicts) if (c.installed) installedPeers[c.peer] = c.installed;
1944
+ const findings = [];
1945
+ for (const c of conflicts) {
1946
+ const suggestion = await suggestVersion(ctx, c, installedPeers);
1947
+ const problem = c.installed ? `${c.name} ${c.version} requires ${c.peer} ${c.range}, but ${c.installed} is installed.` : `${c.name} ${c.version} requires ${c.peer} ${c.range}, which is not installed.`;
1948
+ const fix = suggestion ? suggestion === c.version ? `Reinstall to fix the tree, or align ${c.peer} with ${c.range}.` : `Install a compatible version: npm install ${c.name}@${suggestion} (or upgrade ${c.peer} to ${c.range}).` : c.installed ? `Install a ${c.name} version whose peer range accepts ${c.peer} ${c.installed}, or upgrade ${c.peer} to match ${c.range}.` : `Install ${c.peer} (${c.range}) or remove ${c.name}.`;
1949
+ findings.push({
1950
+ severity: severityFor(c),
1951
+ ruleId: c.installed ? "peer-version-mismatch" : "peer-missing",
1952
+ message: problem,
1953
+ fixHint: `${fix} Mismatched peers make npm install fail with ERESOLVE and are often hidden with --legacy-peer-deps.`,
1954
+ file: "package.json",
1955
+ docsUrl: "https://docs.npmjs.com/cli/configuring-npm/package-json#peerdependencies"
1956
+ });
1957
+ }
1958
+ return { findings, meta: { conflicts } };
1959
+ }
1960
+ });
1961
+
1841
1962
  // src/checks/dependencies/vulnerabilities.ts
1842
1963
  var SEVERITY = {
1843
1964
  critical: "critical",
@@ -1915,7 +2036,7 @@ var vulnerabilities_default = defineCheck({
1915
2036
  });
1916
2037
 
1917
2038
  // src/checks/hygiene/coverage.ts
1918
- import path10 from "path";
2039
+ import path11 from "path";
1919
2040
  import fg from "fast-glob";
1920
2041
  function lcovLinePct(text) {
1921
2042
  let found = 0;
@@ -1942,7 +2063,7 @@ var coverage_default = defineCheck({
1942
2063
  let pct2;
1943
2064
  let source;
1944
2065
  if (summaryFile) {
1945
- const raw = (await readJsonFile(path10.join(ctx.root, summaryFile)))?.total?.lines?.pct;
2066
+ const raw = (await readJsonFile(path11.join(ctx.root, summaryFile)))?.total?.lines?.pct;
1946
2067
  pct2 = typeof raw === "number" ? raw : raw !== void 0 && !Number.isNaN(Number(raw)) ? Number(raw) : void 0;
1947
2068
  source = summaryFile;
1948
2069
  }
@@ -2012,7 +2133,7 @@ var tests_default = defineCheck({
2012
2133
  });
2013
2134
 
2014
2135
  // src/checks/hygiene/tooling.ts
2015
- import path11 from "path";
2136
+ import path12 from "path";
2016
2137
  var ESLINT_FILES = [
2017
2138
  "eslint.config.js",
2018
2139
  "eslint.config.mjs",
@@ -2041,7 +2162,7 @@ var PRETTIER_FILES = [
2041
2162
  "prettier.config.ts"
2042
2163
  ];
2043
2164
  async function firstExisting(root, files) {
2044
- for (const f of files) if (await pathExists(path11.join(root, f))) return f;
2165
+ for (const f of files) if (await pathExists(path12.join(root, f))) return f;
2045
2166
  return void 0;
2046
2167
  }
2047
2168
  var tooling_default = defineCheck({
@@ -2050,12 +2171,12 @@ var tooling_default = defineCheck({
2050
2171
  category: "hygiene",
2051
2172
  description: "ESLint (with angular-eslint) and Prettier are configured.",
2052
2173
  async run(ctx) {
2053
- const pkg = await readJsonFile(path11.join(ctx.root, "package.json")) ?? {};
2174
+ const pkg = await readJsonFile(path12.join(ctx.root, "package.json")) ?? {};
2054
2175
  const deps = await readPackageJson(ctx);
2055
2176
  const all = { ...deps.dependencies, ...deps.devDependencies };
2056
2177
  const eslint = await firstExisting(ctx.root, ESLINT_FILES) ?? ("eslintConfig" in pkg ? "package.json" : void 0);
2057
2178
  const prettier = await firstExisting(ctx.root, PRETTIER_FILES) ?? ("prettier" in pkg ? "package.json" : void 0);
2058
- const tslint = await pathExists(path11.join(ctx.root, "tslint.json"));
2179
+ const tslint = await pathExists(path12.join(ctx.root, "tslint.json"));
2059
2180
  const angularEslint = Object.keys(all).some(
2060
2181
  (d) => d === "angular-eslint" || d.startsWith("@angular-eslint/")
2061
2182
  );
@@ -2104,8 +2225,8 @@ var tooling_default = defineCheck({
2104
2225
  });
2105
2226
 
2106
2227
  // src/checks/hygiene/typescript-strict.ts
2107
- import path12 from "path";
2108
- import semver3 from "semver";
2228
+ import path13 from "path";
2229
+ import semver4 from "semver";
2109
2230
  var STRICT_FAMILY = [
2110
2231
  "noImplicitAny",
2111
2232
  "strictNullChecks",
@@ -2122,7 +2243,7 @@ async function loadTsConfig(file, depth = 0) {
2122
2243
  const parents = (Array.isArray(config.extends) ? config.extends : config.extends ? [config.extends] : []).filter((e) => e.startsWith("."));
2123
2244
  let merged = {};
2124
2245
  for (const parent of parents) {
2125
- const base2 = await loadTsConfig(path12.resolve(path12.dirname(file), parent), depth + 1);
2246
+ const base2 = await loadTsConfig(path13.resolve(path13.dirname(file), parent), depth + 1);
2126
2247
  if (base2) {
2127
2248
  merged = {
2128
2249
  compilerOptions: { ...merged.compilerOptions, ...base2.compilerOptions },
@@ -2140,10 +2261,10 @@ async function loadTsConfig(file, depth = 0) {
2140
2261
  }
2141
2262
  async function typescriptMajor(root, declared) {
2142
2263
  const installed = (await readJsonFile(
2143
- path12.join(root, "node_modules/typescript/package.json")
2264
+ path13.join(root, "node_modules/typescript/package.json")
2144
2265
  ))?.version;
2145
- if (installed && semver3.valid(installed)) return semver3.major(installed);
2146
- const min = declared && semver3.validRange(declared) ? semver3.minVersion(declared) : null;
2266
+ if (installed && semver4.valid(installed)) return semver4.major(installed);
2267
+ const min = declared && semver4.validRange(declared) ? semver4.minVersion(declared) : null;
2147
2268
  return min?.major;
2148
2269
  }
2149
2270
  var typescript_strict_default = defineCheck({
@@ -2156,7 +2277,7 @@ var typescript_strict_default = defineCheck({
2156
2277
  let file;
2157
2278
  let config;
2158
2279
  for (const f of appTsconfig) {
2159
- config = await loadTsConfig(path12.join(ctx.root, f));
2280
+ config = await loadTsConfig(path13.join(ctx.root, f));
2160
2281
  if (config) {
2161
2282
  file = f;
2162
2283
  break;
@@ -2394,6 +2515,7 @@ var builtinChecks = [
2394
2515
  outdated_default,
2395
2516
  vulnerabilities_default,
2396
2517
  deprecated_default,
2518
+ peer_conflicts_default,
2397
2519
  size_default,
2398
2520
  onpush_default,
2399
2521
  template_calls_default,
@@ -2413,10 +2535,10 @@ var builtinChecks = [
2413
2535
  ];
2414
2536
 
2415
2537
  // src/core/baseline.ts
2416
- import path13 from "path";
2538
+ import path14 from "path";
2417
2539
  var delta = (before, after) => before === void 0 || after === void 0 ? void 0 : after - before;
2418
2540
  async function loadBaseline(file) {
2419
- const abs = path13.resolve(file);
2541
+ const abs = path14.resolve(file);
2420
2542
  const raw = await readJsonFile(abs);
2421
2543
  if (!raw) throw new ConfigError(`Baseline report not found: ${abs}`);
2422
2544
  if (typeof raw.schemaVersion !== "string" || !raw.schemaVersion.startsWith("1.")) {
@@ -2467,7 +2589,7 @@ function computeTrend(baseline, current) {
2467
2589
  }
2468
2590
 
2469
2591
  // src/core/config/loader.ts
2470
- import path14 from "path";
2592
+ import path15 from "path";
2471
2593
  function formatZodIssues(error) {
2472
2594
  return error.issues.map((issue) => {
2473
2595
  const where = issue.path.length ? issue.path.join(".") : "(root)";
@@ -2500,7 +2622,7 @@ async function readConfigFile(file) {
2500
2622
  try {
2501
2623
  mod = await importModule(file, { default: true });
2502
2624
  } catch (err) {
2503
- throw new ConfigError(`Could not load ${path14.basename(file)}: ${errorMessage(err)}`, {
2625
+ throw new ConfigError(`Could not load ${path15.basename(file)}: ${errorMessage(err)}`, {
2504
2626
  cause: err
2505
2627
  });
2506
2628
  }
@@ -2512,12 +2634,12 @@ async function loadConfig(projectRoot, source) {
2512
2634
  }
2513
2635
  let file;
2514
2636
  if (source) {
2515
- file = path14.resolve(source);
2637
+ file = path15.resolve(source);
2516
2638
  if (!await pathExists(file)) throw new ConfigError(`Config file not found: ${file}`);
2517
2639
  } else {
2518
2640
  const found = [];
2519
2641
  for (const name of CONFIG_FILE_NAMES) {
2520
- if (await pathExists(path14.join(projectRoot, name))) found.push(name);
2642
+ if (await pathExists(path15.join(projectRoot, name))) found.push(name);
2521
2643
  }
2522
2644
  if (found.length > 1) {
2523
2645
  throw new ConfigError(`Found several config files: ${found.join(", ")}`, {
@@ -2525,19 +2647,19 @@ async function loadConfig(projectRoot, source) {
2525
2647
  });
2526
2648
  }
2527
2649
  if (!found[0]) return { config: parseConfig({}), file: void 0 };
2528
- file = path14.join(projectRoot, found[0]);
2650
+ file = path15.join(projectRoot, found[0]);
2529
2651
  }
2530
- return { config: parseConfig(await readConfigFile(file), path14.basename(file)), file };
2652
+ return { config: parseConfig(await readConfigFile(file), path15.basename(file)), file };
2531
2653
  }
2532
2654
 
2533
2655
  // src/core/context.ts
2534
2656
  import { readFile as readFile3 } from "fs/promises";
2535
- import path16 from "path";
2657
+ import path17 from "path";
2536
2658
  import fg2 from "fast-glob";
2537
2659
 
2538
2660
  // src/core/exec.ts
2539
2661
  import { spawn } from "child_process";
2540
- import path15 from "path";
2662
+ import path16 from "path";
2541
2663
  var exec = (command, args, opts = {}) => new Promise((resolve, reject) => {
2542
2664
  const maxBuffer = opts.maxBuffer ?? 64 * 1024 * 1024;
2543
2665
  const shell = needsShell(command);
@@ -2565,7 +2687,7 @@ var exec = (command, args, opts = {}) => new Promise((resolve, reject) => {
2565
2687
  });
2566
2688
  function needsShell(command) {
2567
2689
  if (process.platform !== "win32") return false;
2568
- return /\.(cmd|bat)$/i.test(command) || !path15.isAbsolute(command);
2690
+ return /\.(cmd|bat)$/i.test(command) || !path16.isAbsolute(command);
2569
2691
  }
2570
2692
  function quoteForCmd(arg) {
2571
2693
  return /[\s^&|<>()%!"]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg;
@@ -2587,12 +2709,12 @@ function createBaseContext(project, config, logger, overrides = {}) {
2587
2709
  runtime: config.runtime,
2588
2710
  onCleanup: (fn) => void cleanups.push(fn),
2589
2711
  glob: (patterns, opts) => fg2(patterns, {
2590
- cwd: opts?.cwd ? path16.resolve(project.root, opts.cwd) : project.root,
2712
+ cwd: opts?.cwd ? path17.resolve(project.root, opts.cwd) : project.root,
2591
2713
  ignore: config.ignore,
2592
2714
  onlyFiles: true,
2593
2715
  dot: false
2594
2716
  }).then((files) => files.sort()),
2595
- readText: (rel) => readFile3(path16.resolve(project.root, rel), "utf8"),
2717
+ readText: (rel) => readFile3(path17.resolve(project.root, rel), "utf8"),
2596
2718
  shared: (key, factory) => {
2597
2719
  let value = cache.get(key);
2598
2720
  if (!value) {
@@ -2615,8 +2737,8 @@ function createBaseContext(project, config, logger, overrides = {}) {
2615
2737
  }
2616
2738
 
2617
2739
  // src/core/detect/index.ts
2618
- import path17 from "path";
2619
- import semver4 from "semver";
2740
+ import path18 from "path";
2741
+ import semver5 from "semver";
2620
2742
  var LOCKFILES = [
2621
2743
  ["pnpm-lock.yaml", "pnpm"],
2622
2744
  ["yarn.lock", "yarn"],
@@ -2631,22 +2753,22 @@ async function detectPackageManager(dir, pkg) {
2631
2753
  if (declared === "npm" || declared === "pnpm" || declared === "yarn" || declared === "bun") {
2632
2754
  return declared;
2633
2755
  }
2634
- let current = path17.resolve(dir);
2756
+ let current = path18.resolve(dir);
2635
2757
  for (; ; ) {
2636
2758
  for (const [file, pm] of LOCKFILES) {
2637
- if (await pathExists(path17.join(current, file))) return pm;
2759
+ if (await pathExists(path18.join(current, file))) return pm;
2638
2760
  }
2639
- if (await pathExists(path17.join(current, ".git"))) break;
2640
- const parent = path17.dirname(current);
2761
+ if (await pathExists(path18.join(current, ".git"))) break;
2762
+ const parent = path18.dirname(current);
2641
2763
  if (parent === current) break;
2642
2764
  current = parent;
2643
2765
  }
2644
2766
  return "npm";
2645
2767
  }
2646
2768
  function majorOf(installed, declared) {
2647
- if (installed && semver4.valid(installed)) return semver4.major(installed);
2648
- if (declared && semver4.validRange(declared)) {
2649
- const min = semver4.minVersion(declared);
2769
+ if (installed && semver5.valid(installed)) return semver5.major(installed);
2770
+ if (declared && semver5.validRange(declared)) {
2771
+ const min = semver5.minVersion(declared);
2650
2772
  if (min) return min.major;
2651
2773
  }
2652
2774
  return void 0;
@@ -2667,7 +2789,7 @@ function toProjects(ws) {
2667
2789
  name,
2668
2790
  type: p.projectType === "library" ? "library" : "application",
2669
2791
  root,
2670
- sourceRoot: p.sourceRoot ?? path17.posix.join(root, "src"),
2792
+ sourceRoot: p.sourceRoot ?? path18.posix.join(root, "src"),
2671
2793
  builder: build2?.builder ?? build2?.executor,
2672
2794
  outputPath: typeof outputPath === "string" ? outputPath : typeof outputPath === "object" && outputPath && "base" in outputPath ? String(outputPath.base) : void 0,
2673
2795
  ssr: isSsrTarget(targets)
@@ -2675,13 +2797,13 @@ function toProjects(ws) {
2675
2797
  });
2676
2798
  }
2677
2799
  async function detectProject(root) {
2678
- const absRoot = path17.resolve(root);
2679
- const pkg = await readJsonFile(path17.join(absRoot, "package.json"));
2800
+ const absRoot = path18.resolve(root);
2801
+ const pkg = await readJsonFile(path18.join(absRoot, "package.json"));
2680
2802
  const deps = { ...pkg?.devDependencies, ...pkg?.dependencies };
2681
2803
  let workspaceFile;
2682
2804
  let ws;
2683
2805
  for (const candidate of ["angular.json", ".angular-cli.json", "workspace.json"]) {
2684
- const file = path17.join(absRoot, candidate);
2806
+ const file = path18.join(absRoot, candidate);
2685
2807
  ws = await readJsonFile(file);
2686
2808
  if (ws) {
2687
2809
  workspaceFile = file;
@@ -2695,20 +2817,20 @@ async function detectProject(root) {
2695
2817
  });
2696
2818
  }
2697
2819
  const installedPkg = await readJsonFile(
2698
- path17.join(absRoot, "node_modules", "@angular", "core", "package.json")
2820
+ path18.join(absRoot, "node_modules", "@angular", "core", "package.json")
2699
2821
  );
2700
2822
  const installed = installedPkg?.version;
2701
2823
  const projects = ws ? toProjects(ws) : [];
2702
2824
  const hasSsrPackage = SSR_PACKAGES.some((p) => p in deps);
2703
- const hasServerTs = await pathExists(path17.join(absRoot, "server.ts"));
2825
+ const hasServerTs = await pathExists(path18.join(absRoot, "server.ts"));
2704
2826
  return {
2705
2827
  root: absRoot,
2706
- name: pkg?.name ?? path17.basename(absRoot),
2828
+ name: pkg?.name ?? path18.basename(absRoot),
2707
2829
  packageManager: await detectPackageManager(absRoot, pkg),
2708
2830
  angular: { declared, installed, major: majorOf(installed, declared) },
2709
2831
  kind: projects.length > 1 ? "workspace" : "single-app",
2710
2832
  workspaceFile,
2711
- nx: await pathExists(path17.join(absRoot, "nx.json")),
2833
+ nx: await pathExists(path18.join(absRoot, "nx.json")),
2712
2834
  projects,
2713
2835
  defaultProject: ws?.defaultProject ?? projects.find((p) => p.type === "application")?.name ?? projects[0]?.name,
2714
2836
  ssr: projects.some((p) => p.ssr) || hasSsrPackage || hasServerTs
@@ -2749,10 +2871,10 @@ var silentLogger = {
2749
2871
  };
2750
2872
 
2751
2873
  // src/core/options.ts
2752
- import path18 from "path";
2874
+ import path19 from "path";
2753
2875
  function resolveOptions(projectPath, options, config, configFile, cwd = process.cwd()) {
2754
2876
  return {
2755
- projectPath: path18.resolve(cwd, projectPath),
2877
+ projectPath: path19.resolve(cwd, projectPath),
2756
2878
  only: options.only?.length ? options.only : config.only,
2757
2879
  skip: [.../* @__PURE__ */ new Set([...config.skip, ...options.skip ?? []])],
2758
2880
  build: options.build !== false,
@@ -2772,8 +2894,8 @@ function resolveRuntime(options, config, configFile, cwd) {
2772
2894
  throw new ConfigError(`Invalid url: ${options.url}`);
2773
2895
  }
2774
2896
  }
2775
- const configDir = configFile ? path18.dirname(configFile) : cwd;
2776
- const loginScript = options.loginScript ? path18.resolve(cwd, options.loginScript) : config.runtime.loginScript ? path18.resolve(configDir, config.runtime.loginScript) : void 0;
2897
+ const configDir = configFile ? path19.dirname(configFile) : cwd;
2898
+ const loginScript = options.loginScript ? path19.resolve(cwd, options.loginScript) : config.runtime.loginScript ? path19.resolve(configDir, config.runtime.loginScript) : void 0;
2777
2899
  return {
2778
2900
  ...config.runtime,
2779
2901
  ...options.url ? { url: options.url } : {},
@@ -2784,7 +2906,7 @@ function resolveRuntime(options, config, configFile, cwd) {
2784
2906
 
2785
2907
  // src/core/plugins.ts
2786
2908
  import { createRequire } from "module";
2787
- import path19 from "path";
2909
+ import path20 from "path";
2788
2910
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
2789
2911
  var OPTIONAL_FIELDS = [
2790
2912
  ["description", "string"],
@@ -2834,11 +2956,11 @@ function checksFromModule(mod, spec) {
2834
2956
  }
2835
2957
  var TS_EXTENSIONS = /\.(c|m)?ts$/;
2836
2958
  function resolvePluginSpecifier(spec, baseDir, projectRoot) {
2837
- if (spec.startsWith(".") || path19.isAbsolute(spec)) {
2838
- return pathToFileURL2(path19.resolve(baseDir, spec)).href;
2959
+ if (spec.startsWith(".") || path20.isAbsolute(spec)) {
2960
+ return pathToFileURL2(path20.resolve(baseDir, spec)).href;
2839
2961
  }
2840
2962
  try {
2841
- return pathToFileURL2(createRequire(path19.join(projectRoot, "package.json")).resolve(spec)).href;
2963
+ return pathToFileURL2(createRequire(path20.join(projectRoot, "package.json")).resolve(spec)).href;
2842
2964
  } catch {
2843
2965
  return spec;
2844
2966
  }
@@ -3214,7 +3336,7 @@ async function runAudit(projectPath, options = {}, deps = {}) {
3214
3336
  const logger = options.logger ?? silentLogger;
3215
3337
  const signal = options.signal;
3216
3338
  signal?.throwIfAborted();
3217
- const absPath = path20.resolve(projectPath);
3339
+ const absPath = path21.resolve(projectPath);
3218
3340
  if (!await isDirectory(absPath)) {
3219
3341
  throw new ProjectError(`Project path does not exist or is not a directory: ${absPath}`);
3220
3342
  }
@@ -3225,7 +3347,7 @@ async function runAudit(projectPath, options = {}, deps = {}) {
3225
3347
  const baseline = typeof options.baseline === "string" ? await loadBaseline(options.baseline) : options.baseline;
3226
3348
  const project = await detectProject(absPath);
3227
3349
  options.onProgress?.({ type: "project-detected", project });
3228
- const plugins = await loadPlugins(config.plugins, file ? path20.dirname(file) : absPath, absPath);
3350
+ const plugins = await loadPlugins(config.plugins, file ? path21.dirname(file) : absPath, absPath);
3229
3351
  const allChecks = [...deps.checks ?? builtinChecks, ...plugins];
3230
3352
  validateRegistry(allChecks);
3231
3353
  const selection = selectChecks(allChecks, config, resolved);
@@ -3766,4 +3888,4 @@ export {
3766
3888
  renderJson,
3767
3889
  renderMarkdown
3768
3890
  };
3769
- //# sourceMappingURL=chunk-K5BPGSW7.js.map
3891
+ //# sourceMappingURL=chunk-T4EN6LCY.js.map