diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..529eb69 --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,57 @@ +name: Build and Publish Portfolio + +on: + push: + branches: [main] + tags: + - "v*" # Trigger on version tags like v1.0.0 + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "" + + - name: Install dependencies + run: npm ci + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') # Only publish on version tags + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "" + + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: dist + path: dist/ + + - name: Install dependencies + run: npm ci + + - name: Deploy + run: npm run-script cdk -- deploy diff --git a/.gitignore b/.gitignore index a547bf3..f6ba5c7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +cdk.out diff --git a/aws/index.ts b/aws/index.ts new file mode 100644 index 0000000..1c63ac5 --- /dev/null +++ b/aws/index.ts @@ -0,0 +1,13 @@ +import { ForgePortfolioStack } from "./stack.ts"; +import * as cdk from 'aws-cdk-lib'; + +const app = new cdk.App(); + +new ForgePortfolioStack(app, 'JaredForgePortfolio', { + env: { + account: process.env.AWS_ACCOUNT, + region: 'us-east-1', + }, + domainName: 'portfolio.kling.dev', + hostedZoneId: process.env.AWS_ROUTE53_ZONE +}); diff --git a/aws/stack.ts b/aws/stack.ts new file mode 100644 index 0000000..e098b55 --- /dev/null +++ b/aws/stack.ts @@ -0,0 +1,202 @@ +import * as cdk from 'aws-cdk-lib'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as route53 from 'aws-cdk-lib/aws-route53'; +import * as targets from 'aws-cdk-lib/aws-route53-targets'; +import * as certificatemanager from 'aws-cdk-lib/aws-certificatemanager'; +import { Construct } from 'constructs'; + +export interface ForgePortfolioStackProps extends cdk.StackProps { + domainName?: string; // Optional custom domain + hostedZoneId?: string; // Required if using custom domain +} + +export class ForgePortfolioStack extends cdk.Stack { + public readonly bucket: s3.Bucket; + public readonly distribution: cloudfront.Distribution; + public readonly deploymentUser: iam.User; + + constructor(scope: Construct, id: string, props: ForgePortfolioStackProps) { + super(scope, id, props); + + // S3 Bucket for hosting static files + this.bucket = new s3.Bucket(this, 'PortfolioBucket', { + bucketName: `${id.toLowerCase()}-portfolio-${this.account}`, + removalPolicy: cdk.RemovalPolicy.DESTROY, // Be careful with this in production + autoDeleteObjects: true, + publicReadAccess: false, // CloudFront will handle access + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + versioned: false, // Keep costs low + websiteIndexDocument: 'index.html', + websiteErrorDocument: 'error.html', + }); + + // Origin Access Control for CloudFront to access S3 + const originAccessControl = new cloudfront.S3OriginAccessControl(this, 'OAC', { + description: 'OAC for portfolio website', + }); + + // SSL Certificate (only if custom domain is provided) + let certificate: certificatemanager.Certificate | undefined; + if (props.domainName) { + certificate = new certificatemanager.Certificate(this, 'Certificate', { + domainName: props.domainName, + validation: certificatemanager.CertificateValidation.fromDns(), + }); + } + + // CloudFront Distribution + this.distribution = new cloudfront.Distribution(this, 'Distribution', { + defaultBehavior: { + origin: origins.S3BucketOrigin.withOriginAccessControl(this.bucket, { + originAccessControl, + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, + compress: true, + allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD, + }, + defaultRootObject: 'index.html', + errorResponses: [ + { + httpStatus: 404, + responseHttpStatus: 200, + responsePagePath: '/index.html', // For SPA routing + ttl: cdk.Duration.minutes(5), + }, + { + httpStatus: 403, + responseHttpStatus: 200, + responsePagePath: '/index.html', // For SPA routing + ttl: cdk.Duration.minutes(5), + }, + ], + domainNames: props.domainName ? [props.domainName] : undefined, + certificate: certificate, + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Use only NA and Europe for cost optimization + }); + + // Grant CloudFront access to S3 bucket + this.bucket.addToResourcePolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: [this.bucket.arnForObjects('*')], + principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], + conditions: { + StringEquals: { + 'AWS:SourceArn': `arn:aws:cloudfront::${this.account}:distribution/${this.distribution.distributionId}`, + }, + }, + }) + ); + + // Ensure bucket allows CloudFront to list objects (needed for error handling) + this.bucket.addToResourcePolicy( + new iam.PolicyStatement({ + actions: ['s3:ListBucket'], + resources: [this.bucket.bucketArn], + principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], + conditions: { + StringEquals: { + 'AWS:SourceArn': `arn:aws:cloudfront::${this.account}:distribution/${this.distribution.distributionId}`, + }, + }, + }) + ); + + // Custom domain DNS record (if provided) + if (props.domainName && props.hostedZoneId) { + const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', { + hostedZoneId: props.hostedZoneId, + zoneName: props.domainName, + }); + + new route53.ARecord(this, 'ARecord', { + zone: hostedZone, + target: route53.RecordTarget.fromAlias( + new targets.CloudFrontTarget(this.distribution) + ), + }); + } + + // IAM User for GitHub Actions deployment + this.deploymentUser = new iam.User(this, 'DeploymentUser', { + userName: `${id}-github-actions-user`, + }); + + // Policy for deployment user + const deploymentPolicy = new iam.Policy(this, 'DeploymentPolicy', { + statements: [ + // S3 permissions for uploading files + new iam.PolicyStatement({ + actions: [ + 's3:PutObject', + 's3:PutObjectAcl', + 's3:GetObject', + 's3:DeleteObject', + 's3:ListBucket', + ], + resources: [ + this.bucket.bucketArn, + this.bucket.arnForObjects('*'), + ], + }), + // CloudFront invalidation permissions + new iam.PolicyStatement({ + actions: ['cloudfront:CreateInvalidation'], + resources: [ + `arn:aws:cloudfront::${this.account}:distribution/${this.distribution.distributionId}`, + ], + }), + ], + }); + + this.deploymentUser.attachInlinePolicy(deploymentPolicy); + + // Create Access Key for the deployment user + const accessKey = new iam.AccessKey(this, 'DeploymentUserAccessKey', { + user: this.deploymentUser, + }); + + // Outputs for GitHub Actions + new cdk.CfnOutput(this, 'BucketName', { + value: this.bucket.bucketName, + description: 'S3 Bucket name for deployment', + }); + + new cdk.CfnOutput(this, 'DistributionId', { + value: this.distribution.distributionId, + description: 'CloudFront Distribution ID for cache invalidation', + }); + + new cdk.CfnOutput(this, 'DistributionDomainName', { + value: this.distribution.distributionDomainName, + description: 'CloudFront Distribution domain name', + }); + + new cdk.CfnOutput(this, 'AccessKeyId', { + value: accessKey.accessKeyId, + description: 'Access Key ID for GitHub Actions (store as secret)', + }); + + new cdk.CfnOutput(this, 'SecretAccessKey', { + value: accessKey.secretAccessKey.unsafeUnwrap(), + description: 'Secret Access Key for GitHub Actions (store as secret)', + }); + + if (props.domainName) { + new cdk.CfnOutput(this, 'WebsiteURL', { + value: `https://${props.domainName}`, + description: 'Website URL', + }); + } else { + new cdk.CfnOutput(this, 'WebsiteURL', { + value: `https://${this.distribution.distributionDomainName}`, + description: 'Website URL', + }); + } + } +} diff --git a/cdk.context.json b/cdk.context.json new file mode 100644 index 0000000..c3f254d --- /dev/null +++ b/cdk.context.json @@ -0,0 +1,6 @@ +{ + "cli-telemetry": false, + "acknowledged-issue-numbers": [ + 34892 + ] +} diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..af19f42 --- /dev/null +++ b/cdk.json @@ -0,0 +1,12 @@ +{ + "app": "npx ts-node --prefer-ts-exts aws/index.ts", + "watch": { + "include": ["**"], + "exclude": ["README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "tsconfig.json", "package*.json", "yarn.lock", "node_modules", "test"] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"] + } +} diff --git a/package-lock.json b/package-lock.json index d196af8..4499e43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "forge-profile", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "forge-profile", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { "@jkling38/forge": "^0.0.17", "react": "^19.1.0", @@ -14,9 +14,12 @@ }, "devDependencies": { "@eslint/js": "^9.29.0", + "@types/node": "^24.0.10", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.5.2", + "aws-cdk": "^2.1020.2", + "aws-cdk-lib": "^2.204.0", "eslint": "^9.29.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -40,6 +43,59 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.242", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.242.tgz", + "integrity": "sha512-4c1bAy2ISzcdKXYS1k4HYZsNrgiwbiDzj36ybwFVxEWZXVAP0dimQTCaB9fxu7sWzEjw3d+eaw6Fon+QTfTIpQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "45.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-45.2.0.tgz", + "integrity": "sha512-5TTUkGHQ+nfuUGwKA8/Yraxb+JdNUh4np24qk/VHXmrCMq+M6HfmGWfhcg/QlHA2S5P3YIamfYHdQAB4uSNLAg==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.2" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1426,6 +1482,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", + "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, "node_modules/@types/react": { "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", @@ -1800,6 +1866,422 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aws-cdk": { + "version": "2.1020.2", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1020.2.tgz", + "integrity": "sha512-yWdt3dJh4aPm1VNyEgfG3lozGrvddw0i7avt+Cl9KOYixmisQtAg39/aZqzVVqjzVZVEanXmz+tlhzzh75Z69A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.204.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.204.0.tgz", + "integrity": "sha512-mY3nYu+QvPhO+fz+LCFKbc0PFhTHbHzDLnbcA2fPcQBKciYnTixpBd2ccRlKYWbG4y6NTc6ju6DudZ3HIS4OlA==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.242", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^45.0.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.7.2", + "table": "^6.9.0", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "dev": true, + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/aws-cdk/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1939,6 +2421,14 @@ "dev": true, "license": "MIT" }, + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3198,6 +3688,13 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", diff --git a/package.json b/package.json index 0e4f672..2eea9f9 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "forge-profile", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "cdk": "cdk" }, "dependencies": { "@jkling38/forge": "^0.0.17", @@ -16,9 +17,12 @@ }, "devDependencies": { "@eslint/js": "^9.29.0", + "@types/node": "^24.0.10", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.5.2", + "aws-cdk": "^2.1020.2", + "aws-cdk-lib": "^2.204.0", "eslint": "^9.29.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/vite.config.ts b/vite.config.ts index 1bd48a2..6b2acc3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,14 +5,14 @@ export default defineConfig({ plugins: [ react(), ], + base: '/', build: { + outDir: 'dist', + assetsDir: 'static', + sourcemap: false, rollupOptions: { - external: ["react", "react-dom"], output: { - globals: { - react: "React", - "react-dom": "ReactDOM", - }, + manualChunks: undefined }, }, },