Compare commits

...

33 commits

Author SHA1 Message Date
Ayo
1dbd3d662c 1.3.78 2026-05-31 18:48:18 +02:00
Ayo
199f3d1429 fix: links to legacy blog 2026-05-31 18:47:57 +02:00
Ayo
74a3a71090 1.3.77 2026-05-31 18:41:01 +02:00
Ayo
7966746711 feat(pgp): copy & download buttons 2026-05-31 18:40:50 +02:00
Ayo
7e0380a324 1.3.76 2026-05-31 17:04:22 +02:00
Ayo
7921721425 feat(pgp): concise description 2026-05-31 17:04:11 +02:00
Ayo
5fa6e68b37 1.3.75 2026-05-31 17:00:38 +02:00
Ayo
a71eb0f975 feat: add 'encryption' purpose for pgp key 2026-05-31 16:28:29 +02:00
Ayo
087403f70f 1.3.74 2026-05-31 13:11:35 +02:00
Ayo
25a83048d5 feat: remove chat link 2026-05-31 13:11:09 +02:00
Ayo
8f09af3829 1.3.73 2026-05-31 10:08:15 +02:00
Ayo
7ff435d619 feat(pgp): concise desc 2026-05-31 10:07:15 +02:00
Ayo
9d21f0282b 1.3.72 2026-05-31 08:42:01 +02:00
Ayo
5cc77e537b fix(pgp): meta descriptions 2026-05-31 08:41:42 +02:00
Ayo
05dd11db99 chore: patch, build, deploy script shortcut 2026-05-31 08:37:36 +02:00
Ayo
2e4cf74810 1.3.71 2026-05-31 08:34:50 +02:00
Ayo
5140c1fe55 feat(pgp): add title and description 2026-05-31 08:34:37 +02:00
Ayo
d9abb4bd53 1.3.70 2026-05-31 02:46:28 +02:00
Ayo
5104dc3176 feat: indicate pgp key expiry date 2026-05-31 02:44:33 +02:00
Ayo
5d2802aca7 feat: copy-able public key 2026-05-31 02:40:12 +02:00
Ayo
f0bb8f9272 feat: pgp key download link 2026-05-31 02:21:03 +02:00
Ayo
e4081c42e4 1.3.69 2026-05-30 23:04:13 +02:00
Ayo
9e4eb6ef15 feat: add PGP key 2026-05-30 23:03:59 +02:00
Ayo
a6f7c1153c feat: add gpg publickey 2026-05-30 22:05:13 +02:00
Ayo
cea29e7a01 chore: use eslint config helpers 2026-05-28 22:12:53 +02:00
Ayo
eaecdbcf23 chore: add eslint & prettier to workspace extensions 2026-05-28 22:09:52 +02:00
Ayo
9d69a6cc67 chore: add vitest-dom & sw test 2026-05-24 15:15:51 +02:00
Ayo
9b4312b49d test: prep-now 2026-05-23 23:26:32 +02:00
Ayo
bb335badf6 chore: add vitest 2026-05-23 23:25:44 +02:00
Ayo
85f9b148de chore: set allowBuild to false 2026-05-23 23:17:28 +02:00
Ayo
ca81b780ce chore: update pnpm & add allowBuild config 2026-05-23 23:16:48 +02:00
Ayo
0f28734a2d chore: add patch build deploy command to readme 2026-05-20 18:08:15 +02:00
Ayo
df5b89e5b1 chore: format 2026-05-17 11:20:17 +02:00
15 changed files with 672 additions and 35 deletions

View file

@ -1,4 +1,8 @@
{
"recommendations": ["astro-build.astro-vscode"],
"recommendations": [
"astro-build.astro-vscode",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"unwantedRecommendations": []
}

View file

@ -28,6 +28,7 @@ $ pnpm i
| `pnpm run build` | generate static files to `dist` directory |
| `pnpm run deploy` | upload to my server |
| `pnpm run prep:now` | back up and clear current `now page` constants |
| `pnpm run patch:build:deploy` | increment version, build, then upload |
## Deployment

View file

@ -5,7 +5,7 @@ import tseslint from 'typescript-eslint'
import astroSwGlobals from '@ayco/astro-sw/globals'
import astroParser from 'astro-eslint-parser'
import { includeIgnoreFile } from '@eslint/compat'
import { includeIgnoreFile } from '@eslint/config-helpers'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

View file

@ -1,7 +1,7 @@
{
"name": "@ayco/personal-website",
"type": "module",
"version": "1.3.68",
"version": "1.3.78",
"private": true,
"scripts": {
"astro": "astro",
@ -15,16 +15,19 @@
"check": "npm run format && npm run lint",
"deploy": "eval $(grep '^HOST' .env) && scp -r dist ayo@$HOST:~/ayco.io-flask",
"patch:build:deploy": "npm version patch && npm run build && npm run deploy",
"pbd": "npm run patch:build:deploy",
"build:preview": "npm run build && astro preview",
"build:deploy": "npm run build && npm run deploy",
"copy:dist": "npm run build && cp -R dist ../ayco.io-flask/",
"prepare": "husky",
"prep:now": "npx jiti ./commands/prep-now.js"
"prep:now": "npx jiti ./commands/prep-now.js",
"test": "vitest run ."
},
"devDependencies": {
"@astrojs/sitemap": "^3.7.2",
"@ayco/astro-sw": "^1.0.0",
"@eslint/compat": "^2.1.0",
"@eslint/config-helpers": "^0.6.0",
"@eslint/js": "^10.0.1",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/simple-icons": "^1.2.82",
@ -52,7 +55,9 @@
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.3",
"unified": "^11.0.5",
"vite-plugin-static-copy": "^4.1.0"
"vite-plugin-static-copy": "^4.1.0",
"vitest": "^4.1.7",
"vitest-dom": "^0.1.1"
},
"lint-staged": {
"*.{js,mjs,astro,ts}": [
@ -63,7 +68,7 @@
"prettier --write"
]
},
"packageManager": "pnpm@10.33.2",
"packageManager": "pnpm@11.2.2",
"dependencies": {
"@ayo-run/status-indicator": "^2.1.2",
"web-component-base": "^4.1.2"

View file

@ -24,6 +24,9 @@ importers:
'@eslint/compat':
specifier: ^2.1.0
version: 2.1.0(eslint@10.4.0(jiti@2.7.0))
'@eslint/config-helpers':
specifier: ^0.6.0
version: 0.6.0
'@eslint/js':
specifier: ^10.0.1
version: 10.0.1(eslint@10.4.0(jiti@2.7.0))
@ -108,6 +111,12 @@ importers:
vite-plugin-static-copy:
specifier: ^4.1.0
version: 4.1.0(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0))
vitest:
specifier: ^4.1.7
version: 4.1.7(@types/node@25.8.0)(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0))
vitest-dom:
specifier: ^0.1.1
version: 0.1.1(vitest@4.1.7(@types/node@25.8.0)(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0)))
packages:
@ -785,9 +794,18 @@ packages:
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/debug@4.1.13':
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
@ -889,6 +907,35 @@ packages:
'@ungap/structured-clone@1.3.1':
resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==}
'@vitest/expect@4.1.7':
resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==}
'@vitest/mocker@4.1.7':
resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.1.7':
resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==}
'@vitest/runner@4.1.7':
resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==}
'@vitest/snapshot@4.1.7':
resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==}
'@vitest/spy@4.1.7':
resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==}
'@vitest/utils@4.1.7':
resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -951,6 +998,10 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
@ -1039,6 +1090,14 @@ packages:
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
chalk@5.6.2:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
@ -1111,6 +1170,9 @@ packages:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie-es@1.2.3:
resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==}
@ -1144,6 +1206,9 @@ packages:
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
engines: {node: '>= 6'}
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@ -1215,6 +1280,9 @@ packages:
resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
engines: {node: '>=0.3.1'}
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@ -1379,6 +1447,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@ -1386,6 +1457,10 @@ packages:
eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@ -1631,6 +1706,10 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
indent-string@5.0.0:
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
engines: {node: '>=12'}
internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
@ -1829,6 +1908,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash-es@4.18.1:
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
log-update@6.1.0:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
@ -2229,6 +2311,10 @@ packages:
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
engines: {node: '>= 20.19.0'}
redent@4.0.0:
resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==}
engines: {node: '>=12'}
reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
@ -2382,6 +2468,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
@ -2413,6 +2502,12 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@ -2455,6 +2550,10 @@ packages:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
strip-indent@4.1.1:
resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
engines: {node: '>=12'}
suf-log@2.5.3:
resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==}
@ -2479,6 +2578,9 @@ packages:
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyclip@0.1.12:
resolution: {integrity: sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==}
engines: {node: ^16.14.0 || >= 17.3.0}
@ -2491,6 +2593,10 @@ packages:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@ -2729,6 +2835,52 @@ packages:
vite:
optional: true
vitest-dom@0.1.1:
resolution: {integrity: sha512-n/bonR2hcRHCE5hlzG/P0yTXTUXx/gPtsaeUWP86ADfwo/+dHDpnTTV14qY7+kevsUbOZFYECu77MXY7AA0QSA==}
peerDependencies:
vitest: '>=0.31.0'
vitest@4.1.7:
resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.7
'@vitest/browser-preview': 4.1.7
'@vitest/browser-webdriverio': 4.1.7
'@vitest/coverage-istanbul': 4.1.7
'@vitest/coverage-v8': 4.1.7
'@vitest/ui': 4.1.7
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
web-component-base@4.1.2:
resolution: {integrity: sha512-Jti4FHgCcwtsFWJ+PPwFNhTm5AJVIHrTXDew0rk2Y3b8EChRIE5xr6fmUni43tOhc6w8HatvR3k+qnxjXr/7Mw==}
@ -2769,6 +2921,11 @@ packages:
engines: {node: '>= 8'}
hasBin: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@ -3340,10 +3497,19 @@ snapshots:
'@shikijs/vscode-textmate@10.0.2': {}
'@standard-schema/spec@1.1.0': {}
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/debug@4.1.13':
dependencies:
'@types/ms': 2.1.0
'@types/deep-eql@4.0.2': {}
'@types/esrecurse@4.3.1': {}
'@types/estree@1.0.8': {}
@ -3479,6 +3645,47 @@ snapshots:
'@ungap/structured-clone@1.3.1': {}
'@vitest/expect@4.1.7':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.7
'@vitest/utils': 4.1.7
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@4.1.7(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0))':
dependencies:
'@vitest/spy': 4.1.7
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0)
'@vitest/pretty-format@4.1.7':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@4.1.7':
dependencies:
'@vitest/utils': 4.1.7
pathe: 2.0.3
'@vitest/snapshot@4.1.7':
dependencies:
'@vitest/pretty-format': 4.1.7
'@vitest/utils': 4.1.7
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.1.7': {}
'@vitest/utils@4.1.7':
dependencies:
'@vitest/pretty-format': 4.1.7
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
acorn-jsx@5.3.2(acorn@8.16.0):
dependencies:
acorn: 8.16.0
@ -3553,6 +3760,8 @@ snapshots:
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
assertion-error@2.0.1: {}
ast-types-flow@0.0.8: {}
astro-eslint-parser@1.4.0:
@ -3736,6 +3945,10 @@ snapshots:
ccount@2.0.1: {}
chai@6.2.2: {}
chalk@5.6.2: {}
character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {}
@ -3812,6 +4025,8 @@ snapshots:
consola@3.4.2: {}
convert-source-map@2.0.0: {}
cookie-es@1.2.3: {}
cookie@1.1.1: {}
@ -3851,6 +4066,8 @@ snapshots:
css-what@6.2.2: {}
css.escape@1.5.1: {}
cssesc@3.0.0: {}
csso@5.0.5:
@ -3916,6 +4133,8 @@ snapshots:
diff@8.0.4: {}
dom-accessibility-api@0.6.3: {}
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
@ -4197,10 +4416,16 @@ snapshots:
estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.9
esutils@2.0.3: {}
eventemitter3@5.0.4: {}
expect-type@1.3.0: {}
exsolve@1.0.8: {}
extend@3.0.2: {}
@ -4504,6 +4729,8 @@ snapshots:
imurmurhash@0.1.4: {}
indent-string@5.0.0: {}
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
@ -4707,6 +4934,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash-es@4.18.1: {}
log-update@6.1.0:
dependencies:
ansi-escapes: 7.3.0
@ -5291,6 +5520,11 @@ snapshots:
readdirp@5.0.0: {}
redent@4.0.0:
dependencies:
indent-string: 5.0.0
strip-indent: 4.1.1
reflect.getprototypeof@1.0.10:
dependencies:
call-bind: 1.0.9
@ -5588,6 +5822,8 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
siginfo@2.0.0: {}
signal-exit@4.1.0: {}
sisteransi@1.0.5: {}
@ -5615,6 +5851,10 @@ snapshots:
space-separated-tokens@2.0.2: {}
stackback@0.0.2: {}
std-env@4.1.0: {}
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@ -5673,6 +5913,8 @@ snapshots:
dependencies:
ansi-regex: 6.2.2
strip-indent@4.1.1: {}
suf-log@2.5.3:
dependencies:
s.color: 0.0.15
@ -5711,6 +5953,8 @@ snapshots:
tiny-inflate@1.0.3: {}
tinybench@2.9.0: {}
tinyclip@0.1.12: {}
tinyexec@1.1.2: {}
@ -5720,6 +5964,8 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyrainbow@3.1.0: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@ -5920,6 +6166,43 @@ snapshots:
optionalDependencies:
vite: 7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0)
vitest-dom@0.1.1(vitest@4.1.7(@types/node@25.8.0)(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0))):
dependencies:
aria-query: 5.3.2
chalk: 5.6.2
css.escape: 1.5.1
dom-accessibility-api: 0.6.3
lodash-es: 4.18.1
redent: 4.0.0
vitest: 4.1.7(@types/node@25.8.0)(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0))
vitest@4.1.7(@types/node@25.8.0)(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.7
'@vitest/mocker': 4.1.7(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0))
'@vitest/pretty-format': 4.1.7
'@vitest/runner': 4.1.7
'@vitest/snapshot': 4.1.7
'@vitest/spy': 4.1.7
'@vitest/utils': 4.1.7
es-module-lexer: 2.1.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
picomatch: 4.0.4
std-env: 4.1.0
tinybench: 2.9.0
tinyexec: 1.1.2
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
vite: 7.3.3(@types/node@25.8.0)(jiti@2.7.0)(yaml@2.9.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.8.0
transitivePeerDependencies:
- msw
web-component-base@4.1.2: {}
web-namespaces@2.0.1: {}
@ -5977,6 +6260,11 @@ snapshots:
dependencies:
isexe: 2.0.0
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
word-wrap@1.2.5: {}
wrap-ansi@10.0.0:

4
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,4 @@
allowBuilds:
esbuild: false
sharp: false
web-component-base: false

13
public/publickey.asc Normal file
View file

@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEahs0NhYJKwYBBAHaRw8BAQdAV2CfblniKxklPgW9eYt2gBl0jMHLwtjrQaY+
BQoWdIa0FkF5byBBeWNvIDxheW9AYXljby5pbz6ImQQTFgoAQRYhBBfxPV6P9zcr
E1RcOGXmv2QVKTxlBQJqGzQ2AhsDBQkB4TOABQsJCAcCAiICBhUKCQgLAgQWAgMB
Ah4HAheAAAoJEGXmv2QVKTxlIeYA/2WLvkDapBbVmGXoACAhqcTN93/CoPyzUbhN
hHE6mmAUAQDHsgCHmh3S/Rn4NRE3Gb41kiPTBWHrlYLTj3Kiw0ASB7g4BGobNDYS
CisGAQQBl1UBBQEBB0C6OEKEIPLaNDOM6JYhvvq0Q4Mk/B1eyZBKF/b4fQvlGgMB
CAeIfgQYFgoAJhYhBBfxPV6P9zcrE1RcOGXmv2QVKTxlBQJqGzQ2AhsMBQkB4TOA
AAoJEGXmv2QVKTxlMSQBAP5ta1kUFp3HAYwcun8qmoiVq1dEJSN1LnI7HlX4ucTl
AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
=MTsv
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -45,7 +45,7 @@ const year = new Date().getFullYear()
<p>
Copyright &#169; 2022-{year}
<a href="/">Ayo Ayco</a>. This website <a
href="https://ayos.blog/stopped-tracking-on-my-sites"
href="https://v1.ayos.blog/stopped-tracking-on-my-sites"
>does not track users</a
>. See the <a href="https://git.ayo.run/ayo/ayco.io-astro">source code.</a>
</p>

View file

@ -36,8 +36,9 @@ import Footer from '../components/Footer.astro'
<p>
In my spare time, I find it fun building <a href="/showcase">projects</a>,
running self-hosted services at <a href="https://ayo.run">ayo.run</a>, and
volunteering to <a href="https://ayos.blog/why-fediverse/" target="_blank"
>Fediverse</a
volunteering to <a
href="https://v1.ayos.blog/why-fediverse/"
target="_blank">Fediverse</a
> projects like <a href="https://elk.zone">elk.zone</a>,
<a href="https://m.webtoo.ls/public/local">webtoo.ls</a>, and <a
href="https://m.webtoo.ls/@vitest">vitest's fedi presence</a
@ -52,11 +53,9 @@ import Footer from '../components/Footer.astro'
<h2 id="contact">Contact info</h2>
<p>My inbox is open to everyone.</p>
<ul>
<li>✉️ Email me: <a href="mailto:hi@ayo.run">hi@ayo.run</a></li>
<li>
💬 Let's chat: <a href="https://chat.ayo.run/join/7IKG-h3nW-pD1H"
>chat.ayo.run</a
>
✉️ Email me: <a href="mailto:ayo@ayco.io">ayo@ayco.io</a> &middot
<small> (<a href="/pgp">PGP key</a>)</small>
</li>
<li>
💬 Signal: <a
@ -68,11 +67,11 @@ import Footer from '../components/Footer.astro'
</ul>
</main>
<Footer />
</Layout>
<style>
ul {
list-style: none;
padding-left: 0;
}
</style>
<style>
ul {
list-style: none;
padding-left: 0;
}
</style>
</Layout>

View file

@ -41,15 +41,6 @@ const avatarSize = 150
<span class="now-label">now</span>
<span class="status">{now.title}</span>
</a>
<a
href="https://chat.ayo.run/join/7IKG-h3nW-pD1H"
class="chat-link action"
>
<status-indicator id="chat-link" pulse status="positive">
Chat
</status-indicator>
</a>
</div>
</div>
</section>
@ -59,9 +50,6 @@ const avatarSize = 150
I care about the <em>Web</em>, and I love to <em>create</em> stuff to <em
>inspire</em
> and <em>serve</em> others.
<!--
<a href="/about">More?</a>
-->
</p>
</section>
<section class="cards-section">

View file

@ -37,7 +37,6 @@ publishedOn = publishedOn === '' ? publishDate : publishedOn
</p>
<Fragment set:html={content} />
</main>
<Footer />
</Layout>

124
src/pages/pgp.astro Normal file
View file

@ -0,0 +1,124 @@
---
import Layout from '../layouts/Layout.astro'
import Footer from '../components/Footer.astro'
const title = 'PGP public key'
const description =
'Use to verify my digital signature or to encrypt messages intended only for me.'
---
<Layout title={"Ayo's " + title} description={description}>
<main>
<h1>My {title}</h1>
<p>{description}</p>
<div class="key-block" role="region" aria-labelledby="public-key">
<div class="btn-wrapper">
<button class="copy-btn btn" aria-label="Copy key to clipboard"
>Copy</button
>
<a href="/publickey.asc" class="btn">Download</a>
</div>
<pre
id="public-key"><code>
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEahs0NhYJKwYBBAHaRw8BAQdAV2CfblniKxklPgW9eYt2gBl0jMHLwtjrQaY+
BQoWdIa0FkF5byBBeWNvIDxheW9AYXljby5pbz6ImQQTFgoAQRYhBBfxPV6P9zcr
E1RcOGXmv2QVKTxlBQJqGzQ2AhsDBQkB4TOABQsJCAcCAiICBhUKCQgLAgQWAgMB
Ah4HAheAAAoJEGXmv2QVKTxlIeYA/2WLvkDapBbVmGXoACAhqcTN93/CoPyzUbhN
hHE6mmAUAQDHsgCHmh3S/Rn4NRE3Gb41kiPTBWHrlYLTj3Kiw0ASB7g4BGobNDYS
CisGAQQBl1UBBQEBB0C6OEKEIPLaNDOM6JYhvvq0Q4Mk/B1eyZBKF/b4fQvlGgMB
CAeIfgQYFgoAJhYhBBfxPV6P9zcrE1RcOGXmv2QVKTxlBQJqGzQ2AhsMBQkB4TOA
AAoJEGXmv2QVKTxlMSQBAP5ta1kUFp3HAYwcun8qmoiVq1dEJSN1LnI7HlX4ucTl
AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
=MTsv
-----END PGP PUBLIC KEY BLOCK-----
</code></pre>
</div>
<p>Expiry Date: 2027-05-30</p>
<p>
Fingerprint: <code
>17F1 3D5E 8FF7 372B 1354 5C38 65E6 BF64 1529 3C65</code>
</p>
<p></p>
</main>
<Footer />
</Layout>
<script>
/* Clipboard copy logic */
document.querySelector('.copy-btn').addEventListener('click', async () => {
const keyText = document.querySelector('#public-key code').innerText.trim()
try {
await navigator.clipboard.writeText(keyText)
const btn = document.querySelector('.copy-btn')
const original = btn.textContent
btn.textContent = 'Copied!'
setTimeout(() => {
btn.textContent = original
}, 2000)
} catch (err) {
console.error('Copy failed', err)
alert('Unable to copy the key. Please copy it manually.')
}
})
</script>
<style>
code {
font-size: var(--font-size-sm);
}
.key-block {
position: relative;
background: #272822;
color: #f8f8f2;
font-family: monospace;
font-size: 0.9rem;
line-height: 1.4;
padding: 1rem 1rem 1rem 1.5rem;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
margin: 1em 0;
}
.key-block code {
display: block;
}
/* buttons */
.btn-wrapper {
position: absolute;
top: 0.5rem;
right: 0.5rem;
}
.btn {
display: inline-block;
position: relative;
background: var(--color-brand-blue-1);
color: #fff;
border: none;
border-radius: 4px;
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
cursor: pointer;
opacity: 0.9;
transition: opacity 0.2s;
text-decoration: none;
}
.btn:hover,
.btn:focus {
opacity: 1;
}
.btn:focus {
outline: 2px solid #0056b3;
outline-offset: 2px;
}
</style>

View file

@ -11,8 +11,8 @@ import Card from '../components/Card.astro'
<main>
<h1><span class="text-gradient">Hobby Projects</span></h1>
<p>
See more of my previous projects at <a href="https://ayos.blog"
>my blog</a
See more of my previous projects at <a
href="https://v1.ayos.blog/projects">my blog</a
>.
</p>
<ul>

121
tests/prep-now.test.ts Normal file
View file

@ -0,0 +1,121 @@
import { readFileSync, writeFileSync, copyFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { consola } from 'consola'
import newNow from '../commands/prep-now'
// Mock file system operations
vi.mock('path', () => ({
resolve: vi.fn(),
readFileSync: vi.fn(),
writeFileSync: vi.fn(),
copyFileSync: vi.fn(),
}))
// Mock consola
vi.mock('consola', () => ({
consola: {
box: vi.fn(),
start: vi.fn(),
success: vi.fn(),
fail: vi.fn(),
error: vi.fn(),
},
}))
// Mock mdToHTML
vi.mock('../command/md-to-html', () => ({
mdToHTML: vi.fn(),
}))
describe('prep-now', () => {
// const mockNow = {
// title: 'Test Title',
// description: 'Test Description',
// publishedOn: '2023-01-01',
// publishDate: '2023-01-01',
// publishState: 'draft',
// content: '# Test Content',
// }
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(resolve).mockImplementation((...paths) => paths.join('/'))
vi.mocked(readFileSync).mockReturnValue('template content')
vi.mocked(writeFileSync).mockImplementation(() => {})
vi.mocked(copyFileSync).mockImplementation(() => {})
vi.mocked(consola.box).mockImplementation(() => {})
vi.mocked(consola.start).mockImplementation(() => {})
vi.mocked(consola.success).mockImplementation(() => {})
vi.mocked(consola.fail).mockImplementation(() => {})
vi.mocked(consola.error).mockImplementation(() => {})
})
it('should create a new now page and clear the original files', async () => {
// Mock the mdToHTML function
const { mdToHTML } = await import('../utils/md-to-html')
vi.mocked(mdToHTML).mockResolvedValue('<h1>Test Content</h1>')
// Mock file paths
vi.mocked(resolve)
.mockReturnValueOnce('/src/constants/now.md')
.mockReturnValueOnce('/src/constants/now.json')
.mockReturnValueOnce('/src/pages/now/and-then/posts/2023-01-01.astro')
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.md')
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.json')
// Mock file content
vi.mocked(readFileSync)
.mockReturnValueOnce('template content')
.mockReturnValueOnce('# Test Content')
// Mock file paths
const __filename = '/src/commands/prep-now.ts'
const __dirname = '/src/commands'
vi.mocked(fileURLToPath).mockReturnValue(__filename)
vi.mocked(dirname).mockReturnValue(__dirname)
// Execute the function
await newNow()
// Verify file operations
expect(writeFileSync).toHaveBeenCalledTimes(2)
expect(copyFileSync).toHaveBeenCalledTimes(2)
expect(consola.success).toHaveBeenCalledWith('now.md cleared')
expect(consola.success).toHaveBeenCalledWith(
'You may now update your Now content and props.\n'
)
})
it('should handle errors gracefully', async () => {
// Mock an error
vi.mocked(writeFileSync).mockImplementation(() => {
throw new Error('Write failed')
})
// Mock file paths
vi.mocked(resolve)
.mockReturnValueOnce('/src/constants/now.md')
.mockReturnValueOnce('/src/constants/now.json')
.mockReturnValueOnce('/src/pages/now/and-then/posts/2023-01-01.astro')
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.md')
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.json')
// Mock file content
vi.mocked(readFileSync)
.mockReturnValueOnce('template content')
.mockReturnValueOnce('# Test Content')
// Execute the function
await newNow()
// Verify error handling
expect(consola.fail).toHaveBeenCalledWith(
'Failed to create a new post from Now'
)
expect(consola.error).toHaveBeenCalled()
})
})

91
tests/sw.test.js Normal file
View file

@ -0,0 +1,91 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { install } from 'vitest-dom'
// Install DOM environment for vitest
install()
// Mock the service worker globals
global.caches = {
keys: vi.fn(),
delete: vi.fn(),
open: vi.fn(),
match: vi.fn(),
}
// Mock the global self object
global.self = {
skipWaiting: vi.fn(),
addEventListener: vi.fn(),
}
// Mock the fetch function
global.fetch = vi.fn()
// Import the service worker module
// Note: We need to use dynamic import to avoid module loading issues
let swModule
beforeEach(async () => {
// Clear all mocks
vi.clearAllMocks()
// Mock the module
swModule = await import('../src/sw.mjs')
})
describe('Service Worker', () => {
describe('cleanOldCaches', () => {
it('should delete old caches', async () => {
const cacheName = 'app-v000'
const oldCacheName = 'app-v001'
global.caches.keys.mockResolvedValue([cacheName, oldCacheName])
global.caches.delete.mockResolvedValue(true)
await swModule.cleanOldCaches()
expect(global.caches.delete).toHaveBeenCalledWith(oldCacheName)
})
})
describe('addResourcesToCache', () => {
it('should add resources to cache', async () => {
const resources = ['/index.html', '/style.css']
const cache = { addAll: vi.fn() }
global.caches.open.mockResolvedValue(cache)
await swModule.addResourcesToCache(resources)
expect(cache.addAll).toHaveBeenCalledWith(resources)
})
})
describe('networkFirst', () => {
it('should return network response when available', async () => {
const request = new Request('/test')
const networkResponse = new Response('network content')
global.fetch.mockResolvedValue(networkResponse)
global.caches.open.mockResolvedValue({ put: vi.fn() })
const result = await swModule.networkFirst({ request })
expect(result).toEqual(networkResponse)
})
it('should return cached response when network fails', async () => {
const request = new Request('/test')
const cachedResponse = new Response('cached content')
global.fetch.mockRejectedValue(new Error('Network error'))
global.caches.open.mockResolvedValue({
match: vi.fn().mockResolvedValue(cachedResponse),
})
const result = await swModule.networkFirst({ request })
expect(result).toEqual(cachedResponse)
})
})
})