Compare commits

...

16 Commits

Author SHA1 Message Date
Arik Chakma
2e06bd1f61 fix: remove roadmap slug 2024-01-06 22:12:15 +06:00
Arik Chakma
41054ba97e feat: replace roadmap slug 2024-01-06 16:28:53 +06:00
Kamran Ahmed
febeb6f586 Minor UI change 2023-12-29 07:21:09 +05:00
Kamran Ahmed
9c82a4d35c Add custom roadmap page 2023-12-29 06:53:56 +05:00
Kamran Ahmed
4a3030948f Fix stats 2023-12-28 11:39:27 +05:00
Kamran Ahmed
3d012f9c64 Fix absolute import 2023-12-28 11:28:42 +05:00
Kamran Ahmed
7856a78bba Refactor best practices 2023-12-28 11:06:28 +05:00
Kamran Ahmed
7b4ced400c Refactor faqs 2023-12-28 11:03:34 +05:00
Kamran Ahmed
bd01586e8e Handle SSR for static pages 2023-12-28 10:56:51 +05:00
Kamran Ahmed
33af126728 Fix best practice topics not loading 2023-12-28 10:41:45 +05:00
Kamran Ahmed
e48fa4f593 Rename 2023-12-28 10:06:48 +05:00
Kamran Ahmed
f00f5d16ea Fix generate-renderer issue 2023-12-28 09:47:29 +05:00
Arik Chakma
4fcf1f6d50 fix: redirect to the error page 2023-12-28 08:24:20 +06:00
Kamran Ahmed
7c1c295c63 Add pre-render 2023-12-27 21:39:03 +05:00
Kamran Ahmed
39e55a06e8 Add stats and health endpoints 2023-12-27 19:55:44 +05:00
Kamran Ahmed
8d6facd983 Update 2023-12-26 14:53:02 +05:00
46 changed files with 800 additions and 292 deletions

View File

@@ -1,10 +1,10 @@
// https://astro.build/config
import sitemap from '@astrojs/sitemap';
import tailwind from '@astrojs/tailwind';
import node from '@astrojs/node';
import compress from 'astro-compress';
import { defineConfig } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links';
import { fileURLToPath } from 'node:url';
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
import react from '@astrojs/react';
@@ -41,9 +41,18 @@ export default defineConfig({
],
],
},
build: {
format: 'file',
},
// @FIXME:
// This should be "hybrid" but there is a bug in the current version of Astro
// that adds trailing slashes to the URLs when using "hybrid" mode.
// ----------------------------------------------
// https://github.com/withastro/astro/issues/7808
// ----------------------------------------------
// For now, we are using "server" mode and then using cloudfront to cache the
// pages and serve them as static.
output: 'server',
adapter: node({
mode: 'standalone',
}),
integrations: [
tailwind({
config: {

View File

@@ -22,6 +22,7 @@
"test:e2e": "playwright test"
},
"dependencies": {
"@astrojs/node": "^7.0.2",
"@astrojs/react": "^3.0.8",
"@astrojs/sitemap": "^3.0.3",
"@astrojs/tailwind": "^5.0.4",
@@ -33,6 +34,7 @@
"astro-compress": "^2.2.3",
"clsx": "^2.0.0",
"dracula-prism": "^2.1.13",
"express": "^4.18.2",
"jose": "^5.1.3",
"js-cookie": "^3.0.5",
"lucide-react": "^0.300.0",

426
pnpm-lock.yaml generated
View File

@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
'@astrojs/node':
specifier: ^7.0.2
version: 7.0.2(astro@4.0.7)
'@astrojs/react':
specifier: ^3.0.8
version: 3.0.8(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)(vite@5.0.10)
@@ -38,6 +41,9 @@ dependencies:
dracula-prism:
specifier: ^2.1.13
version: 2.1.13
express:
specifier: ^4.18.2
version: 4.18.2
jose:
specifier: ^5.1.3
version: 5.1.3
@@ -178,6 +184,18 @@ packages:
- supports-color
dev: false
/@astrojs/node@7.0.2(astro@4.0.7):
resolution: {integrity: sha512-g07KV9JBbUubfzLyu2ql+zu09dW3a7zVX1fDr6UriQdz6BvPh0Y0Qn+DqA5P8JwSKAw93mJDuChDW7JrI7CBJg==}
peerDependencies:
astro: ^4.0.0
dependencies:
astro: 4.0.7(typescript@5.2.2)
send: 0.18.0
server-destroy: 1.0.1
transitivePeerDependencies:
- supports-color
dev: false
/@astrojs/prism@3.0.0:
resolution: {integrity: sha512-g61lZupWq1bYbcBnYZqdjndShr/J3l/oFobBKPA3+qMat146zce3nz2kdO4giGbhYDt4gYdhmoBz0vZJ4sIurQ==}
engines: {node: '>=18.14.1'}
@@ -1720,6 +1738,14 @@ packages:
event-target-shim: 5.0.1
dev: true
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
dev: false
/acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'}
@@ -1828,6 +1854,10 @@ packages:
/argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
/array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: false
/array-iterate@2.0.1:
resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==}
dev: false
@@ -1970,6 +2000,7 @@ packages:
/b4a@1.6.4:
resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
requiresBuild: true
dev: false
/bail@2.0.2:
@@ -1997,6 +2028,7 @@ packages:
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
requiresBuild: true
dependencies:
buffer: 5.7.1
inherits: 2.0.4
@@ -2011,6 +2043,26 @@ packages:
readable-stream: 3.6.2
dev: false
/body-parser@1.20.1:
resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.11.0
raw-body: 2.5.1
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
dev: false
@@ -2064,6 +2116,7 @@ packages:
/buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
requiresBuild: true
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
@@ -2082,6 +2135,11 @@ packages:
semver: 7.5.4
dev: false
/bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
dev: false
/cacache@17.1.4:
resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -2118,6 +2176,14 @@ packages:
responselike: 3.0.0
dev: false
/call-bind@1.0.5:
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
dependencies:
function-bind: 1.1.2
get-intrinsic: 1.2.2
set-function-length: 1.1.1
dev: false
/camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
dependencies:
@@ -2196,6 +2262,7 @@ packages:
/chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
requiresBuild: true
dev: false
/chownr@2.0.0:
@@ -2279,10 +2346,12 @@ packages:
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
requiresBuild: true
dev: false
/color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
requiresBuild: true
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
@@ -2296,6 +2365,7 @@ packages:
/color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
requiresBuild: true
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
@@ -2367,10 +2437,31 @@ packages:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
dev: false
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
dependencies:
safe-buffer: 5.2.1
dev: false
/content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
dev: false
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: false
/cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
dev: false
/cookie@0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
dev: false
/cookie@0.6.0:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
@@ -2566,6 +2657,7 @@ packages:
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
requiresBuild: true
dev: false
/deepmerge-ts@5.1.0:
@@ -2578,6 +2670,15 @@ packages:
engines: {node: '>=10'}
dev: false
/define-data-property@1.1.1:
resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.2.2
gopd: 1.0.1
has-property-descriptors: 1.0.1
dev: false
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -2587,11 +2688,21 @@ packages:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: false
/depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dev: false
/dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
dev: false
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dev: false
/detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
@@ -2601,6 +2712,7 @@ packages:
/detect-libc@2.0.2:
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
engines: {node: '>=8'}
requiresBuild: true
dev: false
/deterministic-object-hash@2.0.2:
@@ -2699,6 +2811,10 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: false
/ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/electron-to-chromium@1.4.565:
resolution: {integrity: sha512-XbMoT6yIvg2xzcbs5hCADi0dXBh4//En3oFXmtPX+jiyyiCTiM9DGFT2SLottjpEs9Z8Mh8SqahbR96MaHfuSg==}
dev: false
@@ -2719,6 +2835,11 @@ packages:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: false
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
dev: false
/encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
requiresBuild: true
@@ -2729,6 +2850,7 @@ packages:
/end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
requiresBuild: true
dependencies:
once: 1.4.0
dev: false
@@ -2832,6 +2954,10 @@ packages:
engines: {node: '>=12'}
dev: false
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@@ -2853,6 +2979,11 @@ packages:
'@types/estree': 1.0.3
dev: false
/etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
dev: false
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@@ -2880,12 +3011,52 @@ packages:
/expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
requiresBuild: true
dev: false
/exponential-backoff@3.1.1:
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
dev: false
/express@4.18.2:
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
engines: {node: '>= 0.10.0'}
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.1
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.5.0
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.2.0
fresh: 0.5.2
http-errors: 2.0.0
merge-descriptors: 1.0.1
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.7
proxy-addr: 2.0.7
qs: 6.11.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.18.0
serve-static: 1.15.0
setprototypeof: 1.2.0
statuses: 2.0.1
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: false
/extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
@@ -2899,6 +3070,7 @@ packages:
/fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
requiresBuild: true
dev: false
/fast-glob@3.3.1:
@@ -2959,6 +3131,21 @@ packages:
dependencies:
to-regex-range: 5.0.1
/finalhandler@1.2.0:
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
engines: {node: '>= 0.8'}
dependencies:
debug: 2.6.9
encodeurl: 1.0.2
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.1
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/find-cache-dir@3.3.2:
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
engines: {node: '>=8'}
@@ -3029,6 +3216,11 @@ packages:
web-streams-polyfill: 4.0.0-beta.3
dev: true
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
dev: false
/fp-and-or@0.1.4:
resolution: {integrity: sha512-+yRYRhpnFPWXSly/6V4Lw9IfOV26uu30kynGJ03PW+MnjOEQe45RZ141QcS0aJehYBYA50GfCDnsRbFJdhssRw==}
engines: {node: '>=10'}
@@ -3038,8 +3230,14 @@ packages:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: false
/fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
dev: false
/fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
requiresBuild: true
dev: false
/fs-extra@10.1.0:
@@ -3119,6 +3317,15 @@ packages:
engines: {node: '>=18'}
dev: false
/get-intrinsic@1.2.2:
resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
dependencies:
function-bind: 1.1.2
has-proto: 1.0.1
has-symbols: 1.0.3
hasown: 2.0.0
dev: false
/get-stdin@8.0.0:
resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
engines: {node: '>=10'}
@@ -3150,6 +3357,7 @@ packages:
/github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
requiresBuild: true
dev: false
/github-slugger@2.0.0:
@@ -3235,6 +3443,12 @@ packages:
pinkie-promise: 2.0.1
dev: true
/gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies:
get-intrinsic: 1.2.2
dev: false
/got@12.6.1:
resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
engines: {node: '>=14.16'}
@@ -3279,6 +3493,22 @@ packages:
engines: {node: '>=8'}
dev: false
/has-property-descriptors@1.0.1:
resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
dependencies:
get-intrinsic: 1.2.2
dev: false
/has-proto@1.0.1:
resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
engines: {node: '>= 0.4'}
dev: false
/has-symbols@1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
dev: false
/has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
dev: false
@@ -3438,6 +3668,17 @@ packages:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
dev: false
/http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
toidentifier: 1.0.1
dev: false
/http-proxy-agent@5.0.0:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
@@ -3487,6 +3728,7 @@ packages:
/iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies:
safer-buffer: 2.1.2
dev: false
@@ -3554,6 +3796,11 @@ packages:
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
dev: false
/ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
dev: false
/is-absolute-url@4.0.1:
resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -3561,6 +3808,7 @@ packages:
/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
requiresBuild: true
dev: false
/is-binary-path@2.1.0:
@@ -4255,6 +4503,15 @@ packages:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
dev: true
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
dev: false
/merge-descriptors@1.0.1:
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
dev: false
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: false
@@ -4263,6 +4520,11 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
/methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
dev: false
/micromark-core-commonmark@2.0.0:
resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==}
dependencies:
@@ -4520,14 +4782,18 @@ packages:
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: true
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
dev: false
/mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
@@ -4548,6 +4814,7 @@ packages:
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
requiresBuild: true
dev: false
/mimic-response@4.0.0:
@@ -4643,6 +4910,7 @@ packages:
/mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
requiresBuild: true
dev: false
/mkdirp@1.0.4:
@@ -4687,6 +4955,7 @@ packages:
/napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
requiresBuild: true
dev: false
/needle@2.9.1:
@@ -4722,12 +4991,14 @@ packages:
/node-abi@3.51.0:
resolution: {integrity: sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==}
engines: {node: '>=10'}
requiresBuild: true
dependencies:
semver: 7.5.4
dev: false
/node-addon-api@6.1.0:
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
requiresBuild: true
dev: false
/node-domexception@1.0.0:
@@ -4944,6 +5215,17 @@ packages:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
/object-inspect@1.13.1:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: false
/on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
dependencies:
ee-first: 1.1.1
dev: false
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -5122,6 +5404,11 @@ packages:
entities: 4.5.0
dev: false
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
dev: false
/pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
dependencies:
@@ -5158,6 +5445,10 @@ packages:
minipass: 7.0.4
dev: false
/path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
dev: false
/path-to-regexp@6.2.1:
resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
dev: false
@@ -5296,6 +5587,7 @@ packages:
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
engines: {node: '>=10'}
hasBin: true
requiresBuild: true
dependencies:
detect-libc: 2.0.2
expand-template: 2.0.3
@@ -5455,8 +5747,17 @@ packages:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
dev: false
/proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
dev: false
/pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
requiresBuild: true
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
@@ -5474,11 +5775,19 @@ packages:
escape-goat: 4.0.0
dev: false
/qs@6.11.0:
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.0.4
dev: false
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
/queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
requiresBuild: true
dev: false
/quick-lru@5.1.1:
@@ -5486,6 +5795,21 @@ packages:
engines: {node: '>=10'}
dev: false
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
dev: false
/raw-body@2.5.1:
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
engines: {node: '>= 0.8'}
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.4.24
unpipe: 1.0.0
dev: false
/rc-config-loader@4.1.3:
resolution: {integrity: sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==}
dependencies:
@@ -5899,6 +6223,39 @@ packages:
lru-cache: 6.0.0
dev: false
/send@0.18.0:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
engines: {node: '>= 0.8.0'}
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
http-errors: 2.0.0
mime: 1.6.0
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
dev: false
/serve-static@1.15.0:
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
engines: {node: '>= 0.8.0'}
dependencies:
encodeurl: 1.0.2
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.18.0
transitivePeerDependencies:
- supports-color
dev: false
/server-destroy@1.0.1:
resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==}
dev: false
@@ -5907,6 +6264,20 @@ packages:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: false
/set-function-length@1.1.1:
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
engines: {node: '>= 0.4'}
dependencies:
define-data-property: 1.1.1
get-intrinsic: 1.2.2
gopd: 1.0.1
has-property-descriptors: 1.0.1
dev: false
/setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
dev: false
/sharp@0.32.6:
resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==}
engines: {node: '>=14.15.0'}
@@ -5949,6 +6320,14 @@ packages:
hast-util-to-html: 9.0.0
dev: false
/side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
call-bind: 1.0.5
get-intrinsic: 1.2.2
object-inspect: 1.13.1
dev: false
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: false
@@ -5974,10 +6353,12 @@ packages:
/simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
requiresBuild: true
dev: false
/simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
requiresBuild: true
dependencies:
decompress-response: 6.0.0
once: 1.4.0
@@ -5986,6 +6367,7 @@ packages:
/simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
requiresBuild: true
dependencies:
is-arrayish: 0.3.2
dev: false
@@ -6099,6 +6481,11 @@ packages:
minipass: 7.0.4
dev: false
/statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
dev: false
/stdin-discarder@0.1.0:
resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -6116,6 +6503,7 @@ packages:
/streamx@2.15.1:
resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==}
requiresBuild: true
dependencies:
fast-fifo: 1.3.2
queue-tick: 1.0.1
@@ -6202,6 +6590,7 @@ packages:
/strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
requiresBuild: true
dev: false
/strip-json-comments@5.0.1:
@@ -6304,6 +6693,7 @@ packages:
/tar-fs@2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
requiresBuild: true
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
@@ -6313,6 +6703,7 @@ packages:
/tar-fs@3.0.4:
resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==}
requiresBuild: true
dependencies:
mkdirp-classic: 0.5.3
pump: 3.0.0
@@ -6322,6 +6713,7 @@ packages:
/tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
requiresBuild: true
dependencies:
bl: 4.1.0
end-of-stream: 1.4.4
@@ -6332,6 +6724,7 @@ packages:
/tar-stream@3.1.6:
resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==}
requiresBuild: true
dependencies:
b4a: 1.6.4
fast-fifo: 1.3.2
@@ -6383,6 +6776,11 @@ packages:
dependencies:
is-number: 7.0.0
/toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
dev: false
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: true
@@ -6435,6 +6833,7 @@ packages:
/tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
requiresBuild: true
dependencies:
safe-buffer: 5.2.1
dev: false
@@ -6453,6 +6852,14 @@ packages:
engines: {node: '>=12.20'}
dev: false
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
dev: false
/typedarray-to-buffer@3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
dependencies:
@@ -6681,6 +7088,11 @@ packages:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
/unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
dev: false
/untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
@@ -6728,6 +7140,11 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
/utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
dev: false
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
@@ -6742,6 +7159,11 @@ packages:
builtins: 5.0.1
dev: false
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false
/vfile-location@5.0.2:
resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==}
dependencies:

View File

@@ -29,4 +29,4 @@ done
# ignore the worktree changes for the editor directory
git update-index --assume-unchanged editor/readonly-editor.tsx
git update-index --assume-unchanged editor/readonly-editor.tsx || true

View File

@@ -14,6 +14,7 @@ type ProgressResponse = {
done: number;
total: number;
isCustomResource: boolean;
roadmapSlug?: string;
};
export type ActivityResponse = {
@@ -52,7 +53,7 @@ export function ActivityPage() {
async function loadActivity() {
const { error, response } = await httpGet<ActivityResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-stats`
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-stats`,
);
if (!response || error) {
@@ -107,6 +108,7 @@ export function ActivityPage() {
.map((roadmap) => (
<ResourceProgress
key={roadmap.id}
roadmapSlug={roadmap.roadmapSlug}
isCustomResource={roadmap.isCustomResource}
doneCount={roadmap.done || 0}
learningCount={roadmap.learning || 0}

View File

@@ -17,6 +17,7 @@ type ResourceProgressType = {
onCleared?: () => void;
showClearButton?: boolean;
isCustomResource: boolean;
roadmapSlug?: string;
};
export function ResourceProgress(props: ResourceProgressType) {
@@ -37,6 +38,7 @@ export function ResourceProgress(props: ResourceProgressType) {
doneCount,
skippedCount,
onCleared,
roadmapSlug,
} = props;
async function clearProgress() {
@@ -46,7 +48,7 @@ export function ResourceProgress(props: ResourceProgressType) {
{
resourceId,
resourceType,
}
},
);
if (error || !response) {
@@ -72,7 +74,7 @@ export function ResourceProgress(props: ResourceProgressType) {
: `/best-practices/${resourceId}`;
if (isCustomResource) {
url = `/r?id=${resourceId}`;
url = `/r/${roadmapSlug}`;
}
const totalMarked = doneCount + skippedCount;

View File

@@ -14,6 +14,7 @@ import { useToast } from '../../hooks/use-toast';
export type TeamResourceConfig = {
isCustomResource: boolean;
roadmapSlug?: string;
title: string;
description?: string;
visibility?: AllowedRoadmapVisibility;
@@ -80,7 +81,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
{
resourceId: roadmapId,
resourceType: 'roadmap',
}
},
);
if (error || !response) {
@@ -114,7 +115,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
resourceId: roadmapId,
resourceType: 'roadmap',
removed: [],
}
},
);
if (error || !response) {
@@ -312,7 +313,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
`${
import.meta.env.PUBLIC_EDITOR_APP_URL
}/${resourceId}`,
'_blank'
'_blank',
);
return;
}
@@ -335,7 +336,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
)}
</div>
);
}
},
)}
</div>
)}

View File

@@ -82,7 +82,7 @@ export function CreateVersion(props: CreateVersionProps) {
return (
<div className={'flex items-center'}>
<a
href={`/r?id=${userVersion._id}`}
href={`/r/${userVersion?.slug}`}
className="flex items-center rounded-md border border-blue-400 bg-gray-50 px-2.5 py-1 text-xs font-medium text-blue-600 hover:bg-blue-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:hover:bg-gray-100 max-sm:hidden sm:text-sm"
>
<Map size="15px" className="mr-1.5" />

View File

@@ -27,6 +27,7 @@ export interface RoadmapDocument {
_id?: string;
title: string;
description?: string;
slug?: string;
creatorId: string;
teamId?: string;
isDiscoverable: boolean;
@@ -145,7 +146,7 @@ export function CreateRoadmapModal(props: CreateRoadmapModalProps) {
name="title"
id="title"
required
className="block text-black w-full rounded-md border border-gray-300 px-2.5 py-2 outline-none focus:border-black sm:text-sm"
className="block w-full rounded-md border border-gray-300 px-2.5 py-2 text-black outline-none focus:border-black sm:text-sm"
placeholder="Enter Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
@@ -165,8 +166,8 @@ export function CreateRoadmapModal(props: CreateRoadmapModalProps) {
name="description"
required
className={cn(
'block text-black h-24 w-full resize-none rounded-md border border-gray-300 px-2.5 py-2 outline-none focus:border-black sm:text-sm',
isInvalidDescription && 'border-red-300 bg-red-100'
'block h-24 w-full resize-none rounded-md border border-gray-300 px-2.5 py-2 text-black outline-none focus:border-black sm:text-sm',
isInvalidDescription && 'border-red-300 bg-red-100',
)}
placeholder="Enter Description"
value={description}

View File

@@ -62,10 +62,11 @@ export function hideRoadmapLoader() {
type CustomRoadmapProps = {
isEmbed?: boolean;
slug?: string;
};
export function CustomRoadmap(props: CustomRoadmapProps) {
const { isEmbed = false } = props;
const { isEmbed = false, slug } = props;
const { id, secret } = getUrlParams() as { id: string; secret: string };
@@ -76,9 +77,11 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
async function getRoadmap() {
setIsLoading(true);
const roadmapUrl = new URL(
`${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap/${id}`,
);
const roadmapUrl = slug
? new URL(
`${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap-by-slug/${slug}`,
)
: new URL(`${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap/${id}`);
if (secret) {
roadmapUrl.searchParams.set('secret', secret);
@@ -102,12 +105,12 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
}
async function trackVisit() {
if (!isLoggedIn() || isEmbed) {
if (!isLoggedIn() || isEmbed || !roadmap) {
return;
}
await httpPost(`${import.meta.env.PUBLIC_API_URL}/v1-visit`, {
resourceId: id,
resourceId: roadmap?._id,
resourceType: 'roadmap',
});
}
@@ -116,9 +119,16 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
getRoadmap().finally(() => {
hideRoadmapLoader();
});
trackVisit().then();
}, []);
useEffect(() => {
if (!roadmap) {
return;
}
trackVisit().then();
}, [roadmap]);
if (isLoading) {
return null;
}

View File

@@ -18,7 +18,7 @@ import { PersonalRoadmapActionDropdown } from './PersonalRoadmapActionDropdown';
import type { GetRoadmapListResponse } from './RoadmapListPage';
import { useState, type Dispatch, type SetStateAction } from 'react';
import { ShareOptionsModal } from '../ShareOptions/ShareOptionsModal';
import {RoadmapIcon} from "../ReactIcons/RoadmapIcon.tsx";
import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx';
type PersonalRoadmapListType = {
roadmaps: GetRoadmapListResponse['personalRoadmaps'];
@@ -37,7 +37,7 @@ export function PersonalRoadmapList(props: PersonalRoadmapListType) {
async function deleteRoadmap(roadmapId: string) {
const { response, error } = await httpDelete<RoadmapDocument[]>(
`${import.meta.env.PUBLIC_API_URL}/v1-delete-roadmap/${roadmapId}`
`${import.meta.env.PUBLIC_API_URL}/v1-delete-roadmap/${roadmapId}`,
);
if (error || !response) {
@@ -61,6 +61,7 @@ export function PersonalRoadmapList(props: PersonalRoadmapListType) {
const shareSettingsModal = selectedRoadmap && (
<ShareOptionsModal
roadmapSlug={selectedRoadmap?.slug}
isDiscoverable={selectedRoadmap.isDiscoverable}
description={selectedRoadmap.description}
visibility={selectedRoadmap.visibility}
@@ -129,7 +130,7 @@ type CustomRoadmapItemProps = {
roadmap: GetRoadmapListResponse['personalRoadmaps'][number];
onRemove: (roadmapId: string) => Promise<void>;
setSelectedRoadmap: (
roadmap: GetRoadmapListResponse['personalRoadmaps'][number] | null
roadmap: GetRoadmapListResponse['personalRoadmaps'][number] | null,
) => void;
};
@@ -183,9 +184,9 @@ function CustomRoadmapItem(props: CustomRoadmapItemProps) {
Edit
</a>
<a
href={`/r?id=${roadmap._id}`}
href={`/r/${roadmap?.slug}`}
className={
'ml-2 flex items-center gap-2 rounded-md border border-blue-400 bg-white px-2 py-1.5 text-xs hover:bg-blue-50 focus:outline-none text-blue-600'
'ml-2 flex items-center gap-2 rounded-md border border-blue-400 bg-white px-2 py-1.5 text-xs text-blue-600 hover:bg-blue-50 focus:outline-none'
}
target={'_blank'}
>

View File

@@ -24,6 +24,7 @@ export function ResourceProgressStats(props: ResourceProgressStatsProps) {
<>
{isSharing && $canManageCurrentRoadmap && $currentRoadmap && (
<ShareOptionsModal
roadmapSlug={$currentRoadmap?.slug}
isDiscoverable={$currentRoadmap.isDiscoverable}
description={$currentRoadmap?.description}
visibility={$currentRoadmap?.visibility}
@@ -47,7 +48,7 @@ export function ResourceProgressStats(props: ResourceProgressStatsProps) {
{
'rounded-bl-md rounded-br-md': isSecondaryBanner,
'rounded-md': !isSecondaryBanner,
}
},
)}
>
<p

View File

@@ -23,6 +23,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
title,
description,
_id: roadmapId,
slug: roadmapSlug,
creator,
team,
visibility,
@@ -78,6 +79,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
>
<ShareSuccess
visibility="public"
roadmapSlug={roadmapSlug}
roadmapId={roadmapId!}
description={description}
onClose={() => setIsSharingWithOthers(false)}
@@ -132,7 +134,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
<ShareRoadmapButton
roadmapId={roadmapId!}
description={description!}
pageUrl={`https://roadmap.sh/r?id=${roadmapId}`}
pageUrl={`https://roadmap.sh/r/${roadmapSlug}`}
allowEmbed={true}
/>
</div>
@@ -141,6 +143,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
<>
{isSharing && $currentRoadmap && (
<ShareOptionsModal
roadmapSlug={$currentRoadmap?.slug}
isDiscoverable={$currentRoadmap.isDiscoverable}
description={$currentRoadmap?.description}
visibility={$currentRoadmap?.visibility}

View File

@@ -91,7 +91,7 @@ export function SharedRoadmapList(props: SharedRoadmapListProps) {
className="relative flex w-full border-t"
>
<a
href={`/r?id=${roadmap._id}`}
href={`/r/=${roadmap?.slug}`}
className="group inline-grid w-full grid-cols-[auto,16px] items-center justify-between gap-2 px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-black"
target={'_blank'}
>

View File

@@ -16,6 +16,7 @@ export type UserProgressResponse = {
total: number;
updatedAt: Date;
isCustomResource: boolean;
roadmapSlug?: string;
team?: {
name: string;
id: string;
@@ -41,7 +42,7 @@ function renderProgress(progressList: UserProgressResponse) {
resourceType: progress.resourceType,
isFavorite: progress.isFavorite,
},
})
}),
);
const totalDone = progress.done + progress.skipped;
@@ -89,7 +90,7 @@ export function FavoriteRoadmaps() {
setIsLoading(true);
const { response: progressList, error } = await httpGet<ProgressResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-hero-roadmaps`
`${import.meta.env.PUBLIC_API_URL}/v1-get-hero-roadmaps`,
);
if (error || !progressList) {
@@ -121,7 +122,7 @@ export function FavoriteRoadmaps() {
const hasProgress = progress?.length > 0;
const customRoadmaps = progress?.filter(
(p) => p.isCustomResource && !p.team?.name
(p) => p.isCustomResource && !p.team?.name,
);
const defaultRoadmaps = progress?.filter((p) => !p.isCustomResource);
const teamRoadmaps: HeroTeamRoadmaps = progress

View File

@@ -172,7 +172,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
customRoadmap.total) *
100
}
url={`/r?id=${customRoadmap.resourceId}`}
url={`/r/${customRoadmap?.roadmapSlug}`}
allowFavorite={false}
/>
);
@@ -187,7 +187,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
const currentTeam: UserProgressResponse[0]['team'] =
teamRoadmaps?.[teamName]?.[0]?.team;
const roadmapsList = teamRoadmaps[teamName].filter(
(roadmap) => !!roadmap.resourceTitle
(roadmap) => !!roadmap.resourceTitle,
);
const canManageTeam = ['admin', 'manager'].includes(currentTeam?.role!);
@@ -242,7 +242,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
customRoadmap.total) *
100
}
url={`/r?id=${customRoadmap.resourceId}`}
url={`/r/${customRoadmap?.roadmapSlug}`}
allowFavorite={false}
/>
);

View File

@@ -1,10 +1,4 @@
import {
type ReactNode,
useCallback,
useState,
useMemo,
useEffect,
} from 'react';
import { type ReactNode, useCallback, useState, useMemo } from 'react';
import { Globe2, Loader2, Lock } from 'lucide-react';
import { type ListFriendsResponse, ShareFriendList } from './ShareFriendList';
import { TransferToTeamList } from './TransferToTeamList';
@@ -37,6 +31,7 @@ type ShareOptionsModalProps = {
teamId?: string;
roadmapId?: string;
description?: string;
roadmapSlug?: string;
onShareSettingsUpdate: OnShareSettingsUpdate;
};
@@ -44,6 +39,7 @@ type ShareOptionsModalProps = {
export function ShareOptionsModal(props: ShareOptionsModalProps) {
const {
roadmapId,
roadmapSlug,
onClose,
isDiscoverable: defaultIsDiscoverable = false,
visibility: defaultVisibility,
@@ -68,10 +64,10 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
const [visibility, setVisibility] = useState(defaultVisibility);
const [isDiscoverable, setIsDiscoverable] = useState(defaultIsDiscoverable);
const [sharedTeamMemberIds, setSharedTeamMemberIds] = useState<string[]>(
defaultSharedMemberIds
defaultSharedMemberIds,
);
const [sharedFriendIds, setSharedFriendIds] = useState<string[]>(
defaultSharedFriendIds
defaultSharedFriendIds,
);
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
@@ -120,7 +116,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
sharedFriendIds,
sharedTeamMemberIds,
isDiscoverable,
}
},
);
if (error) {
@@ -151,7 +147,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
teamId,
sharedTeamMemberIds,
isDiscoverable,
}
},
);
if (error) {
@@ -162,7 +158,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
window.location.reload();
},
[roadmapId]
[roadmapId],
);
if (isSettingsUpdated) {
@@ -173,6 +169,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
bodyClassName="p-4 flex flex-col"
>
<ShareSuccess
roadmapSlug={roadmapSlug}
visibility={visibility}
roadmapId={roadmapId!}
description={description}
@@ -212,11 +209,11 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
setSharedFriendIds([]);
} else if (visibility === 'friends') {
setSharedFriendIds(
defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : []
defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : [],
);
} else if (visibility === 'team' && teamId) {
setSharedTeamMemberIds(
defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : []
defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : [],
);
setSharedFriendIds([]);
} else {
@@ -329,7 +326,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
}
onClick={() => {
handleTransferToTeam(selectedTeamId!, sharedTeamMemberIds).then(
() => null
() => null,
);
}}
>
@@ -374,7 +371,7 @@ function UpdateAction(props: {
className={cn(
'flex min-w-[120px] items-center justify-center gap-1.5 rounded-md border border-gray-900 bg-gray-900 px-4 py-2 text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-75',
disabled && 'border-gray-700 bg-gray-700 text-white hover:bg-gray-700',
className
className,
)}
disabled={disabled}
onClick={onClick}

View File

@@ -4,6 +4,7 @@ import { cn } from '../../lib/classname';
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
type ShareSuccessProps = {
roadmapSlug?: string;
roadmapId: string;
onClose: () => void;
visibility: AllowedRoadmapVisibility;
@@ -13,6 +14,7 @@ type ShareSuccessProps = {
export function ShareSuccess(props: ShareSuccessProps) {
const {
roadmapSlug,
roadmapId,
onClose,
description,
@@ -23,7 +25,9 @@ export function ShareSuccess(props: ShareSuccessProps) {
const baseUrl = import.meta.env.DEV
? 'http://localhost:3000'
: 'https://roadmap.sh';
const shareLink = `${baseUrl}/r?id=${roadmapId}`;
const shareLink = roadmapSlug
? `${baseUrl}/r/${roadmapSlug}`
: `${baseUrl}/r?id=${roadmapId}`;
const { copyText, isCopied } = useCopyText();
@@ -84,13 +88,13 @@ export function ShareSuccess(props: ShareSuccessProps) {
</p>
<div className="mt-2">
<input
onClick={(e) => {
e.currentTarget.select();
copyText(embedHtml);
}}
readOnly={true}
className="w-full resize-none rounded-md border bg-gray-50 p-2 text-sm"
value={embedHtml}
onClick={(e) => {
e.currentTarget.select();
copyText(embedHtml);
}}
readOnly={true}
className="w-full resize-none rounded-md border bg-gray-50 p-2 text-sm"
value={embedHtml}
/>
</div>
</div>
@@ -127,7 +131,7 @@ export function ShareSuccess(props: ShareSuccessProps) {
<button
className={cn(
'flex w-full items-center justify-center gap-1.5 rounded bg-black px-4 py-2.5 text-sm font-medium text-white hover:opacity-80',
isCopied && 'bg-green-300 text-green-800'
isCopied && 'bg-green-300 text-green-800',
)}
disabled={isCopied}
onClick={() => {
@@ -139,7 +143,7 @@ export function ShareSuccess(props: ShareSuccessProps) {
</button>
<button
className={cn(
'flex w-full items-center justify-center gap-1.5 rounded border border-black px-4 py-2 text-sm font-medium hover:bg-gray-100'
'flex w-full items-center justify-center gap-1.5 rounded border border-black px-4 py-2 text-sm font-medium hover:bg-gray-100',
)}
onClick={onClose}
>

View File

@@ -11,7 +11,7 @@ type GroupRoadmapItemProps = {
export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
const { onShowResourceProgress } = props;
const { members, resourceTitle, resourceId, isCustomResource } =
const { members, resourceTitle, resourceId, isCustomResource, roadmapSlug } =
props.roadmap;
const { t: teamId } = getUrlParams();
@@ -19,7 +19,7 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
const [showAll, setShowAll] = useState(false);
const roadmapLink = isCustomResource
? `/r?id=${resourceId}`
? `/r/${roadmapSlug}`
: `/${resourceId}?t=${teamId}`;
return (

View File

@@ -22,6 +22,7 @@ export type UserProgress = {
total: number;
updatedAt: string;
isCustomResource?: boolean;
roadmapSlug?: string;
};
export type TeamMember = {
@@ -39,6 +40,7 @@ export type GroupByRoadmap = {
resourceTitle: string;
resourceType: string;
isCustomResource?: boolean;
roadmapSlug?: string;
members: {
member: TeamMember;
progress: UserProgress | undefined;
@@ -71,7 +73,7 @@ export function TeamProgressPage() {
async function getTeamProgress() {
const { response, error } = await httpGet<TeamMember[]>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-progress/${teamId}`
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-progress/${teamId}`,
);
if (error || !response) {
toast.error(error?.message || 'Failed to get team progress');
@@ -87,7 +89,7 @@ export function TeamProgressPage() {
return 1;
}
return 0;
})
}),
);
}
@@ -116,7 +118,7 @@ export function TeamProgressPage() {
const members: GroupByRoadmap['members'] = [];
for (const member of teamMembers) {
const progress = member.progress.find(
(progress) => progress.resourceId === roadmap
(progress) => progress.resourceId === roadmap,
);
if (!progress) {
continue;
@@ -139,6 +141,7 @@ export function TeamProgressPage() {
resourceId: roadmap,
resourceTitle: members?.[0].progress?.resourceTitle || '',
resourceType: 'roadmap',
roadmapSlug: members?.[0].progress?.roadmapSlug,
members,
isCustomResource,
});
@@ -174,7 +177,7 @@ export function TeamProgressPage() {
setShowMemberProgress({
resourceId: showMemberProgress.resourceId,
member: teamMembers.find(
(member) => member.email === user?.email
(member) => member.email === user?.email,
)!,
isCustomResource: showMemberProgress.isCustomResource,
});

View File

@@ -473,7 +473,7 @@ export function TeamRoadmaps() {
)}
<a
href={`/r?id=${resourceConfig.resourceId}`}
href={`/r/${resourceConfig.roadmapSlug}`}
className={
'ml-2 flex items-center gap-2 rounded-md border border-gray-300 bg-white px-2 py-1.5 text-xs hover:bg-gray-50 focus:outline-none'
}

View File

@@ -1,5 +1,5 @@
---
import type { FAQType } from '../../components/FAQs/FAQs.astro';
import type { FAQType } from '../../../components/FAQs/FAQs.astro';
export const faqs: FAQType[] = [
{

View File

@@ -1,5 +1,5 @@
import type { MarkdownFileType } from './file';
import type { BestPracticeFrontmatter } from './best-pratice';
import type { BestPracticeFrontmatter } from './best-practice';
// Generates URL from the topic file path e.g.
// -> /src/data/best-practices/frontend-performance/content/100-use-https-everywhere
@@ -34,7 +34,7 @@ export async function getAllBestPracticeTopicFiles(): Promise<
'/src/data/best-practices/*/content/**/*.md',
{
eager: true,
}
},
);
const mapping: Record<string, BestPracticeTopicFileType> = {};
@@ -63,4 +63,4 @@ export async function getAllBestPracticeTopicFiles(): Promise<
}
return mapping;
}
}

View File

@@ -48,7 +48,7 @@ export async function getBestPracticeIds() {
'/src/data/best-practices/*/*.md',
{
eager: true,
}
},
);
return Object.keys(bestPracticeFiles).map(bestPracticePathToId);
@@ -64,7 +64,7 @@ export async function getAllBestPractices(): Promise<BestPracticeFileType[]> {
'/src/data/best-practices/*/*.md',
{
eager: true,
}
},
);
const bestPracticeFiles = Object.values(bestPracticeFilesMap);
@@ -74,6 +74,19 @@ export async function getAllBestPractices(): Promise<BestPracticeFileType[]> {
}));
return bestPracticeItems.sort(
(a, b) => a.frontmatter.order - b.frontmatter.order
(a, b) => a.frontmatter.order - b.frontmatter.order,
);
}
export async function getBestPracticeById(
id: string,
): Promise<BestPracticeFileType> {
const bestPracticeFile = await import(
`../data/best-practices/${id}/${id}.md`
);
return {
...bestPracticeFile,
id: bestPracticePathToId(bestPracticeFile.file),
};
}

View File

@@ -48,7 +48,7 @@ export async function getAllGuides(): Promise<GuideFileType[]> {
'/src/data/guides/*.md',
{
eager: true,
}
},
);
const guideFiles = Object.values(guides);
@@ -60,6 +60,15 @@ export async function getAllGuides(): Promise<GuideFileType[]> {
return enrichedGuides.sort(
(a, b) =>
new Date(b.frontmatter.date).valueOf() -
new Date(a.frontmatter.date).valueOf()
new Date(a.frontmatter.date).valueOf(),
);
}
export async function getGuideById(id: string): Promise<GuideFileType> {
const guide = await import(`../data/guides/${id}.md`);
return {
...guide,
id: guidePathToId(guide.file),
};
}

View File

@@ -29,7 +29,7 @@ export async function getAllLinkGroups(): Promise<LinkGroupFileType[]> {
'/src/data/link-groups/*.md',
{
eager: true,
}
},
);
return Object.values(linkGroups).map((linkGroupFile) => ({
@@ -37,3 +37,14 @@ export async function getAllLinkGroups(): Promise<LinkGroupFileType[]> {
id: linkGroupPathToId(linkGroupFile.file),
}));
}
export async function getLinkGroupById(
groupId: string,
): Promise<LinkGroupFileType> {
const linkGroup = await import(`../data/link-groups/${groupId}.md`);
return {
...linkGroup,
id: linkGroupPathToId(linkGroup.file),
};
}

View File

@@ -117,6 +117,12 @@ export async function getAllQuestionGroups(): Promise<QuestionGroupType[]> {
.sort((a, b) => a.frontmatter.order - b.frontmatter.order);
}
export async function getQuestionGroupById(id: string) {
const questionGroups = await getAllQuestionGroups();
return questionGroups.find((group) => group.id === id);
}
export async function getQuestionGroupsByIds(
ids: string[],
): Promise<{ id: string; title: string; description: string }[]> {

View File

@@ -130,3 +130,11 @@ export async function getRoadmapsByIds(
return Promise.all(ids.map((id) => getRoadmapById(id)));
}
export async function getRoadmapFaqsById(roadmapId: string): Promise<string[]> {
const { faqs } = await import(
`../data/roadmaps/${roadmapId}/faqs.astro`
).catch(() => ({}));
return faqs || [];
}

View File

@@ -47,7 +47,7 @@ export async function getAllVideos(): Promise<VideoFileType[]> {
'/src/data/videos/*.md',
{
eager: true,
}
},
);
const videoFiles = Object.values(videos);
@@ -59,6 +59,15 @@ export async function getAllVideos(): Promise<VideoFileType[]> {
return enrichedVideos.sort(
(a, b) =>
new Date(b.frontmatter.date).valueOf() -
new Date(a.frontmatter.date).valueOf()
new Date(a.frontmatter.date).valueOf(),
);
}
export async function getVideoById(id: string): Promise<VideoFileType> {
const video = await import(`../data/videos/${id}.md`);
return {
...video,
id: videoPathToId(video.file),
};
}

View File

@@ -1,34 +1,22 @@
---
import RoadmapBanner from '../../components/RoadmapBanner.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import {
getRoadmapTopicFiles,
type RoadmapTopicFileType,
} from '../../lib/roadmap-topic';
export async function getStaticPaths() {
const topicPathMapping = await getRoadmapTopicFiles();
return Object.keys(topicPathMapping).map((topicSlug) => {
const topicDetails = topicPathMapping[topicSlug];
const roadmapId = topicDetails.roadmapId;
const topicId = topicSlug.replace(`/${roadmapId}/`, '');
return {
params: {
topicId,
roadmapId,
},
props: topicDetails,
};
});
}
import { getRoadmapTopicFiles } from '../../lib/roadmap-topic';
export const partial = true;
const { topicId } = Astro.params;
const { file, url, roadmapId, roadmap, heading } =
Astro.props as RoadmapTopicFileType;
const { topicId, roadmapId } = Astro.params;
if (!topicId) {
return new Response();
}
const topicSlug = `/${roadmapId}/${topicId}`;
const topicPathMapping = await getRoadmapTopicFiles();
const topicDetails = topicPathMapping[topicSlug];
if (!topicDetails) {
return Astro.redirect('/404');
}
const { file } = topicDetails;
const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data');
const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`;
@@ -36,4 +24,4 @@ const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/maste
<div data-github-url={gitHubUrl}></div>
<file.Content />
<file.Content />

View File

@@ -1,7 +1,6 @@
---
import FAQs from '../../components/FAQs/FAQs.astro';
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro';
import ShareIcons from '../../components/ShareIcons/ShareIcons.astro';
@@ -13,27 +12,25 @@ import {
generateArticleSchema,
generateFAQSchema,
} from '../../lib/jsonld-schema';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
export async function getStaticPaths() {
const roadmapIds = await getRoadmapIds();
return roadmapIds.map((roadmapId) => ({
params: { roadmapId },
}));
}
import {
getRoadmapById,
type RoadmapFrontmatter,
getRoadmapFaqsById,
} from '../../lib/roadmap';
interface Params extends Record<string, string | undefined> {
roadmapId: string;
}
const { roadmapId } = Astro.params as Params;
const roadmapFile = await import(
`../../data/roadmaps/${roadmapId}/${roadmapId}.md`
);
const { faqs: roadmapFAQs = [] } = await import(
`../../data/roadmaps/${roadmapId}/faqs.astro`
);
const roadmapFile = await getRoadmapById(roadmapId).catch(() => null);
if (!roadmapFile) {
return Astro.redirect('/404');
}
const roadmapFAQs = await getRoadmapFaqsById(roadmapId);
const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
let jsonLdSchema = [];

View File

@@ -1,30 +1,33 @@
import type { APIRoute } from 'astro';
export async function getStaticPaths() {
const roadmapJsons = await import.meta.glob('/src/data/roadmaps/**/*.json', {
eager: true,
});
return Object.keys(roadmapJsons).map((filePath) => {
const roadmapId = filePath.split('/').pop()?.replace('.json', '');
const roadmapJson = roadmapJsons[filePath] as Record<string, any>;
return {
params: {
roadmapId,
},
props: {
roadmapJson: roadmapJson?.default,
},
};
});
}
export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.roadmapJson), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
const { roadmapId } = params;
try {
const roadmapJson = await import(
`../../data/roadmaps/${roadmapId}/${roadmapId}.json`
);
return new Response(JSON.stringify(roadmapJson), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
return new Response(
JSON.stringify({
data: null,
error: {
message: 'Roadmap not found',
},
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
},
);
}
};

View File

@@ -1,26 +1,16 @@
---
import { getAllBestPracticeTopicFiles } from '../../../lib/best-practice-topic';
import type { BestPracticeTopicFileType } from '../../../lib/best-practice-topic';
export async function getStaticPaths() {
const topicPathMapping = await getAllBestPracticeTopicFiles();
return Object.keys(topicPathMapping).map((topicSlug) => {
const topicDetails = topicPathMapping[topicSlug];
const bestPracticeId = topicDetails.bestPracticeId;
const topicId = topicSlug.replace(`/${bestPracticeId}/`, '');
return {
params: {
topicId,
bestPracticeId,
},
props: topicDetails,
};
});
const { topicId, bestPracticeId } = Astro.params;
if (!topicId) {
return new Response();
}
const { file } = Astro.props as BestPracticeTopicFileType;
const topicSlug = `/${bestPracticeId}/${topicId}`;
const topicPathMapping = await getAllBestPracticeTopicFiles();
const topicDetails = topicPathMapping[topicSlug];
const { file } = topicDetails;
const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data');
const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`;

View File

@@ -10,21 +10,10 @@ import { UserProgressModal } from '../../../components/UserProgress/UserProgress
import {
type BestPracticeFileType,
type BestPracticeFrontmatter,
getAllBestPractices,
} from '../../../lib/best-pratice';
getBestPracticeById,
} from '../../../lib/best-practice';
import { generateArticleSchema } from '../../../lib/jsonld-schema';
export async function getStaticPaths() {
const bestPractices = await getAllBestPractices();
return bestPractices.map((bestPractice: BestPracticeFileType) => ({
params: { bestPracticeId: bestPractice.id },
props: {
bestPractice: bestPractice,
},
}));
}
interface Params extends Record<string, string | undefined> {
bestPracticeId: string;
}
@@ -34,7 +23,14 @@ interface Props {
}
const { bestPracticeId } = Astro.params as Params;
const { bestPractice } = Astro.props as Props;
const bestPractice = await getBestPracticeById(bestPracticeId).catch(
() => null,
);
if (!bestPractice) {
return Astro.redirect('/404');
}
const bestPracticeData = bestPractice.frontmatter as BestPracticeFrontmatter;
let jsonLdSchema = [];
@@ -49,7 +45,7 @@ if (bestPracticeData.schema) {
datePublished: bestPracticeSchema.datePublished,
dateModified: bestPracticeSchema.dateModified,
imageUrl: bestPracticeSchema.imageUrl,
})
}),
);
}
---

View File

@@ -1,33 +1,33 @@
import type { APIRoute } from 'astro';
export async function getStaticPaths() {
const bestPracticeJsons = await import.meta.glob(
'/src/data/best-practices/**/*.json',
{
eager: true,
}
);
return Object.keys(bestPracticeJsons).map((filePath) => {
const bestPracticeId = filePath.split('/').pop()?.replace('.json', '');
const bestPracticeJson = bestPracticeJsons[filePath] as Record<string, any>;
return {
params: {
bestPracticeId,
},
props: {
bestPracticeJson: bestPracticeJson?.default,
},
};
});
}
export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.bestPracticeJson), {
status: 200,
headers: {
'content-type': 'application/json',
},
});
const { bestPracticeId } = params;
try {
const roadmapJson = await import(
`../../../data/best-practices/${bestPracticeId}/${bestPracticeId}.json`
);
return new Response(JSON.stringify(roadmapJson), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
return new Response(
JSON.stringify({
data: null,
error: {
message: 'Best Practices not found',
},
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
},
);
}
};

View File

@@ -2,7 +2,7 @@
import GridItem from '../../components/GridItem.astro';
import SimplePageHeader from '../../components/SimplePageHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllBestPractices } from '../../lib/best-pratice';
import { getAllBestPractices } from '../../lib/best-practice';
const bestPractices = await getAllBestPractices();
---

View File

@@ -1,30 +1,13 @@
---
import BaseLayout from '../../../layouts/BaseLayout.astro';
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
import { getAllLinkGroups } from '../../../lib/link-group';
export async function getStaticPaths() {
const linkGroups = await getAllLinkGroups();
return linkGroups.flatMap((linkGroup) => {
const linkGroupLinks = linkGroup.frontmatter;
return Object.keys(linkGroupLinks).map((slug) => {
return {
params: {
linkGroupId: linkGroup.id,
linkId: slug,
},
props: {
linkGroup,
},
};
});
});
}
import { getLinkGroupById } from '../../../lib/link-group';
const { linkId } = Astro.params;
const { linkGroup } = Astro.props;
const linkGroup = await getLinkGroupById(linkId!).catch(() => null);
if (!linkGroup) {
return Astro.redirect('/404');
}
const fullUrl = linkGroup.frontmatter[linkId!];
---

View File

@@ -2,23 +2,14 @@
import GuideHeader from '../../components/GuideHeader.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllGuides,GuideFileType } from '../../lib/guide';
export interface Props {
guide: GuideFileType;
}
export async function getStaticPaths() {
const guides = await getAllGuides();
return guides.map((guide) => ({
params: { guideId: guide.id },
props: { guide },
}));
}
import { getGuideById } from '../../lib/guide';
const { guideId } = Astro.params;
const { guide } = Astro.props;
const guide = await getGuideById(guideId).catch(() => null);
if (!guide) {
return Astro.redirect('/404');
}
const { frontmatter: guideData } = guide;
---
@@ -30,7 +21,7 @@ const { frontmatter: guideData } = guide;
>
<GuideHeader guide={guide} />
<div class='py-5 sm:py-10 max-w-[700px] mx-auto'>
<div class='mx-auto max-w-[700px] py-5 sm:py-10'>
<MarkdownFile>
<guide.Content />
</MarkdownFile>

View File

@@ -4,7 +4,7 @@ import FeaturedGuides from '../components/FeaturedGuides.astro';
import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro';
import HeroSection from '../components/HeroSection/HeroSection.astro';
import BaseLayout from '../layouts/BaseLayout.astro';
import { getAllBestPractices } from '../lib/best-pratice';
import { getAllBestPractices } from '../lib/best-practice';
import { getAllGuides } from '../lib/guide';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video';

View File

@@ -1,4 +1,4 @@
import { getAllBestPractices } from '../lib/best-pratice';
import { getAllBestPractices } from '../lib/best-practice';
import { getAllGuides } from '../lib/guide';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video';

View File

@@ -8,24 +8,15 @@ import { QuestionsList } from '../../components/Questions/QuestionsList';
import {
getAllQuestionGroups,
type QuestionGroupType,
getQuestionGroupById,
} from '../../lib/question-group';
export interface Props {
questionGroup: QuestionGroupType;
}
export async function getStaticPaths() {
const questionGroups = await getAllQuestionGroups();
return questionGroups.map((questionGroup) => {
return {
params: { questionGroupId: questionGroup.id },
props: { questionGroup },
};
});
const { questionGroupId } = Astro.params;
const questionGroup = await getQuestionGroupById(questionGroupId);
if (!questionGroup) {
return Astro.redirect('/404');
}
const { questionGroup } = Astro.props;
const { frontmatter } = questionGroup;
---
@@ -38,24 +29,24 @@ const { frontmatter } = questionGroup;
>
<div class='flex bg-gray-50 pb-14 pt-4 sm:pb-16 sm:pt-8'>
<div class='container !max-w-[740px]'>
<div class='mb-3 sm:mb-5 mt-2 text-left sm:text-center sm:mt-8'>
<div class='mb-3 mt-2 text-left sm:mb-5 sm:mt-8 sm:text-center'>
<div class='mb-2 md:mb-6'>
<a
href='/questions'
class='group rounded-md text-sm font-medium text-gray-400 hover:text-gray-800 transition-colors duration-200'
class='group rounded-md text-sm font-medium text-gray-400 transition-colors duration-200 hover:text-gray-800'
>
<span
class='inline-block transform transition-transform group-hover:translate-x-[-2px]'
>
&larr;
</span>
Back to all Questions
Back to all Questions
</a>
</div>
<h1 class='mb-1 text-2xl font-bold sm:mb-5 sm:text-5xl'>
{frontmatter.title}
</h1>
<p class='hidden sm:block text-xl text-gray-500'>
<p class='hidden text-xl text-gray-500 sm:block'>
{frontmatter.description}
</p>
</div>

View File

@@ -0,0 +1,24 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { CustomRoadmap } from '../../components/CustomRoadmap/CustomRoadmap';
import { SkeletonRoadmapHeader } from '../../components/CustomRoadmap/SkeletonRoadmapHeader';
import Loader from '../../components/Loader.astro';
import ProgressHelpPopup from '../../components/ProgressHelpPopup.astro';
const { customRoadmapSlug } = Astro.params;
---
<BaseLayout title='Roadmaps'>
<ProgressHelpPopup />
<div>
<div class='flex min-h-[550px] flex-col'>
<div data-roadmap-loader class='flex w-full grow flex-col'>
<SkeletonRoadmapHeader />
<div class='flex grow items-center justify-center'>
<Loader />
</div>
</div>
<CustomRoadmap slug={customRoadmapSlug} client:only='react' />
</div>
</div>
</BaseLayout>

5
src/pages/v1-health.ts Normal file
View File

@@ -0,0 +1,5 @@
import { execSync } from 'child_process';
export async function GET() {
return new Response(JSON.stringify({}), {});
}

View File

@@ -0,0 +1,33 @@
import { execSync } from 'child_process';
export const prerender = true;
export async function GET() {
const commitHash = execSync('git rev-parse HEAD').toString().trim();
const commitDate = execSync('git log -1 --format=%cd').toString().trim();
const commitMessage = execSync('git log -1 --format=%B').toString().trim();
const prevCommitHash = execSync('git rev-parse HEAD~1').toString().trim();
const prevCommitDate = execSync('git log -1 --format=%cd HEAD~1')
.toString()
.trim();
const prevCommitMessage = execSync('git log -1 --format=%B HEAD~1')
.toString()
.trim();
return new Response(
JSON.stringify({
current: {
hash: commitHash,
date: commitDate,
message: commitMessage,
},
previous: {
hash: prevCommitHash,
date: prevCommitDate,
message: prevCommitMessage,
},
}),
{},
);
}

View File

@@ -1,23 +1,14 @@
---
import VideoHeader from '../../components/VideoHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllVideos,VideoFileType } from '../../lib/video';
export interface Props {
video: VideoFileType;
}
export async function getStaticPaths() {
const videos = await getAllVideos();
return videos.map((video) => ({
params: { videoId: video.id },
props: { video },
}));
}
import { getVideoById } from '../../lib/video';
const { videoId } = Astro.params;
const { video } = Astro.props;
const video = await getVideoById(videoId).catch(() => null);
if (!video) {
return Astro.redirect('/404');
}
---
<BaseLayout
@@ -29,7 +20,7 @@ const { video } = Astro.props;
<div class='bg-gray-50 py-5 sm:py-10'>
<div
class='container prose prose-code:bg-transparent prose-h2:text-3xl prose-h2:mt-4 prose-h2:mb-2 prose-h3:mt-2 prose-img:mt-1'
class='container prose prose-h2:mb-2 prose-h2:mt-4 prose-h2:text-3xl prose-h3:mt-2 prose-code:bg-transparent prose-img:mt-1'
>
<video.Content />
</div>

View File

@@ -4,5 +4,6 @@
"moduleResolution": "node",
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
},
"exclude": ["node_modules", "dist"]
}