diff --git a/apps/www/.gitignore b/apps/www/.gitignore index 55a12ae..51a3230 100644 --- a/apps/www/.gitignore +++ b/apps/www/.gitignore @@ -25,4 +25,7 @@ yarn-error.log* # others .env*.local .vercel -next-env.d.ts \ No newline at end of file +next-env.d.ts + +# sitemap +/public/sitemap.xml \ No newline at end of file diff --git a/apps/www/app/robots.txt b/apps/www/app/robots.txt new file mode 100644 index 0000000..5c8e7f0 --- /dev/null +++ b/apps/www/app/robots.txt @@ -0,0 +1,5 @@ +User-Agent: * +Allow: / +Disallow: /private/ + +Sitemap: https://amical.ai/sitemap.xml diff --git a/apps/www/package.json b/apps/www/package.json index dcc0dc0..eb88532 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -3,7 +3,8 @@ "version": "0.0.0", "private": true, "scripts": { - "build": "next build", + "build": "pnpm build:sitemap && next build", + "build:sitemap": "pnpm exec tsx ./scripts/generate-sitemap.mts", "dev": "next dev --turbo", "start": "next start", "serve": "pnpm dlx serve out -p 3000", @@ -36,9 +37,11 @@ "@types/node": "22.15.12", "@types/react": "^19.1.3", "@types/react-dom": "^19.1.3", + "globby": "^14.1.0", "postcss": "^8.5.3", "server": "^1.0.41", "tailwindcss": "^4.1.5", + "tsx": "^4.19.4", "tw-animate-css": "^1.2.9", "typescript": "^5.8.3" } diff --git a/apps/www/scripts/generate-sitemap.mts b/apps/www/scripts/generate-sitemap.mts new file mode 100644 index 0000000..7f37a44 --- /dev/null +++ b/apps/www/scripts/generate-sitemap.mts @@ -0,0 +1,129 @@ +import fs from 'fs'; +import { globby } from 'globby'; +import prettier from 'prettier'; +import path from 'path'; + +async function generate() { + const prettierConfig = await prettier.resolveConfig('./.prettierrc'); + + // Ensure directories exist + fs.mkdirSync('public', { recursive: true }); + fs.mkdirSync('out', { recursive: true }); + + // Get static pages + const pages = await globby([ + 'app/**/page.tsx', + '!app/**/_*/**', + '!app/**/api/**', + '!app/docs/**', // Exclude docs directory as we'll handle it separately + ]); + + // Get doc pages from the build output + const docPages = await globby(['out/docs/**/*.html']) + .then(pages => pages + .map(page => page + .replace('out', '') + .replace('/index.html', '') + .replace('.html', '') + ) + .filter(page => !page.includes('/_')) + ); + + // Get blog pages from the build output + const blogPages = await globby(['out/blog/**/*.html']) + .then(pages => pages + .map(page => page + .replace('out', '') + .replace('/index.html', '') + .replace('.html', '') + ) + .filter(page => !page.includes('/_') && !page.includes('/blog/index')) + ); + + const baseUrl = 'https://amical.ai'; + + const sitemap = ` + + + ${[ + // Add static pages + ...pages.map((page) => { + const path = page + .replace('app', '') + .replace('/page.tsx', '') + .replace('/(home)', '') + .replace(/\[\[\.\.\..*?\]\]/, ''); + + // Skip dynamic routes with parameters + if (path.includes('[') || path.includes(']')) { + return ''; + } + + const route = path === '' ? '' : path; + + return ` + + ${baseUrl}${route} + ${new Date().toISOString()} + daily + 0.7 + + `; + }), + // Add docs index page + ` + + ${baseUrl}/docs + ${new Date().toISOString()} + daily + 0.7 + + `, + // Add doc pages + ...docPages.map((path) => ` + + ${baseUrl}${path} + ${new Date().toISOString()} + daily + 0.7 + + `), + // Add blog index page + ` + + ${baseUrl}/blog + ${new Date().toISOString()} + weekly + 0.8 + + `, + // Add blog pages + ...blogPages.map((path) => ` + + ${baseUrl}${path} + ${new Date().toISOString()} + weekly + 0.7 + + `), + ] + .filter(Boolean) + .join('')} + + `; + + const formatted = await prettier.format(sitemap, { + ...prettierConfig, + parser: 'html', + }); + + fs.writeFileSync('public/sitemap.xml', formatted); + fs.writeFileSync('out/sitemap.xml', formatted); + + console.log('✅ Generated sitemap.xml'); +} + +generate().catch((err) => { + console.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc29ec3..5702432 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ importers: '@types/react-dom': specifier: ^19.1.3 version: 19.1.3(@types/react@19.1.3) + globby: + specifier: ^14.1.0 + version: 14.1.0 postcss: specifier: ^8.5.3 version: 8.5.3 @@ -102,6 +105,9 @@ importers: tailwindcss: specifier: ^4.1.5 version: 4.1.5 + tsx: + specifier: ^4.19.4 + version: 4.19.4 tw-animate-css: specifier: ^1.2.9 version: 1.2.9 @@ -1076,6 +1082,10 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -2191,6 +2201,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fumadocs-core@15.3.0: resolution: {integrity: sha512-6+j2qGntJknzQUyes5BQI3U5EE4GCIb1Pi31eAYZ8GLxMP1p1bh8nAyEeW6oZNZmqfstJoVLQNnw+vwuHJJHUw==} peerDependencies: @@ -2268,6 +2283,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + get-uri@6.0.4: resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} engines: {node: '>= 14'} @@ -2302,6 +2320,10 @@ packages: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} engines: {node: '>=8'} + globby@14.1.0: + resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2431,6 +2453,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.4: + resolution: {integrity: sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==} + engines: {node: '>= 4'} + image-size@2.0.2: resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} engines: {node: '>=16.x'} @@ -3297,6 +3323,10 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -3553,6 +3583,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -3741,6 +3774,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -3973,6 +4010,11 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} + tsx@4.19.4: + resolution: {integrity: sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==} + engines: {node: '>=18.0.0'} + hasBin: true + turbo-darwin-64@2.5.3: resolution: {integrity: sha512-YSItEVBUIvAGPUDpAB9etEmSqZI3T6BHrkBkeSErvICXn3dfqXUfeLx35LfptLDEbrzFUdwYFNmt8QXOwe9yaw==} cpu: [x64] @@ -4073,6 +4115,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -5058,6 +5104,8 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sindresorhus/merge-streams@2.3.0': {} + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.0.0': {} @@ -6463,6 +6511,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.3: + optional: true + fumadocs-core@15.3.0(@types/react@19.1.3)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@formatjs/intl-localematcher': 0.6.1 @@ -6593,6 +6644,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + get-uri@6.0.4: dependencies: basic-ftp: 5.0.5 @@ -6640,6 +6695,15 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globby@14.1.0: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.3 + ignore: 7.0.4 + path-type: 6.0.0 + slash: 5.1.0 + unicorn-magic: 0.3.0 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -6830,6 +6894,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.4: {} + image-size@2.0.2: {} import-fresh@3.3.1: @@ -7984,6 +8050,8 @@ snapshots: path-type@4.0.0: {} + path-type@6.0.0: {} + picocolors@1.0.1: {} picocolors@1.1.1: {} @@ -8337,6 +8405,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -8656,6 +8726,8 @@ snapshots: slash@3.0.0: {} + slash@5.1.0: {} + smart-buffer@4.2.0: {} snake-case@2.1.0: @@ -8900,6 +8972,13 @@ snapshots: tsscmp@1.0.6: {} + tsx@4.19.4: + dependencies: + esbuild: 0.25.4 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + turbo-darwin-64@2.5.3: optional: true @@ -9009,6 +9088,8 @@ snapshots: undici-types@6.21.0: {} + unicorn-magic@0.3.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3