commit 724500ae0ba2501e70d1e255a7cb76c800231a8f Author: Jens Date: Thu Feb 12 23:54:49 2026 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b41f45d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +node_modules/ +generated/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..691d217 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..69c3597 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Mastro Template Basic for Node.js + +This is a basic TypeScript template for [Mastro](https://mastrojs.github.io) when using [Node.js](https://nodejs.org). + +Click the green **Use this template** button in the top right to create your own copy of this repository. Then clone the **Code** to your computer. + +## Run locally + +If you have multiple projects on your computer that require different Node.js versions, you should install a tool to manage those version for you; for example [Volta](https://volta.sh/) (see [pnpm Support](https://docs.volta.sh/advanced/pnpm)). + +Mastro requires Node.js >=24 (unless you want to install a [`URLPattern` polyfill](https://www.npmjs.com/package/urlpattern-polyfill)). + +[JSR recommends](https://jsr.io/docs/npm-compatibility#installing-and-using-jsr-packages) to use `pnpm`. + +The first time, you need to: + + pnpm install + +After that, to start the server: + + pnpm run start + +and open in your browser. + +To generate the whole static site (this will create a `generated` folder): + + pnpm run generate + +## Next steps + +To see how Mastro works, [follow the guide](https://mastrojs.github.io/guide/server-side-components-and-routing/). + +To make sure you're using the latest Mastro packages: + + pnpm update "@mastrojs/*" --latest + + +## Deploy to production + +- [Deploy static site](https://mastrojs.github.io/guide/deploy/#deploy-static-site-with-ci%2Fcd) +- [Deploy server](https://mastrojs.github.io/guide/deploy/#deploy-server-to-production) diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..6d1c94a --- /dev/null +++ b/bun.lock @@ -0,0 +1,135 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "my-website", + "dependencies": { + "@mastrojs/markdown": "npm:@jsr/mastrojs__markdown@^0", + "@mastrojs/mastro": "npm:@jsr/mastrojs__mastro@^0", + "@remix-run/node-fetch-server": "^0.11", + }, + "devDependencies": { + "@types/node": "^24", + "typescript": "^5", + }, + }, + }, + "packages": { + "@jsr/mastrojs__mastro": ["@jsr/mastrojs__mastro@0.6.4", "https://npm.jsr.io/~/11/@jsr/mastrojs__mastro/0.6.4.tgz", { "dependencies": { "@jsr/std__http": "^1.0.19", "@jsr/std__media-types": "^1.1.0", "@jsr/std__path": "1.1.4", "ts-blank-space": "0.6.1" } }, "sha512-w1YnGd++Ev3xq/E6Lzy8mtNKCMBoe2dBxpv4AjNJsdAr2XqFlX0z74Tno3UmuYZ7Ijzmr4YnOK/iEAGe1L3NEg=="], + + "@jsr/std__bytes": ["@jsr/std__bytes@1.0.6", "https://npm.jsr.io/~/11/@jsr/std__bytes/1.0.6.tgz", {}, "sha512-St6yKggjFGhxS52IFLJWvkchRFbAKg2Xh8UxA4S1EGz7GJ2Ui+ssDDldj/w2c8vCxvl6qgR0HaYbKeFJNqujmA=="], + + "@jsr/std__cli": ["@jsr/std__cli@1.0.27", "https://npm.jsr.io/~/11/@jsr/std__cli/1.0.27.tgz", { "dependencies": { "@jsr/std__fmt": "^1.0.9", "@jsr/std__internal": "^1.0.12" } }, "sha512-aaY6VYkdv0qmAIiaYNfBH9Pd3Te5bJsEUQmNg/ak43AorET5+pBcI9RgqCgBXfkb2tBnLUlTBklQvirMz/CnAQ=="], + + "@jsr/std__encoding": ["@jsr/std__encoding@1.0.10", "https://npm.jsr.io/~/11/@jsr/std__encoding/1.0.10.tgz", {}, "sha512-WK2njnDTyKefroRNk2Ooq7GStp6Y0ccAvr4To+Z/zecRAGe7+OSvH9DbiaHpAKwEi2KQbmpWMOYsdNt+TsdmSw=="], + + "@jsr/std__fmt": ["@jsr/std__fmt@1.0.9", "https://npm.jsr.io/~/11/@jsr/std__fmt/1.0.9.tgz", {}, "sha512-YFJJMozmORj2K91c5J9opWeh0VUwrd+Mwb7Pr0FkVCAKVLu2UhT4LyvJqWiyUT+eF+MdfqQ9F7RtQj4bXn9Smw=="], + + "@jsr/std__fs": ["@jsr/std__fs@1.0.22", "https://npm.jsr.io/~/11/@jsr/std__fs/1.0.22.tgz", { "dependencies": { "@jsr/std__internal": "^1.0.12", "@jsr/std__path": "^1.1.4" } }, "sha512-PvDtgT25IqhFEX2LjQI0aTz/Wg61jCtJ8l19fE9MUSvSmtw57Kzr6sM7GcCsSrsZEdQ7wjLfXvvhy8irta4Zww=="], + + "@jsr/std__html": ["@jsr/std__html@1.0.5", "https://npm.jsr.io/~/11/@jsr/std__html/1.0.5.tgz", {}, "sha512-8ypLaw6ORY7jisEvsXOS/D631/pMCX78mV7fyromfzJXxqb35OUNCBC2E4Ca0goKQJW8I2XhEgoFu0ZXaIiGvA=="], + + "@jsr/std__http": ["@jsr/std__http@1.0.24", "https://npm.jsr.io/~/11/@jsr/std__http/1.0.24.tgz", { "dependencies": { "@jsr/std__cli": "^1.0.27", "@jsr/std__encoding": "^1.0.10", "@jsr/std__fmt": "^1.0.9", "@jsr/std__fs": "^1.0.22", "@jsr/std__html": "^1.0.5", "@jsr/std__media-types": "^1.1.0", "@jsr/std__net": "^1.0.6", "@jsr/std__path": "^1.1.4", "@jsr/std__streams": "^1.0.17" } }, "sha512-mfUI8vAkMVvf0wYxkZd9ZKfwFryLanHe+nbvxfFPkNO24B2IY6knkBJNN28cTZ8SITh8t8rv56Cx5uOAc0uGFg=="], + + "@jsr/std__internal": ["@jsr/std__internal@1.0.12", "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.12.tgz", {}, "sha512-6xReMW9p+paJgqoFRpOE2nogJFvzPfaLHLIlyADYjKMUcwDyjKZxryIbgcU+gxiTygn8yCjld1HoI0ET4/iZeA=="], + + "@jsr/std__media-types": ["@jsr/std__media-types@1.1.0", "https://npm.jsr.io/~/11/@jsr/std__media-types/1.1.0.tgz", {}, "sha512-dHvaxHL7ENWnltgL653uo3KnKFse3ZbopZop2gqsT7yrscx7irZEClu5Cba7gMPPRk4Lg1FbriNcaBViM2RSBw=="], + + "@jsr/std__net": ["@jsr/std__net@1.0.6", "https://npm.jsr.io/~/11/@jsr/std__net/1.0.6.tgz", {}, "sha512-mh27Fw4UMCjGSIMoOhjia5cS5fNP9M9DZYhGB7EYSZNnzf/eguFiarii/W4oDwYMmnxCMouUzhc6Y7jFuwTzcg=="], + + "@jsr/std__path": ["@jsr/std__path@1.1.4", "https://npm.jsr.io/~/11/@jsr/std__path/1.1.4.tgz", { "dependencies": { "@jsr/std__internal": "^1.0.12" } }, "sha512-SK4u9H6NVTfolhPdlvdYXfNFefy1W04AEHWJydryYbk+xqzNiVmr5o7TLJLJFqwHXuwMRhwrn+mcYeUfS0YFaA=="], + + "@jsr/std__streams": ["@jsr/std__streams@1.0.17", "https://npm.jsr.io/~/11/@jsr/std__streams/1.0.17.tgz", { "dependencies": { "@jsr/std__bytes": "^1.0.6" } }, "sha512-LnPlWk20mDIV5/nqoUomAB8umOimfGEyWRApxLgekXFuqKGDsGpUAi58amieVU2XAGNclmUOtQOcQ/qOl3PNFg=="], + + "@mastrojs/markdown": ["@jsr/mastrojs__markdown@0.1.4", "https://npm.jsr.io/~/11/@jsr/mastrojs__markdown/0.1.4.tgz", { "dependencies": { "@jsr/mastrojs__mastro": "0", "js-yaml": "^4.1.0", "micromark": "^4.0.2", "micromark-extension-gfm": "^3.0.0" } }, "sha512-wewX6u/Rw19azjAl6Km0tO97IuAllTA+Y9OwYZdG6qShtkTTsx5Kx9Cr2Qo99+1bUd58ElU/qKgKgrzqYPzO+A=="], + + "@mastrojs/mastro": ["@jsr/mastrojs__mastro@0.6.4", "https://npm.jsr.io/~/11/@jsr/mastrojs__mastro/0.6.4.tgz", { "dependencies": { "@jsr/std__http": "^1.0.19", "@jsr/std__media-types": "^1.1.0", "@jsr/std__path": "1.1.4", "ts-blank-space": "0.6.1" } }, "sha512-w1YnGd++Ev3xq/E6Lzy8mtNKCMBoe2dBxpv4AjNJsdAr2XqFlX0z74Tno3UmuYZ7Ijzmr4YnOK/iEAGe1L3NEg=="], + + "@remix-run/node-fetch-server": ["@remix-run/node-fetch-server@0.11.0", "", {}, "sha512-nCrFHVxDFioSHc0g/3m5ztwgjBt7g8qh/UwmYkDjuMePKFepMKfNGgH5S6L7iXKX+jUrf3ooVmhx3NGIoa9iYA=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "ts-blank-space": ["ts-blank-space@0.6.1", "", { "dependencies": { "typescript": "5.1.6 - 5.8.x" } }, "sha512-LcM3W5HEyzTaXUeQITV8ploUOGe+zuuoFYsCfPscFLhx3bZn2sSfHMKxsULVG/zA7an9UhReiHv4Kk/6QzlpXQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "ts-blank-space/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + } +} diff --git a/components/Footer.ts b/components/Footer.ts new file mode 100644 index 0000000..22b4ede --- /dev/null +++ b/components/Footer.ts @@ -0,0 +1,12 @@ +import { html } from "@mastrojs/mastro"; + +export const Footer = () => + html` +
+
+ Check us out + on GitHub. + © ${new Date().getFullYear()} +
+
+ `; diff --git a/components/Header.ts b/components/Header.ts new file mode 100644 index 0000000..55addd8 --- /dev/null +++ b/components/Header.ts @@ -0,0 +1,7 @@ +import { html } from "@mastrojs/mastro"; + +export const Header = () => html` +
+
jens.pub
+
+`; diff --git a/components/Layout.ts b/components/Layout.ts new file mode 100644 index 0000000..149b8f7 --- /dev/null +++ b/components/Layout.ts @@ -0,0 +1,31 @@ +import { ghPagesBasePath, html, type Html } from "@mastrojs/mastro"; +import { Header } from "./Header.ts"; +import { Footer } from "./Footer.ts"; + +export const basePath = ghPagesBasePath(); + +interface Props { + title?: string; + children: Html; +} + +export const Layout = (props: Props) => + html` + + + + ${props.title} + + + + + ${Header()} + +
+ ${props.children} +
+ + ${Footer()} + + + `; diff --git a/data/posts/2024-01-30-hello-world.md b/data/posts/2024-01-30-hello-world.md new file mode 100644 index 0000000..6e533bf --- /dev/null +++ b/data/posts/2024-01-30-hello-world.md @@ -0,0 +1,28 @@ +--- +title: Hello World +date: 2024-01-30 +--- + +Markdown is just a simpler syntax for the most commonly used HTML +elements when writing body text. + +A blank line, like above, marks a new paragraph (HTML `

`). +A line starting with `##` is an HTML `

`: + +## Lists + +An example of an unordered list: + +- item one +- item two + +And an ordered list: + +1. item one +2. item two + + +## More info + +See [CommonMark](https://commonmark.org) for more +information about Markdown. diff --git a/data/posts/2024-01-31-second-post.md b/data/posts/2024-01-31-second-post.md new file mode 100644 index 0000000..bab6f88 --- /dev/null +++ b/data/posts/2024-01-31-second-post.md @@ -0,0 +1,6 @@ +--- +title: Second Post +date: 2024-01-31 +--- + +This is our second blog post. diff --git a/package.json b/package.json new file mode 100644 index 0000000..a1acea4 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "my-website", + "type": "module", + "scripts": { + "start": "node --watch server.ts", + "generate": "node node_modules/@mastrojs/mastro/src/generator.js", + "check": "tsc" + }, + "dependencies": { + "@mastrojs/mastro": "npm:@jsr/mastrojs__mastro@^0", + "@remix-run/node-fetch-server": "^0.11", + "@mastrojs/markdown": "npm:@jsr/mastrojs__markdown@^0" + }, + "devDependencies": { + "@types/node": "^24", + "typescript": "^5" + }, + "engines": { + "node": ">=24.12" + }, + "volta": { + "node": "24.12.0" + } +} \ No newline at end of file diff --git a/routes/index.server.ts b/routes/index.server.ts new file mode 100644 index 0000000..7dcff00 --- /dev/null +++ b/routes/index.server.ts @@ -0,0 +1,88 @@ +import { html, htmlToResponse } from "@mastrojs/mastro"; +import { Layout } from "../components/Layout.ts"; + +export const GET = () => + htmlToResponse( + Layout({ + title: "Home", + children: html` + + +

Common HTML elements

+

+ Let's go through the most important HTML elements to + structure your content: +

+ +

Paragraphs

+

The p element marks a paragraph of text.

+ +

Headings

+

+ At the very top of the body, we have the heading of this page + in an h1 element. This is what search engines (like Google) + and screen readers (used by visually impaired readers) look for + when they want to know what the page's title is. Therefore, you + should only ever have one h1 element on any given page. +

+

+ The h2 element is a sub-heading. HTML has h1 up to h6 elements, + to mark progressively deeper nested sub-headings. You should + use those to mark the structure of your page. All headings + together should act like a table of contents for your page. +

+ +

Lists

+

+ Let's add an ordered list + (meaning the list markers will be numbers): +

+
    +
  1. list item one
  2. +
  3. list item two
  4. +
  5. list item three
  6. +
+

+ and an unordered list + (the list markers will be bullet points): +

+ + +

Formatting

+

+ Note how all elements introduced so far cause a line-break + before and after them? That's because they are so-called + block elements. +

+

+ However, links (like the a element we just saw), + emphasis (rendered as italics), and + strong emphasis (rendered bold), + are all inline elements. That means they don't cause + any line-breaks by default. +

+ +

An image

+ A chair +

+ We will add an image file chair.jpg later. +

+

+ For now, note the alt attribute on the image. It is required + and contains "alternative text" that is read to visually + impaired readers, or shown if the image fails to load. +

+

+ If the image is relevant content, the alt text should + therefore be a brief description of what's in the image. + If the image is just decoration, you should use alt="". +

+ `, + }), + ); diff --git a/routes/news/[slug]/index.server.ts b/routes/news/[slug]/index.server.ts new file mode 100644 index 0000000..0909013 --- /dev/null +++ b/routes/news/[slug]/index.server.ts @@ -0,0 +1,19 @@ +import { getParams, htmlToResponse, readDir } from "@mastrojs/mastro"; +import { readMarkdownFile } from "@mastrojs/markdown"; +import { Layout } from "../../../components/Layout.ts"; + +export const GET = async (req: Request) => { + const { slug } = getParams(req); + const post = await readMarkdownFile(`data/posts/${slug}.md`); + return htmlToResponse( + Layout({ + title: post.meta.title, + children: post.content, + }), + ); +}; + +export const getStaticPaths = async () => { + const posts = await readDir("data/posts/"); + return posts.map((p) => "/news/" + p.slice(0, -3) + "/"); +}; diff --git a/routes/news/index.server.ts b/routes/news/index.server.ts new file mode 100644 index 0000000..830a4db --- /dev/null +++ b/routes/news/index.server.ts @@ -0,0 +1,21 @@ +import { html, htmlToResponse } from "@mastrojs/mastro"; +import { readMarkdownFiles } from "@mastrojs/markdown"; +import { Layout } from "../../components/Layout.ts"; + +export const GET = async () => { + const posts = await readMarkdownFiles("data/posts/*.md"); + return htmlToResponse( + Layout({ + title: "News", + children: posts.map((post) => + html` +

+ + ${post.meta.title} + +

+ ` + ), + }), + ); +}; diff --git a/routes/styles.css b/routes/styles.css new file mode 100644 index 0000000..de2ad1b --- /dev/null +++ b/routes/styles.css @@ -0,0 +1,129 @@ +/* 1. Use a more-intuitive box-sizing model */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* 2. Remove default margin */ +*:not(dialog) { + margin: 0; +} + +/* 3. Enable keyword animations */ +@media (prefers-reduced-motion: no-preference) { + html { + interpolate-size: allow-keywords; + } +} + +body { + /* 4. Add accessible line-height */ + line-height: 1.5; + /* 5. Improve text rendering */ + -webkit-font-smoothing: antialiased; +} + +/* 6. Improve media defaults */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +/* 7. Inherit fonts for form controls */ +input, +button, +textarea, +select { + font: inherit; +} + +/* 8. Avoid text overflows */ +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +/* 9. Improve line wrapping */ +p { + text-wrap: pretty; +} +h1, +h2, +h3, +h4, +h5, +h6 { + text-wrap: balance; +} + +/* + 10. Create a root stacking context +*/ +#root, +#__next { + isolation: isolate; +} + +/* + real css +*/ + +body { + display: grid; + grid-template-rows: auto 1fr auto; + min-height: 100vh; + font-family: Helvetica, Arial, sans-serif; + font-size: 18px; + margin: 0 auto; + --brand-color: rebeccapurple; +} + +header { + background-color: var(--brand-color); + color: whitesmoke; + font-weight: bold; + font-size: 50px; + padding: 1.5em 1em 1em 1em; +} + +main { + padding: 1em; +} + +footer { + background-color: var(--brand-color); + color: whitesmoke; + padding: 2em 1em; + margin-top: 3em; +} + +p { + line-height: 1.3; +} + +header > div, +main, +footer > div { + width: 100%; + max-width: 30rem; + margin: 0 auto; +} + +header > a, +a:visited { + color: white; +} + +a:hover { + text-decoration-line: none; +} diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..fac8350 --- /dev/null +++ b/server.ts @@ -0,0 +1,15 @@ +import * as http from "node:http"; +import { createRequestListener } from "@remix-run/node-fetch-server"; +import mastro from "@mastrojs/mastro/server"; + +const port = 8000; + +const server = http.createServer(createRequestListener(mastro.fetch)); + +server.on("error", (e) => { + console.error(e); +}); + +server.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..826c1a4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "module": "NodeNext", + "moduleResolution": "nodenext", + "noEmit": true, + "skipLibCheck": true, + "strict": true, + "verbatimModuleSyntax": true, + + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true + } +}