@adea-ai/plugins 1.0.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 +201 -0
- package/README.md +74 -0
- package/dist/agent-plugins-catalog.d.ts +26 -0
- package/dist/agent-plugins-catalog.js +699 -0
- package/dist/agent-plugins-catalog.js.map +1 -0
- package/dist/agent-plugins-harness.d.ts +54 -0
- package/dist/agent-plugins-harness.js +212 -0
- package/dist/agent-plugins-harness.js.map +1 -0
- package/dist/agent-plugins-schema.d.ts +402 -0
- package/dist/agent-plugins-schema.js +200 -0
- package/dist/agent-plugins-schema.js.map +1 -0
- package/dist/consumer-index.d.ts +217 -0
- package/dist/consumer-index.js +358 -0
- package/dist/consumer-index.js.map +1 -0
- package/dist/display-name.d.ts +8 -0
- package/dist/display-name.js +160 -0
- package/dist/display-name.js.map +1 -0
- package/dist/harness.d.ts +13 -0
- package/dist/harness.js +169 -0
- package/dist/harness.js.map +1 -0
- package/dist/icon-mirror.d.ts +69 -0
- package/dist/icon-mirror.js +147 -0
- package/dist/icon-mirror.js.map +1 -0
- package/dist/icons.d.ts +65 -0
- package/dist/icons.js +268 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +304 -0
- package/dist/index.js +1877 -0
- package/dist/index.js.map +1 -0
- package/dist/portable-catalog.d.ts +9 -0
- package/dist/portable-catalog.js +175 -0
- package/dist/portable-catalog.js.map +1 -0
- package/dist/publication.d.ts +98 -0
- package/dist/publication.js +121 -0
- package/dist/publication.js.map +1 -0
- package/dist/schema.d.ts +705 -0
- package/dist/schema.js +267 -0
- package/dist/schema.js.map +1 -0
- package/dist/site-icons.d.ts +55 -0
- package/dist/site-icons.js +155 -0
- package/dist/site-icons.js.map +1 -0
- package/dist/sources.d.ts +54 -0
- package/dist/sources.js +306 -0
- package/dist/sources.js.map +1 -0
- package/package.json +102 -0
- package/schemas/agent-package.v1.json +387 -0
- package/schemas/catalog.v1.schema.json +407 -0
- package/schemas/compatibility.v1.schema.json +23 -0
- package/schemas/harness-profile.v1.json +75 -0
- package/schemas/installation-plan.v2.json +695 -0
- package/schemas/sources-lock.v1.schema.json +61 -0
- package/src/agent-plugins-catalog.test.ts +402 -0
- package/src/agent-plugins-catalog.ts +800 -0
- package/src/agent-plugins-harness.test.ts +272 -0
- package/src/agent-plugins-harness.ts +281 -0
- package/src/agent-plugins-schema.test.ts +27 -0
- package/src/agent-plugins-schema.ts +219 -0
- package/src/artifacts.test.ts +545 -0
- package/src/catalog-installation.test.ts +42 -0
- package/src/consumer-index.test.ts +465 -0
- package/src/consumer-index.ts +592 -0
- package/src/display-name.test.ts +56 -0
- package/src/display-name.ts +157 -0
- package/src/harness.ts +202 -0
- package/src/icon-mirror.ts +196 -0
- package/src/icons.test.ts +185 -0
- package/src/icons.ts +297 -0
- package/src/index.test.ts +888 -0
- package/src/index.ts +2538 -0
- package/src/portable-catalog.test.ts +186 -0
- package/src/portable-catalog.ts +203 -0
- package/src/publication.test.ts +117 -0
- package/src/publication.ts +171 -0
- package/src/schema.ts +309 -0
- package/src/site-icon-fallback.test.ts +267 -0
- package/src/site-icons.test.ts +171 -0
- package/src/site-icons.ts +161 -0
- package/src/sources.ts +360 -0
- package/src/upstream-headers.test.ts +148 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { createSiteIconFetcher, fetchUpstreamBytes, upstreamRequestHeaders } from './index.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Header policy for upstream requests.
|
|
6
|
+
*
|
|
7
|
+
* A live build runs with a GitHub token that authorises repository writes, and
|
|
8
|
+
* the same helper fetches vendor sites for brand marks. The credential must
|
|
9
|
+
* never leave GitHub's hosts — and sending it changes what a vendor answers:
|
|
10
|
+
* appwrite.io replies 500 to a page carrying an Authorization header, so a
|
|
11
|
+
* build that leaked the token also lost marks it had already curated.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const VENDOR = 'https://appwrite.io/'
|
|
15
|
+
const VENDOR_ICON = 'https://cdn.example/apple-touch-icon.png'
|
|
16
|
+
const GITHUB_API = 'https://api.github.com/repos/adea-ai/plugins/git/trees/abc?recursive=1'
|
|
17
|
+
const RAW = 'https://raw.githubusercontent.com/adea-ai/plugins/abc/plugin.json'
|
|
18
|
+
|
|
19
|
+
const saved = {
|
|
20
|
+
GITHUB_TOKEN: process.env.GITHUB_TOKEN,
|
|
21
|
+
GH_TOKEN: process.env.GH_TOKEN,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
for (const key of ['GITHUB_TOKEN', 'GH_TOKEN'] as const) {
|
|
26
|
+
const value = saved[key]
|
|
27
|
+
if (value === undefined) delete process.env[key]
|
|
28
|
+
else process.env[key] = value
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function withTokens(github?: string, gh?: string): void {
|
|
33
|
+
for (const [key, value] of [
|
|
34
|
+
['GITHUB_TOKEN', github],
|
|
35
|
+
['GH_TOKEN', gh],
|
|
36
|
+
] as const) {
|
|
37
|
+
if (value === undefined) delete process.env[key]
|
|
38
|
+
else process.env[key] = value
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Captures the init each fetch is called with, so headers and signals are inspectable. */
|
|
43
|
+
function captureFetch(response: () => Response = () => new Response('ok')) {
|
|
44
|
+
const original = globalThis.fetch
|
|
45
|
+
const inits: RequestInit[] = []
|
|
46
|
+
globalThis.fetch = (async (_input: unknown, init?: RequestInit) => {
|
|
47
|
+
inits.push(init ?? {})
|
|
48
|
+
return response()
|
|
49
|
+
}) as typeof fetch
|
|
50
|
+
return {
|
|
51
|
+
inits,
|
|
52
|
+
restore: () => {
|
|
53
|
+
globalThis.fetch = original
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('upstream request headers', () => {
|
|
59
|
+
test('never attach the repository credential to a vendor host', () => {
|
|
60
|
+
withTokens('ghs_realtoken', 'ghs_realtoken')
|
|
61
|
+
for (const url of [VENDOR, VENDOR_ICON]) {
|
|
62
|
+
const headers = upstreamRequestHeaders(url)
|
|
63
|
+
expect(headers.authorization).toBeUndefined()
|
|
64
|
+
// GitHub's media type is part of the same disclosure: a vendor asked for
|
|
65
|
+
// an image with it can answer with something that is not the mark.
|
|
66
|
+
expect(headers.accept).toBeUndefined()
|
|
67
|
+
expect(headers['user-agent']).toBeDefined()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('attach the credential to GitHub hosts', () => {
|
|
72
|
+
withTokens('ghs_realtoken', 'ghs_realtoken')
|
|
73
|
+
for (const url of [GITHUB_API, RAW]) {
|
|
74
|
+
const headers = upstreamRequestHeaders(url)
|
|
75
|
+
expect(headers.authorization).toBe('Bearer ghs_realtoken')
|
|
76
|
+
expect(headers.accept).toBe('application/vnd.github+json')
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('attach nothing when no token is configured', () => {
|
|
81
|
+
withTokens(undefined, undefined)
|
|
82
|
+
expect(upstreamRequestHeaders(GITHUB_API).authorization).toBeUndefined()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('treat an unparseable URL as anonymous', () => {
|
|
86
|
+
withTokens('ghs_realtoken', 'ghs_realtoken')
|
|
87
|
+
expect(upstreamRequestHeaders('not a url').authorization).toBeUndefined()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('fetchUpstreamBytes sends no credential to a vendor host', async () => {
|
|
91
|
+
withTokens('ghs_realtoken', 'ghs_realtoken')
|
|
92
|
+
const mock = captureFetch(() => new Response(new Uint8Array([1, 2, 3])))
|
|
93
|
+
try {
|
|
94
|
+
await fetchUpstreamBytes(VENDOR_ICON)
|
|
95
|
+
} finally {
|
|
96
|
+
mock.restore()
|
|
97
|
+
}
|
|
98
|
+
expect(mock.inits).toHaveLength(1)
|
|
99
|
+
const headers = mock.inits[0]?.headers as Record<string, string>
|
|
100
|
+
expect(headers.authorization).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('fetchUpstreamBytes still authenticates a GitHub host', async () => {
|
|
104
|
+
withTokens('ghs_realtoken', 'ghs_realtoken')
|
|
105
|
+
const mock = captureFetch(() => new Response('{}'))
|
|
106
|
+
try {
|
|
107
|
+
await fetchUpstreamBytes(GITHUB_API)
|
|
108
|
+
} finally {
|
|
109
|
+
mock.restore()
|
|
110
|
+
}
|
|
111
|
+
const headers = mock.inits[0]?.headers as Record<string, string>
|
|
112
|
+
expect(headers.authorization).toBe('Bearer ghs_realtoken')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('site-icon fetcher bounds', () => {
|
|
117
|
+
test('gives every request a live ceiling instead of a shared, aged one', async () => {
|
|
118
|
+
const mock = captureFetch()
|
|
119
|
+
try {
|
|
120
|
+
const fetcher = createSiteIconFetcher()
|
|
121
|
+
await fetcher.fetchBytes(VENDOR_ICON)
|
|
122
|
+
await fetcher.fetchBytes(VENDOR_ICON)
|
|
123
|
+
} finally {
|
|
124
|
+
mock.restore()
|
|
125
|
+
}
|
|
126
|
+
// Both requests carry a signal, and neither is already aborted: the ceiling
|
|
127
|
+
// is created per request, so a second call cannot inherit a spent budget.
|
|
128
|
+
expect(mock.inits).toHaveLength(2)
|
|
129
|
+
for (const init of mock.inits) {
|
|
130
|
+
expect(init.signal).toBeInstanceOf(AbortSignal)
|
|
131
|
+
expect(init.signal?.aborted).toBe(false)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('honours a caller deadline that has already passed', async () => {
|
|
136
|
+
const mock = captureFetch()
|
|
137
|
+
try {
|
|
138
|
+
const fetcher = createSiteIconFetcher(AbortSignal.abort())
|
|
139
|
+
await fetcher.fetchBytes(VENDOR_ICON)
|
|
140
|
+
} finally {
|
|
141
|
+
mock.restore()
|
|
142
|
+
}
|
|
143
|
+
// The request still goes out carrying the spent deadline, so the platform
|
|
144
|
+
// aborts it; a real fetch rejects rather than returning a page.
|
|
145
|
+
expect(mock.inits).toHaveLength(1)
|
|
146
|
+
expect(mock.inits[0]?.signal?.aborted).toBe(true)
|
|
147
|
+
})
|
|
148
|
+
})
|