diff --git a/.gitignore b/.gitignore index 34c5a32..09dd067 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,10 @@ target/ # root puts it here. .vite/ +# Sequoia's record of what it has published. Rebuilt from the PDS when it is +# missing, so it is a cache rather than state to keep. +web/.sequoia-state.json + # Astro's generated types and its content cache. Written by every build and by # `astro check`, so a checkout that has not built yet is missing them and that # is fine - nothing reads them but the checker and the build. diff --git a/README.md b/README.md index 826b052..1be1ba8 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,27 @@ CloudFront is invalidated; the script prints both steps. Pushing and serving are separate on purpose — a push is safe from any laptop, and flipping the path is an infra change with an infra review. +### Publishing the blog to ATProto + + ATP_APP_PASSWORD=... web/scripts/publish.sh + +The last step, once the release is confirmed serving — not part of the push. +It writes the blog's posts to lance.blue's own repository as standard.site +records with [sequoia](https://sequoia.pub), configured by `web/sequoia.json`. + +A document record carries the post's address, so the address has to answer +first. The script refuses to publish a post the live site is not already +serving, comparing the page's canonical link rather than its status — a +missing page comes back as the app's own index.html with a 200. `--dry-run` +runs that check and stops. + +The publication itself is made once, by hand: `sequoia init` creates the +`site.standard.publication` record, writes its at:// URI into `sequoia.json`, +and writes the same URI to `web/public/.well-known/site.standard.publication`, +which is what proves the domain owns the publication. Both are committed, and +nothing rewrites them. `sequoia.json` still holds a placeholder, so that step +has not been done. + ## Building the API image services/api/push.sh diff --git a/TODO.md b/TODO.md index c09dacc..4ea832f 100644 --- a/TODO.md +++ b/TODO.md @@ -167,14 +167,15 @@ rediscovering them: Revisit then, and write down what tipped it rather than reaching for the default. Astro renders the pages around the app and does not route inside it. -- [ ] **Blog posts are pages, not records.** `/blog` is markdown in - `web/src/content/blog`, prerendered. The point of it is to be an ATProto - blog — a `site.standard.publication` and one `site.standard.document` - per post in the lance.blue repository, plus the - `/.well-known/site.standard.publication` that proves the domain and the - `` on each page. None of that is - built. The frontmatter field names already match what the publishing - tools expect. +- [ ] **No blog post has a record yet.** The machinery is here: a post with an + `atUri` in its frontmatter renders its ``, + `web/scripts/publish.sh` writes the records with sequoia and refuses to + publish a post the site is not already serving, and `push.sh` uploads the + well-known key as text. What is missing is the one manual step that + starts it — `sequoia init` against the lance.blue account, which creates + the `site.standard.publication` record and fills in the placeholder URI + in `web/sequoia.json`. See README's "Publishing the blog to ATProto". + Until then `/blog` is pages and nothing else. - [ ] **The masthead's account menu sends you to Home, because there is no profile page.** Signed in, the masthead shows the account's avatar and a menu — the handle, Your account, Your camo, the theme control, Sign out diff --git a/web/package-lock.json b/web/package-lock.json index 663b611..9cc287a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -18,6 +18,7 @@ "astro": "^7.2.0", "png-to-ico": "^2.1.8", "prettier-plugin-astro": "^0.14.1", + "sequoia-cli": "^0.5.7", "sharp": "^0.34.2", "typescript": "^5.9.0" }, @@ -391,6 +392,39 @@ "node": ">=22" } }, + "node_modules/@atproto-labs/fetch-node": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@atproto-labs/fetch-node/-/fetch-node-0.2.0.tgz", + "integrity": "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/fetch": "0.2.3", + "@atproto-labs/pipe": "0.1.1", + "ipaddr.js": "^2.1.0", + "undici": "^6.14.1" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto-labs/fetch-node/node_modules/@atproto-labs/fetch": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz", + "integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/pipe": "0.1.1" + } + }, + "node_modules/@atproto-labs/fetch-node/node_modules/@atproto-labs/pipe": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz", + "integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@atproto-labs/handle-resolver": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.4.8.tgz", @@ -407,6 +441,62 @@ "node": ">=22" } }, + "node_modules/@atproto-labs/handle-resolver-node": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver-node/-/handle-resolver-node-0.1.25.tgz", + "integrity": "sha512-NY9WYM2VLd3IuMGRkkmvGBg8xqVEaK/fitv1vD8SMXqFTekdpjOLCCyv7EFtqVHouzmDcL83VOvWRfHVa8V9Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/fetch-node": "0.2.0", + "@atproto-labs/handle-resolver": "0.3.6", + "@atproto/did": "0.3.0" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto-labs/handle-resolver-node/node_modules/@atproto-labs/handle-resolver": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.6.tgz", + "integrity": "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.3.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto-labs/handle-resolver-node/node_modules/@atproto-labs/simple-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz", + "integrity": "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@atproto-labs/handle-resolver-node/node_modules/@atproto-labs/simple-store-memory": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz", + "integrity": "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/simple-store": "0.3.0", + "lru-cache": "^10.2.0" + } + }, + "node_modules/@atproto-labs/handle-resolver-node/node_modules/@atproto/did": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.3.0.tgz", + "integrity": "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + } + }, "node_modules/@atproto-labs/identity-resolver": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.4.7.tgz", @@ -452,6 +542,132 @@ "node": ">=22" } }, + "node_modules/@atproto/api": { + "version": "0.18.21", + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.18.21.tgz", + "integrity": "sha512-s35MIJerGT/pKe2xJtKKswqlIr/ola2r2iURBKBL0Mk1OKe6jP4YvTMh1N2d2PEANFzNNTbKoDaLfJPo2Uvc/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.16", + "@atproto/lexicon": "^0.6.1", + "@atproto/syntax": "^0.4.3", + "@atproto/xrpc": "^0.7.7", + "await-lock": "^2.2.2", + "multiformats": "^9.9.0", + "tlds": "^1.234.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/common-web": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.21.tgz", + "integrity": "sha512-Odq+wdk3YNasGCjjlpl3bCIPvqYHige5DLfMkIffNv/2PI/iIj5ZvAvMvJlJ59OhReKSxtpI0invx5UQPc3+fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "^0.0.15", + "@atproto/lex-json": "^0.0.16", + "@atproto/syntax": "^0.5.4", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/common-web/node_modules/@atproto/syntax": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.5.4.tgz", + "integrity": "sha512-9XJOpMAgsGFxMEIp8nJ8AIWv+krrY1xQMj+wULbbXhQztQV+9aZ0TbG9Jtn3Op2or8Kr6OqyWR4ga9Z189kKDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/lex-data": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.15.tgz", + "integrity": "sha512-ZsbGiaM5S3CnGrcTMbDGON3bLZzCi/Mx9UvcMREKSRujnF68eHgMiXxJqvykP7+QpOX6tYCK93axZkuJVhtSEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "multiformats": "^9.9.0", + "tslib": "^2.8.1", + "uint8arrays": "3.0.0", + "unicode-segmenter": "^0.14.0" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/lex-json": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.16.tgz", + "integrity": "sha512-IgLgQ0krshVlrIYZ+heTBDbCnM3LmAgWvsaYn5MxvKA3LcBot3PG3ptdO8VOweVZ+WgCLuo39cz9EbUmIbqdtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "^0.0.15", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/lexicon": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.2.tgz", + "integrity": "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.18", + "@atproto/syntax": "^0.5.0", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/lexicon/node_modules/@atproto/syntax": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.5.4.tgz", + "integrity": "sha512-9XJOpMAgsGFxMEIp8nJ8AIWv+krrY1xQMj+wULbbXhQztQV+9aZ0TbG9Jtn3Op2or8Kr6OqyWR4ga9Z189kKDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/syntax": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.3.tgz", + "integrity": "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/api/node_modules/@atproto/xrpc": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.7.tgz", + "integrity": "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/lexicon": "^0.6.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/api/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true, + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/@atproto/api/node_modules/uint8arrays": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/@atproto/common": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.7.5.tgz", @@ -777,6 +993,268 @@ "node": ">=22" } }, + "node_modules/@atproto/oauth-client-node": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@atproto/oauth-client-node/-/oauth-client-node-0.3.17.tgz", + "integrity": "sha512-67LNuKAlC35Exe7CB5S0QCAnEqr6fKV9Nvp64jAHFof1N+Vc9Ltt1K9oekE5Ctf7dvpGByrHRF0noUw9l9sWLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/did-resolver": "^0.2.6", + "@atproto-labs/handle-resolver-node": "^0.1.25", + "@atproto-labs/simple-store": "^0.3.0", + "@atproto/did": "^0.3.0", + "@atproto/jwk": "^0.6.0", + "@atproto/jwk-jose": "^0.1.11", + "@atproto/jwk-webcrypto": "^0.2.0", + "@atproto/oauth-client": "^0.6.0", + "@atproto/oauth-types": "^0.6.3" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/did-resolver": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.6.tgz", + "integrity": "sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/fetch": "0.2.3", + "@atproto-labs/pipe": "0.1.1", + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.3.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/fetch": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz", + "integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/pipe": "0.1.1" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/handle-resolver": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.6.tgz", + "integrity": "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.3.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/identity-resolver": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.3.6.tgz", + "integrity": "sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/did-resolver": "0.2.6", + "@atproto-labs/handle-resolver": "0.3.6" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/pipe": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz", + "integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/simple-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz", + "integrity": "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto-labs/simple-store-memory": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz", + "integrity": "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/simple-store": "0.3.0", + "lru-cache": "^10.2.0" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/common-web": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.21.tgz", + "integrity": "sha512-Odq+wdk3YNasGCjjlpl3bCIPvqYHige5DLfMkIffNv/2PI/iIj5ZvAvMvJlJ59OhReKSxtpI0invx5UQPc3+fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "^0.0.15", + "@atproto/lex-json": "^0.0.16", + "@atproto/syntax": "^0.5.4", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/did": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.3.0.tgz", + "integrity": "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/jwk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.6.0.tgz", + "integrity": "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/jwk-jose": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz", + "integrity": "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/jwk": "0.6.0", + "jose": "^5.2.0" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/jwk-webcrypto": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.2.0.tgz", + "integrity": "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/jwk": "0.6.0", + "@atproto/jwk-jose": "0.1.11", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/lex-data": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.15.tgz", + "integrity": "sha512-ZsbGiaM5S3CnGrcTMbDGON3bLZzCi/Mx9UvcMREKSRujnF68eHgMiXxJqvykP7+QpOX6tYCK93axZkuJVhtSEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "multiformats": "^9.9.0", + "tslib": "^2.8.1", + "uint8arrays": "3.0.0", + "unicode-segmenter": "^0.14.0" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/lex-json": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.16.tgz", + "integrity": "sha512-IgLgQ0krshVlrIYZ+heTBDbCnM3LmAgWvsaYn5MxvKA3LcBot3PG3ptdO8VOweVZ+WgCLuo39cz9EbUmIbqdtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "^0.0.15", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/lexicon": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.2.tgz", + "integrity": "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.18", + "@atproto/syntax": "^0.5.0", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/oauth-client": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.6.1.tgz", + "integrity": "sha512-QTLbEFyv7EJuwJf4A8IZnsylK5wwrzrSsxy0INcZf9zktPVQvgckWhSvbfK8alp60M+rwWfQQAlodcCF4WB78A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto-labs/did-resolver": "^0.2.6", + "@atproto-labs/fetch": "^0.2.3", + "@atproto-labs/handle-resolver": "^0.3.6", + "@atproto-labs/identity-resolver": "^0.3.6", + "@atproto-labs/simple-store": "^0.3.0", + "@atproto-labs/simple-store-memory": "^0.1.4", + "@atproto/did": "^0.3.0", + "@atproto/jwk": "^0.6.0", + "@atproto/oauth-types": "^0.6.3", + "@atproto/xrpc": "^0.7.7", + "core-js": "^3", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/oauth-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.6.3.tgz", + "integrity": "sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/did": "^0.3.0", + "@atproto/jwk": "^0.6.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/syntax": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.5.4.tgz", + "integrity": "sha512-9XJOpMAgsGFxMEIp8nJ8AIWv+krrY1xQMj+wULbbXhQztQV+9aZ0TbG9Jtn3Op2or8Kr6OqyWR4ga9Z189kKDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/@atproto/xrpc": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.7.tgz", + "integrity": "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/lexicon": "^0.6.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/oauth-client-node/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true, + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/@atproto/oauth-client-node/node_modules/uint8arrays": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/@atproto/oauth-types": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.7.5.tgz", @@ -3087,6 +3565,13 @@ "node": ">=8.0.0" } }, + "node_modules/await-lock": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", + "dev": true, + "license": "MIT" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -3136,6 +3621,22 @@ "node": "20 || >=22" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cborg": { "version": "4.5.8", "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.5.8.tgz", @@ -3156,6 +3657,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", @@ -3251,6 +3765,19 @@ "node": ">=6" } }, + "node_modules/cmd-ts": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/cmd-ts/-/cmd-ts-0.14.3.tgz", + "integrity": "sha512-i9miaLBHGPn4T4vUFUdAdWoQ9HI8ato6usFrhubO21o/MQGqI6Nsqyd4ncZusT2chvPetNMmTt0JWkbaAOdWPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "debug": "^4.4.1", + "didyoumean": "^1.2.2", + "strip-ansi": "^7.1.0" + } + }, "node_modules/code-block-writer": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", @@ -3413,6 +3940,67 @@ "dev": true, "license": "CC0-1.0" }, + "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/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defu": { "version": "6.1.7", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", @@ -3468,6 +4056,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/diff": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", @@ -3828,6 +4423,24 @@ "dev": true, "license": "ISC" }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/h3": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", @@ -3981,6 +4594,16 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -4007,6 +4630,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -4020,6 +4691,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/iso-datestring-validator": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", @@ -4507,6 +5194,29 @@ ], "license": "MIT" }, + "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/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/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", @@ -4532,6 +5242,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -4542,6 +5262,13 @@ "node": ">=10" } }, + "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/muggle-string": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", @@ -4696,6 +5423,27 @@ "regex-recursion": "^6.0.2" } }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.1.tgz", @@ -4768,6 +5516,33 @@ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/piccolore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", @@ -4895,6 +5670,19 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prettier": { "version": "3.9.6", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", @@ -5112,6 +5900,19 @@ "@rolldown/binding-win32-x64-msvc": "1.2.1" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/s.color": { "version": "0.0.15", "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", @@ -5185,6 +5986,30 @@ "node": ">=10" } }, + "node_modules/sequoia-cli": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/sequoia-cli/-/sequoia-cli-0.5.7.tgz", + "integrity": "sha512-5gvVsS/cS9HNSujjLi+u0nDU3jtq4W6BtdtAwX0OpVaxxXsWJgJVEyFDYCbHm/RlddnQScGWFpcsD3EfguSi7g==", + "dev": true, + "dependencies": { + "@atproto/api": "^0.18.17", + "@atproto/oauth-client-node": "^0.3.16", + "@clack/prompts": "^1.0.0", + "cmd-ts": "^0.14.3", + "glob": "^13.0.0", + "js-yaml": "^4.1.1", + "mime-types": "^2.1.35", + "minimatch": "^10.1.1", + "open": "^11.0.0", + "smol-toml": "^1.6.0" + }, + "bin": { + "sequoia": "dist/index.js" + }, + "peerDependencies": { + "typescript": "^5" + } + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -5452,6 +6277,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tlds": { + "version": "1.261.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", + "dev": true, + "license": "MIT", + "bin": { + "tlds": "bin.js" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -5551,6 +6386,16 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/unicode-segmenter": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz", @@ -6276,6 +7121,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", diff --git a/web/package.json b/web/package.json index e73f64b..42b02e7 100644 --- a/web/package.json +++ b/web/package.json @@ -31,6 +31,7 @@ "astro": "^7.2.0", "png-to-ico": "^2.1.8", "prettier-plugin-astro": "^0.14.1", + "sequoia-cli": "^0.5.7", "sharp": "^0.34.2", "typescript": "^5.9.0" }, diff --git a/web/scripts/publish.sh b/web/scripts/publish.sh new file mode 100755 index 0000000..e9ca51b --- /dev/null +++ b/web/scripts/publish.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Publish the blog to ATProto as standard.site records: one +# site.standard.document per post, in lance.blue's own repository. +# Configuration is web/sequoia.json. +# +# ATP_APP_PASSWORD=... web/scripts/publish.sh [--dry-run] +# +# The last step of a deploy, not part of one. A document record says "this +# post is at this address", so the address has to answer first - otherwise the +# network is told about a page that is not there. This refuses to publish a +# post the live site is not already serving. +# +# Order: web/scripts/push.sh, point infra's site_origin_path at the new +# prefix, apply, invalidate, then run this. ATP_APP_PASSWORD is an app +# password, never an account password; it is never written or printed here. +set -euo pipefail + +cd "$(dirname "$0")/.." + +dry_run="" +if [ $# -gt 0 ]; then + case "$1" in + --dry-run | -n) dry_run=1 ;; + *) + echo "usage: web/scripts/publish.sh [--dry-run]" >&2 + exit 2 + ;; + esac +fi + +sequoia="./node_modules/.bin/sequoia" +[ -x "$sequoia" ] || { + echo "$sequoia is missing. Run: npm --prefix web ci" >&2 + exit 1 +} + +if grep -q PLACEHOLDER sequoia.json; then + echo "sequoia.json still has the placeholder publicationUri." >&2 + echo "Create the publication once, then put its at:// URI there." >&2 + exit 1 +fi + +: "${ATP_APP_PASSWORD:?set it to an app password for the publishing account}" +export ATP_APP_PASSWORD +export ATP_IDENTIFIER="${ATP_IDENTIFIER:-lance.blue}" + +# What publishing would do, and the only place the post URLs come from. A real +# run may skip some of these after syncing state from the PDS, so this is a +# superset of what gets written - the safe direction for a check. +plan="$("$sequoia" publish --dry-run --verbose)" +echo "$plan" + +# Read from the config rather than spelled again here: a second copy is a +# second thing to get wrong. +prefix="$(node -p 'const c = require("./sequoia.json"); c.siteUrl + c.pathPrefix')" +urls="$(printf '%s\n' "$plan" | grep -oE "${prefix}/[^[:space:]]+" | sort -u || true)" +if [ -z "$urls" ]; then + echo "Nothing to publish." + exit 0 +fi + +# A page that is not being served comes back as the app's own index.html with +# a 200 - see the comment in push.sh - so the status proves nothing on its +# own. The canonical link does: only the right page carries the right one. +echo +echo "Checking the live site..." +not_live=0 +while IFS= read -r url; do + if ! body="$(curl -fsS --max-time 20 "$url")"; then + echo " not serving: $url" >&2 + not_live=1 + elif ! printf '%s' "$body" | grep -qF "rel=\"canonical\" href=\"$url\""; then + echo " wrong page at: $url" >&2 + not_live=1 + else + echo " live: $url" + fi +done <<<"$urls" + +if [ "$not_live" -ne 0 ]; then + echo >&2 + echo "Refusing to publish: deploy first, then run this again." >&2 + exit 1 +fi + +if [ -n "$dry_run" ]; then + echo + echo "Dry run: every post is live, nothing published." + exit 0 +fi + +echo +"$sequoia" publish + +echo +echo "Published. Commit the atUri frontmatter sequoia wrote into the posts." diff --git a/web/scripts/publish.test.mjs b/web/scripts/publish.test.mjs new file mode 100644 index 0000000..ad52d72 --- /dev/null +++ b/web/scripts/publish.test.mjs @@ -0,0 +1,48 @@ +/** + * sequoia.json describes the same site Astro builds. + * + * A standard.site document record carries the post's address, and sequoia + * computes it as siteUrl + pathPrefix + "/" + slug. Astro computes the same + * address from its own `site`. Nothing connects the two files, so if either + * moves the records point somewhere that does not serve the post - and + * because the record is keyed by that path, the next publish does not correct + * the old one, it writes a second record beside it. + * + * Run with `npm test`, which builds first: this reads dist/. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFile, readdir } from "node:fs/promises"; + +const url = (path) => new URL(path, import.meta.url); + +const sequoia = JSON.parse(await readFile(url("../sequoia.json"), "utf8")); +const astro = (await import("../astro.config.mjs")).default; + +test("sequoia.json and astro.config.mjs agree on the site", () => { + assert.equal(sequoia.siteUrl, astro.site); + // A trailing slash would make Astro's canonical /blog/x/ while the record + // still claims /blog/x, and the two would stop being the same address. + assert.equal(astro.trailingSlash, "never"); +}); + +test("every built post is canonical at the address its record will claim", async () => { + // dist/ rather than the content directory: drafts are already excluded, and + // this is the page a reader following the record actually gets. + const slugs = await readdir(url("../dist/blog/"), { withFileTypes: true }); + const posts = slugs.filter((e) => e.isDirectory()); + assert.ok(posts.length > 0, "no posts were built"); + + for (const post of posts) { + const html = await readFile( + url(`../dist/blog/${post.name}/index.html`), + "utf8", + ); + // The exact string web/scripts/publish.sh looks for on the live site. + const claimed = `${sequoia.siteUrl}${sequoia.pathPrefix}/${post.name}`; + assert.ok( + html.includes(`rel="canonical" href="${claimed}"`), + `dist/blog/${post.name} is not canonical at ${claimed}`, + ); + } +}); diff --git a/web/sequoia.json b/web/sequoia.json new file mode 100644 index 0000000..803fa84 --- /dev/null +++ b/web/sequoia.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://tangled.org/stevedylan.dev/sequoia/raw/main/sequoia.schema.json", + "siteUrl": "https://lance.blue", + "contentDir": "./src/content/blog", + "pathPrefix": "/blog", + "identity": "lance.blue", + "publicationUri": "at://did:plc:a2j2g42ai6v65qpbvb6hmubi/site.standard.publication/PLACEHOLDER-borborygmus" +}