Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# Generated files
.docusaurus
.cache-loader
/changelog
/downloaded-changelogs
/changelog-processing

# Generated Graphql Docs
/graphql
Expand Down
43 changes: 42 additions & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const path = require('path');

const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const lightCodeTheme = require('prism-react-renderer/themes/github');

const graphqlMarkdownConfig = require('./graphql-markdown.config');
const { specs } = require('./redoc.config');
const DOCS_URL = 'https://docs.epilot.io';

const changelogProcessingDir = path.join(__dirname, 'changelog-processing');

const apiChangelogPlugins = specs
.filter((spec) => {
// Use the last segment after the last slash as the id, e.g. access-token
const id = spec.routePath.replace('/api/', '').replace(/\/$/, '');
const changelogFile = path.join(changelogProcessingDir, `${id}.md`);

return spec.specUrl && fs.existsSync(changelogFile);
})
.map((spec) => {
const id = `${spec.routePath.replace('/api/', '').replace(/\/$/, '')}`;
const changelogFilename = path.join(changelogProcessingDir, `${id}.md`);

return [
require.resolve('./src/plugins/changelog/index.js'),
{
id: id,
blogTitle: `${spec.layout.title} Changelog`,
blogDescription: `Changelog for ${spec.layout.title}`,
blogSidebarCount: 'ALL',
blogSidebarTitle: 'Changelog',
routeBasePath: `${spec.routePath}/changelog`,
changelogFilename: changelogFilename,
showReadingTime: false,
postsPerPage: 20,
archiveBasePath: null,
feedOptions: {
type: 'all',
title: `${spec.layout.title} Changelog`,
description: `Changelog for ${spec.layout.title}`,
language: 'en',
},
},
];
});

// With JSDoc @type annotations, IDEs can provide config autocompletion
/** @type {import('@docusaurus/types').DocusaurusConfig} */
(module.exports = {
Expand All @@ -19,7 +59,7 @@ const DOCS_URL = 'https://docs.epilot.io';
favicon: 'img/favicon.svg',

organizationName: 'epilot-dev',
projectName: 'docs',
projectName: 'docs',
presets: [
[
'@docusaurus/preset-classic',
Expand Down Expand Up @@ -73,6 +113,7 @@ const DOCS_URL = 'https://docs.epilot.io';
sidebarPath: require.resolve('./sidebars.js'),
},
],
...apiChangelogPlugins, // Spread the dynamically generated changelog plugins
],

themeConfig:
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "npm run graphql:generate && docusaurus start",
"build": "npm run graphql:generate && docusaurus build",
"start": "npm run graphql:generate && npm run fetch-changelogs && docusaurus start",
"build": "npm run graphql:generate && npm run fetch-changelogs && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "npm run graphql:generate && docusaurus deploy",
"clear": "docusaurus clear",
Expand All @@ -16,7 +16,8 @@
"lint": "eslint src --ext js,jsx,ts,tsx,json",
"lint:fix": "eslint src --ext js,jsx,ts,tsx,json --fix",
"pdf": "./scripts/build-pdf.sh",
"graphql:generate": "./scripts/generate-graphql.sh"
"graphql:generate": "./scripts/generate-graphql.sh",
"fetch-changelogs": "./scripts/get-changelogs.sh"
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "^0.10.0",
Expand Down
46 changes: 46 additions & 0 deletions scripts/get-changelogs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/sh
set -e

# This needs to be set via variable during build process
# S3_BUCKET="api-docs-changelog-demo"
S3_PREFIX="changelogs/"
LOCAL_DIR="./downloaded-changelogs"
PROCESSING_DIR="./changelog-processing"

if [ -z "$S3_BUCKET" ]; then
echo 'error: $S3_BUCKET is not set' >&2
exit 1
fi

# Ensure local directory exists
mkdir -p "$LOCAL_DIR"
mkdir -p "$PROCESSING_DIR"

# Download only .md files from S3
aws s3 sync "s3://$S3_BUCKET/$S3_PREFIX" "$LOCAL_DIR" --exclude "*" --include "*.md"

cp "$LOCAL_DIR"/*.md "$PROCESSING_DIR"

# Process downloaded files: update H1 header to only the last semantic version after "vs. "
for file in "$PROCESSING_DIR"/*.md; do
[ -e "$file" ] || continue
sed -i -E '1s/^# .*vs\. ([0-9]+\.[0-9]+\.[0-9]+).*$/# \1/' "$file"
done

echo "Changelog files downloaded and processed."

# Concatenate changelogs by type, highest version at top, lowest at end
for type in $(ls "$PROCESSING_DIR" | grep -oP '^.*(?=-[0-9]+\.[0-9]+\.[0-9]+\.md$)' | sort -u); do
files=$(ls "$PROCESSING_DIR"/${type}-*.md 2>/dev/null | sort -V -r)
[ -z "$files" ] && continue
out_file="$PROCESSING_DIR/${type}.md"
true > "$out_file"
for f in $files; do
cat "$f" >> "$out_file"
printf "\n" >> "$out_file"
done
echo "Concatenated $type changelogs into $out_file"
# Delete the numbered markdown files after processing
rm -f $files
echo "Deleted processed files: $files"
done
1 change: 0 additions & 1 deletion sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

module.exports = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
appsSidebar: [
{
type: 'autogenerated',
Expand Down
39 changes: 39 additions & 0 deletions src/components/ApiSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// src/components/ApiSidebar.tsx
import { useLocation } from '@docusaurus/router';
import DocPageStyles from '@docusaurus/theme-classic/lib-next/theme/DocPage/styles.module.css';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import DocSidebar from '@theme/DocSidebar';
import React from 'react';

interface Spec {
layout: {
title: string;
};
routePath: string;
specUrl: string;
}

export default function ApiSidebar() {
const location = useLocation();
const { siteConfig } = useDocusaurusContext();

const redocusaurusOpts = siteConfig.presets.find((preset) => preset[0] === 'redocusaurus');
const specs: Spec[] = redocusaurusOpts?.[1]?.['specs'] ?? [];

const sidebar = specs.map((spec) => ({
type: 'link',
href: spec.routePath,
label: spec.layout.title,
}));

const pathname = location.pathname;
// Match /api/{api-name}/changelog or /api/{api-name}/changelog/... and extract /api/{api-name}
const changelogMatch = pathname.match(/^\/api\/([^/]+)\/changelog(\/|$)/);
const highlightPath = changelogMatch ? `/api/${changelogMatch[1]}` : pathname;

return (
<aside className={DocPageStyles.docSidebarContainer} role="complementary">
<DocSidebar path={highlightPath} isHidden={false} onCollapse={() => null} sidebar={sidebar} />
</aside>
);
}
32 changes: 2 additions & 30 deletions src/components/RedocPage.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,20 @@
import { useLocation } from '@docusaurus/router';
import DocPageStyles from '@docusaurus/theme-classic/lib-next/theme/DocPage/styles.module.css';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import DocSidebar from '@theme/DocSidebar';
import ApiSidebar from '@site/src/components/ApiSidebar';
import Layout from '@theme/Layout';
import Redoc from '@theme/Redoc';
import { ApiDocProps as Props } from 'docusaurus-theme-redoc/src/types/common';
import React from 'react';

import styles from './RedocPage.module.css';

interface Spec {
layout: {
title: string;
};
routePath: string;
specUrl: string;
}

function RedocPage({ layoutProps, spec: propSpec }: Props): JSX.Element {
const { title = 'API Docs', description = 'Open API Reference Docs for the API' } = layoutProps || {};

const location = useLocation();
const { siteConfig } = useDocusaurusContext();

const redocusaurusOpts = siteConfig.presets.find((preset) => preset[0] === 'redocusaurus');
const specs: Spec[] = redocusaurusOpts?.[1]?.['specs'] ?? [];

const specUrl: string | undefined = propSpec.type === 'url' ? propSpec.content : undefined;

return (
<Layout {...layoutProps} title={title} description={description} pageClassName={DocPageStyles.docPage}>
<aside className={DocPageStyles.docSidebarContainer} role="complementary">
<DocSidebar
path={location.pathname}
isHidden={false}
onCollapse={() => null}
sidebar={specs.map((spec) => ({
type: 'link',
href: spec.routePath,
label: spec.layout.title,
}))}
/>
</aside>

<ApiSidebar />
<main className={styles.redocContainer}>
<Redoc specUrl={specUrl || propSpec.specUrl} style={{ width: '100%' }} />
</main>
Expand Down
Loading
Loading