@jomae/catalog 0.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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/baseline.d.ts +51 -0
  4. package/dist/baseline.d.ts.map +1 -0
  5. package/dist/baseline.js +123 -0
  6. package/dist/baseline.js.map +1 -0
  7. package/dist/detect.d.ts +81 -0
  8. package/dist/detect.d.ts.map +1 -0
  9. package/dist/detect.js +155 -0
  10. package/dist/detect.js.map +1 -0
  11. package/dist/format.d.ts +20 -0
  12. package/dist/format.d.ts.map +1 -0
  13. package/dist/format.js +45 -0
  14. package/dist/format.js.map +1 -0
  15. package/dist/generated/baseline.d.ts +22 -0
  16. package/dist/generated/baseline.d.ts.map +1 -0
  17. package/dist/generated/baseline.js +240 -0
  18. package/dist/generated/baseline.js.map +1 -0
  19. package/dist/generated/sizes.d.ts +18 -0
  20. package/dist/generated/sizes.d.ts.map +1 -0
  21. package/dist/generated/sizes.js +474 -0
  22. package/dist/generated/sizes.js.map +1 -0
  23. package/dist/index.d.ts +13 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/rules/accordion.d.ts +3 -0
  28. package/dist/rules/accordion.d.ts.map +1 -0
  29. package/dist/rules/accordion.js +50 -0
  30. package/dist/rules/accordion.js.map +1 -0
  31. package/dist/rules/aspect-ratio.d.ts +3 -0
  32. package/dist/rules/aspect-ratio.d.ts.map +1 -0
  33. package/dist/rules/aspect-ratio.js +33 -0
  34. package/dist/rules/aspect-ratio.js.map +1 -0
  35. package/dist/rules/carousel.d.ts +3 -0
  36. package/dist/rules/carousel.d.ts.map +1 -0
  37. package/dist/rules/carousel.js +78 -0
  38. package/dist/rules/carousel.js.map +1 -0
  39. package/dist/rules/color-functions.d.ts +3 -0
  40. package/dist/rules/color-functions.d.ts.map +1 -0
  41. package/dist/rules/color-functions.js +42 -0
  42. package/dist/rules/color-functions.js.map +1 -0
  43. package/dist/rules/container-queries.d.ts +3 -0
  44. package/dist/rules/container-queries.d.ts.map +1 -0
  45. package/dist/rules/container-queries.js +53 -0
  46. package/dist/rules/container-queries.js.map +1 -0
  47. package/dist/rules/dialog.d.ts +3 -0
  48. package/dist/rules/dialog.d.ts.map +1 -0
  49. package/dist/rules/dialog.js +51 -0
  50. package/dist/rules/dialog.js.map +1 -0
  51. package/dist/rules/discrete-transitions.d.ts +3 -0
  52. package/dist/rules/discrete-transitions.d.ts.map +1 -0
  53. package/dist/rules/discrete-transitions.js +57 -0
  54. package/dist/rules/discrete-transitions.js.map +1 -0
  55. package/dist/rules/field-sizing.d.ts +3 -0
  56. package/dist/rules/field-sizing.d.ts.map +1 -0
  57. package/dist/rules/field-sizing.js +37 -0
  58. package/dist/rules/field-sizing.js.map +1 -0
  59. package/dist/rules/fluid-type.d.ts +3 -0
  60. package/dist/rules/fluid-type.d.ts.map +1 -0
  61. package/dist/rules/fluid-type.js +30 -0
  62. package/dist/rules/fluid-type.js.map +1 -0
  63. package/dist/rules/height-animation.d.ts +3 -0
  64. package/dist/rules/height-animation.d.ts.map +1 -0
  65. package/dist/rules/height-animation.js +46 -0
  66. package/dist/rules/height-animation.js.map +1 -0
  67. package/dist/rules/index.d.ts +14 -0
  68. package/dist/rules/index.d.ts.map +1 -0
  69. package/dist/rules/index.js +56 -0
  70. package/dist/rules/index.js.map +1 -0
  71. package/dist/rules/masonry.d.ts +3 -0
  72. package/dist/rules/masonry.d.ts.map +1 -0
  73. package/dist/rules/masonry.js +56 -0
  74. package/dist/rules/masonry.js.map +1 -0
  75. package/dist/rules/popover.d.ts +3 -0
  76. package/dist/rules/popover.d.ts.map +1 -0
  77. package/dist/rules/popover.js +62 -0
  78. package/dist/rules/popover.js.map +1 -0
  79. package/dist/rules/scroll-animations.d.ts +3 -0
  80. package/dist/rules/scroll-animations.d.ts.map +1 -0
  81. package/dist/rules/scroll-animations.js +58 -0
  82. package/dist/rules/scroll-animations.js.map +1 -0
  83. package/dist/rules/scroll-lock.d.ts +3 -0
  84. package/dist/rules/scroll-lock.d.ts.map +1 -0
  85. package/dist/rules/scroll-lock.js +40 -0
  86. package/dist/rules/scroll-lock.js.map +1 -0
  87. package/dist/rules/scrollbars.d.ts +3 -0
  88. package/dist/rules/scrollbars.d.ts.map +1 -0
  89. package/dist/rules/scrollbars.js +44 -0
  90. package/dist/rules/scrollbars.js.map +1 -0
  91. package/dist/rules/select.d.ts +3 -0
  92. package/dist/rules/select.d.ts.map +1 -0
  93. package/dist/rules/select.js +54 -0
  94. package/dist/rules/select.js.map +1 -0
  95. package/dist/rules/smooth-scroll.d.ts +3 -0
  96. package/dist/rules/smooth-scroll.d.ts.map +1 -0
  97. package/dist/rules/smooth-scroll.js +41 -0
  98. package/dist/rules/smooth-scroll.js.map +1 -0
  99. package/dist/rules/sticky.d.ts +3 -0
  100. package/dist/rules/sticky.d.ts.map +1 -0
  101. package/dist/rules/sticky.js +39 -0
  102. package/dist/rules/sticky.js.map +1 -0
  103. package/dist/rules/text-box-trim.d.ts +3 -0
  104. package/dist/rules/text-box-trim.d.ts.map +1 -0
  105. package/dist/rules/text-box-trim.js +32 -0
  106. package/dist/rules/text-box-trim.js.map +1 -0
  107. package/dist/rules/text-wrap-balance.d.ts +3 -0
  108. package/dist/rules/text-wrap-balance.d.ts.map +1 -0
  109. package/dist/rules/text-wrap-balance.js +34 -0
  110. package/dist/rules/text-wrap-balance.js.map +1 -0
  111. package/dist/rules/view-transitions.d.ts +3 -0
  112. package/dist/rules/view-transitions.d.ts.map +1 -0
  113. package/dist/rules/view-transitions.js +53 -0
  114. package/dist/rules/view-transitions.js.map +1 -0
  115. package/dist/schema.d.ts +91 -0
  116. package/dist/schema.d.ts.map +1 -0
  117. package/dist/schema.js +124 -0
  118. package/dist/schema.js.map +1 -0
  119. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johannes Maendle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @jomae/catalog
2
+
3
+ The rule catalog behind [youmightnotneed.dev](https://youmightnotneed.dev).
4
+ Maps npm packages to the modern CSS and HTML that replaces them.
5
+
6
+ ```
7
+ npm install @jomae/catalog
8
+ ```
9
+
10
+ ```ts
11
+ import { analyze, baselineLabel } from "@jomae/catalog";
12
+
13
+ const { findings, summary } = analyze({
14
+ dependencies: { swiper: "^11.0.0", "react-wrap-balancer": "^1.1.1" },
15
+ });
16
+
17
+ console.log(summary.replaceableBytes); // 21259
18
+
19
+ for (const finding of findings) {
20
+ console.log(finding.rule.title, baselineLabel(finding.baseline.status));
21
+ console.log(finding.rule.native);
22
+ console.log(finding.rule.agent.unless); // when to keep the dependency
23
+ }
24
+ ```
25
+
26
+ ## detect() is pure
27
+
28
+ No filesystem, no network, no clock. It takes a parsed dependency map and
29
+ returns findings, which is what lets a website, a CLI and an agent share one
30
+ implementation.
31
+
32
+ ```ts
33
+ import { detect, summarize } from "@jomae/catalog";
34
+
35
+ const findings = detect(packageJson); // Finding[]
36
+ const summary = summarize(findings);
37
+ ```
38
+
39
+ `dependencies`, `devDependencies` and `peerDependencies` are all read. Version
40
+ ranges are ignored.
41
+
42
+ ## Every finding carries its caveats
43
+
44
+ `rule.agent.unless` is a non-empty list of the conditions under which the
45
+ dependency is still the correct choice. The schema enforces that it is
46
+ populated. Render it. A replacement shown without its caveats is how this kind
47
+ of tool ends up wrong in public.
48
+
49
+ `baseline.status` is one of `widely`, `newly` or `limited`, derived from
50
+ [`web-features`](https://github.com/web-platform-dx/web-features) at build time
51
+ rather than hardcoded. A rule reports the status of its least-supported
52
+ required feature, and `baseline.limitedBy` names it.
53
+
54
+ ## Sizes
55
+
56
+ `replaceableBytes` is minified and gzipped, from a dated snapshot in
57
+ `packageSizes`. It is `null` when nothing matched has a measurement, so you can
58
+ say "size unknown" rather than "0 kB". Phrase totals as "up to": a package
59
+ being installed is not proof that the native approach covers how it is used.
60
+
61
+ MIT.
@@ -0,0 +1,51 @@
1
+ import type { BaselineStatus, Rule } from "./schema.ts";
2
+ /**
3
+ * One web-features entry, mapped into the catalog's vocabulary.
4
+ */
5
+ export interface ResolvedFeature {
6
+ /** The web-features ID. */
7
+ id: string;
8
+ /** Human-readable name, e.g. "Scroll snap". */
9
+ name: string;
10
+ status: BaselineStatus;
11
+ /** Date the feature reached its current status, when known. */
12
+ since: string | null;
13
+ spec: string | null;
14
+ }
15
+ export interface BaselineInfo {
16
+ /** The rule's overall status: the least-supported required feature. */
17
+ status: BaselineStatus;
18
+ features: ResolvedFeature[];
19
+ /**
20
+ * The feature that caps the rule's status. Null when a rule has a single
21
+ * feature, or when the status came from a manualBaseline.
22
+ */
23
+ limitedBy: ResolvedFeature | null;
24
+ source: "web-features" | "manual";
25
+ /** Date the underlying data was captured, YYYY-MM-DD. */
26
+ dataDate: string;
27
+ /** Why a manual entry exists. Only set when source is "manual". */
28
+ note: string | null;
29
+ }
30
+ export declare function baselineRank(status: BaselineStatus): number;
31
+ /** Sorts best-supported first. */
32
+ export declare function compareBaseline(a: BaselineStatus, b: BaselineStatus): number;
33
+ export declare function baselineLabel(status: BaselineStatus): string;
34
+ export declare function baselineShortLabel(status: BaselineStatus): string;
35
+ /** The date this catalog's Baseline data was captured. */
36
+ export declare const BASELINE_DATA_DATE: string;
37
+ export declare const WEB_FEATURES_VERSION: string;
38
+ /**
39
+ * Resolves one web-features ID against the snapshot. An unknown ID resolves to
40
+ * `unknown` rather than throwing, so a stale snapshot degrades into a visible
41
+ * "unverified" badge instead of a crash. The catalog tests fail on any
42
+ * `unknown`, which is where this is meant to be caught.
43
+ */
44
+ export declare function resolveFeature(id: string): ResolvedFeature;
45
+ /**
46
+ * Derives a rule's support status. A rule is only as available as its
47
+ * least-supported required feature, so the weakest one wins. This is the only
48
+ * place a status is decided, and nothing in the catalog hardcodes one.
49
+ */
50
+ export declare function resolveBaseline(rule: Rule): BaselineInfo;
51
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;;OAGG;IACH,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,cAAc,GAAG,QAAQ,CAAC;IAClC,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAUD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE3D;AAED,kCAAkC;AAClC,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAE5E;AASD,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE5D;AASD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEjE;AAED,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,QAA+B,CAAC;AAC/D,eAAO,MAAM,oBAAoB,QAAsC,CAAC;AAsBxE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,CAa1D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CA2CxD"}
@@ -0,0 +1,123 @@
1
+ import { baselineSnapshot } from "./generated/baseline.js";
2
+ /** Higher is better supported. Used to pick the weakest feature in a rule. */
3
+ const RANK = {
4
+ widely: 3,
5
+ newly: 2,
6
+ limited: 1,
7
+ unknown: 0,
8
+ };
9
+ export function baselineRank(status) {
10
+ return RANK[status];
11
+ }
12
+ /** Sorts best-supported first. */
13
+ export function compareBaseline(a, b) {
14
+ return RANK[b] - RANK[a];
15
+ }
16
+ const LABELS = {
17
+ widely: "Baseline widely available",
18
+ newly: "Baseline newly available",
19
+ limited: "Limited availability",
20
+ unknown: "Support unverified",
21
+ };
22
+ export function baselineLabel(status) {
23
+ return LABELS[status];
24
+ }
25
+ const SHORT_LABELS = {
26
+ widely: "widely available",
27
+ newly: "newly available",
28
+ limited: "limited",
29
+ unknown: "unverified",
30
+ };
31
+ export function baselineShortLabel(status) {
32
+ return SHORT_LABELS[status];
33
+ }
34
+ /** The date this catalog's Baseline data was captured. */
35
+ export const BASELINE_DATA_DATE = baselineSnapshot.generatedOn;
36
+ export const WEB_FEATURES_VERSION = baselineSnapshot.webFeaturesVersion;
37
+ function toStatus(baseline) {
38
+ if (baseline === "high")
39
+ return "widely";
40
+ if (baseline === "low")
41
+ return "newly";
42
+ return "limited";
43
+ }
44
+ /**
45
+ * The date a feature reached its current tier. Widely available features report
46
+ * when they crossed into widely; newly available ones when they crossed into
47
+ * newly. A limited feature has not reached either, so it has no date.
48
+ */
49
+ function sinceDate(status, entry) {
50
+ if (status === "widely")
51
+ return entry.highDate;
52
+ if (status === "newly")
53
+ return entry.lowDate;
54
+ return null;
55
+ }
56
+ /**
57
+ * Resolves one web-features ID against the snapshot. An unknown ID resolves to
58
+ * `unknown` rather than throwing, so a stale snapshot degrades into a visible
59
+ * "unverified" badge instead of a crash. The catalog tests fail on any
60
+ * `unknown`, which is where this is meant to be caught.
61
+ */
62
+ export function resolveFeature(id) {
63
+ const entry = baselineSnapshot.features[id];
64
+ if (!entry) {
65
+ return { id, name: id, status: "unknown", since: null, spec: null };
66
+ }
67
+ const status = toStatus(entry.baseline);
68
+ return {
69
+ id,
70
+ name: entry.name,
71
+ status,
72
+ since: sinceDate(status, entry),
73
+ spec: entry.spec,
74
+ };
75
+ }
76
+ /**
77
+ * Derives a rule's support status. A rule is only as available as its
78
+ * least-supported required feature, so the weakest one wins. This is the only
79
+ * place a status is decided, and nothing in the catalog hardcodes one.
80
+ */
81
+ export function resolveBaseline(rule) {
82
+ if (rule.featureIds.length === 0) {
83
+ const manual = rule.manualBaseline;
84
+ if (!manual) {
85
+ // The schema forbids this, so it can only happen with unvalidated data.
86
+ return {
87
+ status: "unknown",
88
+ features: [],
89
+ limitedBy: null,
90
+ source: "manual",
91
+ dataDate: BASELINE_DATA_DATE,
92
+ note: null,
93
+ };
94
+ }
95
+ return {
96
+ status: manual.status,
97
+ features: [],
98
+ limitedBy: null,
99
+ source: "manual",
100
+ dataDate: manual.verifiedOn,
101
+ note: manual.note,
102
+ };
103
+ }
104
+ const features = rule.featureIds.map(resolveFeature);
105
+ let weakest = features[0];
106
+ for (const feature of features) {
107
+ if (RANK[feature.status] < RANK[weakest.status])
108
+ weakest = feature;
109
+ }
110
+ // Only name a capping feature when one actually caps the rule. Pointing at
111
+ // a feature that matches every other one would read as a warning where
112
+ // there is nothing to warn about.
113
+ const isCapped = features.some((f) => f.status !== weakest.status);
114
+ return {
115
+ status: weakest.status,
116
+ features,
117
+ limitedBy: isCapped ? weakest : null,
118
+ source: "web-features",
119
+ dataDate: BASELINE_DATA_DATE,
120
+ note: null,
121
+ };
122
+ }
123
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { type BaselineInfo } from "./baseline.ts";
2
+ import type { BaselineStatus, Rule } from "./schema.ts";
3
+ /**
4
+ * detect() is pure: no filesystem, no network, no `process`, no clock. It takes
5
+ * a parsed dependency map and returns findings. Every surface (website, CLI,
6
+ * agent tooling) calls this same function, which is what makes four form
7
+ * factors cost one implementation.
8
+ */
9
+ export declare const DEPENDENCY_FIELDS: readonly ["dependencies", "devDependencies", "peerDependencies"];
10
+ export type DependencyField = (typeof DEPENDENCY_FIELDS)[number];
11
+ /** The subset of a package.json that detect() reads. */
12
+ export interface PackageJsonLike {
13
+ name?: string | undefined;
14
+ dependencies?: Record<string, string> | undefined;
15
+ devDependencies?: Record<string, string> | undefined;
16
+ peerDependencies?: Record<string, string> | undefined;
17
+ }
18
+ export interface MatchedPackage {
19
+ name: string;
20
+ /** Every field the package appears in. Versions are ignored in v1. */
21
+ fields: DependencyField[];
22
+ /** Minified and gzipped bytes, or null when we have no measurement. */
23
+ gzip: number | null;
24
+ /** The version that was measured, not the version installed. */
25
+ measuredVersion: string | null;
26
+ }
27
+ export interface Finding {
28
+ rule: Rule;
29
+ baseline: BaselineInfo;
30
+ /** The dependencies that caused this rule to fire. Never empty. */
31
+ matched: MatchedPackage[];
32
+ /**
33
+ * Sum of the known sizes of the matched packages. Null when none of them
34
+ * has a measurement, so callers can say "size unknown" rather than "0 kB".
35
+ */
36
+ replaceableBytes: number | null;
37
+ /** True when at least one matched package has no size data. */
38
+ hasUnknownSizes: boolean;
39
+ }
40
+ export interface DetectOptions {
41
+ /** Override the rule set. Used by tests; defaults to the full catalog. */
42
+ rules?: readonly Rule[] | undefined;
43
+ }
44
+ /**
45
+ * Maps a dependency list to the rules that cover it.
46
+ *
47
+ * A dependency in package.json is not proof of what it is used for, so a
48
+ * finding is a "this may apply" and every surface renders it with its Baseline
49
+ * badge and its `unless` conditions.
50
+ */
51
+ export declare function detect(pkg: PackageJsonLike, options?: DetectOptions): Finding[];
52
+ /**
53
+ * Heaviest first, because the headline number is kilobytes. Ties break on
54
+ * support (better-supported first) and then title, so the order is stable.
55
+ */
56
+ export declare function sortFindings(findings: Finding[]): Finding[];
57
+ export interface Summary {
58
+ /**
59
+ * Total known replaceable bytes, minified and gzipped. Phrased as "up to"
60
+ * everywhere it is shown, because a dependency being present is not proof
61
+ * that the native approach covers how it is used.
62
+ */
63
+ replaceableBytes: number;
64
+ /** Number of rules that fired. */
65
+ findingCount: number;
66
+ /** Number of matched dependencies across all findings. */
67
+ packageCount: number;
68
+ /** True when any matched package had no size measurement. */
69
+ hasUnknownSizes: boolean;
70
+ /** How many findings sit in each support tier. */
71
+ byStatus: Record<BaselineStatus, number>;
72
+ }
73
+ /** Pure. Rolls findings up into the numbers a report leads with. */
74
+ export declare function summarize(findings: readonly Finding[]): Summary;
75
+ export interface Report {
76
+ findings: Finding[];
77
+ summary: Summary;
78
+ }
79
+ /** Convenience wrapper: detect() plus summarize() in one call. Pure. */
80
+ export declare function analyze(pkg: PackageJsonLike, options?: DetectOptions): Report;
81
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;GAKG;AAEH,eAAO,MAAM,iBAAiB,YAC5B,cAAc,EACd,iBAAiB,EACjB,kBAAkB,CACV,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,uEAAuE;IACvE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,gEAAgE;IAChE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,mEAAmE;IACnE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B;;;OAGG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,+DAA+D;IAC/D,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,CAAC;CACrC;AA2FD;;;;;;GAMG;AACH,wBAAgB,MAAM,CACpB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,aAAkB,GAC1B,OAAO,EAAE,CAkBX;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAQ3D;AAED,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,eAAe,EAAE,OAAO,CAAC;IACzB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAyB/D;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wEAAwE;AACxE,wBAAgB,OAAO,CACrB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,aAAkB,GAC1B,MAAM,CAGR"}
package/dist/detect.js ADDED
@@ -0,0 +1,155 @@
1
+ import { compareBaseline, resolveBaseline, } from "./baseline.js";
2
+ import { packageSizes } from "./generated/sizes.js";
3
+ import { rules as defaultRules } from "./rules/index.js";
4
+ /**
5
+ * detect() is pure: no filesystem, no network, no `process`, no clock. It takes
6
+ * a parsed dependency map and returns findings. Every surface (website, CLI,
7
+ * agent tooling) calls this same function, which is what makes four form
8
+ * factors cost one implementation.
9
+ */
10
+ export const DEPENDENCY_FIELDS = [
11
+ "dependencies",
12
+ "devDependencies",
13
+ "peerDependencies",
14
+ ];
15
+ function readSize(name) {
16
+ const entry = packageSizes.sizes[name];
17
+ if (!entry)
18
+ return { gzip: null, version: null };
19
+ return { gzip: entry.gzip, version: entry.version };
20
+ }
21
+ /**
22
+ * Collects the dependency names in a package.json, remembering which fields
23
+ * each one came from. Names are compared exactly and lowercased, matching how
24
+ * npm treats them.
25
+ */
26
+ function collectDependencies(pkg) {
27
+ const found = new Map();
28
+ for (const field of DEPENDENCY_FIELDS) {
29
+ const block = pkg[field];
30
+ if (!block || typeof block !== "object")
31
+ continue;
32
+ for (const rawName of Object.keys(block)) {
33
+ remember(found, rawName.toLowerCase(), field);
34
+ }
35
+ }
36
+ return found;
37
+ }
38
+ /** Notes that `name` appeared in `field`, without duplicating the field. */
39
+ function remember(found, name, field) {
40
+ const fields = found.get(name);
41
+ if (!fields) {
42
+ found.set(name, [field]);
43
+ return;
44
+ }
45
+ if (!fields.includes(field))
46
+ fields.push(field);
47
+ }
48
+ /**
49
+ * Maps a dependency list to the rules that cover it.
50
+ *
51
+ * A dependency in package.json is not proof of what it is used for, so a
52
+ * finding is a "this may apply" and every surface renders it with its Baseline
53
+ * badge and its `unless` conditions.
54
+ */
55
+ /**
56
+ * Finds the packages from one rule that appear in a dependency list. Returns an
57
+ * empty array when the rule does not apply.
58
+ */
59
+ function matchRule(rule, installed) {
60
+ const matched = [];
61
+ for (const candidate of rule.replaces) {
62
+ const fields = installed.get(candidate.toLowerCase());
63
+ if (!fields)
64
+ continue;
65
+ const { gzip, version } = readSize(candidate);
66
+ matched.push({
67
+ name: candidate,
68
+ fields: [...fields],
69
+ gzip,
70
+ measuredVersion: version,
71
+ });
72
+ }
73
+ return matched;
74
+ }
75
+ /** Sums the sizes we know, and reports whether any were missing. */
76
+ function weigh(matched) {
77
+ const known = matched.filter((m) => m.gzip !== null);
78
+ return {
79
+ replaceableBytes: known.length > 0
80
+ ? known.reduce((total, m) => total + (m.gzip ?? 0), 0)
81
+ : null,
82
+ hasUnknownSizes: known.length !== matched.length,
83
+ };
84
+ }
85
+ /**
86
+ * Maps a dependency list to the rules that cover it.
87
+ *
88
+ * A dependency in package.json is not proof of what it is used for, so a
89
+ * finding is a "this may apply" and every surface renders it with its Baseline
90
+ * badge and its `unless` conditions.
91
+ */
92
+ export function detect(pkg, options = {}) {
93
+ const rules = options.rules ?? defaultRules;
94
+ const installed = collectDependencies(pkg);
95
+ const findings = [];
96
+ for (const rule of rules) {
97
+ const matched = matchRule(rule, installed);
98
+ if (matched.length === 0)
99
+ continue;
100
+ findings.push({
101
+ rule,
102
+ baseline: resolveBaseline(rule),
103
+ matched,
104
+ ...weigh(matched),
105
+ });
106
+ }
107
+ return sortFindings(findings);
108
+ }
109
+ /**
110
+ * Heaviest first, because the headline number is kilobytes. Ties break on
111
+ * support (better-supported first) and then title, so the order is stable.
112
+ */
113
+ export function sortFindings(findings) {
114
+ return [...findings].sort((a, b) => {
115
+ const byBytes = (b.replaceableBytes ?? 0) - (a.replaceableBytes ?? 0);
116
+ if (byBytes !== 0)
117
+ return byBytes;
118
+ const byBaseline = compareBaseline(a.baseline.status, b.baseline.status);
119
+ if (byBaseline !== 0)
120
+ return byBaseline;
121
+ return a.rule.title.localeCompare(b.rule.title);
122
+ });
123
+ }
124
+ /** Pure. Rolls findings up into the numbers a report leads with. */
125
+ export function summarize(findings) {
126
+ const byStatus = {
127
+ widely: 0,
128
+ newly: 0,
129
+ limited: 0,
130
+ unknown: 0,
131
+ };
132
+ let replaceableBytes = 0;
133
+ let packageCount = 0;
134
+ let hasUnknownSizes = false;
135
+ for (const finding of findings) {
136
+ replaceableBytes += finding.replaceableBytes ?? 0;
137
+ packageCount += finding.matched.length;
138
+ if (finding.hasUnknownSizes)
139
+ hasUnknownSizes = true;
140
+ byStatus[finding.baseline.status] += 1;
141
+ }
142
+ return {
143
+ replaceableBytes,
144
+ findingCount: findings.length,
145
+ packageCount,
146
+ hasUnknownSizes,
147
+ byStatus,
148
+ };
149
+ }
150
+ /** Convenience wrapper: detect() plus summarize() in one call. Pure. */
151
+ export function analyze(pkg, options = {}) {
152
+ const findings = detect(pkg, options);
153
+ return { findings, summary: summarize(findings) };
154
+ }
155
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared presentation helpers.
3
+ *
4
+ * These live in the catalog so the CLI, the website and any future surface
5
+ * phrase a finding the same way. The phrasing is deliberate: a dependency in
6
+ * package.json is not proof of what it is used for, so sizes are always "up
7
+ * to" and a rule always reads as a conditional.
8
+ */
9
+ /** 20076 becomes "20.1 kB". Uses kB, base 1000, the way npm and bundlers do. */
10
+ export declare function formatBytes(bytes: number): string;
11
+ /** The honest headline. It hedges, and never promises a saving. */
12
+ export declare function formatHeadline(bytes: number, packageCount: number): string;
13
+ /**
14
+ * Phrases a finding as a condition rather than an instruction. "If you're
15
+ * using swiper for X, then Y covers that case."
16
+ */
17
+ export declare function formatConditional(packageNames: readonly string[], when: string, native: string): string;
18
+ /** ["a", "b", "c"] becomes "a, b and c". */
19
+ export declare function formatList(items: readonly string[]): string;
20
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAO1E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,MAAM,CAGR;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAK3D"}
package/dist/format.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared presentation helpers.
3
+ *
4
+ * These live in the catalog so the CLI, the website and any future surface
5
+ * phrase a finding the same way. The phrasing is deliberate: a dependency in
6
+ * package.json is not proof of what it is used for, so sizes are always "up
7
+ * to" and a rule always reads as a conditional.
8
+ */
9
+ /** 20076 becomes "20.1 kB". Uses kB, base 1000, the way npm and bundlers do. */
10
+ export function formatBytes(bytes) {
11
+ if (bytes < 1000)
12
+ return `${bytes} B`;
13
+ const kb = bytes / 1000;
14
+ if (kb < 100)
15
+ return `${kb.toFixed(1)} kB`;
16
+ return `${Math.round(kb)} kB`;
17
+ }
18
+ /** The honest headline. It hedges, and never promises a saving. */
19
+ export function formatHeadline(bytes, packageCount) {
20
+ if (packageCount === 0)
21
+ return "Nothing here that CSS covers yet";
22
+ const deps = packageCount === 1 ? "dependency" : "dependencies";
23
+ if (bytes === 0) {
24
+ return `${packageCount} ${deps} may have a native equivalent`;
25
+ }
26
+ return `Up to ${formatBytes(bytes)} across ${packageCount} ${deps}`;
27
+ }
28
+ /**
29
+ * Phrases a finding as a condition rather than an instruction. "If you're
30
+ * using swiper for X, then Y covers that case."
31
+ */
32
+ export function formatConditional(packageNames, when, native) {
33
+ const list = formatList(packageNames);
34
+ return `If you're using ${list} for ${when}, ${native} covers that case.`;
35
+ }
36
+ /** ["a", "b", "c"] becomes "a, b and c". */
37
+ export function formatList(items) {
38
+ if (items.length === 0)
39
+ return "";
40
+ if (items.length === 1)
41
+ return items[0];
42
+ const head = items.slice(0, -1).join(", ");
43
+ return `${head} and ${items.at(-1)}`;
44
+ }
45
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gFAAgF;AAChF,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,GAAG,GAAG;QAAE,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3C,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC;AAChC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,YAAoB;IAChE,IAAI,YAAY,KAAK,CAAC;QAAE,OAAO,kCAAkC,CAAC;IAClE,MAAM,IAAI,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC;IAChE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,OAAO,GAAG,YAAY,IAAI,IAAI,+BAA+B,CAAC;IAChE,CAAC;IACD,OAAO,SAAS,WAAW,CAAC,KAAK,CAAC,WAAW,YAAY,IAAI,IAAI,EAAE,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,YAA+B,EAC/B,IAAY,EACZ,MAAc;IAEd,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IACtC,OAAO,mBAAmB,IAAI,QAAQ,IAAI,KAAK,MAAM,oBAAoB,CAAC;AAC5E,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,UAAU,CAAC,KAAwB;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAW,CAAC;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,GAAG,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACvC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /** Raw Baseline state for one feature, as web-features reports it. */
2
+ export interface BaselineSnapshotEntry {
3
+ /** Human-readable feature name, e.g. "Scroll snap". */
4
+ name: string;
5
+ /** "high" is widely available, "low" is newly available, false is limited. */
6
+ baseline: "high" | "low" | false;
7
+ /** Date the feature became newly available. */
8
+ lowDate: string | null;
9
+ /** Date the feature became widely available. */
10
+ highDate: string | null;
11
+ /** Canonical specification URL, when web-features records one. */
12
+ spec: string | null;
13
+ }
14
+ export interface BaselineSnapshot {
15
+ /** Date this snapshot was generated, YYYY-MM-DD. */
16
+ generatedOn: string;
17
+ /** The web-features release the data came from. */
18
+ webFeaturesVersion: string;
19
+ features: Record<string, BaselineSnapshotEntry>;
20
+ }
21
+ export declare const baselineSnapshot: BaselineSnapshot;
22
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/generated/baseline.ts"],"names":[],"mappings":"AAGA,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IACjC,+CAA+C;IAC/C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kEAAkE;IAClE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CACjD;AAED,eAAO,MAAM,gBAAgB,EAAE,gBA4O9B,CAAC"}