From ffe4937ca9226c961983f08eb379df9ed65d0bcb Mon Sep 17 00:00:00 2001 From: Povindu Samarasekara Date: Tue, 23 Sep 2025 17:41:42 +0530 Subject: [PATCH 1/5] feat: updated api/reference slugs --- .../pages/en/api/reference/[...mdslug].astro | 26 ++++--------------- .../en/api/reference/[...yamlslug].astro | 20 +++----------- .../ClientApp/src/utils/tocUtils.ts | 21 +++++++++++++++ 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro index f553f1c1..fa2d4c0b 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro +++ b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro @@ -4,7 +4,7 @@ import { glob } from "glob"; import path from "path"; import matter from "gray-matter"; import type { TocData } from "~/types/TableOfContentTypes"; -import { getTocByPath } from "@utils/tocUtils"; +import { loadAPITocData } from "@utils/tocUtils"; import ContentLayout from "@layouts/ContentLayout.astro"; import { renderMarkdownWithHeadingIds } from "@utils/contentUtils"; @@ -47,22 +47,6 @@ export async function getStaticPaths() { }; } - // Function to create lazy loader for TOC data - function loadTocData(apiPath: string) { - return async () => { - try { - const tocData = await getTocByPath(apiPath); - return tocData; - } catch (error) { - console.warn(`Failed to load TOC data for path: ${apiPath}`, error); - // Return a default/empty TOC structure instead of failing - return { - items: [], - } as TocData; - } - }; - } - try { // Temporary configured to exclude "soap" until github pages limitation is solved const pattern = [ @@ -90,17 +74,17 @@ export async function getStaticPaths() { // Generate slug from the relative path, removing base and extension // Remove "superoffice-docs/docs/en/api/reference/" and file extension to get clean slug - const cleanPath = normalizedPath + const slug = normalizedPath .replace("superoffice-docs/docs/en/api/reference", "") - .replace(/\.(md|mdx)$/, ""); - const slug = cleanPath; + .replace(/\.(md|mdx)$/, "") + .replace(/\/index$/, ""); const apiType = slug?.split("/")[1]; const tocCollectionPath = `superoffice-docs/docs/en/api/reference/${apiType}`; // Cache the TOC loader function, not the data if (!tocLoaderCache.has(apiType)) { - tocLoaderCache.set(apiType, loadTocData(tocCollectionPath)); + tocLoaderCache.set(apiType, loadAPITocData(tocCollectionPath)); } return { diff --git a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro index c3579c29..8075d09f 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro +++ b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro @@ -3,7 +3,7 @@ import { readFile } from "fs/promises"; import { glob } from "glob"; import path from "path"; import yaml from "js-yaml"; -import { getTocByPath } from "@utils/tocUtils"; +import { loadAPITocData } from "@utils/tocUtils"; import { getYamlReferenceSlug } from "@utils/slugUtils"; import YamlLayoutPage from "@layouts/YamlLayoutPage.astro"; import type { TocData } from "~/types/TableOfContentTypes"; @@ -40,18 +40,6 @@ export async function getStaticPaths() { }; } - // Lazy loader for TOC - function loadTocData(apiPath: string) { - return async () => { - try { - return await getTocByPath(apiPath); - } catch (error) { - console.warn(`Failed to load TOC for ${apiPath}`, error); - return { items: [] } as TocData; - } - }; - } - function getApiType(path:string):string{ if(path.startsWith("netserver")){ return path @@ -86,14 +74,14 @@ export async function getStaticPaths() { ); const apiType = getApiType(match ? match[1] : "unknown"); - const cleanPath = normalizedPath + const slug = normalizedPath .replace("superoffice-docs/docs/en/api/reference", "") .replace(/\.(yml|yaml)$/, ""); - const slug = cleanPath; + const tocCollectionPath = `superoffice-docs/docs/en/api/reference/${apiType}`; if (!tocLoaderCache.has(apiType)) { - tocLoaderCache.set(apiType, loadTocData(tocCollectionPath)); + tocLoaderCache.set(apiType, loadAPITocData(tocCollectionPath)); } return { diff --git a/Source/SuperOffice.DocsNext/ClientApp/src/utils/tocUtils.ts b/Source/SuperOffice.DocsNext/ClientApp/src/utils/tocUtils.ts index e9c6daae..27a2dcaf 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/src/utils/tocUtils.ts +++ b/Source/SuperOffice.DocsNext/ClientApp/src/utils/tocUtils.ts @@ -117,3 +117,24 @@ export async function getTocByPath(path: string) { ); return getTableOfContentsFromCollection(tocEntries, path); } + + +/** + * Creates an async function that loads table of contents (TOC) data from a specified API path. + * + * @param apiPath - The API path to fetch TOC data from + * @returns An async function that when called: + * - Returns the TOC data if successful + * - Returns an empty TOC data structure ({items: []}) if the fetch fails + * @throws Catches and logs any errors during TOC data fetching + */ + export function loadAPITocData(apiPath: string) { + return async () => { + try { + return await getTocByPath(apiPath); + } catch (error) { + console.warn(`Failed to load TOC for ${apiPath}`, error); + return { items: [] } as TocData; + } + }; + } \ No newline at end of file From d13e52190fb3d7c9312f89b1bc7812839bced4a1 Mon Sep 17 00:00:00 2001 From: Povindu Samarasekara Date: Tue, 23 Sep 2025 17:42:46 +0530 Subject: [PATCH 2/5] feat: removed spilt building variables, introduced partial build for dev purposes --- .../ClientApp/astro.config.mjs | 14 ++-- .../ClientApp/package.json | 5 +- .../ClientApp/src/content.config.ts | 66 ++++--------------- .../pages/en/api/reference/[...mdslug].astro | 4 +- .../en/api/reference/[...yamlslug].astro | 7 +- 5 files changed, 24 insertions(+), 72 deletions(-) diff --git a/Source/SuperOffice.DocsNext/ClientApp/astro.config.mjs b/Source/SuperOffice.DocsNext/ClientApp/astro.config.mjs index 19d141bf..cc0535c8 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/astro.config.mjs +++ b/Source/SuperOffice.DocsNext/ClientApp/astro.config.mjs @@ -4,14 +4,11 @@ import icon from "astro-icon"; import remarkDirective from "remark-directive"; import codeImport from "remark-code-import"; import mdx from "@astrojs/mdx"; -// import preact from "@astrojs/preact"; import robots from "astro-robots"; import sitemap from "@astrojs/sitemap"; import pagefind from "astro-pagefind"; import { rehypeHeadingIds } from "@astrojs/markdown-remark"; -//import rehypeSlug from 'rehype-slug'; import rehypeAutolinkHeadings from "rehype-autolink-headings"; -// import rehypeSanitize from "rehype-sanitize"; import remarkIncludeDirective from "./src/plugins/AddIncludesToMarkdown.js"; import remarkRestyleDirective from "./src/plugins/RestyleDirectives.js"; import react from "@astrojs/react"; @@ -19,13 +16,10 @@ import yaml from '@rollup/plugin-yaml'; import redirectFrom from "astro-redirect-from"; import { getRedirectFromSlug } from './src/utils/slugUtils.ts'; -const apiOnly = process.env.API_ONLY === 'true'; export default defineConfig({ - // Conditionally exclude static landing page pages: [ - 'src/pages/**/*', - ...(apiOnly ? ['!src/pages/contribute/index.astro'] : []), + 'src/pages/**/*' ], markdown: { @@ -39,7 +33,6 @@ export default defineConfig({ }, ], ], - // rehypeSanitize, rehypeSlug shikiConfig: { theme: "houston", wrap: true, @@ -96,13 +89,14 @@ export default defineConfig({ }, }), mdx(), - // pagefind(), + pagefind(), react(), // redirectFrom({ // contentDir: './external-content', // getSlug: getRedirectFromSlug, // Function to get the slug for redirect_from // }), - robots(), sitemap(), + robots(), + sitemap(), ], image: { diff --git a/Source/SuperOffice.DocsNext/ClientApp/package.json b/Source/SuperOffice.DocsNext/ClientApp/package.json index 124cb240..7f9273e5 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/package.json +++ b/Source/SuperOffice.DocsNext/ClientApp/package.json @@ -5,9 +5,8 @@ "scripts": { "dev": "astro dev", "start": "astro dev", - "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=12288\" astro check && astro build", - "build:default": "astro check && astro build", - "build:local": "node build/local-build-script.mjs", + "build": "astro check && astro build", + "build:partial": "powershell -Command \"$env:PARTIAL_BUILD='true'; astro check; astro build\"", "preview": "astro preview", "astro": "astro", "test:e2e": "npm run test --workspace=e2e-tests" diff --git a/Source/SuperOffice.DocsNext/ClientApp/src/content.config.ts b/Source/SuperOffice.DocsNext/ClientApp/src/content.config.ts index 25b0631f..d057a0ee 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/src/content.config.ts +++ b/Source/SuperOffice.DocsNext/ClientApp/src/content.config.ts @@ -3,15 +3,15 @@ import { defineCollection } from "astro:content"; import { glob } from "astro/loaders"; import { DocsSchema, SimplifiedYamlSchema, TocYamlSchema, YamlManagedReferenceSchema } from "~/content.schema" -// apiOnly variable is used in the split build to isolate docs/en/api folder content -const apiOnly = process.env.API_ONLY === 'true'; +// partialBuild variable is used to partially build the content for development purposes. +const partialBuild = process.env.PARTIAL_BUILD === 'true'; const DOCS_BASE = "external-content/superoffice-docs/docs"; const API_BASE = `${DOCS_BASE}/en/api`; const enDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!index.md", "!**/includes/**", @@ -25,7 +25,7 @@ const enDocs = defineCollection({ const apiDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", "!tutorials/minimal-csharp-app", //Temporary excluded due to corrupted images @@ -45,10 +45,10 @@ const apiDocs = defineCollection({ const CRMScript = defineCollection({ loader: glob({ - pattern: apiOnly ? [ + pattern: partialBuild ? [] : [ "**/!(*toc).yml", "!**/includes/**", - ] : [], + ], base: `${DOCS_BASE}/en/automation/crmscript/reference`, }), schema: YamlManagedReferenceSchema, @@ -56,7 +56,7 @@ const CRMScript = defineCollection({ const NSScriptingRef = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**",], base: `${DOCS_BASE}/en/automation/netserver-scripting/reference`, @@ -64,47 +64,13 @@ const NSScriptingRef = defineCollection({ schema: DocsSchema, }); -// const WebAPI = defineCollection({ -// loader: glob({ -// pattern: false ? ["**/!(*toc).yml"] : [], -// base: `${API_BASE}/reference/webapi` -// }), -// }); - -// const Web = defineCollection({ -// loader: glob({ -// pattern: apiOnly ? [ -// "*.yml", -// ] : [], -// base: `${API_BASE}/reference/web` -// }), -// }); - -// const NetserverCore = defineCollection({ -// loader: glob({ -// pattern: apiOnly ? [ -// "*.yml", -// ] : [], -// base: `${API_BASE}/reference/netserver/core` -// }), -// }); - -// const NetserverServices = defineCollection({ -// loader: glob({ -// pattern: apiOnly ? [ -// "*.yml", -// ] : [], -// base: `${API_BASE}/reference/netserver/services` -// }), -// }); - /** * TRANSLATIONS */ const daDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", ], @@ -115,7 +81,7 @@ const daDocs = defineCollection({ const deDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", ], @@ -126,7 +92,7 @@ const deDocs = defineCollection({ const nlDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", ], @@ -137,7 +103,7 @@ const nlDocs = defineCollection({ const noDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", ], @@ -148,7 +114,7 @@ const noDocs = defineCollection({ const svDocs = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", ], @@ -163,7 +129,7 @@ const svDocs = defineCollection({ const contribution = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", "!CODE_OF_CONDUCT.md", @@ -175,7 +141,7 @@ const contribution = defineCollection({ const releaseNotes = defineCollection({ loader: glob({ - pattern: apiOnly ? [] : [ + pattern: partialBuild ? [] : [ "**/*.md", "!**/includes/**", ], @@ -220,10 +186,6 @@ export const collections = { "api-docs": apiDocs, "crmscript": CRMScript, "nsscripting": NSScriptingRef, - // "netserver-core": NetserverCore, - // "netserver-services": NetserverServices, - // webapi: WebAPI, - // web: Web, contribute: contribution, "release-notes": releaseNotes, cats: landingPages, diff --git a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro index fa2d4c0b..35b99dbc 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro +++ b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...mdslug].astro @@ -9,9 +9,9 @@ import ContentLayout from "@layouts/ContentLayout.astro"; import { renderMarkdownWithHeadingIds } from "@utils/contentUtils"; export async function getStaticPaths() { - const apiOnly = process.env.API_ONLY === 'true'; + const partialBuild = process.env.PARTIAL_BUILD === 'true'; - if (!apiOnly) { + if (partialBuild) { return []; } diff --git a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro index 8075d09f..915049f2 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro +++ b/Source/SuperOffice.DocsNext/ClientApp/src/pages/en/api/reference/[...yamlslug].astro @@ -9,14 +9,11 @@ import YamlLayoutPage from "@layouts/YamlLayoutPage.astro"; import type { TocData } from "~/types/TableOfContentTypes"; export async function getStaticPaths() { + const partialBuild = process.env.PARTIAL_BUILD === 'true'; - const apiOnly = process.env.API_ONLY === 'true'; - - if (!apiOnly) { + if (partialBuild) { return []; } - - const baseContentPath = "external-content"; const apiCollections = [ From 1327e7d60f47b0882dd6e826344ab06e34e6d220 Mon Sep 17 00:00:00 2001 From: Povindu Samarasekara Date: Tue, 23 Sep 2025 18:10:59 +0530 Subject: [PATCH 3/5] chore: added comments to scripts --- .../build/detect-duplicate-frontmatter.js | 16 ++++++++-- .../ClientApp/build/local-build-script.mjs | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js b/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js index b3067358..7e2d3c7a 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js +++ b/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js @@ -1,5 +1,17 @@ -// detect-duplicate-frontmatter.js -// Usage: node detect-duplicate-frontmatter.js +/** + * Duplicate Frontmatter Detection Tool + * + * This script analyzes Markdown files for duplicate frontmatter properties + * and generates a report of any duplicates found. + * + * Usage: + * node detect-duplicate-frontmatter.js + * + * Prerequisites: + * - Node.js installed + * - Run from the script's directory + * + */ import fs from "fs"; import path from "path"; diff --git a/Source/SuperOffice.DocsNext/ClientApp/build/local-build-script.mjs b/Source/SuperOffice.DocsNext/ClientApp/build/local-build-script.mjs index af2a9b99..1be17dbd 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/build/local-build-script.mjs +++ b/Source/SuperOffice.DocsNext/ClientApp/build/local-build-script.mjs @@ -1,3 +1,35 @@ +/** + * Local build script for split builds + * + * This script performs a two-phase build process and merges the results: + * 1. Builds with API_ONLY=true + * 2. Builds with API_ONLY=false + * 3. Merges both builds + * 4. Indexes the result with Pagefind + * + * @description + * To use this script for split builds: + * 1. Set up your content collections to handle API_ONLY environment variable + * 2. Configure other files to respond to API_ONLY flag appropriately + * 3. Run this script instead of regular astro build + * + * @example + * // In your content collections: + * const items = API_ONLY === 'true' + * ? apiOnlyContent + * : fullContent; + * + * @note + * - Cleans up temporary build directories (.distA and .distB) + * - Final output will be in dist/ directory + * - Automatically runs Pagefind indexing on final build + * + * @warning + * Currently not actively used in the codebase. + * Ensure proper setup before running split builds. + */ + + import { execSync } from "child_process"; import { cpSync, rmSync, existsSync } from "fs"; import path from "path"; From 97efbb7a1e55cc740eb20ed1a28fe742a8603fbf Mon Sep 17 00:00:00 2001 From: Povindu Samarasekara Date: Wed, 24 Sep 2025 12:53:36 +0530 Subject: [PATCH 4/5] feat: updated build and dev scripts --- Source/SuperOffice.DocsNext/ClientApp/package.json | 1 + Source/SuperOffice.DocsNext/SuperOffice.DocsNext.csproj | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/SuperOffice.DocsNext/ClientApp/package.json b/Source/SuperOffice.DocsNext/ClientApp/package.json index 7f9273e5..23d59f14 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/package.json +++ b/Source/SuperOffice.DocsNext/ClientApp/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "dev": "astro dev", + "dev:partial": "powershell -Command \"$env:PARTIAL_BUILD='true'; astro dev\"", "start": "astro dev", "build": "astro check && astro build", "build:partial": "powershell -Command \"$env:PARTIAL_BUILD='true'; astro check; astro build\"", diff --git a/Source/SuperOffice.DocsNext/SuperOffice.DocsNext.csproj b/Source/SuperOffice.DocsNext/SuperOffice.DocsNext.csproj index 11597917..7e0a6f1a 100644 --- a/Source/SuperOffice.DocsNext/SuperOffice.DocsNext.csproj +++ b/Source/SuperOffice.DocsNext/SuperOffice.DocsNext.csproj @@ -25,11 +25,11 @@ - + - + From ca13bcd52195094cc29fed2636fa6f73011ed310 Mon Sep 17 00:00:00 2001 From: Povindu Samarasekara Date: Wed, 24 Sep 2025 17:57:41 +0530 Subject: [PATCH 5/5] feat: added script to clone external repos and updated README --- README.md | 42 +++++++++-- .../build/detect-duplicate-frontmatter.js | 4 +- .../ClientApp/build/setup-external-repos.js | 74 +++++++++++++++++++ 3 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 Source/SuperOffice.DocsNext/ClientApp/build/setup-external-repos.js diff --git a/README.md b/README.md index 2a77d1ca..604e53a5 100644 --- a/README.md +++ b/README.md @@ -26,21 +26,35 @@ This repository contains two main projects: #### Backend ```bash -cd Server +cd Source/SuperOffice.DocsNext dotnet restore ``` Frontend ```bash -cd ClientApp +cd Source/SuperOffice.DocsNext/ClientApp npm install ``` -### 3. Run Development Servers +### 3. Clone external repos + +You can either +1. Manually clone or copy files of `SuperOfficeDocs/superoffice-docs` and `SuperOfficeDocs/contribution` repos into `ClientApp/src/external-content` + +2. Use following script to clone or update the required external GitHub repositories into the `ClientApp/src/external-content/` directory. +If a repository folder already exists, script will fetch and reset it to the latest commit on the `main` branch. + + ```bash + cd Source/SuperOffice.DocsNext/ClientApp/build + node setup-external-repos.js + ``` + + +### 4. Run Development Servers #### Backend (with API and proxy to frontend) -From docs-next/Server: +From docs-next/Source/SuperOffice.DocsNext: ```bash dotnet run ``` @@ -49,7 +63,7 @@ By default API runs at: http://localhost:5215/api. Any non-API request is proxie #### Frontend (Astro dev server) -From docs-next/ClientApp: +From docs-next/Source/SuperOffice.DocsNext/ClientApp: ```bash npm run dev @@ -61,7 +75,15 @@ By default Frontend dev server runs at: http://localhost:4321. The backend proxi Any other path → served by Astro dev server. -### 4. Run Production Build +##### Reduce content during development (dev:partial) + +To manage the content during development, npm command to run dev server with reduced content was introduced. This is useful when you only need to run the development server without the content from superoffice-docs. It uses a pre-defined enviornment variable (PARTIAL_BUILD) to disable content collections from rendering. + +```bash +npm run dev:partial +``` + +### 5. Run Production Build 1. Build Backend (includes frontend) @@ -81,6 +103,14 @@ dotnet docs-next.dll API: https://localhost:5001/api/... Frontend: served from wwwroot +#### Partial frontend build (build:partial) + +To reduce build time when testing a build, npm command to do partial builds was introduced. This is useful when you only need to build the frontend without the content from superoffice-docs. It uses a pre-defined enviornment variable (PARTIAL_BUILD) to disable content collections from building. + +```bash +npm run build:partial +``` + ### Notes In development, run both servers: diff --git a/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js b/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js index 7e2d3c7a..a5f75237 100644 --- a/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js +++ b/Source/SuperOffice.DocsNext/ClientApp/build/detect-duplicate-frontmatter.js @@ -5,6 +5,7 @@ * and generates a report of any duplicates found. * * Usage: + * go to ClientApp/build directory * node detect-duplicate-frontmatter.js * * Prerequisites: @@ -21,7 +22,7 @@ import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const contentDir = path.resolve("external-content/superoffice-docs/docs"); +const contentDir = path.resolve("../external-content/superoffice-docs"); const outputFile = path.resolve(__dirname, "duplicate-frontmatter.txt"); const extensions = new Set([".md"]); // add ".mdx" if needed @@ -72,7 +73,6 @@ function findTopLevelDuplicateKeys(yamlText) { const files = fs.existsSync(contentDir) ? walk(contentDir) : []; /** @type {{file:string, duplicates:string[]}[]} */ const problemFiles = []; - for (const file of files) { const raw = fs.readFileSync(file, "utf8"); const fm = extractFrontmatter(raw); diff --git a/Source/SuperOffice.DocsNext/ClientApp/build/setup-external-repos.js b/Source/SuperOffice.DocsNext/ClientApp/build/setup-external-repos.js new file mode 100644 index 00000000..f04d939b --- /dev/null +++ b/Source/SuperOffice.DocsNext/ClientApp/build/setup-external-repos.js @@ -0,0 +1,74 @@ +/** + * Setup External Repositories + * + * This script clones or updates the required external GitHub repositories + * into the `src/external-content/` directory. If a repository folder + * already exists, it will fetch and reset it to the latest commit on the + * `main` branch. + * + * Usage: + * # From ClientApp/build directory + * node setup-external-repos.js + * + * Notes: + * - Requires Git to be installed and available in PATH. + * - Local changes inside the external repos will be overwritten when updating. + */ + + + + +import { exec } from "node:child_process"; +import path from "node:path"; +import fs from "node:fs"; + +const baseDir = path.resolve(process.cwd(), "..", "external-content"); + +const repos = [ + { + url: "https://github.com/SuperOfficeDocs/contribution.git", + dest: path.join(baseDir, "contribution"), + }, + { + url: "https://github.com/SuperOfficeDocs/superoffice-docs.git", + dest: path.join(baseDir, "superoffice-docs"), + }, +]; + +function runCommand(command, cwd) { + return new Promise((resolve, reject) => { + exec(command, { cwd }, (error, stdout, stderr) => { + if (error) { + console.error(`Command failed: ${command}\n${stderr}`); + return reject(error); + } + resolve(stdout.trim()); + }); + }); +} + +async function cloneOrUpdate({ url, dest }) { + if (fs.existsSync(dest)) { + console.log(`Repo already exists at ${dest}. Pulling latest changes...`); + await runCommand("git fetch --all", dest); + await runCommand("git reset --hard origin/main", dest); + console.log(`Updated ${url}`); + } else { + console.log(`Cloning ${url} into ${dest}...`); + await runCommand(`git clone ${url} "${dest}"`, process.cwd()); + console.log(`Successfully cloned ${url}`); + } +} + +async function main() { + for (const repo of repos) { + try { + await cloneOrUpdate(repo); + } catch { + process.exitCode = 1; + break; + } + } +} + +main();