custom shiki highlighter from dominikg
This commit is contained in:
parent
c7c2ea60a2
commit
9b976986d7
|
@ -1,6 +1,7 @@
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
// and what to do when importing types
|
// and what to do when importing types
|
||||||
|
|
||||||
declare namespace App {
|
declare namespace App {
|
||||||
// interface Locals {}
|
// interface Locals {}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
|
|
|
@ -24,6 +24,12 @@ body {
|
||||||
src: url('/fonts/Quicksand.ttf');
|
src: url('/fonts/Quicksand.ttf');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prose {
|
||||||
|
font-family: 'Cooper Hewitt', sans-serif;
|
||||||
|
letter-spacing: 0.25px;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Magilio';
|
font-family: 'Magilio';
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
tldr:
|
||||||
|
I started by containerizing my SvelteKit and Strapi apps, then Pushed these to Docker Hub and AWS ECR,
|
||||||
|
leveraging 1 free private image on docker hub and free-tier 500mb limit on AWS ECR, thereby minimizing costs and exploring each option.
|
||||||
|
|
||||||
|
1. Conteinerization
|
||||||
|
a. Sveltekit
|
||||||
|
Keeping in mind that dev, build, test and lint, etc. scripts are handled by turborepo, we use these when containerizing.
|
||||||
|
|
||||||
|
Step 1.
|
||||||
|
|
||||||
|
b. Strapi
|
|
@ -0,0 +1,7 @@
|
||||||
|
choosing a package manager
|
||||||
|
|
||||||
|
I tried to abstain from the community battles between pms and looked at the performance of the top 3 used - npm, yarn and pnpm. https://pnpm.io/benchmarks
|
||||||
|
I've got experience with each and would like to use pnpm as it is the much faster than NPM,
|
||||||
|
however Strapi doesn't officialy support it so I ended up choosing yarn as in most cases it is a bit faster than npm, has some better handling with monorepos and I personally like it.
|
||||||
|
I used Turborepo with npm and there were some problems with setting dependencies in workspaces vs the root of monorepo and private packages, which required workarounds.
|
||||||
|
In terms of CI performance can have significant consequences.
|
|
@ -0,0 +1,15 @@
|
||||||
|
AWS set up
|
||||||
|
|
||||||
|
A thing I learned using linux is user management, specifically don't do everything as root :D.
|
||||||
|
When using AWS first I didn't care about IAM roles, but I think I learned the same lesson...
|
||||||
|
|
||||||
|
Anyways I used this process, when configuring accounts:
|
||||||
|
|
||||||
|
When starting login as root user and access IAM dashboard,
|
||||||
|
Set up Multi-factor Auth as root user (prefferably with Yubikey or some OTP in a secure device)
|
||||||
|
Later on, like in linux, the root account access can be minimized for a better decentralized privilege system, even when being the only admin.
|
||||||
|
|
||||||
|
Go to services then IAM Identity center
|
||||||
|
Select identity source -> In my case stick with the default Identity Center directory, bigger organizations or ones using other sources can connect to external ones.
|
||||||
|
In Multi-account permissions select permission sets and create a permission set.
|
||||||
|
Then go to AWS accounts, select the account you wish and assign the permission set to them.
|
|
@ -0,0 +1,62 @@
|
||||||
|
Namecheap
|
||||||
|
Saw mattmor.in and just bought it, no questions asked.
|
||||||
|
|
||||||
|
I immediately created a cloudflare account, because of their great certificate management, security checks, analytics and mostly DNS.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Setting up AWS EC2 & RDS instances
|
||||||
|
|
||||||
|
EC2 setup
|
||||||
|
I've got a great opportunity to set up a "free-tier" instance, of course the limitations are quite interesting - 750 hrs/m and some compute limiting factors.
|
||||||
|
Let's use this opportunity, it is a better deal than GCP with 300$/3mos and my experience with GCP while dealing with Auth API was pretty bad.
|
||||||
|
|
||||||
|
Made SSH keys
|
||||||
|
got an Elastic IP and connected it with my EC2 instance to be accessible publicly on a stable IP.
|
||||||
|
added DNS records to cloudflare
|
||||||
|
|
||||||
|
|
||||||
|
Log in via SSH, update, upgrade everything, restart.
|
||||||
|
Then I configured fail2ban with some custom responses and enabled it, I also configured the seemingly redundant ufw and allowed ssh, https
|
||||||
|
|
||||||
|
And with that I am ready to roll onto installing necessary software like nginx, jenkins, docker for now.
|
||||||
|
|
||||||
|
RDS setup
|
||||||
|
|
||||||
|
I've already configured Strapi for my backend, I selected PostgreSQL, because while theoretically the default SQLite db would be sufficient and very easy to manage.
|
||||||
|
I know it's scalability is limited and is not suitable for multiple user access, which I might need in other projects with the same tech stack.
|
||||||
|
The main reason for choosing PostgreSQL is I can showcase it and learn with it on a project using AWS.
|
||||||
|
I've worked mainly with MariaDB before, and used PostgreSQL only on some ancient Wordpress projects back in the day.
|
||||||
|
|
||||||
|
As I already set up a free-tier EC2 instance, I can also setup AWS RDS with Aurora(PostgreSQL compatible) or PostgreSQL also on free-tier, which is awesome!
|
||||||
|
Let's choose PostgreSQL, because that's simpler and more than sufficient.
|
||||||
|
|
||||||
|
The Free Tier is limited to the same time frame for RDS...
|
||||||
|
If I really wanted to make this completely free I could use AWS Lambda, which is also free up to 1 million requests/month :D, to turn off both the EC2 and RDS instances at night. Gotta think when do recruiters go to sleep...
|
||||||
|
|
||||||
|
|
||||||
|
Now seriously, to actualy set this up:
|
||||||
|
|
||||||
|
Select:
|
||||||
|
burstable class db.t4g.micro
|
||||||
|
General Purpose SSD (gp3) 20GiB
|
||||||
|
I disabled autoscaling as it's not possible I would use more than this in this use-case.
|
||||||
|
|
||||||
|
Strapi recommends PostgreSQL v14.0 at the time of writing
|
||||||
|
AWS offers 14.5 above, let's choose 14.9 R1, should be backwards compatible.
|
||||||
|
Template -> Free tier
|
||||||
|
|
||||||
|
Security:
|
||||||
|
As my OpSec dictates I choose passwords with high entropy so you can't hack me.
|
||||||
|
|
||||||
|
Backup & Maintenance:
|
||||||
|
Setup Backup and Amazon auto-maintenance windows, I am not a maniac to not backup.
|
||||||
|
|
||||||
|
Connectivity:
|
||||||
|
I will not connect this to my EC2 instance, because I will be testing this on localhost.
|
||||||
|
I need public access and in this case I do not think accessing by a private network is necessary.
|
||||||
|
I will make a new VPC security group, then we can access it via an internet gateway.
|
||||||
|
|
||||||
|
|
||||||
|
https://docs.aws.amazon.com/images/AmazonRDS/latest/UserGuide/images/GS-VPC-network.png
|
|
@ -1,18 +1,26 @@
|
||||||
---
|
---
|
||||||
title: First post
|
title: First post
|
||||||
|
type: blog
|
||||||
excerpt: First post
|
excerpt: First post
|
||||||
date: 2021-01-01
|
|
||||||
tags:
|
tags:
|
||||||
- first
|
- Blog
|
||||||
- post
|
- Linux
|
||||||
published: true
|
- Declarative
|
||||||
image: Feature.jpg
|
image: Feature.jpg
|
||||||
|
postTitle: 'Best Medium Format Camera for Starting Out'
|
||||||
|
focusKeyphrase: 'best medium format camera'
|
||||||
|
datePublished: '2023-04-07T16:04:42.000+0100'
|
||||||
|
lastUpdated: '2023-04-14T10:17:52.000+0100'
|
||||||
|
seoMetaDescription: "Let's take a look."
|
||||||
|
featuredImage: ''
|
||||||
|
featuredImageAlt: 'Our own pcb'
|
||||||
|
publicImageId: ''
|
||||||
---
|
---
|
||||||
## Svelte
|
## Svelte
|
||||||
|
|
||||||
Media inside the **Svelte** folder is served from the `static` folder.
|
Media inside the **Svelte** folder is served from the `static` folder.
|
||||||
|
|
||||||
```python
|
```python showLines
|
||||||
|
|
||||||
input_text = ''' "yahooapis.com",
|
input_text = ''' "yahooapis.com",
|
||||||
"hotmail.com",
|
"hotmail.com",
|
||||||
|
@ -30,3 +38,112 @@ output_text = '\n'.join(formatted_lines)
|
||||||
print(output_text)
|
print(output_text)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Turborepo [see 1st source]
|
||||||
|
I will use turborepo as I have experience with it from previous projects, I prefer the centralized, more orderly handling of repositories.
|
||||||
|
Because our CMS and webapp are workspaces that need to be configured differently, we need to extend the root config of Turborepo to each app individually by creating a turbo.json in each workspace.
|
||||||
|
|
||||||
|
For Strapi this will be:
|
||||||
|
$root/apps/cms/turbo.json
|
||||||
|
|
||||||
|
``` json
|
||||||
|
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
// custom configuration for the build task in this workspace
|
||||||
|
},
|
||||||
|
// new tasks only available in this workspace
|
||||||
|
"special-task": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For Sveltekit Webapp:
|
||||||
|
$root/apps/web/turbo.json
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [".svelte-kit/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key notes about this setup:
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
Base Layer: Installs container dependencies and Turborepo globally.
|
||||||
|
Pruned Layer: Copies project files and runs turbo prune to exclude unnecessary dependencies.
|
||||||
|
Installer Layer: Copies pruned workspace and runs yarn to install dependencies.
|
||||||
|
Runner Layer: Starts the app.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
|
||||||
|
Learned
|
||||||
|
|
||||||
|
Used sources:
|
||||||
|
|
||||||
|
1. https://turbo.build/repo/docs
|
||||||
|
2. https://dev.to/moofoo/creating-a-development-dockerfile-and-docker-composeyml-for-yarn-122-monorepos-using-turborepo-896
|
||||||
|
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
install Grafana, Prometheus, ELK Stack, and Jenkins on a single server and use them to monitor other EC2 instances or cloud resources. There are trade-offs:
|
||||||
|
|
||||||
|
Pros:
|
||||||
|
|
||||||
|
Simplified Management: All tools in one place.
|
||||||
|
Lower Costs: Fewer servers to maintain.
|
||||||
|
|
||||||
|
Cons:
|
||||||
|
|
||||||
|
Resource Contention: These tools can be resource-intensive.
|
||||||
|
Single Point of Failure: If the server goes down, all tools are affected.
|
||||||
|
Security Risks: Multiple services on one server can increase the attack surface.
|
||||||
|
|
||||||
|
Recommendations:
|
||||||
|
|
||||||
|
Use containerization (Docker) for easier management and isolation.
|
||||||
|
Set up a robust backup and recovery strategy.
|
||||||
|
Ensure adequate resource allocation and scaling capabilities.
|
|
@ -1,12 +1,19 @@
|
||||||
---
|
---
|
||||||
title: w post
|
title: First post
|
||||||
excerpt: w post
|
type: blog
|
||||||
date: 2021-01-01
|
excerpt: First post
|
||||||
tags:
|
tags:
|
||||||
- first
|
- NixOS
|
||||||
- post
|
- Linux
|
||||||
published: true
|
- Declarative
|
||||||
image: Feature.jpg
|
image: Feature.jpg
|
||||||
|
postTitle: 'Best Medium Format Camera for Starting Out'
|
||||||
|
focusKeyphrase: 'best medium format camera'
|
||||||
|
datePublished: '2022-04-07T16:04:42.000+0100'
|
||||||
|
lastUpdated: '2022-04-14T10:17:52.000+0100'
|
||||||
|
seoMetaDescription: "Let's take a look."
|
||||||
|
featuredImage: ''
|
||||||
|
featuredImageAlt: 'Our own pcb'
|
||||||
|
publicImageId: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
## Svelte
|
## Svelte
|
|
@ -0,0 +1,76 @@
|
||||||
|
The goal of this project is to showcase my skillset both as a portfolio site and showcase how I used devops and web development practices to build it.
|
||||||
|
The tech stack and feature set is meant to:
|
||||||
|
legitimize the goal of the portfolio site, which is mentioned above
|
||||||
|
Use modern DevOps and web development practices that I know of so I can build it as fast as possible.
|
||||||
|
minimize costs and be potentially scalable and feature-upgradeable.
|
||||||
|
be secure and be build using my evolving OpSec and limited cybersecurity knowledge.
|
||||||
|
show proficiency in using new tools.
|
||||||
|
|
||||||
|
As such the current cloud stack is:
|
||||||
|
1. AWS EC2 t3.micro
|
||||||
|
For: Jenkins
|
||||||
|
Why: CI/CD pipeline, free tier, 1GB RAM sufficient for Jenkins.
|
||||||
|
2. AWS EC2 t3.micro
|
||||||
|
For: docker-compose SvelteKit app
|
||||||
|
Why: Frontend, free tier, 1GB RAM sufficient for SvelteKit.
|
||||||
|
|
||||||
|
3. Amazon ECS Anywhere
|
||||||
|
|
||||||
|
For: docker-compose Strapi
|
||||||
|
Why: Container orchestration, 2200 free hours, scalable.
|
||||||
|
|
||||||
|
4. AWS Lambda
|
||||||
|
|
||||||
|
For: Automated tasks
|
||||||
|
Why: 1 million free requests, event-driven architecture.
|
||||||
|
|
||||||
|
5. Amazon RDS
|
||||||
|
|
||||||
|
For: Database
|
||||||
|
Why: 750 free hours, managed service, 20GB storage.
|
||||||
|
|
||||||
|
6. Amazon S3
|
||||||
|
|
||||||
|
For: File storage, backups
|
||||||
|
Why: 5GB free storage, durable.
|
||||||
|
|
||||||
|
7. Amazon CloudWatch
|
||||||
|
|
||||||
|
For: Monitoring
|
||||||
|
Why: 10 free custom metrics and alarms.
|
||||||
|
|
||||||
|
8. AWS Secrets Manager
|
||||||
|
|
||||||
|
For: Secrets
|
||||||
|
Why: Secure, but consider alternatives due to cost.
|
||||||
|
|
||||||
|
9. Amazon API Gateway
|
||||||
|
|
||||||
|
For: APIs
|
||||||
|
Why: 1 million free API calls, secure.
|
||||||
|
|
||||||
|
10. Terraform and Ansible
|
||||||
|
|
||||||
|
For: IaC and Configuration
|
||||||
|
Why: Version control, automation.
|
||||||
|
|
||||||
|
11. Documentation
|
||||||
|
|
||||||
|
For: READMEs
|
||||||
|
Why: Clarity, onboarding, and best practices.
|
||||||
|
|
||||||
|
12. GitHub and AWS ECR
|
||||||
|
|
||||||
|
For: Code and container repositories
|
||||||
|
Why: Version control, Docker Hub limitations.
|
||||||
|
|
||||||
|
Cost Analysis
|
||||||
|
|
||||||
|
EC2 t3.micro: Free tier
|
||||||
|
ECS Anywhere: Free tier (2200 hours)
|
||||||
|
Lambda: Free tier (1 million requests)
|
||||||
|
RDS: Free tier (750 hours)
|
||||||
|
S3: Free tier (5GB)
|
||||||
|
CloudWatch: Free tier (10 metrics)
|
||||||
|
Secrets Manager: $0.40/secret, consider alternatives
|
||||||
|
API Gateway: Free tier (1 million calls)
|
|
@ -1,13 +1,20 @@
|
||||||
---
|
---
|
||||||
title: Second post
|
title: First post
|
||||||
excerpt: Second post
|
type: blog
|
||||||
date: 2023-01-01
|
excerpt: First post
|
||||||
tags:
|
tags:
|
||||||
- first
|
- NixOS
|
||||||
- post
|
- Linux
|
||||||
published: true
|
- Declarative
|
||||||
image: Feature.jpg
|
image: Feature.jpg
|
||||||
|
postTitle: 'Best Medium Format Camera for Starting Out'
|
||||||
|
focusKeyphrase: 'best medium format camera'
|
||||||
|
datePublished: '2024-04-07T16:04:42.000+0100'
|
||||||
|
lastUpdated: '2021-04-14T10:17:52.000+0100'
|
||||||
|
seoMetaDescription: "Let's take a look."
|
||||||
|
featuredImage: ''
|
||||||
|
featuredImageAlt: 'Our own pcb'
|
||||||
|
publicImageId: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,5 +30,4 @@ Media inside the **Svelte** folder is server from the `static` folder.
|
||||||
|
|
||||||
I am ditching the societal value of having a contributions table on my profile, you should view it on git.mattmor.in
|
I am ditching the societal value of having a contributions table on my profile, you should view it on git.mattmor.in
|
||||||
|
|
||||||
|
|
||||||
--- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore.
|
--- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore.
|
|
@ -0,0 +1,74 @@
|
||||||
|
Hard skills in SW:
|
||||||
|
HTML, CSS, JS, TS, Svelte, Sveltekit, Vite,
|
||||||
|
|
||||||
|
WebDev (meta)frameworks: Svelte (and sveltekit), a theoretical knowledge of reactjs (and next.js) and vue (and nuxt)
|
||||||
|
WebDev langs: HTML, CSS, tailwindcss, postcss (basics), JS, TS (basics), multiple ui libraries and billions of packages :D
|
||||||
|
WebDev linting: eslint
|
||||||
|
WebDev testing: Playwright
|
||||||
|
|
||||||
|
Languages: JS, Python, Micropython, C and C++ (both basics with microcontrollers), I really want to code in OstraJava and Brainf*ck
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OS:
|
||||||
|
Linux [Ubuntu, Debian (Rpi OS, Kali - basics), QubesOS, Arch (Manjaro)], WSL,
|
||||||
|
Windows :D, advanced as a power user of XP,Vista,7,8,10,11 in my life, caused deep trauma. Haven't used win servers and don't plan to.
|
||||||
|
|
||||||
|
Terminal:
|
||||||
|
Process Monitoring
|
||||||
|
Performance Monitoring
|
||||||
|
Networking Tools
|
||||||
|
Text Manipulation
|
||||||
|
|
||||||
|
Scripting:
|
||||||
|
Bash
|
||||||
|
Power Shell (for user tasks)
|
||||||
|
|
||||||
|
Editors:
|
||||||
|
Nano, Emacs, VS Code, Notepad :D and Jupyter notebook
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Version control: Git
|
||||||
|
|
||||||
|
|
||||||
|
VCS Hosting: Github, Gitlab
|
||||||
|
CI/CD:
|
||||||
|
Jenkins In progress
|
||||||
|
Gitlab CI In progress
|
||||||
|
|
||||||
|
Infrastructure Provisioning:
|
||||||
|
Terraform: In progress
|
||||||
|
|
||||||
|
Cloud Providers:
|
||||||
|
AWS - Preffered
|
||||||
|
DigitalOcean
|
||||||
|
Google Cloud (I did auth, api, company set up and some bots with spreadsheet)
|
||||||
|
|
||||||
|
CDN: Cloudinary
|
||||||
|
|
||||||
|
Networking, Security and Protocols:
|
||||||
|
FTP / SFTP
|
||||||
|
SSL / TLS
|
||||||
|
HTTP / HTTPS
|
||||||
|
DNS
|
||||||
|
SSH (putty and linux)
|
||||||
|
|
||||||
|
Serverless: AWS Lambda (0), Cloudflare, Vercel
|
||||||
|
|
||||||
|
Monorepo (with pipelines): all yarn, npm, pnpm with turborepo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Containerization: docker (dockerfile, docker-compose, ran many apps with it), DockerHub, AWS RCD
|
||||||
|
|
||||||
|
Container Orchestration: Docker swarm(basics)
|
||||||
|
K8s/K3s: In progress...
|
||||||
|
|
||||||
|
Orchestration: Terraform (0)
|
||||||
|
|
||||||
|
GitOps: In progress ArgoCD
|
||||||
|
|
||||||
|
Application monitoring: New Relic, Prometheus, Grafana, Elk stack
|
||||||
|
|
||||||
|
Logs Management:
|
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
## Turborepo [see 1st source]
|
||||||
|
I will use turborepo as I have experience with it from previous projects, I prefer the centralized, more orderly handling of repositories.
|
||||||
|
Because our CMS and webapp are workspaces that need to be configured differently, we need to extend the root config of Turborepo to each app individually by creating a turbo.json in each workspace.
|
||||||
|
|
||||||
|
For Strapi this will be:
|
||||||
|
$root/apps/cms/turbo.json
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
// custom configuration for the build task in this workspace
|
||||||
|
},
|
||||||
|
// new tasks only available in this workspace
|
||||||
|
"special-task": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For Sveltekit Webapp:
|
||||||
|
$root/apps/web/turbo.json
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [".svelte-kit/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Key notes about this setup:
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
Base Layer: Installs container dependencies and Turborepo globally.
|
||||||
|
Pruned Layer: Copies project files and runs turbo prune to exclude unnecessary dependencies.
|
||||||
|
Installer Layer: Copies pruned workspace and runs yarn to install dependencies.
|
||||||
|
Runner Layer: Starts the app.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||||
|
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||||
|
|
||||||
|
To push images to docker hub:
|
||||||
|
|
||||||
|
```docker push mando42/portfolio:tagname```
|
||||||
|
|
||||||
|
|
||||||
|
Learned
|
||||||
|
|
||||||
|
Used sources:
|
||||||
|
1. https://turbo.build/repo/docs
|
||||||
|
2. https://dev.to/moofoo/creating-a-development-dockerfile-and-docker-composeyml-for-yarn-122-monorepos-using-turborepo-896
|
|
@ -0,0 +1,40 @@
|
||||||
|
.code-highlight {
|
||||||
|
position: relative;
|
||||||
|
font-family: 'Fira Mono', monospace;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 60vh;
|
||||||
|
margin: 1rem auto;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-highlight > .code-language {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
border-bottom-left-radius: 0.5rem;
|
||||||
|
background: rgba(127, 127, 127, 0.3);
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-highlight > code {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-highlight > code.numbered {
|
||||||
|
margin-left: 2rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
border-left: 1px solid rgba(127, 127, 127, 0.5);
|
||||||
|
counter-reset: line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-highlight > code > .line-of-code {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
display: inline-block;
|
||||||
|
min-height: 1em;
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
:root {
|
||||||
|
--light: #f1f4f8;
|
||||||
|
--dark: #2c2d2f;
|
||||||
|
--primary: #ff3e00;
|
||||||
|
--fg: var(--light);
|
||||||
|
--bg: var(--dark);
|
||||||
|
--color-scrollbar: rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow: 0 0.5rem 0.5rem 0 rgba(0, 0, 0, 0.2), 0 0.25rem 1rem 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 0.5rem 0.25rem -0.25rem rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--fg: var(--light);
|
||||||
|
--bg: var(--dark);
|
||||||
|
--color-scrollbar: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.light {
|
||||||
|
--fg: var(--dark);
|
||||||
|
--bg: var(--light);
|
||||||
|
--color-scrollbar: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--fg: var(--dark);
|
||||||
|
--bg: var(--light);
|
||||||
|
--color-scrollbar: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark {
|
||||||
|
--fg: var(--light);
|
||||||
|
--bg: var(--dark);
|
||||||
|
--color-scrollbar: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--fg);
|
||||||
|
background-color: var(--bg);
|
||||||
|
font-family: 'Fira Mono', monospace;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
height: 1080px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: 'Fira Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
.prose {
|
||||||
|
font-family: 'Cooper Hewitt', sans-serif;
|
||||||
|
letter-spacing: 0.25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
width: calc(100vw - 2rem);
|
||||||
|
max-width: 1920px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: currentColor;
|
||||||
|
transition: color 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--color-scrollbar) transparent;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--color-scrollbar);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0.1rem;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// src/lib/cloudinary.ts
|
||||||
|
import { v2 as cloudinary } from 'cloudinary';
|
||||||
|
import { PUBLIC_CLOUDINARY_NAME } from '$env/static/public';
|
||||||
|
import { CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET } from '$env/static/private';
|
||||||
|
|
||||||
|
cloudinary.config({
|
||||||
|
cloud_name: PUBLIC_CLOUDINARY_NAME,
|
||||||
|
api_key: CLOUDINARY_API_KEY,
|
||||||
|
api_secret: CLOUDINARY_API_SECRET
|
||||||
|
});
|
||||||
|
|
||||||
|
export default cloudinary;
|
|
@ -3,24 +3,24 @@
|
||||||
<i class="fa-solid fa-screwdriver-wrench text-4xl text-primary-500" />
|
<i class="fa-solid fa-screwdriver-wrench text-4xl text-primary-500" />
|
||||||
<h3 class="h3">Development</h3>
|
<h3 class="h3">Development</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
I possess a wide range of skills that enable me to develop visually appealing and
|
I hone my problem solving and coding skills all the time. I try to learn the underlying
|
||||||
interactive user interfaces for web applications.
|
mechanics and use abstractions where relevant.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||||
<i class="fa-solid fa-palette text-4xl text-primary-500" />
|
<i class="fa-solid fa-palette text-4xl text-primary-500" />
|
||||||
<h3 class="h3">Design</h3>
|
<h3 class="h3">Creativity & Presentation</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
Over the years, I have honed my ability to create visually appealing interfaces that
|
I try to create better, novel ways to approach problems and give my best to present them
|
||||||
are both user-friendly and intuitive.
|
in a clear and concise manner.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||||
<i class="fa-solid fa-users text-4xl text-primary-500" />
|
<i class="fa-solid fa-users text-4xl text-primary-500" />
|
||||||
<h3 class="h3">User Experience</h3>
|
<h3 class="h3">Teamwork & Stakeholders</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
I understand the importance of creating a seamless UX for end-users. Which includes
|
From my experience with a wide range of roles I understand the how to communication with
|
||||||
a solid understanding user behavior.
|
stakeholders across an organization.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { promises as fsp } from 'fs';
|
||||||
|
|
||||||
|
export async function makeDirectory(directoryPath) {
|
||||||
|
fsp.mkdir(directoryPath, { recursive: true }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
import { getHighlighter } from 'shiki';
|
||||||
|
import { getTheme, loadTheme } from 'shiki-themes';
|
||||||
|
|
||||||
|
const escapeChars = {
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'&': '&',
|
||||||
|
'{': '{',
|
||||||
|
'}': '}'
|
||||||
|
};
|
||||||
|
|
||||||
|
const escapeRE = new RegExp(`[${Object.keys(escapeChars).join('')}]`, 'g');
|
||||||
|
|
||||||
|
function escape(str) {
|
||||||
|
if (str && str.length !== 0) {
|
||||||
|
return str.replace(escapeRE, (c) => escapeChars[c]);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(lines, options) {
|
||||||
|
const { fg, bg } = options;
|
||||||
|
const lineNumbers = options.showLineNumbers(lines.length, options.lang);
|
||||||
|
const lang = options.lang ? `<span class="code-language">${options.lang}</span>` : '';
|
||||||
|
return `<pre class="code-highlight" style="color: ${fg}; background-color: ${bg}">${lang}<code class="${
|
||||||
|
lineNumbers ? 'numbered' : 'simple'
|
||||||
|
}">${lines.map(lineRenderer(options)).join('\n')}\n</code></pre>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineRenderer = (options) => (line) => {
|
||||||
|
const output = line.map(tokenRenderer(options)).join('');
|
||||||
|
const { leadingWS, content, trailingWS } = splitLeadingAndTrailingWS(output);
|
||||||
|
return `${leadingWS || ''}<span class="line-of-code">${content || ''}</span>${
|
||||||
|
trailingWS || ''
|
||||||
|
}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tokenRenderer = (options) => (token) => {
|
||||||
|
if (!token.color || token.color.toLowerCase() === options.fg) {
|
||||||
|
return escape(token.content);
|
||||||
|
}
|
||||||
|
const { leadingWS, content, trailingWS } = splitLeadingAndTrailingWS(token.content);
|
||||||
|
if (!content) {
|
||||||
|
return leadingWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${leadingWS}<span style="color: ${token.color}">${escape(content)}</span>${trailingWS}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function splitLeadingAndTrailingWS(content) {
|
||||||
|
const len = content.length;
|
||||||
|
let start = 0;
|
||||||
|
let end = len;
|
||||||
|
|
||||||
|
while (start < end && isWS(content.charAt(start))) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === end) {
|
||||||
|
return {
|
||||||
|
leadingWS: content || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (end > start && isWS(content.charAt(end - 1))) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
leadingWS: content.slice(0, start),
|
||||||
|
content: content.slice(start, end),
|
||||||
|
trailingWS: end < content.length ? content.slice(end) : ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWS(char) {
|
||||||
|
return char === ' ' || char === '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlaintext(lang) {
|
||||||
|
return !lang || ['plaintext', 'txt', 'text'].indexOf(lang) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOpts = {
|
||||||
|
theme: 'nord',
|
||||||
|
fg: undefined,
|
||||||
|
bg: undefined,
|
||||||
|
showLineNumbers: (numberOfLines) => numberOfLines > 5
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function createHighlighter(opts) {
|
||||||
|
const options = { ...defaultOpts, ...opts };
|
||||||
|
|
||||||
|
if (options.theme.endsWith('.json')) {
|
||||||
|
options.theme = loadTheme(options.theme);
|
||||||
|
} else {
|
||||||
|
options.theme = getTheme(options.theme);
|
||||||
|
}
|
||||||
|
const baseSettings = (
|
||||||
|
(options.theme['tokenColors'] || []).find((x) => !x.scope) || { settings: {} }
|
||||||
|
).settings;
|
||||||
|
const colors = options.theme.colors || {};
|
||||||
|
const getThemeColor = (name) => baseSettings[name] || colors[`editor.${name}`] || colors[name];
|
||||||
|
const fg = (options.fg || getThemeColor('foreground') || '#eeeeee').toLowerCase();
|
||||||
|
const bg = (options.bg || getThemeColor('background') || '#222222').toLowerCase();
|
||||||
|
return getHighlighter(options).then(
|
||||||
|
(highlighter) => (code, lang) =>
|
||||||
|
render(
|
||||||
|
isPlaintext(lang)
|
||||||
|
? [[{ content: code }]]
|
||||||
|
: highlighter.codeToTokensBase(code, lang),
|
||||||
|
{ ...options, fg, bg, lang }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,48 +1,26 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SkillContainer from '$lib/components/SkillContainer.svelte';
|
import SkillContainer from '$lib/components/skills/SkillContainer.svelte';
|
||||||
import HeroSection from '$lib/components/home/HeroSection.svelte';
|
import HeroSection from '$lib/components/home/HeroSection.svelte';
|
||||||
import QuickCards from '$lib/components/home/QuickCards.svelte';
|
import QuickCards from '$lib/components/home/QuickCards.svelte';
|
||||||
|
|
||||||
|
import { website } from '$lib/config';
|
||||||
import * as conf from '$lib/config';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Matt Morin</title>
|
<title>Matt Morin</title>
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta name="description" content={conf.description} />
|
<meta name="description" content={website.description} />
|
||||||
<meta property="og:title" content={conf.title} />
|
<meta property="og:title" content={website.siteTitle} />
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
<meta property="og:description" content={conf.description} />
|
<meta property="og:description" content={website.description} />
|
||||||
<meta property="og:url" content={conf.url} />
|
<meta property="og:url" content={website.url} />
|
||||||
<meta property="og:image" content="/images/profile-pic.png" />
|
<meta property="og:image" content="/images/profile-pic.png" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="mt-24 container h-full mx-auto flex-col justify-center items-center md:w-3/4">
|
<div class="mt-24 container h-full mx-auto flex-col justify-center items-center md:w-3/4 space-y-8">
|
||||||
<!-- <div class="space-y-10 mt-4 text-center flex flex-col items-center">
|
|
||||||
<h1 class="h1">I make the wheels turn.</h1>
|
|
||||||
|
|
||||||
<figure>
|
|
||||||
<section class="img-bg" />
|
|
||||||
<img
|
|
||||||
src="/images/profile-pic.png"
|
|
||||||
class="w-8 h-8 md:h-[200px] md:w-[200px]"
|
|
||||||
alt="Profile picture"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
|
|
||||||
<img
|
|
||||||
src="/animations/infinity-loop-icon.svg"
|
|
||||||
alt="Icon"
|
|
||||||
class="w-16 md:w-32 lg:w-48 h-full rounded-full"
|
|
||||||
/>
|
|
||||||
<h2 class="h2">My github contributions</h2>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
|
||||||
|
<QuickCards />
|
||||||
|
|
||||||
<SkillContainer />
|
<SkillContainer />
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 122 B |
Binary file not shown.
After Width: | Height: | Size: 147 B |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1 @@
|
||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
|
@ -4,8 +4,9 @@ import forms from '@tailwindcss/forms';
|
||||||
import typography from '@tailwindcss/typography';
|
import typography from '@tailwindcss/typography';
|
||||||
import { skeleton } from '@skeletonlabs/tw-plugin';
|
import { skeleton } from '@skeletonlabs/tw-plugin';
|
||||||
|
|
||||||
export default {
|
const defineConfig: Config = {
|
||||||
darkMode: 'class',
|
darkMode: 'media',
|
||||||
|
|
||||||
content: [
|
content: [
|
||||||
'./src/**/*.{html,js,svelte,ts}',
|
'./src/**/*.{html,js,svelte,ts}',
|
||||||
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
||||||
|
@ -27,4 +28,6 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
} satisfies Config;
|
};
|
||||||
|
|
||||||
|
export default defineConfig;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { purgeCss } from 'vite-plugin-tailwind-purgecss';
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
import { threeMinifier } from '@yushijinhun/three-minifier-rollup';
|
import { threeMinifier } from '@yushijinhun/three-minifier-rollup';
|
||||||
|
import { imagetools } from 'vite-imagetools';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
@ -30,7 +31,8 @@ export default defineConfig({
|
||||||
greedy: [/^hljs-/]
|
greedy: [/^hljs-/]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{ ...threeMinifier(), enforce: 'pre' }
|
{ ...threeMinifier(), enforce: 'pre' },
|
||||||
|
imagetools({ removeMetadata: true })
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString())
|
'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString())
|
||||||
|
|
Loading…
Reference in New Issue