@designesy/score 0.7.0 → 0.7.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @designesy/score
2
2
 
3
- Standalone 44-check design-contract scoring engine. Fetches a URL, extracts CSS + `:root` tokens, runs all checks locally, with **no server required**. Zero dependencies.
3
+ Standalone 44-check design-contract scoring engine. Fetches a URL, extracts CSS + `:root` tokens, runs all checks locally, with **no server required**. No required dependencies.
4
4
 
5
5
  ## Install
6
6
 
@@ -90,12 +90,14 @@ Plus 12 anti-slop deductions (S1 to S12) and 7 originality lifts (O1 to O7).
90
90
 
91
91
  Auto-detect: `designesy.org` → contract, everything else → universal.
92
92
 
93
- ## Zero dependencies
93
+ ## Dependencies
94
94
 
95
- Uses only Node.js built-ins:
95
+ The engine uses only Node.js built-ins:
96
96
  - `node:https` for URL fetching (avoids the Windows libuv/undici crash)
97
97
  - `node:dns` for SSRF guard (DNS resolution validation)
98
- - No `ipaddr.js`, no `undici`, no `fetch()`, no external packages
98
+ - No `ipaddr.js`, no `undici`, no `fetch()`
99
+
100
+ One optional dependency: Google's [`@google/design.md`](https://github.com/google-labs-code/design.md) linter (Apache-2.0), the version the hosted engine runs. v37 uses it to lint a site's `/DESIGN.md`. npm installs it by default, `npx` included. Installed without it (`npm install --omit=optional`), v37 reports MANUAL (not run, weight 0) for a site that serves a DESIGN.md, and every other check is unchanged.
99
101
 
100
102
  ## SSRF guard
101
103
 
package/dist/engine.d.ts CHANGED
@@ -6,8 +6,9 @@
6
6
  * browser), computes a weighted score (0-100), assigns a letter grade (A-F),
7
7
  * and applies anti-slop deductions + originality lifts.
8
8
  *
9
- * Zero dependencies. Uses node:https (not fetch/undici) to avoid the Windows
10
- * libuv crash during process exit.
9
+ * No required dependencies. Uses node:https (not fetch/undici) to avoid the
10
+ * Windows libuv crash during process exit. One optional dependency,
11
+ * @google/design.md, lints /DESIGN.md for v37; without it v37 is MANUAL.
11
12
  *
12
13
  * Extracted from apps/site/app/api/score/route.ts — same engine, same checks,
13
14
  * same scoring math. The API route wraps this module; the CLI calls it directly.
package/dist/engine.js CHANGED
@@ -6,8 +6,9 @@
6
6
  * browser), computes a weighted score (0-100), assigns a letter grade (A-F),
7
7
  * and applies anti-slop deductions + originality lifts.
8
8
  *
9
- * Zero dependencies. Uses node:https (not fetch/undici) to avoid the Windows
10
- * libuv crash during process exit.
9
+ * No required dependencies. Uses node:https (not fetch/undici) to avoid the
10
+ * Windows libuv crash during process exit. One optional dependency,
11
+ * @google/design.md, lints /DESIGN.md for v37; without it v37 is MANUAL.
11
12
  *
12
13
  * Extracted from apps/site/app/api/score/route.ts — same engine, same checks,
13
14
  * same scoring math. The API route wraps this module; the CLI calls it directly.
@@ -3735,7 +3736,7 @@ async function checkDesignMdSpec(targetUrl) {
3735
3736
  // package missing from THIS runtime, so the same page scored lower from the
3736
3737
  // npm CLI than from the API. Unverified-here is what MANUAL means (v02,
3737
3738
  // v04, v21): weight 0, resolved by a run that has the linter.
3738
- const msg = e instanceof Error ? e.message : 'unknown error';
3739
+ const msg = sanitizeErrorText(e instanceof Error ? e.message : 'unknown error');
3739
3740
  return { id: 'v37', item: ITEM, category: CATEGORY, status: 'MANUAL', detail: `/DESIGN.md fetched but linter unavailable: ${msg}. The @google/design.md package may not be installed in this runtime; run the full audit to resolve.` };
3740
3741
  }
3741
3742
  try {
@@ -3753,10 +3754,35 @@ async function checkDesignMdSpec(targetUrl) {
3753
3754
  // Distinct from the branch above: the linter LOADED and then threw, so the
3754
3755
  // file's format is unsupported rather than the runtime being incomplete.
3755
3756
  // Both WARN, but the messages point at different fixes.
3756
- const msg = e instanceof Error ? e.message : 'unknown error';
3757
+ const msg = sanitizeErrorText(e instanceof Error ? e.message : 'unknown error');
3757
3758
  return { id: 'v37', item: ITEM, category: CATEGORY, status: 'WARN', detail: `/DESIGN.md fetched but lint failed: ${msg}. The file may use a format version the linter doesn't support yet.` };
3758
3759
  }
3759
3760
  }
3761
+ // ── Error text in a result, shared by both engine copies ───────────────────
3762
+ // A caught error's message carries the file system of the machine that ran
3763
+ // the engine: Node's "Cannot find package '@google/design.md' imported from
3764
+ // <path>" gives the full path of the npm cache the CLI ran from, under the
3765
+ // Users folder of the account that ran it, and a result is read by people
3766
+ // other than that account. Every check detail passes through
3767
+ // sanitizeErrorText before it is returned. It replaces absolute paths (a
3768
+ // drive letter, a UNC share, a POSIX home, temp or system root), file:// URLs
3769
+ // and npm cache segments, and keeps the clause that says what failed
3770
+ // ("Cannot find package '@google/design.md'"). A path segment may hold spaces
3771
+ // (an account name often does); the last one may not, so the sentence after
3772
+ // a path survives, and trailing punctuation is kept.
3773
+ function sanitizeErrorText(text) {
3774
+ const keep = (label) => (m) => `${label}${/[.,;:!?)]+$/.exec(m)?.[0] ?? ''}`;
3775
+ return text
3776
+ .replace(/\bfile:\/\/[^\s'"<>`|]*/gi, keep('a local file'))
3777
+ .replace(/\\\\[^\\\s'"<>`|]+\\(?:[^\\\r\n'"<>`|*?]+\\)*[^\\\s'"<>`|*?]*/g, keep('a local path'))
3778
+ .replace(/(?<![\w\\/])[A-Za-z]:([\\/])(?:[^\\/\r\n'"<>`|*?]+\1)*[^\\/\s'"<>`|*?]*/g, keep('a local path'))
3779
+ .replace(/(^|[\s'"`(=,:[])\/(?:home|Users|root|tmp|var|private|opt|usr|srv|mnt|Volumes|Library|app|vercel|workspace|workspaces|github|runner|nix|snap)(?:\/[^/\r\n'"<>`|]+(?=\/))*\/[^/\s'"<>`|]+/g, (m, lead) => `${lead}${keep('a local path')(m.slice(lead.length))}`)
3780
+ .replace(/[^\s'"<>`|]*(?:npm-cache|[\\/]_npx[\\/]|[\\/]\.npm[\\/])[^\s'"<>`|]*/gi, keep('the npm cache'));
3781
+ }
3782
+ /** The checks with every detail passed through sanitizeErrorText. */
3783
+ function sanitizeCheckDetails(checks) {
3784
+ return checks.map((c) => (typeof c.detail === 'string' ? { ...c, detail: sanitizeErrorText(c.detail) } : c));
3785
+ }
3760
3786
  // ── Score arithmetic, shared by both engine copies ─────────────────────────
3761
3787
  // Everything between the check verdicts and the grade: category weights, the
3762
3788
  // weighted score, the slop deduction, the originality lift, the per-category
@@ -4082,7 +4108,7 @@ export async function scoreFromParts(input) {
4082
4108
  detail: 'not probed: offline fixture run (no network); excluded from the score',
4083
4109
  }
4084
4110
  : await checkDesignMdSpec(targetUrl));
4085
- checks = applyScopeFilter(checks, effectiveScope);
4111
+ checks = sanitizeCheckDetails(applyScopeFilter(checks, effectiveScope));
4086
4112
  const pass = checks.filter((c) => c.status === 'PASS').length;
4087
4113
  const fail = checks.filter((c) => c.status === 'FAIL').length;
4088
4114
  const warn = checks.filter((c) => c.status === 'WARN').length;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designesy/score",
3
- "version": "0.7.0",
4
- "description": "Standalone 44-check design-contract scoring engine: fetches a URL, extracts CSS + tokens, and runs all checks locally. Zero dependencies.",
3
+ "version": "0.7.1",
4
+ "description": "Standalone 44-check design-contract scoring engine: fetches a URL, extracts CSS + tokens, and runs all checks locally. No required dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Designesy <hello@designesy.org>",
7
7
  "homepage": "https://www.designesy.org/score",
@@ -43,6 +43,9 @@
43
43
  "engines": {
44
44
  "node": ">=18.0.0"
45
45
  },
46
+ "optionalDependencies": {
47
+ "@google/design.md": "0.4.0"
48
+ },
46
49
  "devDependencies": {
47
50
  "@types/node": "^26.2.0",
48
51
  "typescript": "^5.7.0"