@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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/baseline.d.ts +51 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +123 -0
- package/dist/baseline.js.map +1 -0
- package/dist/detect.d.ts +81 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +155 -0
- package/dist/detect.js.map +1 -0
- package/dist/format.d.ts +20 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +45 -0
- package/dist/format.js.map +1 -0
- package/dist/generated/baseline.d.ts +22 -0
- package/dist/generated/baseline.d.ts.map +1 -0
- package/dist/generated/baseline.js +240 -0
- package/dist/generated/baseline.js.map +1 -0
- package/dist/generated/sizes.d.ts +18 -0
- package/dist/generated/sizes.d.ts.map +1 -0
- package/dist/generated/sizes.js +474 -0
- package/dist/generated/sizes.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/accordion.d.ts +3 -0
- package/dist/rules/accordion.d.ts.map +1 -0
- package/dist/rules/accordion.js +50 -0
- package/dist/rules/accordion.js.map +1 -0
- package/dist/rules/aspect-ratio.d.ts +3 -0
- package/dist/rules/aspect-ratio.d.ts.map +1 -0
- package/dist/rules/aspect-ratio.js +33 -0
- package/dist/rules/aspect-ratio.js.map +1 -0
- package/dist/rules/carousel.d.ts +3 -0
- package/dist/rules/carousel.d.ts.map +1 -0
- package/dist/rules/carousel.js +78 -0
- package/dist/rules/carousel.js.map +1 -0
- package/dist/rules/color-functions.d.ts +3 -0
- package/dist/rules/color-functions.d.ts.map +1 -0
- package/dist/rules/color-functions.js +42 -0
- package/dist/rules/color-functions.js.map +1 -0
- package/dist/rules/container-queries.d.ts +3 -0
- package/dist/rules/container-queries.d.ts.map +1 -0
- package/dist/rules/container-queries.js +53 -0
- package/dist/rules/container-queries.js.map +1 -0
- package/dist/rules/dialog.d.ts +3 -0
- package/dist/rules/dialog.d.ts.map +1 -0
- package/dist/rules/dialog.js +51 -0
- package/dist/rules/dialog.js.map +1 -0
- package/dist/rules/discrete-transitions.d.ts +3 -0
- package/dist/rules/discrete-transitions.d.ts.map +1 -0
- package/dist/rules/discrete-transitions.js +57 -0
- package/dist/rules/discrete-transitions.js.map +1 -0
- package/dist/rules/field-sizing.d.ts +3 -0
- package/dist/rules/field-sizing.d.ts.map +1 -0
- package/dist/rules/field-sizing.js +37 -0
- package/dist/rules/field-sizing.js.map +1 -0
- package/dist/rules/fluid-type.d.ts +3 -0
- package/dist/rules/fluid-type.d.ts.map +1 -0
- package/dist/rules/fluid-type.js +30 -0
- package/dist/rules/fluid-type.js.map +1 -0
- package/dist/rules/height-animation.d.ts +3 -0
- package/dist/rules/height-animation.d.ts.map +1 -0
- package/dist/rules/height-animation.js +46 -0
- package/dist/rules/height-animation.js.map +1 -0
- package/dist/rules/index.d.ts +14 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +56 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/masonry.d.ts +3 -0
- package/dist/rules/masonry.d.ts.map +1 -0
- package/dist/rules/masonry.js +56 -0
- package/dist/rules/masonry.js.map +1 -0
- package/dist/rules/popover.d.ts +3 -0
- package/dist/rules/popover.d.ts.map +1 -0
- package/dist/rules/popover.js +62 -0
- package/dist/rules/popover.js.map +1 -0
- package/dist/rules/scroll-animations.d.ts +3 -0
- package/dist/rules/scroll-animations.d.ts.map +1 -0
- package/dist/rules/scroll-animations.js +58 -0
- package/dist/rules/scroll-animations.js.map +1 -0
- package/dist/rules/scroll-lock.d.ts +3 -0
- package/dist/rules/scroll-lock.d.ts.map +1 -0
- package/dist/rules/scroll-lock.js +40 -0
- package/dist/rules/scroll-lock.js.map +1 -0
- package/dist/rules/scrollbars.d.ts +3 -0
- package/dist/rules/scrollbars.d.ts.map +1 -0
- package/dist/rules/scrollbars.js +44 -0
- package/dist/rules/scrollbars.js.map +1 -0
- package/dist/rules/select.d.ts +3 -0
- package/dist/rules/select.d.ts.map +1 -0
- package/dist/rules/select.js +54 -0
- package/dist/rules/select.js.map +1 -0
- package/dist/rules/smooth-scroll.d.ts +3 -0
- package/dist/rules/smooth-scroll.d.ts.map +1 -0
- package/dist/rules/smooth-scroll.js +41 -0
- package/dist/rules/smooth-scroll.js.map +1 -0
- package/dist/rules/sticky.d.ts +3 -0
- package/dist/rules/sticky.d.ts.map +1 -0
- package/dist/rules/sticky.js +39 -0
- package/dist/rules/sticky.js.map +1 -0
- package/dist/rules/text-box-trim.d.ts +3 -0
- package/dist/rules/text-box-trim.d.ts.map +1 -0
- package/dist/rules/text-box-trim.js +32 -0
- package/dist/rules/text-box-trim.js.map +1 -0
- package/dist/rules/text-wrap-balance.d.ts +3 -0
- package/dist/rules/text-wrap-balance.d.ts.map +1 -0
- package/dist/rules/text-wrap-balance.js +34 -0
- package/dist/rules/text-wrap-balance.js.map +1 -0
- package/dist/rules/view-transitions.d.ts +3 -0
- package/dist/rules/view-transitions.d.ts.map +1 -0
- package/dist/rules/view-transitions.js +53 -0
- package/dist/rules/view-transitions.js.map +1 -0
- package/dist/schema.d.ts +91 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +124 -0
- package/dist/schema.js.map +1 -0
- 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"}
|
package/dist/baseline.js
ADDED
|
@@ -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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"}
|
package/dist/detect.d.ts
ADDED
|
@@ -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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"}
|
package/dist/format.d.ts
ADDED
|
@@ -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"}
|