guard-my-design-system 1.0.2 → 1.1.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
@@ -8,6 +8,16 @@ A pull request guard that checks **only the lines a change adds**, says nothing
8
8
  about the past, and points each new sin at the on-system value the author
9
9
  probably meant. Old wiring is not its business; new sparks are.
10
10
 
11
+ It speaks in nearests, not scoldings:
12
+
13
+ > `Card.tsx:24` — new colour `#4a7be8`. Nearest token: `#3b6fe0`.
14
+ >
15
+ > `site.css:31` — new spacing value `13px`. Nearest existing value: `12px`.
16
+ >
17
+ > `site.css:33` — new typeface `Comic Sans MS`. First typeface declared in this codebase.
18
+ >
19
+ > `site.css:35` — `!important`. The cascade admitting defeat; raise specificity or fix the source order.
20
+
11
21
  It learns your system by scanning your repo with the
12
22
  [roast-my-design-system](https://github.com/gregkozakiewicz/roast-my-design-system)
13
23
  engine, then judges the diff against what it learned. No config files, no rules
@@ -83,7 +93,7 @@ Judge your uncommitted work before anyone else sees it:
83
93
  npx guard-my-design-system@latest
84
94
  ```
85
95
 
86
- | Command | What you get |
96
+ | Command                                                                                      | What you get |
87
97
  |---|---|
88
98
  | <code>npx&nbsp;guard-my-design-system@latest</code> | Your working tree's added lines judged against main, in the terminal |
89
99
  | <code>npx&nbsp;guard-my-design-system@latest&nbsp;&lt;path&gt;</code> | Judge a different repo than the current directory |
@@ -138,6 +148,6 @@ by Greg Kozakiewicz*.
138
148
 
139
149
  **guard-my-design-system**™ and the GK mark are trademarks of Greg Kozakiewicz.
140
150
  Forking is welcome, republishing under this name is not: see
141
- [brand and attribution](https://gregkozakiewicz.github.io/roast-my-design-system/brand.html).
151
+ [brand and attribution](https://gregkozakiewicz.github.io/guard-my-design-system/brand.html).
142
152
 
143
153
  Built and designed by <a href="https://gregkozakiewicz.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/gk-mark-dark.png?v=3.10.1"><img src="https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/gk-mark.png?v=3.10.1" height="15" alt="GK mark"></picture> Greg Kozakiewicz</a>.
package/index.mjs CHANGED
@@ -13,6 +13,8 @@
13
13
  * --strict, 2 could not run.
14
14
  */
15
15
  import { resolve } from 'node:path';
16
+ import { readFileSync } from 'node:fs';
17
+ import { execFileSync } from 'node:child_process';
16
18
  import { learnSystem } from 'roast-my-design-system/engine';
17
19
  import { defaultBase, addedLines } from './src/diff.mjs';
18
20
  import { judge } from './src/judge.mjs';
@@ -25,7 +27,22 @@ const opt = (name) => {
25
27
  return i > -1 && argv[i + 1] ? argv[i + 1] : null;
26
28
  };
27
29
 
30
+ if (flag('version') || argv.includes('-v')) {
31
+ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
32
+ console.log(`guard-my-design-system ${pkg.version}`);
33
+ process.exit(0);
34
+ }
35
+
28
36
  const cwd = resolve(argv[0] && !argv[0].startsWith('--') ? argv[0] : '.');
37
+
38
+ // The guard judges diffs, so a git repository is its whole world. Say so
39
+ // calmly instead of letting git's own fatals leak through.
40
+ try {
41
+ execFileSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd, stdio: 'ignore' });
42
+ } catch {
43
+ console.error('guard: this is not a git repository, and the guard judges diffs. Run it inside a repo.');
44
+ process.exit(2);
45
+ }
29
46
  const base = opt('base') ?? defaultBase(cwd);
30
47
  if (!base) {
31
48
  console.error('guard: cannot find a base to diff against (no origin/main, origin/master, main or master). Pass one with --base <ref>.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guard-my-design-system",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Your design system dies one pull request at a time. This makes sure it doesn't. A guard that judges only the lines a change adds, against the system the repo already has, and names the on-system value the author probably meant.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "type": "git",
38
38
  "url": "git+https://github.com/gregkozakiewicz/guard-my-design-system.git"
39
39
  },
40
- "homepage": "https://github.com/gregkozakiewicz/guard-my-design-system#readme",
40
+ "homepage": "https://gregkozakiewicz.github.io/guard-my-design-system/",
41
41
  "bugs": {
42
42
  "url": "https://github.com/gregkozakiewicz/guard-my-design-system/issues"
43
43
  },
package/src/diff.mjs CHANGED
@@ -11,7 +11,7 @@ import { join } from 'node:path';
11
11
  export function defaultBase(cwd) {
12
12
  for (const ref of ['origin/main', 'origin/master', 'main', 'master']) {
13
13
  try {
14
- return execFileSync('git', ['merge-base', 'HEAD', ref], { cwd, encoding: 'utf8' }).trim();
14
+ return execFileSync('git', ['merge-base', 'HEAD', ref], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
15
15
  } catch { /* try the next spelling */ }
16
16
  }
17
17
  return null;
package/src/judge.mjs CHANGED
@@ -13,8 +13,20 @@ import {
13
13
  } from 'roast-my-design-system/engine';
14
14
 
15
15
  // Same honesty exemptions the engine applies: email and print styling must be
16
- // inline, and artwork files carry hex that is drawing, not styling.
17
- const EXEMPT_FILE_RE = /email|(^|[/.])print([/.]|$)|(^|\/)[\w.-]*(icon|logo|badge|illustration|artwork)[\w.-]*\.(tsx|jsx)$/i;
16
+ // inline, always. Artwork-named files (Icon, Logo, Badge…) are exempt only
17
+ // when their added lines actually draw SVG — a Badge component that is plain
18
+ // styled UI gets guarded like everything else.
19
+ const EMAIL_PRINT_RE = /email|(^|[/.])print([/.]|$)/i;
20
+ const ARTWORK_NAME_RE = /(^|\/)[\w.-]*(icon|logo|badge|illustration|artwork)[\w.-]*\.(tsx|jsx)$/i;
21
+ const SVG_MARKUP_RE = /<(svg|path|rect|circle|ellipse|polygon|defs|mask)\b/i;
22
+
23
+ function exemptFiles(added) {
24
+ const svgish = new Set();
25
+ for (const { file, text } of added) {
26
+ if (ARTWORK_NAME_RE.test(file) && SVG_MARKUP_RE.test(text)) svgish.add(file);
27
+ }
28
+ return (file) => EMAIL_PRINT_RE.test(file) || svgish.has(file);
29
+ }
18
30
 
19
31
  const FONT_LINE_RE = /font-family\s*:\s*([^;{}]+)/i;
20
32
 
@@ -29,9 +41,10 @@ export function judge(added, system) {
29
41
  // The system was learned from the tree that already CONTAINS these added
30
42
  // lines, so a new value would vouch for itself. A value is only "known"
31
43
  // if the repo uses it more times than this change added it.
44
+ const exempt = exemptFiles(added);
32
45
  const addedLengths = new Map(), addedFaces = new Map();
33
46
  for (const { file, line, text } of added) {
34
- if (EXEMPT_FILE_RE.test(file)) continue;
47
+ if (exempt(file)) continue;
35
48
  const css = isStyleFile(file);
36
49
  if (!css && !isCodeFile(file)) continue;
37
50
  for (const s of extractStyling(text, { css }).spacing) {
@@ -55,7 +68,7 @@ export function judge(added, system) {
55
68
  const findings = [];
56
69
 
57
70
  for (const { file, line, text } of added) {
58
- if (EXEMPT_FILE_RE.test(file)) continue;
71
+ if (exempt(file)) continue;
59
72
  const css = isStyleFile(file);
60
73
  if (!css && !isCodeFile(file)) continue;
61
74
 
@@ -111,5 +124,15 @@ export function judge(added, system) {
111
124
  }
112
125
  }
113
126
 
114
- return findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
127
+ // Two extractors can see the same value on the same line (a hex inside a
128
+ // Tailwind class is also a hex in the raw sweep). One sin, one line.
129
+ const seen = new Set();
130
+ const deduped = findings.filter((f) => {
131
+ const key = `${f.file}|${f.line}|${f.kind}|${f.value}`;
132
+ if (seen.has(key)) return false;
133
+ seen.add(key);
134
+ return true;
135
+ });
136
+
137
+ return deduped.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
115
138
  }