@variance-authority/core 0.1.1 → 0.3.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/CHANGELOG.md +245 -10
- package/README.md +279 -110
- package/dist/attribute/call-site.d.ts +20 -2
- package/dist/attribute/call-site.js +17 -20
- package/dist/attribute/call-site.js.map +1 -1
- package/dist/attribute/closure.d.ts +105 -0
- package/dist/attribute/closure.js +180 -0
- package/dist/attribute/closure.js.map +1 -0
- package/dist/attribute/composition.d.ts +5 -3
- package/dist/attribute/composition.js +67 -11
- package/dist/attribute/composition.js.map +1 -1
- package/dist/attribute/divergence.js +7 -1
- package/dist/attribute/divergence.js.map +1 -1
- package/dist/attribute/index.d.ts +7 -1
- package/dist/attribute/index.js +4 -1
- package/dist/attribute/index.js.map +1 -1
- package/dist/attribute/landmark.d.ts +97 -0
- package/dist/attribute/landmark.js +144 -0
- package/dist/attribute/landmark.js.map +1 -0
- package/dist/attribute/lexicon.d.ts +112 -0
- package/dist/attribute/lexicon.js +210 -0
- package/dist/attribute/lexicon.js.map +1 -0
- package/dist/attribute/source.d.ts +19 -2
- package/dist/attribute/source.js +12 -0
- package/dist/attribute/source.js.map +1 -1
- package/dist/attribute/stack.d.ts +62 -1
- package/dist/attribute/stack.js +116 -5
- package/dist/attribute/stack.js.map +1 -1
- package/dist/compare/derive.d.ts +17 -0
- package/dist/compare/derive.js.map +1 -1
- package/dist/compare/diff/index.d.ts +25 -1
- package/dist/compare/diff/index.js +8 -5
- package/dist/compare/diff/index.js.map +1 -1
- package/dist/compare/explain.d.ts +1 -1
- package/dist/compare/explain.js +15 -8
- package/dist/compare/explain.js.map +1 -1
- package/dist/compare/index.d.ts +2 -2
- package/dist/compare/index.js +1 -1
- package/dist/compare/index.js.map +1 -1
- package/dist/compare/observability.d.ts +25 -1
- package/dist/compare/observability.js +54 -0
- package/dist/compare/observability.js.map +1 -1
- package/dist/compare/parting.d.ts +24 -2
- package/dist/compare/parting.js +14 -9
- package/dist/compare/parting.js.map +1 -1
- package/dist/compare/slice.d.ts +15 -2
- package/dist/compare/slice.js +6 -2
- package/dist/compare/slice.js.map +1 -1
- package/dist/format/capture.d.ts +15 -0
- package/dist/format/capture.js +30 -1
- package/dist/format/capture.js.map +1 -1
- package/dist/format/document.d.ts +65 -5
- package/dist/format/document.js +34 -0
- package/dist/format/document.js.map +1 -1
- package/dist/format/hash.d.ts +26 -0
- package/dist/format/hash.js +41 -0
- package/dist/format/hash.js.map +1 -1
- package/dist/format/index.d.ts +4 -3
- package/dist/format/index.js +3 -2
- package/dist/format/index.js.map +1 -1
- package/dist/format/sha256.d.ts +10 -2
- package/dist/format/sha256.js +42 -9
- package/dist/format/sha256.js.map +1 -1
- package/dist/format/stabilize.d.ts +15 -0
- package/dist/format/stabilize.js +22 -1
- package/dist/format/stabilize.js.map +1 -1
- package/dist/index.d.ts +3 -11
- package/dist/index.js +3 -11
- package/dist/index.js.map +1 -1
- package/dist/judge/inspect.js +57 -4
- package/dist/judge/inspect.js.map +1 -1
- package/dist/relate/before.d.ts +137 -0
- package/dist/relate/before.js +155 -0
- package/dist/relate/before.js.map +1 -0
- package/dist/relate/graph.d.ts +53 -16
- package/dist/relate/graph.js +33 -15
- package/dist/relate/graph.js.map +1 -1
- package/dist/relate/index.d.ts +3 -2
- package/dist/relate/index.js +2 -1
- package/dist/relate/index.js.map +1 -1
- package/dist/relate/merkle.d.ts +14 -5
- package/dist/relate/merkle.js +14 -6
- package/dist/relate/merkle.js.map +1 -1
- package/dist/relate/reach.d.ts +19 -5
- package/dist/relate/reach.js +10 -8
- package/dist/relate/reach.js.map +1 -1
- package/dist/relate/records.d.ts +95 -4
- package/dist/relate/records.js +93 -13
- package/dist/relate/records.js.map +1 -1
- package/dist/segment/index.d.ts +123 -0
- package/dist/segment/index.js +265 -0
- package/dist/segment/index.js.map +1 -0
- package/dist/share/index.d.ts +148 -0
- package/dist/share/index.js +190 -0
- package/dist/share/index.js.map +1 -0
- package/package.json +10 -2
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/share` — one machine's evaluation, reachable from another.
|
|
3
|
+
*
|
|
4
|
+
* A run derives a great deal that is not about the run: what the suite is made
|
|
5
|
+
* of, which subjects exist, every name each one carries, which files declare
|
|
6
|
+
* which components. All of it is a fact about a *commit*, and all of it is
|
|
7
|
+
* expensive — a source scan, a browser, a composition pass — while being
|
|
8
|
+
* identical on every machine that starts from the same tree.
|
|
9
|
+
*
|
|
10
|
+
* So the second machine should not derive it again. A pull-request runner wants
|
|
11
|
+
* what mainline already knows, and mainline already knew it an hour ago on a
|
|
12
|
+
* runner that has since been destroyed. This module is the seam between those
|
|
13
|
+
* two: a place bytes can be left under a key, and a place they can be looked
|
|
14
|
+
* for, with nothing in between that knows what the bytes are.
|
|
15
|
+
*
|
|
16
|
+
* ## The rule, inherited
|
|
17
|
+
*
|
|
18
|
+
* > **A `SharedCache` never throws.**
|
|
19
|
+
*
|
|
20
|
+
* `get` answers `null` for a miss, an outage, a permission error, a truncated
|
|
21
|
+
* body and a response nobody can parse. `put` resolves whether or not anything
|
|
22
|
+
* was written. This is the same rule
|
|
23
|
+
* [`RenderCache`](../../../raster/src/store.ts) is defined by, and it is safe
|
|
24
|
+
* here for the same reason: everything a share holds is derivable from the tree
|
|
25
|
+
* it was derived at. A miss costs the derivation, which is the answer that was
|
|
26
|
+
* always available.
|
|
27
|
+
*
|
|
28
|
+
* What that costs, stated rather than hidden: a cache that is failing looks
|
|
29
|
+
* exactly like a cache that is cold, so a broken share makes CI slow and never
|
|
30
|
+
* makes it red. An operator watching the wall clock has to go looking. The
|
|
31
|
+
* alternative — failing a build because a bucket answered 503 — is worse.
|
|
32
|
+
*
|
|
33
|
+
* ## Why a key is a commit
|
|
34
|
+
*
|
|
35
|
+
* Everything shared here is derived, so the only question a reader has is *of
|
|
36
|
+
* what*, and the answer is a tree. A commit names one; a branch names whichever
|
|
37
|
+
* one it points at this second, and two runners resolving `main` four minutes
|
|
38
|
+
* apart would silently compare against different suites.
|
|
39
|
+
*
|
|
40
|
+
* Mainline moves while a branch is open, though, which is why the lookup is
|
|
41
|
+
* {@link firstShared} over a lineage rather than a single `get`: the runner asks
|
|
42
|
+
* for the commits its branch actually descends from, newest first, and takes the
|
|
43
|
+
* first one anybody published. What it gets back says which commit it is, so a
|
|
44
|
+
* reader is never in doubt about what it is holding.
|
|
45
|
+
*
|
|
46
|
+
* ## What this is not
|
|
47
|
+
*
|
|
48
|
+
* Not a baseline store. A baseline is the thing a comparison is *against*, it
|
|
49
|
+
* cannot be re-derived, and losing one silently is how a suite records whatever
|
|
50
|
+
* happened to be on screen — which is why
|
|
51
|
+
* [`RasterStore`](../../../raster/src/store.ts) throws on every failure and this
|
|
52
|
+
* throws on none. The two are configured separately because they are answers to
|
|
53
|
+
* different questions, and a share is disposable in a way a baseline may never
|
|
54
|
+
* be.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* Hold an implementation to the rule.
|
|
58
|
+
*
|
|
59
|
+
* Backends are built out of a disk, a socket or a bucket, every one of which
|
|
60
|
+
* throws, and every one of them would otherwise have to remember not to.
|
|
61
|
+
* Wrapping at construction makes the rule a property of the value rather than a
|
|
62
|
+
* promise in a comment.
|
|
63
|
+
*/
|
|
64
|
+
export function neverFails(cache) {
|
|
65
|
+
return {
|
|
66
|
+
async get(key) {
|
|
67
|
+
try {
|
|
68
|
+
return await cache.get(key);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// A miss and a failure are the same instruction: derive it.
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
async put(key, bytes) {
|
|
76
|
+
try {
|
|
77
|
+
await cache.put(key, bytes);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Nothing to report and nothing to retry. The next run publishes again.
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The key an artifact is published under.
|
|
87
|
+
*
|
|
88
|
+
* `project` is a namespace and not a secret: two suites in one monorepo share a
|
|
89
|
+
* bucket and must not share a key. `artifact` names the format, so a reader that
|
|
90
|
+
* does not understand version 2 of something asks for a key version 2 was never
|
|
91
|
+
* written to rather than parsing bytes it will reject.
|
|
92
|
+
*/
|
|
93
|
+
export function shareKey(parts) {
|
|
94
|
+
return `${segment(parts.project)}/${segment(parts.artifact)}/${segment(parts.commit)}.bin`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The newest published artifact among commits this tree descends from.
|
|
98
|
+
*
|
|
99
|
+
* Newest-first because a nearer ancestor is a better description of the same
|
|
100
|
+
* suite, and `behind` is returned rather than discarded because it is the one
|
|
101
|
+
* number that tells an operator their publishing is failing: a share that is
|
|
102
|
+
* always forty commits behind is a share nothing is writing to, and it is
|
|
103
|
+
* otherwise indistinguishable from a healthy one.
|
|
104
|
+
*
|
|
105
|
+
* Stops at the first hit rather than collecting all of them. There is no merge
|
|
106
|
+
* across generations to perform — an evaluation describes one tree — and asking
|
|
107
|
+
* a network for forty keys that will be discarded is the cost this exists to
|
|
108
|
+
* avoid.
|
|
109
|
+
*/
|
|
110
|
+
export async function firstShared(cache, lineage, of) {
|
|
111
|
+
for (let behind = 0; behind < lineage.length; behind += 1) {
|
|
112
|
+
const commit = lineage[behind];
|
|
113
|
+
const bytes = await cache.get(of(commit));
|
|
114
|
+
if (bytes !== null)
|
|
115
|
+
return { commit, behind, bytes };
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A cache in this process, for tests and for a run that shares with itself.
|
|
121
|
+
*
|
|
122
|
+
* Kept here rather than in a test helper because it is also the honest answer
|
|
123
|
+
* to "share, but I have nowhere to put it": the seam is exercised, the code
|
|
124
|
+
* path is the real one, and nothing survives the process.
|
|
125
|
+
*/
|
|
126
|
+
export function memoryShare() {
|
|
127
|
+
const held = new Map();
|
|
128
|
+
return {
|
|
129
|
+
async get(key) {
|
|
130
|
+
return held.get(key) ?? null;
|
|
131
|
+
},
|
|
132
|
+
async put(key, bytes) {
|
|
133
|
+
held.set(key, bytes.slice());
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A share over HTTP: `GET` to read, `PUT` to write, the key as the path.
|
|
139
|
+
*
|
|
140
|
+
* One backend rather than four because a bucket, a signed URL, a static host
|
|
141
|
+
* and a tribunal deployment differ in exactly two ways — the base and the
|
|
142
|
+
* headers — and every one of them speaks this. S3 with `PUT` object access is
|
|
143
|
+
* this with an `Authorization` header; a presigned base is this with none; a
|
|
144
|
+
* deployment is this with a token. Writing three clients would have produced
|
|
145
|
+
* three sets of error handling for an object whose entire error handling is
|
|
146
|
+
* "return null".
|
|
147
|
+
*
|
|
148
|
+
* Nothing here signs anything. Credentials that must be computed per request —
|
|
149
|
+
* SigV4 against a raw bucket — belong to whatever already holds the operator's
|
|
150
|
+
* credentials, and the shape that reaches this is a base URL it produced. That
|
|
151
|
+
* keeps a secret out of this package and out of the config file it would
|
|
152
|
+
* otherwise have to be written in.
|
|
153
|
+
*/
|
|
154
|
+
export function httpShare(options) {
|
|
155
|
+
const base = options.endpoint.replace(/\/+$/, '');
|
|
156
|
+
const headers = options.headers ?? {};
|
|
157
|
+
return {
|
|
158
|
+
async get(key) {
|
|
159
|
+
const response = await fetch(`${base}/${key}`, { headers });
|
|
160
|
+
// A 404 is the common answer and not an event. Every other refusal is
|
|
161
|
+
// read the same way, because the caller's response to all of them is one
|
|
162
|
+
// response, and a cache that distinguished them would be inviting a
|
|
163
|
+
// caller to act on the difference.
|
|
164
|
+
if (!response.ok)
|
|
165
|
+
return null;
|
|
166
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
167
|
+
},
|
|
168
|
+
async put(key, bytes) {
|
|
169
|
+
await fetch(`${base}/${key}`, {
|
|
170
|
+
method: options.method ?? 'PUT',
|
|
171
|
+
headers: { 'content-type': 'application/octet-stream', ...headers },
|
|
172
|
+
body: bytes,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* One path segment, with everything that is not a key character removed.
|
|
179
|
+
*
|
|
180
|
+
* A project name comes from a config file and a commit from `git`, and both end
|
|
181
|
+
* up in a URL and on a filesystem. Escaping would be reversible and is not
|
|
182
|
+
* wanted: a name that needs escaping is a name whose two spellings should not
|
|
183
|
+
* address two different entries, so the mapping is deliberately many-to-one and
|
|
184
|
+
* the cost of a collision is a cache miss.
|
|
185
|
+
*/
|
|
186
|
+
function segment(value) {
|
|
187
|
+
const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '');
|
|
188
|
+
return cleaned === '' ? 'unnamed' : cleaned;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `@variance-authority/core/share` — one machine's evaluation, reachable from another.\n *\n * A run derives a great deal that is not about the run: what the suite is made\n * of, which subjects exist, every name each one carries, which files declare\n * which components. All of it is a fact about a *commit*, and all of it is\n * expensive — a source scan, a browser, a composition pass — while being\n * identical on every machine that starts from the same tree.\n *\n * So the second machine should not derive it again. A pull-request runner wants\n * what mainline already knows, and mainline already knew it an hour ago on a\n * runner that has since been destroyed. This module is the seam between those\n * two: a place bytes can be left under a key, and a place they can be looked\n * for, with nothing in between that knows what the bytes are.\n *\n * ## The rule, inherited\n *\n * > **A `SharedCache` never throws.**\n *\n * `get` answers `null` for a miss, an outage, a permission error, a truncated\n * body and a response nobody can parse. `put` resolves whether or not anything\n * was written. This is the same rule\n * [`RenderCache`](../../../raster/src/store.ts) is defined by, and it is safe\n * here for the same reason: everything a share holds is derivable from the tree\n * it was derived at. A miss costs the derivation, which is the answer that was\n * always available.\n *\n * What that costs, stated rather than hidden: a cache that is failing looks\n * exactly like a cache that is cold, so a broken share makes CI slow and never\n * makes it red. An operator watching the wall clock has to go looking. The\n * alternative — failing a build because a bucket answered 503 — is worse.\n *\n * ## Why a key is a commit\n *\n * Everything shared here is derived, so the only question a reader has is *of\n * what*, and the answer is a tree. A commit names one; a branch names whichever\n * one it points at this second, and two runners resolving `main` four minutes\n * apart would silently compare against different suites.\n *\n * Mainline moves while a branch is open, though, which is why the lookup is\n * {@link firstShared} over a lineage rather than a single `get`: the runner asks\n * for the commits its branch actually descends from, newest first, and takes the\n * first one anybody published. What it gets back says which commit it is, so a\n * reader is never in doubt about what it is holding.\n *\n * ## What this is not\n *\n * Not a baseline store. A baseline is the thing a comparison is *against*, it\n * cannot be re-derived, and losing one silently is how a suite records whatever\n * happened to be on screen — which is why\n * [`RasterStore`](../../../raster/src/store.ts) throws on every failure and this\n * throws on none. The two are configured separately because they are answers to\n * different questions, and a share is disposable in a way a baseline may never\n * be.\n */\n\n// Declared rather than imported, as elsewhere in this package: `core` takes no\n// platform's types, and `fetch` is present in every runtime it targets. Only the\n// three members used are named, so a runtime with a narrower one still fits.\ndeclare const fetch: (\n url: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: Uint8Array;\n },\n) => Promise<{\n readonly ok: boolean;\n readonly status: number;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\n/**\n * Bytes under a key, on terms where losing them is free.\n *\n * Deliberately the smallest interface that can carry an evaluation: no listing,\n * no deletion, no expiry. A backend that can do more is welcome to; nothing\n * here may depend on it, because the set of things every one of a directory, an\n * action cache, a bucket and a deployment can do is this.\n */\nexport interface SharedCache {\n get(key: string): Promise<Uint8Array | null>;\n put(key: string, bytes: Uint8Array): Promise<void>;\n}\n\n/**\n * Hold an implementation to the rule.\n *\n * Backends are built out of a disk, a socket or a bucket, every one of which\n * throws, and every one of them would otherwise have to remember not to.\n * Wrapping at construction makes the rule a property of the value rather than a\n * promise in a comment.\n */\nexport function neverFails(cache: SharedCache): SharedCache {\n return {\n async get(key): Promise<Uint8Array | null> {\n try {\n return await cache.get(key);\n } catch {\n // A miss and a failure are the same instruction: derive it.\n return null;\n }\n },\n async put(key, bytes): Promise<void> {\n try {\n await cache.put(key, bytes);\n } catch {\n // Nothing to report and nothing to retry. The next run publishes again.\n }\n },\n };\n}\n\n/**\n * The key an artifact is published under.\n *\n * `project` is a namespace and not a secret: two suites in one monorepo share a\n * bucket and must not share a key. `artifact` names the format, so a reader that\n * does not understand version 2 of something asks for a key version 2 was never\n * written to rather than parsing bytes it will reject.\n */\nexport function shareKey(parts: {\n readonly project: string;\n readonly artifact: string;\n readonly commit: string;\n}): string {\n return `${segment(parts.project)}/${segment(parts.artifact)}/${segment(parts.commit)}.bin`;\n}\n\n/** What a lookup over a lineage found, and where. */\nexport interface SharedHit {\n /** The commit the bytes were published at — never the one that was asked for first. */\n readonly commit: string;\n /** How many commits back that was, zero being the newest candidate offered. */\n readonly behind: number;\n readonly bytes: Uint8Array;\n}\n\n/**\n * The newest published artifact among commits this tree descends from.\n *\n * Newest-first because a nearer ancestor is a better description of the same\n * suite, and `behind` is returned rather than discarded because it is the one\n * number that tells an operator their publishing is failing: a share that is\n * always forty commits behind is a share nothing is writing to, and it is\n * otherwise indistinguishable from a healthy one.\n *\n * Stops at the first hit rather than collecting all of them. There is no merge\n * across generations to perform — an evaluation describes one tree — and asking\n * a network for forty keys that will be discarded is the cost this exists to\n * avoid.\n */\nexport async function firstShared(\n cache: SharedCache,\n lineage: readonly string[],\n of: (commit: string) => string,\n): Promise<SharedHit | null> {\n for (let behind = 0; behind < lineage.length; behind += 1) {\n const commit = lineage[behind]!;\n const bytes = await cache.get(of(commit));\n if (bytes !== null) return { commit, behind, bytes };\n }\n return null;\n}\n\n/**\n * A cache in this process, for tests and for a run that shares with itself.\n *\n * Kept here rather than in a test helper because it is also the honest answer\n * to \"share, but I have nowhere to put it\": the seam is exercised, the code\n * path is the real one, and nothing survives the process.\n */\nexport function memoryShare(): SharedCache {\n const held = new Map<string, Uint8Array>();\n return {\n async get(key) {\n return held.get(key) ?? null;\n },\n async put(key, bytes) {\n held.set(key, bytes.slice());\n },\n };\n}\n\n/** What an HTTP share needs to reach its endpoint. */\nexport interface HttpShareOptions {\n /** Base URL. A key is appended to it, with exactly one `/` between. */\n readonly endpoint: string;\n /** Sent on every request. A bucket wants `Authorization`; a signed URL wants nothing. */\n readonly headers?: Readonly<Record<string, string>>;\n /** The verb a write uses. `PUT` for a bucket, `POST` for a deployment that routes on it. */\n readonly method?: 'PUT' | 'POST';\n}\n\n/**\n * A share over HTTP: `GET` to read, `PUT` to write, the key as the path.\n *\n * One backend rather than four because a bucket, a signed URL, a static host\n * and a tribunal deployment differ in exactly two ways — the base and the\n * headers — and every one of them speaks this. S3 with `PUT` object access is\n * this with an `Authorization` header; a presigned base is this with none; a\n * deployment is this with a token. Writing three clients would have produced\n * three sets of error handling for an object whose entire error handling is\n * \"return null\".\n *\n * Nothing here signs anything. Credentials that must be computed per request —\n * SigV4 against a raw bucket — belong to whatever already holds the operator's\n * credentials, and the shape that reaches this is a base URL it produced. That\n * keeps a secret out of this package and out of the config file it would\n * otherwise have to be written in.\n */\nexport function httpShare(options: HttpShareOptions): SharedCache {\n const base = options.endpoint.replace(/\\/+$/, '');\n const headers = options.headers ?? {};\n return {\n async get(key) {\n const response = await fetch(`${base}/${key}`, { headers });\n // A 404 is the common answer and not an event. Every other refusal is\n // read the same way, because the caller's response to all of them is one\n // response, and a cache that distinguished them would be inviting a\n // caller to act on the difference.\n if (!response.ok) return null;\n return new Uint8Array(await response.arrayBuffer());\n },\n async put(key, bytes) {\n await fetch(`${base}/${key}`, {\n method: options.method ?? 'PUT',\n headers: { 'content-type': 'application/octet-stream', ...headers },\n body: bytes,\n });\n },\n };\n}\n\n/**\n * One path segment, with everything that is not a key character removed.\n *\n * A project name comes from a config file and a commit from `git`, and both end\n * up in a URL and on a filesystem. Escaping would be reversible and is not\n * wanted: a name that needs escaping is a name whose two spellings should not\n * address two different entries, so the mapping is deliberately many-to-one and\n * the cost of a collision is a cache miss.\n */\nfunction segment(value: string): string {\n const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '');\n return cleaned === '' ? 'unnamed' : cleaned;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variance-authority/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The Variance Authority format, rules, comparison, attribution and verdicts. Pure data in, pure data out, no DOM and no I/O.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"variance-authority",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"author": "Machine Garden",
|
|
14
|
-
"homepage": "https://variance-authority.dev
|
|
14
|
+
"homepage": "https://variance-authority.dev/reference/packages/core",
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/Variance-Authority/variance-authority.git",
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
"types": "./dist/format/index.d.ts",
|
|
32
32
|
"default": "./dist/format/index.js"
|
|
33
33
|
},
|
|
34
|
+
"./share": {
|
|
35
|
+
"types": "./dist/share/index.d.ts",
|
|
36
|
+
"default": "./dist/share/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./segment": {
|
|
39
|
+
"types": "./dist/segment/index.d.ts",
|
|
40
|
+
"default": "./dist/segment/index.js"
|
|
41
|
+
},
|
|
34
42
|
"./rules": {
|
|
35
43
|
"types": "./dist/rules/index.d.ts",
|
|
36
44
|
"default": "./dist/rules/index.js"
|