diff --git a/.changeset/config.json b/.changeset/config.json index 7b662a1..5b4dd11 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -6,6 +6,6 @@ "commit": false, "fixed": [], "ignore": ["@astro-aws/www", "@astro-aws/infra"], - "linked": [], + "linked": [["@astro-aws/adapter", "@astro-aws/constructs"]], "updateInternalDependencies": "patch" } diff --git a/.changeset/cool-buckets-hunt.md b/.changeset/cool-buckets-hunt.md new file mode 100644 index 0000000..7d05a18 --- /dev/null +++ b/.changeset/cool-buckets-hunt.md @@ -0,0 +1,6 @@ +--- +"@astro-aws/adapter": minor +"@astro-aws/constructs": minor +--- + +Added support for static deployments diff --git a/.eslintignore b/.eslintignore index 539d946..ff68ff5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,7 @@ .env .env.production .netlify +.turbo cdk.out/ dist/ node_modules/ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0e3f9dd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,14 @@ +github: + - lukeshay +custom: + - https://account.venmo.com/u/LukeShay + - https://paypal.me/rlshay +# patreon: # Replace with a single Patreon username +# open_collective: # Replace with a single Open Collective username +# ko_fi: # Replace with a single Ko-fi username +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +# liberapay: # Replace with a single Liberapay username +# issuehunt: # Replace with a single IssueHunt username +# otechie: # Replace with a single Otechie username +# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index 9d37279..e187ad7 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -11,6 +11,8 @@ on: env: CI: true + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} permissions: id-token: write @@ -18,49 +20,51 @@ permissions: deployments: write jobs: - deploy-dev: - name: Deploy Dev + prepare-yarn-cache: + name: Cache dependencies runs-on: ubuntu-22.04 - environment: - name: development - url: https://astro-aws.dev.lshay.dev/ - concurrency: - group: ${{ format('{0}-{1}', github.workflow, github.job) }} steps: - - run: echo "::add-mask::${{ secrets.AWS_ACCOUNT_ID }}" - uses: actions/checkout@v3 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-region: us-east-1 - role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - role-session-name: astro-aws-cd-dev - uses: actions/setup-node@v3 with: node-version: "18" cache: "yarn" - run: yarn install --mode=skip-build - - run: yarn deploy:one infra -- DEV + env: + YARN_ENABLE_SCRIPTS: false + deploy-dev: + name: Deploy Dev + needs: [prepare-yarn-cache] + concurrency: + group: ${{ format('{0}-{1}', github.workflow, github.job) }} + uses: ./.github/workflows/deploy.yml + with: + aws_session_name: astro-aws-cd-dev + environment_name: development + environment_url: https://dev.astro-aws.org/ + stack_environment: DEV + secrets: inherit deploy-dev-node-16: name: Deploy Dev Node 16 - runs-on: ubuntu-22.04 - environment: - name: development-node-16 - url: https://astro-aws.dev-node-16.lshay.dev/ + needs: [prepare-yarn-cache] concurrency: group: ${{ format('{0}-{1}', github.workflow, github.job) }} - steps: - - run: echo "::add-mask::${{ secrets.AWS_ACCOUNT_ID }}" - - uses: actions/checkout@v3 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-region: us-east-1 - role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - role-session-name: astro-aws-cd-dev-node-16 - - uses: actions/setup-node@v3 - with: - node-version: "16" - cache: "yarn" - - run: yarn install --mode=skip-build - - run: yarn deploy:one infra -- NODE16 + uses: ./.github/workflows/deploy.yml + with: + aws_session_name: astro-aws-cd-node-16 + environment_name: development-node-16 + environment_url: https://dev-node-16.astro-aws.org/ + stack_environment: NODE16 + secrets: inherit + deploy-dev-node-18: + name: Deploy Dev Node 18 + needs: [prepare-yarn-cache] + concurrency: + group: ${{ format('{0}-{1}', github.workflow, github.job) }} + uses: ./.github/workflows/deploy.yml + with: + aws_session_name: astro-aws-cd-node-18 + environment_name: development-node-18 + environment_url: https://dev-node-18.astro-aws.org/ + stack_environment: NODE18 + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 723be5b..b417051 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,8 @@ on: env: CI: true + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} jobs: prepare-yarn-cache: @@ -17,7 +19,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" cache: "yarn" - run: yarn install --mode=skip-build env: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..57571b6 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,89 @@ +name: Deploy + +on: + workflow_call: + inputs: + node_version: + description: The version of node to use + required: true + type: string + default: "18" + stack_environment: + description: The stack environment to deploy + required: true + type: string + environment_name: + description: The GitHub environment name + required: true + type: string + environment_url: + description: The GitHub environment url + required: true + type: string + aws_session_name: + description: AWS session name + required: false + type: string + aws_region: + description: The AWS region + required: false + type: string + default: us-east-1 + ref: + description: The git ref to checkout + required: false + type: string + repository: + description: The git repository to checkout + required: false + type: string + secrets: + AWS_ROLE_ARN: + description: The AWS Role ARN to use + required: true + AWS_ACCOUNT_ID: + description: The AWS Account ID + required: true + TURBO_TOKEN: + description: Turborepo remote cache token + required: false + TURBO_TEAM: + description: Turborepo remote cache team + required: false + +env: + CI: true + +permissions: + id-token: write + contents: read + deployments: write + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-22.04 + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + environment: + name: ${{ inputs.environment_name }} + url: ${{ inputs.environment_url }} + steps: + - run: echo "::add-mask::${{ secrets.AWS_ACCOUNT_ID }}" + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ inputs.aws_region }} + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + role-session-name: ${{ inputs.aws_session_name }} + - uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.node_version }} + cache: "yarn" + - run: yarn install --mode=skip-build + - run: yarn deploy:one infra -- ${{ inputs.stack_environment }} diff --git a/.gitignore b/.gitignore index 2faa43d..45503eb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .DS_Store .env* .netlify +.turbo .turbo/ cdk.out/ dist/ diff --git a/.prettierignore b/.prettierignore index 539d946..ff68ff5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,6 +6,7 @@ .env .env.production .netlify +.turbo cdk.out/ dist/ node_modules/ diff --git a/README.md b/README.md index 1d8d668..72310e7 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # astro-aws + +[![CI](https://github.com/lukeshay/astro-aws/actions/workflows/ci.yml/badge.svg)](https://github.com/lukeshay/astro-aws/actions/workflows/ci.yml) [![CD](https://github.com/lukeshay/astro-aws/actions/workflows/cd-dev.yml/badge.svg)](https://github.com/lukeshay/astro-aws/actions/workflows/cd-dev.yml) diff --git a/apps/infra/package.json b/apps/infra/package.json index 1a3e8f6..99d6b72 100644 --- a/apps/infra/package.json +++ b/apps/infra/package.json @@ -2,7 +2,7 @@ "name": "@astro-aws/infra", "version": "0.0.0", "private": true, - "homepage": "https://astro-aws.lshay.dev/", + "homepage": "https://astro-aws.org/", "repository": { "type": "git", "url": "ssh://git@github.com/lukeshay/astro-aws.git", @@ -18,8 +18,7 @@ ], "scripts": { "build": "scripts build", - "deploy": "./scripts/build-and-invalidate.sh", - "invalidate": "./scripts/invalidate-cloudfront-cache.sh", + "deploy": "./scripts/deploy.sh", "synth": "cdk synth" }, "eslintConfig": { @@ -32,9 +31,9 @@ "@astro-aws/constructs": "workspace:^", "@astro-aws/www": "workspace:^", "@types/node": "^18.11.9", - "aws-cdk": "^2.52.0", - "aws-cdk-lib": "^2.52.0", - "constructs": "^10.1.173", + "aws-cdk": "^2.53.0", + "aws-cdk-lib": "^2.53.0", + "constructs": "^10.1.175", "eslint": "^8.28.0", "prettier": "^2.8.0", "typescript": "^4.9.3" diff --git a/apps/infra/scripts/build-and-invalidate.sh b/apps/infra/scripts/deploy.sh similarity index 89% rename from apps/infra/scripts/build-and-invalidate.sh rename to apps/infra/scripts/deploy.sh index fe7beee..e415af4 100755 --- a/apps/infra/scripts/build-and-invalidate.sh +++ b/apps/infra/scripts/deploy.sh @@ -13,4 +13,3 @@ echo "AWS_ACCOUNT: ${AWS_ACCOUNT}" echo "AWS_REGION: ${AWS_REGION}" yarn cdk deploy --require-approval never "AstroAWS-${ENVIRONMENT}-*" ${AWS_ARGS} -./scripts/invalidate-cloudfront-cache.sh "${@}" diff --git a/apps/infra/scripts/invalidate-cloudfront-cache.sh b/apps/infra/scripts/invalidate-cloudfront-cache.sh deleted file mode 100755 index c85478b..0000000 --- a/apps/infra/scripts/invalidate-cloudfront-cache.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -e - -ENVIRONMENT="${1:-DEV}" -AWS_ARGS="${@:2}" -AWS_ACCOUNT="$(aws sts get-caller-identity --query Account --output text ${AWS_ARGS})" -AWS_REGION="$(aws configure get region ${AWS_ARGS})" - -echo "ENVIRONMENT: ${ENVIRONMENT}" -echo "AWS_ARGS: ${AWS_ARGS}" -echo "AWS_ACCOUNT: ${AWS_ACCOUNT}" -echo "AWS_REGION: ${AWS_REGION}" - -DISTIRBUTION_ID="$(aws cloudformation describe-stacks --stack-name "AstroAWS-${ENVIRONMENT}-WebsiteStack-${AWS_ACCOUNT}-${AWS_REGION}" --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" --output text ${AWS_ARGS})" -PATHS="$(jq -r ". | join(\" \")" ../www/dist/invalidationPaths.json)" - -echo "Invalidating CloudFront distribution ${DISTIRBUTION_ID}: ${PATHS}" - -aws cloudfront create-invalidation --distribution-id "${DISTIRBUTION_ID}" --paths ${PATHS} ${AWS_ARGS} diff --git a/apps/infra/src/lib/constants/environments.ts b/apps/infra/src/lib/constants/environments.ts index a38872a..6909ea3 100644 --- a/apps/infra/src/lib/constants/environments.ts +++ b/apps/infra/src/lib/constants/environments.ts @@ -1,10 +1,12 @@ const base = { analyticsReporting: false, + hostedZoneId: "Z0584480MGUI8KRBPWM", }; export const Environments = { DEV: "DEV", DEV_NODE_16: "NODE16", + DEV_NODE_18: "NODE18", PERSONAL: "PERSONAL", PROD: "PROD", } as const; @@ -14,21 +16,34 @@ export type Environment = typeof Environments[keyof typeof Environments]; export const ENVIRONMENT_PROPS = { [Environments.DEV]: { ...base, - domainName: "astro-aws.dev.lshay.dev", + alias: "dev", environment: Environments.DEV, + hostedZoneName: "astro-aws.org", + output: "static", }, [Environments.DEV_NODE_16]: { ...base, - domainName: "astro-aws.dev-node-16.lshay.dev", + alias: "node16.dev", environment: Environments.DEV_NODE_16, + hostedZoneName: "astro-aws.org", + output: "server", + }, + [Environments.DEV_NODE_18]: { + ...base, + alias: "node18.dev", + environment: Environments.DEV_NODE_18, + hostedZoneName: "astro-aws.org", + output: "server", }, [Environments.PROD]: { ...base, - domainName: "astro-aws.lshay.dev", environment: Environments.PROD, + hostedZoneName: "astro-aws.org", + output: "static", }, [Environments.PERSONAL]: { ...base, environment: Environments.PERSONAL, + output: "static", }, } as const; diff --git a/apps/infra/src/lib/stacks/website-stack.ts b/apps/infra/src/lib/stacks/website-stack.ts index 33c6a42..32c75fa 100644 --- a/apps/infra/src/lib/stacks/website-stack.ts +++ b/apps/infra/src/lib/stacks/website-stack.ts @@ -1,11 +1,15 @@ import type { StackProps } from "aws-cdk-lib"; import { Stack, CfnOutput, Duration } from "aws-cdk-lib"; -import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; -import { ResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront"; +import type { Certificate } from "aws-cdk-lib/aws-certificatemanager"; +import { DnsValidatedCertificate } from "aws-cdk-lib/aws-certificatemanager"; +import { Function, FunctionCode, FunctionEventType, ResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront"; import { Architecture } from "aws-cdk-lib/aws-lambda"; import type { Construct } from "constructs"; import { AstroAWSConstruct } from "@astro-aws/constructs"; import type { Dashboard } from "aws-cdk-lib/aws-cloudwatch"; +import type { IHostedZone } from "aws-cdk-lib/aws-route53"; +import { AaaaRecord, ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; +import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets"; import { DistributionMetric } from "../constructs/distribution-metric.js"; import { BasicGraphWidget } from "../constructs/basic-graph-widget.js"; @@ -13,30 +17,75 @@ import type { Environment } from "../constants/environments.js"; import { Environments } from "../constants/environments.js"; export type WebsiteStackProps = StackProps & { + alias?: string; + cloudwatchDashboard: Dashboard; domainName?: string; env: StackProps["env"]; - cloudwatchDashboard: Dashboard; environment: Environment; + hostedZoneId?: string; + hostedZoneName?: string; + output: "server" | "static"; }; export class WebsiteStack extends Stack { public constructor(scope: Construct, id: string, props: WebsiteStackProps) { super(scope, id, props); - const { domainName, cloudwatchDashboard, environment } = props; + const { hostedZoneName, alias, hostedZoneId, cloudwatchDashboard, environment, output } = props; + + const domainName = [alias, hostedZoneName].filter(Boolean).join("."); + const domainNames = [domainName].filter(Boolean); + + let certificate: Certificate | undefined, + hostedZone: IHostedZone | undefined, + wwwRedirectFunction: Function | undefined; - let certificate: Certificate | undefined; + if (hostedZoneName && hostedZoneId) { + const theHostedZone = HostedZone.fromHostedZoneAttributes(this, "HostedZone", { + hostedZoneId, + zoneName: hostedZoneName, + }); - if (domainName) { - certificate = new Certificate(this, "Certificate", { - domainName, + certificate = new DnsValidatedCertificate(this, "Certificate", { + domainName: environment === Environments.PROD ? `*.${domainName}` : domainName, + hostedZone: theHostedZone, }); + + hostedZone = theHostedZone; + + if (environment === Environments.PROD) { + domainNames.push(`www.${domainName}`); + + wwwRedirectFunction = new Function(this, "WwwRedirectFunction", { + code: FunctionCode.fromInline(` + function handler(event) { + return { + statusCode: 301, + statusDescription: "Moved Permanently", + headers: { + location: { + value: "https://${domainName}" + event.request.uri, + } + } + } + } + `), + }); + } } const astroAwsConstruct = new AstroAWSConstruct(this, "AstroAWSConstruct", { distributionProps: { certificate, defaultBehavior: { + functionAssociations: wwwRedirectFunction + ? [ + { + eventType: FunctionEventType.VIEWER_REQUEST, + function: wwwRedirectFunction, + }, + ] + : undefined, responseHeadersPolicy: new ResponseHeadersPolicy(this, "ResponseHeadersPolicy", { securityHeadersBehavior: { contentSecurityPolicy: { @@ -47,39 +96,30 @@ export class WebsiteStack extends Stack { }, }), }, - domainNames: domainName ? [domainName] : [], + domainNames, }, lambdaProps: { architecture: Architecture.ARM_64, - memorySize: 1024, + memorySize: 512, }, node16: environment === Environments.DEV_NODE_16, - websitePath: "../www", - }); - - const lambdaFailureRateMetric = astroAwsConstruct.lambda.metricErrors({ - label: "Lambda failure rate", - period: Duration.minutes(5), - statistic: "sum", - }); - - const lambdaInvocationsMetric = astroAwsConstruct.lambda.metricInvocations({ - label: "Lambda invocations", - period: Duration.minutes(5), - statistic: "sum", + outDir: `../www/dist/${output}`, + output, }); - const lambdaDurationMetric = astroAwsConstruct.lambda.metricDuration({ - label: "Lambda duration", - period: Duration.minutes(5), - statistic: "avg", - }); + if (hostedZone) { + new ARecord(this, "ARecord", { + recordName: domainName, + target: RecordTarget.fromAlias(new CloudFrontTarget(astroAwsConstruct.distribution)), + zone: hostedZone, + }); - const lambdaThrottlesMetric = astroAwsConstruct.lambda.metricThrottles({ - label: "Lambda throttles", - period: Duration.minutes(5), - statistic: "sum", - }); + new AaaaRecord(this, "AaaaRecord", { + recordName: domainName, + target: RecordTarget.fromAlias(new CloudFrontTarget(astroAwsConstruct.distribution)), + zone: hostedZone, + }); + } const distribution5xxErrorRateMetric = new DistributionMetric({ distribution: astroAwsConstruct.distribution, @@ -91,20 +131,51 @@ export class WebsiteStack extends Stack { const distributionRequestsMetric = new DistributionMetric({ distribution: astroAwsConstruct.distribution, - label: "CloudFront 5xx error rate", + label: "CloudFront requests", metricName: "Requests", period: Duration.minutes(5), statistic: "Sum", }); - cloudwatchDashboard.addWidgets( - new BasicGraphWidget({ metric: lambdaFailureRateMetric }), - new BasicGraphWidget({ metric: lambdaInvocationsMetric }), - new BasicGraphWidget({ metric: lambdaDurationMetric }), - new BasicGraphWidget({ metric: lambdaThrottlesMetric }), + const widgets = [ new BasicGraphWidget({ metric: distribution5xxErrorRateMetric }), new BasicGraphWidget({ metric: distributionRequestsMetric }), - ); + ]; + + if (astroAwsConstruct.lambda) { + const lambdaFailureRateMetric = astroAwsConstruct.lambda.metricErrors({ + label: "Lambda failure rate", + period: Duration.minutes(5), + statistic: "sum", + }); + + const lambdaInvocationsMetric = astroAwsConstruct.lambda.metricInvocations({ + label: "Lambda invocations", + period: Duration.minutes(5), + statistic: "sum", + }); + + const lambdaDurationMetric = astroAwsConstruct.lambda.metricDuration({ + label: "Lambda duration", + period: Duration.minutes(5), + statistic: "avg", + }); + + const lambdaThrottlesMetric = astroAwsConstruct.lambda.metricThrottles({ + label: "Lambda throttles", + period: Duration.minutes(5), + statistic: "sum", + }); + + widgets.push( + new BasicGraphWidget({ metric: lambdaFailureRateMetric }), + new BasicGraphWidget({ metric: lambdaInvocationsMetric }), + new BasicGraphWidget({ metric: lambdaDurationMetric }), + new BasicGraphWidget({ metric: lambdaThrottlesMetric }), + ); + } + + cloudwatchDashboard.addWidgets(...widgets); new CfnOutput(this, "CloudFrontDistributionId", { value: astroAwsConstruct.distribution.distributionId, diff --git a/apps/www/astro.config.js b/apps/www/astro.config.ts similarity index 54% rename from apps/www/astro.config.js rename to apps/www/astro.config.ts index 15e6015..044c49c 100644 --- a/apps/www/astro.config.js +++ b/apps/www/astro.config.ts @@ -1,22 +1,27 @@ +import process from "node:process"; + import { defineConfig } from "astro/config"; -import preact from "@astrojs/preact"; -import react from "@astrojs/react"; import tailwind from "@astrojs/tailwind"; import aws from "@astro-aws/adapter"; import mdx from "@astrojs/mdx"; import sitemap from "@astrojs/sitemap"; import remarkToc from "remark-toc"; +const isSSR = process.env.SSR_BUILD === "true"; + // https://astro.build/config export default defineConfig({ - adapter: aws({ - esm: true, - }), - integrations: [preact(), react(), tailwind(), mdx(), sitemap()], + adapter: isSSR + ? aws({ + esm: true, + }) + : undefined, + integrations: [tailwind(), mdx(), sitemap()], markdown: { extendDefaultPlugins: true, remarkPlugins: [remarkToc], }, - output: "server", - site: `https://astro-aws.lshay.dev/`, + outDir: isSSR ? "dist/server" : "dist/static", + output: isSSR ? "server" : "static", + site: `https://astro-aws.org/`, }); diff --git a/apps/www/package.json b/apps/www/package.json index 9cbb6af..b9a207a 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -2,7 +2,7 @@ "name": "@astro-aws/www", "version": "0.0.0", "private": true, - "homepage": "https://astro-aws.lshay.dev/", + "homepage": "https://astro-aws.org/", "repository": { "type": "git", "url": "ssh://git@github.com/lukeshay/astro-aws.git", @@ -15,12 +15,12 @@ ], "scripts": { "astro": "astro", - "build": "./scripts/copy-package-readmes.sh && astro build", + "build": "yarn clean && ./scripts/copy-package-readmes.sh && astro build && SSR_BUILD=true astro build", "check": "astro check && tsc", "clean": "rimraf dist", "dev": "astro dev", "preview": "astro preview", - "release": "yarn clean && yarn build && yarn package", + "release": "yarn build && yarn package", "start": "astro dev" }, "eslintConfig": { @@ -45,6 +45,7 @@ "@types/react": "^18.0.25", "@types/react-dom": "^18.0.9", "astro": "^1.6.11", + "astro-icon": "^0.8.0", "astro-link": "^1.1.2", "astro-seo": "^0.6.0", "autoprefixer": "^10.4.13", diff --git a/apps/www/src/components/layout/LeftSidenav.astro b/apps/www/src/components/layout/LeftSidenav.astro index a8946a7..348c22f 100644 --- a/apps/www/src/components/layout/LeftSidenav.astro +++ b/apps/www/src/components/layout/LeftSidenav.astro @@ -1,6 +1,6 @@ --- import Logo from "../Logo.astro"; -import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; +import { Icon } from "astro-icon"; export type Link = { text: string; @@ -28,8 +28,8 @@ const chevronClass = "w-5 h-5 pl-2 stroke-[4]";