docs: added edit date (#8623)
This commit is contained in:
1 parent
8c784a8b30
commit
c479fe8220
16 files changed
+575
-17
No files matched your search
@@ -0,0 +1,156 @@
|
||||
import { exec } from "child_process"
|
||||
import { existsSync } from "fs"
|
||||
import { readdir, readFile, stat, writeFile } from "fs/promises"
|
||||
import path from "path"
|
||||
import util from "util"
|
||||
|
||||
const promiseExec = util.promisify(exec)
|
||||
|
||||
const projectBasePath = path.resolve()
|
||||
const rootPath = path.resolve("..", "..", "..")
|
||||
const monorepoRelativePath = projectBasePath
|
||||
.replace(rootPath, "")
|
||||
.replace(/^\//, "")
|
||||
const generatedFilePath = path.join(
|
||||
projectBasePath,
|
||||
"generated",
|
||||
"edit-dates.mjs"
|
||||
)
|
||||
|
||||
const getGitEditDatesOfPaths = async (
|
||||
paths: string[]
|
||||
): Promise<Record<string, string>> => {
|
||||
const editDates: Record<string, string> = {}
|
||||
await Promise.all(
|
||||
paths.map(async (filePath) => {
|
||||
const editDateProcess = await promiseExec(
|
||||
`git log -1 --pretty="format:%cI" ${filePath}`
|
||||
)
|
||||
|
||||
editDates[filePath] = editDateProcess.stdout || new Date().toISOString()
|
||||
})
|
||||
)
|
||||
|
||||
return editDates
|
||||
}
|
||||
|
||||
// Note: this gets the date a file was edited in the OS. One drawback here
|
||||
// is if a file is edited on a day, but the PR is merged a few days later, so the
|
||||
// date doesn't accurately represent the publish date of the file.
|
||||
const getOsLastEditDates = async (
|
||||
paths: string[]
|
||||
): Promise<Record<string, string>> => {
|
||||
const editDates: Record<string, string> = {}
|
||||
await Promise.all(
|
||||
paths.map(async (filePath) => {
|
||||
const fileStat = await stat(filePath)
|
||||
|
||||
editDates[filePath] = fileStat.mtime.toISOString()
|
||||
})
|
||||
)
|
||||
|
||||
return editDates
|
||||
}
|
||||
|
||||
const getChangedFiles = async (): Promise<string[]> => {
|
||||
const changedFiles: string[] = []
|
||||
|
||||
// get untracked files
|
||||
const untrackedFiles = await promiseExec(
|
||||
`git ls-files --other --exclude-standard`
|
||||
)
|
||||
|
||||
untrackedFiles.stdout.split("\n").forEach((file) => {
|
||||
if (path.basename(file) !== "page.mdx") {
|
||||
return
|
||||
}
|
||||
|
||||
changedFiles.push(path.relative(projectBasePath, file))
|
||||
})
|
||||
|
||||
// get tracked files
|
||||
const trackedFiles = await promiseExec(
|
||||
`{ git diff --name-only ; git diff --name-only --staged ; } | sort | uniq`
|
||||
)
|
||||
|
||||
trackedFiles.stdout.split("\n").forEach((file) => {
|
||||
// The above command retrieve the full file path relative to the monorepo
|
||||
// so all parts before the project's path should be removed.
|
||||
if (
|
||||
!file.startsWith(monorepoRelativePath) ||
|
||||
path.basename(file) !== "page.mdx"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
changedFiles.push(file.replace(`${monorepoRelativePath}/`, ""))
|
||||
})
|
||||
|
||||
return changedFiles
|
||||
}
|
||||
|
||||
const getAllProjectFiles = async (): Promise<string[]> => {
|
||||
const appDir = path.join(projectBasePath, "app")
|
||||
|
||||
const allFiles: string[] = []
|
||||
const filesInDir = await readdir(appDir, {
|
||||
withFileTypes: true,
|
||||
recursive: true,
|
||||
})
|
||||
|
||||
filesInDir.forEach((file) => {
|
||||
if (file.isDirectory() || path.basename(file.name) !== "page.mdx") {
|
||||
return
|
||||
}
|
||||
|
||||
const fullFilePath = path.join(file.path, file.name)
|
||||
|
||||
// TODO check if this produces correct file paths
|
||||
allFiles.push(path.relative(projectBasePath, fullFilePath))
|
||||
})
|
||||
|
||||
return allFiles
|
||||
}
|
||||
|
||||
export const generateEditedDates = async () => {
|
||||
const generatedFileExists = existsSync(generatedFilePath)
|
||||
const type = generatedFileExists ? "git" : "all"
|
||||
|
||||
let files: string[] = []
|
||||
let editDates: Record<string, string> = {}
|
||||
|
||||
if (type === "all") {
|
||||
// get all files in a project
|
||||
files = await getAllProjectFiles()
|
||||
editDates = await getGitEditDatesOfPaths(files)
|
||||
} else {
|
||||
// set the last edit dates for git changes only
|
||||
files = await getChangedFiles()
|
||||
editDates = await getOsLastEditDates(files)
|
||||
}
|
||||
|
||||
if (!files.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const fileContentPrefix = `export const generatedEditDates = `
|
||||
|
||||
if (generatedFileExists) {
|
||||
const existingEditDates = JSON.parse(
|
||||
(await readFile(generatedFilePath, "utf-8")).replace(
|
||||
fileContentPrefix,
|
||||
""
|
||||
)
|
||||
)
|
||||
|
||||
editDates = Object.assign(existingEditDates, editDates)
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
generatedFilePath,
|
||||
`${fileContentPrefix}${JSON.stringify(editDates, undefined, 2)}`,
|
||||
{
|
||||
flag: "w",
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./generate-edited-dates.js"
|
||||
export * from "./generate-sidebar.js"
|
||||
|
||||
export * from "./utils/find-metadata-title.js"
|
||||
|
||||
@@ -4,10 +4,7 @@ import React from "react"
|
||||
export const MainNavDivider = () => {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
"h-docs_0.75 w-px block bg-medusa-border-base",
|
||||
"mx-docs_0.5"
|
||||
)}
|
||||
className={clsx("h-docs_0.75 w-px block bg-medusa-border-base")}
|
||||
></span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react"
|
||||
|
||||
const DATE_REGEX = /^[a-zA-Z]+ (?<month>[a-zA-Z]+)/
|
||||
|
||||
type MainNavEditDateProps = {
|
||||
date: string
|
||||
}
|
||||
|
||||
export const MainNavEditDate = ({ date }: MainNavEditDateProps) => {
|
||||
const today = new Date(date)
|
||||
const dateObj = new Date(date)
|
||||
const formattedDate = dateObj.toString()
|
||||
const dateMatch = DATE_REGEX.exec(formattedDate)
|
||||
|
||||
if (!dateMatch?.groups?.month) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
Edited {dateMatch.groups.month} {dateObj.getDate()}
|
||||
{dateObj.getFullYear() !== today.getFullYear()
|
||||
? `, ${dateObj.getFullYear()}`
|
||||
: ""}
|
||||
</span>
|
||||
<span className="text-compact-small-plus">·</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -11,9 +11,10 @@ import { MainNavDivider } from "./Divider"
|
||||
import { MainNavSidebarOpener } from "./SidebarOpener"
|
||||
import { MainNavHelpButton } from "./HelpButton"
|
||||
import { SidebarLeftIcon } from "../Icons/SidebarLeft"
|
||||
import { MainNavEditDate } from "./EditDate"
|
||||
|
||||
export const MainNav = () => {
|
||||
const { reportIssueLink } = useMainNav()
|
||||
const { reportIssueLink, editDate } = useMainNav()
|
||||
const { setMobileSidebarOpen } = useSidebar()
|
||||
return (
|
||||
<div
|
||||
@@ -35,14 +36,17 @@ export const MainNav = () => {
|
||||
<MainNavigationDropdown />
|
||||
<MainNavBreadcrumbs />
|
||||
</div>
|
||||
<div className="flex items-center gap-docs_0.25">
|
||||
<Link href={reportIssueLink} className="text-medusa-fg-muted">
|
||||
Report Issue
|
||||
</Link>
|
||||
<div className="flex items-center gap-docs_0.75">
|
||||
<div className="flex items-center gap-[6px] text-medusa-fg-muted">
|
||||
{editDate && <MainNavEditDate date={editDate} />}
|
||||
<Link href={reportIssueLink}>Report Issue</Link>
|
||||
</div>
|
||||
<MainNavDivider />
|
||||
<MainNavHelpButton />
|
||||
<MainNavColorMode />
|
||||
<SearchModalOpener />
|
||||
<div className="flex items-center gap-docs_0.25">
|
||||
<MainNavHelpButton />
|
||||
<MainNavColorMode />
|
||||
<SearchModalOpener />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useContext, useMemo, useState } from "react"
|
||||
import React, { createContext, useContext, useMemo } from "react"
|
||||
import { NavigationDropdownItem } from "types"
|
||||
|
||||
export type MainNavContext = {
|
||||
navItems: NavigationDropdownItem[]
|
||||
activeItem?: NavigationDropdownItem
|
||||
reportIssueLink: string
|
||||
editDate?: string
|
||||
}
|
||||
|
||||
const MainNavContext = createContext<MainNavContext | null>(null)
|
||||
@@ -14,6 +15,7 @@ const MainNavContext = createContext<MainNavContext | null>(null)
|
||||
export type MainNavProviderProps = {
|
||||
navItems: NavigationDropdownItem[]
|
||||
reportIssueLink: string
|
||||
editDate?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
@@ -21,6 +23,7 @@ export const MainNavProvider = ({
|
||||
navItems,
|
||||
reportIssueLink,
|
||||
children,
|
||||
editDate,
|
||||
}: MainNavProviderProps) => {
|
||||
const activeItem = useMemo(
|
||||
() => navItems.find((item) => item.type === "link" && item.isActive),
|
||||
@@ -33,6 +36,7 @@ export const MainNavProvider = ({
|
||||
navItems,
|
||||
activeItem,
|
||||
reportIssueLink,
|
||||
editDate,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo-client",
|
||||
"noEmit": false,
|
||||
"jsx": "react",
|
||||
"lib": ["dom", "ES2015", "es2022"],
|
||||
"lib": ["dom", "es2022"],
|
||||
"module": "ESNext",
|
||||
"target": "es6",
|
||||
"target": "ESNext",
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user