diff --git a/examples/too_many_cooks/readme.md b/examples/too_many_cooks/readme.md index 2bd5c21..5591275 100644 --- a/examples/too_many_cooks/readme.md +++ b/examples/too_many_cooks/readme.md @@ -9,7 +9,7 @@ Multi-agent coordination MCP server - enables multiple AI agents to safely edit - **Messaging**: Inter-agent communication with broadcast support - **Plan Visibility**: Share goals and current tasks across agents - **Real-time Status**: System overview of all agents, locks, and plans -- **Written in Dart**: Made with [dart_node](https://dartnode.dev) +- **Written in Dart**: Made with [dart_node](https://www.dartnode.org) ## Installation diff --git a/website/eleventy.config.js b/website/eleventy.config.js index 84d2f35..edf96dc 100644 --- a/website/eleventy.config.js +++ b/website/eleventy.config.js @@ -1,53 +1,97 @@ -import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"; -import pluginRss from "@11ty/eleventy-plugin-rss"; -import eleventyNavigationPlugin from "@11ty/eleventy-navigation"; -import markdownIt from "markdown-it"; -import markdownItAnchor from "markdown-it-anchor"; +import { readFileSync } from "fs"; import { execSync } from "child_process"; -import { dirname, resolve } from "path"; +import { dirname, resolve, join } from "path"; import { fileURLToPath } from "url"; +// Import plugin submodules via file paths (workaround for #9: +// virtual templates override local layouts, so we register non-layout +// virtual templates separately. Package only exports "." so we use +// direct file paths.) +import { registerFilters } from "./node_modules/eleventy-plugin-techdoc/lib/filters/index.js"; +import { registerCollections } from "./node_modules/eleventy-plugin-techdoc/lib/plugins/collections.js"; +import { registerShortcodes } from "./node_modules/eleventy-plugin-techdoc/lib/shortcodes/index.js"; +import { configureMarkdown } from "./node_modules/eleventy-plugin-techdoc/lib/plugins/markdown.js"; +import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"; +import rss from "@11ty/eleventy-plugin-rss"; +import navigation from "@11ty/eleventy-navigation"; + const __dirname = dirname(fileURLToPath(import.meta.url)); const packagesDir = resolve(__dirname, "..", "packages"); -const supportedLanguages = ['en', 'zh']; -const defaultLanguage = 'en'; +const techdocOptions = { + site: { + name: "dart_node", + title: "dart_node - Full-Stack Dart for the JavaScript Ecosystem", + url: "https://dartnode.org", + description: "Write React, React Native, and Express apps entirely in Dart. One language for frontend, backend, and mobile.", + author: "dart_node team", + themeColor: "#0E7C6B", + stylesheet: "/assets/css/styles.css", + twitterSite: "@dart_node", + twitterCreator: "@dart_node", + ogImage: "/assets/images/og-image.png", + ogImageWidth: "1200", + ogImageHeight: "630", + organization: { + name: "dart_node", + logo: "/assets/images/og-image.png", + sameAs: [ + "https://github.com/melbournedeveloper/dart_node", + "https://twitter.com/dart_node", + "https://pub.dev/publishers/christianfindlay.com/packages" + ] + } + }, + features: { + blog: true, + docs: true, + darkMode: true, + i18n: true, + }, + i18n: { + defaultLanguage: "en", + languages: ["en", "zh"], + }, +}; export default function(eleventyConfig) { - // Don't use .gitignore to ignore files (we want to process generated docs) eleventyConfig.setUseGitIgnore(false); - // Configure markdown-it with anchor plugin for header IDs - const mdOptions = { - html: true, - breaks: false, - linkify: true - }; + // === techdoc plugin features (without layout virtual templates) === + // We use the plugin's filters, collections, shortcodes, markdown config, + // and bundled plugins, but NOT its layout virtual templates because + // dart_node's layouts are superior (see GitHub issue #9). - const mdAnchorOptions = { - permalink: markdownItAnchor.permalink.headerLink(), - slugify: (s) => s.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, ''), - level: [1, 2, 3, 4] - }; + // Global data (same as plugin sets) + eleventyConfig.addGlobalData("techdocOptions", techdocOptions); + eleventyConfig.addGlobalData("supportedLanguages", techdocOptions.i18n.languages); + eleventyConfig.addGlobalData("defaultLanguage", techdocOptions.i18n.defaultLanguage); - const md = markdownIt(mdOptions).use(markdownItAnchor, mdAnchorOptions); - eleventyConfig.setLibrary("md", md); + // Plugin submodules + configureMarkdown(eleventyConfig); + registerFilters(eleventyConfig, techdocOptions); + registerCollections(eleventyConfig, techdocOptions); + registerShortcodes(eleventyConfig); - // Plugins + // Bundled plugins eleventyConfig.addPlugin(syntaxHighlight); - eleventyConfig.addPlugin(pluginRss); - eleventyConfig.addPlugin(eleventyNavigationPlugin); + eleventyConfig.addPlugin(rss); + eleventyConfig.addPlugin(navigation); + + // Plugin structural CSS (no colors - site provides visual styling) + const techdocAssetsDir = join(__dirname, "node_modules", "eleventy-plugin-techdoc", "assets"); + eleventyConfig.addPassthroughCopy({ [techdocAssetsDir]: "techdoc" }); - // Passthrough copy for assets + // Register only NON-LAYOUT virtual templates from the plugin + // (feed, sitemap, robots.txt, llms.txt, blog scaffold pages) + // Layouts come from our local src/_includes/layouts/ which are superior. + registerSeoVirtualTemplates(eleventyConfig); + + // === Site-specific config === eleventyConfig.addPassthroughCopy("src/assets"); eleventyConfig.addPassthroughCopy("src/api"); - eleventyConfig.addPassthroughCopy("src/robots.txt"); - eleventyConfig.addPassthroughCopy("src/llms.txt"); - - // Watch targets eleventyConfig.addWatchTarget("src/assets/"); - // Watch READMEs and copy when they change eleventyConfig.addWatchTarget(packagesDir); eleventyConfig.on("eleventy.beforeWatch", (changedFiles) => { if (changedFiles.some(f => f.endsWith("README.md"))) { @@ -55,174 +99,6 @@ export default function(eleventyConfig) { } }); - // Collections - // English posts only (from src/blog/) - eleventyConfig.addCollection("posts", function(collectionApi) { - return collectionApi.getFilteredByGlob("src/blog/*.md").sort((a, b) => { - return b.date - a.date; - }); - }); - - // Chinese posts only (from src/zh/blog/) - eleventyConfig.addCollection("zhPosts", function(collectionApi) { - return collectionApi.getFilteredByGlob("src/zh/blog/*.md").sort((a, b) => { - return b.date - a.date; - }); - }); - - eleventyConfig.addCollection("docs", function(collectionApi) { - return collectionApi.getFilteredByGlob("src/docs/**/*.md"); - }); - - // Tag collection - get all unique tags from blog posts - eleventyConfig.addCollection("tagList", function(collectionApi) { - const tagSet = new Set(); - collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => { - (post.data.tags || []).forEach(tag => { - tag !== 'post' && tag !== 'posts' && tagSet.add(tag); - }); - }); - return [...tagSet].sort(); - }); - - // Category collection - get all unique categories from blog posts - eleventyConfig.addCollection("categoryList", function(collectionApi) { - const categorySet = new Set(); - collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => { - post.data.category && categorySet.add(post.data.category); - }); - return [...categorySet].sort(); - }); - - // Posts by tag - creates a collection for each tag - eleventyConfig.addCollection("postsByTag", function(collectionApi) { - const postsByTag = {}; - collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => { - (post.data.tags || []).forEach(tag => { - tag !== 'post' && tag !== 'posts' && (postsByTag[tag] = postsByTag[tag] || []).push(post); - }); - }); - Object.keys(postsByTag).forEach(tag => { - postsByTag[tag].sort((a, b) => b.date - a.date); - }); - return postsByTag; - }); - - // Posts by category - creates a collection for each category - eleventyConfig.addCollection("postsByCategory", function(collectionApi) { - const postsByCategory = {}; - collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => { - post.data.category && (postsByCategory[post.data.category] = postsByCategory[post.data.category] || []).push(post); - }); - Object.keys(postsByCategory).forEach(cat => { - postsByCategory[cat].sort((a, b) => b.date - a.date); - }); - return postsByCategory; - }); - - // Chinese tag collection - eleventyConfig.addCollection("zhTagList", function(collectionApi) { - const tagSet = new Set(); - collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => { - (post.data.tags || []).forEach(tag => { - tag !== 'post' && tag !== 'posts' && tagSet.add(tag); - }); - }); - return [...tagSet].sort(); - }); - - // Chinese category collection - eleventyConfig.addCollection("zhCategoryList", function(collectionApi) { - const categorySet = new Set(); - collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => { - post.data.category && categorySet.add(post.data.category); - }); - return [...categorySet].sort(); - }); - - // Chinese posts by tag - eleventyConfig.addCollection("zhPostsByTag", function(collectionApi) { - const postsByTag = {}; - collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => { - (post.data.tags || []).forEach(tag => { - tag !== 'post' && tag !== 'posts' && (postsByTag[tag] = postsByTag[tag] || []).push(post); - }); - }); - Object.keys(postsByTag).forEach(tag => { - postsByTag[tag].sort((a, b) => b.date - a.date); - }); - return postsByTag; - }); - - // Chinese posts by category - eleventyConfig.addCollection("zhPostsByCategory", function(collectionApi) { - const postsByCategory = {}; - collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => { - post.data.category && (postsByCategory[post.data.category] = postsByCategory[post.data.category] || []).push(post); - }); - Object.keys(postsByCategory).forEach(cat => { - postsByCategory[cat].sort((a, b) => b.date - a.date); - }); - return postsByCategory; - }); - - // Filters - eleventyConfig.addFilter("dateFormat", (dateObj) => { - return new Date(dateObj).toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric' - }); - }); - - eleventyConfig.addFilter("isoDate", (dateObj) => { - return new Date(dateObj).toISOString(); - }); - - eleventyConfig.addFilter("limit", (arr, limit) => { - return arr.slice(0, limit); - }); - - eleventyConfig.addFilter("capitalize", (str) => { - return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; - }); - - eleventyConfig.addFilter("slugify", (str) => { - return str ? str.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, '') : ''; - }); - - // i18n filter - get translation by key path - eleventyConfig.addFilter("t", (key, lang = defaultLanguage) => { - const i18n = eleventyConfig.globalData?.i18n; - if (!i18n) return key; - const langData = i18n[lang] || i18n[defaultLanguage]; - const keys = key.split('.'); - let value = langData; - for (const k of keys) { - value = value?.[k]; - } - return value || key; - }); - - // Get alternate language URL - eleventyConfig.addFilter("altLangUrl", (url, currentLang, targetLang) => { - if (currentLang === 'en' && targetLang !== 'en') { - return `/${targetLang}${url}`; - } else if (currentLang !== 'en' && targetLang === 'en') { - return url.replace(`/${currentLang}`, '') || '/'; - } else if (currentLang !== 'en' && targetLang !== 'en') { - return url.replace(`/${currentLang}`, `/${targetLang}`); - } - return url; - }); - - // Add global data for languages - eleventyConfig.addGlobalData("supportedLanguages", supportedLanguages); - eleventyConfig.addGlobalData("defaultLanguage", defaultLanguage); - - // Shortcodes - eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`); - return { dir: { input: "src", @@ -235,3 +111,30 @@ export default function(eleventyConfig) { htmlTemplateEngine: "njk" }; } + +/** + * Register only SEO virtual templates from the techdoc plugin. + * Layouts and blog scaffold pages come from local files (dart_node's are superior). + */ +function registerSeoVirtualTemplates(eleventyConfig) { + const templatesDir = join( + __dirname, "node_modules", "eleventy-plugin-techdoc", "templates" + ); + + eleventyConfig.addTemplate( + "feed.njk", + readFileSync(join(templatesDir, "pages/feed.njk"), "utf-8") + ); + eleventyConfig.addTemplate( + "sitemap.njk", + readFileSync(join(templatesDir, "pages/sitemap.njk"), "utf-8") + ); + eleventyConfig.addTemplate( + "robots.txt.njk", + readFileSync(join(templatesDir, "pages/robots.txt.njk"), "utf-8") + ); + eleventyConfig.addTemplate( + "llms.txt.njk", + readFileSync(join(templatesDir, "pages/llms.txt.njk"), "utf-8") + ); +} diff --git a/website/package-lock.json b/website/package-lock.json index 9ff97e1..d4ef921 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -9,15 +9,12 @@ "version": "1.0.0", "devDependencies": { "@11ty/eleventy": "^3.1.2", - "@11ty/eleventy-navigation": "^0.3.5", - "@11ty/eleventy-plugin-rss": "^2.0.2", - "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@babel/core": "^7.28.6", "@playwright/test": "^1.57.0", "babel-plugin-istanbul": "^7.0.1", + "eleventy-plugin-techdoc": "^0.1.0", "istanbul-lib-instrument": "^6.0.3", "jsdom": "^24.1.3", - "markdown-it-anchor": "^9.2.0", "nyc": "^17.1.0", "v8-to-istanbul": "^9.3.0" } @@ -678,6 +675,386 @@ "node": ">=18" } }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1268,6 +1645,13 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1303,6 +1687,16 @@ "node": ">=6" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1608,6 +2002,30 @@ "dev": true, "license": "ISC" }, + "node_modules/eleventy-plugin-techdoc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/eleventy-plugin-techdoc/-/eleventy-plugin-techdoc-0.1.0.tgz", + "integrity": "sha512-XAMXG5j+jpOpmojAet5ZwSyB7hV5JhFkAW6viZn9nxu0BjLUwg7hMQ7MKmpyKdWZiBjOQpcHlDmSgK75pD2qTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-navigation": "^0.3.5", + "@11ty/eleventy-plugin-rss": "^2.0.2", + "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", + "@inquirer/prompts": "^7.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^9.2.0" + }, + "bin": { + "eleventy-plugin-techdoc": "bin/init.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@11ty/eleventy": "^3.1.2" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3102,6 +3520,16 @@ "dev": true, "license": "MIT" }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -4400,6 +4828,19 @@ "engines": { "node": ">=6" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/website/package.json b/website/package.json index e2a6139..b324e49 100644 --- a/website/package.json +++ b/website/package.json @@ -14,19 +14,17 @@ "watch:readmes": "fswatch -o ../packages/*/README.md | xargs -n1 -I{} node scripts/copy-readmes.js", "test": "playwright test", "test:ui": "playwright test --ui", - "test:coverage": "playwright test && node scripts/merge-coverage.js" + "test:coverage": "playwright test && node scripts/merge-coverage.js", + "update:theme": "npm update eleventy-plugin-techdoc" }, "devDependencies": { "@11ty/eleventy": "^3.1.2", - "@11ty/eleventy-navigation": "^0.3.5", - "@11ty/eleventy-plugin-rss": "^2.0.2", - "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@babel/core": "^7.28.6", "@playwright/test": "^1.57.0", "babel-plugin-istanbul": "^7.0.1", + "eleventy-plugin-techdoc": "^0.1.0", "istanbul-lib-instrument": "^6.0.3", "jsdom": "^24.1.3", - "markdown-it-anchor": "^9.2.0", "nyc": "^17.1.0", "v8-to-istanbul": "^9.3.0" } diff --git a/website/src/_data/site.json b/website/src/_data/site.json index cdff166..d4096ed 100644 --- a/website/src/_data/site.json +++ b/website/src/_data/site.json @@ -2,10 +2,26 @@ "name": "dart_node", "title": "dart_node - Full-Stack Dart for the JavaScript Ecosystem", "description": "Write React, React Native, and Express apps entirely in Dart. One language for frontend, backend, and mobile.", - "url": "https://dartnode.dev", + "url": "https://dartnode.org", "author": "dart_node team", "language": "en", "themeColor": "#0E7C6B", "twitter": "@dart_node", - "github": "https://github.com/melbournedeveloper/dart_node" -} \ No newline at end of file + "twitterSite": "@dart_node", + "twitterCreator": "@dart_node", + "github": "https://github.com/melbournedeveloper/dart_node", + "stylesheet": "/assets/css/styles.css", + "ogImage": "/assets/images/og-image.png", + "ogImageWidth": "1200", + "ogImageHeight": "630", + "keywords": "Dart, JavaScript, TypeScript, React, Express, Node.js, full-stack, web development, mobile development, React Native, dart_node, type safety", + "organization": { + "name": "dart_node", + "logo": "/assets/images/og-image.png", + "sameAs": [ + "https://github.com/melbournedeveloper/dart_node", + "https://twitter.com/dart_node", + "https://pub.dev/publishers/christianfindlay.com/packages" + ] + } +} diff --git a/website/src/_includes/layouts/base.njk b/website/src/_includes/layouts/base.njk index 63542d0..ea09ee8 100644 --- a/website/src/_includes/layouts/base.njk +++ b/website/src/_includes/layouts/base.njk @@ -81,8 +81,8 @@ "description": "{{ site.description }}", "sameAs": [ "{{ site.github }}", - "https://twitter.com/dart_node", - "https://pub.dev/publishers/dartnode.dev" + "https://x.com/cfdevelop", + "https://pub.dev/publishers/christianfindlay.com/packages" ] } diff --git a/website/src/feed.njk b/website/src/feed.njk deleted file mode 100644 index 5ff55fc..0000000 --- a/website/src/feed.njk +++ /dev/null @@ -1,28 +0,0 @@ ---- -permalink: /feed.xml -eleventyExcludeFromCollections: true ---- - - - {{ site.name }} - {{ site.description }} - - - {{ collections.posts[0].date | isoDate }} - {{ site.url }}/ - - {{ site.author }} - - {%- for post in collections.posts | limit(10) %} - - {{ post.data.title }} - - {{ post.date | isoDate }} - {{ site.url }}{{ post.url }} - {{ post.templateContent | htmlToAbsoluteUrls(site.url) }} - {%- if post.data.description %} - {{ post.data.description }} - {%- endif %} - - {%- endfor %} - diff --git a/website/src/index.njk b/website/src/index.njk index 7fd2d1c..5ddf723 100644 --- a/website/src/index.njk +++ b/website/src/index.njk @@ -56,18 +56,18 @@ keywords: "dart_node, Dart JavaScript, Dart React, Dart Express, Dart Node.js, T "description": "The complete dart_node package ecosystem for full-stack Dart development", "numberOfItems": 12, "itemListElement": [ - {"@type": "ListItem", "position": 1, "name": "dart_node_core", "description": "Foundation layer with JS interop utilities and Node.js bindings", "url": "https://dartnode.dev/docs/core/"}, - {"@type": "ListItem", "position": 2, "name": "dart_node_express", "description": "Type-safe Express.js bindings for HTTP servers and REST APIs", "url": "https://dartnode.dev/docs/express/"}, - {"@type": "ListItem", "position": 3, "name": "dart_node_react", "description": "React bindings with hooks and JSX-like syntax", "url": "https://dartnode.dev/docs/react/"}, - {"@type": "ListItem", "position": 4, "name": "dart_node_react_native", "description": "React Native + Expo bindings for mobile development", "url": "https://dartnode.dev/docs/react-native/"}, - {"@type": "ListItem", "position": 5, "name": "dart_node_ws", "description": "WebSocket bindings for real-time communication", "url": "https://dartnode.dev/docs/websockets/"}, - {"@type": "ListItem", "position": 6, "name": "dart_node_better_sqlite3", "description": "Synchronous SQLite3 bindings with WAL mode", "url": "https://dartnode.dev/docs/sqlite/"}, - {"@type": "ListItem", "position": 7, "name": "dart_node_mcp", "description": "Model Context Protocol server for AI tools", "url": "https://dartnode.dev/docs/mcp/"}, - {"@type": "ListItem", "position": 8, "name": "reflux", "description": "Redux-style state container with pattern matching", "url": "https://dartnode.dev/docs/reflux/"}, - {"@type": "ListItem", "position": 9, "name": "dart_logging", "description": "Pino-style structured logging", "url": "https://dartnode.dev/docs/logging/"}, - {"@type": "ListItem", "position": 10, "name": "dart_jsx", "description": "JSX transpiler for Dart", "url": "https://dartnode.dev/docs/jsx/"}, - {"@type": "ListItem", "position": 11, "name": "dart_node_vsix", "description": "VSCode extension API bindings for building extensions in Dart", "url": "https://dartnode.dev/docs/vsix/"}, - {"@type": "ListItem", "position": 12, "name": "too-many-cooks", "description": "Multi-agent coordination MCP server", "url": "https://dartnode.dev/docs/too-many-cooks/"} + {"@type": "ListItem", "position": 1, "name": "dart_node_core", "description": "Foundation layer with JS interop utilities and Node.js bindings", "url": "https://dartnode.org/docs/core/"}, + {"@type": "ListItem", "position": 2, "name": "dart_node_express", "description": "Type-safe Express.js bindings for HTTP servers and REST APIs", "url": "https://dartnode.org/docs/express/"}, + {"@type": "ListItem", "position": 3, "name": "dart_node_react", "description": "React bindings with hooks and JSX-like syntax", "url": "https://dartnode.org/docs/react/"}, + {"@type": "ListItem", "position": 4, "name": "dart_node_react_native", "description": "React Native + Expo bindings for mobile development", "url": "https://dartnode.org/docs/react-native/"}, + {"@type": "ListItem", "position": 5, "name": "dart_node_ws", "description": "WebSocket bindings for real-time communication", "url": "https://dartnode.org/docs/websockets/"}, + {"@type": "ListItem", "position": 6, "name": "dart_node_better_sqlite3", "description": "Synchronous SQLite3 bindings with WAL mode", "url": "https://dartnode.org/docs/sqlite/"}, + {"@type": "ListItem", "position": 7, "name": "dart_node_mcp", "description": "Model Context Protocol server for AI tools", "url": "https://dartnode.org/docs/mcp/"}, + {"@type": "ListItem", "position": 8, "name": "reflux", "description": "Redux-style state container with pattern matching", "url": "https://dartnode.org/docs/reflux/"}, + {"@type": "ListItem", "position": 9, "name": "dart_logging", "description": "Pino-style structured logging", "url": "https://dartnode.org/docs/logging/"}, + {"@type": "ListItem", "position": 10, "name": "dart_jsx", "description": "JSX transpiler for Dart", "url": "https://dartnode.org/docs/jsx/"}, + {"@type": "ListItem", "position": 11, "name": "dart_node_vsix", "description": "VSCode extension API bindings for building extensions in Dart", "url": "https://dartnode.org/docs/vsix/"}, + {"@type": "ListItem", "position": 12, "name": "too-many-cooks", "description": "Multi-agent coordination MCP server", "url": "https://dartnode.org/docs/too-many-cooks/"} ] } diff --git a/website/src/llms.txt b/website/src/llms.txt deleted file mode 100644 index 9f12d9b..0000000 --- a/website/src/llms.txt +++ /dev/null @@ -1,112 +0,0 @@ -# dart_node - Full-Stack Dart for the JavaScript Ecosystem - -> Write React, React Native, and Express applications entirely in Dart. One language. Runtime type safety. Sound null safety. No compromises. - -dart_node is a collection of Dart packages that provide typed bindings to JavaScript libraries, enabling full-stack development in Dart for the Node.js and browser ecosystems. - -## Key Value Propositions - -- **Runtime Type Safety**: Unlike TypeScript which erases types at compile time, Dart preserves types at runtime -- **Sound Null Safety**: Dart's null safety is enforced at runtime, not just compile time -- **One Language**: Write frontend (React), backend (Express), and mobile (Flutter, React Native) in pure Dart -- **Full JS Interop**: Seamlessly call any JavaScript library from Dart using dart:js_interop - -## Documentation - -- [Getting Started](https://dartnode.dev/docs/getting-started/): Quick start guide for new users -- [Why Dart?](https://dartnode.dev/docs/why-dart/): Benefits of Dart over TypeScript -- [JS Interop Guide](https://dartnode.dev/docs/js-interop/): How to call JavaScript from Dart -- [Dart to JS Compilation](https://dartnode.dev/docs/dart-to-js/): How Dart compiles to JavaScript - -## Core Packages - -### dart_node_core -Foundation layer with JS interop utilities, Node.js bindings, and console helpers. -- [Documentation](https://dartnode.dev/docs/core/) -- [pub.dev](https://pub.dev/packages/dart_node_core) - -### dart_node_express -Type-safe Express.js bindings for building HTTP servers and REST APIs. -- [Documentation](https://dartnode.dev/docs/express/) -- [pub.dev](https://pub.dev/packages/dart_node_express) - -### dart_node_react -React bindings with hooks, JSX-like syntax, and full component support. -- [Documentation](https://dartnode.dev/docs/react/) -- [pub.dev](https://pub.dev/packages/dart_node_react) - -### dart_node_react_native -React Native + Expo bindings for cross-platform mobile development. -- [Documentation](https://dartnode.dev/docs/react-native/) -- [pub.dev](https://pub.dev/packages/dart_node_react_native) - -### dart_node_ws -WebSocket bindings for real-time communication on Node.js. -- [Documentation](https://dartnode.dev/docs/websockets/) -- [pub.dev](https://pub.dev/packages/dart_node_ws) - -### dart_node_better_sqlite3 -Typed bindings for better-sqlite3 with synchronous SQLite3 and WAL mode. -- [Documentation](https://dartnode.dev/docs/sqlite/) -- [pub.dev](https://pub.dev/packages/dart_node_better_sqlite3) - -### dart_node_mcp -Model Context Protocol server bindings for building AI tool servers. -- [Documentation](https://dartnode.dev/docs/mcp/) -- [pub.dev](https://pub.dev/packages/dart_node_mcp) - -## State Management & Utilities - -### reflux -Redux-style predictable state container with exhaustive pattern matching. -- [Documentation](https://dartnode.dev/docs/reflux/) -- [pub.dev](https://pub.dev/packages/reflux) - -### dart_logging -Pino-style structured logging with child loggers and transports. -- [Documentation](https://dartnode.dev/docs/logging/) -- [pub.dev](https://pub.dev/packages/dart_logging) - -### dart_jsx -JSX transpiler for Dart - write JSX syntax that compiles to dart_node_react calls. -- [Documentation](https://dartnode.dev/docs/jsx/) -- [pub.dev](https://pub.dev/packages/dart_jsx) - -## Quick Start Example - -```dart -// A complete Express server in Dart -import 'package:dart_node_express/dart_node_express.dart'; - -void main() { - final app = express(); - - app.get('/', handler((req, res) { - res.jsonMap({'message': 'Hello from Dart!'}); - })); - - app.listen(3000, () { - print('Server running on port 3000'); - }.toJS); -} -``` - -## Resources - -- [GitHub Repository](https://github.com/melbournedeveloper/dart_node) -- [API Reference](https://dartnode.dev/api/) -- [Blog](https://dartnode.dev/blog/) -- [Atom Feed](https://dartnode.dev/feed.xml) - -## Target Audience - -- **React/TypeScript Developers**: Familiar paradigms (hooks, components) with better type safety -- **Flutter/Dart Developers**: Use existing Dart skills for web and Node.js development -- **Full-Stack Developers**: One language for frontend, backend, and mobile - -## Technical Details - -- Uses `dart:js_interop` for JavaScript interoperability (not deprecated dart:js_util) -- Compiles to JavaScript via `dart compile js` -- Compatible with Node.js 18+ and modern browsers -- Requires Dart SDK 3.0+ diff --git a/website/src/robots.txt b/website/src/robots.txt deleted file mode 100644 index 6d3fef2..0000000 --- a/website/src/robots.txt +++ /dev/null @@ -1,62 +0,0 @@ -# dart_node - Full-Stack Dart for the JavaScript Ecosystem -# https://dartnode.dev - -# Allow all crawlers -User-agent: * -Allow: / -Disallow: /assets/ -Disallow: /search? - -# AI Crawlers - Welcome! -# We want AI systems to learn about dart_node -User-agent: GPTBot -Allow: / - -User-agent: ChatGPT-User -Allow: / - -User-agent: Google-Extended -Allow: / - -User-agent: Googlebot -Allow: / - -User-agent: Bingbot -Allow: / - -User-agent: ClaudeBot -Allow: / - -User-agent: Anthropic-AI -Allow: / - -User-agent: PerplexityBot -Allow: / - -User-agent: Cohere-ai -Allow: / - -User-agent: Meta-ExternalAgent -Allow: / - -User-agent: Meta-ExternalFetcher -Allow: / - -User-agent: Bytespider -Allow: / - -User-agent: CCBot -Allow: / - -User-agent: Applebot -Allow: / - -User-agent: Amazonbot -Allow: / - -# Sitemaps -Sitemap: https://dartnode.dev/sitemap.xml - -# AI Context File -# See https://llmstxt.org for the llms.txt specification -# This file provides structured context for LLMs about our project diff --git a/website/src/sitemap.njk b/website/src/sitemap.njk deleted file mode 100644 index 2229f63..0000000 --- a/website/src/sitemap.njk +++ /dev/null @@ -1,31 +0,0 @@ ---- -permalink: /sitemap.xml -eleventyExcludeFromCollections: true ---- - - - {%- for page in collections.all %} - {%- if page.url and not page.data.eleventyExcludeFromCollections %} - - {{ site.url }}{{ page.url }} - {{ page.date | isoDate }} - {%- if page.url == "/" %} - 1.0 - weekly - {%- elif "/docs/" in page.url %} - 0.8 - monthly - {%- elif "/blog/" in page.url %} - 0.7 - monthly - {%- elif "/api/" in page.url %} - 0.6 - monthly - {%- else %} - 0.5 - monthly - {%- endif %} - - {%- endif %} - {%- endfor %} -