@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,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-name normalization for catalog plugins.
|
|
3
|
+
*
|
|
4
|
+
* Marketplace manifests are inconsistent: a vendor names a plugin `Gmail`, the
|
|
5
|
+
* next one names it `zapier`, and a curated source names it
|
|
6
|
+
* `aws-agents-for-devsecops`. The UI renders those strings verbatim, so one
|
|
7
|
+
* list reads `Zapier` and the next reads `zapier` beside it. This turns a
|
|
8
|
+
* slug-shaped name into the title-cased form a person would write, in the
|
|
9
|
+
* catalog, so every consumer shows the same thing without its own guesswork.
|
|
10
|
+
*
|
|
11
|
+
* Two rules keep it from "correcting" names that are already right:
|
|
12
|
+
*
|
|
13
|
+
* - a token that carries any uppercase is left exactly as it is, which
|
|
14
|
+
* preserves vendor casing (`Gmail`, `iMessage`, `eToro`, `PDF Viewer`) and
|
|
15
|
+
* makes the function idempotent;
|
|
16
|
+
* - a token with a published non-title-case form comes from `BRAND_TOKENS`
|
|
17
|
+
* instead of the general rule, because title-casing `npm` → `Npm` or
|
|
18
|
+
* `github` → `Github` is exactly the inconsistency this exists to remove.
|
|
19
|
+
*/
|
|
20
|
+
const BRAND_TOKENS = {
|
|
21
|
+
'42crunch': '42Crunch',
|
|
22
|
+
adlc: 'ADLC',
|
|
23
|
+
ai: 'AI',
|
|
24
|
+
bigquery: 'BigQuery',
|
|
25
|
+
brightdata: 'Bright Data',
|
|
26
|
+
amd: 'AMD',
|
|
27
|
+
alloydb: 'AlloyDB',
|
|
28
|
+
api: 'API',
|
|
29
|
+
apis: 'APIs',
|
|
30
|
+
aws: 'AWS',
|
|
31
|
+
cds: 'CDS',
|
|
32
|
+
ckeditor: 'CKEditor',
|
|
33
|
+
clickhouse: 'ClickHouse',
|
|
34
|
+
cli: 'CLI',
|
|
35
|
+
coderabbit: 'CodeRabbit',
|
|
36
|
+
crowdsec: 'CrowdSec',
|
|
37
|
+
crm: 'CRM',
|
|
38
|
+
crowdstrike: 'CrowdStrike',
|
|
39
|
+
css: 'CSS',
|
|
40
|
+
dak: 'DAK',
|
|
41
|
+
dominodatalab: 'Domino Data Lab',
|
|
42
|
+
datadog: 'Datadog',
|
|
43
|
+
fullstory: 'FullStory',
|
|
44
|
+
gitkraken: 'GitKraken',
|
|
45
|
+
devops: 'DevOps',
|
|
46
|
+
devsecops: 'DevSecOps',
|
|
47
|
+
gcp: 'GCP',
|
|
48
|
+
github: 'GitHub',
|
|
49
|
+
gitlab: 'GitLab',
|
|
50
|
+
graphql: 'GraphQL',
|
|
51
|
+
html: 'HTML',
|
|
52
|
+
huggingface: 'Hugging Face',
|
|
53
|
+
ibm: 'IBM',
|
|
54
|
+
idmp: 'IDMP',
|
|
55
|
+
imessage: 'iMessage',
|
|
56
|
+
ios: 'iOS',
|
|
57
|
+
javascript: 'JavaScript',
|
|
58
|
+
json: 'JSON',
|
|
59
|
+
k8s: 'Kubernetes',
|
|
60
|
+
langchain: 'LangChain',
|
|
61
|
+
llm: 'LLM',
|
|
62
|
+
llms: 'LLMs',
|
|
63
|
+
logrocket: 'LogRocket',
|
|
64
|
+
lsp: 'LSP',
|
|
65
|
+
lseg: 'LSEG',
|
|
66
|
+
macos: 'macOS',
|
|
67
|
+
md: 'MD',
|
|
68
|
+
mcp: 'MCP',
|
|
69
|
+
mongodb: 'MongoDB',
|
|
70
|
+
oracledb: 'Oracle Database',
|
|
71
|
+
pagerduty: 'PagerDuty',
|
|
72
|
+
planetscale: 'PlanetScale',
|
|
73
|
+
posthog: 'PostHog',
|
|
74
|
+
mysql: 'MySQL',
|
|
75
|
+
ngs: 'NGS',
|
|
76
|
+
nosql: 'NoSQL',
|
|
77
|
+
nvidia: 'NVIDIA',
|
|
78
|
+
ocr: 'OCR',
|
|
79
|
+
openai: 'OpenAI',
|
|
80
|
+
paypal: 'PayPal',
|
|
81
|
+
pdf: 'PDF',
|
|
82
|
+
postgres: 'Postgres',
|
|
83
|
+
postgresql: 'PostgreSQL',
|
|
84
|
+
pytorch: 'PyTorch',
|
|
85
|
+
sap: 'SAP',
|
|
86
|
+
sdk: 'SDK',
|
|
87
|
+
seo: 'SEO',
|
|
88
|
+
servicenow: 'ServiceNow',
|
|
89
|
+
sql: 'SQL',
|
|
90
|
+
sre: 'SRE',
|
|
91
|
+
tensorflow: 'TensorFlow',
|
|
92
|
+
terraform: 'Terraform',
|
|
93
|
+
twg: 'TWG',
|
|
94
|
+
typescript: 'TypeScript',
|
|
95
|
+
ui: 'UI',
|
|
96
|
+
ui5: 'UI5',
|
|
97
|
+
ux: 'UX',
|
|
98
|
+
whatsapp: 'WhatsApp',
|
|
99
|
+
wordpress: 'WordPress',
|
|
100
|
+
youtube: 'YouTube',
|
|
101
|
+
};
|
|
102
|
+
/** Articles, conjunctions and prepositions stay lowercase inside a name. */
|
|
103
|
+
const SMALL_WORDS = new Set([
|
|
104
|
+
'a',
|
|
105
|
+
'an',
|
|
106
|
+
'and',
|
|
107
|
+
'as',
|
|
108
|
+
'at',
|
|
109
|
+
'by',
|
|
110
|
+
'for',
|
|
111
|
+
'from',
|
|
112
|
+
'in',
|
|
113
|
+
'of',
|
|
114
|
+
'on',
|
|
115
|
+
'or',
|
|
116
|
+
'the',
|
|
117
|
+
'to',
|
|
118
|
+
'with',
|
|
119
|
+
]);
|
|
120
|
+
/**
|
|
121
|
+
* Names whose slug form hides a domain or an initialism, which the token rules
|
|
122
|
+
* below cannot recover: `monday-com` is `monday.com`, not `Monday Com`.
|
|
123
|
+
*/
|
|
124
|
+
const FULL_NAMES = {
|
|
125
|
+
'bigdata-com': 'BigData.com',
|
|
126
|
+
'incident-io': 'Incident.io',
|
|
127
|
+
'monday-com': 'Monday.com',
|
|
128
|
+
vpai: 'VPAI',
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Title-cases a slug-shaped name and leaves a properly cased one alone.
|
|
132
|
+
*
|
|
133
|
+
* Separators become spaces (`adobe-for-creativity` → `Adobe for Creativity`),
|
|
134
|
+
* but a dot does not, so domain-style names keep their shape
|
|
135
|
+
* (`monday.com` → `Monday.com`).
|
|
136
|
+
*/
|
|
137
|
+
export function normalizeDisplayName(value) {
|
|
138
|
+
const trimmed = value.trim();
|
|
139
|
+
const known = FULL_NAMES[trimmed.toLowerCase()];
|
|
140
|
+
if (known !== undefined)
|
|
141
|
+
return known;
|
|
142
|
+
const tokens = trimmed.split(/[-_\s]+/u).filter(Boolean);
|
|
143
|
+
if (tokens.length === 0)
|
|
144
|
+
return value;
|
|
145
|
+
return tokens
|
|
146
|
+
.map((token, index) => {
|
|
147
|
+
const key = token.toLowerCase();
|
|
148
|
+
const brand = BRAND_TOKENS[key];
|
|
149
|
+
if (brand !== undefined)
|
|
150
|
+
return brand;
|
|
151
|
+
// Any uppercase means the vendor already chose a casing.
|
|
152
|
+
if (/\p{Lu}/u.test(token))
|
|
153
|
+
return token;
|
|
154
|
+
if (index > 0 && SMALL_WORDS.has(key))
|
|
155
|
+
return key;
|
|
156
|
+
return `${token.slice(0, 1).toUpperCase()}${token.slice(1)}`;
|
|
157
|
+
})
|
|
158
|
+
.join(' ');
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=display-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-name.js","sourceRoot":"","sources":["../src/display-name.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Catalog, type Harness, type MaterializationPlan } from './schema.js';
|
|
2
|
+
export declare class MaterializationError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly details: readonly string[];
|
|
5
|
+
constructor(code: string, details?: readonly string[]);
|
|
6
|
+
}
|
|
7
|
+
export declare function createMaterializationPlan(input: {
|
|
8
|
+
readonly catalog: Catalog;
|
|
9
|
+
readonly pluginId: string;
|
|
10
|
+
readonly harness: Harness;
|
|
11
|
+
readonly releaseId?: string;
|
|
12
|
+
}): MaterializationPlan;
|
|
13
|
+
export declare function supportedHarnesses(): readonly Harness[];
|
package/dist/harness.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { HarnessSchema, MaterializationPlanSchema, } from './schema.js';
|
|
2
|
+
export class MaterializationError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, details = []) {
|
|
6
|
+
super(`${code}${details.length ? `: ${details.join(', ')}` : ''}`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.details = details;
|
|
9
|
+
this.name = 'MaterializationError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function createMaterializationPlan(input) {
|
|
13
|
+
const plugin = input.catalog.plugins.find((candidate) => candidate.pluginId === input.pluginId);
|
|
14
|
+
if (!plugin)
|
|
15
|
+
throw new MaterializationError('PLUGIN_NOT_FOUND', [input.pluginId]);
|
|
16
|
+
const release = plugin.availableReleases.find((candidate) => candidate.releaseId === (input.releaseId ?? plugin.currentReleaseId));
|
|
17
|
+
if (!release)
|
|
18
|
+
throw new MaterializationError('RELEASE_NOT_FOUND', [
|
|
19
|
+
input.releaseId ?? plugin.currentReleaseId,
|
|
20
|
+
]);
|
|
21
|
+
const compatibility = plugin.harnessCompatibility[input.harness];
|
|
22
|
+
if (!compatibility)
|
|
23
|
+
throw new MaterializationError('HARNESS_UNSUPPORTED', [input.harness]);
|
|
24
|
+
return MaterializationPlanSchema.parse({
|
|
25
|
+
planVersion: 1,
|
|
26
|
+
pluginId: plugin.pluginId,
|
|
27
|
+
releaseId: release.releaseId,
|
|
28
|
+
harness: input.harness,
|
|
29
|
+
source: {
|
|
30
|
+
repositoryUrl: release.resolvedRepositoryUrl,
|
|
31
|
+
commitSha: release.resolvedCommitSha,
|
|
32
|
+
pluginSubdirectory: release.pluginSubdirectory,
|
|
33
|
+
contentDigest: release.canonicalContentDigest,
|
|
34
|
+
},
|
|
35
|
+
targetLayout: targetLayout(input.harness),
|
|
36
|
+
files: release.fileIndex
|
|
37
|
+
.map((sourcePath) => filePlan(release, sourcePath, input.harness, compatibility.status))
|
|
38
|
+
.toSorted((left, right) => left.targetPath.localeCompare(right.targetPath) ||
|
|
39
|
+
left.sourcePath.localeCompare(right.sourcePath)),
|
|
40
|
+
configuration: {
|
|
41
|
+
compatibilityStatus: compatibility.status,
|
|
42
|
+
sourceDialect: release.releaseMetadata.sourceDialect ?? 'unknown',
|
|
43
|
+
executeInstallation: false,
|
|
44
|
+
},
|
|
45
|
+
requiredConnectors: release.requiredConnectors,
|
|
46
|
+
requiredCredentials: release.requiredCredentials,
|
|
47
|
+
policyConstraints: [
|
|
48
|
+
'This plan describes files and translation only; it does not install or execute upstream code.',
|
|
49
|
+
...release.permissionSensitiveChanges.map((type) => `Control Plane must authorize ${type} capability explicitly.`),
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function targetLayout(harness) {
|
|
54
|
+
switch (harness) {
|
|
55
|
+
case 'codex':
|
|
56
|
+
return {
|
|
57
|
+
root: '.agents',
|
|
58
|
+
notes: [
|
|
59
|
+
'Skills and instructions map to the Codex .agents layout.',
|
|
60
|
+
'MCP and executable capabilities require Control Plane policy.',
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
case 'claude-code':
|
|
64
|
+
return {
|
|
65
|
+
root: '.claude',
|
|
66
|
+
notes: [
|
|
67
|
+
'Claude Code-native files can be copied when the source dialect is Claude.',
|
|
68
|
+
'Hooks and MCP configuration still require explicit runtime authorization.',
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
case 'cursor':
|
|
72
|
+
return {
|
|
73
|
+
root: '.cursor',
|
|
74
|
+
notes: [
|
|
75
|
+
'Cursor rules and skills map to the .cursor layout.',
|
|
76
|
+
'MCP configuration is declarative and is not executed by this plan.',
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
case 'pi':
|
|
80
|
+
return {
|
|
81
|
+
root: '.pi',
|
|
82
|
+
notes: [
|
|
83
|
+
'Pi receives portable SKILL.md and MCP metadata only.',
|
|
84
|
+
'No Pi process or package lifecycle is invoked.',
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
case 'hermes':
|
|
88
|
+
return {
|
|
89
|
+
root: '.hermes',
|
|
90
|
+
notes: [
|
|
91
|
+
'Hermes receives portable skill and MCP metadata only.',
|
|
92
|
+
'Harness-specific translation remains explicit in each file action.',
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
case 'opencode':
|
|
96
|
+
return {
|
|
97
|
+
root: '.opencode',
|
|
98
|
+
notes: [
|
|
99
|
+
'OpenCode receives portable instruction and MCP metadata only.',
|
|
100
|
+
'Executable capabilities are excluded.',
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
case 'generic-skill-mcp':
|
|
104
|
+
return {
|
|
105
|
+
root: '.agent',
|
|
106
|
+
notes: ['Generic SKILL.md and MCP-compatible files are copied when recognized.'],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function filePlan(release, sourcePath, harness, status) {
|
|
111
|
+
const type = capabilityTypeForPath(release, sourcePath);
|
|
112
|
+
const directory = sourcePath.includes('/') ? sourcePath.slice(0, sourcePath.lastIndexOf('/')) : '';
|
|
113
|
+
if (status === 'unsupported' || type === 'executable' || type === 'unknown') {
|
|
114
|
+
return {
|
|
115
|
+
sourcePath,
|
|
116
|
+
targetPath: sourcePath,
|
|
117
|
+
action: 'unsupported',
|
|
118
|
+
reason: `${type} capability is not executable through a materialization plan.`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (type === 'hook' ||
|
|
122
|
+
type === 'browser' ||
|
|
123
|
+
type === 'scheduled-task' ||
|
|
124
|
+
type === 'ui-component') {
|
|
125
|
+
return {
|
|
126
|
+
sourcePath,
|
|
127
|
+
targetPath: sourcePath,
|
|
128
|
+
action: 'ignore',
|
|
129
|
+
reason: `${type} is cataloged but has no portable target layout for ${harness}.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const targetPath = targetForType(type, sourcePath, directory, harness);
|
|
133
|
+
const action = type === 'skill' || type === 'rule' || (type === 'mcp-server' && status === 'native')
|
|
134
|
+
? 'copy'
|
|
135
|
+
: 'translate';
|
|
136
|
+
return {
|
|
137
|
+
sourcePath,
|
|
138
|
+
targetPath,
|
|
139
|
+
action: action,
|
|
140
|
+
reason: action === 'copy'
|
|
141
|
+
? `Copy recognized ${type} content.`
|
|
142
|
+
: `Translate ${type} metadata into the ${harness} layout.`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function capabilityTypeForPath(release, path) {
|
|
146
|
+
const match = release.capabilities.find((capability) => capability.paths.includes(path));
|
|
147
|
+
return match?.type ?? 'unknown';
|
|
148
|
+
}
|
|
149
|
+
function targetForType(type, path, directory, harness) {
|
|
150
|
+
if (type === 'skill')
|
|
151
|
+
return `${targetLayout(harness).root}/skills/${directory || basenameWithoutExtension(path)}/${path.endsWith('SKILL.md') ? 'SKILL.md' : basenameWithoutExtension(path)}`;
|
|
152
|
+
if (type === 'rule')
|
|
153
|
+
return `${targetLayout(harness).root}/rules/${basenameWithoutExtension(path)}.md`;
|
|
154
|
+
if (type === 'command')
|
|
155
|
+
return `${targetLayout(harness).root}/commands/${basenameWithoutExtension(path)}.md`;
|
|
156
|
+
if (type === 'agent')
|
|
157
|
+
return `${targetLayout(harness).root}/agents/${basenameWithoutExtension(path)}.md`;
|
|
158
|
+
if (type === 'mcp-server')
|
|
159
|
+
return `${targetLayout(harness).root}/mcp/${basenameWithoutExtension(path)}.json`;
|
|
160
|
+
return `${targetLayout(harness).root}/${path}`;
|
|
161
|
+
}
|
|
162
|
+
function basenameWithoutExtension(path) {
|
|
163
|
+
const basename = path.split('/').at(-1) ?? path;
|
|
164
|
+
return basename.replace(/\.[^.]+$/, '');
|
|
165
|
+
}
|
|
166
|
+
export function supportedHarnesses() {
|
|
167
|
+
return HarnessSchema.options;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Catalog } from './schema.js';
|
|
2
|
+
import { type PublishedAsset } from './index.js';
|
|
3
|
+
import { type IconContentType } from './icons.js';
|
|
4
|
+
import type { ConsumerIndex } from './consumer-index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where an icon's bytes come from, so a mirror needs no tree walk and no
|
|
7
|
+
* rediscovery: `content` refetches a file at the pinned commit, `favicon`
|
|
8
|
+
* refetches the site icon the catalog recorded.
|
|
9
|
+
*/
|
|
10
|
+
export type IconSource = {
|
|
11
|
+
readonly kind: 'content';
|
|
12
|
+
readonly asset: string;
|
|
13
|
+
readonly digest: string;
|
|
14
|
+
readonly bytes: number;
|
|
15
|
+
readonly contentType: IconContentType;
|
|
16
|
+
readonly repositoryUrl: string;
|
|
17
|
+
readonly commitSha: string;
|
|
18
|
+
readonly pluginSubdirectory: string;
|
|
19
|
+
/** Path relative to `pluginSubdirectory`, as recorded in the catalog. */
|
|
20
|
+
readonly path: string;
|
|
21
|
+
/** Path including the plugin subdirectory, for a raw content fetch. */
|
|
22
|
+
readonly sourcePath: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'favicon';
|
|
25
|
+
readonly asset: string;
|
|
26
|
+
readonly digest: string;
|
|
27
|
+
readonly bytes: number;
|
|
28
|
+
readonly contentType: IconContentType;
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly homepage: string;
|
|
31
|
+
};
|
|
32
|
+
export interface IconMirrorResult {
|
|
33
|
+
/** Icons whose verified bytes are present in the assets directory. */
|
|
34
|
+
readonly staged: readonly PublishedAsset[];
|
|
35
|
+
/** Icons that could not be staged, with the reason, never silently dropped. */
|
|
36
|
+
readonly skipped: readonly {
|
|
37
|
+
readonly asset: string;
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Maps every mirrored icon back to the exact commit it was resolved from, by
|
|
43
|
+
* matching the digest the product advertises. Canonical variants are inspected
|
|
44
|
+
* first, mirroring the order product records were built in.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveIconSources(catalog: Pick<Catalog, 'plugins'>, index: ConsumerIndex): readonly IconSource[];
|
|
47
|
+
/**
|
|
48
|
+
* Writes mirrored icon bytes beside the published artifacts.
|
|
49
|
+
*
|
|
50
|
+
* Bytes are verified against the digest the catalog advertises before they are
|
|
51
|
+
* staged, so a mirror can never publish content that differs from what the
|
|
52
|
+
* index promised. Staging never overwrites a differing file: content-addressed
|
|
53
|
+
* names make an existing file either identical or a build error.
|
|
54
|
+
*/
|
|
55
|
+
export declare function stageIconAssets(input: {
|
|
56
|
+
readonly sources: readonly IconSource[];
|
|
57
|
+
readonly assetsDirectory: string;
|
|
58
|
+
/** Reads the icon bytes for a source; live fetches raw content, offline reads fixtures. */
|
|
59
|
+
readonly readBytes: (source: IconSource) => Promise<Uint8Array>;
|
|
60
|
+
}): Promise<IconMirrorResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Writes one verified icon into the assets directory.
|
|
63
|
+
*
|
|
64
|
+
* The build already holds the exact bytes it recorded a digest for, so staging
|
|
65
|
+
* them here removes the re-fetch a later mirror would need. That matters for
|
|
66
|
+
* site icons: their URL is not content-addressed, so a vendor can change the
|
|
67
|
+
* file between resolution and publication.
|
|
68
|
+
*/
|
|
69
|
+
export declare function stageIconBytes(assetsDirectory: string, asset: string, bytes: Uint8Array): Promise<void>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { byteDigest } from './index.js';
|
|
4
|
+
import { iconAssetName, inspectIconBytes } from './icons.js';
|
|
5
|
+
/**
|
|
6
|
+
* Maps every mirrored icon back to the exact commit it was resolved from, by
|
|
7
|
+
* matching the digest the product advertises. Canonical variants are inspected
|
|
8
|
+
* first, mirroring the order product records were built in.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveIconSources(catalog, index) {
|
|
11
|
+
const byAsset = new Map();
|
|
12
|
+
for (const plugin of catalog.plugins) {
|
|
13
|
+
for (const release of plugin.availableReleases) {
|
|
14
|
+
const icon = release.icon;
|
|
15
|
+
if (!icon)
|
|
16
|
+
continue;
|
|
17
|
+
const asset = iconAssetName(icon.digest, icon.contentType);
|
|
18
|
+
if (byAsset.has(asset))
|
|
19
|
+
continue;
|
|
20
|
+
if (icon.kind === 'favicon') {
|
|
21
|
+
byAsset.set(asset, {
|
|
22
|
+
kind: 'favicon',
|
|
23
|
+
asset,
|
|
24
|
+
digest: icon.digest,
|
|
25
|
+
bytes: icon.bytes,
|
|
26
|
+
contentType: icon.contentType,
|
|
27
|
+
url: icon.url,
|
|
28
|
+
homepage: icon.homepage,
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const subdirectory = release.pluginSubdirectory && release.pluginSubdirectory !== '.'
|
|
33
|
+
? release.pluginSubdirectory
|
|
34
|
+
: '';
|
|
35
|
+
byAsset.set(asset, {
|
|
36
|
+
kind: 'content',
|
|
37
|
+
asset,
|
|
38
|
+
digest: icon.digest,
|
|
39
|
+
bytes: icon.bytes,
|
|
40
|
+
contentType: icon.contentType,
|
|
41
|
+
repositoryUrl: release.resolvedRepositoryUrl,
|
|
42
|
+
commitSha: release.resolvedCommitSha,
|
|
43
|
+
pluginSubdirectory: subdirectory,
|
|
44
|
+
path: icon.path,
|
|
45
|
+
sourcePath: subdirectory ? `${subdirectory}/${icon.path}` : icon.path,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const sources = [];
|
|
50
|
+
for (const product of Object.values(index.products)) {
|
|
51
|
+
const icon = product.icon;
|
|
52
|
+
if (!icon)
|
|
53
|
+
continue;
|
|
54
|
+
const source = byAsset.get(icon.asset);
|
|
55
|
+
if (source)
|
|
56
|
+
sources.push(source);
|
|
57
|
+
}
|
|
58
|
+
return sources.toSorted((left, right) => left.asset.localeCompare(right.asset));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Writes mirrored icon bytes beside the published artifacts.
|
|
62
|
+
*
|
|
63
|
+
* Bytes are verified against the digest the catalog advertises before they are
|
|
64
|
+
* staged, so a mirror can never publish content that differs from what the
|
|
65
|
+
* index promised. Staging never overwrites a differing file: content-addressed
|
|
66
|
+
* names make an existing file either identical or a build error.
|
|
67
|
+
*/
|
|
68
|
+
export async function stageIconAssets(input) {
|
|
69
|
+
const staged = [];
|
|
70
|
+
const skipped = [];
|
|
71
|
+
await fs.mkdir(input.assetsDirectory, { recursive: true });
|
|
72
|
+
for (const source of input.sources) {
|
|
73
|
+
const destination = join(input.assetsDirectory, source.asset);
|
|
74
|
+
const existing = await fs.readFile(destination).catch(() => undefined);
|
|
75
|
+
if (existing && byteDigest(existing) === source.digest) {
|
|
76
|
+
staged.push(publishedAssetOf(source));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (existing) {
|
|
80
|
+
skipped.push({ asset: source.asset, reason: 'ASSET_CONFLICT' });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
let bytes;
|
|
84
|
+
try {
|
|
85
|
+
bytes = await input.readBytes(source);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
skipped.push({
|
|
89
|
+
asset: source.asset,
|
|
90
|
+
reason: `FETCH_FAILED: ${error instanceof Error ? error.message : String(error)}`,
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const inspected = inspectIconBytes(bytes);
|
|
95
|
+
if (!('contentType' in inspected)) {
|
|
96
|
+
skipped.push({ asset: source.asset, reason: inspected.rejected });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (inspected.contentType !== source.contentType) {
|
|
100
|
+
skipped.push({ asset: source.asset, reason: 'CONTENT_TYPE_CHANGED' });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (byteDigest(bytes) !== source.digest || bytes.byteLength !== source.bytes) {
|
|
104
|
+
skipped.push({ asset: source.asset, reason: 'DIGEST_MISMATCH' });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
await fs.mkdir(dirname(destination), { recursive: true });
|
|
108
|
+
await fs.writeFile(destination, bytes);
|
|
109
|
+
staged.push(publishedAssetOf(source));
|
|
110
|
+
}
|
|
111
|
+
// Distinct assets: two products can share one mark, and the catalog declares
|
|
112
|
+
// each asset once.
|
|
113
|
+
return { staged: dedupeAssets(staged), skipped };
|
|
114
|
+
}
|
|
115
|
+
function dedupeAssets(assets) {
|
|
116
|
+
const unique = new Map();
|
|
117
|
+
for (const asset of assets)
|
|
118
|
+
unique.set(asset.name, asset);
|
|
119
|
+
return [...unique.values()];
|
|
120
|
+
}
|
|
121
|
+
function publishedAssetOf(source) {
|
|
122
|
+
return {
|
|
123
|
+
name: source.asset,
|
|
124
|
+
digest: source.digest,
|
|
125
|
+
bytes: source.bytes,
|
|
126
|
+
contentType: source.contentType,
|
|
127
|
+
kind: source.kind,
|
|
128
|
+
origin: source.kind === 'content' ? source.sourcePath : source.url,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Writes one verified icon into the assets directory.
|
|
133
|
+
*
|
|
134
|
+
* The build already holds the exact bytes it recorded a digest for, so staging
|
|
135
|
+
* them here removes the re-fetch a later mirror would need. That matters for
|
|
136
|
+
* site icons: their URL is not content-addressed, so a vendor can change the
|
|
137
|
+
* file between resolution and publication.
|
|
138
|
+
*/
|
|
139
|
+
export async function stageIconBytes(assetsDirectory, asset, bytes) {
|
|
140
|
+
const destination = join(assetsDirectory, asset);
|
|
141
|
+
const existing = await fs.readFile(destination).catch(() => undefined);
|
|
142
|
+
if (existing && byteDigest(existing) === byteDigest(bytes))
|
|
143
|
+
return;
|
|
144
|
+
await fs.mkdir(dirname(destination), { recursive: true });
|
|
145
|
+
await fs.writeFile(destination, bytes);
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=icon-mirror.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon-mirror.js","sourceRoot":"","sources":["../src/icon-mirror.ts"],"names":[],"mappings":"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"}
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time icon resolution.
|
|
3
|
+
*
|
|
4
|
+
* Marketplaces do not declare an icon field, so a plugin's mark is a file in
|
|
5
|
+
* its own content (`assets/logo.svg`, `assets/icon-large.png`, ...). Resolving
|
|
6
|
+
* one at compile time removes a runtime guess from consumers, and mirroring the
|
|
7
|
+
* bytes at publish time removes the upstream fetch entirely.
|
|
8
|
+
*/
|
|
9
|
+
/** Why a candidate file was rejected as an icon. */
|
|
10
|
+
export type IconRejection = 'CONTENT_TYPE_UNSUPPORTED' | 'EMPTY_FILE' | 'SIZE_LIMIT_EXCEEDED' | 'SVG_ACTIVE_CONTENT' | 'SVG_EXTERNAL_REFERENCE';
|
|
11
|
+
/** Content types the catalog will publish as a brand mark. */
|
|
12
|
+
export type IconContentType = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'image/x-icon';
|
|
13
|
+
/** Icon file size ceiling; a brand mark is never a large asset. */
|
|
14
|
+
export declare const MAX_ICON_BYTES: number;
|
|
15
|
+
/**
|
|
16
|
+
* Chooses one icon file from a release's file index, deterministically.
|
|
17
|
+
*
|
|
18
|
+
* Preference: `assets/icon.svg` before `assets/icon.png` before `assets/logo.*`,
|
|
19
|
+
* an `icon`/`logo` stem before `avatar`/`mark`/`brand`, an `assets` directory
|
|
20
|
+
* before other asset directories, SVG before raster for the same stem, and a
|
|
21
|
+
* `-large` variant before `-small`. Anything that looks like documentation or a
|
|
22
|
+
* screenshot is never selected. Ties break on path so the result is stable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectIconPath(fileIndex: readonly string[]): string | null;
|
|
25
|
+
/** Sniffs a supported image type from magic bytes; SVG is detected as text. */
|
|
26
|
+
export declare function sniffContentType(bytes: Uint8Array): IconContentType | null;
|
|
27
|
+
/**
|
|
28
|
+
* Verifies icon bytes before they are published.
|
|
29
|
+
*
|
|
30
|
+
* SVG is text that a browser can execute when it is inlined, so active content
|
|
31
|
+
* and external references are rejected outright. Consumers must still render a
|
|
32
|
+
* mirrored SVG through an `<img>` element and never inline it into the DOM.
|
|
33
|
+
*/
|
|
34
|
+
export declare function inspectIconBytes(bytes: Uint8Array): {
|
|
35
|
+
contentType: IconContentType;
|
|
36
|
+
} | {
|
|
37
|
+
rejected: IconRejection;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Content-addressed asset name. Release assets cannot contain `/`, so the
|
|
41
|
+
* digest prefix and extension form one flat name that any release can host.
|
|
42
|
+
*/
|
|
43
|
+
export declare function iconAssetName(digest: string, contentType: IconContentType): string;
|
|
44
|
+
/**
|
|
45
|
+
* Curation override for a product's brand mark, keyed by product key.
|
|
46
|
+
*
|
|
47
|
+
* `null` publishes no mark; a path forces a file inside the plugin content; an
|
|
48
|
+
* `https://` value names the vendor site to take the mark from. The override
|
|
49
|
+
* exists because the selection rule is a heuristic over vendor filenames: many
|
|
50
|
+
* products ship no conventional mark and declare nothing but a repository, and
|
|
51
|
+
* a curator may know the real brand source.
|
|
52
|
+
*/
|
|
53
|
+
export interface ProductIconOverrides {
|
|
54
|
+
readonly schemaVersion: 1;
|
|
55
|
+
readonly overrides: Readonly<Record<string, string | null>>;
|
|
56
|
+
}
|
|
57
|
+
export declare function parseProductIconOverrides(input: unknown): ProductIconOverrides;
|
|
58
|
+
/**
|
|
59
|
+
* Reads the override for a product: `forced` names a content path or `null` to
|
|
60
|
+
* publish no mark, and `site` names a vendor site to take the mark from.
|
|
61
|
+
*/
|
|
62
|
+
export declare function iconOverrideFor(overrides: ProductIconOverrides | undefined, productKey: string): {
|
|
63
|
+
readonly forced?: string | null;
|
|
64
|
+
readonly site?: string;
|
|
65
|
+
} | undefined;
|