Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EXPERIMENTAL]: Extract git log info to display version of documentation #62

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import withMarkdoc from '@markdoc/next.js';
import withSearch from './src/markdoc/search.mjs';
import withGitReflection from './src/markdoc/with-git-reflection.mjs';

/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'ts', 'tsx'],
};

export default withSearch(
withMarkdoc({ schemaPath: './src/markdoc' })(nextConfig),
export default withGitReflection(
withSearch(withMarkdoc({ schemaPath: './src/markdoc' })(nextConfig)),
);
2 changes: 2 additions & 0 deletions src/components/documentation/DocsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PrevNextLinks } from '@/components/documentation/PrevNextLinks';
import { Prose } from '@/components/documentation/Prose';
import { TableOfContents } from '@/components/documentation/TableOfContents';
import { collectSections } from '@/lib/sections';
import GitInfo from './GitInfo';

export function DocsLayout({
children,
Expand All @@ -21,6 +22,7 @@ export function DocsLayout({
<>
<div className="min-w-0 max-w-2xl flex-auto px-4 py-16 lg:max-w-none lg:pl-8 lg:pr-0 xl:px-16">
<article>
<GitInfo />
<DocsHeader title={title} />
<Prose>{children}</Prose>
</article>
Expand Down
82 changes: 82 additions & 0 deletions src/components/documentation/GitInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use client';

import { format } from 'date-fns';
import { nb } from 'date-fns/locale';
// @ts-ignore
import { gitData } from '@/markdoc/with-git-reflection.mjs';
import { usePathname } from 'next/navigation';
import { useMemo } from 'react';
import Link from 'next/link';

const formatDate = (date: Date): string => {
return format(date, 'd. MMM yyyy', { locale: nb });
};

interface GitInfo {
updatedByAuthor: string;
updatedDateIso: string;
updatedMessage: string;
updatedHash: string;
createdByAuthor: string;
createdDateIso: string;
createdMessage: string;
createdHash: string;
}

export default function GitInfo() {
const pathName = usePathname();
const git = useMemo(() => gitData[pathName] as GitInfo, [pathName]);

if (
!git?.createdByAuthor ||
!git?.updatedByAuthor ||
!git?.createdDateIso ||
!git?.updatedDateIso ||
!git?.createdMessage ||
!git?.updatedMessage ||
!git?.createdHash ||
!git?.updatedHash
) {
return null;
}

return (
<dl className="mb-5 grid w-full grid-cols-2 gap-5 sm:grid-cols-2 ">
{[
{
title: 'Sist oppdatert',
author: git.updatedByAuthor,
date: new Date(git.updatedDateIso),
hash: git.updatedHash,
},
{
title: 'Opprettet',
author: git.createdByAuthor,
date: new Date(git.createdDateIso),
hash: git.createdHash,
},
].map((item) => (
<div
key={item.title}
className="flex items-center justify-between overflow-hidden rounded-lg border border-slate-200 bg-white px-3 py-2 dark:border-slate-800 dark:bg-transparent"
>
<div>
<dt className="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{item.title}
</dt>
<dd className="text-base font-medium tracking-tight text-gray-900 dark:text-gray-200">
{formatDate(item.date)}{' '}
<span className="text-sm font-normal"> av {item.author}</span>
</dd>
</div>
<Link
href={`https://github.com/TIHLDE/Codex/commit/${item.hash}`}
className="text-sm font-light underline underline-offset-2"
>
{item.hash.substring(0, 7)}
</Link>
</div>
))}
</dl>
);
}
104 changes: 104 additions & 0 deletions src/markdoc/with-git-reflection.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Runs git log commands to get file information about the given file path in the repo
* @param {string} filePath The path of the file to get git info for
* @returns Information about the creation and last update of the file
*/
const getFileGitInfo = async (filePath) => {
const { exec } = await import('child_process');
const util = await import('util');
const execPromise = util.promisify(exec);

// Get latest info (last updated)
const { stdout: updatedStdout } = await execPromise(
`git log -1 --format=%an,%aI,%s,%H -- '${filePath}'`,
);

const [updatedByAuthor, updatedDateIso, updatedMessage, updatedHash] =
updatedStdout.trim().split(',');

// Get info about file creation
const { stdout: createdStdout } = await execPromise(
`git log --follow --format=%an,%aI,%s,%H --date default '${filePath}' | tail -1`,
);

const [createdByAuthor, createdDateIso, createdMessage, createdHash] =
createdStdout.trim().split(',');

return {
updatedByAuthor,
updatedDateIso,
updatedMessage,
updatedHash,
createdByAuthor,
createdDateIso,
createdMessage,
createdHash,
};
};

import glob from 'fast-glob';
import * as path from 'path';
import { createLoader } from 'simple-functional-loader';
import * as url from 'url';
import Os from 'os'

const __filename = url.fileURLToPath(import.meta.url);

export default function withGitReflection(nextConfig = {}) {
return Object.assign({}, nextConfig, {
webpack(config, options) {
config.module.rules.push({
test: __filename,
use: [
createLoader(function () {
if(Os.platform() === "win32") {
return;
}

let pagesDir = path.resolve('./src/app');
this.addContextDependency(pagesDir);

let files = glob.sync('**/page.md', { cwd: pagesDir });

let gitPromises = [];
let nextUrls = [];

for (let i = 0; i < files.length; i++) {
const file = files[i];

let url =
file === 'page.md'
? '/'
: `/${file.replace(/\/page\.md$/, '')}`;
let cleanedUrl = url.replace(/\/\(private\)\/\(docs\)/, '');

let gitInfo = getFileGitInfo('src/app/' + file);

gitPromises.push(gitInfo);
nextUrls.push(cleanedUrl);
}

return Promise.all(gitPromises).then((gitInfo) => {
const data = nextUrls.reduce(function (map, url, i) {
map[url] = gitInfo[i];
return map;
}, {});

// When this file is imported within the application
// the following module is loaded:
return `
export const gitData = ${JSON.stringify(data)};
`;
});
}),
],
});

if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options);
}

return config;
},
});
}