@intentic/registry 1.223.0 → 1.225.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/package.json +2 -2
- package/src/registry.test.ts +2 -2
- package/src/registry.ts +20 -20
- package/src/source.test.ts +1 -1
- package/src/source.ts +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @intentic/registry
|
|
2
2
|
|
|
3
|
-
The **extension-registry file format
|
|
3
|
+
The **extension-registry file format**: what a registry repo contains, how its two files join, and the order
|
|
4
4
|
the result is shown in. Published to npm, and depended on by three otherwise-unrelated consumers, which is the
|
|
5
5
|
whole reason it is a package: the daemon that clones a registry, the site that builds the public gallery, and
|
|
6
6
|
the [scanner](../../_tools/registry-scan) that writes the files. One zod schema instead of three that drift.
|
|
@@ -10,7 +10,7 @@ below, and [Verification & trust](https://intentic.dev/developers/verify/) for w
|
|
|
10
10
|
|
|
11
11
|
## The shape of it
|
|
12
12
|
|
|
13
|
-
intentic hosts no extension code, builds none, and signs none. **A registry is a git repo of pointers
|
|
13
|
+
intentic hosts no extension code, builds none, and signs none. **A registry is a git repo of pointers**:
|
|
14
14
|
each entry names somebody else's repository at a commit, and installing follows that pointer from the owner's
|
|
15
15
|
sandbox straight to the author's git host. Listing costs a pull request; delisting removes a pointer and
|
|
16
16
|
deletes nothing.
|
|
@@ -23,13 +23,13 @@ lists a team's agent plugins and its intentic extensions together.
|
|
|
23
23
|
|
|
24
24
|
| File | Written by | Holds |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `.claude-plugin/marketplace.json` | humans + protected admission workflow | every decision
|
|
26
|
+
| `.claude-plugin/marketplace.json` | humans + protected admission workflow | every decision: what is listed, the exact source, trust level, and source-bound security record |
|
|
27
27
|
| `.claude-plugin/registry.generated.json` | the nightly scanner | only facts read back off the source host: stars, last push |
|
|
28
28
|
|
|
29
29
|
Keeping the derived data out of the curated file is load-bearing, not tidiness. Star counts in the hand-edited
|
|
30
30
|
file would make every nightly refresh a merge conflict against every open listing pull request, and would bury
|
|
31
31
|
the decision under churn in the review diff. A registry with no generated file is a registry with no stars,
|
|
32
|
-
which renders fine
|
|
32
|
+
which renders fine: most registries are a dozen internal extensions in a private repo and run no scanner.
|
|
33
33
|
|
|
34
34
|
[`resolveRegistry`](src/registry.ts) joins them by entry name into `RegistryEntry`, which is also the daemon's
|
|
35
35
|
browse wire shape, so the app's list and the website's gallery are the same rows in the same order.
|
|
@@ -48,7 +48,7 @@ with install disabled and is omitted from the public gallery; it cannot become a
|
|
|
48
48
|
the runtime backstop behind the registry's required PR check.
|
|
49
49
|
|
|
50
50
|
A blocked entry stays in the file. Deleting the row hides it from people browsing and tells the people who
|
|
51
|
-
already installed it nothing, which is backwards
|
|
51
|
+
already installed it nothing, which is backwards: they are the ones at risk. Absent on a third-party registry
|
|
52
52
|
resolves to `listed`, because a registry that doesn't use the field hasn't asserted anything.
|
|
53
53
|
Third-party registries are their own admission boundary: their non-blocked rows remain installable without
|
|
54
54
|
adopting intentic's gate or policy, and the app states when they carry no audit record.
|
|
@@ -56,40 +56,40 @@ adopting intentic's gate or policy, and the app states when they carry no audit
|
|
|
56
56
|
Installed sandboxes read these states back on a daily comparison, so trust reaches the people past the browse
|
|
57
57
|
moment too: a row turned `blocked` raises an advisory on the installed extension (and, by default, switches it
|
|
58
58
|
off), and `securityFix: true` on an entry marks its pinned commit as fixing a security problem in earlier ones
|
|
59
|
-
|
|
59
|
+
- the installed side promotes its update badge from ambient to loud, because there the OLD version is the
|
|
60
60
|
dangerous one. Both are asserted by pull request, like `trust`, and are worth exactly that review.
|
|
61
61
|
|
|
62
62
|
## Tier, and what premium buys into
|
|
63
63
|
|
|
64
|
-
`tier` is the listing's price: `free` (the default, and the whole story for most rows) or `premium
|
|
64
|
+
`tier` is the listing's price: `free` (the default, and the whole story for most rows) or `premium`, the
|
|
65
65
|
listing opts into the **creator pool**. A premium row needs an intentic membership, both surfaces badge it
|
|
66
66
|
before the click, and installing it **donates a published number of the member's credits to the publisher**
|
|
67
|
-
(once, deduped monthly
|
|
67
|
+
(once, deduped monthly: updates donate again at most monthly). No usage is metered or reported anywhere;
|
|
68
68
|
the deliberate install is the whole signal. The economics live in
|
|
69
69
|
[The creator pool](https://intentic.dev/earn/).
|
|
70
70
|
|
|
71
71
|
## The mark
|
|
72
72
|
|
|
73
|
-
A row carries the two display tiers the manifest declares
|
|
74
|
-
from the app's own set)
|
|
73
|
+
A row carries the two display tiers the manifest declares: `logo` (a simple-icons slug) and `icon` (a glyph
|
|
74
|
+
from the app's own set): and the [scanner](../../_tools/registry-scan) copies whichever is set into the listing it
|
|
75
75
|
proposes, exactly as it copies the version. A row with neither is drawn as the extension's initials.
|
|
76
76
|
|
|
77
77
|
They ride the **curated** file, which looks wrong for a copied value until you ask what the two files are for:
|
|
78
78
|
the mark is part of how a listing presents itself, so it belongs in the row a human reviews and can correct.
|
|
79
|
-
It also has to be here to be worth anything
|
|
79
|
+
It also has to be here to be worth anything: the gallery and the app's browse list render this row and have
|
|
80
80
|
no access to the manifest, because the whole point of browsing is that the code has not been cloned yet.
|
|
81
81
|
|
|
82
82
|
## The order
|
|
83
83
|
|
|
84
84
|
[`compareEntries`](src/registry.ts): verified first, then stars, then most-recently-pushed, then name. Stars
|
|
85
|
-
are the obvious sort and the wrong one alone
|
|
85
|
+
are the obvious sort and the wrong one alone: every listing sits at nought to three of them for months, so a
|
|
86
86
|
pure star sort is a random order wearing a merit badge, and it is the most purchasable number on GitHub.
|
|
87
87
|
Recency is what actually does the ordering early on. Stars stay visible; they just don't get to be the
|
|
88
88
|
ranking.
|
|
89
89
|
|
|
90
90
|
## Identity
|
|
91
91
|
|
|
92
|
-
An entry's `name` is `publisher.name` from the manifest
|
|
92
|
+
An entry's `name` is `publisher.name` from the manifest: [`extensionIdOf`](../extension-api/src/manifest.ts),
|
|
93
93
|
the same identity the app installs under. It is derived, never declared by the registry, so a registry entry
|
|
94
94
|
cannot rename or spoof an extension, and a repo that copies somebody else's manifest collides with their
|
|
95
95
|
listing instead of shadowing it.
|
|
@@ -107,6 +107,6 @@ exists because neither those signatures nor manifest validation can answer malic
|
|
|
107
107
|
|
|
108
108
|
## Key files
|
|
109
109
|
|
|
110
|
-
- [src/registry.ts](src/registry.ts)
|
|
111
|
-
- [src/source.ts](src/source.ts)
|
|
112
|
-
- [src/index.ts](src/index.ts)
|
|
110
|
+
- [src/registry.ts](src/registry.ts), the file format: what a registry repo contains.
|
|
111
|
+
- [src/source.ts](src/source.ts): how a sha-pinned pointer names an extension.
|
|
112
|
+
- [src/index.ts](src/index.ts): the public surface.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentic/registry",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The extension-registry file format
|
|
3
|
+
"version": "1.225.0",
|
|
4
|
+
"description": "The extension-registry file format, a git repo of sha-pinned pointers, shared by the daemon, the site gallery and the scanner",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
package/src/registry.test.ts
CHANGED
|
@@ -113,9 +113,9 @@ describe(`resolveRegistry`, () => {
|
|
|
113
113
|
expect(resolveRegistry(premium, undefined, REGISTRY)[0]?.tier).toBe(`premium`);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
// Blocked rows must survive the resolve
|
|
116
|
+
// Blocked rows must survive the resolve: deleting them is what hides a warning from the people who
|
|
117
117
|
/* The staleness rule the checks ride on: a fact is bound to the sha it was derived from, and a listing
|
|
118
|
-
* repointed since the last scan renders no checks at all
|
|
118
|
+
* repointed since the last scan renders no checks at all (the honest gap) rather than yesterday's verdict
|
|
119
119
|
* describing today's pointer. */
|
|
120
120
|
it(`joins checks only when they were derived from the sha the listing still pins`, () => {
|
|
121
121
|
const facts = RegistryFactsSchema.parse({
|
package/src/registry.ts
CHANGED
|
@@ -4,15 +4,15 @@ import { type RegistryInstall, RegistryInstallSchema, resolveSource } from "./so
|
|
|
4
4
|
/* THE EXTENSION REGISTRY: a git repo of pointers, and the two files in it.
|
|
5
5
|
*
|
|
6
6
|
* intentic hosts no extension code, builds none, and signs none. A registry is a repository whose
|
|
7
|
-
* `.claude-plugin/marketplace.json` lists pointers to other people's repositories at a commit
|
|
7
|
+
* `.claude-plugin/marketplace.json` lists pointers to other people's repositories at a commit, so listing
|
|
8
8
|
* costs a pull request, delisting deletes nothing, and anybody can run their own registry against a private
|
|
9
9
|
* one. That file is Claude Code's plugin-marketplace format deliberately: `kind` and `trust` are intentic's
|
|
10
10
|
* own fields and Claude Code ignores what it doesn't know, so one repo serves both consumers.
|
|
11
11
|
*
|
|
12
|
-
* The split into TWO files is the
|
|
12
|
+
* The split into TWO files is the part that matters. `marketplace.json` is hand-edited and is the only place a
|
|
13
13
|
* curated decision and source-bound admission are recorded; `registry.generated.json` is bot-written and holds
|
|
14
14
|
* nothing but facts read back off the source host. Keeping star counts out of the curated file is what stops every nightly refresh from
|
|
15
|
-
* being a merge conflict against an open pull request
|
|
15
|
+
* being a merge conflict against an open pull request, and it keeps the review diff to the thing being
|
|
16
16
|
* decided. A registry that carries no generated file is a registry with no stars, which renders fine. */
|
|
17
17
|
|
|
18
18
|
// Repo-relative, and the same two strings for the daemon (which clones), the scanner (which commits) and the
|
|
@@ -24,7 +24,7 @@ export const REGISTRY_FACTS_FILE = ".claude-plugin/registry.generated.json";
|
|
|
24
24
|
// field stays editable, so a company points it at an internal registry and never touches this one.
|
|
25
25
|
export const OFFICIAL_REGISTRY_URL = "https://github.com/intentic/registry";
|
|
26
26
|
|
|
27
|
-
// The GitHub topic an author adds to be found. This is discovery only
|
|
27
|
+
// The GitHub topic an author adds to be found. This is discovery only, appearing in the scan gets you a pull
|
|
28
28
|
// request against the registry, never a listing.
|
|
29
29
|
export const REGISTRY_TOPIC = "intentic-extension";
|
|
30
30
|
|
|
@@ -65,7 +65,7 @@ export type RegistrySecurityReview = z.infer<typeof RegistrySecurityReviewSchema
|
|
|
65
65
|
* - `verified` both automated checks passed and a human also read the source at that sha. Sorted first and badged.
|
|
66
66
|
* - `blocked` known-malicious or known-broken. It STAYS in the file with a reason rather than being deleted:
|
|
67
67
|
* removing the row hides it from people browsing and tells the people who already installed it
|
|
68
|
-
* nothing, which is backwards
|
|
68
|
+
* nothing, which is backwards, they are the ones at risk.
|
|
69
69
|
*
|
|
70
70
|
* Absent on a third-party registry ⇒ `listed`, because a registry that doesn't use the field hasn't asserted
|
|
71
71
|
* anything and shouldn't be read as if it had. */
|
|
@@ -74,10 +74,10 @@ export type RegistryTrust = z.infer<typeof RegistryTrustSchema>;
|
|
|
74
74
|
|
|
75
75
|
/* WHAT A LISTING COSTS. `free` is the default and the whole story for most rows. `premium` opts the listing
|
|
76
76
|
* into the creator pool: installing it requires an intentic membership and donates a published number of the
|
|
77
|
-
* member's credits to the publisher (once, deduped monthly
|
|
77
|
+
* member's credits to the publisher (once, deduped monthly, an update in a later month donates again), and
|
|
78
78
|
* both surfaces badge it so the price is visible before the click. No usage is ever metered or reported for
|
|
79
79
|
* this; the deliberate act of installing is the whole signal. On a third-party registry the field still
|
|
80
|
-
* parses but means nothing
|
|
80
|
+
* parses but means nothing, the pool only pays listings the platform's members actually install. */
|
|
81
81
|
export const RegistryTierSchema = z.enum(["free", "premium"]);
|
|
82
82
|
export type RegistryTier = z.infer<typeof RegistryTierSchema>;
|
|
83
83
|
|
|
@@ -89,27 +89,27 @@ const RegistryFileEntrySchema = z
|
|
|
89
89
|
// "extension" installs as the sha-pinned `extension` capability; absent/"plugin" is a Claude Code plugin.
|
|
90
90
|
kind: z.enum(["plugin", "extension"]).optional(),
|
|
91
91
|
trust: RegistryTrustSchema.optional(),
|
|
92
|
-
// Why it is blocked, or what was checked to verify it
|
|
92
|
+
// Why it is blocked, or what was checked to verify it, shown verbatim wherever the badge is, because a
|
|
93
93
|
// trust state with no stated reason is an opinion the reader can't weigh.
|
|
94
94
|
trustReason: z.string().optional(),
|
|
95
95
|
// Both automated checks' evidence, bound to the complete source identity below. It is deliberately
|
|
96
96
|
// distinct from `trust: verified`: passing admission admits code; verified additionally says a human
|
|
97
97
|
// read it. Keeping those claims apart stops automated verdicts being presented as human review.
|
|
98
98
|
securityReview: RegistrySecurityReviewSchema.optional(),
|
|
99
|
-
// This listing's pinned commit fixes a security problem in earlier commits
|
|
99
|
+
// This listing's pinned commit fixes a security problem in earlier commits, the fast lane: an installed
|
|
100
100
|
// sandbox promotes its "update available" badge from ambient to attention-demanding, because there the OLD
|
|
101
101
|
// version is the dangerous one. Asserted by the pull request like trust, and worth exactly that review.
|
|
102
102
|
securityFix: z.boolean().optional(),
|
|
103
103
|
tier: RegistryTierSchema.optional(),
|
|
104
104
|
category: z.string().optional(),
|
|
105
105
|
/* The mark the row is drawn with, copied off the extension's manifest exactly like the description and
|
|
106
|
-
* the version
|
|
106
|
+
* the version, the same three tiers the manifest declares (the author's own inline drawing, then a
|
|
107
107
|
* simple-icons slug, then a name from the app's icon set), and none is required.
|
|
108
108
|
*
|
|
109
109
|
* It rides the CURATED file rather than the generated one, which looks wrong for a derived value until you
|
|
110
110
|
* ask what a registry is for: a listing is what a human decided to publish, and the mark is part of how it
|
|
111
111
|
* presents itself, so it belongs in the row a reviewer reads and can strike out. The generated file holds
|
|
112
|
-
* only what a bot re-reads nightly and nobody reviews. It also has to be here to be of any use at all
|
|
112
|
+
* only what a bot re-reads nightly and nobody reviews. It also has to be here to be of any use at all,
|
|
113
113
|
* this is what the gallery and the in-app browse list render, and neither of them has the manifest: the
|
|
114
114
|
* whole point of the row is that the code has NOT been cloned yet.
|
|
115
115
|
*
|
|
@@ -166,15 +166,15 @@ export type RegistryFile = z.infer<typeof RegistryFileSchema>;
|
|
|
166
166
|
/* Facts read off the source host, keyed by the curated entry's name. Deliberately NOT the upstream head sha:
|
|
167
167
|
* the approved sha is the one that runs, updating is a pull request, and a file that advertised "there's a
|
|
168
168
|
* newer commit over there" would be inviting a click that skips the review the whole model rests on. */
|
|
169
|
-
/* What the scan re-derived COLD at the listing's pinned sha
|
|
169
|
+
/* What the scan re-derived COLD at the listing's pinned sha, the same questions the daemon's readiness check
|
|
170
170
|
* answers for an author before publishing, asked again by a stranger with nothing but the pointer. That
|
|
171
171
|
* re-derivation is the whole value: an author's own checks describe the directory they ran them in, and these
|
|
172
172
|
* describe what an installer actually gets. `sha` binds the answers to the commit they were read from, so a
|
|
173
|
-
* listing repointed since the last scan renders no stale verdicts
|
|
173
|
+
* listing repointed since the last scan renders no stale verdicts, the join drops checks whose sha no longer
|
|
174
174
|
* matches rather than letting yesterday's answer describe today's pointer. */
|
|
175
175
|
const RegistryChecksSchema = z.object({
|
|
176
176
|
sha: z.string(),
|
|
177
|
-
// "ok", or the reason it is not, verbatim
|
|
177
|
+
// "ok", or the reason it is not, verbatim, a verdict with no stated reason is an opinion.
|
|
178
178
|
manifest: z.string(),
|
|
179
179
|
// "ok" / "none" (no UI bundle) / the reason the bundle cannot load where it is installed.
|
|
180
180
|
bundle: z.string(),
|
|
@@ -187,7 +187,7 @@ export type RegistryChecks = z.infer<typeof RegistryChecksSchema>;
|
|
|
187
187
|
const RegistryFactsEntrySchema = z.object({
|
|
188
188
|
name: z.string(),
|
|
189
189
|
stars: z.number().int().nonnegative().optional(),
|
|
190
|
-
// ISO-8601, last push to the source repo's default branch
|
|
190
|
+
// ISO-8601, last push to the source repo's default branch, the tiebreaker that does the real work while
|
|
191
191
|
// every listing still has single-digit stars.
|
|
192
192
|
pushedAt: z.string().optional(),
|
|
193
193
|
checks: RegistryChecksSchema.optional(),
|
|
@@ -200,7 +200,7 @@ export const RegistryFactsSchema = z.object({
|
|
|
200
200
|
export type RegistryFacts = z.infer<typeof RegistryFactsSchema>;
|
|
201
201
|
|
|
202
202
|
// One row as every surface consumes it: the curated decision, the resolved pointer, and the upstream facts,
|
|
203
|
-
// already joined. This is also the daemon's browse wire shape
|
|
203
|
+
// already joined. This is also the daemon's browse wire shape, the app renders what the site renders.
|
|
204
204
|
export const RegistryEntrySchema = z.object({
|
|
205
205
|
name: z.string(),
|
|
206
206
|
description: z.string().optional(),
|
|
@@ -217,7 +217,7 @@ export const RegistryEntrySchema = z.object({
|
|
|
217
217
|
securityFix: z.boolean().optional(),
|
|
218
218
|
tier: RegistryTierSchema,
|
|
219
219
|
category: z.string().optional(),
|
|
220
|
-
// The mark, as the gallery and the app's browse list draw it
|
|
220
|
+
// The mark, as the gallery and the app's browse list draw it, see the curated file's fields above.
|
|
221
221
|
art: z.string().optional(),
|
|
222
222
|
logo: z.string().optional(),
|
|
223
223
|
icon: z.string().optional(),
|
|
@@ -265,7 +265,7 @@ export const isCurrentSecurityReview = (review: RegistrySecurityReview | undefin
|
|
|
265
265
|
review.deterministic.version === OFFICIAL_DETERMINISTIC_SCANNER_VERSION;
|
|
266
266
|
|
|
267
267
|
/* Join the curated file to the generated facts. `facts` is undefined for any registry that runs no scanner,
|
|
268
|
-
* which is most of them
|
|
268
|
+
* which is most of them, a private registry of six internal extensions wants the pointers and nothing else. */
|
|
269
269
|
export const resolveRegistry = (file: RegistryFile, facts: RegistryFacts | undefined, registryUrl: string): RegistryEntry[] => {
|
|
270
270
|
const byName = new Map(facts?.entries.map((entry) => [entry.name, entry]) ?? []);
|
|
271
271
|
const official = isOfficialRegistryUrl(registryUrl);
|
|
@@ -295,7 +295,7 @@ export const resolveRegistry = (file: RegistryFile, facts: RegistryFacts | undef
|
|
|
295
295
|
...(install !== undefined ? { install } : {}),
|
|
296
296
|
...(upstream?.stars !== undefined ? { stars: upstream.stars } : {}),
|
|
297
297
|
...(upstream?.pushedAt !== undefined ? { pushedAt: upstream.pushedAt } : {}),
|
|
298
|
-
// Only when derived from the sha this row still points at
|
|
298
|
+
// Only when derived from the sha this row still points at, a repointed listing renders no checks
|
|
299
299
|
// until the next scan, which is the honest gap rather than yesterday's verdict on today's pointer.
|
|
300
300
|
...(upstream?.checks !== undefined && upstream.checks.sha === install?.ref ? { checks: upstream.checks } : {}),
|
|
301
301
|
};
|
|
@@ -305,7 +305,7 @@ export const resolveRegistry = (file: RegistryFile, facts: RegistryFacts | undef
|
|
|
305
305
|
/* THE ORDER, and why it isn't just stars.
|
|
306
306
|
*
|
|
307
307
|
* Stars are the obvious sort and the wrong one on day one: every listing will sit at nought to three of them
|
|
308
|
-
* for months, so a pure star sort is a random order wearing a merit badge
|
|
308
|
+
* for months, so a pure star sort is a random order wearing a merit badge, and it is the single most
|
|
309
309
|
* purchasable number on GitHub. So: the one field a human actually asserted leads, stars rank within that,
|
|
310
310
|
* and recency breaks the ties that will be the overwhelmingly common case early on. Stars stay VISIBLE
|
|
311
311
|
* either way; a reader can weigh them, they just don't get to be the whole ranking. */
|
package/src/source.test.ts
CHANGED
|
@@ -31,7 +31,7 @@ describe(`resolveSource`, () => {
|
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
// An unclonable source is a row we still show
|
|
34
|
+
// An unclonable source is a row we still show: undefined here becomes "not installable", not a dropped entry.
|
|
35
35
|
it(`gives up on shapes it cannot clone rather than throwing`, () => {
|
|
36
36
|
expect(resolveSource({ source: `npm`, package: `@acme/incidents` }, REGISTRY, undefined)).toBeUndefined();
|
|
37
37
|
expect(resolveSource({ source: `github` }, REGISTRY, undefined)).toBeUndefined();
|
package/src/source.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
/* WHERE A REGISTRY ENTRY'S CODE LIVES, and how that resolves to something cloneable.
|
|
4
4
|
*
|
|
5
|
-
* A registry never hosts code
|
|
5
|
+
* A registry never hosts code, an entry is a pointer to somebody else's repository at a commit. `source` is
|
|
6
6
|
* that pointer in the shapes Claude Code's plugin-marketplace format already defines, so one registry repo
|
|
7
7
|
* serves both consumers; `resolveSource` maps the shapes we can clone onto the url/ref/path a capability
|
|
8
|
-
* install takes, and returns undefined for the ones we can't (npm, say) rather than dropping the entry
|
|
8
|
+
* install takes, and returns undefined for the ones we can't (npm, say) rather than dropping the entry,
|
|
9
9
|
* an entry that exists but can't be installed in a click is information, a missing row is a bug report. */
|
|
10
10
|
|
|
11
11
|
// The resolved pointer: exactly the fields a plugin- or extension-capability install needs.
|
|
@@ -46,14 +46,14 @@ export const resolveSource = (source: unknown, registryUrl: string, pluginRoot:
|
|
|
46
46
|
|
|
47
47
|
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
48
48
|
|
|
49
|
-
/* Whether this pointer names one immutable commit. An EXTENSION install requires it
|
|
49
|
+
/* Whether this pointer names one immutable commit. An EXTENSION install requires it, extension code runs
|
|
50
50
|
* trusted in the owner's browser, so the approved code and the running code have to be the same object, and a
|
|
51
51
|
* branch name is a promise the upstream can break with a force-push. A registry entry that gives only a branch
|
|
52
52
|
* is still listed and still readable; it just can't be a one-click install, which is the pressure that makes
|
|
53
53
|
* authors pin. Plugins are laxer by design: they load into the agent, not the browser. */
|
|
54
54
|
export const isShaPinned = (install: RegistryInstall | undefined): boolean => install?.ref !== undefined && FULL_SHA.test(install.ref);
|
|
55
55
|
|
|
56
|
-
// `owner/repo` for a GitHub pointer
|
|
56
|
+
// `owner/repo` for a GitHub pointer, what the scanner keys upstream facts by, and what the gallery links to.
|
|
57
57
|
// Undefined for any host that isn't GitHub, which is a listing we simply carry no stars for.
|
|
58
58
|
export const githubRepoOf = (install: RegistryInstall | undefined): string | undefined => {
|
|
59
59
|
if (install === undefined) {
|