17 Commits

Author SHA1 Message Date
Arnie ea5fed1bf9 Formatting
continuous-integration/drone/tag Build is passing
2024-10-24 13:20:19 +02:00
Arnie 373f01c9c2 Update skills, page breaks and add profile picture
continuous-integration/drone/tag Build is failing
2024-10-24 13:15:06 +02:00
Arnie df24388d95 Fix translation ids 2024-10-21 14:52:24 +02:00
Arnie f71449985d Use new cmd for nix builds
continuous-integration/drone/tag Build is passing
2024-10-21 14:22:40 +02:00
Arnie 438f83e217 Update CV
continuous-integration/drone/tag Build is failing
2024-10-21 11:37:59 +02:00
Arnie 1d67745c69 Flakeify 2024-10-21 11:19:42 +02:00
Arnie b183c239b3 Extract npm deps hash calculation to a separate command 2023-06-02 14:24:29 +02:00
Arnie 4b48f20371 Update vendor sha for golang 2023-06-02 14:24:14 +02:00
Arnie 5ef48d262b Automatically calculate npm deps and update it 2023-06-02 14:17:03 +02:00
Arnie 1d68d0f91f Update nix derivation 2023-06-02 13:55:58 +02:00
Arnie 65bc08f033 Add nix shell to the list of skills 2023-06-02 13:52:08 +02:00
Arnie f8db5d68cc Update nix derivation sha 2023-06-02 13:51:38 +02:00
Arnie 75325472b8 Add a sticky navigation 2023-06-02 13:45:03 +02:00
Arnie e7f6b2be79 Link to sections on the homepage from the header 2023-06-02 13:44:52 +02:00
Arnie c414b7d1f2 Change navigation headline to allow href attributes 2023-06-02 13:44:11 +02:00
Arnie ad183bbc0e Change Caption component tag from a caption to div
Caption tag can only reside in specific context, this change

makes it more generic, so it can be used freely
2023-06-02 13:43:45 +02:00
Arnie cccc762e51 Fix invalid dashboard anchor name 2023-06-02 13:43:03 +02:00
22 changed files with 620 additions and 365 deletions
+27 -3
View File
@@ -27,8 +27,20 @@ steps:
volumes: volumes:
- name: deps - name: deps
path: /nix/store path: /nix/store
environment:
DRONE_SSH_KEY:
from_secret: drone_ssh_key
GITEA_KNOWN_HOSTS:
from_secret: gitea_known_hosts
SSH_CONFIG:
from_secret: ssh_config
commands: commands:
- nix-build --argstr version ${DRONE_TAG} --argstr rev ${DRONE_COMMIT} --argstr repoUrl ${DRONE_REPO_LINK} - mkdir /root/.ssh
- echo "$DRONE_SSH_KEY" > /root/.ssh/id_ed25519
- chmod 400 /root/.ssh/id_ed25519
- echo "$SSH_CONFIG" > /root/.ssh/config
- echo "$GITEA_KNOWN_HOSTS" > /root/.ssh/known_hosts
- nix --extra-experimental-features nix-command --extra-experimental-features flakes build .#image
- name: publish - name: publish
image: docker:dind image: docker:dind
@@ -46,7 +58,7 @@ steps:
- sleep 5 - sleep 5
- ./result | docker load - ./result | docker load
- echo $$REGISTRY_PASSWORD | docker login -u $$REGISTRY_USER --password-stdin dr.cechis.cz - echo $$REGISTRY_PASSWORD | docker login -u $$REGISTRY_USER --password-stdin dr.cechis.cz
- docker tag cv:${DRONE_TAG} dr.cechis.cz/c3c/cv:${DRONE_TAG} - docker tag cv:current dr.cechis.cz/c3c/cv:${DRONE_TAG}
- docker push dr.cechis.cz/c3c/cv:${DRONE_TAG} - docker push dr.cechis.cz/c3c/cv:${DRONE_TAG}
depends_on: depends_on:
- build - build
@@ -85,8 +97,20 @@ services:
- name: dockersock - name: dockersock
path: /var/run path: /var/run
---
kind: secret
name: drone_ssh_key
data: 12kJDhu2Tb/25dBS+1HrXwn4tSuhD0S5F+z9WzIkNVl2MMbDmOlsV2XPhF1YgINsczyU7ceIWEp+tJI1X3+uMCDyuCU3zxVV4SOe3gc/2zdZ9fewz2uFxGDk6xHm/y1y9gmDfAt+//UjjjDgtGJKEKJX6r9D9diLyiF+P6m/zZCyRnTZv3SDvLv0PduOonEj/jWVVORjuBoKkshniygqqkfHL/v3TmIEvdZ0uQMCXEKKs0fEi+qOv2gpjt+tykHdlf57zSRUeDHIarKrlslGIae9HBq40CcVHLzbFMnCXpRA4wxxGbcsNnu0263fufZ169ef2Aqntgvqjxt7c0tppnI9GxMzCShBa3dHXh2mAs7irFaEAGJKtCXJ6ETMeS6/Li0Yyf1cNvW2dLVgtWGUggkW8Dbl0hD2ZqO26mu1lbxJ1v25JN0U6meNXhKFMxJDMSz+/kmPW6miMpJh3YSW7YIVZHcUrS4RoiJuYdOPD8OUcAJrHbCin3W78glqYSM1P5z0LowSlP12fNg0eYZ+5m1gML15j8DZ92PwKPKAM2WEVivfgs873cs3PVoeM8m9p3xd0qm0
---
kind: secret
name: gitea_known_hosts
data: r9Bi+OWiiOtFustCnv/oUn2ULPCHZwQKlhZ3IroztOaHsTy/RJiTsqm9Ntachs8by+skida+21YVry1oyabrbXcnClyejEuqgTKhB8fSm4Oy3GW35gZVoxRCOdYfteFgPAvX2iOpxzREwiOzhw9hcGSFNwZG5Iij+Ylp1/dD5fDc3SAczWE8M7A1mACwe2sBFv0EiG+SXOsHjH+n9P64EV52R4F7y+kSQmQ/Frjv1ZjtcbhVyP1LsLn3xhbotOI8ug0sxybRH2Mf48xC1wahdJJ54eoTDLFWBrOsopbZEVIC3wPHRSKjouSTPyzysb6QPnJdEYzQCwCNOJ4JNDrH5txVft7yLc6egyiIpiFE3IUfZHGWKeZiraqkUc3WjRIyX4+7v89HWmLlTPG08I3hBFR5aESu4NGPOp72fGjc3YKRze/nBuKDM5RXVH2AH3J9VLGD+6pfnllz9cgmHP3CEaS+8XC0rnOIpBKlNZWQJrApiWeBnLMdopy5E9ri1dPPmkvJjxaLyJ/23r35gz9RUUj/ws1AlGCIo3sM04XCXbLJbC1mgM1YDcRr6GMVztHMiMA4z0zCDzYHlQzTuSRPUl1Ygb/lyXBwVnvaajZDoyHju0D5Ie8kajcjlRxAMDcLZFlPTBJLuLp89WN9JEyKMss2L5kH0Ku7IDn3tZ7w4Ix9YJ4tN7WnjszZsks+lUbOwy5w4bKjiwwS7S9haiN2gMQ8TwCwzXIfynFS9c5xnOEzvDYTCjz04/1YbuSsYCM4HjR/ObpQWGo+1Zs5iUC9Lrhv9EaOO2q/8O7kpbqJURePN2F8f6lGRKe/aR8VGhrRrdd+YuKchL+BQaJK7q9d7m2ov3m+njNGO8/wzgUmB5GjALfYZ1nNVPta/+aMhVPrIc2iHMwlsGCx9pMClen0gtKic6MZhRWIZWxbxjoF4n9PNcBdcUW4/VvCLJRokAVHfxIH1/epH1hBrFRrDfI3hmFw8BXXIIEk4PDi1KydbJilbF3OvSOradcdzXlwIOOW2zqiq0tkeqjPrApdcCM6hkETkKgRfV9B9Ia9Ses3iy/v1bBN9Db8WjDu+KqoHfZrSkZGF2UlMjXQYLKoAPtH6ShlnaGHeeDZLk/+El6smOgeyUN6WJjKPPyBpSEZzbFfIZh7kkrqNmrkxh8ZdVlYloAuwzbCKzxCxHxDsGK5
---
kind: secret
name: ssh_config
data: 30Q8ofMygsDpqq4BcCtdv16/EWguc+mi2fYRszfZtmhe4PrIud4lxlOW5KJvyRVo7EZ2KzlZhHmGWGJKyA==
--- ---
kind: signature kind: signature
hmac: 8db89c3a13466a543ec8edb72923c4985aae191b0b6de8c324bb24116a7684e9 hmac: 3d73eb044fd8a4fa566e7c19e0302b6db2e888f29784353de22a3937f49bdfec
... ...
+1
View File
@@ -12,6 +12,7 @@
# Frontend # Frontend
/app/frontend/node_modules /app/frontend/node_modules
/app/frontend/dist
/app/frontend/.pnp /app/frontend/.pnp
/app/frontend/.pnp.js /app/frontend/.pnp.js
/app/frontend/.dependency-graph /app/frontend/.dependency-graph
-24
View File
@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

+137 -52
View File
@@ -3,7 +3,7 @@ import { defineMessages, useIntl } from "react-intl";
import { Content } from "../common/Content"; import { Content } from "../common/Content";
import { Headline, SubHeadline } from "../common/Headline"; import { Headline, SubHeadline } from "../common/Headline";
import { Spacer } from "../common/Spacer"; import PageBreakAvoid from "../common/PageBreakAvoid";
import { Paragraph } from "../common/Text"; import { Paragraph } from "../common/Text";
const messages = defineMessages({ const messages = defineMessages({
@@ -11,67 +11,83 @@ const messages = defineMessages({
defaultMessage: "Skills", defaultMessage: "Skills",
id: "Experience.skills", id: "Experience.skills",
}, },
yourpass: {
defaultMessage: "July 2021 - Present ({count} years)",
id: "Experience.yourpass",
},
yoursystem: {
defaultMessage: "February 2016 - December 2021 (5 years)",
id: "Experience.yoursystem",
},
past: {
defaultMessage: "Birth - February 2016",
id: "Experience.past",
},
pastDescription: {
defaultMessage:
"Working on smaller projects such as chats, forum based websites, web based presentations, most notably a system for handling the driver training center and driving school agenda... Tinkering...",
id: "Experience.pastDescription",
},
}); });
const approxCurrentYears = Math.round( const approxCurrentYears = {
InvestBay: Math.round(
(Date.now() - new Date("2024-02-01").getTime()) / 1000 / 60 / 60 / 24 / 365
),
YourPass: Math.round(
(Date.now() - new Date("2021-06-01").getTime()) / 1000 / 60 / 60 / 24 / 365 (Date.now() - new Date("2021-06-01").getTime()) / 1000 / 60 / 60 / 24 / 365
); ),
YourSystemSysOps: Math.round(
(Date.now() - new Date("2022-01-01").getTime()) / 1000 / 60 / 60 / 24 / 365
),
};
const yourpassSkills = [ const investbaySkills = [
"DevOps", "DevOps",
"Front-End Development", "Google Cloud Platform (GCP)",
"Back-end Operations", "Amazon Web Services (AWS)",
"Software Development", "Gitlab",
"Kubernetes", "Terraform",
"TypeScript", "Cloud infrastructure",
"Go", "Cloud Applications",
"Linux", "Software Development Life Cycle (SDLC)",
"Linux Server",
"Linux System Administration", "Linux System Administration",
"Back-End Web Development", "Go",
"Git", "TypeScript",
"Nix",
"GraphQL",
"gRPC",
"PostgreSQL", "PostgreSQL",
"Node.js", "Node.js",
"React.js", "React.js",
];
const yourpassSkills = [
"DevOps",
"Software as a Service (SaaS)",
"Cloud Infrastructure",
"Kubernetes",
"Back-end Operations",
"Linux System Administration",
"Amazon Web Services (AWS)", "Amazon Web Services (AWS)",
"Amazon EKS", "Terraform",
"Go (Programming Language)",
"gRPC",
"Front-End Development",
"React.js",
"Node.js",
"TypeScript",
"Nix",
"Software Development Life Cycle (SDLC)",
"Software Development",
"Git",
"PostgreSQL",
]; ];
const yoursystemSkills = [ const yoursystemSkills = [
"DevOps", "DevOps",
"Front-End Development", "Docker Swarm",
"Linux System Administration",
"Back-end Operations", "Back-end Operations",
"Front-End Development",
"Amazon Web Services (AWS)",
"Bash",
"Linux Server",
"Terraform",
"Cloud Infrastructure",
"Cloud Applications",
"Go (Programming Language)",
"TypeScript",
"Node.js",
"Software Development Life Cycle (SDLC)",
"Software Development", "Software Development",
"Kubernetes", "Kubernetes",
"TypeScript",
"Linux",
"Linux Server",
"Linux System Administration",
"Back-End Web Development", "Back-End Web Development",
"GraphQL",
"Git", "Git",
"Node.js",
"Vue.js",
"PHP",
"Bash",
"Amazon Web Services (AWS)",
"Amazon EKS",
]; ];
const Experience: React.FC = () => { const Experience: React.FC = () => {
@@ -79,40 +95,109 @@ const Experience: React.FC = () => {
return ( return (
<> <>
<PageBreakAvoid>
<Headline level={3}>INVESTBAY s.r.o.</Headline>
<SubHeadline level={4}>DevOps Architect</SubHeadline>
<Content>
<Paragraph>
{intl.formatMessage(
{
defaultMessage: "February 2024 - Present",
id: "Experience.investbay",
},
{
// not used atm
count: approxCurrentYears.InvestBay,
}
)}
</Paragraph>
<Paragraph>
<strong>{intl.formatMessage(messages.skills)}:</strong>{" "}
{investbaySkills.join(" · ")}
</Paragraph>
</Content>
</PageBreakAvoid>
<PageBreakAvoid>
<Headline level={3}>YOUR PASS s.r.o.</Headline> <Headline level={3}>YOUR PASS s.r.o.</Headline>
<SubHeadline level={4}>DevOps Engineer</SubHeadline> <SubHeadline level={4}>DevOps Engineer</SubHeadline>
<Content> <Content>
<Paragraph> <Paragraph>
{intl.formatMessage(messages.yourpass, { {intl.formatMessage(
count: approxCurrentYears, {
})} defaultMessage: "July 2021 - Present ({count} years)",
id: "Experience.yourpass",
},
{
count: approxCurrentYears.YourPass,
}
)}
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
<strong>{intl.formatMessage(messages.skills)}:</strong>{" "} <strong>{intl.formatMessage(messages.skills)}:</strong>{" "}
{yourpassSkills.join(" · ")} {yourpassSkills.join(" · ")}
</Paragraph> </Paragraph>
</Content> </Content>
</PageBreakAvoid>
<Spacer /> <PageBreakAvoid>
<Headline level={3}>YOUR SYSTEM s.r.o.</Headline> <Headline level={3}>YOUR SYSTEM s.r.o.</Headline>
<SubHeadline level={4}>Lead Developer/Architect</SubHeadline> <SubHeadline level={4}>SysOps</SubHeadline>
<Content> <Content>
<Paragraph>{intl.formatMessage(messages.yoursystem)}</Paragraph> <Paragraph>
{intl.formatMessage(
{
defaultMessage: "January 2022 - Present ({count} years)",
id: "Experience.yoursystem",
},
{
count: approxCurrentYears.YourSystemSysOps,
}
)}
</Paragraph>
<Paragraph> <Paragraph>
<strong>{intl.formatMessage(messages.skills)}:</strong>{" "} <strong>{intl.formatMessage(messages.skills)}:</strong>{" "}
{yoursystemSkills.join(" · ")} {yoursystemSkills.join(" · ")}
</Paragraph> </Paragraph>
</Content> </Content>
<SubHeadline level={4}>Lead Developer/Architect</SubHeadline>
<Content>
<Paragraph>
{intl.formatMessage({
defaultMessage: "February 2016 - December 2021 (5 years)",
id: "Experience.yoursystem",
})}
</Paragraph>
<Paragraph>
<strong>{intl.formatMessage(messages.skills)}:</strong>{" "}
{yoursystemSkills.join(" · ")}
</Paragraph>
</Content>
</PageBreakAvoid>
<PageBreakAvoid>
<Headline level={3}>Past experience</Headline> <Headline level={3}>Past experience</Headline>
<SubHeadline level={4}>Developer</SubHeadline> <SubHeadline level={4}>Developer</SubHeadline>
<Content> <Content>
<Paragraph>{intl.formatMessage(messages.past)}</Paragraph> <Paragraph>
<Paragraph>{intl.formatMessage(messages.pastDescription)}</Paragraph> {intl.formatMessage({
defaultMessage: "Birth - February 2016",
id: "Experience.past",
})}
</Paragraph>
<Paragraph>
{intl.formatMessage({
defaultMessage:
"Working on smaller projects such as chats, forum based websites, web based presentations, most notably a system for handling the driver training center and driving school agenda... Tinkering...",
id: "Experience.pastDescription",
})}
</Paragraph>
</Content> </Content>
</PageBreakAvoid>
</> </>
); );
}; };
+3 -2
View File
@@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import PageBreakAvoid from "../common/PageBreakAvoid";
import { Spacer } from "../common/Spacer"; import { Spacer } from "../common/Spacer";
import { Paragraph } from "../common/Text"; import { Paragraph } from "../common/Text";
@@ -11,13 +12,13 @@ const Skill: React.FC<React.PropsWithChildren<SkillProps>> = (props) => {
const { children, title } = props; const { children, title } = props;
return ( return (
<div> <PageBreakAvoid>
<strong>{title}</strong> <strong>{title}</strong>
<Spacer /> <Spacer />
{React.Children.map(children, (c) => ( {React.Children.map(children, (c) => (
<Paragraph>{c}</Paragraph> <Paragraph>{c}</Paragraph>
))} ))}
</div> </PageBreakAvoid>
); );
}; };
+45 -50
View File
@@ -1,49 +1,13 @@
import React from "react"; import React from "react";
import { defineMessages, useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { SubHeadline } from "../common/Headline"; import { SubHeadline } from "../common/Headline";
import { List, ListItem } from "../common/List"; import { List, ListItem } from "../common/List";
import PageBreakAvoid from "../common/PageBreakAvoid";
import { Spacer } from "../common/Spacer"; import { Spacer } from "../common/Spacer";
import { Paragraph } from "../common/Text"; import { Paragraph } from "../common/Text";
import Skill from "./Skill"; import Skill from "./Skill";
const messages = defineMessages({
backendDevelopment: {
defaultMessage:
"Deep knowledge of backend operations, mostly supporting web based applications using various technologies, programming languages and frameworks.",
id: "Skills.backendDevelopment",
},
backendDevelopmentAdditional: {
defaultMessage:
"In recent years, the main focus was on Go to write tooling and backend services.",
id: "Skills.backendDevelopment",
},
devops: {
defaultMessage:
"Several years of experience developing, maintaining and adhering to the DevOps practices to streamline organization's processes across both backend and frontend development.",
id: "Skills.devops",
},
frontendDevelopment: {
defaultMessage:
"Extensive experience in frontend development spanning back to the early 2000s.",
id: "Skills.frontendDevelopment",
},
infrastructureOperations: {
defaultMessage:
"Throughout my career, I have gained valuable experience in developing and maintaining high-load infrastructures over several years.",
id: "Skills.infrastructureOperations",
},
infrastructureOperationsAdditional: {
defaultMessage:
"Following the OCI (Open Container Initiative) and leveraging Docker and Kubernetes to create highly available deployments, primarily on the AWS platform, was a challenging and enjoyable experience, that has greatly enhanced my skills in this area.",
id: "Skills.infrastructureOperationsAdditional",
},
otherNotableSkills: {
defaultMessage: "Other notable skills",
id: "Skills.otherNotableSkills",
},
});
type OtherSkill = { type OtherSkill = {
title: string; title: string;
description?: string; description?: string;
@@ -51,13 +15,13 @@ type OtherSkill = {
const otherSkills: OtherSkill[] = [ const otherSkills: OtherSkill[] = [
{ {
title: "Primary languages", title: "Strong proficiency in",
description: "Go, Typescript (and Javascript)", description: "Go, Typescript (and Javascript), Bash, IaaC tools",
}, },
{ {
title: "Infrastructure based skills", title: "Infrastructure related skills",
description: description:
"Kubernetes and AWS EKS, Containerized applications, Linux system administration, Amazon Web Services - good understanding of Networking, RDS, Queues", "Kubernetes and AWS EKS, Containerized applications, Linux system administration, Amazon Web Services - good understanding of Networking, RDS, Queues. Notable experience with GCP/Azure.",
}, },
{ {
title: "Databases", title: "Databases",
@@ -66,7 +30,7 @@ const otherSkills: OtherSkill[] = [
{ {
title: "Some of the relevant Frameworks/Tooling", title: "Some of the relevant Frameworks/Tooling",
description: description:
"Terraform, Prometheus, Grafana, React, Webpack, Rollup, Esbuild, deep understanding of Git", "Terraform, Prometheus, Grafana, Nix shell, React, Webpack, Rollup, Esbuild, deep understanding of Git",
}, },
{ {
title: "Software Development", title: "Software Development",
@@ -88,27 +52,58 @@ const Skills: React.FC = () => {
return ( return (
<> <>
<Skill title="DevOps">{intl.formatMessage(messages.devops)}</Skill> <Skill title="DevOps">
{intl.formatMessage({
defaultMessage:
"Several years of experience developing, maintaining and adhering to the DevOps practices to streamline organization's processes across both backend and frontend development.",
id: "Skills.devops",
})}
</Skill>
<Skill title="Front-End Development"> <Skill title="Front-End Development">
{intl.formatMessage(messages.frontendDevelopment)} {intl.formatMessage({
defaultMessage:
"Extensive experience in frontend development spanning back to the early 2000s.",
id: "Skills.frontendDevelopment",
})}
</Skill> </Skill>
<Skill title="Back-End Development"> <Skill title="Back-End Development">
{intl.formatMessage(messages.backendDevelopment)} {intl.formatMessage({
{intl.formatMessage(messages.backendDevelopmentAdditional)} defaultMessage:
"Deep knowledge of backend operations, mostly supporting web based applications using various technologies, programming languages and frameworks.",
id: "Skills.backendDevelopmentSum1",
})}
{intl.formatMessage({
defaultMessage:
"In recent years, the main focus was on Go to write tooling and backend services.",
id: "Skills.backendDevelopmentSum2",
})}
</Skill> </Skill>
<Skill title="Infrastructure operations"> <Skill title="Infrastructure operations">
{intl.formatMessage(messages.infrastructureOperations)} {intl.formatMessage({
{intl.formatMessage(messages.infrastructureOperationsAdditional)} defaultMessage:
"Throughout my career, I have gained valuable experience in developing and maintaining high-load infrastructures over several years.",
id: "Skills.infrastructureOperations",
})}
{intl.formatMessage({
defaultMessage:
"Following the OCI (Open Container Initiative) and leveraging Docker and Kubernetes to create highly available deployments, primarily on the AWS platform, was a challenging and enjoyable experience, that has greatly enhanced my skills in this area.",
id: "Skills.infrastructureOperationsAdditional",
})}
</Skill> </Skill>
<Spacer withDivider /> <Spacer withDivider />
<SubHeadline level={4}> <SubHeadline level={4}>
{intl.formatMessage(messages.otherNotableSkills)} {intl.formatMessage({
defaultMessage: "Other notable skills",
id: "Skills.otherNotableSkills",
})}
</SubHeadline> </SubHeadline>
<List> <List>
{otherSkills.map((s) => ( {otherSkills.map((s) => (
<ListItem key={s.title}> <ListItem key={s.title}>
<PageBreakAvoid>
<strong>{s.title}</strong> <strong>{s.title}</strong>
{s.description && `: ${s.description}`} {s.description && `: ${s.description}`}
</PageBreakAvoid>
</ListItem> </ListItem>
))} ))}
</List> </List>
+1 -2
View File
@@ -38,8 +38,7 @@ const Summary: React.FC = () => {
knowledge of KNX systems. knowledge of KNX systems.
</Paragraph> </Paragraph>
<Caption> <Caption>
-- This summary was graciously generated by an AI and then rewritten and -- Summary graciously generated by an AI and curated by human
adjusted by a human
</Caption> </Caption>
</> </>
); );
@@ -23,7 +23,7 @@ const Nav = styled("nav")({
flex: "0 0 280px", flex: "0 0 280px",
width: 280, width: 280,
minHeight: "100%", minHeight: "100%",
padding: "3rem 2rem 2rem", padding: "2rem 2rem 2rem",
[hideNavigationMQ]: { [hideNavigationMQ]: {
display: "none", display: "none",
@@ -1,70 +1,165 @@
import React from "react"; import styled from "@emotion/styled";
import { defineMessages, useIntl } from "react-intl"; import React, { useEffect, useRef } from "react";
import { useIntl } from "react-intl";
import profile from "../../assets/profile.jpg";
import { CONTACT_EMAIL, CONTACT_PHONE } from "../../config/environment"; import { CONTACT_EMAIL, CONTACT_PHONE } from "../../config/environment";
import { Contact } from "../common/Contact"; import { Contact } from "../common/Contact";
import { List, ListItem } from "../common/List"; import { List, ListItem } from "../common/List";
import { Spacer } from "../common/Spacer"; import { Spacer } from "../common/Spacer";
import NavigationHeadline from "./NavigationHeadline"; import NavigationHeadline from "./NavigationHeadline";
const messages = defineMessages({ const ProfileImg = styled("img")({
certifications: { borderRadius: "50%",
defaultMessage: "Certifications", border: "2px solid white",
id: "Navigation.certifications", display: "block",
}, margin: "0 auto",
contact: { maxWidth: "100%",
defaultMessage: "Contact", width: 150,
id: "Navigation.contact",
},
experience: {
defaultMessage: "Experience",
id: "Navigation.experience",
},
objective: {
defaultMessage: "Objective",
id: "Navigation.objective",
},
topSkills: {
defaultMessage: "Top Skills",
id: "Navigation.topSkills",
},
}); });
const getTopOffset = (input: HTMLElement) => {
let el: HTMLElement | Element | null = input;
let o = 0;
do {
if (!(el instanceof HTMLElement)) {
break;
}
if (!isNaN(el.offsetTop)) {
o += el.offsetTop;
}
} while ((el = el.offsetParent));
return o;
};
const MainNavigation: React.FC = () => { const MainNavigation: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const d = ref.current;
if (!d) {
return;
}
let { height } = d.getBoundingClientRect();
const ro = new ResizeObserver((entries) => {
if (entries[0]) {
height = entries[0].contentRect.height;
}
});
ro.observe(d);
let scrollPrev = window.scrollY;
const listener = () => {
const down = scrollPrev < window.scrollY;
scrollPrev = window.scrollY;
const top = getTopOffset(d);
console.log({
"window.innerHeight": window.innerHeight,
height,
top,
down,
});
if (window.innerHeight > height) {
d.style.marginTop = `${Math.max(window.scrollY, 0)}px`;
return;
}
if (down) {
const delta = top + height;
const wDelta = window.scrollY + window.innerHeight;
const diff = wDelta - delta;
console.log({ delta, wDelta, "window.scrollY": window.scrollY, diff });
if (diff > 0) {
const sizeDelta = Math.min(window.innerHeight - height, 0);
d.style.marginTop = `${Math.max(top + diff + sizeDelta, 0)}px`;
console.log({
sizeDelta,
marginTop: Math.max(top + diff + sizeDelta, 0),
});
}
} else {
const delta = top;
const wDelta = window.scrollY;
const diff = wDelta - delta;
console.log({ delta, wDelta, "window.scrollY": window.scrollY, diff });
if (diff < 0) {
console.log({ marginTop: Math.max(top + diff, 0) });
d.style.marginTop = `${Math.max(top + diff, 0)}px`;
}
}
};
window.addEventListener("scroll", listener);
return () => {
window.removeEventListener("scroll", listener);
ro.disconnect();
};
}, []);
return ( return (
<> <div ref={ref}>
<ProfileImg src={profile} alt="profile picture" />
<NavigationHeadline> <NavigationHeadline>
{intl.formatMessage(messages.contact)} {intl.formatMessage({
defaultMessage: "Contact",
id: "Navigation.contact",
})}
</NavigationHeadline> </NavigationHeadline>
<Spacer /> <Spacer />
<Contact email={CONTACT_EMAIL} phoneNumber={CONTACT_PHONE} /> <Contact email={CONTACT_EMAIL} phoneNumber={CONTACT_PHONE} />
<NavigationHeadline to="#objective"> <NavigationHeadline href="#objective">
{intl.formatMessage(messages.objective)} {intl.formatMessage({
defaultMessage: "Objective",
id: "Navigation.objective",
})}
</NavigationHeadline> </NavigationHeadline>
<NavigationHeadline to="#skills"> <NavigationHeadline href="#skills">
{intl.formatMessage(messages.topSkills)} {intl.formatMessage({
defaultMessage: "Top Skills",
id: "Navigation.topSkills",
})}
</NavigationHeadline> </NavigationHeadline>
<Spacer /> <Spacer />
<List> <List>
<ListItem>DevOps</ListItem> <ListItem>DevOps</ListItem>
<ListItem>Front-End Development</ListItem>
<ListItem>Back-End Development</ListItem>
<ListItem>Kubernetes</ListItem> <ListItem>Kubernetes</ListItem>
<ListItem>Typescript</ListItem> <ListItem>Linux</ListItem>
<ListItem>Go</ListItem> <ListItem>Go</ListItem>
<ListItem>Typescript</ListItem>
<ListItem>Back-End Development</ListItem>
<ListItem>Front-End Development</ListItem>
<ListItem>Scripting:</ListItem>
<ListItem>Bash, Python, Lua</ListItem>
</List> </List>
<Spacer spacing={0} withDivider /> <Spacer spacing={0} withDivider />
<NavigationHeadline to="#certifications"> <NavigationHeadline>
{intl.formatMessage(messages.certifications)} {intl.formatMessage({
defaultMessage: "Certifications",
id: "Navigation.certifications",
})}
</NavigationHeadline> </NavigationHeadline>
<Spacer /> <Spacer />
@@ -76,10 +171,13 @@ const MainNavigation: React.FC = () => {
<Spacer spacing={0} withDivider /> <Spacer spacing={0} withDivider />
<NavigationHeadline to="#experience"> <NavigationHeadline href="#experience">
{intl.formatMessage(messages.experience)} {intl.formatMessage({
defaultMessage: "Experience",
id: "Navigation.experience",
})}
</NavigationHeadline> </NavigationHeadline>
</> </div>
); );
}; };
@@ -15,17 +15,25 @@ const Link = styled(RouterLink)({
textDecoration: "none", textDecoration: "none",
}); });
type NavigationHeadlineProps = Partial<Pick<RouterLinkProps, "to">>; type NavigationHeadlineProps =
| (Partial<Pick<RouterLinkProps, "to">> & {
href?: undefined;
})
| {
href?: string;
to?: undefined;
};
const NavigationHeadline: React.FC< const NavigationHeadline: React.FC<
React.PropsWithChildren<NavigationHeadlineProps> React.PropsWithChildren<NavigationHeadlineProps>
> = (props) => { > = (props) => {
const { children, to } = props; const { children, href, to } = props;
return ( return (
<Headline> <Headline>
{to && <Link to={to}>{children}</Link>} {to && <Link to={to}>{children}</Link>}
{!to && children} {href && <a href={href}>{children}</a>}
{!to && !href && children}
</Headline> </Headline>
); );
}; };
@@ -0,0 +1,7 @@
import styled from "@emotion/styled";
const PageBreakAvoid = styled("div")({
pageBreakInside: "avoid",
});
export default PageBreakAvoid;
@@ -1,6 +1,6 @@
import styled from "@emotion/styled"; import styled from "@emotion/styled";
const Caption = styled("caption")(({ theme }) => ({ const Caption = styled("div")(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
fontSize: "0.75rem", fontSize: "0.75rem",
lineHeight: 1.5, lineHeight: 1.5,
+16 -20
View File
@@ -1,6 +1,6 @@
import styled from "@emotion/styled"; import styled from "@emotion/styled";
import React from "react"; import React from "react";
import { defineMessages, useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { Experience, Skills, Summary } from "../components/CV"; import { Experience, Skills, Summary } from "../components/CV";
import MainLayout from "../components/Layout/MainLayout"; import MainLayout from "../components/Layout/MainLayout";
@@ -11,21 +11,6 @@ import { Headline, SubHeadline } from "../components/common/Headline";
import { Spacer } from "../components/common/Spacer"; import { Spacer } from "../components/common/Spacer";
import { CONTACT_EMAIL, CONTACT_PHONE, POSITION } from "../config/environment"; import { CONTACT_EMAIL, CONTACT_PHONE, POSITION } from "../config/environment";
const messages = defineMessages({
experienceTitle: {
defaultMessage: "Experience",
id: "Dashboard.experienceTitle",
},
location: {
defaultMessage: "Central Bohemia, Czechia",
id: "Dashboard.location",
},
skillsTitle: {
defaultMessage: "Skills",
id: "Dashboard.skillsTitle",
},
});
const HiddenWithNav = styled("div")({ const HiddenWithNav = styled("div")({
display: "none", display: "none",
[hideNavigationMQ]: { [hideNavigationMQ]: {
@@ -41,7 +26,10 @@ const Dashboard: React.FC = () => {
<Headline level={1}>Lukáš Čech</Headline> <Headline level={1}>Lukáš Čech</Headline>
<SubHeadline level={2}>{POSITION}</SubHeadline> <SubHeadline level={2}>{POSITION}</SubHeadline>
<SubHeadline level={4}> <SubHeadline level={4}>
{intl.formatMessage(messages.location)} {intl.formatMessage({
defaultMessage: "Central Bohemia, Czechia",
id: "Dashboard.location",
})}
</SubHeadline> </SubHeadline>
<HiddenWithNav> <HiddenWithNav>
@@ -55,10 +43,15 @@ const Dashboard: React.FC = () => {
<Summary /> <Summary />
</Content> </Content>
<div id="experience" /> <div id="skills" />
<Spacer withDivider /> <Spacer withDivider />
<Headline level={2}>{intl.formatMessage(messages.skillsTitle)}</Headline> <Headline level={2}>
{intl.formatMessage({
defaultMessage: "Skills",
id: "Dashboard.skillsTitle",
})}
</Headline>
<Spacer /> <Spacer />
<Content> <Content>
<Skills /> <Skills />
@@ -68,7 +61,10 @@ const Dashboard: React.FC = () => {
<Spacer withDivider /> <Spacer withDivider />
<Headline level={2}> <Headline level={2}>
{intl.formatMessage(messages.experienceTitle)} {intl.formatMessage({
defaultMessage: "Experience",
id: "Dashboard.experienceTitle",
})}
</Headline> </Headline>
<Spacer /> <Spacer />
<Content> <Content>
+7 -9
View File
@@ -1,16 +1,9 @@
import styled from "@emotion/styled"; import styled from "@emotion/styled";
import React from "react"; import React from "react";
import { defineMessages, useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { Link } from "../components/common/Link"; import { Link } from "../components/common/Link";
const messages = defineMessages({
notFound: {
defaultMessage: "Nothing to see here",
id: "Router.NotFound",
},
});
const Root = styled("div")({ const Root = styled("div")({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
@@ -25,7 +18,12 @@ const NotFound = () => {
return ( return (
<Root> <Root>
<p>{intl.formatMessage(messages.notFound)}</p> <p>
{intl.formatMessage({
defaultMessage: "Nothing to see here",
id: "Router.NotFound",
})}
</p>
<Link to="/">Go home</Link> <Link to="/">Go home</Link>
</Root> </Root>
); );
+1 -2
View File
@@ -7,10 +7,9 @@ import (
"os" "os"
"time" "time"
"go.c3c.cz/cv/app/server/internal/version"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"go.c3c.cz/cv/app/server/internal/version"
) )
type FrontendConfigDef struct { type FrontendConfigDef struct {
@@ -14,10 +14,9 @@ import (
"strings" "strings"
"time" "time"
"go.uber.org/zap"
"go.c3c.cz/cv/app/server/internal/files" "go.c3c.cz/cv/app/server/internal/files"
"go.c3c.cz/cv/app/server/internal/version" "go.c3c.cz/cv/app/server/internal/version"
"go.uber.org/zap"
) )
type handler struct { type handler struct {
@@ -67,7 +66,6 @@ func (h *handler) serveFile(w http.ResponseWriter, r *http.Request, modtime time
gw.Reset(io.Discard) gw.Reset(io.Discard)
} }
err = gw.Close() err = gw.Close()
if err != nil { if err != nil {
h.logger.Warn("Could not close gzip writer", zap.Error(err)) h.logger.Warn("Could not close gzip writer", zap.Error(err))
return return
+1 -2
View File
@@ -10,9 +10,8 @@ import (
"net/http" "net/http"
"time" "time"
"go.uber.org/zap"
"go.c3c.cz/cv/app/server/internal/files" "go.c3c.cz/cv/app/server/internal/files"
"go.uber.org/zap"
) )
func init() { func init() {
+1 -3
View File
@@ -8,12 +8,11 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"go.uber.org/zap"
"go.c3c.cz/cv/app/server/internal/config" "go.c3c.cz/cv/app/server/internal/config"
"go.c3c.cz/cv/app/server/internal/httpserver" "go.c3c.cz/cv/app/server/internal/httpserver"
"go.c3c.cz/cv/app/server/internal/pprofserver" "go.c3c.cz/cv/app/server/internal/pprofserver"
"go.c3c.cz/cv/app/server/internal/version" "go.c3c.cz/cv/app/server/internal/version"
"go.uber.org/zap"
) )
func main() { func main() {
@@ -33,7 +32,6 @@ func main() {
var err error var err error
logger, err = config.LoggerConfig.Build() logger, err = config.LoggerConfig.Build()
if err != nil { if err != nil {
fmt.Println(notice) fmt.Println(notice)
fmt.Println(version.CommitTime.Format("Committed at 2006/01/02 15:04:05 MST")) fmt.Println(version.CommitTime.Format("Committed at 2006/01/02 15:04:05 MST"))
-128
View File
@@ -1,128 +0,0 @@
{ system ? builtins.currentSystem
, version ? "latest"
, rev ? ""
, commitTime ? "1970-01-01T00:00:00+00:00"
, repoUrl ? ""
}:
let
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/4ecab3273592f27479a583fb6d975d4aba3486fe.tar.gz";
sha256 = "10wn0l08j9lgqcw8177nh2ljrnxdrpri7bp0g7nvrsn9rkawvlbf";
};
pkgs = import nixpkgs { inherit system; };
devshell = import
(pkgs.fetchFromGitHub {
owner = "numtide";
repo = "devshell";
rev = "5143ea68647c4cf5227e4ad2100db6671fc4c369";
sha256 = "sha256-9goJTd05yOyD/McaMqZ4BUB8JW+mZMnZQJZ7VQ6C/Lw=";
})
{ nixpkgs = pkgs; };
nodejs = pkgs.nodejs_20;
go = pkgs.go_1_20;
dream2nix = pkgs.dream2nix;
buildGoModule = pkgs.buildGoModule.override { inherit go; };
golangci-lint = pkgs.golangci-lint.override { inherit buildGoModule; };
src = pkgs.lib.cleanSource ./.;
uiSrc = pkgs.lib.cleanSource ./app/frontend;
package = pkgs.buildNpmPackage {
name = "ui";
buildInputs = [
nodejs
];
pname = "ui";
src = uiSrc;
buildPhase = ''
npm run check
npm run build
mkdir $out
cp -a dist $out
'';
npmInstallFlags = "--no-audit --no-progress --no-fund";
npmDepsHash = "sha256-dnUbw0kfAA50ZaQ91q/fC+gZhAGNo6vrSQBz6Qf2ngo=";
npmPackFlags = [ "--ignore-scripts" ];
};
server = buildGoModule {
inherit src version;
pname = "cv";
postPatch = ''
rm -rf app/server/internal/files/data/public
mkdir -p app/server/internal/files/data/public
touch app/server/internal/files/data/public/.gitkeep
cp -Tr ${package}/dist/ app/server/internal/files/data/public/
chmod +w -R app/server/internal/files/data/public
sed -i 's#<script#<script nonce="{{ .CspNonce }}"#g' app/server/internal/files/data/public/index.html
'';
CGO_ENABLED = 0;
ldflags = "-s -w -X gopkg.c3c.cz/cv/app/server/internal/version.Tag=${version} -X gopkg.c3c.cz/cv/app/server/internal/version.Commit=${rev} -X gopkg.c3c.cz/cv/app/server/internal/version.commitTime=${commitTime} -X gopkg.c3c.cz/cv/app/server/internal/version.repoUrl=${repoUrl}";
subPackages = [ "app/server" ];
vendorSha256 = "sha256-BkwXD8n62+p639vTLwXpCiY8cqYtCjWxaa5tuw78u5g=";
overrideModAttrs = (_: {
impureEnvVars = pkgs.lib.fetchers.proxyImpureEnvVars ++ [ "GIT_PROXY_COMMAND" "SOCKS_SERVER" "GOPROXY" ];
});
};
in
{
image = pkgs.dockerTools.streamLayeredImage {
name = "cv";
tag = version;
config = {
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
Entrypoint = [ "${server}/bin/server" ];
};
};
shell = devshell.mkShell {
devshell = {
name = "c3c-cv";
packages = [
go
golangci-lint
nodejs
# keep pinned nixpkgs so they are not garbage collected
pkgs.path
];
};
commands = [
{
name = "lint";
help = "run golangci-lint and npm run check";
command = ''
cd ''$PRJ_ROOT
golangci-lint run --sort-results --out-format tab
npm --prefix app/frontend run check
'';
}
{
name = "fmt";
help = "Format and fix found issues";
command = ''
cd ''$PRJ_ROOT
golangci-lint run --sort-results --out-format tab --fix
npm --prefix app/frontend run fix
'';
}
{
name = "dev";
help = "Starts the javascript project in dev";
command = ''
npm --prefix ''$PRJ_ROOT/app/frontend run dev
'';
}
];
};
}
Generated
+67
View File
@@ -0,0 +1,67 @@
{
"nodes": {
"devshell": {
"inputs": {
"nixpkgs": [
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1728330715,
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
"owner": "numtide",
"repo": "devshell",
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"nix": {
"inputs": {
"devshell": "devshell",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1729027123,
"narHash": "sha256-LjD40ya8efdhxQ26Zy/LkLDOriZIHJZqutAorvPIka8=",
"ref": "refs/heads/main",
"rev": "d3596fc7e933c171a284771f7f72342d33ad6508",
"revCount": 5,
"type": "git",
"url": "ssh://git@git.c3c.cz/C3C/nix"
},
"original": {
"type": "git",
"url": "ssh://git@git.c3c.cz/C3C/nix"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1728538411,
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nix": "nix"
}
}
},
"root": "root",
"version": 7
}
+134
View File
@@ -0,0 +1,134 @@
{
inputs = {
nix.url = "git+ssh://git@git.c3c.cz/C3C/nix";
};
outputs =
{ self, nix }:
{
formatter = nix.formatter;
packages = nix.lib.forAllSystems (
pkgs:
let
version = "rev-${self.shortRev or self.dirtyShortRev}";
package = builtins.fromJSON (builtins.readFile ./app/frontend/package.json);
in
rec {
ui = pkgs.buildNpmPackage {
inherit version;
pname = "cv";
src = ./app/frontend;
npmInstallFlags = "--no-audit --no-progress --no-fund";
npmDepsHash = "sha256-p4rTpy0t8aajbubvtF1TA83/FFqvfKEOPBb5T0ZRfQY=";
npmPackFlags = [ "--ignore-scripts" ];
};
server = pkgs.buildGoModule {
inherit version;
pname = "cv";
CGO_ENABLED = 0;
src = ./.;
subPackages = [ "app/server" ];
postPatch = ''
rm -rf app/server/internal/files/data/public
mkdir -p app/server/internal/files/data/public
touch app/server/internal/files/data/public/.gitkeep
cp -Tr ${ui}/lib/node_modules/${package.name}/dist app/server/internal/files/data/public/
chmod +w -R app/server/internal/files/data/public
sed -i 's#<script#<script nonce="{{ .CspNonce }}"#g' app/server/internal/files/data/public/index.html
'';
ldflags = [
"-s"
"-w"
"-X gopkg.c3c.cz/cv/app/server/internal/version.Tag=${version}"
"-X gopkg.c3c.cz/cv/app/server/internal/version.Commit=${self.rev or self.dirtyRev}"
"-X gopkg.c3c.cz/cv/app/server/internal/version.commitTime=${
builtins.toString (self.lastModified or 0)
}"
];
vendorHash = "sha256-44xcyVk5KcurQLkVJv1MeAj+Pfcu53664pvVgHdyv3E=";
};
image = pkgs.dockerTools.streamLayeredImage {
name = "cv";
tag = "current";
config = {
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
Entrypoint = [ "${server}/bin/server" ];
};
};
}
);
devShells = nix.lib.forAllSystems (pkgs: {
default = pkgs.devshell.mkShell {
name = "c3c-cv";
packages = [
pkgs.go
pkgs.nodejs_22
];
commands = [
{
# Override golangci-lint for vscode, because the extension incorrectly assumes usage of global binaries is preferred
name = "golangci-lint";
command = ''
CMD=''${1:-}
if [[ "$CMD" == "run" ]]; then
shift
${pkgs.golangci-lint}/bin/golangci-lint run --config ${nix.lib.golangci-config-file} $@
else
${pkgs.golangci-lint}/bin/golangci-lint $@
fi
'';
}
{
name = "lint";
help = "run linters";
command = ''
${nix.lib.cd_root}
${pkgs.golangci-lint}/bin/golangci-lint run --sort-results --out-format tab --config ${nix.lib.golangci-config-file} ./...
npm --prefix app/frontend run check
'';
}
{
name = "fix";
help = "format & fix found issues";
command = ''
${nix.lib.cd_root}
nix fmt ./*.nix
${pkgs.golangci-lint}/bin/golangci-lint run --sort-results --out-format tab --config ${nix.lib.golangci-config-file} --fix --issues-exit-code 0 ./...
npm --prefix app/frontend run fix
'';
}
{
name = "update-npm-deps-hash";
help = "Calculates and updates the npmDepsHash in default.nix";
command = ''
${nix.lib.cd_root}
echo "Calculating npm deps"
# STDERR is poluted by the installed nodules, silencing
HASH=''$(${pkgs.prefetch-npm-deps}/bin/prefetch-npm-deps ''$PRJ_ROOT/app/frontend/package-lock.json 2> /dev/null)
[[ ''$HASH = sha256* ]] && echo "Hash is ''$HASH"
[[ ''$HASH != sha256* ]] && echo "Failed" && exit 137
# Replace the first occurence of npmDepsHash with the new calculated hash in this file
sed -i "0,/npmDepsHash =/{s@npmDepsHash = .*@npmDepsHash = \"''$HASH\";@}" ./flake.nix
'';
}
{
name = "dev";
help = "Starts the javascript project in dev";
command = ''
npm --prefix ''$PRJ_ROOT/app/frontend run dev
'';
}
];
};
});
};
}