diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b1ba93fb..888f0b77 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "pocket-id", - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "image": "ghcr.io/voidzero-dev/vite-plus:1.0.0", "features": { "ghcr.io/devcontainers/features/go:1": { "version": "1.27" @@ -12,12 +12,14 @@ "golang.go", "svelte.svelte-vscode", "bradlc.vscode-tailwindcss", - "esbenp.prettier-vscode" + "oxc.oxc-vscode", + "VoidZero.vite-plus-extension-pack" ] } }, "containerEnv": { "HOST": "0.0.0.0" }, - "postCreateCommand": "npm install --prefix frontend && cd backend && go mod download" + "remoteUser": "vp", + "postCreateCommand": "vp install && cd backend && go mod download" } diff --git a/.github/workflows/build-next.yml b/.github/workflows/build-next.yml index 4c4e19a4..ab460aa2 100644 --- a/.github/workflows/build-next.yml +++ b/.github/workflows/build-next.yml @@ -27,8 +27,12 @@ jobs: with: fetch-depth: 0 - - name: Setup Node.js and pnpm - uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0 + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1 + with: + node-version-file: .node-version + cache: true + run-install: false - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2a97e879..97188c0e 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -49,8 +49,12 @@ jobs: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Setup Node.js and pnpm - uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0 + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1 + with: + node-version-file: .node-version + cache: true + run-install: false - name: Set up Depot CLI uses: depot/setup-action@91bc8495a33ebfc504ffc89e5674379ccf23c29c # v1.7.2 @@ -136,12 +140,11 @@ jobs: run: docker load < /tmp/francis-image.tar - name: Install test dependencies - run: pnpm --filter pocket-id-tests install --frozen-lockfile + run: vp -C tests install --frozen-lockfile - name: Install Playwright Browsers - working-directory: ./tests if: steps.playwright-cache.outputs.cache-hit != 'true' - run: pnpm exec playwright install --with-deps chromium + run: vp -C tests exec playwright install --with-deps chromium - name: Run Docker containers working-directory: ./tests/setup @@ -184,8 +187,7 @@ jobs: fi - name: Run Playwright tests - working-directory: ./tests - run: pnpm exec playwright test + run: vp -C tests exec playwright test - name: Upload Test Report uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffa75e3c..ff6ef940 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,13 +68,17 @@ jobs: echo "tag=v$version" >> "$GITHUB_OUTPUT" echo "Preparing release v$version from $GITHUB_SHA." >> "$GITHUB_STEP_SUMMARY" - - name: Setup Node.js and pnpm + - name: Setup Vite+ if: steps.version.outputs.tag != '' - uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0 + uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1 + with: + node-version-file: .node-version + cache: true + run-install: false - name: Install dependencies if: steps.version.outputs.tag != '' - run: pnpm install --frozen-lockfile + run: vp install --frozen-lockfile - name: Update version and changelog if: steps.version.outputs.tag != '' @@ -86,7 +90,7 @@ jobs: printf '%s\n' "$VERSION" > .version jq --arg version "$VERSION" '.version = $version' frontend/package.json > "$RUNNER_TEMP/package.json" mv "$RUNNER_TEMP/package.json" frontend/package.json - pnpm --dir frontend exec prettier --write package.json + vp fmt frontend/package.json git cliff --prepend CHANGELOG.md --tag "$RELEASE_TAG" --unreleased - name: Create bot app token @@ -166,8 +170,12 @@ jobs: gh release create "$RELEASE_TAG" --verify-tag --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-notes.md" --draft fi - - name: Setup Node.js and pnpm - uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0 + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1 + with: + node-version-file: .node-version + cache: true + run-install: false - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 diff --git a/.github/workflows/svelte-check.yml b/.github/workflows/svelte-check.yml index fa98d2ce..cc0acd3a 100644 --- a/.github/workflows/svelte-check.yml +++ b/.github/workflows/svelte-check.yml @@ -35,19 +35,21 @@ jobs: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Setup Node.js and pnpm - uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0 + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1 + with: + node-version-file: .node-version + cache: true + run-install: false - name: Install dependencies - run: pnpm --filter pocket-id-frontend install --frozen-lockfile + run: vp -C frontend install --frozen-lockfile - name: Build Pocket ID Frontend - working-directory: frontend - run: pnpm --filter pocket-id-frontend build + run: vp -C frontend run build - name: Add svelte-check problem matcher run: echo "::add-matcher::.github/svelte-check-matcher.json" - name: Run svelte-check - working-directory: frontend - run: pnpm --filter pocket-id-frontend check + run: vp -C frontend run check diff --git a/.gitignore b/.gitignore index 5b4ed738..9eda3498 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,7 @@ dist/ /backend/go.work /backend/go.work.sum -.deepsec/ \ No newline at end of file +.deepsec/ +# Vite+ generated git hook dispatcher +.vite-hooks/_ +.vitest/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9bf3c7e4..d3b48efd 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -7,8 +7,8 @@ project_name: pocket-id before: hooks: - - pnpm install --frozen-lockfile - - pnpm -C frontend build + - vp install --frozen-lockfile + - vp -C frontend run build - mkdir -p .tmp - mv backend/frontend/dist/cyclonedx/frontend.cdx.json .tmp/frontend.cdx.json - rm -r backend/frontend/dist/cyclonedx diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit new file mode 100755 index 00000000..85fb65b4 --- /dev/null +++ b/.vite-hooks/pre-commit @@ -0,0 +1 @@ +vp staged diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 64fce0ea..3988f6c4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,7 @@ "golang.go", "svelte.svelte-vscode", "bradlc.vscode-tailwindcss", - "esbenp.prettier-vscode" + "oxc.oxc-vscode", + "VoidZero.vite-plus-extension-pack" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 5346ba26..1f438894 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,7 @@ "request": "launch", "envFile": "${workspaceFolder}/frontend/.env", "cwd": "${workspaceFolder}/frontend", - "runtimeExecutable": "npm", + "runtimeExecutable": "vp", "runtimeArgs": [ "run", "dev" diff --git a/.vscode/settings.json b/.vscode/settings.json index ce7b561f..c2225d93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,8 @@ { "go.buildTags": "e2etest unit", - "prettier.documentSelectors": ["**/*.svelte"] + "editor.defaultFormatter": "oxc.oxc-vscode", + "oxc.fmt.disableNestedConfig": true, + "[svelte]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + } } diff --git a/AGENTS.md b/AGENTS.md index 79cfd9b8..8db356fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,8 +19,8 @@ golangci-lint run # lint (config: backend/.golangci.yml - includes build tags) # frontend/ (or root) pnpm check # svelte-check — the ONLY frontend type gate (no unit tests exist) -pnpm lint # prettier --check && eslint (note: not enforced by CI) -pnpm format # prettier --write — REQUIRED before opening a PR +pnpm lint # vp check — oxlint + oxfmt check via Vite+ (note: not enforced by CI) +pnpm format # vp fmt — REQUIRED before opening a PR ``` End-to-end (needs Docker; **stop any local backend on `:1411` first** — see gotchas): diff --git a/docker/Dockerfile b/docker/Dockerfile index 25408d64..6e420f61 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.7 +# syntax=docker/dockerfile:1 # This file uses multi-stage builds to build the application from source, including the front-end @@ -6,21 +6,20 @@ ARG BUILD_TAGS="" # Stage 1: Build Frontend -FROM node:24-alpine AS frontend-builder -RUN corepack enable +FROM --platform=$BUILDPLATFORM ghcr.io/voidzero-dev/vite-plus:1.0.0 AS frontend-builder +ENV CI=true +WORKDIR /app -WORKDIR /build +COPY --chown=vp:vp package.json pnpm-workspace.yaml pnpm-lock.yaml .node-version ./ +COPY --chown=vp:vp --parents frontend/package.json tests/package.json email-templates/package.json ./ +RUN --mount=type=cache,id=pocket-id-pnpm,target=/home/vp/.local/share/pnpm/store,sharing=locked,uid=1000,gid=1000 \ + vp -C frontend install --frozen-lockfile --prefer-offline -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ -COPY frontend/package.json ./frontend/ -RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ - pnpm --filter pocket-id-frontend install --frozen-lockfile +COPY --chown=vp:vp ./frontend ./frontend/ -COPY ./frontend ./frontend/ - -RUN --mount=type=cache,target=/build/frontend/node_modules/.vite \ - --mount=type=cache,target=/build/frontend/.svelte-kit \ - BUILD_OUTPUT_PATH=dist pnpm --filter pocket-id-frontend run build +RUN --mount=type=cache,target=/app/frontend/node_modules/.vite,uid=1000,gid=1000 \ + --mount=type=cache,target=/app/frontend/.svelte-kit,uid=1000,gid=1000 \ + BUILD_OUTPUT_PATH=dist vp -C frontend run build # Stage 2: Build Backend FROM golang:1.27.1-alpine AS backend-builder @@ -31,7 +30,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY ./backend ./ -COPY --from=frontend-builder /build/frontend/dist ./frontend/dist +COPY --from=frontend-builder /app/frontend/dist ./frontend/dist COPY .version .version WORKDIR /build/cmd diff --git a/email-templates/build.ts b/email-templates/build.ts index 3e407a37..42b04d00 100644 --- a/email-templates/build.ts +++ b/email-templates/build.ts @@ -1,78 +1,74 @@ -import { render } from "react-email"; -import * as fs from "node:fs"; -import * as path from "node:path"; +import { render } from 'react-email'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; -const outputDir = "../backend/resources/email-templates"; +const outputDir = '../backend/resources/email-templates'; if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); + fs.mkdirSync(outputDir, { recursive: true }); } function getTemplateName(filename: string): string { - return filename.replace(".tsx", ""); + return filename.replace('.tsx', ''); } -async function buildTemplateFile( - Component: any, - templateName: string, - isPlainText: boolean -) { - const rendered = await render( - Component(Component.TemplateProps), - isPlainText ? { plainText: true } : {}, - ); +async function buildTemplateFile(Component: any, templateName: string, isPlainText: boolean) { + const rendered = await render( + Component(Component.TemplateProps), + isPlainText ? { plainText: true } : {} + ); - // Normalize quotes - const normalized = rendered.replace(/"/g, '"'); + // Normalize quotes + const normalized = rendered.replace(/"/g, '"'); - const goTemplate = `{{define "root"}}${normalized}{{end}}`; - const suffix = isPlainText ? "_text.tmpl" : "_html.tmpl"; - const templatePath = path.join(outputDir, `${templateName}${suffix}`); + const goTemplate = `{{define "root"}}${normalized}{{end}}`; + const suffix = isPlainText ? '_text.tmpl' : '_html.tmpl'; + const templatePath = path.join(outputDir, `${templateName}${suffix}`); - fs.writeFileSync(templatePath, goTemplate); + fs.writeFileSync(templatePath, goTemplate); } async function discoverAndBuildTemplates() { - console.log("Discovering and building email templates..."); + console.log('Discovering and building email templates...'); - const emailsDir = "./emails"; - const files = fs.readdirSync(emailsDir); + const emailsDir = './emails'; + const files = fs.readdirSync(emailsDir); - for (const file of files) { - if (!file.endsWith(".tsx")) continue; + for (const file of files) { + if (!file.endsWith('.tsx')) continue; - const templateName = getTemplateName(file); - const modulePath = `./${emailsDir}/${file}`; + const templateName = getTemplateName(file); + const modulePath = `./${emailsDir}/${file}`; - console.log(`Building ${templateName}...`); + console.log(`Building ${templateName}...`); - try { - const module = await import(modulePath); - const Component = module.default || module[Object.keys(module)[0]]; + try { + const module = await import(modulePath); + const Component = module.default || module[Object.keys(module)[0]]; - if (!Component) { - console.error(`✗ No component found in ${file}`); - continue; - } + if (!Component) { + console.error(`✗ No component found in ${file}`); + continue; + } - if (!Component.TemplateProps) { - console.error(`✗ No TemplateProps found in ${file}`); - continue; - } + if (!Component.TemplateProps) { + console.error(`✗ No TemplateProps found in ${file}`); + continue; + } - await buildTemplateFile(Component, templateName, false); // HTML - await buildTemplateFile(Component, templateName, true); // Text + await buildTemplateFile(Component, templateName, false); // HTML + await buildTemplateFile(Component, templateName, true); // Text - console.log(`✓ Built ${templateName}`); - } catch (error) { - console.error(`✗ Error building ${templateName}:`, error); - } - } + console.log(`✓ Built ${templateName}`); + } catch (error) { + console.error(`✗ Error building ${templateName}:`, error); + } + } } async function main() { - await discoverAndBuildTemplates(); - console.log("All templates built successfully!"); + await discoverAndBuildTemplates(); + console.log('All templates built successfully!'); } main().catch(console.error); diff --git a/email-templates/components/base-template.tsx b/email-templates/components/base-template.tsx index 5f027b3d..52038770 100644 --- a/email-templates/components/base-template.tsx +++ b/email-templates/components/base-template.tsx @@ -1,119 +1,100 @@ -import { - Body, - Column, - Container, - Head, - Html, - Img, - Link, - Preview, - Row, - Section, -} from "react-email"; -import type { SharedProps } from "../props"; -import { colors, fonts, radius } from "./theme"; +import { Body, Column, Container, Head, Html, Img, Link, Preview, Row, Section } from 'react-email'; +import type { SharedProps } from '../props'; +import { colors, fonts, radius } from './theme'; interface BaseTemplateProps extends SharedProps { - preview?: string; - children: React.ReactNode; + preview?: string; + children: React.ReactNode; } export const BaseTemplate = ({ - logoURL, - appName, - appURL, - preview, - children, + logoURL, + appName, + appURL, + preview, + children }: BaseTemplateProps) => ( - - - - -