From 7396c7288dd867e621a809fa5b1d1d72e4ce963e Mon Sep 17 00:00:00 2001 From: xylophonez Date: Fri, 27 Mar 2026 12:05:15 +0000 Subject: [PATCH] init --- .gitignore | 26 + AGENTS.md | 18 + index.html | 12 + package-lock.json | 2167 +++++++++++++++++++++++++++++++++++ package.json | 34 + scripts/deploy-up.mjs | 47 + scripts/deploy.mjs | 356 ++++++ scripts/generate-wallet.mjs | 25 + scripts/ship.mjs | 100 ++ src/App.tsx | 248 ++++ src/config.ts | 6 + src/lib.ts | 185 +++ src/main.tsx | 13 + src/styles.css | 303 +++++ src/types.ts | 110 ++ tsconfig.app.json | 20 + tsconfig.json | 7 + tsconfig.node.json | 11 + vite.config.d.ts | 2 + vite.config.js | 6 + vite.config.ts | 7 + 21 files changed, 3703 insertions(+) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/deploy-up.mjs create mode 100644 scripts/deploy.mjs create mode 100644 scripts/generate-wallet.mjs create mode 100644 scripts/ship.mjs create mode 100644 src/App.tsx create mode 100644 src/config.ts create mode 100644 src/lib.ts create mode 100644 src/main.tsx create mode 100644 src/styles.css create mode 100644 src/types.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.d.ts create mode 100644 vite.config.js create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ebce92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +node_modules/ +dist/ +coverage/ + +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +*.tsbuildinfo +.vite/ +.cache/ +.eslintcache + +.DS_Store +Thumbs.db + +.idea/ +.vscode/ + +.env +.env.local +.env.*.local + +wallet.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..15feef2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ +Look at bundle-uploader/arghost.js for a guide on how the manifest to power this blog is generated. + +We are starting with arweave.net/VKzmyjIucGm2t9XBYNXpP0aolCD10CfEkivJvRnTpp4 as the manifest. + +Inside you have a JSON of arweave.net txids for the blog, which need rendering as an index and as /slug -> load single post. + +We want: + +* Markdown -> html engine, use something that exists and is the robust standard +* A minimal blog template. Light theme, only black and white. +* Pretty individual post view, with metadata from the manifest +* Nice index view + +The look and feel is a modern, elegant, developer-focused light-theme blog. No rounded corners. + +Ideal flow going forward: I update some const with the manifest view whenever I make an update to the blog. + +The blog is called Hyperzine. diff --git a/index.html b/index.html new file mode 100644 index 0000000..7127fae --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + hyperzine + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1610dbf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2167 @@ +{ + "name": "hyperzine", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hyperzine", + "version": "0.0.0", + "dependencies": { + "arweave": "^1.15.7", + "dompurify": "^3.2.6", + "marked": "^15.0.12", + "mime-types": "^3.0.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.5.3", + "warp-arbundles": "^1.0.4", + "yaml": "^2.8.1" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "~5.8.3", + "vite": "^6.3.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/arconnect": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/arconnect/-/arconnect-0.4.2.tgz", + "integrity": "sha512-Jkpd4QL3TVqnd3U683gzXmZUVqBUy17DdJDuL/3D9rkysLgX6ymJ2e+sR+xyZF5Rh42CBqDXWNMmCjBXeP7Gbw==", + "license": "MIT", + "dependencies": { + "arweave": "^1.10.13" + } + }, + "node_modules/arweave": { + "version": "1.15.7", + "resolved": "https://registry.npmmirror.com/arweave/-/arweave-1.15.7.tgz", + "integrity": "sha512-F+Y4iWU1qea9IsKQ/YNmLsY4DHQVsaJBuhEbFxQn9cfGHOmtXE+bwo14oY8xqymsqSNf/e1PeIfLk7G7qN/hVA==", + "license": "MIT", + "dependencies": { + "arconnect": "^0.4.2", + "asn1.js": "^5.4.1", + "base64-js": "^1.5.1", + "bignumber.js": "^9.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/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/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.325", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", + "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmmirror.com/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmmirror.com/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.2", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.13.2.tgz", + "integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.2", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.13.2.tgz", + "integrity": "sha512-aR7SUORwTqAW0JDeiWF07e9SBE9qGpByR9I8kJT5h/FrBKxPMS6TiC7rmVO+gC0q52Bx7JnjWe8Z1sR9faN4YA==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/warp-arbundles": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/warp-arbundles/-/warp-arbundles-1.0.4.tgz", + "integrity": "sha512-KeRac/EJ7VOK+v5+PSMh2SrzpCKOAFnJICLlqZWt6qPkDCzVwcrNE5wFxOlEk5U170ewMDAB3e86UHUblevXpw==", + "license": "ISC", + "dependencies": { + "arweave": "^1.13.7", + "base64url": "^3.0.1", + "buffer": "^6.0.3", + "warp-isomorphic": "^1.0.7" + } + }, + "node_modules/warp-isomorphic": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/warp-isomorphic/-/warp-isomorphic-1.0.7.tgz", + "integrity": "sha512-fXHbUXwdYqPm9fRPz8mjv5ndPco09aMQuTe4kXfymzOq8V6F3DLsg9cIafxvjms9/mc6eijzkLBJ63yjEENEjA==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "undici": "^5.19.1" + }, + "engines": { + "node": ">=16.8.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e2b5e99 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "hyperzine", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "wallet:new": "node scripts/generate-wallet.mjs", + "deploy": "node scripts/deploy.mjs --app-name=hyperzine --app-version=1.0.0", + "deploy:up": "npm run build && node scripts/deploy-up.mjs", + "ship": "node scripts/ship.mjs" + }, + "dependencies": { + "arweave": "^1.15.7", + "dompurify": "^3.2.6", + "marked": "^15.0.12", + "mime-types": "^3.0.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.5.3", + "warp-arbundles": "^1.0.4", + "yaml": "^2.8.1" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "~5.8.3", + "vite": "^6.3.5" + } +} diff --git a/scripts/deploy-up.mjs b/scripts/deploy-up.mjs new file mode 100644 index 0000000..6f8695d --- /dev/null +++ b/scripts/deploy-up.mjs @@ -0,0 +1,47 @@ +import path from "node:path"; +import { spawn } from "node:child_process"; + +const root = process.cwd(); + +function parseArg(name) { + const raw = process.argv.find((arg) => arg.startsWith(`--${name}=`)); + if (!raw) return ""; + return raw.slice(name.length + 3).trim(); +} + +function runNodeScript(scriptPath, args) { + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [scriptPath, ...args], { + cwd: root, + stdio: "inherit" + }); + child.on("error", reject); + child.on("close", (code) => { + if (code !== 0) { + reject(new Error(`Deploy failed with exit code ${code}`)); + return; + } + resolve(); + }); + }); +} + +async function main() { + const walletArg = parseArg("wallet"); + const gatewayArg = parseArg("gateway"); + const appName = parseArg("app-name"); + const appVersion = parseArg("app-version"); + + await runNodeScript(path.join(root, "scripts", "deploy.mjs"), [ + "--upload-mode=up", + ...(walletArg ? [`--wallet=${walletArg}`] : []), + ...(gatewayArg ? [`--gateway=${gatewayArg}`] : []), + ...(appName ? [`--app-name=${appName}`] : []), + ...(appVersion ? [`--app-version=${appVersion}`] : []) + ]); +} + +main().catch((error) => { + console.error(error?.message || String(error)); + process.exit(1); +}); diff --git a/scripts/deploy.mjs b/scripts/deploy.mjs new file mode 100644 index 0000000..fea4853 --- /dev/null +++ b/scripts/deploy.mjs @@ -0,0 +1,356 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { gzipSync } from "node:zlib"; +import { spawn } from "node:child_process"; +import Arweave from "arweave"; +import mime from "mime-types"; +import arbundles from "warp-arbundles"; + +const { createData, ArweaveSigner } = arbundles; + +const root = process.cwd(); +const distDir = path.join(root, "dist"); +const sourceConfigPath = path.join(root, "src", "config.ts"); +const DEFAULT_GATEWAY = "https://arweave.net"; +const DEFAULT_UPLOAD_MODE = "arweave"; +const UP_UPLOAD_MODE = "up"; +const SUPPORTED_UPLOAD_MODES = new Set([DEFAULT_UPLOAD_MODE, UP_UPLOAD_MODE]); +const DEFAULT_UP_UPLOAD_SERVICE = "https://up.arweave.net"; + +function parseArg(name) { + const raw = process.argv.find((arg) => arg.startsWith(`--${name}=`)); + if (!raw) return ""; + return raw.slice(name.length + 3).trim(); +} + +function buildGatewayConfig(gateway) { + const parsed = new URL(gateway); + return { + host: parsed.hostname, + port: parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80, + protocol: parsed.protocol.replace(":", "") + }; +} + +async function pathExists(targetPath) { + try { + await fs.access(targetPath); + return true; + } catch { + return false; + } +} + +async function listFiles(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }); + const nested = await Promise.all( + entries.map(async (entry) => { + const absolutePath = path.join(dir, entry.name); + if (entry.isDirectory()) return listFiles(absolutePath); + return [absolutePath]; + }) + ); + return nested.flat(); +} + +function runBinary(command, args, cwd) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd, + stdio: ["ignore", "pipe", "pipe"] + }); + + const stdoutChunks = []; + let stderr = ""; + + child.stdout.on("data", (chunk) => stdoutChunks.push(chunk)); + child.stderr.on("data", (chunk) => { + stderr += chunk.toString(); + }); + + child.on("error", reject); + child.on("close", (code) => { + if (code !== 0) { + reject(new Error(`${command} exited with code ${code}${stderr ? `: ${stderr.trim()}` : ""}`)); + return; + } + resolve({ stdout: Buffer.concat(stdoutChunks), stderr }); + }); + }); +} + +async function createCodeArchiveBuffer() { + const { stdout } = await runBinary( + "tar", + ["--exclude=./node_modules", "--exclude=./wallet.json", "-cf", "-", "."], + root + ); + return gzipSync(stdout); +} + +async function loadWallet(arweave, walletArg) { + const envWallet = process.env.ARWEAVE_WALLET?.trim(); + + if (walletArg) { + const absoluteWalletPath = path.resolve(root, walletArg); + if (!(await pathExists(absoluteWalletPath))) { + throw new Error(`Wallet file not found: ${absoluteWalletPath}`); + } + const jwkRaw = await fs.readFile(absoluteWalletPath, "utf8"); + return { walletPath: absoluteWalletPath, generated: false, jwk: JSON.parse(jwkRaw) }; + } + + if (envWallet) { + const absoluteWalletPath = path.resolve(root, envWallet); + if (!(await pathExists(absoluteWalletPath))) { + throw new Error(`ARWEAVE_WALLET points to a missing file: ${absoluteWalletPath}`); + } + const jwkRaw = await fs.readFile(absoluteWalletPath, "utf8"); + return { walletPath: absoluteWalletPath, generated: false, jwk: JSON.parse(jwkRaw) }; + } + + const defaultWalletPath = path.join(root, "wallet.json"); + if (!(await pathExists(defaultWalletPath))) { + const jwk = await arweave.wallets.generate(); + await fs.writeFile(defaultWalletPath, `${JSON.stringify(jwk, null, 2)}\n`, "utf8"); + return { walletPath: defaultWalletPath, generated: true, jwk }; + } + + const jwkRaw = await fs.readFile(defaultWalletPath, "utf8"); + return { walletPath: defaultWalletPath, generated: false, jwk: JSON.parse(jwkRaw) }; +} + +async function uploadTransaction(arweave, jwk, data, tags) { + const tx = await arweave.createTransaction({ data }, jwk); + for (const [key, value] of tags) tx.addTag(key, value); + + await arweave.transactions.sign(tx, jwk); + const uploader = await arweave.transactions.getUploader(tx); + while (!uploader.isComplete) await uploader.uploadChunk(); + return tx.id; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function isRetriableUploadError(message) { + return ( + message.includes("timeout") || + message.includes("504") || + message.includes("502") || + message.includes("503") || + message.includes("429") || + message.includes("EAI_AGAIN") || + message.includes("ECONNRESET") || + message.includes("failed to fetch") + ); +} + +async function uploadWithUpService(serviceUrl, signer, data, tags) { + const payload = Buffer.isBuffer(data) ? data : Buffer.from(data); + const dataItem = createData(payload, signer, { + tags: tags.map(([name, value]) => ({ name, value })) + }); + await dataItem.sign(signer); + const txId = await dataItem.id; + + for (let attempt = 1; attempt <= 3; attempt += 1) { + try { + const response = await fetch(`${serviceUrl.replace(/\/+$/, "")}/tx`, { + method: "POST", + headers: { "content-type": "application/octet-stream" }, + body: dataItem.getRaw() + }); + + const bodyText = await response.text(); + if (!response.ok) { + throw new Error( + `up upload failed (${response.status}${bodyText ? `): ${bodyText.slice(0, 220)}` : ")"}` + ); + } + + let responseTxId = ""; + try { + const bodyJson = JSON.parse(bodyText); + if (typeof bodyJson?.id === "string") responseTxId = bodyJson.id.trim(); + } catch { + responseTxId = bodyText.trim(); + } + + return responseTxId || txId; + } catch (error) { + const message = error?.message || String(error); + if (!isRetriableUploadError(message) || attempt === 3) throw error; + await sleep(1500 * attempt); + } + } + + throw new Error("up upload failed unexpectedly."); +} + +function resolveUploadMode(uploadModeArg) { + const mode = (uploadModeArg || process.env.ARWEAVE_UPLOAD_MODE || DEFAULT_UPLOAD_MODE).trim(); + if (!SUPPORTED_UPLOAD_MODES.has(mode)) { + throw new Error( + `Unsupported upload mode "${mode}". Supported values: ${[...SUPPORTED_UPLOAD_MODES].join(", ")}` + ); + } + return mode; +} + +async function createUploader(uploadMode, arweave, jwk) { + if (uploadMode === DEFAULT_UPLOAD_MODE) { + return { + mode: DEFAULT_UPLOAD_MODE, + serviceUrl: null, + async beforeUpload() {}, + upload(data, tags) { + return uploadTransaction(arweave, jwk, data, tags); + } + }; + } + + const signer = new ArweaveSigner(jwk); + return { + mode: UP_UPLOAD_MODE, + serviceUrl: DEFAULT_UP_UPLOAD_SERVICE, + async beforeUpload() {}, + upload(data, tags) { + return uploadWithUpService(DEFAULT_UP_UPLOAD_SERVICE, signer, data, tags); + } + }; +} + +async function resolveBlogManifestTxId() { + const source = await fs.readFile(sourceConfigPath, "utf8"); + const match = source.match(/MANIFEST_TX_ID\s*=\s*"([^"]+)"/); + return match?.[1] || ""; +} + +async function resolvePostSlugs(gateway) { + const blogManifestTxId = await resolveBlogManifestTxId(); + if (!blogManifestTxId) return []; + + try { + const response = await fetch(`${gateway.replace(/\/+$/, "")}/${blogManifestTxId}`); + if (!response.ok) return []; + const payload = await response.json(); + if (!Array.isArray(payload?.posts)) return []; + return payload.posts + .map((post) => (typeof post?.slug === "string" ? post.slug.trim() : "")) + .filter((slug) => slug.length > 0); + } catch { + return []; + } +} + +async function main() { + const walletArg = parseArg("wallet"); + const gatewayArg = parseArg("gateway"); + const uploadModeArg = parseArg("upload-mode"); + const appName = parseArg("app-name") || "hyperzine"; + const appVersion = parseArg("app-version") || "1.0.0"; + const uploadMode = resolveUploadMode(uploadModeArg); + const gateway = gatewayArg || process.env.ARWEAVE_GATEWAY || DEFAULT_GATEWAY; + + if (!(await pathExists(distDir))) { + throw new Error("dist/ is missing. Run npm run build first."); + } + + const arweave = Arweave.init({ + ...buildGatewayConfig(gateway), + timeout: 30_000, + logging: false + }); + + const { walletPath, generated, jwk } = await loadWallet(arweave, walletArg); + console.log(`${generated ? "Generated" : "Using"} wallet: ${walletPath}`); + console.log(`Gateway: ${gateway}`); + console.log(`Upload mode: ${uploadMode}`); + + const uploader = await createUploader(uploadMode, arweave, jwk); + if (uploader.serviceUrl) console.log(`Upload service: ${uploader.serviceUrl}`); + await uploader.beforeUpload(); + + const filePaths = await listFiles(distDir); + if (!filePaths.length) { + throw new Error("dist/ is empty. Run npm run build first."); + } + + const codeArchiveData = await createCodeArchiveBuffer(); + const codeArchiveId = await uploader.upload(codeArchiveData, [ + ["Content-Type", "application/gzip"], + ["Content-Encoding", "gzip"], + ["App-Name", appName], + ["App-Version", appVersion], + ["Type", "code-archive"] + ]); + console.log(`Uploaded code archive: ${codeArchiveId}`); + + const pathMap = {}; + let indexTxId = ""; + + for (const absolutePath of filePaths) { + const relativePath = path.relative(distDir, absolutePath).replace(/\\/g, "/"); + const contentType = mime.lookup(relativePath) || "application/octet-stream"; + const data = await fs.readFile(absolutePath); + + const txId = await uploader.upload(data, [ + ["Content-Type", String(contentType)], + ["App-Name", appName], + ["App-Version", appVersion], + ["Type", "app-asset"], + ["File-Path", relativePath], + ["code", codeArchiveId] + ]); + + pathMap[relativePath] = { id: txId }; + if (relativePath === "index.html") indexTxId = txId; + console.log(`Uploaded ${relativePath}: ${txId}`); + } + + if (!indexTxId) { + throw new Error("Could not find uploaded index.html. Dist output is invalid for SPA deploy."); + } + + const slugs = await resolvePostSlugs(gateway); + for (const slug of slugs) { + pathMap[slug] = { id: indexTxId }; + pathMap[`${slug}/`] = { id: indexTxId }; + } + + const manifest = { + manifest: "arweave/paths", + version: "0.2.0", + index: { path: "index.html" }, + paths: pathMap + }; + + const manifestId = await uploader.upload(JSON.stringify(manifest), [ + ["Content-Type", "application/x.arweave-manifest+json"], + ["App-Name", appName], + ["App-Version", appVersion], + ["Type", "manifest"], + ["code", codeArchiveId] + ]); + + const appUrl = `${gateway.replace(/\/+$/, "")}/${manifestId}/`; + const codeArchiveUrl = `${gateway.replace(/\/+$/, "")}/${codeArchiveId}`; + + console.log(""); + console.log(`Code Archive ID: ${codeArchiveId}`); + console.log(`Code Archive URL: ${codeArchiveUrl}`); + console.log(`Manifest ID (AppArweaveID): ${manifestId}`); + console.log(`App URL: ${appUrl}`); + if (slugs.length) { + console.log(`Slug route aliases: ${slugs.join(", ")}`); + } else { + console.log("Slug route aliases: none generated."); + } +} + +main().catch((error) => { + console.error(error?.message || String(error)); + process.exit(1); +}); diff --git a/scripts/generate-wallet.mjs b/scripts/generate-wallet.mjs new file mode 100644 index 0000000..025d80e --- /dev/null +++ b/scripts/generate-wallet.mjs @@ -0,0 +1,25 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import Arweave from "arweave"; + +const root = process.cwd(); +const walletPath = path.join(root, "wallet.json"); + +async function main() { + const arweave = Arweave.init({ + host: "arweave.net", + port: 443, + protocol: "https", + timeout: 30_000, + logging: false + }); + + const jwk = await arweave.wallets.generate(); + await fs.writeFile(walletPath, `${JSON.stringify(jwk, null, 2)}\n`, "utf8"); + console.log(`Generated wallet: ${walletPath}`); +} + +main().catch((error) => { + console.error(error?.message || String(error)); + process.exit(1); +}); diff --git a/scripts/ship.mjs b/scripts/ship.mjs new file mode 100644 index 0000000..2aae81b --- /dev/null +++ b/scripts/ship.mjs @@ -0,0 +1,100 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { spawn } from "node:child_process"; + +const root = process.cwd(); +const nodeModulesPath = path.join(root, "node_modules"); +const walletPath = path.join(root, "wallet.json"); + +async function pathExists(targetPath) { + try { + await fs.access(targetPath); + return true; + } catch { + return false; + } +} + +function runCommand(command, args) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: root, + stdio: ["ignore", "pipe", "pipe"] + }); + + let stdout = ""; + let stderr = ""; + + child.stdout.on("data", (chunk) => { + const text = chunk.toString(); + stdout += text; + process.stdout.write(text); + }); + + child.stderr.on("data", (chunk) => { + const text = chunk.toString(); + stderr += text; + process.stderr.write(text); + }); + + child.on("error", reject); + child.on("close", (code) => { + if (code !== 0) { + reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`)); + return; + } + resolve({ stdout, stderr }); + }); + }); +} + +function extractLink(output, label) { + const regex = new RegExp(`${label}:\\s*(https?:\\/\\/\\S+)`); + const match = output.match(regex); + return match?.[1] || ""; +} + +async function ensureDependencies() { + if (await pathExists(nodeModulesPath)) { + console.log("Dependencies present: skipping npm install."); + return; + } + console.log("Dependencies missing: running npm install..."); + await runCommand("npm", ["install"]); +} + +async function ensureWallet() { + if (await pathExists(walletPath)) { + console.log("wallet.json present: skipping wallet:new."); + return; + } + console.log("wallet.json missing: running npm run wallet:new..."); + await runCommand("npm", ["run", "wallet:new"]); +} + +async function main() { + await ensureDependencies(); + await ensureWallet(); + + console.log("Running deploy pipeline: npm run deploy:up"); + const deployResult = await runCommand("npm", ["run", "deploy:up"]); + + const appUrl = extractLink(deployResult.stdout, "App URL"); + const codeArchiveUrl = extractLink(deployResult.stdout, "Code Archive URL"); + + if (!appUrl || !codeArchiveUrl) { + throw new Error( + "Deploy step did not emit required links (App URL and Code Archive URL)." + ); + } + + console.log(""); + console.log("Ship completed."); + console.log(`App URL: ${appUrl}`); + console.log(`Code Archive URL: ${codeArchiveUrl}`); +} + +main().catch((error) => { + console.error(error?.message || String(error)); + process.exit(1); +}); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..774da0c --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,248 @@ +import { useEffect, useMemo, useState } from "react"; +import { Link, Navigate, Route, Routes, useParams } from "react-router-dom"; +import { BLOG_NAME } from "./config"; +import { arweaveUrl, getReadableDate, loadManifest, loadPostContent } from "./lib"; +import type { Frontmatter, ManifestPost } from "./types"; + +type LoadState = "idle" | "loading" | "error"; + +function BlisterLoader() { + return ( +
+ +
+ ); +} + +function App() { + const [posts, setPosts] = useState([]); + const [state, setState] = useState("idle"); + const [error, setError] = useState(""); + + useEffect(() => { + let ignore = false; + + const run = async () => { + try { + setState("loading"); + const data = await loadManifest(); + if (!ignore) { + setPosts( + [...data].sort((a, b) => { + const aDate = a.publishedAt ?? ""; + const bDate = b.publishedAt ?? ""; + return aDate < bDate ? 1 : -1; + }) + ); + setState("idle"); + } + } catch (err) { + if (!ignore) { + setError(err instanceof Error ? err.message : "Failed to load manifest"); + setState("error"); + } + } + }; + + void run(); + return () => { + ignore = true; + }; + }, []); + + return ( +
+
+ +
+
+ + } + /> + } + /> + } /> + +
+
+ ); +} + +function IndexPage({ + posts, + state, + error +}: { + posts: ManifestPost[]; + state: LoadState; + error: string; +}) { + if (state === "loading") return ; + if (state === "error") return

Error: {error}

; + if (posts.length === 0) return

No posts yet.

; + + const hasFeatured = posts.length >= 3; + const featuredPost = hasFeatured ? posts[0] : null; + const gridPosts = hasFeatured ? posts.slice(1) : posts; + + const renderPost = (post: ManifestPost) => { + const publishedDate = getReadableDate(post.publishedAt); + const bannerTxId = post.frontmatter?.banner ?? post.bannerTxId; + + return ( + <> + {bannerTxId && ( + + {`${post.title} + + )} + +

{post.title}

+ +

+ {post.description || post.excerpt || "No description provided."} +

+
+ {publishedDate && {publishedDate}} + {post.readingTime && {post.readingTime} min read} + {post.wordCount && {post.wordCount} words} +
+ + ); + }; + + return ( +
+ {featuredPost && ( +
+ {renderPost(featuredPost)} +
+ )} +
+ {gridPosts.map((post) => ( +
+ {renderPost(post)} +
+ ))} +
+
+ ); +} + +function PostPage({ + posts, + state, + manifestError +}: { + posts: ManifestPost[]; + state: LoadState; + manifestError: string; +}) { + const { slug = "" } = useParams(); + const post = useMemo(() => posts.find((entry) => entry.slug === slug), [posts, slug]); + const [html, setHtml] = useState(""); + const [postFrontmatter, setPostFrontmatter] = useState({}); + const [postState, setPostState] = useState("idle"); + const [postError, setPostError] = useState(""); + const [showPostLoader, setShowPostLoader] = useState(false); + + useEffect(() => { + if (postState !== "loading") { + setShowPostLoader(false); + return; + } + + const timeout = window.setTimeout(() => setShowPostLoader(true), 300); + return () => window.clearTimeout(timeout); + }, [postState]); + + useEffect(() => { + let ignore = false; + if (!post) return; + + const run = async () => { + try { + setPostError(""); + setPostState("loading"); + const { html: nextHtml, frontmatter } = await loadPostContent(post.postTxId); + if (!ignore) { + setHtml(nextHtml); + setPostFrontmatter(frontmatter); + setPostState("idle"); + } + } catch (err) { + if (!ignore) { + setPostError(err instanceof Error ? err.message : "Failed to load post"); + setPostState("error"); + } + } + }; + + void run(); + return () => { + ignore = true; + }; + }, [post]); + + if (state === "loading") return ; + if (state === "error") return

Error: {manifestError}

; + if (!post) return

Post not found.

; + if (postState === "loading") return showPostLoader ? : null; + if (postState === "error") return

Error: {postError}

; + + const title = postFrontmatter.title || post.title; + const description = + postFrontmatter.desc || + postFrontmatter.description || + post.description || + postFrontmatter.excerpt || + post.excerpt || + ""; + const bannerTxId = postFrontmatter.banner || post.frontmatter?.banner || post.bannerTxId; + const publishedDate = getReadableDate(postFrontmatter.date || post.publishedAt); + const updatedDate = getReadableDate(postFrontmatter.updated || post.updated || undefined); + + return ( +
+
+

{title}

+

{description}

+
+ {publishedDate && Published {publishedDate}} + {updatedDate && Updated {updatedDate}} + {post.readingTime && {post.readingTime} min read} + {post.wordCount && {post.wordCount} words} +
+
+ {bannerTxId && ( +
+ {`${title} +
+ )} +
+
+ ); +} + +export default App; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..f852d56 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,6 @@ +export const BLOG_NAME = "hyperzine"; +export const MANIFEST_TX_ID = "Zjc1hce_CgyNrq7qqufrJ20ra7IvNU7t8EnK2QA1EHE"; +export const ARWEAVE_GATEWAY = "https://arweave.net"; + +export const AO_URL = "https://push-1.forward.computer"; +export const AO_PROCESS_ID = "fiZ72JP4b6vOuy9PGzqTJo3JLG_LJRmjfZoI6z8E3n4"; diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..49950b1 --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,185 @@ +import DOMPurify from "dompurify"; +import { marked } from "marked"; +import { parse as parseYaml } from "yaml"; +import { + AO_PROCESS_ID, + AO_URL, + ARWEAVE_GATEWAY, + MANIFEST_TX_ID +} from "./config"; +import { parseManifest, type Frontmatter, type ManifestPost } from "./types"; + +const formatDate = (date: string): string => + new Date(date).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric" + }); + +marked.setOptions({ + gfm: true, + breaks: false +}); + +export const arweaveUrl = (txId: string): string => `${ARWEAVE_GATEWAY}/${txId}`; + +export const getReadableDate = (date?: string | null): string | null => { + if (!date) return null; + const parsed = Date.parse(date); + if (Number.isNaN(parsed)) return null; + return formatDate(date); +}; + +const asObject = (value: unknown): Record | null => + typeof value === "object" && value !== null ? (value as Record) : null; + +const asString = (value: unknown): string | null => + typeof value === "string" && value.length > 0 ? value : null; + +const getLatestManifestFromAo = async (): Promise => { + try { + const pushResponse = await fetch(`${AO_URL}/${AO_PROCESS_ID}~process@1.0/push`, { + method: "POST", + headers: { + "content-type": "application/json", + "signing-format": "ans104", + "accept-bundle": "true", + "require-codec": "application/json" + }, + body: JSON.stringify({ + Type: "Message", + "Data-Protocol": "ao", + Variant: "ao.N.1", + Action: "Get", + target: AO_PROCESS_ID, + data: "1984" + }) + }); + if (!pushResponse.ok) return null; + + const pushPayload = (await pushResponse.json()) as unknown; + const slot = asString(asObject(pushPayload)?.slot); + if (!slot) return null; + + const computeResponse = await fetch(`${AO_URL}/${AO_PROCESS_ID}~process@1.0/compute=${slot}`, { + method: "POST", + headers: { + "content-type": "application/json", + "signing-format": "ans104", + "accept-bundle": "true", + "require-codec": "application/json" + }, + body: JSON.stringify({ + target: AO_PROCESS_ID, + data: "1984" + }) + }); + if (!computeResponse.ok) return null; + + const computePayload = (await computeResponse.json()) as unknown; + const body = asObject(asObject(asObject(computePayload)?.results)?.json)?.body; + const parsedBody = typeof body === "string" ? asObject(JSON.parse(body)) : asObject(body); + const messages = Array.isArray(parsedBody?.Messages) ? parsedBody.Messages : []; + + for (const message of messages) { + const tags = Array.isArray(asObject(message)?.Tags) ? (asObject(message)?.Tags as unknown[]) : []; + for (const tag of tags) { + const tagObject = asObject(tag); + const name = asString(tagObject?.name) ?? asString(tagObject?.Name); + const value = asString(tagObject?.value) ?? asString(tagObject?.Value); + if (name === "LatestManifestId" && value) return value; + } + } + + return null; + } catch { + return null; + } +}; + +export const loadManifest = async (): Promise => { + const manifestTxId = (await getLatestManifestFromAo()) ?? MANIFEST_TX_ID; + const response = await fetch(arweaveUrl(manifestTxId)); + if (!response.ok) { + throw new Error(`Failed to load manifest (${response.status})`); + } + + const payload: unknown = await response.json(); + return parseManifest(payload); +}; + +const isObject = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +const asStringOrUndefined = (value: unknown): string | undefined => + typeof value === "string" ? value : undefined; + +const asBannerTxId = (value: unknown): string | undefined => { + if (typeof value === "string") return value; + if (!isObject(value)) return undefined; + return ( + asStringOrUndefined(value.txId) ?? + asStringOrUndefined(value.id) ?? + asStringOrUndefined(value.src) ?? + asStringOrUndefined(value.url) + ); +}; + +const asStringArray = (value: unknown): string[] => + Array.isArray(value) + ? value.filter((entry): entry is string => typeof entry === "string") + : []; + +const parseFrontmatter = (input: unknown): Frontmatter => { + if (!isObject(input)) return {}; + return { + title: asStringOrUndefined(input.title), + desc: asStringOrUndefined(input.desc), + description: asStringOrUndefined(input.description), + excerpt: asStringOrUndefined(input.excerpt), + slug: asStringOrUndefined(input.slug), + banner: asBannerTxId(input.banner), + date: asStringOrUndefined(input.date), + updated: asStringOrUndefined(input.updated), + tags: asStringArray(input.tags), + categories: asStringArray(input.categories) + }; +}; + +export interface PostContent { + html: string; + frontmatter: Frontmatter; +} + +const FRONTMATTER_PATTERN = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/; + +const splitFrontmatter = ( + markdown: string +): { frontmatter: Frontmatter; content: string } => { + const match = markdown.match(FRONTMATTER_PATTERN); + if (!match) { + return { frontmatter: {}, content: markdown }; + } + + const parsed = parseYaml(match[1]); + const frontmatter = parseFrontmatter(parsed); + return { + frontmatter, + content: markdown.slice(match[0].length) + }; +}; + +export const loadPostContent = async (txId: string): Promise => { + const response = await fetch(arweaveUrl(txId)); + if (!response.ok) { + throw new Error(`Failed to load post (${response.status})`); + } + + const markdown = await response.text(); + const { frontmatter, content } = splitFrontmatter(markdown); + const html = await marked.parse(content); + return { + html: DOMPurify.sanitize(html), + frontmatter + }; +}; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..5e74ac7 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App"; +import "./styles.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..4421d7f --- /dev/null +++ b/src/styles.css @@ -0,0 +1,303 @@ +:root { + color: #0a0a0a; + background: #ffffff; + font-family: "IBM Plex Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + line-height: 1.6; + --brand-red: #f60000; + --brand-purple: #9611ff; + --brand-blue: #86dafe; + --brand-yellow: #fee55f; + --brand-green: #33f22f; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: #ffffff; + color: #111111; +} + +a { + color: #111111; + text-decoration-thickness: 1px; + text-underline-offset: 3px; +} + +.page { + min-height: 100vh; + padding: 24px; +} + +.header { + border-bottom: 2px solid #111111; + padding-bottom: 14px; + margin-bottom: 28px; +} + +.brand { + font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, Consolas, monospace; + font-size: 1.3rem; + font-weight: 600; + letter-spacing: 0.08em; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 10px; +} + +.brand-mark { + display: inline-flex; + gap: 1px; + transform: translateY(2px); +} + +.brand-text { + display: inline-block; + transform: rotate(-3deg); + transform-origin: left center; +} + +.brand-square { + display: inline-block; + width: 10px; + height: 10px; +} + +.brand-square-red { + background: var(--brand-red); +} + +.brand-square-purple { + background: var(--brand-purple); +} + +.brand-square-blue { + background: var(--brand-blue); +} + +.brand-square-yellow { + background: var(--brand-yellow); +} + +.brand-square-green { + background: var(--brand-green); +} + +.content { + max-width: 860px; + margin: 0 auto; +} + +.status { + font-size: 1rem; +} + +.loader-screen { + min-height: calc(100vh - 160px); + display: grid; + place-items: center; +} + +.loader-square { + width: 22px; + height: 22px; + background: var(--brand-red); + animation: blister-flicker 180ms steps(1, end) infinite; +} + +@keyframes blister-flicker { + 0% { + background: var(--brand-red); + } + 20% { + background: var(--brand-purple); + } + 40% { + background: var(--brand-blue); + } + 60% { + background: var(--brand-yellow); + } + 80% { + background: var(--brand-green); + } + 100% { + background: var(--brand-red); + } +} + +.index { + display: grid; + gap: 28px; +} + +.index-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 28px; + row-gap: 28px; +} + +.post-card { + padding-top: 0; +} + +.index-grid .post-card:nth-child(n + 3) { + border-top: 1px solid #111111; + padding-top: 18px; +} + +.post-card-featured { + border-top: 0; + padding-top: 0; +} + +.banner-link { + display: block; + margin-bottom: 14px; +} + +.post-banner { + display: block; + width: 100%; + height: auto; + border: 1px solid #111111; +} + +.post-title-link { + text-decoration: none; +} + +.post-title { + margin: 0; + font-size: 1.8rem; + line-height: 1.2; +} + +.post-description { + margin: 10px 0 12px; + font-size: 1.04rem; +} + +.meta-row { + display: flex; + flex-wrap: wrap; + gap: 0; + font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, Consolas, monospace; + font-size: 0.86rem; +} + +.meta-row > span + span { + position: relative; + margin-left: 14px; + padding-left: 14px; +} + +.meta-row > span + span::before { + content: ""; + position: absolute; + left: 0; + top: 50%; + width: 6px; + height: 1px; + background: #111111; + transform: translateY(-50%); +} + +.post { + max-width: 760px; +} + +.post-topline { + margin-bottom: 16px; +} + +.home-link { + text-decoration: none; + border-bottom: 1px solid #111111; +} + +.post-header h1 { + margin: 0; + line-height: 1.15; + font-size: clamp(2rem, 6vw, 3.4rem); +} + +.post-header p { + margin: 12px 0; + font-size: 1.06rem; +} + +.post-hero { + margin-top: 24px; +} + +.article { + margin-top: 32px; + border-top: 1px solid #111111; + padding-top: 24px; +} + +.article > *:first-child { + margin-top: 0; +} + +.article h2, +.article h3, +.article h4 { + margin-top: 1.9em; + line-height: 1.25; +} + +.article pre { + border: 1px solid #111111; + padding: 14px; + overflow: auto; + background: #ffffff; +} + +.article code { + font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, Consolas, monospace; +} + +.article blockquote { + margin-left: 0; + padding-left: 16px; + border-left: 2px solid #111111; +} + +.article img { + max-width: 100%; + height: auto; +} + +@media (max-width: 740px) { + .page { + padding: 16px; + } + + .index { + grid-template-columns: 1fr; + } + + .index-grid { + grid-template-columns: 1fr; + } + + .index-grid .post-card:nth-child(n + 3) { + border-top: 0; + padding-top: 0; + } + + .index-grid .post-card + .post-card { + border-top: 1px solid #111111; + padding-top: 18px; + } + + .post-title { + font-size: 1.45rem; + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..883f265 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,110 @@ +export interface Frontmatter { + title?: string; + desc?: string; + description?: string; + excerpt?: string; + slug?: string; + banner?: string; + date?: string; + updated?: string; + tags?: string[]; + categories?: string[]; +} + +export interface ManifestPost { + slug: string; + title: string; + description: string; + excerpt?: string; + postTxId: string; + bannerTxId?: string; + publishedAt?: string; + updated?: string | null; + readingTime?: number; + wordCount?: number; + tags?: string[]; + categories?: string[]; + frontmatter?: Frontmatter; +} + +interface ManifestResponse { + posts?: ManifestPost[]; +} + +const isObject = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +const asString = (value: unknown): string | undefined => + typeof value === "string" ? value : undefined; + +const asBannerTxId = (value: unknown): string | undefined => { + if (typeof value === "string") return value; + if (!isObject(value)) return undefined; + return ( + asString(value.txId) ?? + asString(value.id) ?? + asString(value.src) ?? + asString(value.url) + ); +}; + +const asStringArray = (value: unknown): string[] => + Array.isArray(value) + ? value.filter((entry): entry is string => typeof entry === "string") + : []; + +const toFrontmatter = (value: unknown): Frontmatter | undefined => { + if (!isObject(value)) return undefined; + return { + title: asString(value.title), + desc: asString(value.desc), + description: asString(value.description), + excerpt: asString(value.excerpt), + slug: asString(value.slug), + banner: asBannerTxId(value.banner), + date: asString(value.date), + updated: asString(value.updated), + tags: asStringArray(value.tags), + categories: asStringArray(value.categories) + }; +}; + +const toPost = (input: unknown): ManifestPost | null => { + if (!isObject(input)) return null; + + const frontmatter = toFrontmatter(input.frontmatter); + const slug = asString(input.slug) ?? frontmatter?.slug; + const title = asString(input.title) ?? frontmatter?.title ?? "Untitled"; + const postTxId = asString(input.postTxId); + if (!slug || !postTxId) return null; + + return { + slug, + title, + description: + asString(input.description) ?? + frontmatter?.desc ?? + frontmatter?.description ?? + "", + excerpt: asString(input.excerpt) ?? frontmatter?.excerpt, + postTxId, + bannerTxId: asBannerTxId(input.bannerTxId) ?? frontmatter?.banner, + publishedAt: asString(input.publishedAt) ?? frontmatter?.date, + updated: asString(input.updated) ?? frontmatter?.updated, + readingTime: + typeof input.readingTime === "number" ? input.readingTime : undefined, + wordCount: typeof input.wordCount === "number" ? input.wordCount : undefined, + tags: asStringArray(input.tags).length ? asStringArray(input.tags) : frontmatter?.tags ?? [], + categories: asStringArray(input.categories).length + ? asStringArray(input.categories) + : frontmatter?.categories ?? [], + frontmatter + }; +}; + +export const parseManifest = (input: unknown): ManifestPost[] => { + if (!isObject(input)) return []; + const { posts } = input as ManifestResponse; + if (!Array.isArray(posts)) return []; + return posts.map(toPost).filter((post): post is ManifestPost => post !== null); +}; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..0426f7b --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.d.ts b/vite.config.d.ts new file mode 100644 index 0000000..340562a --- /dev/null +++ b/vite.config.d.ts @@ -0,0 +1,2 @@ +declare const _default: import("vite").UserConfig; +export default _default; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..04d7917 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +export default defineConfig({ + base: "./", + plugins: [react()] +}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..3de48b9 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + base: "./", + plugins: [react()] +});