diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f95fdb7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +{ + "name": "DocumentDB Next.js Dev Container", + "image": "mcr.microsoft.com/devcontainers/javascript-node", + "customizations": { + "vscode": { + "extensions": [ + "redhat.vscode-yaml" + ] + } + }, + "forwardPorts": [ + 3000 + ], + "postCreateCommand": "npm install" +} diff --git a/.github/scripts/ajv-to-ctrf.js b/.github/scripts/ajv-to-ctrf.js new file mode 100644 index 0000000..1bfbe92 --- /dev/null +++ b/.github/scripts/ajv-to-ctrf.js @@ -0,0 +1,90 @@ +// Minimal AJV output to CTRF converter +// Reads validation-output.txt and writes a dummy CTRF report + +const fs = require('fs'); +const [, , inputPath, outputPath] = process.argv; + +if (!inputPath || !outputPath) { + console.error('Usage: node ajv-to-ctrf.js '); + process.exit(1); +} + +if (!fs.existsSync(inputPath)) { + console.error(`Error: Input file not found: ${inputPath}`); + fs.writeFileSync(outputPath, JSON.stringify({ version: '1.0', results: [], error: `Input file not found: ${inputPath}` }, null, 2)); + process.exit(0); +} + +const output = { + version: '1.0', + results: [] +}; + +try { + const lines = fs.readFileSync(inputPath, 'utf-8').split('\n'); + let currentTest = null; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + // Detect start of a new validation + if (trimmed.startsWith('Validating:')) { + if (currentTest) { + output.results.push(currentTest); + } + const match = trimmed.match(/Validating:\s+(.+?)\s+against\s+(.+)/); + if (match) { + currentTest = { + testName: match[1], + status: 'unknown', + message: `Validating ${match[1]} against ${match[2]}`, + details: [] + }; + } + } + // Detect pass/fail status + else if (trimmed.startsWith('✓ PASSED:')) { + if (currentTest) { + currentTest.status = 'pass'; + output.results.push(currentTest); + currentTest = null; + } + } + else if (trimmed.startsWith('✗ FAILED:')) { + if (currentTest) { + currentTest.status = 'fail'; + output.results.push(currentTest); + currentTest = null; + } + } + // Collect error details + else if (currentTest && (trimmed.includes('error') || trimmed.includes('invalid') || trimmed.includes('must'))) { + currentTest.details.push(trimmed); + currentTest.message += '\n' + trimmed; + } + } + + // Add last test if exists + if (currentTest) { + output.results.push(currentTest); + } + + // If no results, create a summary entry + if (output.results.length === 0) { + output.results.push({ + testName: 'YAML Schema Validation', + status: 'pass', + message: 'No validation results found or all files skipped' + }); + } + + fs.writeFileSync(outputPath, JSON.stringify(output, null, 2)); + console.log(`CTRF report generated: ${outputPath}`); + console.log(`Total tests: ${output.results.length}`); + console.log(`Passed: ${output.results.filter(r => r.status === 'pass').length}`); + console.log(`Failed: ${output.results.filter(r => r.status === 'fail').length}`); +} catch (err) { + console.error('Error:', err); + process.exit(1); +} diff --git a/.github/workflows/nextjs.yml b/.github/workflows/continuous-deployment.yml similarity index 87% rename from .github/workflows/nextjs.yml rename to .github/workflows/continuous-deployment.yml index 8cb65da..d86b912 100644 --- a/.github/workflows/nextjs.yml +++ b/.github/workflows/continuous-deployment.yml @@ -1,33 +1,28 @@ # Workflow for building Next.js site and downloading DocumentDB packages, then deploying to GitHub Pages name: Deploy Next.js site and DocumentDB packages to Pages - on: # Runs on pushes targeting the default branch push: - branches: ["main"] - + branches: + - main # Allows you to run this workflow manually from the Actions tab workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: - group: "pages" + group: pages cancel-in-progress: false - jobs: # Build job build: + name: Build Next.js static site + # Sets permissions of the GITHUB_TOKEN to allow reading of repository content + permissions: + contents: read runs-on: ubuntu-22.04 steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout source + uses: actions/checkout@v5 - name: Install required packages run: | until sudo apt-get update; do sleep 1; done @@ -72,19 +67,11 @@ jobs: echo "Unable to determine package manager" exit 1 fi - - name: Setup Node - uses: actions/setup-node@v4 + - name: Setup Node.js + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: 24 cache: ${{ steps.detect-package-manager.outputs.manager }} - - name: Setup Pages - uses: actions/configure-pages@v5 - with: - # Automatically inject basePath in your Next.js configuration file and disable - # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). - # - # You may remove this line if you want to manage the configuration yourself. - static_site_generator: next - name: Restore cache uses: actions/cache@v4 with: @@ -98,6 +85,8 @@ jobs: - name: Install dependencies run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} - name: Build with Next.js + env: + NEXT_BASE_PATH: ${{ github.event.repository.name }} run: ${{ steps.detect-package-manager.outputs.runner }} next build - name: Download DocumentDB packages from latest release run: .github/scripts/download_packages.sh @@ -105,15 +94,28 @@ jobs: uses: actions/upload-pages-artifact@v3 with: path: ./out - # Deployment job deploy: + name: Publish site to GitHub Pages environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest - needs: build + needs: + - build + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages + permissions: + pages: write + id-token: write steps: + - name: Setup Pages + uses: actions/configure-pages@v5 + with: + # Automatically inject basePath in your Next.js configuration file and disable + # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). + # + # You may remove this line if you want to manage the configuration yourself. + static_site_generator: next - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..fba2a6f --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,27 @@ +name: Validate structured content +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + yaml-schema-validation: + name: Validate YAML files against schemas + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: 24 + - name: Install dependencies + run: | + npm install yaml-ls-check + - name: Validate YAML files + run: | + npx yaml-ls-check .\articles + npx yaml-ls-check .\blogs + npx yaml-ls-check .\reference diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8180dc6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Sourced from https://github.com/github/gitignore/blob/main/Nextjs.gitignore +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1b3c4dc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "yaml.schemas": { + "./schema/blog.content.schema.json": [ + "blogs/content.{yml,yaml}" + ], + "./schema/reference.content.schema.json": [ + "reference/content.{yml,yaml}" + ], + "./schema/reference.data.schema.json": [ + "reference/*/**/*.{yml,yaml}" + ], + "./schema/articles.content.schema.json": [ + "articles/**/content.{yml,yaml}" + ], + "./schema/articles.navigation.schema.json": [ + "articles/**/navigation.{yml,yaml}" + ] + }, + "editor.tabSize": 2, + "editor.insertSpaces": true +} \ No newline at end of file diff --git a/app/blogs/page.tsx b/app/blogs/page.tsx index 7033bff..a42308b 100644 --- a/app/blogs/page.tsx +++ b/app/blogs/page.tsx @@ -1,4 +1,12 @@ +import { getAllPosts } from '../services/blogService'; +import { Card } from '../components/Card'; +import { Post } from '../types/Post'; + export default function Blogs() { + const posts: Post[] = getAllPosts(); + const featuredPosts = posts.filter(post => post.featured); + const regularPosts = posts.filter(post => !post.featured); + return (
{/* Artistic background elements */} @@ -38,359 +46,29 @@ export default function Blogs() {
{/* Header */}
-

+

Latest from our Blog

- Insights, updates, and deep dives into the world of document - databases and open-source innovation + Insights, updates, and deep dives into the world of document databases and open-source innovation

+ {/* Blog Grid */}
- {/* Featured Blog - DocumentDB Joins the Linux Foundation */} -
-
-
-
-
-
-
- - - -
-
- - Microsoft Open Source Blog - -

August 25, 2025

-
-
- -

- DocumentDB Joins the Linux Foundation -

- -

- Today, we are excited to announce yet another milestone in - DocumentDB’s journey: the project is officially joining the - Linux Foundation. -

- -
- - Open Source - - - PostgreSQL - - - MIT License - -
- - - Read full article - - - - -
-
-
-
- - {/* Blog 2 - AWS joins the DocumentDB project */} -
-
-
-
-
-
- - - -
-
- - AWS Blogs - -

Recent

-
-
-

- AWS : DocumentDB Project Announcement -

-

- AWS joins the DocumentDB project to build interoperable, open source document database technology -

-
- - Community - - - Growth - -
- - Read more - - - - -
-
-
- - {/* Blog 3 - DocumentDB Open Source Announcement */} -
-
-
-
-
-
- - - -
-
- - Azure Cosmos DB Blog - -

Recent

-
-
- -

- DocumentDB: Open-Source Announcement -

- -

- We are excited to announce the official release of - DocumentDB—an open-source document database platform and the - engine powering the vCore-based Azure Cosmos DB for MongoDB, - built on PostgreSQL. This marks a significant milestone in - creating an interoperable, portable, and fully supported - production-ready document data store. -

- -
- - Community - - - Growth - -
- - - Read more - - - - -
-
-
- - {/* Blog 4 - DocumentDB Gaining Momentum */} -
-
-
-
-
-
- - - -
-
- - Azure Cosmos DB Blog - -

Recent

-
-
- -

- DocumentDB is Gaining Momentum in the Open-Source Database - World -

- -

- DocumentDB has quickly caught the attention of major tech - publications and earned significant community engagement. In - just under a week, our project earned 1000 GitHub stars, - nearly 50 forks, and multiple pull requests. -

- -
- - Community - - - Growth - -
- - - Read more - - - - -
-
-
- - {/* Blog 5 - YugabyteDB MongoDB API */} -
-
-
-
-
-
- - - -
-
- - YugabyteDB Blog - -

Partner Content

-
-
- -

- MongoDB API in YugabyteDB -

- -

- Learn how YugabyteDB now offers a MongoDB-compatible API with - the support of the DocumentDB PostgreSQL extension, providing - developers with an open source alternative for MongoDB - workloads. -

- -
- - MongoDB API - - - Distributed - -
- - - Read more - - - - -
-
-
+ {/* Featured Posts */} + {featuredPosts.map((post, index) => ( + + ))} + + {/* Regular Posts */} + {regularPosts.map((post, index) => ( + + ))}
); -} +} \ No newline at end of file diff --git a/app/components/Breadcrumb.tsx b/app/components/Breadcrumb.tsx new file mode 100644 index 0000000..07f156e --- /dev/null +++ b/app/components/Breadcrumb.tsx @@ -0,0 +1,41 @@ +import { capitalCase } from 'change-case'; +import Link from 'next/link'; +import pluralize from 'pluralize'; + +export default function Breadcrumb({ type, category, name }: { + type?: string; + category?: string; + name?: string; +}) { + return ( + + ); +} \ No newline at end of file diff --git a/app/components/Card.tsx b/app/components/Card.tsx new file mode 100644 index 0000000..2fe678d --- /dev/null +++ b/app/components/Card.tsx @@ -0,0 +1,154 @@ +import { JSX } from 'react'; +import { Post } from '../types/Post'; + +export function Card({ post, featured = false }: { post: Post; featured?: boolean }) { + const icons: Record = { + 'microsoft-open-source-blog': ( + + ), + 'aws-blog': ( + + ), + 'azure-cosmos-db-blog': ( + + ), + 'yugabytedb-blog': ( + + ), + }; + + const icon = icons[post.category as keyof typeof icons]; + + const styles = { + 'microsoft-open-source-blog': { + label: 'Microsoft Open Source Blog', + timestamp: 'August 25, 2025', + gradientFrom: 'from-blue-500', + gradientTo: 'to-blue-600', + textColor: 'blue-400', + hoverColor: 'blue-300', + bgGradient: 'from-blue-500/10 to-purple-500/10', + borderHover: 'border-blue-500/50', + tagColors: [ + 'bg-blue-500/20 text-blue-400', + 'bg-purple-500/20 text-purple-400', + 'bg-green-500/20 text-green-400', + 'bg-orange-500/20 text-orange-400', + ], + }, + 'aws-blog': { + label: 'AWS Blogs', + timestamp: 'Recent', + gradientFrom: 'from-orange-500', + gradientTo: 'to-orange-600', + textColor: 'orange-400', + hoverColor: 'orange-300', + bgGradient: 'from-orange-500/10 to-amber-500/10', + borderHover: 'border-orange-500/50', + tagColors: [ + 'bg-orange-500/20 text-orange-400', + 'bg-amber-500/20 text-amber-400', + 'bg-yellow-500/20 text-yellow-400', + 'bg-blue-500/20 text-blue-400', + ], + }, + 'azure-cosmos-db-blog': { + label: 'Azure Cosmos DB Blog', + timestamp: 'Recent', + gradientFrom: 'from-purple-500', + gradientTo: 'to-purple-600', + textColor: 'purple-400', + hoverColor: 'purple-300', + bgGradient: 'from-purple-500/10 to-pink-500/10', + borderHover: 'border-purple-500/50', + tagColors: [ + 'bg-purple-500/20 text-purple-400', + 'bg-blue-500/20 text-blue-400', + 'bg-green-500/20 text-green-400', + 'bg-orange-500/20 text-orange-400', + ], + }, + 'yugabytedb-blog': { + label: 'YugabyteDB Blog', + timestamp: 'Partner Content', + gradientFrom: 'from-green-500', + gradientTo: 'to-green-600', + textColor: 'green-400', + hoverColor: 'green-300', + bgGradient: 'from-green-500/10 to-emerald-500/10', + borderHover: 'border-green-500/50', + tagColors: [ + 'bg-green-500/20 text-green-400', + 'bg-blue-500/20 text-blue-400', + 'bg-orange-500/20 text-orange-400', + 'bg-yellow-500/20 text-yellow-400', + ], + }, + }; + + const style = styles[post.category as keyof typeof styles]; + + return ( + + ); +} diff --git a/app/components/Code.tsx b/app/components/Code.tsx new file mode 100644 index 0000000..6c56842 --- /dev/null +++ b/app/components/Code.tsx @@ -0,0 +1,25 @@ +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; + +export default function Index({ + code, + language = 'javascript' +}: { + code: string; + language?: string; +}) { + return ( +
+ + {code} + +
+ ) +} diff --git a/app/components/ComingSoon.tsx b/app/components/ComingSoon.tsx new file mode 100644 index 0000000..9d46ed6 --- /dev/null +++ b/app/components/ComingSoon.tsx @@ -0,0 +1,113 @@ +export default function ComingSoon() { + return ( +
+ {/* Main container with glow effect */} +
+ {/* Background glow */} +
+ + {/* JSON Document container */} +
+ {/* JSON Content */} +
+
{'{'}
+
+ "status":{' '} + "building" + , +
+
+ "progress":{' '} + 75 + , +
+
+ "architecture":{' '} + {'{'} +
+
+ "layers":{' '} + [ +
+
+ "DocumentDB API", +
+
+ "DocumentDB Core", +
+
+ "DocumentDB Gateway" +
+
+ ], +
+
+ "coming_soon":{' '} + true +
+
+ {'}'} + , +
+
+ "tools":{' '} + [ + + 🔨 + + + ⚙️ + + + 🔧 + + ] +
+
{'}'}
+
+ + {/* Construction helmet on JSON */} +
+ 👷‍♂️ +
+ + {/* Construction cone */} +
+ 🚧 +
+ + {/* Blueprints */} +
+ 📐 +
+ + {/* Floating construction particles */} +
+
+
+ + {/* Progress indicator */} +
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/app/components/Grid.tsx b/app/components/Grid.tsx new file mode 100644 index 0000000..041ab28 --- /dev/null +++ b/app/components/Grid.tsx @@ -0,0 +1,27 @@ +import Link from 'next/link'; +import type { Page } from '../types/Page'; + +export default function Grid({ items }: { + items: Page[]; +}) { + if (!items.length) return null; + return ( +
+ {items.map(page => ( + +
+

+ {page.name} +

+

+ {page.description} +

+
+ + ))} +
+ ); +} diff --git a/app/components/Index.tsx b/app/components/Index.tsx new file mode 100644 index 0000000..20ad186 --- /dev/null +++ b/app/components/Index.tsx @@ -0,0 +1,92 @@ +"use client"; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import type { ReferencePage } from '../services/referenceService'; +import { capitalCase } from 'change-case'; + +export default function Index({ + groupedReferences +}: { + groupedReferences: Record>; +}) { + const pathname = usePathname(); + + // Get all types and sort them + const types = Object.keys(groupedReferences).sort(); + + return ( +
+ +
+ ); +} + +function Option({ + target, + display, + className, + currentPath +}: { + target: string; + display: string; + className?: string; + currentPath: string; +}) { + // Determine if this option should be highlighted + // Highlight if: + // 1. Exact match (e.g., on /docs/reference/operator, highlight "Operators") + // 2. Category match (e.g., on /docs/reference/operator/accumulator or /docs/reference/operator/accumulator/avg, highlight "Operators" and "Accumulator") + const isExactMatch = currentPath === target; + const isCategoryMatch = currentPath.startsWith(target + '/'); + const isActive = isExactMatch || isCategoryMatch; + + return ( + + {display} + + ); +} diff --git a/app/components/Markdown.tsx b/app/components/Markdown.tsx new file mode 100644 index 0000000..ca70b6e --- /dev/null +++ b/app/components/Markdown.tsx @@ -0,0 +1,281 @@ +'use client'; + +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { useMemo } from 'react'; +import type { ReactElement } from 'react'; +import Code from './Code'; +import { kebabCase } from 'change-case'; + +export default function Markdown({ content }: { content: string }) { + const processedContent = useMemo(() => { + // Split content by H2 headings to group sections + const sections = content.split(/^## /gm); + const elements: ReactElement[] = []; + + sections.forEach((section, index) => { + if (!section.trim()) return; + + // First section (before any H2) is intro text + if (index === 0) { + elements.push( +
+ + {section} + +
+ ); + } else { + // Extract H2 title (first line) and content (rest) + const lines = section.split('\n'); + const title = lines[0]; + const sectionContent = lines.slice(1).join('\n'); + + elements.push( + + ); + } + }); + + return elements; + }, [content]); + + return
{processedContent}
; +} + +function getMarkdownComponents() { + return { + // Paragraphs + p: ({ children, ...props }: any) => ( +

+ {children} +

+ ), + + // H3 sections (subsections within H2 cards) + h3: ({ children, ...props }: any) => ( +

+ {children} +

+ ), + + // H4 subsections + h4: ({ children, ...props }: any) => ( +

+ {children} +

+ ), + + // Unordered lists with blue round bullets + ul: ({ depth, ...props }: any) => ( +