@produtype/core 1.20.0 → 1.21.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.
@@ -33,15 +33,57 @@ const LOGGING_PACKAGES = [
33
33
  ];
34
34
  /** Elixir reaches for a backend rather than a logger: Logger itself is in OTP. */
35
35
  const ELIXIR_LOGGING_PACKAGES = ['logger_json', 'logger_file_backend', 'sentry'];
36
+ /**
37
+ * The route, in every spelling somebody writes it.
38
+ *
39
+ * `\/(health|healthz|readyz|livez|alive)\b` does not match `/healthcheck`: after
40
+ * `health` comes a `c`, and the boundary fails. Netflix's dispatch declares
41
+ * `@api_router.get("/healthcheck")` and was told it has no health endpoint — the
42
+ * plainest spelling there is, missed by the pattern meant to find it.
43
+ *
44
+ * This does not disturb the decision recorded below about Dropwizard. That one is
45
+ * about an endpoint the *framework* publishes, which no Dropwizard application writes
46
+ * in its own source; a line like dispatch's is somebody declaring the route
47
+ * themselves.
48
+ */
49
+ const HEALTH_ROUTE = /\/(health|healthz|healthcheck|health[-_]check|readyz|livez|alive)\b/i;
36
50
  async function detectObservability(ctx) {
37
51
  const evidence = [];
38
- const logDeps = [...(0, detectContext_1.hasAnyDep)(ctx, LOGGING_PACKAGES), ...(0, detectContext_1.hasAnyElixirDep)(ctx, ELIXIR_LOGGING_PACKAGES)];
52
+ /**
53
+ * .NET names its logging in the project file and nothing here was reading it.
54
+ *
55
+ * Radarr declares `NLog`, `NLog.Extensions.Logging` and
56
+ * `NLog.Layouts.ClefJsonLayout` — CLEF being the compact JSON event format — and
57
+ * was reported as logging something but not structurally. The npm list has had
58
+ * winston and pino since the beginning and treats the dependency alone as enough;
59
+ * these are the same statement in another manifest.
60
+ */
61
+ const DOTNET_LOGGING_PACKAGES = ['NLog', 'NLog.Extensions.Logging', 'Serilog', 'Serilog.AspNetCore', 'Microsoft.Extensions.Logging', 'log4net'];
62
+ const logDeps = [
63
+ ...(0, detectContext_1.hasAnyDep)(ctx, LOGGING_PACKAGES),
64
+ ...(0, detectContext_1.hasAnyElixirDep)(ctx, ELIXIR_LOGGING_PACKAGES),
65
+ ...(0, detectContext_1.hasAnyDotnetDep)(ctx, DOTNET_LOGGING_PACKAGES),
66
+ ];
39
67
  const sentryDeps = (0, detectContext_1.hasAnyDep)(ctx, ['@sentry/node', 'sentry-sdk']);
40
68
  for (const d of logDeps)
41
69
  evidence.push({ type: 'dependency', value: d, claim: 'logging' });
42
70
  for (const d of sentryDeps)
43
71
  evidence.push({ type: 'dependency', value: d, claim: 'logging' });
44
- const hits = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [/\/(health|healthz|readyz|livez|alive)\b/i, /x-request-id/i, /correlation-id/i, /error\s*handler/i, /RotatingFileHandler/i], 25);
72
+ /**
73
+ * An import path is not a route.
74
+ *
75
+ * `import Health from 'typings/Health';` is a TypeScript type in Radarr's frontend,
76
+ * and `/Health'` matches the route pattern exactly as `/health` in a URL does. It
77
+ * was the evidence behind Radarr's passing health check — a false pass, which is
78
+ * the direction that raises a score rather than lowering one.
79
+ *
80
+ * Imports are good evidence elsewhere and are left alone there; this is the search
81
+ * for a *route*, and a module specifier is never one. Filtered inside the search so
82
+ * that a frontend full of them cannot spend the budget before a real route is
83
+ * reached.
84
+ */
85
+ const IMPORT_LINE = /^\s*(?:import\b|from\s+['"]|const\s+\{?[\w\s,}]*\}?\s*=\s*require\()/;
86
+ const hits = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [HEALTH_ROUTE, /x-request-id/i, /correlation-id/i, /error\s*handler/i, /RotatingFileHandler/i], 25, (match) => !IMPORT_LINE.test(match.snippet));
45
87
  /**
46
88
  * One search, three answers, so the claim is decided per hit rather than per search.
47
89
  *
@@ -50,7 +92,7 @@ async function detectObservability(ctx) {
50
92
  * is what the line is evidence of.
51
93
  */
52
94
  for (const m of hits) {
53
- const claim = /\/(health|healthz|readyz|livez|alive)\b/i.test(m.snippet)
95
+ const claim = HEALTH_ROUTE.test(m.snippet)
54
96
  ? 'health'
55
97
  : /x-request-id|correlation-id/i.test(m.snippet)
56
98
  ? 'request-id'
@@ -116,7 +158,7 @@ async function detectObservability(ctx) {
116
158
  const hasHealth = declaredProbes.length > 0
117
159
  || healthFiles.length > 0
118
160
  || actuator.length > 0
119
- || hits.some((h) => /\/(health|healthz|readyz|livez|alive)\b/i.test(h.snippet));
161
+ || hits.some((h) => HEALTH_ROUTE.test(h.snippet));
120
162
  const hasReqId = hits.some((h) => /x-request-id|correlation-id/i.test(h.snippet));
121
163
  // Structured logging without a logging library is still structured logging. What
122
164
  // matters is that entries are machine-readable and correlated, not which package
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "1.20.0",
3
+ "version": "1.21.1",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {