diff --git a/.github/actions/setup-node-deps/action.yml b/.github/actions/setup-node-deps/action.yml index 23137daa..defc8e9d 100644 --- a/.github/actions/setup-node-deps/action.yml +++ b/.github/actions/setup-node-deps/action.yml @@ -1,5 +1,5 @@ name: Setup Node.js and Dependencies -description: Sets up Node.js 22 with yarn cache and installs dependencies +description: Sets up Node.js 24 with yarn cache and installs dependencies runs: using: composite @@ -7,7 +7,7 @@ runs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "22" + node-version: "24" cache: "yarn" - name: Install dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7be6b4fd..7cce4862 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: "yarn" - name: Initialize Repository @@ -51,7 +51,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: "yarn" - name: Initialize Repository diff --git a/.github/workflows/deploy-nettango.yml b/.github/workflows/deploy-nettango.yml index 173852fe..91f25ab7 100644 --- a/.github/workflows/deploy-nettango.yml +++ b/.github/workflows/deploy-nettango.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "22" + node-version: "24" cache: "yarn" - name: Install dependencies diff --git a/.gitignore b/.gitignore index 046c3210..23bef708 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,8 @@ packages/mustache/test-dist/ # should not be tracked GITIGNORE_* GITIGNORE_*/**/* + +.env +.env.local +.env.development +.env.production diff --git a/.nvmrc b/.nvmrc index 2bd5a0a9..a45fd52c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +24 diff --git a/README.md b/README.md index a69aca3b..5c7ae8fe 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ For development guides, contribution instructions, and more, please refer to the ### Getting Started 1. **Install Requirements** - - [Node.js v22](https://nodejs.org/en/download/) + - [Node.js v24](https://nodejs.org/en/download/) - [Yarn v1](https://classic.yarnpkg.com/lang/en/docs/install/) - [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) (for Windows users) diff --git a/apps/docs/.env b/apps/docs/.env deleted file mode 100644 index 5f50f61a..00000000 --- a/apps/docs/.env +++ /dev/null @@ -1,16 +0,0 @@ -PROJECT_ROOT="." - -PRODUCT_NAME="NetLogo Documentation" -PRODUCT_VERSION="7.0.3" -PRODUCT_DISPLAY_NAME="7.0.3" -PRODUCT_BUILD_DATE=$(date +%Y-%m-%dT%H:%M:%SZ) -PRODUCT_WEBSITE="https://docs.netlogo.org" - -VERSIONS_SRC="/versions.json" - -REPO_ROOT=../.. -EXTENSIONS_DIR="$REPO_ROOT/external/extensions" - -BUILD_LATEST=${BUILD_LATEST:-true} -BUILD_REPO="git@github.com:NetLogo/docs.git" -BUILD_BRANCH="main" diff --git a/apps/docs/.env.development b/apps/docs/.env.development deleted file mode 100644 index 9cf7197c..00000000 --- a/apps/docs/.env.development +++ /dev/null @@ -1,16 +0,0 @@ -# Development environment variables for better HMR -NUXT_DEV_TOOLS=true -NUXT_TELEMETRY_DISABLED=true - -PRODUCT_NAME="NetLogo Documentation" -PRODUCT_VERSION="7.0.3" -PRODUCT_DISPLAY_NAME="7.0.3" -PRODUCT_BUILD_DATE=$(date +%Y-%m-%dT%H:%M:%SZ) -PRODUCT_WEBSITE="https://docs.netlogo.org" - -VERSIONS_SRC="/versions.json" - -REPO_ROOT=../.. -EXTENSIONS_DIR="$REPO_ROOT/external/extensions" - -BASE_PATH="" diff --git a/apps/docs/.env.example b/apps/docs/.env.example new file mode 100644 index 00000000..799b0f4c --- /dev/null +++ b/apps/docs/.env.example @@ -0,0 +1,25 @@ +NUXT_TELEMETRY_DISABLED=true + +PROJECT_ROOT="." +REPO_ROOT=../.. +VERSIONS_SRC="/versions.json" +EXTENSIONS_DIR="$REPO_ROOT/external/extensions" + +PRODUCT_NAME="NetLogo Documentation" +PRODUCT_DESCRIPTION="The official documentation for the NetLogo modeling environment, including user manuals, tutorials, and reference materials." +PRODUCT_KEYWORDS="NetLogo, Documentation, User Manual, Tutorials, Reference, Agent-Based Modeling, Simulation, Programming, Modeling Environment" +PRODUCT_VERSION="7.0.3" +PRODUCT_DISPLAY_NAME="7.0.3" +PRODUCT_BUILD_DATE=$(date +%Y-%m-%dT%H:%M:%SZ) +PRODUCT_WEBSITE="https://docs.netlogo.org" + +BUILD_LATEST=${BUILD_LATEST:-true} +BUILD_REPO="git@github.com:NetLogo/docs.git" +BUILD_BRANCH="main" + +# We will enable it when it is planned +# for release +NUXT_PRIM_TOOLTIP_DISABLED=1 + +# Enable for Development +# NUXT_DEV_TOOLS=true diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index 6a789caa..1908ba1c 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -26,3 +26,8 @@ logs .idea routes.json + +# Environment variables +.env +.env.*.local +.env.local diff --git a/apps/docs/.nvmrc b/apps/docs/.nvmrc deleted file mode 100644 index 2bd5a0a9..00000000 --- a/apps/docs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -22 diff --git a/apps/docs/app/app.config.ts b/apps/docs/app/app.config.ts new file mode 100644 index 00000000..b2d0682c --- /dev/null +++ b/apps/docs/app/app.config.ts @@ -0,0 +1 @@ +export default defineAppConfig({}); diff --git a/apps/docs/app/assets/styles/tailwind.css b/apps/docs/app/assets/styles/tailwind.css index 2ccb9883..c790a779 100644 --- a/apps/docs/app/assets/styles/tailwind.css +++ b/apps/docs/app/assets/styles/tailwind.css @@ -6,4 +6,5 @@ @import 'tw-animate-css'; @source "../../../../../packages/vue-ui/src"; +@source "../../../../../packages/nuxt-core/"; @source "../../../content/**/*.md"; diff --git a/apps/docs/app/assets/website-logo.ts b/apps/docs/app/assets/website-logo.ts new file mode 100644 index 00000000..516c8170 --- /dev/null +++ b/apps/docs/app/assets/website-logo.ts @@ -0,0 +1,2 @@ +import WebsiteLogo from '@repo/vue-ui/assets/brands/NetLogoUserManual.svg'; +export { WebsiteLogo }; diff --git a/apps/docs/app/components/Announcer.vue b/apps/docs/app/components/Announcer.vue index bb8b603c..357fce1e 100644 --- a/apps/docs/app/components/Announcer.vue +++ b/apps/docs/app/components/Announcer.vue @@ -31,14 +31,18 @@ const fetcher = async () => { const { data: announcements } = await useLazyAsyncData('announcements', fetcher, { server: false }); const route = useRoute(); -const productInfo = useProductInfo(); +const { + public: { + website: { productVersion }, + }, +} = useRuntimeConfig(); const toast = useToast(); const visibleAnnouncements = computed(() => { if (!announcements.value) return []; return announcements.value.filter((item) => { - return item.appliesTo(route.path, productInfo.productVersion); + return item.appliesTo(route.path, productVersion); }); }); diff --git a/apps/docs/app/components/ClientNavbar.vue b/apps/docs/app/components/ClientNavbar.vue index 3a979316..e8ceccc5 100644 --- a/apps/docs/app/components/ClientNavbar.vue +++ b/apps/docs/app/components/ClientNavbar.vue @@ -28,7 +28,7 @@ @@ -57,8 +57,11 @@ import { useMediaQuery } from '@vueuse/core'; import { onMounted, ref, watch } from 'vue'; import { onVersionChange, pullVersionsFromSource } from '~~/shared/versions'; -const productInfo = useProductInfo(); -const runtimeInfo = useRuntimeInfo(); +const { + public: { + website: { productWebsite, productVersion, versionsSrc }, + }, +} = useRuntimeConfig(); interface NavbarLink { title: string; @@ -174,7 +177,7 @@ const arePathnamesCongruent = (windowPathname: string, candidatePathname: string pathname .split('/') .filter((p) => p.length > 0) - .filter((p) => p !== productInfo.productVersion) + .filter((p) => p !== productVersion) .map((p) => p.replace(/\$/, '')) .map((p) => p.trim().split('#')[0] ?? '') .join('/'); @@ -196,7 +199,7 @@ const isLinkParentActive = (link: NavbarLink, currentPath: string): boolean => { // Version selection const versions = ref>({ - [productInfo.productVersion]: { displayName: productInfo.productVersion }, + [productVersion]: { displayName: productVersion }, '7.0.2': { displayName: '7.0.2' }, '7.0.1': { displayName: '7.0.1' }, '7.0.0': { displayName: '7.0.0' }, @@ -223,14 +226,14 @@ const versions = ref>({ '1.0': { disabled: true }, }); -const selectedVersion = ref(productInfo.productVersion); +const selectedVersion = ref(productVersion); onMounted(() => { if (import.meta.client) { updateActiveStates(); handleMediaQueryChange(); setTimeout(async () => { - versions.value = await pullVersionsFromSource(versions.value, runtimeInfo.versionsSrc); + versions.value = await pullVersionsFromSource(versions.value, versionsSrc); }, 0); } }); diff --git a/apps/docs/app/components/PrimitiveCatalog/PrimitiveCatalog.vue b/apps/docs/app/components/PrimitiveCatalog/PrimitiveCatalog.vue index 19b52dc3..6a7f4814 100644 --- a/apps/docs/app/components/PrimitiveCatalog/PrimitiveCatalog.vue +++ b/apps/docs/app/components/PrimitiveCatalog/PrimitiveCatalog.vue @@ -28,7 +28,11 @@ import { wrapCacheLocalStorage } from '@repo/utils/lib/storage/cached-local-stor import { removeHtmlExtension } from '~/utils/url'; import type { CatalogItemData, PrimitiveCatalogProps, SideCatalogItem } from './types'; -const productInfo = useProductInfo(); +const { + public: { + website: { productVersion }, + }, +} = useRuntimeConfig(); const { dictionaryDisplayName, dictionaryHomeDirectory, indexFileURI, currentItemId, currentItemLabel, primRoot } = defineProps(); @@ -69,7 +73,7 @@ const { error, } = await useLazyAsyncData( indexFileURI, - wrapCacheLocalStorage([productInfo.productVersion, 'primitive-catalog', indexFileURI].join('-'), null, () => + wrapCacheLocalStorage([productVersion, 'primitive-catalog', indexFileURI].join('-'), null, () => fetcher(indexFileURI), ), { server: false }, diff --git a/apps/docs/app/composables/configuration.ts b/apps/docs/app/composables/configuration.ts deleted file mode 100644 index 7e54e7ac..00000000 --- a/apps/docs/app/composables/configuration.ts +++ /dev/null @@ -1,31 +0,0 @@ -function getEnvironmentVariable(key: string, defaultValue: T, showWarning: boolean = true): T { - const { public: config } = useRuntimeConfig(); - const value = (config[key] as T | undefined) ?? (process.env[key] as T | undefined); - - if (showWarning && !value) { - console.warn(`Environment variable ${key} is not set. Using default value: ${defaultValue}`); - } - - return value ?? defaultValue; -} - -function useProductInfo() { - return { - productName: getEnvironmentVariable('PRODUCT_NAME', 'NetLogo Documentation'), - productVersion: getEnvironmentVariable('PRODUCT_VERSION', '7.0.3'), - productDisplayName: getEnvironmentVariable('PRODUCT_DISPLAY_NAME', '7.0.3'), - productBuildDate: getEnvironmentVariable('PRODUCT_BUILD_DATE', new Date().toISOString()), - productWebsite: getEnvironmentVariable('PRODUCT_WEBSITE', 'https://docs.netlogo.org'), - isBeta: getEnvironmentVariable('PRODUCT_VERSION', '7.0.3').toLowerCase().includes('beta'), - }; -} - -function useRuntimeInfo() { - const productInfo = useProductInfo(); - return { - noAutogen: getEnvironmentVariable('NO_AUTOGEN', 'false', false), - versionsSrc: productInfo.productWebsite + getEnvironmentVariable('VERSIONS_SRC', '/versions.json'), - }; -} - -export { getEnvironmentVariable, useProductInfo, useRuntimeInfo }; diff --git a/apps/docs/app/layouts/default.vue b/apps/docs/app/layouts/default.vue index d0fa642d..ebb3332b 100644 --- a/apps/docs/app/layouts/default.vue +++ b/apps/docs/app/layouts/default.vue @@ -11,14 +11,18 @@ diff --git a/apps/docs/app/plugins/force-light-mode.ts b/apps/docs/app/plugins/force-light-mode.ts index 9f5375a6..a73b294b 100644 --- a/apps/docs/app/plugins/force-light-mode.ts +++ b/apps/docs/app/plugins/force-light-mode.ts @@ -1,7 +1,11 @@ export default defineNuxtPlugin((nuxtApp) => { const colorMode = useColorMode(); - nuxtApp.hook("app:mounted", () => { - colorMode.preference = "light"; - colorMode.value = "light"; + nuxtApp.hook('app:mounted', () => { + colorMode.forced = true; + // @ts-expect-error - preference may or may not exist depending + // on how @nuxtjs/color-mode was installed + colorMode.preference = 'light'; + // @ts-expect-error - see above + colorMode.value = 'light'; }); }); diff --git a/apps/docs/env.public.ts b/apps/docs/env.public.ts deleted file mode 100644 index c9b28cfa..00000000 --- a/apps/docs/env.public.ts +++ /dev/null @@ -1,40 +0,0 @@ -const publicEnvironmentVariablesKeys = [ - 'PRODUCT_NAME', - 'PRODUCT_VERSION', - 'PRODUCT_BUILD_DATE', - 'PRODUCT_DISPLAY_NAME', - 'PRODUCT_WEBSITE', - 'NO_AUTOGEN', - 'VERSIONS_SRC', -] as const; - -const NOT_REQUIRED_ENV_VARS = new Set(['NO_AUTOGEN']); - -const publicEnvironmentVariables = Object.fromEntries( - publicEnvironmentVariablesKeys.map((key) => [key, process.env[key]]), -); - -export function verifyEnvironmentVariables() { - for (const key of publicEnvironmentVariablesKeys) { - if (NOT_REQUIRED_ENV_VARS.has(key)) continue; - const res = process.env[key]; - if (!res || res.length === 0) { - throw new Error(`Missing required environment variable: ${key}`); - } - } - - const productVersion = process.env['PRODUCT_VERSION'] as string; - if (!/^\d/.test(productVersion)) { - throw new Error(`PRODUCT_VERSION must start with a digit: ${productVersion}`); - } - - const productWebsite = process.env['PRODUCT_WEBSITE'] as string; - if (productWebsite.endsWith('/')) { - throw new Error(`PRODUCT_WEBSITE must not end with a slash: ${productWebsite}`); - } - if (!/^https?:\/\//.test(productWebsite)) { - throw new Error(`PRODUCT_WEBSITE must start with http:// or https://: ${productWebsite}`); - } -} - -export { publicEnvironmentVariables }; diff --git a/apps/docs/lib/docs/NetLogoDocs.ts b/apps/docs/lib/docs/NetLogoDocs.ts index 13a50307..4e1cf890 100644 --- a/apps/docs/lib/docs/NetLogoDocs.ts +++ b/apps/docs/lib/docs/NetLogoDocs.ts @@ -2,7 +2,7 @@ import type TemplateRenderer from "@repo/template"; import type { PageResult } from "@repo/template"; import { getEntryNames } from "@repo/netlogo-docs/dictionary"; -import { saveNavigationMetadata } from "@repo/netlogo-docs/helpers"; +import { saveNavigationMetadata } from "@repo/netlogo-docs/helpers-node"; import { generatePrimitiveIndex } from "@repo/netlogo-docs/primitive-index"; import * as Constants from "./constants"; diff --git a/apps/docs/lib/docs/autogen.config.ts b/apps/docs/lib/docs/autogen.config.ts index 184d1c34..b16d2de4 100644 --- a/apps/docs/lib/docs/autogen.config.ts +++ b/apps/docs/lib/docs/autogen.config.ts @@ -1,4 +1,4 @@ -import { appendAssetsRootToMetadata } from '@repo/netlogo-docs/helpers'; +import { appendAssetsRootToMetadata } from '@repo/netlogo-docs/helpers-node'; import type { ProjectConfigInput } from '@repo/template/schemas'; const autogenConfig: ProjectConfigInput = { diff --git a/apps/docs/lib/docs/runDocsAutogen.ts b/apps/docs/lib/docs/runDocsAutogen.ts index 28d1b433..79c78d16 100644 --- a/apps/docs/lib/docs/runDocsAutogen.ts +++ b/apps/docs/lib/docs/runDocsAutogen.ts @@ -4,7 +4,7 @@ import config from "./autogen.config"; import { prebuildHandlebarsHelper } from "@repo/netlogo-docs/dictionary"; import * as ExtensionDocs from "@repo/netlogo-docs/extension-docs"; -import { generateRoutesFile } from "@repo/netlogo-docs/helpers"; +import { generateRoutesFile } from "@repo/netlogo-docs/helpers-node"; import { generateBetweenDirectoriesPages, generateDictionary3DPrimitivePages, diff --git a/apps/docs/nuxt.config.pdf.ts b/apps/docs/nuxt.config.pdf.ts index b43fb0ba..0c7f0135 100644 --- a/apps/docs/nuxt.config.pdf.ts +++ b/apps/docs/nuxt.config.pdf.ts @@ -1,4 +1,4 @@ -import { getRoutesSubset } from '@repo/netlogo-docs/helpers'; +import { getRoutesSubset } from '@repo/netlogo-docs/helpers-node'; import type { DefineNuxtConfig } from 'nuxt/config'; const pdfOverrides: Parameters[0] = { @@ -34,7 +34,6 @@ const pdfOverrides: Parameters[0] = { prerender: { autoSubfolderIndex: false, crawlLinks: false, - concurrency: 20, routes: await getRoutesSubset(process.env.NITRO_PRERENDER_ROUTES?.split(',').map((route) => route.trim()) || []), }, }, diff --git a/apps/docs/nuxt.config.ts b/apps/docs/nuxt.config.ts index cf78de57..0bb6dd77 100644 --- a/apps/docs/nuxt.config.ts +++ b/apps/docs/nuxt.config.ts @@ -1,18 +1,16 @@ -import { deepMerge } from '@repo/utils/std/objects'; -import tailwindcss from '@tailwindcss/vite'; +import { addNuxtContentAssetsRoot } from '@repo/netlogo-docs/helpers-node'; +import type { DefineNuxtConfig } from 'nuxt/config'; import { getDocumentedExtensionBuilders } from '@repo/netlogo-docs/extension-docs'; -import { addNuxtContentAssetsRoot, getRoutes } from '@repo/netlogo-docs/helpers'; +import { deepMerge } from '@repo/utils/std/objects'; +import pdfOverrides from './nuxt.config.pdf'; +import { websiteConfigSchema } from './runtime.config'; + +import * as MarkdownConfig from '@repo/nuxt-core/markdown.config'; import autogenConfig from './lib/docs/autogen.config'; -import * as MarkdownConfig from './lib/docs/markdown.config'; import runDocsAutogen from './lib/docs/runDocsAutogen'; -import type { DefineNuxtConfig } from 'nuxt/config'; -import { publicEnvironmentVariables, verifyEnvironmentVariables } from './env.public'; -import pdfOverrides from './nuxt.config.pdf'; -import { vueUiSrc, vueUiStyles } from './turbo'; - type NuxtConfigInput = Parameters[0]; const basePath = process.env['BASE_PATH'] ?? '/'; @@ -23,71 +21,33 @@ if (isUsingPdfConfig) { console.info('[repo] Running with PDF generation configuration overrides'); } -// https://nuxt.com/docs/api/configuration/nuxt-config +const extensions = (await getDocumentedExtensionBuilders(autogenConfig)).map((ext) => ({ + title: ext.fullName, + href: `/${ext.name.toLowerCase()}`, +})); export default defineNuxtConfig( deepMerge( { - compatibilityDate: '2025-07-15', + extends: ['@repo/nuxt-core/nuxt.config.ts'], + app: { baseURL: basePath, - rootId: '__netlogo', - }, - devtools: { enabled: true }, - ssr: true, - // prettier-ignore - modules: [ - '@nuxtjs/sitemap', // Adds sitemap.xml - '@repo/nuxt-content-assets', // Local asset loading based on relative paths in Markdowns - '@nuxt/content', // Query, navigation, search, and rendering of Markdown files - '@nuxt/ui', // Reka-UI library for Nuxt - '@nuxt/icon', // Optimized icons with iconify - 'nuxt-svgo', // SVG loader from file - '@nuxt/eslint', // Linter - 'nuxt-gtag', // Google - 'nuxt-og-image', // Dynamic Open Graph image generation - 'nuxt-link-checker', // Link checking post-build - ], - site: { url: 'https://docs.netlogo.org', name: process.env['PRODUCT_NAME'] }, - css: ['~/assets/styles/main.scss', '~/assets/styles/tailwind.css', vueUiStyles], - colorMode: { - preference: 'light', - }, - - gtag: { - id: 'G-ZET3KSPLMC', }, - imports: { - autoImport: true, - transform: { - exclude: [/\bnode_modules\b/, /\b\.git\b/], - }, - }, + ssr: true, - $development: { - experimental: { - payloadExtraction: false, - }, - }, - - icon: { - mode: 'css', - cssLayer: 'base', - }, + routeRules: + process.env.DOCS_USE_CLIENT_CATALOG === '1' + ? { + '/dict/**': { ssr: false, prerender: true }, + ...Object.fromEntries(extensions.map((ext) => [`${ext.href}/**`, { ssr: false, prerender: true }])), + } + : {}, components: [ - { - path: vueUiSrc, - global: true, - pattern: '**/*.vue', - ignore: ['**/examples/*.vue', '**/tests/*.vue'], - pathPrefix: false, - watch: true, - }, { path: '~/components', - global: true, pattern: '**/*.vue', ignore: ['**/examples/*.vue', '**/tests/*.vue'], pathPrefix: false, @@ -95,77 +55,36 @@ export default defineNuxtConfig( }, ], - ignore: ['.build/', '.latest/', '.static/', '.preview/'], - - svgo: { - customComponent: 'SvgImport', + gtag: { + id: 'G-ZET3KSPLMC', }, vite: { - plugins: [tailwindcss()], optimizeDeps: { include: ['@repo/vue-ui', ...MarkdownConfig.externalImports], }, - server: { - hmr: { - overlay: true, - }, - watch: { usePolling: true }, - }, }, content: { build: MarkdownConfig.buildOptions, - renderer: { - anchorLinks: false, - }, - watch: { enabled: false }, - }, - - contentAssets: { - imageSize: 'style attrs', - contentExtensions: 'md', - debug: true, - overrideStaticDimensions: false, - }, - - ogImage: { - defaults: { - extension: 'jpeg', - sharp: { - quality: 70, - }, - }, }, linkChecker: { excludeLinks: ['/*.pdf', `${basePath}NetLogo_User_Manual.pdf`], - skipInspections: ['no-baseless', 'no-underscores', 'trailing-slash'], - report: { - html: process.env['CHECK'] === 'true', - }, }, runtimeConfig: { public: { - extensions: (await getDocumentedExtensionBuilders(autogenConfig)).map((ext) => ({ - title: ext.fullName, - href: `/${ext.name.toLowerCase()}`, - })), - ...publicEnvironmentVariables, + extensions, + website: websiteConfigSchema.parse(process.env), }, }, hooks: { async ready() { - verifyEnvironmentVariables(); - - console.info(`[repo] Using @repo/vue-ui source path: ${vueUiSrc}`); - console.info(`[repo] Using @repo/vue-ui styles path: ${vueUiStyles}`); - - const noAutogen = process.env['NO_AUTOGEN'] === 'true'; - if (noAutogen === true) return; - await runDocsAutogen(); + if (process.env['NO_AUTOGEN'] !== 'true') { + await runDocsAutogen(); + } }, 'content:file:beforeParse'(ctx) { addNuxtContentAssetsRoot(ctx.file); @@ -176,12 +95,6 @@ export default defineNuxtConfig( static: true, serveStatic: true, baseURL: basePath, - prerender: { - autoSubfolderIndex: false, - crawlLinks: true, - concurrency: 1, - routes: await getRoutes(), - }, }, }, optionalPdfLayer, diff --git a/apps/docs/package.json b/apps/docs/package.json index 7a20218a..c29fded3 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -4,10 +4,11 @@ "version": "0.0.1", "private": true, "engines": { - "node": "^22.0.0" + "node": "^24.0.0" }, "scripts": { - "init": "nuxt prepare", + "create:env": "node -e \"const fs = require('fs'); if (fs.existsSync('.env.example')) { if (!fs.existsSync('.env')) { fs.copyFileSync('.env.example', '.env'); console.log('✓ .env created from .env.example'); } else { console.log('✓ .env already exists'); } } else { console.log('⚠ .env.example not found'); }\"", + "init": "npm run create:env && nuxt prepare", "docs:build": "./scripts/generate.sh", "docs:deploy": "bash ./scripts/deploy.sh", "docs:preview": "bash ./scripts/preview.sh", @@ -22,7 +23,7 @@ "nuxt:dev:no-autogen": "NO_AUTOGEN=true nuxt dev --port 3001", "nuxt:dev:watch": "nuxt dev --port 3001 --dotenv .env.development", "nuxt:dev:no-autogen:watch": "NO_AUTOGEN=true nuxt dev --port 3001 --dotenv .env.development", - "nuxt:generate": "nuxt prepare && nuxt generate", + "nuxt:generate": "NODE_OPTIONS=\"--max-old-space-size=6144\" nuxt prepare && nuxt generate", "nuxt:preview": "nuxt preview", "nuxt:postinstall": "nuxt prepare", "nuxt:prepare": "nuxt prepare", @@ -39,14 +40,13 @@ "@nuxt/ui": "4.2.1", "@nuxtjs/sitemap": "7.4.7", "@repo/eslint-config": "0.0.0", - "@repo/markdown": "0.0.0", "@repo/netlogo-docs": "0.0.0", - "@repo/nuxt-content-assets": "1.5.0", "@repo/tailwind-config": "0.0.0", "@repo/template": "0.0.0", "@repo/typescript-config": "0.0.0", "@repo/utils": "0.0.0", "@repo/vue-ui": "1.0.0", + "@repo/nuxt-core": "0.0.0", "@tailwindcss/postcss": "^4.1.13", "@tailwindcss/vite": "^4.1.18", "@vueuse/core": "^14.1.0", @@ -60,7 +60,6 @@ "nuxt-link-checker": "4.3.6", "nuxt-og-image": "^5.1.12", "nuxt-svgo": "4.2.6", - "radix-vue": "1.9.17", "reka-ui": "^2.6.0", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.18", @@ -85,6 +84,7 @@ "@iconify-json/streamline": "^1.2.5", "@nuxt/kit": "^4.2.1", "@repo/nuxt-content-assets": "1.5.0", + "@repo/markdown": "0.0.0", "eslint-plugin-perfectionist": "^4.15.1", "extend": "^3.0.2", "fs-extra": "^11.3.0", diff --git a/apps/docs/runtime.config.ts b/apps/docs/runtime.config.ts new file mode 100644 index 00000000..d0649336 --- /dev/null +++ b/apps/docs/runtime.config.ts @@ -0,0 +1,19 @@ +import { websiteConfigSchema as rootWebsiteConfigSchema } from '@repo/nuxt-core/runtime.config.schema'; +import * as z from 'zod'; + +const extraWebsiteConfig = z + .object({ + VERSIONS_SRC: z.string().default('/versions.json'), + }) + .transform((data) => ({ + versionsSrc: data.VERSIONS_SRC, + })); + +const websiteConfigSchema = rootWebsiteConfigSchema + .transform((data) => ({ + ...data, + productIsBeta: data.productVersion.toLowerCase().includes('beta'), + })) + .and(extraWebsiteConfig); + +export { websiteConfigSchema }; diff --git a/apps/docs/scripts/generate-manual/build-html.sh b/apps/docs/scripts/generate-manual/build-html.sh index a540a0b6..b1602cc9 100755 --- a/apps/docs/scripts/generate-manual/build-html.sh +++ b/apps/docs/scripts/generate-manual/build-html.sh @@ -2,4 +2,4 @@ source .env source ${BASH_SOURCE%/*}/pdf-content -DOCS_ENV_PDF=1 BUILD_LATEST=false ./scripts/generate.sh +DOCS_ENV_PDF=1 NUXT_PRIM_TOOLTIP_DISABLED=1 BUILD_LATEST=false ./scripts/generate.sh diff --git a/apps/learn/.env b/apps/learn/.env deleted file mode 100644 index ff5c203f..00000000 --- a/apps/learn/.env +++ /dev/null @@ -1,11 +0,0 @@ -PROJECT_ROOT="." - -PRODUCT_NAME="NetLogo Learn Guide" -PRODUCT_WEBSITE="https://learn.netlogo.org" - - -REPO_ROOT=../.. -EXTENSIONS_DIR="$REPO_ROOT/external/extensions" - -BUILD_REPO="https://github.com/NetLogo/Helio.git" -BUILD_BRANCH="deploy/learn" diff --git a/apps/learn/.env.development b/apps/learn/.env.development deleted file mode 100644 index 20016179..00000000 --- a/apps/learn/.env.development +++ /dev/null @@ -1,15 +0,0 @@ -# Development environment variables for better HMR -NUXT_DEV_TOOLS=true -NUXT_TELEMETRY_DISABLED=true - -PROJECT_ROOT="." - -PRODUCT_NAME="NetLogo Learn Guide" -PRODUCT_WEBSITE="https://learn.netlogo.org" - - -REPO_ROOT=../.. -EXTENSIONS_DIR="$REPO_ROOT/external/extensions" - -BUILD_REPO="https://github.com/NetLogo/Helio.git" -BUILD_BRANCH="deploy/learn" diff --git a/apps/learn/.env.example b/apps/learn/.env.example new file mode 100644 index 00000000..66ffa2e8 --- /dev/null +++ b/apps/learn/.env.example @@ -0,0 +1,18 @@ +# Enable for Development +# NUXT_DEV_TOOLS=true +NUXT_TELEMETRY_DISABLED=true + +PROJECT_ROOT="." +REPO_ROOT=../.. + +PRODUCT_NAME="NetLogo Learn Guide" +PRODUCT_DESCRIPTION="A comprehensive guide to learning NetLogo, the multi-agent programmable modeling environment." +PRODUCT_LONG_DESCRIPTION="The official learning resources for NetLogo, the multi-agent programmable modeling environment. Explore tutorials, documentation, and guides to get started with NetLogo and enhance your modeling skills." +PRODUCT_KEYWORDS="NetLogo, NetLogo Web, NetLogo 3D, Hubnet Web, Agent-based Modeling, Turtles, Learn, Guide, Tutorials, Documentation, Multi-agent modeling, Programming environment" +PRODUCT_BUILD_DATE=$(date +%Y-%m-%dT%H:%M:%SZ) +PRODUCT_WEBSITE="https://learn.netlogo.org" + +BUILD_REPO="https://github.com/NetLogo/Helio.git" +BUILD_BRANCH="deploy/learn" + +DOCS_BUILD_SSR_CATALOG=1 diff --git a/apps/learn/.gitignore b/apps/learn/.gitignore index b760ce01..61075adb 100644 --- a/apps/learn/.gitignore +++ b/apps/learn/.gitignore @@ -26,3 +26,7 @@ logs .DS_Store .fleet .idea + +# Environment variables +.env +.env.local diff --git a/apps/learn/.nvmrc b/apps/learn/.nvmrc deleted file mode 100644 index 2bd5a0a9..00000000 --- a/apps/learn/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -22 diff --git a/apps/learn/app/assets/styles/tailwind.css b/apps/learn/app/assets/styles/tailwind.css index 2ccb9883..c790a779 100644 --- a/apps/learn/app/assets/styles/tailwind.css +++ b/apps/learn/app/assets/styles/tailwind.css @@ -6,4 +6,5 @@ @import 'tw-animate-css'; @source "../../../../../packages/vue-ui/src"; +@source "../../../../../packages/nuxt-core/"; @source "../../../content/**/*.md"; diff --git a/apps/learn/app/assets/website-logo.ts b/apps/learn/app/assets/website-logo.ts new file mode 100644 index 00000000..527ac6ed --- /dev/null +++ b/apps/learn/app/assets/website-logo.ts @@ -0,0 +1,2 @@ +import WebsiteLogo from '@repo/vue-ui/assets/brands/NetLogoLearnGuide.svg'; +export { WebsiteLogo }; diff --git a/apps/learn/app/components/Articles/ArticleBody.vue b/apps/learn/app/components/Articles/ArticleBody.vue index af2643aa..88c70d0c 100644 --- a/apps/learn/app/components/Articles/ArticleBody.vue +++ b/apps/learn/app/components/Articles/ArticleBody.vue @@ -4,7 +4,6 @@ :value="article.item" tag="article" class="prose mt-4 prose-medium prose-text-medium" - :components="MDCComponents" v-bind="dProps.contentRenderer" /> @@ -14,7 +13,6 @@ diff --git a/apps/learn/app/components/mdc/components.ts b/apps/learn/app/components/mdc/components.ts deleted file mode 100644 index 1e56ff0c..00000000 --- a/apps/learn/app/components/mdc/components.ts +++ /dev/null @@ -1,17 +0,0 @@ -import MDCButton from './MDCButton.vue'; -import MDCContainer from './MDCContainer.vue'; -import MDCErrorBanner from './MDCErrorBanner.vue'; -import MDCIcon from './MDCIcon.vue'; -import MDCPrimitive from './MDCPrimitive.vue'; -import NetLogoCommand from './NetLogoCommand.vue'; - -const MDCComponents = { - Button: MDCButton, - Container: MDCContainer, - Icon: MDCIcon, - NetLogoCommand: NetLogoCommand, - ErrorBanner: MDCErrorBanner, - Primitive: MDCPrimitive, -}; - -export default MDCComponents; diff --git a/apps/learn/app/composables/configuration.ts b/apps/learn/app/composables/configuration.ts deleted file mode 100644 index d25bfe90..00000000 --- a/apps/learn/app/composables/configuration.ts +++ /dev/null @@ -1,25 +0,0 @@ -function getEnvironmentVariable(key: string, defaultValue: T, showWarning: boolean = true): T { - const { public: config } = useRuntimeConfig(); - const value = (config[key] as T | undefined) ?? (process.env[key] as T | undefined); - - if (showWarning && !value) { - console.warn(`Environment variable ${key} is not set. Using default value: ${defaultValue}`); - } - - return value ?? defaultValue; -} - -function useProductInfo() { - return { - productName: getEnvironmentVariable('PRODUCT_NAME', 'NetLogo Learning'), - productWebsite: getEnvironmentVariable('PRODUCT_WEBSITE', 'https://learn.netlogo.org'), - }; -} - -function useRuntimeInfo() { - return { - noAutogen: getEnvironmentVariable('NO_AUTOGEN', 'false', false), - }; -} - -export { getEnvironmentVariable, useProductInfo, useRuntimeInfo }; diff --git a/apps/learn/app/composables/usePrimitive.ts b/apps/learn/app/composables/usePrimitive.ts deleted file mode 100644 index 2264e58a..00000000 --- a/apps/learn/app/composables/usePrimitive.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Primitives } from '@repo/common-data'; - -export const usePrimitive = async ({ name }: { name: string }) => { - const { data, pending, error } = await useAsyncData(`primitive-${name}`, () => queryCollection('primitives').all(), { - deep: false, - server: true, - dedupe: 'defer', - transform: async (allPrimitivesData) => { - const primitives = Primitives.getInstance(allPrimitivesData[0]?.primitives ?? []); - const rawPrimitive = { ...primitives.getPrimByName(name) }; - - return rawPrimitive || null; - }, - }); - - const primitive = computed(() => { - if (!data.value) return null; - return data.value; - }); - - return { - primitive, - pending, - error, - }; -}; diff --git a/apps/learn/app/layouts/default.vue b/apps/learn/app/layouts/default.vue index c4b4da00..0cbe2198 100644 --- a/apps/learn/app/layouts/default.vue +++ b/apps/learn/app/layouts/default.vue @@ -10,11 +10,14 @@ + + diff --git a/packages/nuxt-core/app/plugins/force-light-mode.ts b/packages/nuxt-core/app/plugins/force-light-mode.ts index 9f5375a6..9225b13d 100644 --- a/packages/nuxt-core/app/plugins/force-light-mode.ts +++ b/packages/nuxt-core/app/plugins/force-light-mode.ts @@ -1,7 +1,11 @@ export default defineNuxtPlugin((nuxtApp) => { const colorMode = useColorMode(); nuxtApp.hook("app:mounted", () => { + colorMode.forced = true; + // @ts-expect-error - preference may or may not exist depending + // on how @nuxtjs/color-mode was installed colorMode.preference = "light"; + // @ts-expect-error - see above colorMode.value = "light"; }); }); diff --git a/apps/learn/app/components/mdc/MDCButton.vue b/packages/nuxt-core/layers/mdc/app/components/MDCButton.global.vue similarity index 100% rename from apps/learn/app/components/mdc/MDCButton.vue rename to packages/nuxt-core/layers/mdc/app/components/MDCButton.global.vue diff --git a/apps/learn/app/components/mdc/MDCContainer.vue b/packages/nuxt-core/layers/mdc/app/components/MDCContainer.global.vue similarity index 50% rename from apps/learn/app/components/mdc/MDCContainer.vue rename to packages/nuxt-core/layers/mdc/app/components/MDCContainer.global.vue index 3d3362c4..f7c84b6c 100644 --- a/apps/learn/app/components/mdc/MDCContainer.vue +++ b/packages/nuxt-core/layers/mdc/app/components/MDCContainer.global.vue @@ -1,5 +1,5 @@ diff --git a/apps/learn/app/components/mdc/MDCErrorBanner.vue b/packages/nuxt-core/layers/mdc/app/components/MDCErrorBanner.global.vue similarity index 100% rename from apps/learn/app/components/mdc/MDCErrorBanner.vue rename to packages/nuxt-core/layers/mdc/app/components/MDCErrorBanner.global.vue diff --git a/apps/learn/app/components/mdc/MDCIcon.vue b/packages/nuxt-core/layers/mdc/app/components/MDCIcon.global.vue similarity index 100% rename from apps/learn/app/components/mdc/MDCIcon.vue rename to packages/nuxt-core/layers/mdc/app/components/MDCIcon.global.vue diff --git a/packages/nuxt-core/layers/mdc/app/components/MDCUnstylizedHeading.global.vue b/packages/nuxt-core/layers/mdc/app/components/MDCUnstylizedHeading.global.vue new file mode 100644 index 00000000..c27e9d82 --- /dev/null +++ b/packages/nuxt-core/layers/mdc/app/components/MDCUnstylizedHeading.global.vue @@ -0,0 +1,13 @@ + + + diff --git a/apps/learn/app/components/mdc/NetLogoCommand.vue b/packages/nuxt-core/layers/mdc/app/components/NetLogoCommand.global.vue similarity index 100% rename from apps/learn/app/components/mdc/NetLogoCommand.vue rename to packages/nuxt-core/layers/mdc/app/components/NetLogoCommand.global.vue diff --git a/packages/nuxt-core/layers/mdc/app/components/ProseA.global.vue b/packages/nuxt-core/layers/mdc/app/components/ProseA.global.vue new file mode 100644 index 00000000..152951fa --- /dev/null +++ b/packages/nuxt-core/layers/mdc/app/components/ProseA.global.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/packages/nuxt-core/layers/mdc/nuxt.config.ts b/packages/nuxt-core/layers/mdc/nuxt.config.ts new file mode 100644 index 00000000..340be7ed --- /dev/null +++ b/packages/nuxt-core/layers/mdc/nuxt.config.ts @@ -0,0 +1,15 @@ +export default defineNuxtConfig({ + mdc: { + components: { + prose: false, + map: { + Icon: "MDCIcon", + Container: "MDCContainer", + ErrorBanner: "MDCErrorBanner", + Button: "MDCButton", + NetlogoCommand: "NetLogoCommand", + a: "ProseA", + }, + }, + }, +}); diff --git a/apps/learn/app/components/mdc/MDCPrimitive.vue b/packages/nuxt-core/layers/primitive-tooltip/app/components/MDCPrimitive.global.vue similarity index 100% rename from apps/learn/app/components/mdc/MDCPrimitive.vue rename to packages/nuxt-core/layers/primitive-tooltip/app/components/MDCPrimitive.global.vue diff --git a/apps/learn/app/components/PrimTooltip.vue b/packages/nuxt-core/layers/primitive-tooltip/app/components/PrimTooltip.vue similarity index 57% rename from apps/learn/app/components/PrimTooltip.vue rename to packages/nuxt-core/layers/primitive-tooltip/app/components/PrimTooltip.vue index 89b6bc90..fe7d0ffc 100644 --- a/apps/learn/app/components/PrimTooltip.vue +++ b/packages/nuxt-core/layers/primitive-tooltip/app/components/PrimTooltip.vue @@ -11,35 +11,27 @@ :delay-duration="300" :disabled="isNested" > - - {{ props.name }} - - + - - {{ props.name }} - - + + + + + + {{ props.name }} + diff --git a/packages/nuxt-core/layers/primitive-tooltip/app/components/PrimitiveMarkup.vue b/packages/nuxt-core/layers/primitive-tooltip/app/components/PrimitiveMarkup.vue new file mode 100644 index 00000000..624e3546 --- /dev/null +++ b/packages/nuxt-core/layers/primitive-tooltip/app/components/PrimitiveMarkup.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/nuxt-core/layers/primitive-tooltip/app/composables/usePrimitive.ts b/packages/nuxt-core/layers/primitive-tooltip/app/composables/usePrimitive.ts new file mode 100644 index 00000000..c1caf32a --- /dev/null +++ b/packages/nuxt-core/layers/primitive-tooltip/app/composables/usePrimitive.ts @@ -0,0 +1,127 @@ +import { Primitives } from "@repo/common-data"; +import { escapeHTML } from "@repo/utils/std/string"; + +type ParsedMarkdown = Awaited>; +type Primitive = ReturnType | null | undefined; +type ParsedPrimitive = + | (Omit, "description" | "examples"> & { + description: ParsedMarkdown | null; + examples: ParsedMarkdown | null; + }) + | undefined + | null; +let primitives: Primitives | undefined = undefined; + +const usePrimitive = async ({ name }: { name: string }) => { + if (import.meta.dev) { + return usePrimitiveDev({ name }); + } + + const { data, pending, error } = await useAsyncData( + `primitive-${name}`, + () => queryCollection("primitives").all(), + { + deep: false, + server: true, + dedupe: "defer", + transform: async (allPrimitivesData) => { + const primitives = Primitives.getInstance(allPrimitivesData[0]?.primitives ?? []); + const prim = await parsePrimitive(primitives.getPrimByName(name)); + + return prim ?? null; + }, + }, + ); + + const primitive = computed(() => { + if (!data.value) return null; + return data.value; + }); + + return { + primitive, + pending, + error, + }; +}; + +const usePrimitiveDev = async ({ name }: { name: string }) => { + const { data } = await useAsyncData("primitives", () => queryCollection("primitives").all(), { + deep: false, + server: true, + dedupe: "defer", + }); + + if (data.value) { + primitives = Primitives.getInstance(data.value[0]?.primitives ?? []); + } + + const prim = await parsePrimitive(primitives?.getPrimByName(name)); + + return { + primitive: computed(() => prim), + pending: computed(() => typeof primitives === "undefined" && !primitives), + error: computed(() => null), + }; +}; + +const useNoPrimitive = () => { + return { + primitive: computed(() => null), + pending: computed(() => false), + error: computed(() => null), + }; +}; + +// Utilities +function convertRelativeMarkdownLinksToAbsolute(markdown: string, baseUrl: string): string { + const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + const convertedMarkdown = markdown.replace(markdownLinkRegex, (match, text, url) => { + if (!url.startsWith("http://") && !url.startsWith("https://")) { + const absoluteUrl = new URL(url, baseUrl).href; + return `[${text}](${absoluteUrl})`; + } + return match; + }); + + return convertedMarkdown; +} + +async function parsePrimitive(prim: Primitive): Promise { + if (!prim) return null; + if (prim.description) { + prim.description = convertRelativeMarkdownLinksToAbsolute( + prim.description, + "https://docs.netlogo.org/", + ); + } + + const [parsedDescription, parsedExamples] = await Promise.all([ + prim.description ? await parseMarkdown(prim.description) : null, + prim.examples ? await parseMarkdown(buildExamples(prim)) : null, + ]); + + return { + ...prim, + description: parsedDescription, + examples: parsedExamples, + }; +} + +function buildExamples(primitive: Primitive | null): string { + return `
${ + primitive?.examples + ?.map( + (ex: string) => ` + + + ${escapeHTML(ex)} + + + `, + ) + .join("") ?? "" + }
`; +} + +export { useNoPrimitive, usePrimitive }; diff --git a/packages/nuxt-core/layers/primitive-tooltip/content.config.ts b/packages/nuxt-core/layers/primitive-tooltip/content.config.ts new file mode 100644 index 00000000..51207730 --- /dev/null +++ b/packages/nuxt-core/layers/primitive-tooltip/content.config.ts @@ -0,0 +1,20 @@ +import { defineCollection, defineContentConfig } from "@nuxt/content"; +import { PrimitiveSchema } from "@repo/common-data"; +import { fileURLToPath } from "url"; +import * as z from "zod"; + +const PrimitiveDataSchema = z.object({ + primitives: z.array(PrimitiveSchema), +}); + +const primitivesCollection = defineCollection({ + type: "data", + source: fileURLToPath(import.meta.resolve("@repo/common-data/datasets/primitives.yaml")), + schema: PrimitiveDataSchema, +}); + +export default defineContentConfig({ + collections: { + primitives: primitivesCollection, + }, +}); diff --git a/packages/nuxt-core/layers/primitive-tooltip/nuxt.config.ts b/packages/nuxt-core/layers/primitive-tooltip/nuxt.config.ts new file mode 100644 index 00000000..fa6c7c6f --- /dev/null +++ b/packages/nuxt-core/layers/primitive-tooltip/nuxt.config.ts @@ -0,0 +1,9 @@ +export default defineNuxtConfig({ + mdc: { + components: { + map: { + primitive: "MDCPrimitive", + }, + }, + }, +}); diff --git a/apps/docs/lib/docs/markdown.config.ts b/packages/nuxt-core/markdown.config.ts similarity index 56% rename from apps/docs/lib/docs/markdown.config.ts rename to packages/nuxt-core/markdown.config.ts index 62b34ebb..e19a0e74 100644 --- a/apps/docs/lib/docs/markdown.config.ts +++ b/packages/nuxt-core/markdown.config.ts @@ -1,6 +1,6 @@ -import type { MarkdownPlugin, ModuleOptions } from '@nuxt/content'; +import type { MarkdownPlugin, ModuleOptions } from "@nuxt/content"; -import { isNonEmptyString } from '@repo/utils/std/string'; +import { isNonEmptyString } from "@repo/utils/std/string"; import { getDefaultRehypePluginsObject, @@ -8,30 +8,32 @@ import { type Hast, NetLogoRemarkWikiLink, Utilities, -} from '@repo/markdown'; -import { slugifyOptions, toSlug } from '@repo/netlogo-docs/helpers'; +} from "@repo/markdown"; +import { slugifyOptions, toSlug } from "@repo/netlogo-docs/helpers"; -import rehypeAutolinkHeadings, { type Options as RehypeAutolinkHeadingsOptions } from 'rehype-autolink-headings'; -import remarkToc from 'remark-toc'; +import rehypeAutolinkHeadings, { + type Options as RehypeAutolinkHeadingsOptions, +} from "rehype-autolink-headings"; +import remarkToc from "remark-toc"; const { hasNoChild, isNodeElement } = Utilities; const tocConfig = { maxDepth: 2, - heading: 'Table of Contents', + heading: "Table of Contents", } as const; class WikiLinkWrapper { private _internalType: - | 'absolute' - | 'absolute-anchor' - | 'relative' - | 'relative-anchor' - | 'dictionary' - | 'external' - | 'external-anchor' - | 'image' - | 'unknown' = 'unknown'; + | "absolute" + | "absolute-anchor" + | "relative" + | "relative-anchor" + | "dictionary" + | "external" + | "external-anchor" + | "image" + | "unknown" = "unknown"; constructor( public permalink: string, public linkType: NetLogoRemarkWikiLink.LinkType, @@ -43,45 +45,45 @@ class WikiLinkWrapper { private _setInternalType() { const hasAnchor = isNonEmptyString(this.anchor); - const isRelative = this.permalink.startsWith('./'); - const isAbsoluteWithoutSlashes = !this.permalink.includes('/') && !isRelative && hasAnchor; - const isAbsolute = this.permalink.startsWith('/') || isAbsoluteWithoutSlashes; - if (['wikiLink', 'missingLink'].includes(this.linkType)) { + const isRelative = this.permalink.startsWith("./"); + const isAbsoluteWithoutSlashes = !this.permalink.includes("/") && !isRelative && hasAnchor; + const isAbsolute = this.permalink.startsWith("/") || isAbsoluteWithoutSlashes; + if (["wikiLink", "missingLink"].includes(this.linkType)) { // Absolute Anchor: /Page#Anchor, /Page# or Page#Anchor <== Common. // Absolute: /Page (not Page) // Relative Anchor: ./Page#Anchor or ./Page# <== Not common. Only one relative case supported. // Relative: ./Page // Dictionary: Page (no slashes) or Page/ <== Most common. if (isAbsolute && hasAnchor) { - this._internalType = 'absolute-anchor'; + this._internalType = "absolute-anchor"; } else if (isAbsolute) { - this._internalType = 'absolute'; + this._internalType = "absolute"; } else if (isRelative && hasAnchor) { - this._internalType = 'relative-anchor'; + this._internalType = "relative-anchor"; } else if (isRelative) { - this._internalType = 'relative'; + this._internalType = "relative"; } else { - this._internalType = 'dictionary'; + this._internalType = "dictionary"; } - } else if (this.linkType === 'externalLink' && isNonEmptyString(this.anchor)) { - this._internalType = 'external-anchor'; - } else if (this.linkType === 'externalLink') { - this._internalType = 'external'; - } else if (this.linkType === 'imageLink') { - this._internalType = 'image'; + } else if (this.linkType === "externalLink" && isNonEmptyString(this.anchor)) { + this._internalType = "external-anchor"; + } else if (this.linkType === "externalLink") { + this._internalType = "external"; + } else if (this.linkType === "imageLink") { + this._internalType = "image"; } } private _normalize() { - if (this._internalType.startsWith('absolute') && this.permalink.startsWith('/')) { + if (this._internalType.startsWith("absolute") && this.permalink.startsWith("/")) { this.permalink = this.permalink.slice(1); } - if (this._internalType.startsWith('relative') && this.permalink.startsWith('./')) { + if (this._internalType.startsWith("relative") && this.permalink.startsWith("./")) { this.permalink = this.permalink.slice(2); } - if (isNonEmptyString(this.anchor) && this.anchor!.startsWith('#')) { + if (isNonEmptyString(this.anchor) && this.anchor!.startsWith("#")) { this.anchor = this.anchor!.slice(1); } } @@ -95,21 +97,21 @@ class WikiLinkWrapper { // - Omar I (Oct 23 2025) toString(): string { switch (this._internalType) { - case 'absolute-anchor': + case "absolute-anchor": return `/${this.permalink}#${encodeURIComponent(toSlug(this.anchor as string))}`; - case 'absolute': + case "absolute": return `/${this.permalink}`; - case 'relative-anchor': + case "relative-anchor": return `${this.permalink}#${encodeURIComponent(toSlug(this.anchor as string))}`; - case 'relative': + case "relative": return `${this.permalink}`; - case 'dictionary': + case "dictionary": return `/dictionary#${encodeURIComponent(toSlug(this.permalink))}`; - case 'external-anchor': + case "external-anchor": return `${this.permalink}#${encodeURIComponent(this.anchor as string)}`; - case 'external': + case "external": return `${this.permalink}`; - case 'image': + case "image": return `images/${this.permalink}`; default: return `#${encodeURIComponent(toSlug(this.permalink))}`; @@ -118,17 +120,21 @@ class WikiLinkWrapper { } const wikiLinkConfig: NetLogoRemarkWikiLink.Options = { - hrefTemplate: (permalink: string, linkType: NetLogoRemarkWikiLink.LinkType, anchor?: string | null) => { + hrefTemplate: ( + permalink: string, + linkType: NetLogoRemarkWikiLink.LinkType, + anchor?: string | null, + ) => { const wrapper = new WikiLinkWrapper(permalink, linkType, anchor); return wrapper.toString(); }, htmlOptions: { wikiLink: { - parentNode: { tagName: 'code', properties: { class: 'netlogo-command' } }, - target: '_self', + parentNode: { tagName: "primitive" }, + target: "_self", rel: undefined, titleTemplate: (permalink: string, displayText?: string) => { - if (typeof displayText === 'string') { + if (typeof displayText === "string") { return displayText; } return permalink; @@ -136,9 +142,9 @@ const wikiLinkConfig: NetLogoRemarkWikiLink.Options = { }, }, classNames: { - wikiLink: 'netlogo-wiki-link', - missingLink: 'missing-wiki-link', - imageLink: 'netlogo-image', + wikiLink: "netlogo-wiki-link", + missingLink: "missing-wiki-link", + imageLink: "netlogo-image", }, integration: { encode: (permalink: string) => permalink, @@ -146,17 +152,17 @@ const wikiLinkConfig: NetLogoRemarkWikiLink.Options = { }, greedyMatch: { maxIterations: 15, - consumableTypes: ['text', 'html', 'link'], + consumableTypes: ["text", "html", "link"], accessor: (node: { type: string }) => { switch (node.type) { - case 'text': + case "text": return (node as Hast.Text).value; - case 'html': - return (node as { type: 'html'; value: string }).value; - case 'link': - return (node as { type: 'link'; url: string }).url; + case "html": + return (node as { type: "html"; value: string }).value; + case "link": + return (node as { type: "link"; url: string }).url; default: - return ''; + return ""; } }, }, @@ -164,28 +170,31 @@ const wikiLinkConfig: NetLogoRemarkWikiLink.Options = { const autoLinkHeadingsConfig: RehypeAutolinkHeadingsOptions = { headingProperties: { - className: ['section-heading'], + className: ["section-heading"], }, properties: { - className: ['section-anchor'], + className: ["section-anchor"], }, - behavior: 'wrap', - test: hasNoChild((node) => isNodeElement(node) && node.tagName === 'a', { recursive: true }), + behavior: "wrap", + test: hasNoChild((node) => isNodeElement(node) && node.tagName === "a", { recursive: true }), }; -const buildOptions: Partial['build'] = { +const buildOptions: Partial["build"] = { markdown: { remarkPlugins: { ...(getDefaultRemarkPluginsObject() as MarkdownPluginCollection), - 'remark-toc': { instance: remarkToc, options: tocConfig, src: 'remark-toc' }, - '@repo/markdown/plugins/wikilink': { instance: NetLogoRemarkWikiLink.plugin, options: wikiLinkConfig }, + "remark-toc": { instance: remarkToc, options: tocConfig, src: "remark-toc" }, + "@repo/markdown/plugins/wikilink": { + instance: NetLogoRemarkWikiLink.plugin, + options: wikiLinkConfig, + }, }, rehypePlugins: { ...(getDefaultRehypePluginsObject() as MarkdownPluginCollection), - 'rehype-autolink-headings': { + "rehype-autolink-headings": { instance: rehypeAutolinkHeadings, options: autoLinkHeadingsConfig, - src: 'rehype-autolink-headings', + src: "rehype-autolink-headings", }, }, toc: { @@ -199,7 +208,10 @@ const buildOptions: Partial['build'] = { }; const externalImports: Array = Array.from( - new Set([...Object.keys(getDefaultRemarkPluginsObject()), ...Object.keys(getDefaultRehypePluginsObject())]), + new Set([ + ...Object.keys(getDefaultRemarkPluginsObject()), + ...Object.keys(getDefaultRehypePluginsObject()), + ]), ); type MarkdownPluginCollection = Record; diff --git a/packages/nuxt-core/nuxt.config.ts b/packages/nuxt-core/nuxt.config.ts index 5d644805..2e0a75ca 100644 --- a/packages/nuxt-core/nuxt.config.ts +++ b/packages/nuxt-core/nuxt.config.ts @@ -1,18 +1,21 @@ -// https://nuxt.com/docs/api/configuration/nuxt-config +import { getRoutesSubset } from "@repo/netlogo-docs/helpers-node"; import tailwindcss from "@tailwindcss/vite"; +import { websiteConfigSchema } from "./runtime.config.schema"; import { vueUiIconPack, vueUiSrc, vueUiStyles } from "./turbo"; -import { getRoutes } from "@repo/netlogo-docs/helpers"; - type NuxtBaseConfig = Parameters[0]; +const website = websiteConfigSchema.parse(process.env); + export const nuxtBaseConfig: NuxtBaseConfig = { + extends: ["./layers/mdc", "./layers/primitive-tooltip"], + compatibilityDate: "2025-07-15", - devtools: { enabled: true }, app: { rootId: "__netlogo", }, modules: [ + "@nuxt/devtools", // Nuxt Devtools for enhanced development experience "@nuxtjs/sitemap", // Adds sitemap.xml "@repo/nuxt-content-assets", // Local asset loading based on relative paths in Markdowns "@nuxt/content", // Query, navigation, search, and rendering of Markdown files @@ -27,9 +30,7 @@ export const nuxtBaseConfig: NuxtBaseConfig = { "@nuxtjs/google-fonts", // Google Fonts ], - colorMode: { - preference: "light", - }, + site: { url: website.productWebsite, name: website.productName }, css: ["~/assets/styles/main.scss", "~/assets/styles/tailwind.css", vueUiStyles], @@ -41,6 +42,8 @@ export const nuxtBaseConfig: NuxtBaseConfig = { }, $development: { + devtools: { enabled: true }, + experimental: { payloadExtraction: false, }, @@ -60,7 +63,6 @@ export const nuxtBaseConfig: NuxtBaseConfig = { components: [ { path: vueUiSrc, - global: true, pattern: "**/*.vue", ignore: ["**/examples/*.vue", "**/tests/*.vue"], pathPrefix: false, @@ -68,7 +70,6 @@ export const nuxtBaseConfig: NuxtBaseConfig = { }, { path: "~/components", - global: true, pattern: "**/*.vue", ignore: ["**/examples/*.vue", "**/tests/*.vue"], pathPrefix: false, @@ -76,6 +77,8 @@ export const nuxtBaseConfig: NuxtBaseConfig = { }, ], + ignore: [".build/", ".latest/", ".static/", ".preview/"], + svgo: { customComponent: "SvgImport", }, @@ -96,10 +99,15 @@ export const nuxtBaseConfig: NuxtBaseConfig = { vite: { plugins: [tailwindcss()], optimizeDeps: { - include: ["@repo/vue-ui", "@repo/utils", "@repo/netlogo-docs"], + include: ["@repo/vue-ui", "@repo/utils", "@repo/netlogo-docs", "zod"], + exclude: ["@repo/netlogo-docs/primitive-index", "@repo/utils/lib/server"], }, + build: { sourcemap: true, + rollupOptions: { + external: ["prismjs", "zod/locales", "zod/v4/locales"], + }, }, server: { watch: { usePolling: true }, @@ -111,6 +119,14 @@ export const nuxtBaseConfig: NuxtBaseConfig = { renderer: { anchorLinks: false, }, + build: { + markdown: { + highlight: false, + remarkPlugins: { + "remark-emoji": false, + }, + }, + }, watch: { enabled: false }, }, @@ -121,30 +137,38 @@ export const nuxtBaseConfig: NuxtBaseConfig = { overrideStaticDimensions: false, }, - mdc: { - components: { - prose: false, - }, - }, - ui: { mdc: false, content: false, + colorMode: false, }, ogImage: { defaults: { - extension: "png", + extension: "jpeg", + sharp: { + quality: 70, + }, }, }, - linkChecker: { - excludeLinks: ["/*.pdf"], - skipInspections: ["no-baseless", "no-underscores", "trailing-slash"], - report: { - html: process.env["CHECK"] === "true", - }, - }, + linkChecker: + process.env.NUXT_BUILD_LINK_CHECKER === "0" + ? { enabled: false } + : { + excludeLinks: ["/*.pdf"], + skipInspections: [ + "no-baseless", + "no-underscores", + "trailing-slash", + "absolute-site-urls", + "no-uppercase-chars", + "no-non-ascii-chars", + ], + report: { + html: process.env["CHECK"] === "true", + }, + }, hooks: { async ready() { @@ -154,9 +178,12 @@ export const nuxtBaseConfig: NuxtBaseConfig = { console.info( `[repo] Using primitives.yaml from ${import.meta.resolve("@repo/common-data/datasets/primitives.yaml")}`, ); + }, + }, - const noAutogen = process.env["NO_AUTOGEN"] === "true"; - if (noAutogen === true) return; + runtimeConfig: { + public: { + website, }, }, @@ -165,9 +192,11 @@ export const nuxtBaseConfig: NuxtBaseConfig = { serveStatic: true, prerender: { autoSubfolderIndex: false, - crawlLinks: true, + crawlLinks: process.env.NITRO_PRERENDER_CRAWL_LINKS === "0" ? false : true, concurrency: 1, - routes: await getRoutes(), + routes: await getRoutesSubset( + process.env.NITRO_PRERENDER_ROUTES?.split(",").map((route) => route.trim()) || [], + ), }, }, }; diff --git a/packages/nuxt-core/package.json b/packages/nuxt-core/package.json index 81a75462..1ec08504 100644 --- a/packages/nuxt-core/package.json +++ b/packages/nuxt-core/package.json @@ -5,10 +5,21 @@ "private": true, "exports": { "./nuxt.config.ts": "./nuxt.config.ts", - "./nuxt/tsconfig.json": "./.nuxt/tsconfig.json" + "./nuxt/tsconfig.json": "./.nuxt/tsconfig.json", + "./runtime.config.schema": "./runtime.config.schema.ts", + "./markdown.config": "./markdown.config.ts", + "./content.config": "./content.config.ts", + "./app/**/*": [ + "./app/**/*.ts", + "./app/**/*.vue" + ] }, "scripts": { - "turbo:postinstall": "nuxt prepare" + "create:env": "node -e \"const fs = require('fs'); if (fs.existsSync('.env.example')) { if (!fs.existsSync('.env')) { fs.copyFileSync('.env.example', '.env'); console.log('✓ .env created from .env.example'); } else { console.log('✓ .env already exists'); } } else { console.log('⚠ .env.example not found'); }\"", + "init": "yarn run create:env && nuxt prepare", + "turbo:postinstall": "nuxt prepare", + "lint": "eslint . --max-warnings 0", + "check-types": "nuxi typecheck" }, "dependencies": { "nuxt": "^4.1.2", @@ -16,7 +27,6 @@ "nuxt-link-checker": "4.3.6", "nuxt-og-image": "^5.1.12", "nuxt-svgo": "4.2.6", - "radix-vue": "1.9.17", "reka-ui": "^2.6.0", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.18", @@ -34,7 +44,6 @@ "@repo/eslint-config": "0.0.0", "@repo/markdown": "0.0.0", "@repo/netlogo-docs": "0.0.0", - "@repo/nuxt-content-assets": "1.5.0", "@repo/tailwind-config": "0.0.0", "@repo/template": "0.0.0", "@repo/typescript-config": "0.0.0", @@ -50,4 +59,4 @@ "sass": "^1.93.3", "eslint": "^9.31.0" } -} +} \ No newline at end of file diff --git a/packages/nuxt-core/package.template.json b/packages/nuxt-core/package.template.json index 96966c91..48af73a2 100644 --- a/packages/nuxt-core/package.template.json +++ b/packages/nuxt-core/package.template.json @@ -30,7 +30,6 @@ "nuxt-link-checker": "4.3.6", "nuxt-og-image": "^5.1.12", "nuxt-svgo": "4.2.6", - "radix-vue": "1.9.17", "reka-ui": "^2.6.0", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.18", @@ -46,9 +45,7 @@ "@nuxtjs/sitemap": "7.4.7", "@repo/common-data": "1.0.0", "@repo/eslint-config": "0.0.0", - "@repo/markdown": "0.0.0", "@repo/netlogo-docs": "0.0.0", - "@repo/nuxt-content-assets": "1.5.0", "@repo/tailwind-config": "0.0.0", "@repo/template": "0.0.0", "@repo/typescript-config": "0.0.0", @@ -59,6 +56,7 @@ "devDependencies": { "@nuxt/kit": "^4.2.1", "@repo/nuxt-content-assets": "1.5.0", + "@repo/markdown": "0.0.0", "turbo-meta-utilities": "^1.0.1", "typescript": "^5.4.0", "zod": "^4.1.12", diff --git a/packages/nuxt-core/runtime.config.schema.ts b/packages/nuxt-core/runtime.config.schema.ts new file mode 100644 index 00000000..ec5d2bf2 --- /dev/null +++ b/packages/nuxt-core/runtime.config.schema.ts @@ -0,0 +1,28 @@ +import * as z from "zod"; + +export const websiteConfigSchema = z + .object({ + PRODUCT_NAME: z.string(), + PRODUCT_VERSION: z.string().default("latest"), + PRODUCT_WEBSITE: z.url(), + PRODUCT_DISPLAY_NAME: z.string().optional(), + PRODUCT_BUILD_DATE: z.string().optional(), + PRODUCT_DESCRIPTION: z.string().optional(), + PRODUCT_LONG_DESCRIPTION: z.string().optional(), + PRODUCT_KEYWORDS: z.string().optional(), + NO_AUTOGEN: z.string().optional(), + }) + .transform((data) => ({ + productName: data.PRODUCT_NAME, + productVersion: data.PRODUCT_VERSION, + productWebsite: data.PRODUCT_WEBSITE, + productDisplayName: data.PRODUCT_DISPLAY_NAME ?? data.PRODUCT_NAME, + productFullName: [data.PRODUCT_NAME, data.PRODUCT_DISPLAY_NAME].filter(Boolean).join(" "), + productBuildDate: data.PRODUCT_BUILD_DATE ? data.PRODUCT_BUILD_DATE : new Date().toISOString(), + productDescription: data.PRODUCT_DESCRIPTION ?? "", + productLongDescription: data.PRODUCT_LONG_DESCRIPTION ?? "", + productKeywords: data.PRODUCT_KEYWORDS + ? data.PRODUCT_KEYWORDS.split(",").map((k) => k.trim()) + : [], + docsBuilderNoAutogen: ["true", "1", "yes"].includes(data.NO_AUTOGEN?.toLowerCase() ?? ""), + })); diff --git a/packages/tailwind-config/index.scss b/packages/tailwind-config/index.scss index d054ad94..f939276f 100644 --- a/packages/tailwind-config/index.scss +++ b/packages/tailwind-config/index.scss @@ -1,13 +1,13 @@ -@use './scss/utilities.scss'; +@use "./scss/utilities.scss"; -@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); // Declare the `website` layer higher than all // Tailwind CSS layers to ensure it can override styles. @layer theme, base, components, utilities, website; // Normalizing and variable setting happens at the lowest level -@import './scss/variables.scss'; +@import "./scss/variables.scss"; @layer website { - @import './scss/normalize.scss'; + @import "./scss/normalize.scss"; } diff --git a/packages/tailwind-config/scss/docs-mixins.scss b/packages/tailwind-config/scss/docs-mixins.scss new file mode 100644 index 00000000..329093da --- /dev/null +++ b/packages/tailwind-config/scss/docs-mixins.scss @@ -0,0 +1,347 @@ +@mixin default { + /* Lists */ + ul, + ol, + menu { + list-style: none; + } +} + +@mixin headings { + h1, + h2, + h3 { + --heading-inline-padding: var(--space-xl); + } + + /* Headings */ + h1 { + font-size: var(--h1-size); + font-weight: 700; + letter-spacing: 0.00625rem; + line-height: var(--line-height-heading); + + background-color: var(--primary-heading-background-color); + color: var(--primary-heading-text-color); + + --padding-block: calc(0.8 * var(--space-lg)); + padding: var(--padding-block) var(--heading-inline-padding); + } + + h1, + h1 a { + scroll-margin-top: calc( + var(--scroll-margin) + var(--h1-size) + 2 * var(--padding-block) + ); /* 100% of the h1 height + padding */ + } + + h2 { + font-size: var(--h2-size); + font-weight: 700; + letter-spacing: 0.00625rem; + line-height: var(--line-height-heading); + + background-color: var(--primary-heading-background-color); + color: var(--primary-heading-text-color); + + --padding-block: calc(0.7 * var(--space-lg)); + padding: var(--padding-block) var(--heading-inline-padding); + } + + h2, + h2 a { + scroll-margin-top: calc( + var(--scroll-margin) + var(--h2-size) + 2 * var(--padding-block) + ); /* 100% of the h2 height + padding */ + } + + h3 { + font-size: var(--h3-size); + background-color: var(--secondary-heading-background-color); + color: var(--secondary-heading-text-color); + + --padding-block: calc(0.6 * var(--space-lg)); + padding: var(--padding-block) var(--heading-inline-padding); + font-weight: 700; + } + + h3, + h3 a, + .dict_entry { + /* special case for dictionary entries requires redefining padding */ + --padding-block: calc(0.6 * var(--space-lg)); + scroll-margin-top: calc( + var(--scroll-margin) + var(--h3-size) + 2 * var(--padding-block) + var(--ui-header-height) + ); /* 100% of the h3 height + padding */ + } + + h4 { + color: var(--color-black); + font-size: var(--h4-size); + font-weight: 500; + } + + h4, + h4 a { + scroll-margin-top: calc( + var(--scroll-margin) + var(--h4-size) + ); /* 100% of the h4 height + padding */ + } + + h5 { + font-size: var(--h5-size); + font-weight: 600; + color: #707290; + } + + h5, + h5 a { + scroll-margin-top: calc( + var(--scroll-margin) + var(--h5-size) + ); /* 100% of the h5 height + padding */ + } + + h1 a:focus-visible, + h2 a:focus-visible, + h3 a:focus-visible, + h4 a:focus-visible, + h5 a:focus-visible { + outline: none; + text-decoration: underline; + } + + h1:has(a:hover), + h2:has(a:hover) { + background-color: var(--primary-heading-background-color-hover); + color: var(--primary-heading-text-color); + } + + h3:has(a:hover) { + background-color: var(--secondary-heading-background-color-hover); + color: var(--secondary-heading-text-color); + } + + h4:has(a:hover), + h5:has(a:hover), + h6:has(a:hover) { + /* Placeholder */ + filter: brightness(0.9); /* Darken link on hover */ + } + + h1 code a, + h2 code a { + color: var(--primary-heading-text-color); + } + + h1 code a:hover, + h2 code a:hover { + color: var(--primary-heading-text-color); + text-decoration: underline; + } + + h1 code a:visited, + h2 code a:visited { + color: var(--primary-heading-text-color); + text-decoration: dotted; + } +} + +@mixin toc { + /* Common */ + div.toc ul, + div.toc ul a { + color: var(--color-link); + text-decoration: none; + } + + div.toc ul a:hover, + div.toc ul a:focus { + color: var(--color-link-hover); + border: none; + outline: none; + } + + /* Level 1 List */ + div.toc > ul { + /* level 1 */ + font-weight: 800; /* Bold for top-level links */ + font-size: 1.3rem; /* Larger size for top-level links */ + line-height: 2rem; /* Increased line height for readability */ + list-style: decimal; + } + + /* Second Level List */ + div.toc > ul ul { + max-width: 100ch; /* Limit the width */ + list-style: decimal; + } + + div.toc > ul li ul { + margin-top: var(--space-md); /* Has some space before the sublist */ + } + + div.toc > ul ul { + /* level 2 */ + list-style-type: disc; /* Use disc bullets for sublists */ + margin-bottom: var(--block-bottom); /* Space after the sublist */ + } + + div.toc > ul ul li, + div.toc > ul ul li a { + /* level 2 */ + font-weight: 400; /* Normal weight for sublist items */ + font-size: 1rem; /* Normal size for sublist items */ + } +} + +@mixin prose { + .prose { + margin-block: var(--space-lg); + } + + .contained-prose-lg { + padding: 0 var(--space-lg); + max-width: var(--max-width-ch); + line-height: var(--line-height); + } + + .contained-prose-xl { + padding: 0 var(--space-xl); + max-width: var(--max-width-ch); + line-height: var(--line-height); + } + + .prose > p, + .prose > ul, + .prose > dl, + .prose > ol, + .prose > table, + .prose > img, + .prose > h4, + .prose > h5, + .prose > div, + .prose > video, + .prose > audio, + .prose > figure, + .prose > figure > figcaption, + .prose > button { + padding: 0 var(--space-xl); + max-width: var(--max-width-ch); + } + + // Table Specific Configuration + .prose > table { + margin: 0 var(--space-xl) var(--space-lg) var(--space-xl); + } + + .prose > div.table-container, + .prose > table:not(.table-container table) { + margin-inline: var(--space-xl); + } + + .prose > pre, + .prose > blockquote, + .prose div.blockquote { + margin: var(--space-lg) var(--space-xl); + padding: var(--space-md); + } + + .prose blockquote, + .prose div.blockquote { + max-width: calc(min(var(--max-width-ch), 100%) - 2 * (var(--space-xl))); + } + .prose blockquote p, + .prose div.blockquote p { + width: min(100%, calc(var(--max-width-ch) - 2 * (var(--space-xl)))); + } + + .prose blockquote > *:first-child:last-child { + margin-bottom: 0; + } + + .prose blockquote > pre:has(+ pre) { + margin-bottom: 0; + padding-bottom: 0; + } + + .prose blockquote > pre + pre { + margin-top: 0; + padding-top: 0; + } + + .prose li pre { + padding: 0; + } + + // Images + .prose img, + .prose video, + .prose audio { + max-width: 100%; + height: auto; + display: block; + margin: 1rem auto; + } + + .prose img.screenshot { + display: inherit; + margin: 1em auto; + } + + // Prose lists + .prose ol { + list-style: decimal; + } + + .prose ul, + menu { + list-style: disc; + } + + .prose ul ul { + list-style: circle; + } + + // Variants + + .prose-tight { + --space-xl: 0.5rem; + --space-lg: 0.3rem; + --space-md: 0; + --space-sm: 0; + } + + .prose-medium { + --space-xl: 3rem; + --space-lg: 1.5rem; + --space-md: 0.7rem; + --space-sm: 0.55rem; + + @media (max-width: 1024px) { + --space-xl: 2rem; + --space-lg: 1.5rem; + --space-md: 1rem; + --space-sm: 0.75rem; + } + } + + .prose-text-medium { + --max-width-ch: min(90%, 100ch); + + --h1-size: 2.3rem; + --h2-size: 1.8rem; + --h3-size: 1.55rem; + --h4-size: 1.35rem; + --h5-size: 1.05rem; + --h6-size: 0.9rem; + + @media (max-width: 1024px) { + --max-width-ch: 150ch; + } + } + + .prose-margin-tight { + --block-top: 0.5rem; + --block-bottom: 0.5rem; + } +} diff --git a/packages/tailwind-config/scss/docs-theme.scss b/packages/tailwind-config/scss/docs-theme.scss index 2bb7eb4d..611900d4 100644 --- a/packages/tailwind-config/scss/docs-theme.scss +++ b/packages/tailwind-config/scss/docs-theme.scss @@ -1,517 +1,26 @@ /* ===================================================================== Theming ========================================================================== */ +@use "netlogo-mixins.scss" as netlogo; +@use "docs-mixins.scss" as docs; +@include docs.default; +@include netlogo.default; +@include netlogo.table; +@include docs.headings; +@include docs.toc; +@include docs.prose; + +/* Docs-specific layouting */ div { scroll-margin-top: var(--scroll-margin); } -/* Headings */ -h1 { - font-size: var(--h1-size); - font-weight: 700; - letter-spacing: 0.00625rem; - line-height: var(--line-height-heading); - - background-color: var(--primary-heading-background-color); - color: var(--primary-heading-text-color); - - --padding-block: calc(0.8 * var(--space-lg)); - padding: var(--padding-block) var(--space-xl); -} - -h1, -h1 a { - scroll-margin-top: calc( - var(--scroll-margin) + var(--h1-size) + 2 * var(--padding-block) - ); /* 100% of the h1 height + padding */ -} - -h2 { - font-size: var(--h2-size); - font-weight: 700; - letter-spacing: 0.00625rem; - line-height: var(--line-height-heading); - - background-color: var(--primary-heading-background-color); - color: var(--primary-heading-text-color); - - --padding-block: calc(0.7 * var(--space-lg)); - padding: var(--padding-block) var(--space-xl); -} - -h2, -h2 a { - scroll-margin-top: calc( - var(--scroll-margin) + var(--h2-size) + 2 * var(--padding-block) - ); /* 100% of the h2 height + padding */ -} - -h3 { - font-size: var(--h3-size); - background-color: var(--secondary-heading-background-color); - color: var(--secondary-heading-text-color); - - --padding-block: calc(0.6 * var(--space-lg)); - padding: var(--padding-block) var(--space-xl); - font-weight: 700; -} - -h3, -h3 a, -.dict_entry { - /* special case for dictionary entries requires redefining padding */ - --padding-block: calc(0.6 * var(--space-lg)); - scroll-margin-top: calc( - var(--scroll-margin) + var(--h3-size) + 2 * var(--padding-block) + var(--ui-header-height) - ); /* 100% of the h3 height + padding */ -} - -h4 { - color: var(--color-black); - font-size: var(--h4-size); - font-weight: 500; -} - -h4, -h4 a { - scroll-margin-top: calc( - var(--scroll-margin) + var(--h4-size) - ); /* 100% of the h4 height + padding */ -} - -h5 { - font-size: var(--h5-size); - font-weight: 600; - color: #707290; -} - -h5, -h5 a { - scroll-margin-top: calc( - var(--scroll-margin) + var(--h5-size) - ); /* 100% of the h5 height + padding */ -} - -h1 a:focus-visible, -h2 a:focus-visible, -h3 a:focus-visible, -h4 a:focus-visible, -h5 a:focus-visible { - outline: none; - text-decoration: underline; -} - -h1:has(a:hover), -h2:has(a:hover) { - background-color: var(--primary-heading-background-color-hover); - color: var(--primary-heading-text-color); -} - -h3:has(a:hover) { - background-color: var(--secondary-heading-background-color-hover); - color: var(--secondary-heading-text-color); -} - -h4:has(a:hover), -h5:has(a:hover), -h6:has(a:hover) { - /* Placeholder */ - filter: brightness(0.9); /* Darken link on hover */ -} - -h1 code a, -h2 code a { - color: var(--primary-heading-text-color); -} - -h1 code a:hover, -h2 code a:hover { - color: var(--primary-heading-text-color); - text-decoration: underline; -} - -h1 code a:visited, -h2 code a:visited { - color: var(--primary-heading-text-color); - text-decoration: dotted; -} - -/* Top-level Indentation */ -.prose { - margin-block: var(--space-lg); -} - body { max-width: 100vw; overflow-x: hidden; } -.contained-prose-lg { - padding: 0 var(--space-lg); - max-width: var(--max-width-ch); - line-height: var(--line-height); -} - -.contained-prose-xl { - padding: 0 var(--space-xl); - max-width: var(--max-width-ch); - line-height: var(--line-height); -} - -.prose > p, -.prose > ul, -.prose > dl, -.prose > ol, -.prose > table, -.prose > img, -.prose > h4, -.prose > h5, -.prose > div, -.prose > video, -.prose > audio, -.prose > figure, -.prose > figure > figcaption, -.prose > button { - padding: 0 var(--space-xl); - max-width: var(--max-width-ch); -} - -.prose > pre, -.prose > blockquote, -.prose div.blockquote { - margin: var(--space-lg) var(--space-xl); - padding: var(--space-md); -} - -.prose blockquote, -.prose div.blockquote { - max-width: calc(min(var(--max-width-ch), 100%) - 2 * (var(--space-xl))); -} -.prose blockquote p, -.prose div.blockquote p { - width: min(100%, calc(var(--max-width-ch) - 2 * (var(--space-xl)))); -} - -.prose blockquote > *:first-child:last-child { - margin-bottom: 0; -} - -.prose li pre { - padding: 0; -} - -p.question { - font-style: italic; - font-weight: bold; - margin-left: var(--space-sm) !important; - margin-bottom: var(--space-sm); -} - -/* Pre/Code */ -pre { - overflow: auto; - padding: var(--space-sm) var(--space-md); - font-size: 0.9rem; - line-height: var(--line-height-code); - background-color: var(--color-background-overlay); -} - -p code, -li code, -table code, -a.code { - padding: calc(0.3 * var(--space-xxs)) var(--space-xxs); - font-size: 0.9rem; - border-radius: var(--radius-md); - line-height: var(--line-height-code); - background-color: var(--color-background-overlay); - font-family: var(--font-mono); -} - -li pre { - margin-top: var(--space-md); -} - -/* HR */ -hr { - color: var(--netlogo-black); - height: 0.0625rem; /* 1px */ - border: none; - background-color: var(--color-grey-accent); -} - -/* Images */ -.prose img, -.prose video, -.prose audio { - max-width: 100%; - height: auto; - display: block; - margin: 1rem auto; -} - -.prose img.screenshot { - display: inherit; - margin: 1em auto; -} - -/* Lists when made of links only */ -ul.no-bullets { - list-style-type: none; - list-style-position: outside; -} - -/* Table of Contents - ========================================================================== */ - -/* Common */ -div.toc ul, -div.toc ul a { - color: var(--color-link); - text-decoration: none; -} - -div.toc ul a:hover, -div.toc ul a:focus { - color: var(--color-link-hover); - border: none; - outline: none; -} - -/* Level 1 List */ -div.toc > ul { - /* level 1 */ - font-weight: 800; /* Bold for top-level links */ - font-size: 1.3rem; /* Larger size for top-level links */ - line-height: 2rem; /* Increased line height for readability */ - list-style: decimal; -} - -/* Second Level List */ -div.toc > ul ul { - max-width: 100ch; /* Limit the width */ - list-style: decimal; -} - -div.toc > ul li ul { - margin-top: var(--space-md); /* Has some space before the sublist */ -} - -div.toc > ul ul { - /* level 2 */ - list-style-type: disc; /* Use disc bullets for sublists */ - margin-bottom: var(--block-bottom); /* Space after the sublist */ -} - -div.toc > ul ul li, -div.toc > ul ul li a { - /* level 2 */ - font-weight: 400; /* Normal weight for sublist items */ - font-size: 1rem; /* Normal size for sublist items */ -} - -/* Blockquotes */ -blockquote, -div.blockquote, -.highlight { - border-left: 0.4rem solid var(--color-primary); - background: var(--color-background-overlay); - border-radius: var(--radius-md); - padding: var(--space-sm) var(--space-md); - margin: var(--space-md) 0; - box-shadow: var(--shadow-md); - width: min(100%, min(max(85vw, 30ch), var(--max-width-ch))); - font-family: var(--font-sans); - font-size: 1rem; - color: var(--color-black); -} - -blockquote p:last-child, -div.blockquote p:last-child, -.highlight p:last-child { - margin-bottom: 0; -} - -.highlight-warning { - border-left-color: var(--color-warning); -} - -.highlight-success { - border-left-color: var(--color-success); -} - -.highlight-error { - border-left-color: var(--color-error); -} - -/* ------------------------------------------------------------------ - NetLogo “command list” table ------------------------------------------------------------------- */ -table { - width: 100%; - overflow: auto; - - border-collapse: collapse; - /* 1 px outline that matches the header colour */ - font-family: var(--font-sans); - line-height: var(--line-height); - margin: 0 var(--space-xl) var(--space-lg) var(--space-xl); - - --table-background: rgb(30, 30, 30); - --table-border-color: rgb(40, 50, 80); -} - -/* Workaround because max-width does not work on table */ -div.table-container table { - margin: 0; - padding: 0; -} - -div.table-container, -table:not(.table-container table) { - display: block; - padding: 0; - margin-inline: var(--space-xl); - margin-bottom: var(--block-bottom); - max-width: min(calc(var(--max-width-ch) - 2 * (var(--space-xl))), 100%) !important; - overflow-x: auto; /* Allow horizontal scrolling */ -} - -@media (max-width: 1214px) { - div.table-container, - table:not(.table-container table) { - max-width: min( - 100vw - 2 * (var(--space-xl)), - calc(var(--max-width-ch) - 2 * (var(--space-md))) - ) !important; - } -} - -/* --- HEADER ROW -------------------------------------------------- */ -table thead { - width: 100%; -} -table thead th, -table th { - background: var(--table-background); - color: var(--color-white); - font-weight: 600; - text-align: left; - padding: var(--space-xs) var(--space-md); - border-bottom: 2px solid var(--table-border-color); -} - -/* --- BODY CELLS --------------------------------------------------- */ -table td { - padding: var(--space-md) var(--space-md); - vertical-align: top; -} - -table td, -table th { - border-right: 1px solid var(--table-border-color); -} - -table td:first-child, -table th:first-child { - border-left: 1px solid var(--table-border-color); /* Add left border to first column */ -} - -table th { - border-top: 1px solid var(--table-border-color); /* Add top border to header */ -} - -table tr td { - border-top: 1px solid var(--table-border-color); /* Add bottom border to last row */ - border-bottom: 1px solid var(--table-border-color); /* Add bottom border to last row */ -} - -/* command-name column */ -table td:first-child { - width: 11rem; /* fixed width for alignment */ - font-weight: 600; - white-space: nowrap; -} - -/* description column links */ -table a { - color: var(--color-link); - text-decoration: none; -} -table a:hover { - text-decoration: underline; -} - -/* zebra-striping */ -table tbody tr:nth-child(even) td { - background: #f5f6f8; -} -table tbody tr:nth-child(odd) td { - background: #ffffff; -} - -table thead th code { - background-color: #161616; -} - -/* optional: subtle hover highlight for the entire row */ -@media (hover: hover) { - table tbody tr:hover td { - background: var(--color-background-overlay); - } -} - -/* External links */ -span.external-link { - font-size: 0.7rem; - color: var(--color-link); - text-decoration: none; - vertical-align: top; - font-weight: bold; -} - -// Links - -.landing a, -a:has(code):not(h3 > a), -code a, -a.code, -a.modern-anchor, -a:not(h1 a):not(h2 a):not(h3 a):not(h4 a):not(h5 a):not(h6 a) { - text-decoration: none; - color: var(--color-link); - font-weight: 600; -} - -.landing a:hover, -a:has(code:hover), -code a:hover, -a.code:hover, -a.modern-anchor:hover, -a:not(h1 a):not(h2 a):not(h3 a):not(h4 a):not(h5 a):not(h6 a):hover { - color: var(--color-link-hover); -} - -ul, -ol, -menu { - list-style: none; -} - -.prose ol { - list-style: decimal; -} - -.prose ul, -menu { - list-style: disc; -} - -.prose ul ul { - list-style: circle; -} - // Special cases for dictionary div.dict_entry > h3 { display: flex; @@ -601,47 +110,6 @@ span.prim_example span.since p { display: inline; } -.prose-tight { - --space-xl: 0.5rem; - --space-lg: 0.3rem; - --space-md: 0; - --space-sm: 0; -} - -.prose-medium { - --space-xl: 3rem; - --space-lg: 1.5rem; - --space-md: 0.7rem; - --space-sm: 0.55rem; - - @media (max-width: 1024px) { - --space-xl: 2rem; - --space-lg: 1.5rem; - --space-md: 1rem; - --space-sm: 0.75rem; - } -} - -.prose-text-medium { - --max-width-ch: min(90%, 100ch); - - --h1-size: 2.3rem; - --h2-size: 1.8rem; - --h3-size: 1.55rem; - --h4-size: 1.35rem; - --h5-size: 1.05rem; - --h6-size: 0.9rem; - - @media (max-width: 1024px) { - --max-width-ch: 150ch; - } -} - -.prose-margin-tight { - --block-top: 0.5rem; - --block-bottom: 0.5rem; -} - @media (max-width: 1214px) { div.dict_entry > :not(:first-child) { margin-inline: var(--space-lg); diff --git a/packages/tailwind-config/scss/netlogo-mixins.scss b/packages/tailwind-config/scss/netlogo-mixins.scss new file mode 100644 index 00000000..ce083b53 --- /dev/null +++ b/packages/tailwind-config/scss/netlogo-mixins.scss @@ -0,0 +1,254 @@ +@mixin default { + /* HR */ + hr { + color: var(--netlogo-black); + height: 0.0625rem; /* 1px */ + border: none; + background-color: var(--color-grey-accent); + } + + /* Links */ + .landing a, + a:has(code):not(h3 > a), + code a, + a.code, + a.modern-anchor, + a:not(h1 a):not(h2 a):not(h3 a):not(h4 a):not(h5 a):not(h6 a) { + text-decoration: none; + color: var(--color-link); + font-weight: 600; + } + + .landing a:hover, + a:has(code:hover), + code a:hover, + a.code:hover, + a.modern-anchor:hover, + a:not(h1 a):not(h2 a):not(h3 a):not(h4 a):not(h5 a):not(h6 a):hover { + color: var(--color-link-hover); + } + + /* External links */ + span.external-link { + font-size: 0.7rem; + color: var(--color-link); + text-decoration: none; + vertical-align: top; + font-weight: bold; + } + + /* Pre/Code */ + p ~ pre { + margin-top: var(--space-md); + } + + pre { + overflow: auto; + padding-block: var(--space-sm); + font-size: 0.9rem; + line-height: var(--line-height-code); + background-color: var(--color-background-overlay); + } + + p code, + li code, + table code, + a.code { + padding: calc(0.3 * var(--space-xxs)) var(--space-xxs); + font-size: 0.9rem; + border-radius: var(--radius-md); + line-height: var(--line-height-code); + background-color: var(--color-background-overlay); + font-family: var(--font-mono); + } + + li pre { + margin-top: var(--space-md); + } + + pre { + overflow: auto; + padding: var(--space-sm) var(--space-md); + } + + a.code, + li code, + p code, + pre, + table code { + background-color: var(--color-background-overlay); + font-size: 0.9rem; + line-height: var(--line-height-code); + } + + a.code, + li code, + p code, + table code { + border-radius: var(--radius-md); + font-family: var(--font-mono); + padding: calc(var(--space-xxs) * 0.3) var(--space-xxs); + } + + /* Question */ + p.question { + font-style: italic; + font-weight: bold; + margin-left: var(--space-sm) !important; + margin-bottom: var(--space-sm); + } + + /* Blockquotes */ + blockquote, + div.blockquote, + .highlight { + border-left: 0.4rem solid var(--color-primary); + background: var(--color-background-overlay); + border-radius: var(--radius-md); + padding: var(--space-sm) var(--space-md); + margin: var(--space-md) 0; + box-shadow: var(--shadow-md); + width: min(100%, min(max(85vw, 30ch), var(--max-width-ch))); + font-family: var(--font-sans); + font-size: 1rem; + color: var(--color-black); + } + + blockquote p:last-child, + div.blockquote p:last-child, + .highlight p:last-child { + margin-bottom: 0; + } + + .highlight-warning { + border-left-color: var(--color-warning); + } + + .highlight-success { + border-left-color: var(--color-success); + } + + .highlight-error { + border-left-color: var(--color-error); + } + + // Lists + ul.no-bullets { + list-style-type: none; + list-style-position: outside; + } +} + +@mixin table { + table { + width: 100%; + overflow: auto; + + border-collapse: collapse; + /* 1 px outline that matches the header colour */ + font-family: var(--font-sans); + line-height: var(--line-height); + + --table-background: rgb(30, 30, 30); + --table-border-color: rgb(40, 50, 80); + } + + /* Workaround because max-width does not work on table */ + div.table-container table { + margin: 0; + padding: 0; + } + + div.table-container, + table:not(.table-container table) { + display: block; + padding: 0; + margin-bottom: var(--block-bottom); + max-width: min(calc(var(--max-width-ch) - 2 * (var(--space-xl))), 100%) !important; + overflow-x: auto; /* Allow horizontal scrolling */ + } + + @media (max-width: 1214px) { + div.table-container, + table:not(.table-container table) { + max-width: min( + 100vw - 2 * (var(--space-xl)), + calc(var(--max-width-ch) - 2 * (var(--space-md))) + ) !important; + } + } + + /* --- HEADER ROW -------------------------------------------------- */ + table thead { + width: 100%; + } + table thead th, + table th { + background: var(--table-background); + color: var(--color-white); + font-weight: 600; + text-align: left; + padding: var(--space-xs) var(--space-md); + border-bottom: 2px solid var(--table-border-color); + } + + /* --- BODY CELLS --------------------------------------------------- */ + table td { + padding: var(--space-md) var(--space-md); + vertical-align: top; + } + + table td, + table th { + border-right: 1px solid var(--table-border-color); + } + + table td:first-child, + table th:first-child { + border-left: 1px solid var(--table-border-color); /* Add left border to first column */ + } + + table th { + border-top: 1px solid var(--table-border-color); /* Add top border to header */ + } + + table tr td { + border-top: 1px solid var(--table-border-color); /* Add bottom border to last row */ + border-bottom: 1px solid var(--table-border-color); /* Add bottom border to last row */ + } + + /* command-name column */ + table td:first-child { + width: 11rem; /* fixed width for alignment */ + font-weight: 600; + white-space: nowrap; + } + + /* description column links */ + table a { + color: var(--color-link); + text-decoration: none; + } + table a:hover { + text-decoration: underline; + } + + /* zebra-striping */ + table tbody tr:nth-child(even) td { + background: #f5f6f8; + } + table tbody tr:nth-child(odd) td { + background: #ffffff; + } + + table thead th code { + background-color: #161616; + } + + /* optional: subtle hover highlight for the entire row */ + @media (hover: hover) { + table tbody tr:hover td { + background: var(--color-background-overlay); + } + } +} diff --git a/packages/tailwind-config/scss/netlogo-theme.scss b/packages/tailwind-config/scss/netlogo-theme.scss new file mode 100644 index 00000000..d2ca5428 --- /dev/null +++ b/packages/tailwind-config/scss/netlogo-theme.scss @@ -0,0 +1,14 @@ +@use "netlogo-mixins.scss" as netlogo; +@use "docs-mixins.scss" as docs; + +@include netlogo.default; +@include netlogo.table; + +.docs { + & { + @include docs.default; + @include docs.headings; + @include docs.toc; + @include docs.prose; + } +} diff --git a/packages/template/src/api.schemas.ts b/packages/template/src/api.schemas.ts index 1bc033ac..7c0044cc 100644 --- a/packages/template/src/api.schemas.ts +++ b/packages/template/src/api.schemas.ts @@ -1,5 +1,4 @@ -import { z } from 'zod'; - +import * as z from "zod"; export const PageResultSchema = z.object({ /** Base file name */ baseName: z.string(), diff --git a/packages/template/src/schemas.ts b/packages/template/src/schemas.ts index 838d9920..34d06d2d 100644 --- a/packages/template/src/schemas.ts +++ b/packages/template/src/schemas.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import * as z from "zod"; /** * Schema for individual build variables declarations, diff --git a/packages/template/tests/api.schemas.zod.test.ts b/packages/template/tests/api.schemas.zod.test.ts index a98a39ed..c0afd0f7 100644 --- a/packages/template/tests/api.schemas.zod.test.ts +++ b/packages/template/tests/api.schemas.zod.test.ts @@ -1,7 +1,6 @@ -import { describe, expect, it } from '@jest/globals'; -import { z } from 'zod'; - -describe('api.schemas Zod Validation', () => { +import { describe, expect, it } from "@jest/globals"; +import * as z from "zod"; +describe("api.schemas Zod Validation", () => { // We need to access the schemas directly to test validation // Since they're not exported, we'll create them locally for testing const PageResultSchema = z.object({ @@ -32,17 +31,17 @@ describe('api.schemas Zod Validation', () => { .optional(), }); - describe('PageResultSchema validation', () => { - it('should validate a valid page result', () => { + describe("PageResultSchema validation", () => { + it("should validate a valid page result", () => { const validPageResult = { - baseName: 'test-page', - sourcePath: 'test.md', - outputPath: 'test.html', - language: 'en', - title: 'Test Page', - description: 'A test page', + baseName: "test-page", + sourcePath: "test.md", + outputPath: "test.html", + language: "en", + title: "Test Page", + description: "A test page", success: true, - metadataPath: 'test.json', + metadataPath: "test.json", }; expect(() => PageResultSchema.parse(validPageResult)).not.toThrow(); @@ -50,47 +49,47 @@ describe('api.schemas Zod Validation', () => { expect(parsed).toEqual(validPageResult); }); - it('should validate minimal page result with required fields only', () => { + it("should validate minimal page result with required fields only", () => { const minimalPageResult = { - baseName: 'minimal', - sourcePath: 'minimal.md', + baseName: "minimal", + sourcePath: "minimal.md", success: true, }; expect(() => PageResultSchema.parse(minimalPageResult)).not.toThrow(); }); - it('should reject page result missing required baseName', () => { + it("should reject page result missing required baseName", () => { const invalidPageResult = { - sourcePath: 'test.md', + sourcePath: "test.md", success: true, }; expect(() => PageResultSchema.parse(invalidPageResult)).toThrow(); }); - it('should reject page result missing required sourcePath', () => { + it("should reject page result missing required sourcePath", () => { const invalidPageResult = { - baseName: 'test', + baseName: "test", success: true, }; expect(() => PageResultSchema.parse(invalidPageResult)).toThrow(); }); - it('should reject page result missing required success', () => { + it("should reject page result missing required success", () => { const invalidPageResult = { - baseName: 'test', - sourcePath: 'test.md', + baseName: "test", + sourcePath: "test.md", }; expect(() => PageResultSchema.parse(invalidPageResult)).toThrow(); }); - it('should reject page result with wrong types', () => { + it("should reject page result with wrong types", () => { const invalidPageResult = { baseName: 123, // should be string - sourcePath: 'test.md', + sourcePath: "test.md", success: true, }; @@ -98,13 +97,13 @@ describe('api.schemas Zod Validation', () => { }); }); - describe('BuildResultSchema validation', () => { - it('should validate a valid build result', () => { + describe("BuildResultSchema validation", () => { + it("should validate a valid build result", () => { const validBuildResult = { pages: { - 'test.md': { - baseName: 'test', - sourcePath: 'test.md', + "test.md": { + baseName: "test", + sourcePath: "test.md", success: true, }, }, @@ -123,7 +122,7 @@ describe('api.schemas Zod Validation', () => { expect(() => BuildResultSchema.parse(validBuildResult)).not.toThrow(); }); - it('should validate build result without stats', () => { + it("should validate build result without stats", () => { const buildResult = { pages: {}, totalPages: 0, @@ -136,12 +135,12 @@ describe('api.schemas Zod Validation', () => { expect(() => BuildResultSchema.parse(buildResult)).not.toThrow(); }); - it('should reject build result with invalid pages', () => { + it("should reject build result with invalid pages", () => { const invalidBuildResult = { pages: { - 'test.md': { + "test.md": { // Missing required fields - sourcePath: 'test.md', + sourcePath: "test.md", }, }, totalPages: 1, @@ -154,10 +153,10 @@ describe('api.schemas Zod Validation', () => { expect(() => BuildResultSchema.parse(invalidBuildResult)).toThrow(); }); - it('should reject build result with wrong types', () => { + it("should reject build result with wrong types", () => { const invalidBuildResult = { pages: {}, - totalPages: '1', // should be number + totalPages: "1", // should be number successfulPages: 1, failedPages: 0, success: true, @@ -167,7 +166,7 @@ describe('api.schemas Zod Validation', () => { expect(() => BuildResultSchema.parse(invalidBuildResult)).toThrow(); }); - it('should reject build result with invalid stats', () => { + it("should reject build result with invalid stats", () => { const invalidBuildResult = { pages: {}, totalPages: 0, @@ -176,7 +175,7 @@ describe('api.schemas Zod Validation', () => { success: true, errors: [], stats: { - buildTimeMs: '1000', // should be number + buildTimeMs: "1000", // should be number startTime: new Date(), endTime: new Date(), }, @@ -186,20 +185,20 @@ describe('api.schemas Zod Validation', () => { }); }); - describe('Type inference', () => { - it('should infer correct types from schemas', () => { + describe("Type inference", () => { + it("should infer correct types from schemas", () => { type PageResult = z.infer; type BuildResult = z.infer; // This test ensures the type exports work correctly const pageResult: PageResult = { - baseName: 'test', - sourcePath: 'test.md', + baseName: "test", + sourcePath: "test.md", success: true, }; const buildResult: BuildResult = { - pages: { 'test.md': pageResult }, + pages: { "test.md": pageResult }, totalPages: 1, successfulPages: 1, failedPages: 0, @@ -207,7 +206,7 @@ describe('api.schemas Zod Validation', () => { errors: [], }; - expect(pageResult.baseName).toBe('test'); + expect(pageResult.baseName).toBe("test"); expect(buildResult.totalPages).toBe(1); }); }); diff --git a/packages/template/turbo.json b/packages/template/turbo.json index 61304a76..599d67b4 100644 --- a/packages/template/turbo.json +++ b/packages/template/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://turborepo.com/schema.json", + "$schema": "https://v2-8-20.turborepo.dev/schema.json", "extends": [ "//" ], diff --git a/packages/utils/package.json b/packages/utils/package.json index ad946a12..0840ea35 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -31,13 +31,9 @@ }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.45.0", - "class-variance-authority": "^0.7.1", "eslint": "^9.31.0", "glob": "^13.0.0", "tsc-alias": "^1.8.16", "vitest": "^4.0.16" - }, - "peerDependencies": { - "class-variance-authority": "^0.7.1" } } diff --git a/packages/vue-ui/README.md b/packages/vue-ui/README.md index e5755d63..a2ad0d2c 100644 --- a/packages/vue-ui/README.md +++ b/packages/vue-ui/README.md @@ -54,7 +54,6 @@ export default defineNuxtConfig({ { path: vueUiSrc, // Recommended options - global: true, pathPrefix: false, pattern: '**/*.vue', ignore: ['**/examples/*.vue', '**/tests/*.vue'], diff --git a/packages/vue-ui/package.json b/packages/vue-ui/package.json index 8213981b..1d95b230 100644 --- a/packages/vue-ui/package.json +++ b/packages/vue-ui/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "type": "module", "engines": { - "node": "^22.0.0" + "node": "^24.0.0" }, "sideEffects": [ "**/*.css", @@ -49,7 +49,6 @@ "setup": "bash scripts/setup.sh" }, "peerDependencies": { - "radix-vue": "1.9.17", "reka-ui": "^2.6.0", "vue": "^3.5.24" }, @@ -60,10 +59,9 @@ "@nuxt/ui": "^4.2.1", "@repo/eslint-config": "0.0.0", "@repo/utils": "0.0.0", - "@types/node": "^22.0.0", + "@types/node": "^24.0.0", "@vue/eslint-config-prettier": "^10.2.0", "@vueuse/core": "^14.1.0", - "class-variance-authority": "^0.7.1", "eslint": "^9.31.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-vue": "^10.5.1", @@ -71,7 +69,6 @@ "nuxt-og-image": "^5.1.12", "nuxt-site-config": "^3.2.11", "prettier": "^3.6.2", - "reka-ui": "^2.6.0", "sass": "^1.93.3", "typescript": "^5.4.0", "vue-eslint-parser": "^10.2.0", @@ -82,7 +79,6 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-vue-next": "^0.544.0", - "radix-vue": "1.9.17", "motion-v": "^1.7.4", "reka-ui": "^2.6.0", "tailwind-merge": "^3.3.1", diff --git a/packages/vue-ui/src/components/html/Anchor.vue b/packages/vue-ui/src/components/html/Anchor.vue index 1e6d9e96..4fc63575 100644 --- a/packages/vue-ui/src/components/html/Anchor.vue +++ b/packages/vue-ui/src/components/html/Anchor.vue @@ -1,6 +1,6 @@