diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d804607 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +## TLDR; + +## What Does This Do? + +## Brief Details? + +## How Do The Tests Prove The Change Works? \ No newline at end of file diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 0000000..707e267 --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,63 @@ +name: Deploy Website to GitHub Pages + +on: + workflow_dispatch: + +permissions: + contents: write + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: website/package-lock.json + + - name: Install website dependencies + working-directory: website + run: npm ci + + - name: Generate API documentation + working-directory: website + run: ./scripts/generate-api-docs.sh + + - name: Build website + working-directory: website + run: npx @11ty/eleventy --pathprefix=/dart_node/ + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/_site + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index d38019b..7f90cce 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ node_modules/ *.js.map examples/mobile/rn/.expo/ + +# Website generated files +website/_site/ +website/src/api/ +website/.dart-doc-temp/ diff --git a/website/eleventy.config.js b/website/eleventy.config.js new file mode 100644 index 0000000..0f95bec --- /dev/null +++ b/website/eleventy.config.js @@ -0,0 +1,115 @@ +import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"; +import pluginRss from "@11ty/eleventy-plugin-rss"; +import eleventyNavigationPlugin from "@11ty/eleventy-navigation"; + +export default function(eleventyConfig) { + // Plugins + eleventyConfig.addPlugin(syntaxHighlight); + eleventyConfig.addPlugin(pluginRss); + eleventyConfig.addPlugin(eleventyNavigationPlugin); + + // Passthrough copy for assets + eleventyConfig.addPassthroughCopy("src/assets"); + eleventyConfig.addPassthroughCopy("src/api"); + eleventyConfig.addPassthroughCopy("src/robots.txt"); + + // Watch targets + eleventyConfig.addWatchTarget("src/assets/"); + + // Collections + eleventyConfig.addCollection("posts", function(collectionApi) { + return collectionApi.getFilteredByGlob("src/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; + }); + + // 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, '') : ''; + }); + + // Shortcodes + eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`); + + return { + dir: { + input: "src", + output: "_site", + includes: "_includes", + data: "_data" + }, + templateFormats: ["md", "njk", "html"], + markdownTemplateEngine: "njk", + htmlTemplateEngine: "njk" + }; +} diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..b7aa2aa --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,2546 @@ +{ + "name": "dart-node-website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dart-node-website", + "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", + "jsdom": "^24.1.3" + } + }, + "node_modules/@11ty/dependency-tree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.0.tgz", + "integrity": "sha512-PTOnwM8Xt+GdJmwRKg4pZ8EKAgGoK7pedZBfNSOChXu8MYk2FdEsxdJYecX4t62owpGw3xK60q9TQv/5JI59jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-utils": "^2.0.1" + } + }, + "node_modules/@11ty/dependency-tree-esm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-2.0.2.tgz", + "integrity": "sha512-kSTmXneksQLBhwsfqjxiSi9ecRKENXmRtT5RG95rFoWSI8kkwLcGlYpoXsPkCD9uQwSU1rmDzXBDnqUJlWaIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-utils": "^2.0.7", + "acorn": "^8.15.0", + "dependency-graph": "^1.0.0", + "normalize-path": "^3.0.0" + } + }, + "node_modules/@11ty/eleventy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.1.2.tgz", + "integrity": "sha512-IcsDlbXnBf8cHzbM1YBv3JcTyLB35EK88QexmVyFdVJVgUU6bh9g687rpxryJirHzo06PuwnYaEEdVZQfIgRGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/dependency-tree": "^4.0.0", + "@11ty/dependency-tree-esm": "^2.0.0", + "@11ty/eleventy-dev-server": "^2.0.8", + "@11ty/eleventy-plugin-bundle": "^3.0.6", + "@11ty/eleventy-utils": "^2.0.7", + "@11ty/lodash-custom": "^4.17.21", + "@11ty/posthtml-urls": "^1.0.1", + "@11ty/recursive-copy": "^4.0.2", + "@sindresorhus/slugify": "^2.2.1", + "bcp-47-normalize": "^2.3.0", + "chokidar": "^3.6.0", + "debug": "^4.4.1", + "dependency-graph": "^1.0.0", + "entities": "^6.0.1", + "filesize": "^10.1.6", + "gray-matter": "^4.0.3", + "iso-639-1": "^3.1.5", + "js-yaml": "^4.1.0", + "kleur": "^4.1.5", + "liquidjs": "^10.21.1", + "luxon": "^3.6.1", + "markdown-it": "^14.1.0", + "minimist": "^1.2.8", + "moo": "^0.5.2", + "node-retrieve-globals": "^6.0.1", + "nunjucks": "^3.2.4", + "picomatch": "^4.0.2", + "please-upgrade-node": "^3.2.0", + "posthtml": "^0.16.6", + "posthtml-match-helper": "^2.0.3", + "semver": "^7.7.2", + "slugify": "^1.6.6", + "tinyglobby": "^0.2.14" + }, + "bin": { + "eleventy": "cmd.cjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-dev-server": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.8.tgz", + "integrity": "sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-utils": "^2.0.1", + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "finalhandler": "^1.3.1", + "mime": "^3.0.0", + "minimist": "^1.2.8", + "morphdom": "^2.7.4", + "please-upgrade-node": "^3.2.0", + "send": "^1.1.0", + "ssri": "^11.0.0", + "urlpattern-polyfill": "^10.0.0", + "ws": "^8.18.1" + }, + "bin": { + "eleventy-dev-server": "cmd.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-navigation": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-navigation/-/eleventy-navigation-0.3.5.tgz", + "integrity": "sha512-4aKW5aIQDFed8xs1G1pWcEiFPcDSwZtA4IH1eERtoJ+Xy+/fsoe0pzbDmw84bHZ9ACny5jblENhfZhcCxklqQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dependency-graph": "^0.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-navigation/node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/@11ty/eleventy-plugin-bundle": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.7.tgz", + "integrity": "sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-utils": "^2.0.2", + "debug": "^4.4.0", + "posthtml-match-helper": "^2.0.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-plugin-rss": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-rss/-/eleventy-plugin-rss-2.0.4.tgz", + "integrity": "sha512-LF60sGVlxGTryQe3hTifuzrwF8R7XbrNsM2xfcDcNMSliLN4kmB+7zvoLRySRx0AQDjqhPTAeeeT0ra6/9zHUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-utils": "^2.0.0", + "@11ty/posthtml-urls": "^1.0.1", + "debug": "^4.4.0", + "posthtml": "^0.16.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-plugin-syntaxhighlight": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-syntaxhighlight/-/eleventy-plugin-syntaxhighlight-5.0.2.tgz", + "integrity": "sha512-T6xVVRDJuHlrFMHbUiZkHjj5o1IlLzZW+1IL9eUsyXFU7rY2ztcYhZew/64vmceFFpQwzuSfxQOXxTJYmKkQ+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-2.0.7.tgz", + "integrity": "sha512-6QE+duqSQ0GY9rENXYb4iPR4AYGdrFpqnmi59tFp9VrleOl0QSh8VlBr2yd6dlhkdtj7904poZW5PvGr9cMiJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/lodash-custom": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", + "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/posthtml-urls": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.1.tgz", + "integrity": "sha512-6EFN/yYSxC/OzYXpq4gXDyDMlX/W+2MgCvvoxf11X1z76bqkqFJ8eep5RiBWfGT5j0323a1pwpelcJJdR46MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "evaluate-value": "^2.0.0", + "http-equiv-refresh": "^2.0.1", + "list-to-array": "^1.1.0", + "parse-srcset": "^1.0.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@11ty/recursive-copy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.2.tgz", + "integrity": "sha512-174nFXxL/6KcYbLYpra+q3nDbfKxLxRTNVY1atq2M1pYYiPfHse++3IFNl8mjPFsd7y2qQjxLORzIjHMjL3NDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "errno": "^1.0.0", + "junk": "^3.1.0", + "maximatch": "^0.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", + "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bcp-47": "^2.0.0", + "bcp-47-match": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", + "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm-import-transformer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.5.tgz", + "integrity": "sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/evaluate-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz", + "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filesize": { + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", + "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 10.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-equiv-refresh": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz", + "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/iso-639-1": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz", + "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/liquidjs": { + "version": "10.24.0", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.24.0.tgz", + "integrity": "sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/list-to-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", + "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/maximatch": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", + "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/morphdom": { + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz", + "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-retrieve-globals": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz", + "integrity": "sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.1", + "acorn-walk": "^8.3.4", + "esm-import-transformer": "^3.0.3" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/nwsapi": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/posthtml": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz", + "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-match-helper": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz", + "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "posthtml": "^0.16.6" + } + }, + "node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz", + "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..32f549c --- /dev/null +++ b/website/package.json @@ -0,0 +1,19 @@ +{ + "name": "dart-node-website", + "version": "1.0.0", + "description": "Documentation website for dart_node - Full-stack Dart for the JavaScript ecosystem", + "type": "module", + "scripts": { + "dev": "eleventy --serve", + "build": "bash scripts/generate-api-docs.sh && eleventy", + "build:docs": "bash scripts/generate-api-docs.sh", + "build:site": "eleventy" + }, + "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", + "jsdom": "^24.1.3" + } +} diff --git a/website/scripts/generate-api-docs.js b/website/scripts/generate-api-docs.js new file mode 100644 index 0000000..d1cb604 --- /dev/null +++ b/website/scripts/generate-api-docs.js @@ -0,0 +1,453 @@ +#!/usr/bin/env node + +/** + * Generate API documentation for dart_node packages. + * Extracts content from dart doc HTML and wraps it in site templates. + * + * Structure: + * /api/ - index of all packages + * /api/dart_node_core/ - library page (classes, functions, etc.) + * /api/dart_node_core/ClassName/ - class page + * /api/dart_node_core/ClassName/method/ - method page + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { JSDOM } from 'jsdom'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const WEBSITE_DIR = path.dirname(__dirname); +const PROJECT_ROOT = path.dirname(WEBSITE_DIR); +const PACKAGES_DIR = path.join(PROJECT_ROOT, 'packages'); +const API_OUTPUT_DIR = path.join(WEBSITE_DIR, 'src', 'api'); +const TEMP_DIR = path.join(WEBSITE_DIR, '.dart-doc-temp'); + +const PACKAGES = [ + 'dart_node_core', + 'dart_node_express', + 'dart_node_react', + 'dart_node_react_native', + 'dart_node_ws', +]; + +// Base URLs for external documentation +const DOC_BASES = { + react: 'https://react.dev/reference/react', + reactDom: 'https://react.dev/reference/react-dom', + reactNative: 'https://reactnative.dev/docs', + express: 'https://expressjs.com/en/5x/api.html', + mdn: 'https://developer.mozilla.org/en-US/docs/Web/HTML/Element', + ws: 'https://github.com/websockets/ws/blob/master/doc/ws.md', + dartJsInterop: 'https://dart.dev/interop/js-interop', +}; + +// Package-level external documentation links (for package index pages) +const PACKAGE_DOCS = { + dart_node_core: [ + { name: 'Dart JS Interop', url: DOC_BASES.dartJsInterop }, + { name: 'dart:js_interop API', url: 'https://api.flutter.dev/flutter/dart-js_interop/dart-js_interop-library.html' }, + ], + dart_node_express: [ + { name: 'Express.js API', url: DOC_BASES.express }, + ], + dart_node_react: [ + { name: 'React API Reference', url: DOC_BASES.react }, + ], + dart_node_react_native: [ + { name: 'React Native Components', url: `${DOC_BASES.reactNative}/components-and-apis` }, + { name: 'Expo SDK', url: 'https://docs.expo.dev/versions/latest/' }, + ], + dart_node_ws: [ + { name: 'ws (WebSocket) npm', url: 'https://github.com/websockets/ws' }, + { name: 'WebSocket API (MDN)', url: 'https://developer.mozilla.org/en-US/docs/Web/API/WebSocket' }, + ], +}; + +// Maps element name patterns to doc generators +// Each entry: { pattern: regex or string, generate: (elementName) => [{ name, url }] } +const ELEMENT_DOC_GENERATORS = [ + // React hooks - use* functions + { + pattern: /^use[A-Z]/, + generate: (name) => [{ name: `React ${name}`, url: `${DOC_BASES.react}/${name}` }], + }, + + // HTML elements - *Element types (for dart_node_react) + // React has official docs for DOM components: https://react.dev/reference/react-dom/components + { + pattern: /^(Div|Button|Input|Span|A|Img|P|H1|H2|H3|H4|H5|H6|Ul|Li|Header|Footer|Main|Nav|Section|Article|Form|Label|Textarea|Select|Option|Table|Tr|Td|Th)Element$/, + generate: (name) => { + const tag = name.replace(/Element$/, '').toLowerCase(); + // React docs use "common" for generic elements, specific pages for form elements + const reactDocMap = { + input: 'input', select: 'select', textarea: 'textarea', form: 'form', + option: 'option', progress: 'progress', a: 'a', link: 'link', + }; + const reactDoc = reactDocMap[tag] || 'common'; + return [{ name: `React ${tag} component`, url: `https://react.dev/reference/react-dom/components/${reactDoc}` }]; + }, + }, + + // React Native components - RN*Element types + { + pattern: /^RN(\w+)Element$/, + generate: (name) => { + const component = name.match(/^RN(\w+)Element$/)?.[1]?.toLowerCase(); + return component + ? [{ name: `React Native ${name.match(/^RN(\w+)Element$/)[1]}`, url: `${DOC_BASES.reactNative}/${component}` }] + : []; + }, + }, + + // React Native lowercase functions (view, text, button, etc.) + { + pattern: /^(view|text|rnButton|rnImage|textInput|scrollView|flatList|touchableOpacity|safeAreaView|rnSwitch|activityIndicator)$/, + generate: (name) => { + const componentMap = { + view: 'view', text: 'text', rnButton: 'button', rnImage: 'image', + textInput: 'textinput', scrollView: 'scrollview', flatList: 'flatlist', + touchableOpacity: 'touchableopacity', safeAreaView: 'safeareaview', + rnSwitch: 'switch', activityIndicator: 'activityindicator', + }; + const doc = componentMap[name]; + return doc + ? [{ name: `React Native ${doc.charAt(0).toUpperCase() + doc.slice(1)}`, url: `${DOC_BASES.reactNative}/${doc}` }] + : []; + }, + }, + + // AppRegistry + { + pattern: /^AppRegistry/, + generate: () => [{ name: 'React Native AppRegistry', url: `${DOC_BASES.reactNative}/appregistry` }], + }, + + // React core types + { + pattern: /^React$/, + generate: () => [{ name: 'React API Reference', url: DOC_BASES.react }], + }, + { + pattern: /^ReactDOM$/, + generate: () => [{ name: 'ReactDOM API', url: DOC_BASES.reactDom }], + }, + { + pattern: /^ReactElement$/, + generate: () => [{ name: 'React createElement', url: `${DOC_BASES.react}/createElement` }], + }, + { + pattern: /^ReactRoot$/, + generate: () => [{ name: 'ReactDOM createRoot', url: `${DOC_BASES.reactDom}/client/createRoot` }], + }, + { + pattern: /^createElement$/, + generate: () => [{ name: 'React createElement', url: `${DOC_BASES.react}/createElement` }], + }, + + // Express types + { + pattern: /^(Request|Response|Router|ExpressApp)$/, + generate: (name) => { + const anchorMap = { Request: 'req', Response: 'res', Router: 'router', ExpressApp: 'app' }; + return [{ name: `Express ${name}`, url: `${DOC_BASES.express}#${anchorMap[name]}` }]; + }, + }, + { + pattern: /^express$/, + generate: () => [{ name: 'Express express()', url: `${DOC_BASES.express}#express` }], + }, + + // WebSocket types + { + pattern: /^(WebSocketServer|JSWebSocketServer)$/, + generate: () => [{ name: 'ws WebSocket.Server', url: `${DOC_BASES.ws}#class-websocketserver` }], + }, + { + pattern: /^(WebSocketClient|JSWebSocket)$/, + generate: () => [{ name: 'ws WebSocket', url: `${DOC_BASES.ws}#class-websocket` }], + }, + + // ReactNative extension type + { + pattern: /^ReactNative$/, + generate: () => [{ name: 'React Native API', url: `${DOC_BASES.reactNative}/components-and-apis` }], + }, +]; + +// Get external docs for an element using pattern matching +const getExternalDocs = (elementName, packageName) => { + // Clean up element name - remove suffixes like -extension-type, -class + const cleanName = elementName + .replace(/-extension-type$/, '') + .replace(/-class$/, ''); + + // Find matching generator + const generator = ELEMENT_DOC_GENERATORS.find(g => + (typeof g.pattern === 'string') + ? cleanName === g.pattern + : g.pattern.test(cleanName) + ); + + return generator ? generator.generate(cleanName) : null; +}; + +const ensureDir = (dir) => fs.mkdirSync(dir, { recursive: true }); + +const cleanDir = (dir) => { + fs.existsSync(dir) && fs.rmSync(dir, { recursive: true }); + ensureDir(dir); +}; + +const runDartDoc = (packageDir, outputDir) => { + console.log(` Running dart pub get...`); + execSync('dart pub get', { cwd: packageDir, stdio: 'inherit' }); + console.log(` Running dart doc...`); + execSync(`dart doc --output="${outputDir}"`, { cwd: packageDir, stdio: 'inherit' }); +}; + +const escapeYaml = (str) => str.replace(/"/g, '\\"').replace(/\n/g, ' '); + +const extractContent = (htmlPath, packageName) => { + const html = fs.readFileSync(htmlPath, 'utf-8'); + const dom = new JSDOM(html); + const doc = dom.window.document; + + const selfName = doc.querySelector('.self-name'); + const h1 = doc.querySelector('#dartdoc-main-content h1'); + const title = selfName?.textContent?.trim() || h1?.textContent?.trim() || 'API Documentation'; + + const metaDesc = doc.querySelector('meta[name="description"]'); + const description = metaDesc?.getAttribute('content') || ''; + + const mainContent = doc.querySelector('#dartdoc-main-content'); + + return mainContent + ? { title, description, content: processContent(mainContent, packageName, dom) } + : null; +}; + +const processContent = (element, packageName, dom) => { + const h1 = element.querySelector('h1'); + h1?.remove(); + + element.removeAttribute('data-above-sidebar'); + element.removeAttribute('data-below-sidebar'); + + // Convert mermaid code blocks + element.querySelectorAll('pre.language-mermaid, pre code.language-mermaid').forEach(el => { + const pre = el.tagName === 'PRE' ? el : el.parentElement; + const code = el.tagName === 'CODE' ? el.textContent : el.querySelector('code')?.textContent || el.textContent; + const mermaidDiv = dom.window.document.createElement('div'); + mermaidDiv.className = 'mermaid'; + mermaidDiv.textContent = code; + pre.replaceWith(mermaidDiv); + }); + + // Fix internal links for new flat structure + element.querySelectorAll('a').forEach(a => { + const href = a.getAttribute('href'); + (!href || href.startsWith('http') || href.startsWith('#')) && null; + + href && !href.startsWith('http') && !href.startsWith('#') && (() => { + let newHref = href.replace(/\.html$/, '/'); + + // Links like ../dart_node_ws/Foo.html -> /api/dart_node_ws/Foo/ + // (removing the duplicate package/package structure) + newHref.startsWith('../') && (newHref = newHref.replace(/^\.\.\//, `/api/`)); + + // Links like Foo.html -> Foo/ (relative, stays same level) + // Links like Foo/bar.html -> Foo/bar/ + + a.setAttribute('href', newHref); + })(); + }); + + return element.innerHTML; +}; + +const createMdFile = (outputPath, title, description, packageName, content, elementName = null) => { + // Get element-specific docs only - no fallbacks + const externalLinks = elementName + ? (getExternalDocs(elementName, packageName) || []) + : (PACKAGE_DOCS[packageName] || []); + + const linksHtml = externalLinks.length > 0 + ? `
+

External Documentation

+ +
+ +` + : ''; + + const md = `--- +layout: layouts/api.njk +title: "${escapeYaml(title)}" +description: "${escapeYaml(description)}" +package: "${packageName}" +--- + +${linksHtml}${content} +`; + fs.writeFileSync(outputPath, md); +}; + +const processPackage = async (packageName) => { + const packageDir = path.join(PACKAGES_DIR, packageName); + + !fs.existsSync(packageDir) && console.log(`Warning: Package not found: ${packageDir}`); + + fs.existsSync(packageDir) && (() => { + console.log(`\n=== Processing ${packageName} ===`); + + const tempDocDir = path.join(TEMP_DIR, packageName); + ensureDir(tempDocDir); + runDartDoc(packageDir, tempDocDir); + + const outputDir = path.join(API_OUTPUT_DIR, packageName); + ensureDir(outputDir); + + // Use LIBRARY index.html as the package index + // dart doc creates: tempDocDir/packageName/index.html (the library page) + const indexHtml = path.join(tempDocDir, packageName, 'index.html'); + + fs.existsSync(indexHtml) && (() => { + const data = extractContent(indexHtml, packageName); + data && createMdFile( + path.join(outputDir, 'index.md'), + `${packageName} library`, + data.description, + packageName, + data.content + ); + })(); + + // Process all other files in library directory - put them DIRECTLY under package + const libDir = path.join(tempDocDir, packageName); + fs.existsSync(libDir) && processLibraryDir(libDir, outputDir, packageName); + + console.log(`Documentation processed for ${packageName}`); + })(); +}; + +const processLibraryDir = (libDir, outputDir, packageName) => { + const entries = fs.readdirSync(libDir, { withFileTypes: true }); + + entries.forEach(entry => { + const fullPath = path.join(libDir, entry.name); + + // Skip the library index files - already processed as package index + const skipFiles = [`${packageName}-library.html`, `${packageName}-library-sidebar.html`, 'index.html']; + + entry.isFile() && entry.name.endsWith('.html') && !skipFiles.includes(entry.name) && (() => { + const data = extractContent(fullPath, packageName); + const baseName = path.basename(entry.name, '.html'); + // Put class files directly under package: /api/package/ClassName/ + const classDir = path.join(outputDir, baseName); + ensureDir(classDir); + data && createMdFile( + path.join(classDir, 'index.md'), + data.title, + data.description, + packageName, + data.content, + baseName // Pass element name for specific external docs + ); + })(); + + // Process subdirectories (class methods, properties, etc.) + entry.isDirectory() && (() => { + const subOutputDir = path.join(outputDir, entry.name); + const parentElementName = entry.name; // The class/type name + ensureDir(subOutputDir); + + fs.readdirSync(fullPath) + .filter(f => f.endsWith('.html')) + .forEach(file => { + const data = extractContent(path.join(fullPath, file), packageName); + const baseName = path.basename(file, '.html'); + const methodDir = path.join(subOutputDir, baseName); + ensureDir(methodDir); + data && createMdFile( + path.join(methodDir, 'index.md'), + data.title, + data.description, + packageName, + data.content, + parentElementName // Use parent class name for external docs + ); + }); + })(); + }); +}; + +const createMainIndex = () => { + const content = `--- +layout: layouts/api.njk +title: API Reference +description: Complete API documentation for all dart_node packages +--- + +

Select a package to view its API documentation:

+ +

Packages

+ +
+ +

dart_node_core

+

Core JS interop utilities and foundation for all other packages.

+
+ + +

dart_node_express

+

Express.js bindings for building HTTP servers and REST APIs.

+
+ + +

dart_node_react

+

React bindings for building web applications.

+
+ + +

dart_node_react_native

+

React Native bindings for mobile apps with Expo.

+
+ + +

dart_node_ws

+

WebSocket bindings for real-time communication.

+
+
+`; + fs.writeFileSync(path.join(API_OUTPUT_DIR, 'index.md'), content); +}; + +const main = async () => { + console.log('Generating API documentation for dart_node packages...'); + console.log(`Packages directory: ${PACKAGES_DIR}`); + console.log(`Output directory: ${API_OUTPUT_DIR}`); + + cleanDir(TEMP_DIR); + cleanDir(API_OUTPUT_DIR); + + for (const pkg of PACKAGES) { + await processPackage(pkg); + } + + createMainIndex(); + fs.rmSync(TEMP_DIR, { recursive: true }); + + console.log('\n=== API documentation generation complete ==='); + console.log(`Output: ${API_OUTPUT_DIR}`); +}; + +main().catch(err => { + console.error('Error generating API docs:', err); + process.exit(1); +}); diff --git a/website/scripts/generate-api-docs.sh b/website/scripts/generate-api-docs.sh new file mode 100755 index 0000000..dbf7780 --- /dev/null +++ b/website/scripts/generate-api-docs.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Generate API documentation for all dart_node packages +# This wrapper script calls the Node.js processor + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WEBSITE_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$WEBSITE_DIR" + +# Ensure jsdom is available +npm list jsdom > /dev/null 2>&1 || npm install jsdom + +# Run the Node.js processor +node "$SCRIPT_DIR/generate-api-docs.js" diff --git a/website/src/_data/navigation.json b/website/src/_data/navigation.json new file mode 100644 index 0000000..3679904 --- /dev/null +++ b/website/src/_data/navigation.json @@ -0,0 +1,122 @@ +{ + "main": [ + { + "text": "Docs", + "url": "/docs/getting-started/" + }, + { + "text": "API", + "url": "/api/" + }, + { + "text": "Blog", + "url": "/blog/" + }, + { + "text": "GitHub", + "url": "https://github.com/melbournedeveloper/dart_node", + "external": true + } + ], + "docs": [ + { + "title": "Introduction", + "items": [ + { + "text": "Getting Started", + "url": "/docs/getting-started/" + }, + { + "text": "Why Dart?", + "url": "/docs/why-dart/" + }, + { + "text": "Dart to JavaScript", + "url": "/docs/dart-to-js/" + }, + { + "text": "JS Interop", + "url": "/docs/js-interop/" + } + ] + }, + { + "title": "Packages", + "items": [ + { + "text": "dart_node_core", + "url": "/docs/core/" + }, + { + "text": "dart_node_express", + "url": "/docs/express/" + }, + { + "text": "dart_node_react", + "url": "/docs/react/" + }, + { + "text": "dart_node_react_native", + "url": "/docs/react-native/" + }, + { + "text": "dart_node_ws", + "url": "/docs/websockets/" + } + ] + } + ], + "footer": [ + { + "title": "Documentation", + "items": [ + { + "text": "Getting Started", + "url": "/docs/getting-started/" + }, + { + "text": "API Reference", + "url": "/api/" + }, + { + "text": "Examples", + "url": "/docs/examples/" + } + ] + }, + { + "title": "Community", + "items": [ + { + "text": "GitHub", + "url": "https://github.com/melbournedeveloper/dart_node" + }, + { + "text": "Discord", + "url": "#" + }, + { + "text": "Twitter", + "url": "https://twitter.com/dart_node" + } + ] + }, + { + "title": "More", + "items": [ + { + "text": "Blog", + "url": "/blog/" + }, + { + "text": "Dart Official", + "url": "https://dart.dev" + }, + { + "text": "Flutter", + "url": "https://flutter.dev" + } + ] + } + ] +} \ No newline at end of file diff --git a/website/src/_data/site.json b/website/src/_data/site.json new file mode 100644 index 0000000..cdff166 --- /dev/null +++ b/website/src/_data/site.json @@ -0,0 +1,11 @@ +{ + "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", + "author": "dart_node team", + "language": "en", + "themeColor": "#0E7C6B", + "twitter": "@dart_node", + "github": "https://github.com/melbournedeveloper/dart_node" +} \ No newline at end of file diff --git a/website/src/_includes/layouts/api.njk b/website/src/_includes/layouts/api.njk new file mode 100644 index 0000000..7c049b2 --- /dev/null +++ b/website/src/_includes/layouts/api.njk @@ -0,0 +1,31 @@ +--- +layout: layouts/base.njk +--- + +
+ + +
+

{{ title }}

+ {{ content | safe }} +
+
diff --git a/website/src/_includes/layouts/base.njk b/website/src/_includes/layouts/base.njk new file mode 100644 index 0000000..ebc6fa5 --- /dev/null +++ b/website/src/_includes/layouts/base.njk @@ -0,0 +1,162 @@ + + + + + + + + {{ title | default(site.title) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% block head %}{% endblock %} + + + + +
+ +
+ +
+ {% block content %}{{ content | safe }}{% endblock %} +
+ + + + + + + + diff --git a/website/src/_includes/layouts/blog.njk b/website/src/_includes/layouts/blog.njk new file mode 100644 index 0000000..5635768 --- /dev/null +++ b/website/src/_includes/layouts/blog.njk @@ -0,0 +1,33 @@ +--- +layout: layouts/base.njk +--- + +
+
+
+ +

{{ title }}

+ {% if tags %} +
+ {% for tag in tags %} + {% if tag != 'post' and tag != 'posts' %} + {{ tag }} + {% endif %} + {% endfor %} +
+ {% endif %} +
+ +
+ {{ content | safe }} +
+ + +
+
diff --git a/website/src/_includes/layouts/docs.njk b/website/src/_includes/layouts/docs.njk new file mode 100644 index 0000000..81b91f3 --- /dev/null +++ b/website/src/_includes/layouts/docs.njk @@ -0,0 +1,41 @@ +--- +layout: layouts/base.njk +--- + +
+ + +
+

{{ title }}

+ {{ content | safe }} + + {% if prevPage or nextPage %} + + {% endif %} +
+
diff --git a/website/src/assets/css/styles.css b/website/src/assets/css/styles.css new file mode 100644 index 0000000..34ded6b --- /dev/null +++ b/website/src/assets/css/styles.css @@ -0,0 +1,1257 @@ +/* ============================================ + dart_node - Unified Stylesheet + NO SECTION-SPECIFIC STYLES - All classes are global + ============================================ */ + +/* CSS Custom Properties (Design Tokens) */ +:root { + /* Colors - Original scheme (no purple!) */ + --color-primary: #0E7C6B; + --color-primary-light: #10A08A; + --color-primary-dark: #0A5C50; + --color-secondary: #E85D3B; + --color-secondary-light: #F07858; + --color-secondary-dark: #C74A2A; + --color-accent: #2C89C7; + --color-accent-light: #4AA3E0; + --color-accent-dark: #1E6A9E; + + /* Neutral colors */ + --color-gray-50: #FAFBFC; + --color-gray-100: #F3F4F6; + --color-gray-200: #E5E7EB; + --color-gray-300: #D1D5DB; + --color-gray-400: #9CA3AF; + --color-gray-500: #6B7280; + --color-gray-600: #4B5563; + --color-gray-700: #374151; + --color-gray-800: #1F2937; + --color-gray-900: #111827; + + /* Semantic colors */ + --color-success: #10B981; + --color-warning: #F59E0B; + --color-error: #EF4444; + --color-info: #3B82F6; + + /* Light theme (default) */ + --bg-primary: var(--color-gray-50); + --bg-secondary: #FFFFFF; + --bg-tertiary: var(--color-gray-100); + --text-primary: var(--color-gray-900); + --text-secondary: var(--color-gray-600); + --text-tertiary: var(--color-gray-500); + --border-color: var(--color-gray-200); + --code-bg: var(--color-gray-100); + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'JetBrains Mono', 'Fira Code', Consolas, Monaco, monospace; + + /* Font sizes */ + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + --text-5xl: 3rem; + + /* Spacing */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + --space-16: 4rem; + --space-20: 5rem; + + /* Border radius */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-base: 200ms ease; + --transition-slow: 300ms ease; + + /* Layout */ + --max-width: 1200px; + --header-height: 64px; + --sidebar-width: 280px; +} + +/* Dark theme */ +[data-theme="dark"] { + --bg-primary: #0F1419; + --bg-secondary: #1A1F26; + --bg-tertiary: #242B33; + --text-primary: #F3F4F6; + --text-secondary: #9CA3AF; + --text-tertiary: #6B7280; + --border-color: #374151; + --code-bg: #1A1F26; +} + +/* Reset & Base */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: calc(var(--header-height) + var(--space-4)); +} + +body { + font-family: var(--font-sans); + font-size: var(--text-base); + line-height: 1.6; + color: var(--text-primary); + background-color: var(--bg-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Skip link for accessibility */ +.skip-link { + position: absolute; + top: -100%; + left: var(--space-4); + padding: var(--space-2) var(--space-4); + background: var(--color-primary); + color: white; + border-radius: var(--radius-md); + z-index: 1000; + transition: top var(--transition-fast); +} + +.skip-link:focus { + top: var(--space-4); +} + +/* Container */ +.container { + width: 100%; + max-width: var(--max-width); + margin: 0 auto; + padding: 0 var(--space-4); +} + +/* Typography - Unified across ALL sections */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + color: var(--text-primary); +} + +h1 { + font-size: var(--text-4xl); + font-weight: 700; + letter-spacing: -0.02em; +} + +h2 { + font-size: var(--text-3xl); + letter-spacing: -0.01em; +} + +h3 { + font-size: var(--text-2xl); +} + +h4 { + font-size: var(--text-xl); +} + +h5 { + font-size: var(--text-lg); +} + +h6 { + font-size: var(--text-base); +} + +p { + margin-bottom: var(--space-4); + color: var(--text-secondary); +} + +a { + color: var(--color-primary); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--color-primary-light); +} + +strong { + font-weight: 600; +} + +/* Lists */ +ul, ol { + margin-bottom: var(--space-4); + padding-left: var(--space-6); +} + +li { + margin-bottom: var(--space-2); + color: var(--text-secondary); +} + +/* Code - Unified styling */ +code { + font-family: var(--font-mono); + font-size: 0.9em; + padding: 0.2em 0.4em; + background: var(--code-bg); + border-radius: var(--radius-sm); + color: var(--color-secondary); +} + +pre { + font-family: var(--font-mono); + font-size: var(--text-sm); + line-height: 1.7; + padding: var(--space-4); + background: var(--code-bg); + border-radius: var(--radius-lg); + overflow-x: auto; + margin-bottom: var(--space-4); +} + +pre code { + padding: 0; + background: none; + color: inherit; +} + +/* Header & Navigation */ +.header { + position: sticky; + top: 0; + height: var(--header-height); + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + z-index: 100; +} + +.nav { + display: flex; + align-items: center; + justify-content: space-between; + height: 100%; +} + +.logo { + display: flex; + align-items: center; + gap: var(--space-2); + font-weight: 700; + font-size: var(--text-xl); + color: var(--text-primary); +} + +.logo:hover { + color: var(--color-primary); +} + +.nav-links { + display: flex; + align-items: center; + gap: var(--space-6); + list-style: none; + margin: 0; + padding: 0; +} + +.nav-link { + font-weight: 500; + color: var(--text-secondary); + transition: color var(--transition-fast); +} + +.nav-link:hover, +.nav-link.active { + color: var(--color-primary); +} + +.external-icon { + font-size: var(--text-sm); + margin-left: var(--space-1); +} + +.nav-actions { + display: flex; + align-items: center; + gap: var(--space-3); +} + +/* Theme Toggle */ +.theme-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: transparent; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.theme-toggle:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +[data-theme="light"] .icon-moon { + display: none; +} + +[data-theme="dark"] .icon-sun { + display: none; +} + +/* Mobile Menu Toggle */ +.mobile-menu-toggle { + display: none; + flex-direction: column; + gap: 4px; + padding: var(--space-2); + background: transparent; + border: none; + cursor: pointer; +} + +.mobile-menu-toggle span { + display: block; + width: 24px; + height: 2px; + background: var(--text-primary); + transition: all var(--transition-fast); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-6); + font-family: var(--font-sans); + font-size: var(--text-base); + font-weight: 500; + text-decoration: none; + border-radius: var(--radius-md); + border: none; + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn-primary { + background: var(--color-primary); + color: white; +} + +.btn-primary:hover { + background: var(--color-primary-light); + color: white; +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + background: var(--bg-tertiary); +} + +.btn-large { + padding: var(--space-4) var(--space-8); + font-size: var(--text-lg); +} + +/* Hero Section */ +.hero { + padding: var(--space-20) 0; + text-align: center; +} + +.hero h1 { + font-size: var(--text-5xl); + margin-bottom: var(--space-6); +} + +.hero-subtitle { + font-size: var(--text-xl); + color: var(--text-secondary); + max-width: 700px; + margin: 0 auto var(--space-8); +} + +.hero-buttons { + display: flex; + gap: var(--space-4); + justify-content: center; + flex-wrap: wrap; +} + +.hero-code { + max-width: 700px; + margin: var(--space-12) auto 0; + text-align: left; +} + +/* Feature Cards */ +.features { + padding: var(--space-16) 0; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-6); +} + +.feature-card { + padding: var(--space-6); + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + transition: all var(--transition-fast); +} + +.feature-card:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-md); +} + +.feature-icon { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--color-primary); + color: white; + border-radius: var(--radius-md); + margin-bottom: var(--space-4); + font-size: var(--text-xl); +} + +.feature-card h3 { + margin-bottom: var(--space-2); +} + +.feature-card p { + margin: 0; + color: var(--text-secondary); +} + +/* Section titles */ +.section-title { + text-align: center; + margin-bottom: var(--space-12); +} + +.section-title h2 { + margin-bottom: var(--space-4); +} + +.section-title p { + max-width: 600px; + margin: 0 auto; +} + +/* Code Comparison */ +.code-comparison { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-6); + margin: var(--space-8) 0; +} + +.code-block { + border-radius: var(--radius-lg); + overflow: hidden; +} + +.code-block-header { + padding: var(--space-3) var(--space-4); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + font-weight: 500; + font-size: var(--text-sm); +} + +/* Audience Tabs */ +.audience-section { + padding: var(--space-16) 0; + background: var(--bg-secondary); +} + +.audience-tabs { + display: flex; + justify-content: center; + gap: var(--space-2); + margin-bottom: var(--space-8); +} + +.audience-tab { + padding: var(--space-3) var(--space-6); + background: transparent; + border: 1px solid var(--border-color); + border-radius: var(--radius-full); + font-weight: 500; + cursor: pointer; + transition: all var(--transition-fast); +} + +.audience-tab:hover, +.audience-tab.active { + background: var(--color-primary); + border-color: var(--color-primary); + color: white; +} + +.audience-content { + display: none; + animation: fadeIn var(--transition-base); +} + +.audience-content.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Docs Layout */ +.docs-layout { + display: grid; + grid-template-columns: var(--sidebar-width) 1fr; + min-height: calc(100vh - var(--header-height)); +} + +.docs-sidebar { + position: sticky; + top: var(--header-height); + height: calc(100vh - var(--header-height)); + overflow-y: auto; + padding: var(--space-6); + background: var(--bg-secondary); + border-right: 1px solid var(--border-color); +} + +.sidebar-section { + margin-bottom: var(--space-6); +} + +.sidebar-section h4 { + font-size: var(--text-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-tertiary); + margin-bottom: var(--space-3); +} + +.sidebar-section ul { + list-style: none; + padding: 0; + margin: 0; +} + +.sidebar-section li { + margin: 0; +} + +.sidebar-section a { + display: block; + padding: var(--space-2) var(--space-3); + color: var(--text-secondary); + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.sidebar-section a:hover, +.sidebar-section a.active { + background: var(--bg-tertiary); + color: var(--color-primary); +} + +.docs-content { + padding: var(--space-8); +} + +.docs-content h1 { + margin-bottom: var(--space-6); +} + +.docs-content h2 { + margin-top: var(--space-10); + margin-bottom: var(--space-4); + padding-bottom: var(--space-2); + border-bottom: 1px solid var(--border-color); +} + +.docs-content h3 { + margin-top: var(--space-8); + margin-bottom: var(--space-3); +} + +/* Blog Layout */ +.blog-list { + padding: var(--space-12) 0; +} + +.blog-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: var(--space-6); +} + +.blog-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + overflow: hidden; + transition: all var(--transition-fast); +} + +.blog-card:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-md); +} + +.blog-card-content { + padding: var(--space-6); +} + +.blog-card-meta { + font-size: var(--text-sm); + color: var(--text-tertiary); + margin-bottom: var(--space-2); +} + +.blog-card h3 { + margin-bottom: var(--space-2); +} + +.blog-card h3 a { + color: var(--text-primary); +} + +.blog-card h3 a:hover { + color: var(--color-primary); +} + +.blog-post { + padding: var(--space-12) 0; +} + +.blog-post-header { + text-align: center; + margin-bottom: var(--space-10); +} + +.blog-post-meta { + color: var(--text-tertiary); + margin-bottom: var(--space-4); +} + +.blog-post-content { + max-width: 720px; + margin: 0 auto; +} + +/* Tags */ +.tag { + display: inline-block; + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); + font-weight: 500; + background: var(--color-primary); + color: white; + border-radius: var(--radius-full); +} + +.tag-secondary { + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +/* Tables */ +table { + width: 100%; + border-collapse: collapse; + margin-bottom: var(--space-6); +} + +th, td { + padding: var(--space-3) var(--space-4); + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +th { + font-weight: 600; + background: var(--bg-tertiary); +} + +/* Blockquotes */ +blockquote { + margin: var(--space-6) 0; + padding: var(--space-4) var(--space-6); + border-left: 4px solid var(--color-primary); + background: var(--bg-tertiary); + border-radius: 0 var(--radius-md) var(--radius-md) 0; +} + +blockquote p { + margin: 0; + color: var(--text-primary); +} + +/* Callouts */ +.callout { + padding: var(--space-4) var(--space-5); + border-radius: var(--radius-md); + margin-bottom: var(--space-4); + border-left: 4px solid; +} + +.callout-info { + background: rgba(59, 130, 246, 0.1); + border-color: var(--color-info); +} + +.callout-warning { + background: rgba(245, 158, 11, 0.1); + border-color: var(--color-warning); +} + +.callout-success { + background: rgba(16, 185, 129, 0.1); + border-color: var(--color-success); +} + +.callout-error { + background: rgba(239, 68, 68, 0.1); + border-color: var(--color-error); +} + +/* Footer */ +.footer { + padding: var(--space-16) 0 var(--space-8); + background: var(--bg-secondary); + border-top: 1px solid var(--border-color); +} + +.footer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-8); + margin-bottom: var(--space-8); +} + +.footer-section h3 { + font-size: var(--text-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: var(--space-4); +} + +.footer-section ul { + list-style: none; + padding: 0; + margin: 0; +} + +.footer-section li { + margin-bottom: var(--space-2); +} + +.footer-section a { + color: var(--text-secondary); + font-size: var(--text-sm); +} + +.footer-section a:hover { + color: var(--color-primary); +} + +.footer-bottom { + padding-top: var(--space-8); + border-top: 1px solid var(--border-color); + text-align: center; +} + +.footer-bottom p { + font-size: var(--text-sm); + color: var(--text-tertiary); + margin-bottom: var(--space-2); +} + +/* Syntax Highlighting (Prism theme compatible) */ +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: var(--color-gray-500); +} + +.token.punctuation { + color: var(--text-secondary); +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: var(--color-secondary); +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: var(--color-success); +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: var(--color-warning); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: var(--color-accent); +} + +.token.function, +.token.class-name { + color: var(--color-primary); +} + +.token.regex, +.token.important, +.token.variable { + color: var(--color-secondary); +} + +/* Responsive */ +@media (max-width: 1024px) { + .docs-layout { + grid-template-columns: 1fr; + } + + .docs-sidebar { + display: none; + position: fixed; + top: var(--header-height); + left: 0; + width: 100%; + height: calc(100vh - var(--header-height)); + z-index: 50; + } + + .docs-sidebar.open { + display: block; + } +} + +@media (max-width: 768px) { + :root { + --text-5xl: 2.25rem; + --text-4xl: 1.875rem; + --text-3xl: 1.5rem; + } + + .nav-links { + display: none; + position: fixed; + top: var(--header-height); + left: 0; + width: 100%; + padding: var(--space-4); + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + flex-direction: column; + gap: var(--space-2); + } + + .nav-links.open { + display: flex; + } + + .mobile-menu-toggle { + display: flex; + } + + .hero { + padding: var(--space-12) 0; + } + + .hero-buttons { + flex-direction: column; + align-items: center; + } + + .code-comparison { + grid-template-columns: 1fr; + } +} + +/* Utility Classes */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.mt-4 { margin-top: var(--space-4); } +.mt-8 { margin-top: var(--space-8); } +.mb-4 { margin-bottom: var(--space-4); } +.mb-8 { margin-bottom: var(--space-8); } + +.hidden { display: none; } +.block { display: block; } +.flex { display: flex; } +.grid { display: grid; } + +.gap-4 { gap: var(--space-4); } +.gap-6 { gap: var(--space-6); } + +.items-center { align-items: center; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } + +/* API Documentation Styles */ + +/* Code-like elements in API docs should be monospace */ +.docs-content .name, +.docs-content .signature, +.docs-content .type-annotation, +.docs-content .returntype, +.docs-content .parameter-name, +.docs-content .callable .name, +.docs-content .property .name, +.docs-content .kind-class, +.docs-content .kind-function, +.docs-content .kind-property, +.docs-content .kind-method, +.docs-content .kind-constructor, +.docs-content .clazz-relationships a, +.docs-content dl dd a, +.docs-content dl dd li { + font-family: var(--font-mono); +} + +/* Name styling */ +.docs-content .name { + font-weight: 600; + color: var(--color-primary); +} + +.docs-content .name a { + color: var(--color-primary); +} + +/* Signature styling */ +.docs-content .signature { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.docs-content .type-annotation, +.docs-content .returntype { + color: var(--color-accent); +} + +.docs-content .parameter-name { + color: var(--text-primary); +} + +/* Definition lists (properties, methods, constructors) */ +.docs-content dl { + margin: var(--space-4) 0; +} + +.docs-content dt { + padding: var(--space-3) 0; + font-weight: 600; + color: var(--text-primary); +} + +.docs-content dd { + padding: var(--space-2) 0 var(--space-3) var(--space-4); + color: var(--text-secondary); + margin: 0; +} + +/* Constructor/method/property definition items - these have .callable or .property class */ +.docs-content dt.callable, +.docs-content dt.property { + font-family: var(--font-mono); + border-bottom: 1px solid var(--border-color); +} + +.docs-content dt.callable:last-of-type, +.docs-content dt.property:last-of-type { + border-bottom: none; +} + +/* Features badges (no setter, inherited, etc.) */ +.docs-content .features { + font-family: var(--font-sans); + font-size: var(--text-xs); + color: var(--text-tertiary); + margin-top: var(--space-1); +} + +.docs-content .features .feature { + display: inline-block; + padding: 0.1em 0.5em; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + margin-right: var(--space-1); +} + +/* Code blocks */ +.docs-content pre { + font-family: var(--font-mono); + font-size: var(--text-sm); + background: var(--code-bg); + padding: var(--space-4); + border-radius: var(--radius-md); + overflow-x: auto; + margin: var(--space-4) 0; +} + +/* Inline code in descriptions */ +.docs-content .desc code { + font-family: var(--font-mono); + font-size: 0.9em; + background: var(--code-bg); + padding: 0.15em 0.4em; + border-radius: var(--radius-sm); +} + +/* Section headers in API docs */ +.docs-content section h2 { + margin-top: var(--space-8); + margin-bottom: var(--space-4); + padding-bottom: var(--space-2); + border-bottom: 1px solid var(--border-color); +} + +/* External documentation links */ +.external-docs { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: var(--space-4); + margin-bottom: var(--space-6); +} + +.external-docs h4 { + font-size: var(--text-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-tertiary); + margin-bottom: var(--space-2); +} + +.external-docs ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: var(--space-3); +} + +.external-docs li { + margin: 0; +} + +.external-docs a { + display: inline-block; + padding: var(--space-2) var(--space-3); + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + font-size: var(--text-sm); + color: var(--color-primary); + transition: all var(--transition-fast); +} + +.external-docs a:hover { + background: var(--color-primary); + color: white; + border-color: var(--color-primary); +} + +/* Mermaid diagrams */ +.mermaid { + background: var(--bg-tertiary); + padding: var(--space-4); + border-radius: var(--radius-lg); + margin: var(--space-4) 0; + text-align: center; +} + +/* Markdown section styling */ +.desc.markdown h1, +.desc.markdown h2 { + margin-top: var(--space-6); + margin-bottom: var(--space-3); +} + +.desc.markdown p { + margin-bottom: var(--space-3); +} + +.desc.markdown pre { + margin: var(--space-4) 0; +} + +/* Blog navigation */ +.blog-nav { + display: flex; + gap: var(--space-4); + margin-bottom: var(--space-8); + padding-bottom: var(--space-4); + border-bottom: 1px solid var(--border-color); +} + +.blog-nav-link { + padding: var(--space-2) var(--space-4); + background: var(--bg-tertiary); + border-radius: var(--radius-md); + font-weight: 500; + transition: all var(--transition-fast); +} + +.blog-nav-link:hover { + background: var(--color-primary); + color: white; +} + +/* Tag cloud */ +.tag-cloud { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); +} + +.tag-link { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-weight: 500; + transition: all var(--transition-fast); +} + +.tag-link:hover { + background: var(--color-primary); + color: white; + border-color: var(--color-primary); +} + +.tag-count { + font-size: var(--text-sm); + color: var(--text-tertiary); +} + +.tag-link:hover .tag-count { + color: rgba(255, 255, 255, 0.8); +} + +/* Category grid */ +.category-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: var(--space-6); +} + +.category-card { + display: block; + padding: var(--space-6); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + transition: all var(--transition-fast); +} + +.category-card:hover { + border-color: var(--color-primary); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.category-card h3 { + margin-bottom: var(--space-2); + color: var(--text-primary); +} + +.category-card p { + color: var(--text-secondary); + font-size: var(--text-sm); +} + +/* Tag styling - make clickable */ +.tag.tag-secondary { + text-decoration: none; + transition: all var(--transition-fast); +} + +a.tag.tag-secondary:hover { + background: var(--color-primary); + color: white; +} + +.tag-active { + background: var(--color-primary) !important; + color: white !important; +} diff --git a/website/src/assets/images/favicon.svg b/website/src/assets/images/favicon.svg new file mode 100644 index 0000000..ccd82ba --- /dev/null +++ b/website/src/assets/images/favicon.svg @@ -0,0 +1,4 @@ + + + dn + diff --git a/website/src/assets/js/main.js b/website/src/assets/js/main.js new file mode 100644 index 0000000..6964296 --- /dev/null +++ b/website/src/assets/js/main.js @@ -0,0 +1,192 @@ +/** + * dart_node website main JavaScript + * Dark mode toggle, mobile navigation, and utilities + */ + +(function() { + 'use strict'; + + // Theme toggle + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + + // Check for saved theme preference or system preference + const getPreferredTheme = () => { + const saved = localStorage.getItem('theme'); + if (saved) return saved; + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + }; + + // Apply theme + const setTheme = (theme) => { + html.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + }; + + // Initialize theme + setTheme(getPreferredTheme()); + + // Toggle theme on button click + if (themeToggle) { + themeToggle.addEventListener('click', () => { + const current = html.getAttribute('data-theme'); + setTheme(current === 'dark' ? 'light' : 'dark'); + }); + } + + // Listen for system theme changes + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + if (!localStorage.getItem('theme')) { + setTheme(e.matches ? 'dark' : 'light'); + } + }); + + // Mobile menu toggle + const mobileMenuToggle = document.getElementById('mobile-menu-toggle'); + const navLinks = document.querySelector('.nav-links'); + + if (mobileMenuToggle && navLinks) { + mobileMenuToggle.addEventListener('click', () => { + navLinks.classList.toggle('open'); + mobileMenuToggle.classList.toggle('active'); + }); + + // Close menu when clicking outside + document.addEventListener('click', (e) => { + if (!navLinks.contains(e.target) && !mobileMenuToggle.contains(e.target)) { + navLinks.classList.remove('open'); + mobileMenuToggle.classList.remove('active'); + } + }); + } + + // Docs sidebar toggle (mobile) + const docsSidebar = document.getElementById('docs-sidebar'); + if (docsSidebar) { + // Create toggle button for mobile + const sidebarToggle = document.createElement('button'); + sidebarToggle.className = 'sidebar-toggle'; + sidebarToggle.innerHTML = 'Menu'; + sidebarToggle.style.cssText = ` + display: none; + position: fixed; + bottom: var(--space-4); + right: var(--space-4); + padding: var(--space-3) var(--space-6); + background: var(--color-primary); + color: white; + border: none; + border-radius: var(--radius-full); + font-weight: 500; + cursor: pointer; + z-index: 60; + box-shadow: var(--shadow-lg); + `; + + document.body.appendChild(sidebarToggle); + + // Show toggle on mobile + const checkMobile = () => { + sidebarToggle.style.display = window.innerWidth <= 1024 ? 'block' : 'none'; + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + + sidebarToggle.addEventListener('click', () => { + docsSidebar.classList.toggle('open'); + sidebarToggle.innerHTML = docsSidebar.classList.contains('open') ? 'Close' : 'Menu'; + }); + } + + // Smooth scroll for anchor links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function(e) { + const target = document.querySelector(this.getAttribute('href')); + if (target) { + e.preventDefault(); + target.scrollIntoView({ behavior: 'smooth' }); + } + }); + }); + + // Add copy button to code blocks + document.querySelectorAll('pre').forEach(pre => { + const wrapper = document.createElement('div'); + wrapper.style.position = 'relative'; + pre.parentNode.insertBefore(wrapper, pre); + wrapper.appendChild(pre); + + const copyBtn = document.createElement('button'); + copyBtn.className = 'copy-btn'; + copyBtn.innerHTML = 'Copy'; + copyBtn.style.cssText = ` + position: absolute; + top: var(--space-2); + right: var(--space-2); + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + cursor: pointer; + opacity: 0; + transition: opacity var(--transition-fast); + `; + + wrapper.appendChild(copyBtn); + + wrapper.addEventListener('mouseenter', () => { + copyBtn.style.opacity = '1'; + }); + + wrapper.addEventListener('mouseleave', () => { + copyBtn.style.opacity = '0'; + }); + + copyBtn.addEventListener('click', async () => { + const code = pre.querySelector('code'); + const text = code ? code.textContent : pre.textContent; + + try { + await navigator.clipboard.writeText(text); + copyBtn.innerHTML = 'Copied!'; + setTimeout(() => { + copyBtn.innerHTML = 'Copy'; + }, 2000); + } catch (err) { + copyBtn.innerHTML = 'Failed'; + } + }); + }); + + // Add heading anchors for docs + document.querySelectorAll('.docs-content h2, .docs-content h3, .blog-post-content h2, .blog-post-content h3').forEach(heading => { + if (heading.id) { + const anchor = document.createElement('a'); + anchor.href = `#${heading.id}`; + anchor.className = 'heading-anchor'; + anchor.innerHTML = '#'; + anchor.style.cssText = ` + margin-left: var(--space-2); + color: var(--text-tertiary); + text-decoration: none; + opacity: 0; + transition: opacity var(--transition-fast); + `; + + heading.style.position = 'relative'; + heading.appendChild(anchor); + + heading.addEventListener('mouseenter', () => { + anchor.style.opacity = '1'; + }); + + heading.addEventListener('mouseleave', () => { + anchor.style.opacity = '0'; + }); + } + }); + +})(); diff --git a/website/src/blog/categories-pages.njk b/website/src/blog/categories-pages.njk new file mode 100644 index 0000000..8317003 --- /dev/null +++ b/website/src/blog/categories-pages.njk @@ -0,0 +1,44 @@ +--- +pagination: + data: collections.categoryList + size: 1 + alias: category +permalink: /blog/categories/{{ category | slugify }}/ +layout: layouts/base.njk +eleventyComputed: + title: "{{ category | capitalize }}" + description: "All blog posts in the {{ category }} category." +--- + +
+
+
+

{{ category | capitalize }}

+

← All categories

+
+ +
+ {% for post in collections.postsByCategory[category] %} +
+
+

+ + {% if post.data.author %} · {{ post.data.author }}{% endif %} +

+

{{ post.data.title }}

+

{{ post.data.description }}

+ {% if post.data.tags %} +
+ {% for tag in post.data.tags %} + {% if tag != 'post' and tag != 'posts' %} + {{ tag }} + {% endif %} + {% endfor %} +
+ {% endif %} +
+
+ {% endfor %} +
+
+
diff --git a/website/src/blog/categories.njk b/website/src/blog/categories.njk new file mode 100644 index 0000000..bbb0125 --- /dev/null +++ b/website/src/blog/categories.njk @@ -0,0 +1,30 @@ +--- +layout: layouts/base.njk +title: Categories +description: Browse blog posts by category. +permalink: /blog/categories/ +--- + +
+
+
+

Categories

+

Browse blog posts by category.

+
+ + + + {% if collections.categoryList.length == 0 %} +
+

No categories yet. Check back soon!

+
+ {% endif %} +
+
diff --git a/website/src/blog/index.njk b/website/src/blog/index.njk new file mode 100644 index 0000000..4dcfa0a --- /dev/null +++ b/website/src/blog/index.njk @@ -0,0 +1,50 @@ +--- +layout: layouts/base.njk +title: Blog +description: Latest news, tutorials, and updates from the dart_node team. +--- + +
+
+
+

Blog

+

Latest news, tutorials, and updates from the dart_node team.

+
+ + + +
+ {% for post in collections.posts %} +
+
+

+ + {% if post.data.author %} · {{ post.data.author }}{% endif %} + {% if post.data.category %} · {{ post.data.category | capitalize }}{% endif %} +

+

{{ post.data.title }}

+

{{ post.data.description }}

+ {% if post.data.tags %} +
+ {% for tag in post.data.tags %} + {% if tag != 'post' and tag != 'posts' %} + {{ tag }} + {% endif %} + {% endfor %} +
+ {% endif %} +
+
+ {% endfor %} +
+ + {% if collections.posts.length == 0 %} +
+

No blog posts yet. Check back soon!

+
+ {% endif %} +
+
diff --git a/website/src/blog/introducing-dart-node.md b/website/src/blog/introducing-dart-node.md new file mode 100644 index 0000000..332ff77 --- /dev/null +++ b/website/src/blog/introducing-dart-node.md @@ -0,0 +1,200 @@ +--- +layout: layouts/blog.njk +title: "Introducing dart_node: Full-Stack Dart for the JavaScript Ecosystem" +description: "We're excited to announce dart_node, a framework for building React, React Native, and Express applications entirely in Dart." +date: 2024-01-15 +author: "dart_node team" +category: announcements +tags: + - announcement + - dart + - react + - express +--- + +Today we're excited to introduce **dart_node**, a framework that lets you build React, React Native, and Express applications entirely in Dart. + +## Why dart_node? + +If you're a **React developer**, you've probably wished TypeScript's types existed at runtime. You've probably fought with complex webpack configs. You've probably wondered why you need three different config files just to get a project started. + +If you're a **Flutter developer**, you've probably wished you could use your Dart skills in the web ecosystem. You've probably wanted access to React's massive component library. You've probably wanted to share code between your Flutter app and a React Native version. + +dart_node is for both of you. + +## What Makes Dart Different + +Dart and TypeScript both add type safety to dynamic languages. But they made different design choices: + +**TypeScript chose maximum JavaScript compatibility.** This was brilliant - it meant instant access to the npm ecosystem and gradual adoption in existing codebases. But it came with a cost: types are erased at compile time. + +**Dart chose maximum type safety.** Types exist at runtime. Null safety is sound. Generics aren't erased. When you serialize an object, you can validate its structure. When you deserialize it, you know what you're getting. + +Here's a concrete example: + +```typescript +// TypeScript +interface User { + name: string; + age: number; +} + +const user: User = JSON.parse(apiResponse); +// Is user actually a User? We hope so! +console.log(user.name.toUpperCase()); +// Runtime crash if name is undefined +``` + +```dart +// Dart +class User { + final String name; + final int age; + + User({required this.name, required this.age}); + + factory User.fromJson(Map json) => User( + name: json['name'] as String, // Validated! + age: json['age'] as int, // Validated! + ); +} + +final user = User.fromJson(jsonDecode(apiResponse)); +// If we get here, user.name is definitely a String +print(user.name.toUpperCase()); // Safe! +``` + +This isn't about TypeScript being bad - it's about different trade-offs for different needs. + +## The dart_node Stack + +We've built five packages that give you full-stack capabilities: + +### dart_node_core + +The foundation layer. Provides JavaScript interop utilities, Node.js bindings, and the glue that makes everything work together. + +### dart_node_express + +Type-safe Express.js bindings. Build REST APIs with the same patterns you know from Express, but with Dart's type safety. + +```dart +final app = createExpressApp(); + +app.get('/users/:id', (req, res) { + final id = req.params['id']; + res.json({'user': {'id': id}}); +}); + +app.listen(3000); +``` + +### dart_node_react + +React bindings with hooks, components, and JSX-like syntax. Everything you love about React, in Dart. + +```dart +ReactElement counter() { + final (count, setCount) = useState(0); + + return button( + onClick: (_) => setCount((c) => c + 1), + children: [text('Count: $count')], + ); +} +``` + +### dart_node_react_native + +React Native bindings for mobile development. Use with Expo for a complete mobile development experience. + +```dart +ReactElement app() { + return safeAreaView(children: [ + view(style: {'padding': 20}, children: [ + rnText(children: [text('Hello from Dart!')]), + ]), + ]); +} +``` + +### dart_node_ws + +WebSocket bindings for real-time communication. Build chat apps, dashboards, and more. + +```dart +final server = createWebSocketServer(port: 8080); + +server.on('connection', (client) { + client.on('message', (data) { + client.send('Echo: $data'); + }); +}); +``` + +## Getting Started + +Getting started is straightforward: + +```bash +mkdir my_app && cd my_app +dart create -t package . +dart pub add dart_node_core dart_node_express +``` + +Write your server: + +```dart +import 'package:dart_node_express/dart_node_express.dart'; + +void main() { + final app = createExpressApp(); + + app.get('/', (req, res) { + res.json({'message': 'Hello from Dart!'}); + }); + + app.listen(3000, () { + print('Server running on port 3000'); + }); +} +``` + +Compile and run: + +```bash +dart compile js lib/server.dart -o build/server.js +node build/server.js +``` + +That's it. No webpack. No babel. No complex configuration. + +## Who Is This For? + +**React developers** who want better type safety without losing the React patterns they know. + +**Flutter developers** who want to use their Dart skills in the JavaScript ecosystem. + +**Full-stack developers** who want to share code between frontend, backend, and mobile. + +**Anyone** who's tired of maintaining three different codebases in three different languages. + +## What's Next + +This is just the beginning. We're working on: + +- More React hooks and component bindings +- Navigation libraries for React Native +- State management solutions +- Build tooling improvements +- More documentation and examples + +## Try It Out + +Check out the [Getting Started guide](/docs/getting-started/) to build your first dart_node application. Browse the [API documentation](/api/) to see what's available. And if you have questions, open an issue on GitHub. + +We can't wait to see what you build. + +--- + +*dart_node is open source and MIT licensed. Contributions are welcome!* diff --git a/website/src/blog/tags-pages.njk b/website/src/blog/tags-pages.njk new file mode 100644 index 0000000..a3cad3f --- /dev/null +++ b/website/src/blog/tags-pages.njk @@ -0,0 +1,44 @@ +--- +pagination: + data: collections.tagList + size: 1 + alias: tag +permalink: /blog/tags/{{ tag | slugify }}/ +layout: layouts/base.njk +eleventyComputed: + title: "Posts tagged '{{ tag }}'" + description: "All blog posts tagged with {{ tag }}." +--- + +
+
+
+

Posts tagged "{{ tag }}"

+

← All tags

+
+ +
+ {% for post in collections.postsByTag[tag] %} +
+
+

+ + {% if post.data.author %} · {{ post.data.author }}{% endif %} +

+

{{ post.data.title }}

+

{{ post.data.description }}

+ {% if post.data.tags %} +
+ {% for t in post.data.tags %} + {% if t != 'post' and t != 'posts' %} + {{ t }} + {% endif %} + {% endfor %} +
+ {% endif %} +
+
+ {% endfor %} +
+
+
diff --git a/website/src/blog/tags.njk b/website/src/blog/tags.njk new file mode 100644 index 0000000..11280f6 --- /dev/null +++ b/website/src/blog/tags.njk @@ -0,0 +1,30 @@ +--- +layout: layouts/base.njk +title: Tags +description: Browse blog posts by tag. +permalink: /blog/tags/ +--- + +
+
+
+

Tags

+

Browse blog posts by tag.

+
+ +
+ {% for tag in collections.tagList %} + + {{ tag }} + ({{ collections.postsByTag[tag].length }}) + + {% endfor %} +
+ + {% if collections.tagList.length == 0 %} +
+

No tags yet. Check back soon!

+
+ {% endif %} +
+
diff --git a/website/src/docs/core/index.md b/website/src/docs/core/index.md new file mode 100644 index 0000000..70b204e --- /dev/null +++ b/website/src/docs/core/index.md @@ -0,0 +1,85 @@ +--- +layout: layouts/docs.njk +title: dart_node_core +description: Core JS interop utilities and foundation for all dart_node packages. +eleventyNavigation: + key: dart_node_core + parent: Packages + order: 1 +--- + +`dart_node_core` is the foundation layer that all other dart_node packages build upon. It provides low-level JavaScript interop utilities, Node.js bindings, and console helpers. + +## Installation + +```yaml +dependencies: + dart_node_core: ^0.2.0 +``` + +## Core Utilities + +### Console Logging + +```dart +import 'package:dart_node_core/dart_node_core.dart'; + +void main() { + consoleLog('Hello, world!'); + consoleError('Something went wrong'); + consoleWarn('This is a warning'); +} +``` + +### Requiring Node.js Modules + +```dart +import 'package:dart_node_core/dart_node_core.dart'; + +void main() { + // Load a Node.js built-in module + final fs = require('fs'); + + // Load an npm package + final express = require('express'); +} +``` + +### Accessing Global Objects + +```dart +import 'package:dart_node_core/dart_node_core.dart'; + +void main() { + // Access global JavaScript objects + final global = getGlobal('process'); + final env = global['env']; +} +``` + +## Interop Helpers + +### Converting Between Dart and JavaScript + +```dart +import 'package:dart_node_core/dart_node_core.dart'; + +void main() { + // Dart to JS + final jsString = 'hello'.toJS; + final jsNumber = 42.toJS; + final jsList = [1, 2, 3].toJS; + + // JS to Dart + final dartString = jsString.toDart; + final dartList = jsList.toDart; +} +``` + +## API Reference + +See the [full API documentation](/api/dart_node_core/) for all available functions and types. + +## Source Code + +The source code is available on [GitHub](https://github.com/melbournedeveloper/dart_node/tree/main/packages/dart_node_core). diff --git a/website/src/docs/dart-to-js.md b/website/src/docs/dart-to-js.md new file mode 100644 index 0000000..4edf729 --- /dev/null +++ b/website/src/docs/dart-to-js.md @@ -0,0 +1,193 @@ +--- +layout: layouts/docs.njk +title: Dart to JavaScript Compilation +description: Learn how dart2js compiles Dart code to JavaScript for Node.js and browser environments. +eleventyNavigation: + key: Dart to JS + order: 3 +--- + +Dart can compile to JavaScript using `dart compile js` (also known as dart2js). This guide explains how it works and how to use it with dart_node. + +## How It Works + +The Dart compiler performs several transformations: + +1. **Type checking** - Verifies your code is type-safe +2. **Tree shaking** - Removes unused code +3. **Minification** - Reduces output size (in production mode) +4. **Optimization** - Inlines functions, constant folding, etc. + +The result is efficient JavaScript that runs anywhere JS runs. + +## Basic Usage + +```bash +# Compile a Dart file to JavaScript +dart compile js lib/main.dart -o build/main.js + +# With optimizations for production +dart compile js lib/main.dart -o build/main.js -O2 +``` + +## Optimization Levels + +| Level | Description | Use Case | +|-------|-------------|----------| +| `-O0` | No optimization | Debugging | +| `-O1` | Basic optimization | Development | +| `-O2` | Full optimization (default) | Production | +| `-O3` | Aggressive optimization | Maximum performance | +| `-O4` | Most aggressive | When size/speed is critical | + +## Node.js Compatibility + +Standard dart2js output is designed for browsers. For Node.js, you need to add a preamble. The `node_preamble` package handles this: + +```dart +// In your build script +import 'package:node_preamble/preamble.dart' as preamble; + +void main() { + final dartOutput = File('build/app.dart.js').readAsStringSync(); + final nodeCompatible = '${preamble.getPreamble()}\n$dartOutput'; + File('build/app.js').writeAsStringSync(nodeCompatible); +} +``` + +Or use our build tool (recommended): + +```bash +dart run tools/build/build.dart my_app +``` + +## Output Structure + +A compiled Dart application produces: + +``` +build/ +├── main.js # Main JavaScript output +├── main.js.deps # Dependency information +└── main.js.map # Source maps (for debugging) +``` + +## Source Maps + +Source maps let you debug Dart code in JavaScript environments: + +```bash +# Generate with source maps (default) +dart compile js lib/main.dart -o build/main.js + +# Disable source maps +dart compile js lib/main.dart -o build/main.js --no-source-maps +``` + +In Node.js, enable source map support: + +```bash +node --enable-source-maps build/main.js +``` + +## Deferred Loading + +Split your app into multiple chunks for faster initial load: + +```dart +import 'heavy_feature.dart' deferred as heavy; + +Future loadFeature() async { + await heavy.loadLibrary(); + heavy.runFeature(); +} +``` + +This creates separate `.part.js` files loaded on demand. + +## Interacting with JavaScript + +Dart can call JavaScript and vice versa. See the [JS Interop guide](/docs/js-interop/) for details. + +## Common Issues + +### "Cannot find dart:html" + +This happens when using browser-only libraries in Node.js. Solution: use `dart:js_interop` instead of `dart:html`. + +### Large Output Size + +The output can be large for small apps because Dart includes its runtime. For production: + +```bash +dart compile js lib/main.dart -o build/main.js -O4 +``` + +### Async/Await Issues + +Dart's async/await compiles to JavaScript promises. Ensure your Node.js version supports them (Node 8+). + +## Build Script Example + +Here's a complete build script for a dart_node project: + +```dart +// tools/build.dart +import 'dart:io'; +import 'package:node_preamble/preamble.dart' as preamble; + +Future main(List args) async { + final target = args.isNotEmpty ? args[0] : 'server'; + final inputFile = 'lib/$target.dart'; + final outputFile = 'build/$target.js'; + + print('Compiling $inputFile...'); + + // Run dart compile js + final result = await Process.run('dart', [ + 'compile', 'js', + inputFile, + '-o', '$outputFile.tmp', + '-O2', + ]); + + if (result.exitCode != 0) { + print('Compilation failed:'); + print(result.stderr); + exit(1); + } + + // Add Node.js preamble + final dartOutput = File('$outputFile.tmp').readAsStringSync(); + final nodeOutput = '${preamble.getPreamble()}\n$dartOutput'; + File(outputFile).writeAsStringSync(nodeOutput); + + // Cleanup + File('$outputFile.tmp').deleteSync(); + + print('Output: $outputFile'); + print('Run with: node $outputFile'); +} +``` + +## Performance Tips + +1. **Use `-O2` or higher for production** - Significant size and speed improvements + +2. **Enable tree shaking** - Ensure you're not importing unused code + +3. **Avoid `dynamic`** - The compiler can't optimize dynamic calls + +4. **Prefer `const`** - Constant values are evaluated at compile time + +5. **Profile your output** - Check the `.js.info` file for size breakdown: + +```bash +dart compile js lib/main.dart -o build/main.js --dump-info +``` + +## Next Steps + +- [JS Interop](/docs/js-interop/) - Call JavaScript from Dart +- [dart_node_core](/docs/core/) - Core utilities for Node.js +- [dart_node_express](/docs/express/) - Build Express servers diff --git a/website/src/docs/express/index.md b/website/src/docs/express/index.md new file mode 100644 index 0000000..dfd24b9 --- /dev/null +++ b/website/src/docs/express/index.md @@ -0,0 +1,303 @@ +--- +layout: layouts/docs.njk +title: dart_node_express +description: Type-safe Express.js bindings for building HTTP servers and REST APIs in Dart. +eleventyNavigation: + key: dart_node_express + parent: Packages + order: 2 +--- + +`dart_node_express` provides type-safe bindings for Express.js, letting you build HTTP servers and REST APIs entirely in Dart. + +## Installation + +```yaml +dependencies: + dart_node_express: ^0.2.0 +``` + +Also install Express via npm: + +```bash +npm install express +``` + +## Quick Start + +```dart +import 'package:dart_node_express/dart_node_express.dart'; + +void main() { + final app = createExpressApp(); + + app.get('/', (req, res) { + res.send('Hello, Dart!'); + }); + + app.listen(3000, () { + print('Server running on port 3000'); + }); +} +``` + +## Routing + +### Basic Routes + +```dart +app.get('/users', (req, res) { + res.json({'users': []}); +}); + +app.post('/users', (req, res) { + final body = req.body; + res.status(201).json({'created': true}); +}); + +app.put('/users/:id', (req, res) { + final id = req.params['id']; + res.json({'updated': id}); +}); + +app.delete('/users/:id', (req, res) { + res.status(204).end(); +}); +``` + +### Route Parameters + +```dart +app.get('/users/:userId/posts/:postId', (req, res) { + final userId = req.params['userId']; + final postId = req.params['postId']; + + res.json({ + 'userId': userId, + 'postId': postId, + }); +}); +``` + +### Query Parameters + +```dart +app.get('/search', (req, res) { + final query = req.query['q']; + final page = int.tryParse(req.query['page'] ?? '1') ?? 1; + + res.json({ + 'query': query, + 'page': page, + }); +}); +``` + +## Request Object + +The `Request` object provides access to incoming request data: + +```dart +app.post('/api/data', (req, res) { + // Request body (requires body-parsing middleware) + final body = req.body; + + // Headers + final contentType = req.headers['content-type']; + + // URL path + final path = req.path; + + // HTTP method + final method = req.method; + + // Query string parameters + final params = req.query; + + res.json({'received': body}); +}); +``` + +## Response Object + +The `Response` object provides methods for sending responses: + +```dart +// Send text +res.send('Hello!'); + +// Send JSON +res.json({'message': 'Hello!'}); + +// Set status code +res.status(201).json({'created': true}); + +// Set headers +res.setHeader('X-Custom-Header', 'value'); + +// Redirect +res.redirect('/new-location'); + +// End response without body +res.status(204).end(); +``` + +## Middleware + +### Built-in Middleware + +```dart +// JSON body parsing +app.use(jsonMiddleware()); + +// URL-encoded body parsing +app.use(urlencodedMiddleware(extended: true)); + +// Static files +app.use(staticMiddleware('public')); + +// CORS +app.use(corsMiddleware()); +``` + +### Custom Middleware + +```dart +void loggingMiddleware(Request req, Response res, NextFunction next) { + print('${req.method} ${req.path}'); + next(); +} + +app.use(loggingMiddleware); +``` + +### Error Handling Middleware + +```dart +void errorHandler(dynamic error, Request req, Response res, NextFunction next) { + print('Error: $error'); + res.status(500).json({'error': 'Internal Server Error'}); +} + +// Error handlers have 4 parameters +app.use(errorHandler); +``` + +## Router + +Organize routes with the Router: + +```dart +Router createUserRouter() { + final router = createRouter(); + + router.get('/', (req, res) { + res.json({'users': []}); + }); + + router.post('/', (req, res) { + res.status(201).json({'created': true}); + }); + + router.get('/:id', (req, res) { + res.json({'user': req.params['id']}); + }); + + return router; +} + +void main() { + final app = createExpressApp(); + + // Mount the router + app.use('/api/users', createUserRouter()); + + app.listen(3000); +} +``` + +## Async Handlers + +Use async handlers for database calls and other async operations: + +```dart +app.get('/users', asyncHandler((req, res) async { + final users = await database.fetchUsers(); + res.json({'users': users}); +})); +``` + +The `asyncHandler` wrapper ensures errors are properly caught and passed to error middleware. + +## Validation + +Validate request data: + +```dart +app.post('/users', (req, res) { + final body = req.body; + + // Validate required fields + final validation = validateRequired(body, ['name', 'email']); + + if (validation.isErr) { + return res.status(400).json({ + 'error': 'Validation failed', + 'details': validation.err, + }); + } + + // Create user... + res.status(201).json({'created': true}); +}); +``` + +## Complete Example + +```dart +import 'package:dart_node_express/dart_node_express.dart'; + +void main() { + final app = createExpressApp(); + + // Middleware + app.use(jsonMiddleware()); + app.use(corsMiddleware()); + + // Logging + app.use((req, res, next) { + print('[${DateTime.now()}] ${req.method} ${req.path}'); + next(); + }); + + // Routes + app.get('/', (req, res) { + res.json({ + 'name': 'My API', + 'version': '1.0.0', + }); + }); + + app.get('/health', (req, res) { + res.json({'status': 'ok'}); + }); + + app.use('/api/users', createUserRouter()); + + // Error handler + app.use((error, req, res, next) { + print('Error: $error'); + res.status(500).json({'error': 'Something went wrong'}); + }); + + // Start server + final port = int.tryParse(Platform.environment['PORT'] ?? '3000') ?? 3000; + app.listen(port, () { + print('Server running on port $port'); + }); +} +``` + +## API Reference + +See the [full API documentation](/api/dart_node_express/) for all available functions and types. diff --git a/website/src/docs/getting-started.md b/website/src/docs/getting-started.md new file mode 100644 index 0000000..a322cf6 --- /dev/null +++ b/website/src/docs/getting-started.md @@ -0,0 +1,144 @@ +--- +layout: layouts/docs.njk +title: Getting Started +description: Get up and running with dart_node in minutes. Build Express servers, React apps, and React Native mobile apps - all in Dart. +eleventyNavigation: + key: Getting Started + order: 1 +--- + +Welcome to dart_node! This guide will help you build your first application using Dart for the JavaScript ecosystem. + +## Prerequisites + +Before you begin, make sure you have: + +- **Dart SDK** (3.0 or higher) - [Install Dart](https://dart.dev/get-dart) +- **Node.js** (18 or higher) - [Install Node.js](https://nodejs.org/) +- A code editor (VS Code with Dart extension recommended) + +## Quick Start: Express Server + +Let's build a simple REST API server in Dart. + +### 1. Create a new project + +```bash +mkdir my_dart_server +cd my_dart_server +dart create -t package . +``` + +### 2. Add dependencies + +Edit your `pubspec.yaml`: + +```yaml +name: my_dart_server +environment: + sdk: ^3.0.0 + +dependencies: + dart_node_core: ^0.2.0 + dart_node_express: ^0.2.0 +``` + +Then run: + +```bash +dart pub get +``` + +### 3. Write your server + +Create `lib/server.dart`: + +```dart +import 'package:dart_node_express/dart_node_express.dart'; + +void main() { + final app = createExpressApp(); + + // Simple GET endpoint + app.get('/', (req, res) { + res.json({ + 'message': 'Hello from Dart!', + 'timestamp': DateTime.now().toIso8601String(), + }); + }); + + // POST endpoint with body parsing + app.use(jsonMiddleware()); + + app.post('/users', (req, res) { + final body = req.body; + res.status(201).json({ + 'created': true, + 'user': body, + }); + }); + + // Start the server + app.listen(3000, () { + print('Server running at http://localhost:3000'); + }); +} +``` + +### 4. Compile and run + +```bash +# Compile Dart to JavaScript +dart compile js lib/server.dart -o build/server.js + +# Run with Node.js +node build/server.js +``` + +Visit `http://localhost:3000` to see your server in action! + +## Project Structure + +A typical dart_node project looks like this: + +``` +my_project/ +├── lib/ +│ ├── server.dart # Entry point +│ ├── routes/ # Route handlers +│ ├── models/ # Data models +│ └── services/ # Business logic +├── build/ # Compiled JS output +├── pubspec.yaml # Dart dependencies +├── package.json # Node dependencies (for npm packages) +└── README.md +``` + +## Using npm Packages + +Some dart_node packages wrap npm modules (like Express). You'll need to install these: + +```bash +npm init -y +npm install express +``` + +The Dart code uses JS interop to call these npm packages at runtime. + +## Next Steps + +Now that you have a basic server running, explore: + +- [Why Dart?](/docs/why-dart/) - Understand the benefits over TypeScript +- [Dart-to-JS Compilation](/docs/dart-to-js/) - How dart2js works +- [JS Interop](/docs/js-interop/) - Calling JavaScript from Dart +- [dart_node_express](/docs/express/) - Full Express.js API reference + +## Example Projects + +Check out the [examples directory](https://github.com/melbournedeveloper/dart_node/tree/main/examples) for complete working applications: + +- **backend/** - Express server with REST API +- **frontend/** - React web application +- **mobile/** - React Native + Expo mobile app +- **shared/** - Shared models across platforms diff --git a/website/src/docs/js-interop.md b/website/src/docs/js-interop.md new file mode 100644 index 0000000..d914f67 --- /dev/null +++ b/website/src/docs/js-interop.md @@ -0,0 +1,321 @@ +--- +layout: layouts/docs.njk +title: JavaScript Interop +description: Learn how to call JavaScript from Dart and vice versa using dart:js_interop. +eleventyNavigation: + key: JS Interop + order: 4 +--- + +Dart 3.3+ provides `dart:js_interop` for seamless interaction with JavaScript. This is how dart_node wraps npm packages like Express and React. + +## The Basics + +### Importing dart:js_interop + +```dart +import 'dart:js_interop'; +``` + +This gives you access to: +- Extension types for JavaScript objects +- Conversion utilities between Dart and JS +- The `external` keyword for JS bindings + +## Calling JavaScript Functions + +### Global Functions + +```dart +import 'dart:js_interop'; + +// Declare the external function +@JS('console.log') +external void consoleLog(JSAny? message); + +// Use it +void main() { + consoleLog('Hello from Dart!'.toJS); +} +``` + +### Importing npm Modules + +```dart +import 'dart:js_interop'; + +// Require a Node.js module +@JS('require') +external JSObject require(String module); + +void main() { + final express = require('express'); + // Now you have the express module! +} +``` + +## Extension Types + +Extension types provide zero-cost wrappers around JavaScript objects. They're the foundation of dart_node's typed APIs. + +```dart +import 'dart:js_interop'; + +// Define an extension type for a JS object +extension type JSPerson._(JSObject _) implements JSObject { + // Constructor + external factory JSPerson({String name, int age}); + + // Properties + external String get name; + external set name(String value); + external int get age; + + // Methods + external void greet(); +} + +void main() { + final person = JSPerson(name: 'Alice', age: 30); + print(person.name); // Access JS property + person.greet(); // Call JS method +} +``` + +## Type Conversions + +### Dart to JavaScript + +```dart +// Primitives +final jsString = 'hello'.toJS; // JSString +final jsNumber = 42.toJS; // JSNumber +final jsBool = true.toJS; // JSBoolean + +// Lists +final jsList = [1, 2, 3].toJS; // JSArray + +// Maps (as plain JS objects) +final jsObject = {'key': 'value'}.jsify(); // JSObject +``` + +### JavaScript to Dart + +```dart +// Primitives +final dartString = jsString.toDart; // String +final dartNumber = jsNumber.toDartInt; // int +final dartBool = jsBool.toDart; // bool + +// Arrays +final dartList = jsList.toDart; // List + +// Objects (as Map) +final dartMap = jsObject.dartify(); // Map +``` + +## Working with Callbacks + +JavaScript often uses callbacks. Here's how to handle them: + +```dart +extension type EventEmitter._(JSObject _) implements JSObject { + external void on(String event, JSFunction callback); + external void emit(String event, JSAny? data); +} + +void main() { + final emitter = getEventEmitter(); + + // Convert a Dart function to JS + emitter.on('data', ((JSAny? data) { + print('Received: ${data?.dartify()}'); + }).toJS); +} +``` + +## Promises and Futures + +JavaScript Promises convert to Dart Futures: + +```dart +extension type FetchAPI._(JSObject _) implements JSObject { + external JSPromise fetch(String url); +} + +Future main() async { + final api = getFetchAPI(); + + // JSPromise converts to Future automatically + final response = await api.fetch('https://api.example.com/data').toDart; + print(response.status); +} +``` + +## How dart_node Uses Interop + +Here's a simplified example of how dart_node wraps Express: + +```dart +// Low-level JS binding +@JS('require') +external JSObject _require(String module); + +// Extension type for Express app +extension type ExpressApp._(JSObject _) implements JSObject { + external void get(String path, JSFunction handler); + external void post(String path, JSFunction handler); + external void listen(int port, JSFunction? callback); +} + +// High-level Dart API +ExpressApp createExpressApp() { + final express = _require('express'); + return (express as JSFunction).callAsFunction() as ExpressApp; +} + +// Typed request handler +typedef RequestHandler = void Function(Request req, Response res); + +// Convert Dart handler to JS +JSFunction wrapHandler(RequestHandler handler) { + return ((JSObject req, JSObject res) { + handler(Request._(req), Response._(res)); + }).toJS; +} + +// Usage +void main() { + final app = createExpressApp(); + + app.get('/'.toJS, wrapHandler((req, res) { + res.send('Hello!'); + })); + + app.listen(3000, null); +} +``` + +## Best Practices + +### 1. Hide JSObject from Public APIs + +```dart +// Bad: Exposes raw JS types +class MyService { + JSObject getData() => fetchData(); +} + +// Good: Returns Dart types +class MyService { + Map getData() => fetchData().dartify(); +} +``` + +### 2. Use Extension Types for Type Safety + +```dart +// Bad: Passing around raw JSObject +void processUser(JSObject user) { + // What properties does user have? Who knows! +} + +// Good: Typed extension type +void processUser(JSUser user) { + print(user.name); // Compiler knows this exists +} +``` + +### 3. Handle Null Carefully + +JavaScript's `null` and `undefined` are both valid. Use `JSAny?`: + +```dart +extension type Config._(JSObject _) implements JSObject { + external JSAny? get optionalValue; +} + +void main() { + final config = getConfig(); + + // Check for null/undefined + final value = config.optionalValue; + if (value != null) { + print(value.dartify()); + } +} +``` + +### 4. Validate at Boundaries + +Validate JavaScript data when it enters your Dart code: + +```dart +class User { + final String name; + final int age; + + User({required this.name, required this.age}); + + factory User.fromJS(JSObject obj) { + final name = (obj['name'] as JSString?)?.toDart; + final age = (obj['age'] as JSNumber?)?.toDartInt; + + if (name == null || age == null) { + throw FormatException('Invalid user object'); + } + + return User(name: name, age: age); + } +} +``` + +## Common Patterns + +### Wrapping a Constructor + +```dart +@JS('Date') +extension type JSDate._(JSObject _) implements JSObject { + external factory JSDate(); + external factory JSDate.fromMilliseconds(int ms); + external int getTime(); + external String toISOString(); +} +``` + +### Wrapping Static Methods + +```dart +@JS('JSON') +extension type JSJSON._(JSObject _) implements JSObject { + external static String stringify(JSAny? value); + external static JSAny? parse(String text); +} +``` + +### Accessing Global Objects + +```dart +@JS('window') +external JSObject get window; + +@JS('document') +external JSObject get document; + +@JS('globalThis') +external JSObject get globalThis; +``` + +## Debugging Tips + +1. **Check the browser console** - JS errors show up there +2. **Use source maps** - Debug Dart code directly +3. **Print JS objects** - `consoleLog(jsObject)` shows the raw structure +4. **Type assertions** - Use `as` carefully; it can hide errors + +## Further Reading + +- [Official JS Interop Documentation](https://dart.dev/interop/js-interop) +- [Extension Types](https://dart.dev/language/extension-types) +- [dart_node_core Source](/api/dart_node_core/) - See real-world interop examples diff --git a/website/src/docs/react-native/index.md b/website/src/docs/react-native/index.md new file mode 100644 index 0000000..08c77bc --- /dev/null +++ b/website/src/docs/react-native/index.md @@ -0,0 +1,444 @@ +--- +layout: layouts/docs.njk +title: dart_node_react_native +description: React Native bindings for building cross-platform mobile apps in Dart with Expo. +eleventyNavigation: + key: dart_node_react_native + parent: Packages + order: 4 +--- + +`dart_node_react_native` provides type-safe React Native bindings for building iOS and Android apps in Dart. Combined with Expo, you get a complete mobile development experience. + +## Installation + +```yaml +dependencies: + dart_node_react_native: ^0.2.0 + dart_node_react: ^0.2.0 # Required peer dependency +``` + +Set up your Expo project: + +```bash +npx create-expo-app my-app +cd my-app +``` + +## Quick Start + +```dart +import 'package:dart_node_react_native/dart_node_react_native.dart'; +import 'package:dart_node_react/dart_node_react.dart'; + +ReactElement app() { + return safeAreaView( + style: {'flex': 1, 'backgroundColor': '#fff'}, + children: [ + view( + style: {'padding': 20}, + children: [ + rnText( + style: {'fontSize': 24, 'fontWeight': 'bold'}, + children: [text('Hello, Dart!')], + ), + rnText( + children: [text('Welcome to React Native with Dart.')], + ), + ], + ), + ], + ); +} +``` + +## Components + +### View + +The fundamental building block, similar to `div` in web: + +```dart +view( + style: { + 'flex': 1, + 'flexDirection': 'row', + 'justifyContent': 'center', + 'alignItems': 'center', + 'backgroundColor': '#f5f5f5', + }, + children: [...], +) +``` + +### Text + +For displaying text (note: `rnText` to avoid conflict with React's `text()`): + +```dart +rnText( + style: { + 'fontSize': 18, + 'fontWeight': '600', + 'color': '#333', + 'textAlign': 'center', + }, + children: [text('Hello, World!')], +) +``` + +### TextInput + +For user text input: + +```dart +ReactElement searchInput() { + final (query, setQuery) = useState(''); + + return textInput( + value: query, + onChangeText: setQuery, + placeholder: 'Search...', + style: { + 'height': 40, + 'borderWidth': 1, + 'borderColor': '#ccc', + 'borderRadius': 8, + 'paddingHorizontal': 12, + }, + ); +} +``` + +### TouchableOpacity + +For pressable elements with opacity feedback: + +```dart +touchableOpacity( + onPress: () => print('Pressed!'), + style: { + 'backgroundColor': '#007AFF', + 'padding': 12, + 'borderRadius': 8, + }, + children: [ + rnText( + style: {'color': '#fff', 'textAlign': 'center'}, + children: [text('Press Me')], + ), + ], +) +``` + +### Button + +Simple button component: + +```dart +rnButton( + title: 'Submit', + onPress: () => print('Button pressed!'), + color: '#007AFF', +) +``` + +### ScrollView + +For scrollable content: + +```dart +scrollView( + style: {'flex': 1}, + contentContainerStyle: {'padding': 20}, + children: [ + // Many children that exceed screen height + ...items.map((item) => itemCard(item)), + ], +) +``` + +### FlatList + +For efficient list rendering: + +```dart +ReactElement userList({required List users}) { + return flatList( + data: users, + keyExtractor: (user, _) => user.id, + renderItem: (info) => userCard(user: info.item), + ItemSeparatorComponent: () => view( + style: {'height': 1, 'backgroundColor': '#eee'}, + ), + ); +} +``` + +### Image + +For displaying images: + +```dart +// Local image +image( + source: AssetSource('assets/logo.png'), + style: {'width': 100, 'height': 100}, +) + +// Remote image +image( + source: UriSource('https://example.com/image.jpg'), + style: {'width': 200, 'height': 150}, + resizeMode: 'cover', +) +``` + +### SafeAreaView + +For respecting device safe areas (notch, home indicator): + +```dart +safeAreaView( + style: {'flex': 1}, + children: [ + // Content here is safe from notches and system UI + ], +) +``` + +### ActivityIndicator + +Loading spinner: + +```dart +activityIndicator( + size: 'large', + color: '#007AFF', +) +``` + +## Styling + +React Native uses JavaScript objects for styles (like React inline styles but with different properties): + +```dart +view( + style: { + // Layout + 'flex': 1, + 'flexDirection': 'column', // or 'row' + 'justifyContent': 'center', // main axis + 'alignItems': 'center', // cross axis + + // Spacing + 'padding': 20, + 'paddingHorizontal': 16, + 'margin': 10, + 'marginTop': 20, + + // Appearance + 'backgroundColor': '#ffffff', + 'borderRadius': 8, + 'borderWidth': 1, + 'borderColor': '#ccc', + + // Shadows (iOS) + 'shadowColor': '#000', + 'shadowOffset': {'width': 0, 'height': 2}, + 'shadowOpacity': 0.25, + 'shadowRadius': 4, + + // Shadows (Android) + 'elevation': 5, + }, + children: [...], +) +``` + +## Navigation + +Use with React Navigation (via JS interop): + +```dart +// Define screens +ReactElement homeScreen({required NavigationProps nav}) { + return view(children: [ + rnText(children: [text('Home Screen')]), + touchableOpacity( + onPress: () => nav.navigate('Details', {'id': 123}), + children: [rnText(children: [text('Go to Details')])], + ), + ]); +} + +ReactElement detailsScreen({required NavigationProps nav}) { + final id = nav.route.params['id']; + + return view(children: [ + rnText(children: [text('Details for $id')]), + touchableOpacity( + onPress: () => nav.goBack(), + children: [rnText(children: [text('Go Back')])], + ), + ]); +} +``` + +## Complete Example + +```dart +import 'package:dart_node_react_native/dart_node_react_native.dart'; +import 'package:dart_node_react/dart_node_react.dart'; + +ReactElement todoApp() { + final (todos, setTodos) = useState>([]); + final (input, setInput) = useState(''); + + void addTodo() { + if (input.trim().isEmpty) return; + + setTodos((prev) => [ + ...prev, + Todo(id: DateTime.now().toString(), title: input, completed: false), + ]); + setInput(''); + } + + void toggleTodo(String id) { + setTodos((prev) => prev.map((todo) => + todo.id == id + ? Todo(id: todo.id, title: todo.title, completed: !todo.completed) + : todo + ).toList()); + } + + return safeAreaView( + style: {'flex': 1, 'backgroundColor': '#f5f5f5'}, + children: [ + // Header + view( + style: { + 'padding': 20, + 'backgroundColor': '#007AFF', + }, + children: [ + rnText( + style: { + 'fontSize': 24, + 'fontWeight': 'bold', + 'color': '#fff', + }, + children: [text('My Todos')], + ), + ], + ), + + // Input + view( + style: { + 'flexDirection': 'row', + 'padding': 16, + 'backgroundColor': '#fff', + }, + children: [ + textInput( + style: { + 'flex': 1, + 'height': 44, + 'borderWidth': 1, + 'borderColor': '#ddd', + 'borderRadius': 8, + 'paddingHorizontal': 12, + }, + value: input, + onChangeText: setInput, + placeholder: 'Add a todo...', + ), + touchableOpacity( + onPress: addTodo, + style: { + 'marginLeft': 12, + 'backgroundColor': '#007AFF', + 'paddingHorizontal': 20, + 'justifyContent': 'center', + 'borderRadius': 8, + }, + children: [ + rnText( + style: {'color': '#fff', 'fontWeight': '600'}, + children: [text('Add')], + ), + ], + ), + ], + ), + + // List + flatList( + data: todos, + keyExtractor: (todo, _) => todo.id, + renderItem: (info) => touchableOpacity( + onPress: () => toggleTodo(info.item.id), + style: { + 'flexDirection': 'row', + 'alignItems': 'center', + 'padding': 16, + 'backgroundColor': '#fff', + 'borderBottomWidth': 1, + 'borderBottomColor': '#eee', + }, + children: [ + view( + style: { + 'width': 24, + 'height': 24, + 'borderRadius': 12, + 'borderWidth': 2, + 'borderColor': info.item.completed ? '#4CAF50' : '#ccc', + 'backgroundColor': info.item.completed ? '#4CAF50' : 'transparent', + 'marginRight': 12, + }, + ), + rnText( + style: { + 'flex': 1, + 'fontSize': 16, + 'textDecorationLine': info.item.completed ? 'line-through' : 'none', + 'color': info.item.completed ? '#999' : '#333', + }, + children: [text(info.item.title)], + ), + ], + ), + style: {'flex': 1}, + ), + + // Footer + view( + style: {'padding': 16, 'backgroundColor': '#fff'}, + children: [ + rnText( + style: {'textAlign': 'center', 'color': '#666'}, + children: [ + text('${todos.where((t) => !t.completed).length} items remaining'), + ], + ), + ], + ), + ], + ); +} + +class Todo { + final String id; + final String title; + final bool completed; + + Todo({required this.id, required this.title, required this.completed}); +} +``` + +## API Reference + +See the [full API documentation](/api/dart_node_react_native/) for all available components and types. diff --git a/website/src/docs/react/index.md b/website/src/docs/react/index.md new file mode 100644 index 0000000..320f487 --- /dev/null +++ b/website/src/docs/react/index.md @@ -0,0 +1,410 @@ +--- +layout: layouts/docs.njk +title: dart_node_react +description: React bindings for building web applications in Dart with hooks, components, and JSX-like syntax. +eleventyNavigation: + key: dart_node_react + parent: Packages + order: 3 +--- + +`dart_node_react` provides type-safe React bindings for building web applications in Dart. If you know React, you'll feel right at home. + +## Installation + +```yaml +dependencies: + dart_node_react: ^0.2.0 +``` + +Also install React via npm: + +```bash +npm install react react-dom +``` + +## Quick Start + +```dart +import 'package:dart_node_react/dart_node_react.dart'; + +ReactElement app() { + return div( + className: 'app', + children: [ + h1(children: [text('Hello, Dart!')]), + p(children: [text('Welcome to React with Dart.')]), + ], + ); +} + +void main() { + final container = document.getElementById('root'); + final root = ReactDOM.createRoot(container); + root.render(app()); +} +``` + +## Components + +### Functional Components + +```dart +ReactElement greeting({required String name}) { + return div( + className: 'greeting', + children: [ + text('Hello, $name!'), + ], + ); +} + +// Usage +greeting(name: 'World'); +``` + +### Components with Props + +```dart +ReactElement userCard({ + required String name, + required String email, + String? avatarUrl, +}) { + return div( + className: 'user-card', + children: [ + avatarUrl != null + ? img(src: avatarUrl, alt: name) + : div(className: 'avatar-placeholder'), + h2(children: [text(name)]), + p(children: [text(email)]), + ], + ); +} +``` + +## Hooks + +### useState + +```dart +ReactElement counter() { + final (count, setCount) = useState(0); + + return div(children: [ + p(children: [text('Count: $count')]), + button( + onClick: (_) => setCount((c) => c + 1), + children: [text('Increment')], + ), + button( + onClick: (_) => setCount((c) => c - 1), + children: [text('Decrement')], + ), + ]); +} +``` + +### useEffect + +```dart +ReactElement timer() { + final (seconds, setSeconds) = useState(0); + + useEffect(() { + final timer = Timer.periodic(Duration(seconds: 1), (_) { + setSeconds((s) => s + 1); + }); + + // Cleanup function + return () => timer.cancel(); + }, []); // Empty deps = run once on mount + + return p(children: [text('Seconds: $seconds')]); +} +``` + +### useRef + +```dart +ReactElement focusInput() { + final inputRef = useRef(null); + + void handleClick() { + inputRef.current?.focus(); + } + + return div(children: [ + input(ref: inputRef, type: 'text'), + button( + onClick: (_) => handleClick(), + children: [text('Focus Input')], + ), + ]); +} +``` + +### useMemo + +```dart +ReactElement expensiveList({required List numbers}) { + // Only recalculate when numbers changes + final sorted = useMemo( + () => numbers.toList()..sort(), + [numbers], + ); + + return ul( + children: sorted.map((n) => li(children: [text('$n')])).toList(), + ); +} +``` + +### useCallback + +```dart +ReactElement searchBox({required void Function(String) onSearch}) { + final (query, setQuery) = useState(''); + + // Memoize the callback + final handleSubmit = useCallback( + () => onSearch(query), + [query, onSearch], + ); + + return form( + onSubmit: (_) => handleSubmit(), + children: [ + input( + value: query, + onChange: (e) => setQuery(e.target.value), + ), + button(type: 'submit', children: [text('Search')]), + ], + ); +} +``` + +## Elements + +### HTML Elements + +```dart +// Divs and spans +div(className: 'container', children: [...]) +span(className: 'highlight', children: [...]) + +// Headings +h1(children: [text('Title')]) +h2(children: [text('Subtitle')]) + +// Paragraphs and text +p(children: [text('Some text')]) +text('Raw text content') + +// Links +a(href: 'https://example.com', children: [text('Click me')]) + +// Images +img(src: '/image.png', alt: 'Description') + +// Forms +form(onSubmit: handleSubmit, children: [...]) +input(type: 'text', value: value, onChange: handleChange) +button(type: 'submit', children: [text('Submit')]) +``` + +### Lists + +```dart +ReactElement todoList({required List todos}) { + return ul( + className: 'todo-list', + children: todos.map((todo) => + li( + key: todo.id, + children: [ + input( + type: 'checkbox', + checked: todo.completed, + ), + text(todo.title), + ], + ) + ).toList(), + ); +} +``` + +### Conditional Rendering + +```dart +ReactElement userStatus({required User? user}) { + return div(children: [ + user != null + ? span(children: [text('Welcome, ${user.name}!')]) + : span(children: [text('Please log in')]), + ]); +} +``` + +## Event Handling + +```dart +ReactElement interactiveButton() { + void handleClick(MouseEvent e) { + print('Button clicked at (${e.clientX}, ${e.clientY})'); + } + + void handleMouseEnter(MouseEvent e) { + print('Mouse entered'); + } + + return button( + onClick: handleClick, + onMouseEnter: handleMouseEnter, + children: [text('Hover and Click Me')], + ); +} +``` + +### Form Events + +```dart +ReactElement loginForm() { + final (email, setEmail) = useState(''); + final (password, setPassword) = useState(''); + + void handleSubmit(Event e) { + e.preventDefault(); + print('Login: $email / $password'); + } + + return form( + onSubmit: handleSubmit, + children: [ + input( + type: 'email', + value: email, + onChange: (e) => setEmail(e.target.value), + placeholder: 'Email', + ), + input( + type: 'password', + value: password, + onChange: (e) => setPassword(e.target.value), + placeholder: 'Password', + ), + button(type: 'submit', children: [text('Log In')]), + ], + ); +} +``` + +## Styling + +### Inline Styles + +```dart +div( + style: { + 'backgroundColor': '#f0f0f0', + 'padding': '1rem', + 'borderRadius': '8px', + }, + children: [...], +) +``` + +### CSS Classes + +```dart +div( + className: 'card card-primary', + children: [...], +) +``` + +## Complete Example + +```dart +import 'package:dart_node_react/dart_node_react.dart'; + +ReactElement todoApp() { + final (todos, setTodos) = useState>([]); + final (input, setInput) = useState(''); + + void addTodo() { + if (input.trim().isEmpty) return; + + setTodos((prev) => [ + ...prev, + Todo(id: DateTime.now().toString(), title: input, completed: false), + ]); + setInput(''); + } + + void toggleTodo(String id) { + setTodos((prev) => prev.map((todo) => + todo.id == id + ? Todo(id: todo.id, title: todo.title, completed: !todo.completed) + : todo + ).toList()); + } + + return div( + className: 'todo-app', + children: [ + h1(children: [text('Todo List')]), + + form( + onSubmit: (e) { + e.preventDefault(); + addTodo(); + }, + children: [ + input( + value: input, + onChange: (e) => setInput(e.target.value), + placeholder: 'What needs to be done?', + ), + button(type: 'submit', children: [text('Add')]), + ], + ), + + ul( + children: todos.map((todo) => + li( + key: todo.id, + className: todo.completed ? 'completed' : '', + onClick: (_) => toggleTodo(todo.id), + children: [text(todo.title)], + ) + ).toList(), + ), + + p(children: [ + text('${todos.where((t) => !t.completed).length} items left'), + ]), + ], + ); +} + +class Todo { + final String id; + final String title; + final bool completed; + + Todo({required this.id, required this.title, required this.completed}); +} + +void main() { + final root = ReactDOM.createRoot(document.getElementById('root')!); + root.render(todoApp()); +} +``` + +## API Reference + +See the [full API documentation](/api/dart_node_react/) for all available functions and types. diff --git a/website/src/docs/websockets/index.md b/website/src/docs/websockets/index.md new file mode 100644 index 0000000..79c1fef --- /dev/null +++ b/website/src/docs/websockets/index.md @@ -0,0 +1,338 @@ +--- +layout: layouts/docs.njk +title: dart_node_ws +description: WebSocket bindings for real-time communication on Node.js. +eleventyNavigation: + key: dart_node_ws + parent: Packages + order: 5 +--- + +`dart_node_ws` provides type-safe WebSocket bindings for Node.js, enabling real-time bidirectional communication in your Dart applications. + +## Installation + +```yaml +dependencies: + dart_node_ws: ^0.2.0 +``` + +Also install the ws package via npm: + +```bash +npm install ws +``` + +## Quick Start + +### WebSocket Server + +```dart +import 'package:dart_node_ws/dart_node_ws.dart'; + +void main() { + final server = createWebSocketServer(port: 8080); + + server.on('connection', (WebSocketClient client) { + print('Client connected'); + + client.on('message', (data) { + print('Received: $data'); + + // Echo back + client.send('You said: $data'); + }); + + client.on('close', () { + print('Client disconnected'); + }); + + // Send welcome message + client.send('Welcome to the WebSocket server!'); + }); + + print('WebSocket server running on port 8080'); +} +``` + +### Integrating with Express + +```dart +import 'package:dart_node_express/dart_node_express.dart'; +import 'package:dart_node_ws/dart_node_ws.dart'; + +void main() { + final app = createExpressApp(); + final httpServer = app.listen(3000); + + // Attach WebSocket server to the HTTP server + final wss = createWebSocketServer(server: httpServer); + + wss.on('connection', (WebSocketClient client) { + // Handle WebSocket connections + }); + + // HTTP routes still work + app.get('/', (req, res) { + res.send('HTTP server with WebSocket support'); + }); +} +``` + +## WebSocket Server API + +### Creating a Server + +```dart +// Standalone server on a port +final server = createWebSocketServer(port: 8080); + +// Attached to an existing HTTP server +final server = createWebSocketServer(server: httpServer); + +// With path filtering +final server = createWebSocketServer( + server: httpServer, + path: '/ws', // Only accept connections to /ws +); +``` + +### Server Events + +```dart +server.on('connection', (WebSocketClient client, Request req) { + // New client connected + // req contains the HTTP upgrade request + print('Connection from ${req.headers['origin']}'); +}); + +server.on('error', (error) { + print('Server error: $error'); +}); + +server.on('close', () { + print('Server closed'); +}); +``` + +### Broadcasting to All Clients + +```dart +void broadcast(String message) { + for (final client in server.clients) { + if (client.readyState == WebSocket.OPEN) { + client.send(message); + } + } +} +``` + +## WebSocket Client API + +### Client Events + +```dart +client.on('message', (data) { + // Handle incoming message + // data can be String or Buffer +}); + +client.on('close', (code, reason) { + print('Closed with code $code: $reason'); +}); + +client.on('error', (error) { + print('Client error: $error'); +}); + +client.on('ping', (data) { + // Ping received (pong sent automatically) +}); + +client.on('pong', (data) { + // Pong received (response to our ping) +}); +``` + +### Sending Messages + +```dart +// Send text +client.send('Hello, client!'); + +// Send JSON +client.send(jsonEncode({'type': 'update', 'data': someData})); + +// Send binary data +client.send(Uint8List.fromList([0x01, 0x02, 0x03])); +``` + +### Client State + +```dart +// Check connection state +if (client.readyState == WebSocket.OPEN) { + client.send('Connected!'); +} + +// States: CONNECTING, OPEN, CLOSING, CLOSED +``` + +### Closing Connection + +```dart +// Close gracefully +client.close(); + +// Close with code and reason +client.close(1000, 'Normal closure'); +``` + +## Chat Server Example + +```dart +import 'package:dart_node_ws/dart_node_ws.dart'; +import 'package:dart_node_express/dart_node_express.dart'; + +void main() { + final app = createExpressApp(); + + // Serve static files for the chat client + app.use(staticMiddleware('public')); + + final httpServer = app.listen(3000, () { + print('Server running on http://localhost:3000'); + }); + + // WebSocket server + final wss = createWebSocketServer(server: httpServer); + final clients = {}; + + wss.on('connection', (WebSocketClient client) { + String? username; + + client.on('message', (data) { + final message = jsonDecode(data); + + switch (message['type']) { + case 'join': + username = message['username']; + clients[username!] = client; + broadcast({ + 'type': 'system', + 'text': '$username joined the chat', + }); + break; + + case 'message': + if (username != null) { + broadcast({ + 'type': 'message', + 'username': username, + 'text': message['text'], + 'timestamp': DateTime.now().toIso8601String(), + }); + } + break; + } + }); + + client.on('close', () { + if (username != null) { + clients.remove(username); + broadcast({ + 'type': 'system', + 'text': '$username left the chat', + }); + } + }); + }); + + void broadcast(Map message) { + final json = jsonEncode(message); + for (final client in clients.values) { + if (client.readyState == WebSocket.OPEN) { + client.send(json); + } + } + } +} +``` + +## Real-time Dashboard Example + +```dart +import 'dart:async'; +import 'package:dart_node_ws/dart_node_ws.dart'; + +void main() { + final server = createWebSocketServer(port: 8080); + final subscribers = {}; + + // Simulate real-time data updates + Timer.periodic(Duration(seconds: 1), (_) { + final data = { + 'timestamp': DateTime.now().toIso8601String(), + 'cpu': Random().nextDouble() * 100, + 'memory': Random().nextDouble() * 100, + 'requests': Random().nextInt(1000), + }; + + final json = jsonEncode(data); + for (final client in subscribers) { + if (client.readyState == WebSocket.OPEN) { + client.send(json); + } + } + }); + + server.on('connection', (WebSocketClient client) { + print('Dashboard client connected'); + subscribers.add(client); + + // Send initial state + client.send(jsonEncode({ + 'type': 'init', + 'serverTime': DateTime.now().toIso8601String(), + })); + + client.on('close', () { + subscribers.remove(client); + print('Dashboard client disconnected'); + }); + }); + + print('Dashboard WebSocket server on port 8080'); +} +``` + +## Error Handling + +```dart +server.on('connection', (WebSocketClient client) { + client.on('message', (data) { + try { + final message = jsonDecode(data); + // Process message... + } catch (e) { + client.send(jsonEncode({ + 'error': 'Invalid message format', + })); + } + }); + + client.on('error', (error) { + print('Client error: $error'); + // Don't crash the server + }); +}); + +server.on('error', (error) { + print('Server error: $error'); + // Handle server-level errors +}); +``` + +## API Reference + +See the [full API documentation](/api/dart_node_ws/) for all available functions and types. diff --git a/website/src/docs/why-dart.md b/website/src/docs/why-dart.md new file mode 100644 index 0000000..2eeb57f --- /dev/null +++ b/website/src/docs/why-dart.md @@ -0,0 +1,230 @@ +--- +layout: layouts/docs.njk +title: Why Dart? +description: A respectful comparison of Dart and TypeScript. Learn why Dart's runtime type safety and sound null safety make it an excellent choice for full-stack development. +eleventyNavigation: + key: Why Dart + order: 2 +--- + +If you're a TypeScript developer, you already appreciate the value of types. Dart takes that appreciation further with features that TypeScript, due to its design constraints, cannot provide. + +If you're a Flutter developer, you already know Dart. Now you can use it across the entire JavaScript ecosystem. + +## TypeScript: A Brilliant Compromise + +TypeScript is an engineering marvel. It adds static typing to JavaScript without breaking compatibility with the massive JS ecosystem. This was a deliberate choice - and it was the right one for TypeScript's goals. + +However, this choice comes with trade-offs that become apparent in larger applications. + +## The Type Erasure Problem + +TypeScript types exist only at compile time. When your code runs, they're gone. + +```typescript +interface User { + id: number; + name: string; + email: string; +} + +// This compiles fine +const user: User = JSON.parse(apiResponse); + +// But at runtime, there's no guarantee `user` matches the interface. +// If the API returns { id: "123", name: null }, TypeScript can't help you. +console.log(user.name.toUpperCase()); // Runtime error if name is null! +``` + +**Dart preserves types at runtime:** + +```dart +class User { + final int id; + final String name; + final String email; + + User({required this.id, required this.name, required this.email}); + + factory User.fromJson(Map json) { + return User( + id: json['id'] as int, // Fails immediately if wrong type + name: json['name'] as String, + email: json['email'] as String, + ); + } +} + +// Validation happens at the boundary +final user = User.fromJson(jsonDecode(apiResponse)); + +// If we get here, we KNOW user.name is a non-null String +print(user.name.toUpperCase()); // Safe! +``` + +## Sound Null Safety + +TypeScript has `strictNullChecks`, which is excellent. But "strict" still allows escape hatches. + +```typescript +// TypeScript: The ! operator trusts you (sometimes wrongly) +function processUser(user: User | null) { + console.log(user!.name); // You're asserting user isn't null + // TypeScript trusts you. The runtime might not. +} +``` + +**Dart's null safety is sound:** + +```dart +// Dart: The compiler ensures this +void processUser(User? user) { + print(user.name); // Compile error! user might be null + + // You must handle the null case + if (user != null) { + print(user.name); // Now it's safe + } + + // Or use null-aware operators + print(user?.name ?? 'Anonymous'); +} +``` + +The `!` operator exists in Dart too, but using it on a null value throws immediately - you can't silently proceed with undefined behavior. + +## Real-World Implications + +### Serialization + +In TypeScript, you often need runtime validation libraries like Zod or io-ts: + +```typescript +import { z } from 'zod'; + +const UserSchema = z.object({ + id: z.number(), + name: z.string(), + email: z.string().email(), +}); + +type User = z.infer; + +// You define the shape twice: once for Zod, once for TypeScript +const user = UserSchema.parse(apiData); +``` + +In Dart, the class definition IS the runtime validation: + +```dart +class User { + final int id; + final String name; + final String email; + + User({required this.id, required this.name, required this.email}); + + factory User.fromJson(Map json) => User( + id: json['id'] as int, + name: json['name'] as String, + email: json['email'] as String, + ); +} + +// One definition, used for both types and validation +final user = User.fromJson(apiData); +``` + +### Generics + +TypeScript generics are erased: + +```typescript +class Box { + constructor(public value: T) {} + + isString(): boolean { + // Can't check if T is string at runtime! + // typeof this.value === 'string' checks the value, not T + return typeof this.value === 'string'; + } +} +``` + +Dart generics exist at runtime: + +```dart +class Box { + final T value; + Box(this.value); + + bool isString() { + // We can check the actual type parameter! + return T == String; + } + + // Even better: type-safe operations + R map(R Function(T) fn) => fn(value); +} +``` + +## Single Language, Multiple Platforms + +With dart_node, you write Dart everywhere: + +| Platform | TypeScript | Dart (dart_node) | +|----------|-----------|------------------| +| Backend | Node.js + TypeScript | dart_node_express | +| Web Frontend | React + TypeScript | dart_node_react | +| Mobile | React Native + TypeScript | dart_node_react_native | +| Desktop | Electron + TypeScript | Flutter Desktop | + +Share models, validation logic, and business rules across all platforms - with runtime type safety. + +## Simpler Build Pipeline + +A typical TypeScript project: + +``` +Source → TypeScript Compiler → Babel → Webpack/Rollup → Bundle + (tsconfig.json) (.babelrc) (webpack.config.js) +``` + +A dart_node project: + +``` +Source → dart compile js → Bundle + (just works) +``` + +No configuration maze. No compatibility issues between tools. One command. + +## For Flutter Developers + +If you already know Dart from Flutter, dart_node opens up the JavaScript ecosystem: + +- Use the massive React component ecosystem +- Access npm packages (millions of them) +- Deploy to Node.js hosting (cheaper than server-side Dart) +- Build with familiar tools (same language, same patterns) + +## When TypeScript Makes Sense + +TypeScript remains an excellent choice when: + +- You're working with an existing JavaScript codebase +- Your team is deeply invested in the TypeScript ecosystem +- You need maximum compatibility with JS libraries +- You prefer TypeScript's structural typing over Dart's nominal typing + +## Conclusion + +Dart and TypeScript both add type safety to dynamic languages. TypeScript chose maximum JavaScript compatibility. Dart chose maximum type safety. + +For new projects where runtime safety matters, Dart offers guarantees that TypeScript cannot provide - not because TypeScript is flawed, but because it was designed with different constraints. + +With dart_node, you get the best of both worlds: Dart's type safety with access to the JavaScript ecosystem. + +--- + +Ready to try it? [Get started with dart_node](/docs/getting-started/) diff --git a/website/src/feed.njk b/website/src/feed.njk new file mode 100644 index 0000000..5ff55fc --- /dev/null +++ b/website/src/feed.njk @@ -0,0 +1,28 @@ +--- +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 new file mode 100644 index 0000000..399cf1c --- /dev/null +++ b/website/src/index.njk @@ -0,0 +1,297 @@ +--- +layout: layouts/base.njk +title: "dart_node - Full-Stack Dart for the JavaScript Ecosystem" +description: "Write React, React Native, and Express apps entirely in Dart. Runtime type safety, sound null safety, and one language for everything." +--- + +
+
+

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. +

+ + +
+
// A complete Express server in Dart
+import 'package:dart_node_express/dart_node_express.dart';
+
+void main() {
+  final app = createExpressApp();
+
+  app.get('/', (req, res) {
+    res.json({'message': 'Hello from Dart!'});
+  });
+
+  app.listen(3000, () {
+    print('Server running on port 3000');
+  });
+}
+
+
+
+ +
+
+
+

Built for You

+

Whether you're coming from React or Flutter, dart_node speaks your language.

+
+ +
+ + +
+ +
+
+
+
React (TypeScript)
+
const Counter: React.FC = () => {
+  const [count, setCount] = useState(0);
+
+  return (
+    <button onClick={() => setCount(c => c + 1)}>
+      Count: {count}
+    </button>
+  );
+};
+
+
+
React (Dart)
+
ReactElement counter() {
+  final (count, setCount) = useState(0);
+
+  return button(
+    onClick: (_) => setCount((c) => c + 1),
+    children: [text('Count: $count')],
+  );
+}
+
+
+ +
+
+
1
+

Same Paradigms

+

Hooks, components, props, state — everything you know from React works the same way in Dart.

+
+
+
2
+

Runtime Type Safety

+

Unlike TypeScript, Dart preserves types at runtime. No more any escapes or erased generics.

+
+
+
3
+

Simpler Tooling

+

No webpack, no babel, no tsconfig. Just dart compile js and you're done.

+
+
+
+ +
+
+
+
Flutter
+
class Counter extends StatefulWidget {
+  @override
+  State<Counter> createState() => _CounterState();
+}
+
+class _CounterState extends State<Counter> {
+  int count = 0;
+
+  @override
+  Widget build(BuildContext context) {
+    return ElevatedButton(
+      onPressed: () => setState(() => count++),
+      child: Text('Count: $count'),
+    );
+  }
+}
+
+
+
dart_node React
+
ReactElement counter() {
+  final (count, setCount) = useState(0);
+
+  return button(
+    onClick: (_) => setCount((c) => c + 1),
+    children: [text('Count: $count')],
+  );
+}
+
+
+ +
+
+
1
+

Same Language

+

Use your existing Dart skills. Share models, utilities, and business logic across platforms.

+
+
+
2
+

Web Ecosystem Access

+

Leverage the massive React and npm ecosystems while writing pure Dart code.

+
+
+
3
+

Full-Stack Dart

+

Backend (Express), web (React), mobile (React Native) — all in one language.

+
+
+
+
+
+ +
+
+
+

The dart_node Stack

+

Five packages that give you full-stack superpowers.

+
+ +
+
+
C
+

dart_node_core

+

Foundation layer with JS interop utilities, Node.js bindings, and console helpers.

+ Learn more → +
+ +
+
E
+

dart_node_express

+

Type-safe Express.js bindings for building HTTP servers and REST APIs.

+ Learn more → +
+ +
+
R
+

dart_node_react

+

React bindings with hooks, JSX-like syntax, and full component support.

+ Learn more → +
+ +
+
N
+

dart_node_react_native

+

React Native + Expo bindings for cross-platform mobile development.

+ Learn more → +
+ +
+
W
+

dart_node_ws

+

WebSocket bindings for real-time communication on Node.js.

+ Learn more → +
+
+
+
+ +
+
+
+

Why Types Matter at Runtime

+

TypeScript erases types when it compiles to JavaScript. Dart doesn't.

+
+ +
+
+
TypeScript (Types Erased)
+
interface User {
+  id: number;
+  name: string;
+}
+
+// At runtime, this is just a plain object
+// No way to validate the shape!
+const user: User = JSON.parse(data);
+
+// This could fail silently
+console.log(user.name.toUpperCase());
+// Runtime error if name is undefined!
+
+
+
Dart (Types Preserved)
+
class User {
+  final int id;
+  final String name;
+  User({required this.id, required this.name});
+}
+
+// Types exist at runtime - you can validate!
+final user = User.fromJson(jsonDecode(data));
+
+// If name were null, this would fail
+// at deserialization, not at usage
+print(user.name.toUpperCase());
+
+
+ + +
+
+ +
+
+
+

Get Started in Minutes

+
+ +
+
# Create a new project
+mkdir my_dart_app && cd my_dart_app
+dart create -t package .
+
+# Add dart_node packages
+dart pub add dart_node_core dart_node_express
+
+# Write your server
+cat > lib/server.dart << 'EOF'
+import 'package:dart_node_express/dart_node_express.dart';
+
+void main() {
+  final app = createExpressApp();
+  app.get('/', (req, res) => res.send('Hello, Dart!'));
+  app.listen(3000);
+}
+EOF
+
+# Compile to JavaScript and run
+dart compile js lib/server.dart -o build/server.js
+node build/server.js
+
+ + +
+
+ + diff --git a/website/src/robots.txt b/website/src/robots.txt new file mode 100644 index 0000000..e8b290d --- /dev/null +++ b/website/src/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://dartnode.dev/sitemap.xml diff --git a/website/src/sitemap.njk b/website/src/sitemap.njk new file mode 100644 index 0000000..2229f63 --- /dev/null +++ b/website/src/sitemap.njk @@ -0,0 +1,31 @@ +--- +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 %} +