1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2026-05-22 08:55:42 +02:00

Add plugin marketplace (for official plugins) (#451)

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Anbraten
2021-10-19 18:54:01 +02:00
committed by GitHub
parent 5990d32fd3
commit 0812a29163
13 changed files with 9193 additions and 1 deletions
@@ -0,0 +1,125 @@
import { LoadContext, Plugin, PluginContentLoadedActions } from '@docusaurus/types';
import { Octokit } from '@octokit/rest';
import { components as OctokitComponents } from '@octokit/openapi-types';
import path from 'path';
import { Content, WoodpeckerPlugin, WoodpeckerPluginHeader } from './types';
import * as markdown from './markdown';
const octokit = new Octokit();
async function getDocs(repoName: string): Promise<string | undefined> {
try {
const docsResult = (
await octokit.repos.getContent({
owner: 'woodpecker-ci',
repo: repoName,
path: '/docs.md',
})
).data as OctokitComponents['schemas']['content-file'];
return Buffer.from(docsResult.content, 'base64').toString('ascii');
} catch (e) {
console.error("Can't fetch docs file for repository", repoName, e);
}
return undefined;
}
async function loadContent(): Promise<Content> {
const repositories = (
await octokit.rest.search.repos({
// search for repos in woodpecker-ci org with the topic: woodpecker-plugin including forks
q: 'org:woodpecker-ci topic:woodpecker-plugin fork:true',
})
).data.items;
console.log(repositories.map((r) => r.name));
const plugins = (
await Promise.all(
repositories.map(async (repo) => {
const docs = await getDocs(repo.name);
if (!docs) {
return undefined;
}
const header = markdown.getHeader<WoodpeckerPluginHeader>(docs);
const body = markdown.getContent(docs);
const plugin: WoodpeckerPlugin = {
name: header?.name || repo.name,
repoName: repo.name,
url: repo.html_url,
icon: header?.icon,
description: header?.description,
docs: body,
};
return plugin;
}),
)
).filter((plugin) => plugin);
return {
plugins,
};
}
async function contentLoaded({
content: { plugins },
actions,
}: {
content: Content;
actions: PluginContentLoadedActions;
}): Promise<void> {
const { createData, addRoute } = actions;
const pluginsJsonPath = await createData('plugins.json', JSON.stringify(plugins));
await Promise.all(
plugins.map(async (plugin) => {
const pluginJsonPath = await createData(`plugin-${plugin.repoName}.json`, JSON.stringify(plugin));
addRoute({
path: `/plugins/${plugin.repoName}`,
component: '@theme/WoodpeckerPlugin',
modules: {
plugin: pluginJsonPath,
},
exact: true,
});
}),
);
addRoute({
path: '/plugins',
component: '@theme/WoodpeckerPluginList',
modules: {
plugins: pluginsJsonPath,
},
exact: true,
});
}
export default function pluginWoodpeckerPluginsIndex(context: LoadContext, options: any): Plugin<Content> {
return {
name: 'woodpecker-plugins',
loadContent,
contentLoaded,
getThemePath() {
return path.join(__dirname, '..', 'dist', 'theme');
},
getTypeScriptThemePath() {
return path.join(__dirname, '..', 'src', 'theme');
},
getPathsToWatch() {
return [path.join(__dirname, '..', 'dist', '**', '*.{js,jsx}')];
},
};
}
const getSwizzleComponentList = (): string[] => {
return ['WoodpeckerPluginList', 'WoodpeckerPlugin'];
};
export { getSwizzleComponentList };
@@ -0,0 +1,37 @@
import marked from 'marked';
const tokens = ['---', '---'];
const regexHeader = new RegExp('^' + tokens[0] + '([\\s|\\S]*?)' + tokens[1]);
const regexContent = new RegExp(
'^ *?\\' + tokens[0] + '[^]*?' + tokens[1] + '*'
);
export function getHeader<T = any>(data: string): T {
const header = getRawHeader(data);
const tmpObj = {};
const lines = header.trim().split('\n');
lines.forEach((line, i) => {
var arr = line.trim().split(':');
tmpObj[arr.shift()] = arr.join(':').trim();
});
return tmpObj as T;
}
export function getRawHeader(data: string): string {
const header = regexHeader.exec(data);
if (!header) {
new Error("Can't get the header");
}
return header[1];
}
export function getContent(data): string {
const content = data.replace(regexContent, '').replace(/<!--(.*?)-->/gm, '');
if (!content) {
throw new Error("Can't get the content");
}
return marked(content);
}
@@ -0,0 +1,37 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import { WoodpeckerPlugin as WoodpeckerPluginType } from '../types';
export function WoodpeckerPlugin({ plugin }: { plugin: WoodpeckerPluginType }) {
return (
<Layout
title="Woodpecker CI plugins"
description="List of Woodpecker-CI plugins"
>
<main className={clsx("container margin-vert--lg")}>
<section>
<div className={clsx("container")}>
<a href="/plugins">&lt;&lt; Back to plugin list</a>
<div className={clsx("row")}>
<div className={clsx("col col--10")}>
<h1>{plugin.name}</h1>
<p>{plugin.description}</p>
<a href={plugin.url} target="_blank" rel="noopener noreferrer">
{plugin.url}
</a>
</div>
<div className={clsx("col col--2")}>
<img src={plugin.icon} width="150" height="150" />
</div>
</div>
<hr />
<div dangerouslySetInnerHTML={{ __html: plugin.docs }} />
</div>
</section>
</main>
</Layout>
);
}
export default WoodpeckerPlugin;
@@ -0,0 +1,91 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import { WoodpeckerPlugin } from '../types';
function PluginPanel({ plugin }: { plugin: WoodpeckerPlugin }) {
const pluginUrl = `/plugins/${plugin.repoName}`;
return (
<div className={clsx('col col--6')}>
<div className={clsx('card margin-horiz--sm margin-vert--md ')}>
<div className={clsx('card__header row')}>
<div className={clsx('col col--8')}>
<a href={pluginUrl}>
<h3>{plugin.name}</h3>
</a>
<p>{plugin.description}</p>
</div>
<a href={pluginUrl} className={clsx('col col--4 text--right')}>
<img src={plugin.icon} width="100" height="100" />
</a>
</div>
<div className={clsx('card__footer')}>
<a href={pluginUrl} className={clsx('button button--secondary button--outline button--block ')}>
Open {plugin.name}
</a>
</div>
</div>
</div>
);
}
export function WoodpeckerPluginList({ plugins }: { plugins: WoodpeckerPlugin[] }) {
const applyForIndexUrl =
'https://github.com/woodpecker-ci/woodpecker/issues/new?labels=plugin&template=plugin_index.yml';
return (
<Layout title="Woodpecker CI plugins" description="List of all Woodpecker-CI plugins">
<main className="container margin-vert--lg">
<section>
<div className="container">
<div className="row">
{plugins.map((plugin, idx) => (
<PluginPanel key={idx} plugin={plugin} />
))}
{/* <div className={clsx('col col--6')}>
<div className={clsx('card margin-horiz--sm margin-vert--md ')}>
<div className={clsx('card__header row')}>
<div className={clsx('col col--8')}>
<a href={applyForIndexUrl}>
<h3>Add your own plugin</h3>
</a>
<p>You can simply add your own plugin to this index.</p>
</div>
<a
href={applyForIndexUrl}
target="_blank"
rel="noopener noreferrer"
className={clsx('col col--4 text--right')}
>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M88.2357 38.0952H61.9048V11.7643C61.9048 5.29524 56.4714 0 50 0C43.5286 0 38.0952 5.29524 38.0952 11.7643V38.0952H11.7643C5.29524 38.0952 0 43.5286 0 50C0 56.4714 5.29524 61.9048 11.7643 61.9048H38.0952V88.2357C38.0952 94.7048 43.5286 100 50 100C56.4714 100 61.9048 94.7048 61.9048 88.2357V61.9048H88.2357C94.7048 61.9048 100 56.4714 100 50C100 43.5286 94.7048 38.0952 88.2357 38.0952Z"
fill="#4CAF50"
/>
</svg>
</a>
</div>
<div className={clsx('card__footer')}>
<a
href={applyForIndexUrl}
className={clsx('button button--secondary button--outline button--block ')}
>
Add your own plugin
</a>
</div>
</div>
</div> */}
</div>
</div>
</section>
</main>
</Layout>
);
}
export default WoodpeckerPluginList;
@@ -0,0 +1,18 @@
export type WoodpeckerPluginHeader = {
name?: string;
description?: string;
icon?: string;
};
export type WoodpeckerPlugin = {
name: string;
repoName: string;
description: string;
url: string;
icon: string;
docs: string;
};
export type Content = {
plugins: WoodpeckerPlugin[];
};