Repository navigation
Expand file tree
/
Copy pathproviders.js
More file actions
162 lines (148 loc) · 6.7 KB
/
Copy pathproviders.js
File metadata and controls
162 lines (148 loc) · 6.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Provider catalog helpers. The catalog itself is config, not code:
// src/configs/providers.json. This module reads it, validates it, derives
// each provider's slug and route path, and exposes the views the site uses:
//
// PROVIDER_CATEGORIES the catalog with `slug` and `path` added to every
// provider (the tiles and TOC on /providers)
// PROVIDERS flat list in catalog order, with `category`
// providerRoutes() /providers/<slug>: one per entry, no exceptions
// registryRoutes() /registry/<name>: entries plus registryAliases
//
// providerRoutes() and registryRoutes() describe the two families of short
// URLs that 301 to the microsites. They are Netlify rules in netlify.toml,
// written by scripts/generate-provider-redirects.js, not Docusaurus routes:
// the tiles and the navbar link straight to `href`.
//
// Catalog entry fields (src/configs/providers.json):
// name display name: tile label, default menu label
// href microsite URL, must be https://<slug>-provider.stackql.io/
// (the slug is derived from it)
// icon tile icon, a path under static/
// deployedBy hosting platform of the microsite: "netlify" or
// "github" (GitHub Pages)
// repo GitHub repository of the provider and its microsite,
// as owner/name (stackql-registry/stackql-provider-<x>)
// branch the branch the microsite is deployed from (today
// always the repository's default branch)
// deployedBy, repo and branch are not rendered on this
// site; they are published in /providers.json so
// cross-repo automation can tell where each microsite
// lives and how to redeploy it
// invertOnDark optional, invert the icon in dark mode
// registryAliases optional, extra names that redirect to this microsite
// under /registry only (never under /providers), so a
// provider family can expose one canonical inbound link
//
// To add a provider, add one entry to the JSON and run
// scripts/generate-provider-redirects.js. Nothing else changes: the tile,
// the navbar menu entry (src/theme/NavbarItem/ProvidersDropdownNavbarItem)
// and the catalog published at /providers.json (plugins/provider-catalog),
// which the provider microsites' shared chrome reads at build time, all
// follow.
//
// CommonJS on purpose: docusaurus.config.js and the redirect generator
// require() this; docs/providers.md imports it via webpack.
const catalog = require('../configs/providers.json');
// Every microsite is https://<slug>-provider.stackql.io/. The slug is the
// path segment used by the redirect routes.
const MICROSITE_RE = /^https:\/\/([a-z0-9-]+)-provider\.stackql\.io\/$/;
// Hosting platforms a microsite can be deployed by (the `deployedBy` field).
const DEPLOYED_BY = ['netlify', 'github'];
// `repo` is a GitHub owner/name, nothing more (no URL, no .git).
const REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
function slugFromHref(href) {
const match = MICROSITE_RE.exec(href);
if (!match) {
throw new Error(
`[providers] href "${href}" is not a https://<slug>-provider.stackql.io/ microsite URL`,
);
}
return match[1];
}
function requireString(value, field, where) {
if (typeof value !== 'string' || value.length === 0) {
throw new Error(`[providers] ${where}: "${field}" must be a non-empty string`);
}
}
if (!Array.isArray(catalog)) {
throw new Error('[providers] src/configs/providers.json must be an array of categories');
}
// Validated copy of the catalog with `slug` and `path` on every provider.
// The required JSON object is left untouched.
const PROVIDER_CATEGORIES = catalog.map((category, ci) => {
requireString(category.id, 'id', `category #${ci}`);
requireString(category.name, 'name', `category "${category.id}"`);
if (!Array.isArray(category.providers)) {
throw new Error(`[providers] category "${category.id}": "providers" must be an array`);
}
return {
...category,
providers: category.providers.map((provider, pi) => {
const where = `category "${category.id}" provider #${pi}`;
requireString(provider.name, 'name', where);
requireString(provider.href, 'href', `"${provider.name}"`);
requireString(provider.icon, 'icon', `"${provider.name}"`);
if (!DEPLOYED_BY.includes(provider.deployedBy)) {
throw new Error(
`[providers] "${provider.name}": "deployedBy" must be one of ${DEPLOYED_BY.join(', ')}`,
);
}
requireString(provider.repo, 'repo', `"${provider.name}"`);
if (!REPO_RE.test(provider.repo)) {
throw new Error(`[providers] "${provider.name}": "repo" must be a GitHub owner/name`);
}
requireString(provider.branch, 'branch', `"${provider.name}"`);
if (provider.registryAliases !== undefined && !Array.isArray(provider.registryAliases)) {
throw new Error(`[providers] "${provider.name}": "registryAliases" must be an array`);
}
const slug = slugFromHref(provider.href);
return { ...provider, slug, path: `/providers/${slug}` };
}),
};
});
// Flat list in catalog order, each entry with the id of its category.
const PROVIDERS = PROVIDER_CATEGORIES.flatMap((category) =>
category.providers.map((provider) => ({ ...provider, category: category.id })),
);
function claim(owner, name, ownerName, surface) {
if (owner.has(name)) {
throw new Error(
`[providers] ${surface} name "${name}" is claimed by both "${owner.get(name)}" and "${ownerName}"`,
);
}
owner.set(name, ownerName);
}
// /providers/<slug>: one { slug, href, name } per catalog entry, no
// exceptions. Throws if two entries derive the same slug.
function providerRoutes() {
const owner = new Map();
return PROVIDERS.map((provider) => {
claim(owner, provider.slug, provider.name, '/providers');
return { slug: provider.slug, href: provider.href, name: provider.name };
});
}
// /registry/<name>: every catalog entry plus each entry's registryAliases.
// Throws if an alias collides with another entry's slug or alias.
function registryRoutes() {
const owner = new Map();
const routes = [];
for (const provider of PROVIDERS) {
for (const name of [provider.slug, ...(provider.registryAliases || [])]) {
claim(owner, name, provider.name, '/registry');
routes.push({ slug: name, href: provider.href, name: provider.name });
}
}
return routes;
}
// Validate at load so a bad entry fails the config, not a page render.
providerRoutes();
registryRoutes();
module.exports = {
PROVIDER_CATEGORIES,
PROVIDERS,
providerRoutes,
registryRoutes,
slugFromHref,
MICROSITE_RE,
DEPLOYED_BY,
};