diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 362c2fa3..50411fae 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,27 +1,180 @@
name: Release
on:
- push:
- tags:
- - "v*.*.*"
+ workflow_dispatch:
+ inputs:
+ bump:
+ description: Version bump (auto uses conventional commits)
+ type: choice
+ default: auto
+ options:
+ - auto
+ - major
+ - minor
+ - patch
+
+concurrency:
+ group: release
+ cancel-in-progress: false
permissions:
- contents: write
- packages: write
- id-token: write
- attestations: write
- artifact-metadata: write
+ contents: read
+
+defaults:
+ run:
+ shell: bash
jobs:
- release:
+ prepare:
+ name: Prepare release
+ runs-on: depot-ubuntu-latest
+ outputs:
+ tag: ${{ steps.version.outputs.tag }}
+ commit: ${{ steps.commit.outputs.commit }}
+
+ steps:
+ - name: Require the main branch
+ run: |
+ if [[ "$GITHUB_REF" != refs/heads/main ]]; then
+ echo "::error::Releases must be triggered from main."
+ exit 1
+ fi
+
+ - name: Checkout release source
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ github.sha }}
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Setup git-cliff
+ uses: taiki-e/install-action@v2
+ with:
+ tool: git-cliff@2.14.2
+
+ - name: Calculate next version
+ id: version
+ env:
+ BUMP: ${{ inputs.bump }}
+ run: |
+ version=$(git cliff --bumped-version --unreleased --offline --bump "$BUMP")
+ version=${version#v}
+ if [[ "$version" == "$(cat .version)" ]]; then
+ echo "No commits requiring a version bump; no release created." >> "$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "tag=v$version" >> "$GITHUB_OUTPUT"
+ echo "Preparing release v$version from $GITHUB_SHA." >> "$GITHUB_STEP_SUMMARY"
+
+ - name: Setup pnpm
+ if: steps.version.outputs.tag != ''
+ uses: pnpm/action-setup@v6
+ with:
+ run_install: false
+
+ - name: Setup Node.js
+ if: steps.version.outputs.tag != ''
+ uses: actions/setup-node@v6.5.0
+ with:
+ node-version: 24
+ cache: pnpm
+
+ - name: Install dependencies
+ if: steps.version.outputs.tag != ''
+ run: pnpm install --frozen-lockfile
+
+ - name: Update version and changelog
+ if: steps.version.outputs.tag != ''
+ env:
+ VERSION: ${{ steps.version.outputs.version }}
+ RELEASE_TAG: ${{ steps.version.outputs.tag }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ 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
+ git cliff --prepend CHANGELOG.md --tag "$RELEASE_TAG" --unreleased
+
+ - name: Create bot app token
+ if: steps.version.outputs.tag != ''
+ id: app-token
+ uses: actions/create-github-app-token@v3
+ with:
+ client-id: ${{ vars.BOT_APP_CLIENT_ID }}
+ private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
+ permission-contents: write
+
+ - name: Commit and tag release
+ if: steps.version.outputs.tag != ''
+ id: commit
+ env:
+ VERSION: ${{ steps.version.outputs.version }}
+ RELEASE_TAG: ${{ steps.version.outputs.tag }}
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
+ run: |
+ # Attribute the release commit to the app and authenticate the push with its installation token
+ bot_name="$APP_SLUG[bot]"
+ bot_id=$(gh api "users/$bot_name" --jq .id)
+ git config user.name "$bot_name"
+ git config user.email "$bot_id+$bot_name@users.noreply.github.com"
+ gh auth setup-git
+
+ git add .version frontend/package.json CHANGELOG.md
+ git commit -m "release: $VERSION"
+ git tag "$RELEASE_TAG"
+
+ # Publish both refs together so a concurrent main update cannot leave an orphaned release tag
+ git push --atomic origin HEAD:refs/heads/main "refs/tags/$RELEASE_TAG"
+ echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
+
+ build:
+ name: Build and attest
+ needs: prepare
+ if: needs.prepare.outputs.tag != ''
runs-on: depot-ubuntu-24.04-16
+ permissions:
+ contents: write
+ packages: write
+ id-token: write
+ attestations: write
+ artifact-metadata: write
+ env:
+ RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
+ ref: ${{ needs.prepare.outputs.commit }}
fetch-depth: 0
+ - name: Create bot app token
+ id: app-token
+ uses: actions/create-github-app-token@v3
+ with:
+ client-id: ${{ vars.BOT_APP_CLIENT_ID }}
+ private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
+ permission-contents: write
+
+ - name: Create draft release
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ run: |
+ # Reuse the draft when retrying a failed build
+ if is_draft=$(gh release view "$RELEASE_TAG" --json isDraft --jq .isDraft); then
+ if [[ "$is_draft" != true ]]; then
+ echo "::error::Release $RELEASE_TAG is already published."
+ exit 1
+ fi
+ else
+ awk '/^## v[0-9]/ { if (found) exit; found=1; next } found' CHANGELOG.md > "$RUNNER_TEMP/release-notes.md"
+ gh release create "$RELEASE_TAG" --verify-tag --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-notes.md" --draft
+ fi
+
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
@@ -39,6 +192,11 @@ jobs:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
+ - name: Setup Syft
+ uses: anchore/sbom-action/download-syft@v0.24.2
+ with:
+ syft-version: v1.52.0
+
- name: Set up Depot CLI
uses: depot/setup-action@v1
@@ -85,7 +243,25 @@ jobs:
with:
subject-checksums: ./dist/digests.txt
+ publish:
+ name: Publish release
+ needs: [prepare, build]
+ runs-on: depot-ubuntu-latest
+
+ steps:
+ - name: Create bot app token
+ id: app-token
+ uses: actions/create-github-app-token@v3
+ with:
+ client-id: ${{ vars.BOT_APP_CLIENT_ID }}
+ private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
+ permission-contents: write
+
- name: Publish release
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: gh release edit ${{ github.ref_name }} --draft=false
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ GH_REPO: ${{ github.repository }}
+ RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
+ run: |
+ gh release edit "$RELEASE_TAG" --draft=false
+ echo "Published [$RELEASE_TAG](https://github.com/$GH_REPO/releases/tag/$RELEASE_TAG)." >> "$GITHUB_STEP_SUMMARY"
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 34408144..9bf3c7e4 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -9,6 +9,9 @@ before:
hooks:
- pnpm install --frozen-lockfile
- pnpm -C frontend build
+ - mkdir -p .tmp
+ - mv backend/frontend/dist/cyclonedx/frontend.cdx.json .tmp/frontend.cdx.json
+ - rm -r backend/frontend/dist/cyclonedx
builds:
- id: pocket-id
@@ -91,6 +94,23 @@ archives:
checksum:
name_template: checksums.txt
+sboms:
+ - id: binaries
+ artifacts: binary
+ disable: '{{ if index .Env "BUILD_NEXT" }}true{{ end }}'
+ documents:
+ - "pocket-id_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ targetVariant . }}.sbom.spdx.json"
+ cmd: sh
+ args:
+ - ../scripts/development/generate-binary-sbom.sh
+ - $artifact
+ - $document
+ - "pocket-id_{{ .Os }}_{{ .Arch }}{{ targetVariant . }}"
+ - "{{ .Version }}"
+ env:
+ - SYFT_CHECK_FOR_APP_UPDATE=false
+ - SYFT_CACHE_DIR=
+
docker_digest:
name_template: digests.txt
@@ -124,6 +144,7 @@ dockers_v2:
sbom: true
extra_files:
- scripts/docker
+ - .tmp/frontend.cdx.json
- id: pocket-id-distroless
ids:
@@ -152,6 +173,8 @@ dockers_v2:
"org.opencontainers.image.title": "Pocket ID"
disable: '{{ if index .Env "BUILD_NEXT" }}true{{ end }}'
sbom: true
+ extra_files:
+ - .tmp/frontend.cdx.json
- id: pocket-id-next
ids:
@@ -179,6 +202,7 @@ dockers_v2:
sbom: true
extra_files:
- scripts/docker
+ - .tmp/frontend.cdx.json
- id: pocket-id-next-distroless
ids:
@@ -204,6 +228,8 @@ dockers_v2:
"org.opencontainers.image.title": "Pocket ID"
disable: '{{ if not (index .Env "BUILD_NEXT") }}true{{ end }}'
sbom: true
+ extra_files:
+ - .tmp/frontend.cdx.json
notarize:
macos:
diff --git a/.version b/.version
index 68e69e40..75249069 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-2.15.0
+2.16.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60a4e1ae..751d0efa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+## v2.16.0
+
+### Features
+
+- update the design of the email templates ([3249b0d](https://github.com/pocket-id/pocket-id/commit/3249b0dc8c00220b74e1008f94812cc02f6313c1) by @stonith404)
+
+### Bug Fixes
+
+- loading indicator not visible ([4ba2899](https://github.com/pocket-id/pocket-id/commit/4ba28992ff496caa20b6abe6f614eeb1fa777469) by @stonith404)
+- login with Yubikey not working in some Safari browsers ([4858db3](https://github.com/pocket-id/pocket-id/commit/4858db3bb4cf6619cad8682da61f0e793f24874c) by @stonith404)
+
+### Other
+
+- upgrade git cliff version ([98f9e39](https://github.com/pocket-id/pocket-id/commit/98f9e39785ae913d65ab3ad3523feaf907d3686e) by @stonith404)
+- update francis to rc7 ([\#1769](https://github.com/pocket-id/pocket-id/pull/1769) by @ItalyPaleAle)
+- include frontend dependencies in SBOM ([5a1c6f0](https://github.com/pocket-id/pocket-id/commit/5a1c6f0547f779175107d31ea13d4f24000a1a65) by @stonith404)
+- include SBOMs for binaries ([8fe53ed](https://github.com/pocket-id/pocket-id/commit/8fe53ed42c522717c8c69612cc700e195b8ab2ea) by @stonith404)
+- replace local release script with action ([ba246e6](https://github.com/pocket-id/pocket-id/commit/ba246e61465378bc11571fcc77ca01c936fda174) by @stonith404)
+
+**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v2.15.0...v2.16.0
## v2.15.0
### Bug Fixes
diff --git a/backend/internal/email/module.go b/backend/internal/email/module.go
index 1f6b2230..38e318e9 100644
--- a/backend/internal/email/module.go
+++ b/backend/internal/email/module.go
@@ -36,6 +36,7 @@ type template[V any] struct {
type templateData[V any] struct {
AppName string
LogoURL string
+ AppURL string
Data *V
}
@@ -138,6 +139,7 @@ func send[V any](ctx context.Context, module *Module, dbConfig *appconfig.AppCon
templateData := &templateData[V]{
AppName: dbConfig.AppName.String(),
LogoURL: common.EnvConfig.AppURL + "/api/application-images/email",
+ AppURL: common.EnvConfig.AppURL,
Data: data,
}
diff --git a/backend/internal/email/module_test.go b/backend/internal/email/module_test.go
index 88afc793..c067ee80 100644
--- a/backend/internal/email/module_test.go
+++ b/backend/internal/email/module_test.go
@@ -67,7 +67,7 @@ func TestModuleSendsEveryEmailType(t *testing.T) {
{
name: "email verification",
subject: "Verify your Pocket ID Test email address",
- bodyContains: []string{"EMAIL VERIFICATION", "Hello Test User", "https://id.example.test/verify-token"},
+ bodyContains: []string{"VERIFY YOUR EMAIL ADDRESS", "Hello Test User", "https://id.example.test/verify-token"},
send: func(ctx context.Context, config *appconfig.AppConfigModel) error {
return module.SendEmailVerification(ctx, config, user.FullName(), userEmail, "https://id.example.test/verify-token")
},
diff --git a/backend/internal/middleware/error_handler.go b/backend/internal/middleware/error_handler.go
index fd1949d2..e5336fcf 100644
--- a/backend/internal/middleware/error_handler.go
+++ b/backend/internal/middleware/error_handler.go
@@ -194,6 +194,22 @@ func writeErrorResponse(c *gin.Context, classified classifiedError, requestID st
func logRequestError(c *gin.Context, err error, classified classifiedError, requestID string) {
if classified.status < http.StatusInternalServerError {
+ cause := errors.Unwrap(err)
+ if cause == nil {
+ return
+ }
+
+ slog.DebugContext(c.Request.Context(), "Request rejected",
+ slog.String("error_code", string(classified.code)),
+ slog.String("error_type", errorTypeName(err)),
+ slog.String("cause_type", errorTypeName(cause)),
+ slog.Int("http_status", classified.status),
+ slog.String("request_id", requestID),
+ slog.String("http_method", c.Request.Method),
+ slog.String("http_path", c.Request.URL.Path),
+ slog.Any("error", err),
+ slog.Any("cause", cause),
+ )
return
}
diff --git a/backend/internal/webauthn/service.go b/backend/internal/webauthn/service.go
index d05d70db..cdeedd05 100644
--- a/backend/internal/webauthn/service.go
+++ b/backend/internal/webauthn/service.go
@@ -281,6 +281,7 @@ func (s *Service) VerifyLogin(ctx context.Context, dbConfig *appconfig.AppConfig
Extensions: storedSession.Extensions,
CredParams: storedSession.CredentialParams,
}
+ discardUnrequestedFalseAppIDOutput(session.Extensions, credentialAssertionData)
var user *model.User
_, err := s.webAuthn.ValidateDiscoverableLogin(func(_, userHandle []byte) (gowebauthn.User, error) {
@@ -535,6 +536,7 @@ func (s *Service) CreateReauthenticationTokenWithWebauthn(ctx context.Context, s
Extensions: storedSession.Extensions,
CredParams: storedSession.CredentialParams,
}
+ discardUnrequestedFalseAppIDOutput(session.Extensions, credentialAssertionData)
// Validate the credential assertion
var user *model.User
@@ -589,6 +591,21 @@ func classifyPasskeyError(err error, fallback func(error) *apperror.Error) *appe
return fallback(err)
}
+func discardUnrequestedFalseAppIDOutput(session protocol.SessionExtensions, credential *protocol.ParsedCredentialAssertionData) {
+ if credential == nil || credential.ClientExtensionResults.AppID == nil || *credential.ClientExtensionResults.AppID {
+ return
+ }
+
+ for _, requested := range session.Requested {
+ if requested == protocol.ExtensionAppID {
+ return
+ }
+ }
+
+ // Safari reports appid=false for security keys even when the relying party did not request the legacy extension
+ credential.ClientExtensionResults.AppID = nil
+}
+
func (s *Service) ConsumeReauthenticationToken(ctx context.Context, tx *gorm.DB, token string, userID string) (time.Time, error) {
hashedToken := utils.CreateSha256Hash(token)
var reauthToken ReauthenticationToken
diff --git a/backend/internal/webauthn/service_test.go b/backend/internal/webauthn/service_test.go
index c270d1e3..283c210f 100644
--- a/backend/internal/webauthn/service_test.go
+++ b/backend/internal/webauthn/service_test.go
@@ -263,6 +263,66 @@ func TestClassifyPasskeyErrorPreservesStructuredLookupFailure(t *testing.T) {
require.ErrorIs(t, err, cause)
}
+func TestDiscardUnrequestedFalseAppIDOutput(t *testing.T) {
+ tests := []struct {
+ name string
+ requested []string
+ appID bool
+ extra map[string]any
+ wantAppID *bool
+ wantError string
+ }{
+ {
+ name: "unrequested false appid is discarded",
+ appID: false,
+ wantAppID: nil,
+ },
+ {
+ name: "requested false appid is preserved",
+ requested: []string{protocol.ExtensionAppID},
+ appID: false,
+ wantAppID: new(false),
+ },
+ {
+ name: "unrequested true appid is rejected",
+ appID: true,
+ wantAppID: new(true),
+ wantError: "appid",
+ },
+ {
+ name: "other unsolicited output is rejected",
+ appID: false,
+ extra: map[string]any{"example": true},
+ wantAppID: nil,
+ wantError: "example",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ credential := &protocol.ParsedCredentialAssertionData{
+ ParsedPublicKeyCredential: protocol.ParsedPublicKeyCredential{
+ ClientExtensionResults: protocol.AuthenticationExtensionsClientOutputs{
+ AppID: new(tc.appID),
+ Extra: tc.extra,
+ },
+ },
+ }
+ session := protocol.SessionExtensions{Requested: tc.requested}
+
+ discardUnrequestedFalseAppIDOutput(session, credential)
+
+ assert.Equal(t, tc.wantAppID, credential.ClientExtensionResults.AppID)
+ err := credential.ClientExtensionResults.Verify(session, protocol.AssertCeremony, protocol.UnsolicitedOutputPolicyReject)
+ if tc.wantError == "" {
+ require.NoError(t, err)
+ } else {
+ require.ErrorContains(t, err, tc.wantError)
+ }
+ })
+ }
+}
+
func TestWebAuthnManagementOperationsReturnSpecificNotFoundErrors(t *testing.T) {
service, err := newService(Dependencies{
DB: testutils.NewDatabaseForTest(t),
diff --git a/backend/resources/email-templates/api-key-expiring-soon_html.tmpl b/backend/resources/email-templates/api-key-expiring-soon_html.tmpl
index b9b3bb5c..451840c9 100644
--- a/backend/resources/email-templates/api-key-expiring-soon_html.tmpl
+++ b/backend/resources/email-templates/api-key-expiring-soon_html.tmpl
@@ -1 +1 @@
-{{define "root"}}
{{.AppName}}
API Key Expiring Soon Warning
Hello {{.Data.Name}}, This is a reminder that your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}} .
Please generate a new API key if you need continued access.
{{end}}
\ No newline at end of file
+{{define "root"}}Your API key {{.Data.ApiKeyName}} expires on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}} Your API key {{.Data.ApiKeyName}} expires on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}
API key expiring soon Hello {{.Data.Name}},
Your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}} . Anything that uses this key will stop working once it expires.
To keep access, create a new API key in your {{.AppName}} account settings before then.
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/api-key-expiring-soon_text.tmpl b/backend/resources/email-templates/api-key-expiring-soon_text.tmpl
index 247969d5..fb04995e 100644
--- a/backend/resources/email-templates/api-key-expiring-soon_text.tmpl
+++ b/backend/resources/email-templates/api-key-expiring-soon_text.tmpl
@@ -1,11 +1,7 @@
-{{define "root"}}{{.AppName}}
-
-
-API KEY EXPIRING SOON
-
-Warning
+{{define "root"}}API KEY EXPIRING SOON
Hello {{.Data.Name}},
-This is a reminder that your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}.
-Please generate a new API key if you need continued access.{{end}}
\ No newline at end of file
+Your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}. Anything that uses this key will stop working once it expires.
+
+To keep access, create a new API key in your {{.AppName}} account settings before then.{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/email-verification_html.tmpl b/backend/resources/email-templates/email-verification_html.tmpl
index 3d32f78f..bd98dc3f 100644
--- a/backend/resources/email-templates/email-verification_html.tmpl
+++ b/backend/resources/email-templates/email-verification_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
Hello {{.Data.UserFullName}}, Click the button below to verify your email address for {{.AppName}}. This link will expire in 24 hours.
{{end}}
\ No newline at end of file
+{{define "root"}}Confirm the email address for your {{.AppName}} account Confirm the email address for your {{.AppName}} account
Verify your email address Hello {{.Data.UserFullName}},
Click the button below to confirm the email address for your {{.AppName}} account. This link expires in 24 hours.
Or if you don't like clicking buttons, open this link:{{.Data.VerificationLink}}
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/email-verification_text.tmpl b/backend/resources/email-templates/email-verification_text.tmpl
index 660394ed..fdaef652 100644
--- a/backend/resources/email-templates/email-verification_text.tmpl
+++ b/backend/resources/email-templates/email-verification_text.tmpl
@@ -1,10 +1,10 @@
-{{define "root"}}{{.AppName}}
-
-
-EMAIL VERIFICATION
+{{define "root"}}VERIFY YOUR EMAIL ADDRESS
Hello {{.Data.UserFullName}},
-Click the button below to verify your email address for {{.AppName}}. This link will expire in 24 hours.
+Click the button below to confirm the email address for your {{.AppName}} account. This link expires in 24 hours.
-Verify {{.Data.VerificationLink}}{{end}}
\ No newline at end of file
+Verify email address {{.Data.VerificationLink}}
+
+Or if you don't like clicking buttons, open this link:
+{{.Data.VerificationLink}}{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/login-with-new-device_html.tmpl b/backend/resources/email-templates/login-with-new-device_html.tmpl
index a3c5aa5c..06211b1b 100644
--- a/backend/resources/email-templates/login-with-new-device_html.tmpl
+++ b/backend/resources/email-templates/login-with-new-device_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
New Sign-In Detected Warning
Your {{.AppName}} account was recently accessed from a new IP address or browser. If you recognize this activity, no further action is required.
Details Approximate Location
{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
IP Address
{{.Data.IPAddress}}
Device
{{.Data.Device}}
Sign-In Time
{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}
{{end}}
\ No newline at end of file
+{{define "root"}}A new sign-in to your {{.AppName}} account was detected A new sign-in to your {{.AppName}} account was detected
New sign-in detected Your {{.AppName}} account was recently accessed from a new IP address or browser. If this was you, no further action is needed.
Approximate location {{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
IP address {{.Data.IPAddress}}
Time {{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}
If you don't recognize this activity, review the passkeys in your {{.AppName}} account settings and remove any you don't recognize.
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/login-with-new-device_text.tmpl b/backend/resources/email-templates/login-with-new-device_text.tmpl
index 9d8c183c..e858802a 100644
--- a/backend/resources/email-templates/login-with-new-device_text.tmpl
+++ b/backend/resources/email-templates/login-with-new-device_text.tmpl
@@ -1,26 +1,17 @@
-{{define "root"}}{{.AppName}}
+{{define "root"}}NEW SIGN-IN DETECTED
+Your {{.AppName}} account was recently accessed from a new IP address or browser. If this was you, no further action is needed.
-NEW SIGN-IN DETECTED
-
-Warning
-
-Your {{.AppName}} account was recently accessed from a new IP address or browser. If you recognize this activity, no further action is required.
-
-DETAILS
-
-Approximate Location
-
+Approximate location
{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
-IP Address
-
+IP address
{{.Data.IPAddress}}
Device
-
{{.Data.Device}}
-Sign-In Time
+Time
+{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}
-{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}{{end}}
\ No newline at end of file
+If you don't recognize this activity, review the passkeys in your {{.AppName}} account settings and remove any you don't recognize.{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/one-time-access_html.tmpl b/backend/resources/email-templates/one-time-access_html.tmpl
index 86007b1c..c84b26bd 100644
--- a/backend/resources/email-templates/one-time-access_html.tmpl
+++ b/backend/resources/email-templates/one-time-access_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
Click the button below to sign in to {{.AppName}} with a login code. Or visit {{.Data.LoginLink}} and enter the code {{.Data.Code}} . This code expires in {{.Data.ExpirationString}}.
{{end}}
\ No newline at end of file
+{{define "root"}}Your {{.AppName}} login code is {{.Data.Code}} Your {{.AppName}} login code is {{.Data.Code}}
Your login code Use the code below to sign in to {{.AppName}}. It expires in {{.Data.ExpirationString}}.
Or open {{.Data.LoginLink}} and enter the code manually.
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/one-time-access_text.tmpl b/backend/resources/email-templates/one-time-access_text.tmpl
index 8a311977..040da20a 100644
--- a/backend/resources/email-templates/one-time-access_text.tmpl
+++ b/backend/resources/email-templates/one-time-access_text.tmpl
@@ -1,11 +1,9 @@
-{{define "root"}}{{.AppName}}
+{{define "root"}}YOUR LOGIN CODE
+Use the code below to sign in to {{.AppName}}. It expires in {{.Data.ExpirationString}}.
-YOUR LOGIN CODE
+{{.Data.Code}}
-Click the button below to sign in to {{.AppName}} with a login code.
-Or visit {{.Data.LoginLink}} and enter the code {{.Data.Code}}.
+Sign in {{.Data.LoginLinkWithCode}}
-This code expires in {{.Data.ExpirationString}}.
-
-Sign In {{.Data.LoginLinkWithCode}}{{end}}
\ No newline at end of file
+Or open {{.Data.LoginLink}} and enter the code manually.{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/test_html.tmpl b/backend/resources/email-templates/test_html.tmpl
index ac1b65be..985c5981 100644
--- a/backend/resources/email-templates/test_html.tmpl
+++ b/backend/resources/email-templates/test_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
Your email setup is working correctly!
{{end}}
\ No newline at end of file
+{{define "root"}}Your email setup is working correctly Your email setup is working correctly
Test email Your email setup is working correctly!
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/test_text.tmpl b/backend/resources/email-templates/test_text.tmpl
index 08459a50..9da30fc4 100644
--- a/backend/resources/email-templates/test_text.tmpl
+++ b/backend/resources/email-templates/test_text.tmpl
@@ -1,6 +1,3 @@
-{{define "root"}}{{.AppName}}
-
-
-TEST EMAIL
+{{define "root"}}TEST EMAIL
Your email setup is working correctly!{{end}}
\ No newline at end of file
diff --git a/docker/Dockerfile-distroless b/docker/Dockerfile-distroless
index d252de3c..ca3819cd 100644
--- a/docker/Dockerfile-distroless
+++ b/docker/Dockerfile-distroless
@@ -10,6 +10,7 @@ ARG TARGETVARIANT
WORKDIR /app
COPY linux/${TARGETARCH}/${TARGETVARIANT}/pocket-id /app/pocket-id
+COPY ./.tmp/frontend.cdx.json /usr/share/doc/pocket-id/frontend.cdx.json
EXPOSE 1411
ENV APP_ENV=production
diff --git a/docker/Dockerfile-prebuilt b/docker/Dockerfile-prebuilt
index 156c8ab2..0be7185e 100644
--- a/docker/Dockerfile-prebuilt
+++ b/docker/Dockerfile-prebuilt
@@ -13,6 +13,7 @@ RUN apk add --no-cache su-exec
COPY linux/${TARGETARCH}/${TARGETVARIANT}/pocket-id /app/pocket-id
COPY ./scripts/docker /app/docker
+COPY ./.tmp/frontend.cdx.json /usr/share/doc/pocket-id/frontend.cdx.json
EXPOSE 1411
ENV APP_ENV=production
diff --git a/email-templates/build.ts b/email-templates/build.ts
index 3c235918..3e407a37 100644
--- a/email-templates/build.ts
+++ b/email-templates/build.ts
@@ -1,4 +1,4 @@
-import { render } from "@react-email/components";
+import { render } from "react-email";
import * as fs from "node:fs";
import * as path from "node:path";
@@ -17,9 +17,10 @@ async function buildTemplateFile(
templateName: string,
isPlainText: boolean
) {
- const rendered = await render(Component(Component.TemplateProps), {
- plainText: isPlainText,
- });
+ const rendered = await render(
+ Component(Component.TemplateProps),
+ isPlainText ? { plainText: true } : {},
+ );
// Normalize quotes
const normalized = rendered.replace(/"/g, '"');
diff --git a/email-templates/components/base-template.tsx b/email-templates/components/base-template.tsx
index c4e7590d..5f027b3d 100644
--- a/email-templates/components/base-template.tsx
+++ b/email-templates/components/base-template.tsx
@@ -5,77 +5,115 @@ import {
Head,
Html,
Img,
+ Link,
+ Preview,
Row,
Section,
- Text,
-} from "@react-email/components";
+} from "react-email";
+import type { SharedProps } from "../props";
+import { colors, fonts, radius } from "./theme";
-interface BaseTemplateProps {
- logoURL?: string;
- appName: string;
+interface BaseTemplateProps extends SharedProps {
+ preview?: string;
children: React.ReactNode;
}
export const BaseTemplate = ({
logoURL,
appName,
+ appURL,
+ preview,
children,
-}: BaseTemplateProps) => {
- return (
-
-
-
-
-
-
+}: BaseTemplateProps) => (
+
+
+
+
+
+
+ {preview && {preview} }
+
+
+
+
+
+
-
-
- {appName}
-
-
-
- {children}
-
-
-
- );
+
+
+
+
+ {appName}
+
+
+
+
+
+
+
+
+
+
+);
+
+// The heading font is self-hosted by the app, so it loads from the same origin as the logo and works without third-party requests
+// Clients without web font support fall back to the serif stack declared on the headings
+const fontFaceCss = (appURL: string) =>
+ `@font-face{font-family:'Gloock';font-style:normal;font-weight:400;mso-font-alt:'Georgia';src:url(${appURL}/fonts/Gloock-Regular.woff) format('woff');}`;
+
+const bodyStyle = {
+ margin: 0,
+ padding: "32px 16px",
+ backgroundColor: colors.background,
+ fontFamily: fonts.sans,
+ color: colors.text,
};
-const mainStyle = {
- padding: "50px",
- backgroundColor: "#FBFBFB",
- fontFamily: "Arial, sans-serif",
+const containerStyle = {
+ width: "100%",
+ maxWidth: "480px",
+ margin: "0 auto",
};
-const logoStyle = {
- width: "32px",
- height: "32px",
+const headerStyle = {
+ marginBottom: "20px",
+};
+
+const logoColumnStyle = {
+ width: "36px",
verticalAlign: "middle",
};
-const titleStyle = {
- fontSize: "23px",
- fontWeight: "bold",
- margin: "0",
- padding: "0",
+const logoStyle = {
+ display: "block",
+ width: "28px",
+ height: "28px",
+ borderRadius: "6px",
};
-const content = {
- backgroundColor: "white",
- padding: "24px",
- borderRadius: "10px",
- boxShadow: "0 1px 4px 0px rgba(0, 0, 0, 0.1)",
+const appNameStyle = {
+ fontFamily: fonts.serif,
+ fontSize: "20px",
+ lineHeight: "28px",
+ color: colors.foreground,
+ textDecoration: "none",
};
+
+const cardStyle = {
+ backgroundColor: colors.card,
+ border: `1px solid ${colors.border}`,
+ borderRadius: radius.card,
+ padding: "32px",
+ textAlign: "left" as const,
+};
+
diff --git a/email-templates/components/button.tsx b/email-templates/components/button.tsx
index ea1b3eed..037eb942 100644
--- a/email-templates/components/button.tsx
+++ b/email-templates/components/button.tsx
@@ -1,33 +1,33 @@
-import { Button as EmailButton } from "@react-email/components";
+import { Button as EmailButton, Section } from "react-email";
+import { colors, fonts, radius } from "./theme";
interface ButtonProps {
href: string;
children: React.ReactNode;
- style?: React.CSSProperties;
}
-export const Button = ({ href, children, style = {} }: ButtonProps) => {
- const buttonStyle = {
- backgroundColor: "#000000",
- color: "#ffffff",
- padding: "12px 24px",
- borderRadius: "4px",
- fontSize: "15px",
- fontWeight: "500",
- cursor: "pointer",
- marginTop: "10px",
- ...style,
- };
+export const Button = ({ href, children }: ButtonProps) => (
+
+);
- return (
-
-
- {children}
-
-
- );
-};
-
-const buttonContainer = {
+const containerStyle = {
+ margin: "24px 0 8px 0",
textAlign: "center" as const,
};
+
+const buttonStyle = {
+ display: "inline-block",
+ padding: "12px 28px",
+ backgroundColor: colors.primary,
+ color: colors.primaryForeground,
+ borderRadius: radius.pill,
+ fontFamily: fonts.sans,
+ fontSize: "14px",
+ lineHeight: "20px",
+ fontWeight: 500,
+ textDecoration: "none",
+};
diff --git a/email-templates/components/card-header.tsx b/email-templates/components/card-header.tsx
index 6d916218..5a15de86 100644
--- a/email-templates/components/card-header.tsx
+++ b/email-templates/components/card-header.tsx
@@ -1,38 +1,24 @@
-import { Column, Heading, Row, Text } from "@react-email/components";
+import { Heading } from "react-email";
+import { colors, fonts } from "./theme";
-export default function CardHeader({
- title,
- warning,
-}: {
+interface CardHeaderProps {
title: string;
- warning?: boolean;
-}) {
+}
+
+export default function CardHeader({ title }: CardHeaderProps) {
return (
-
-
-
- {title}
-
-
-
- {warning && Warning }
-
-
+
+ {title}
+
);
}
const titleStyle = {
- fontSize: "20px",
- fontWeight: "bold" as const,
- margin: 0,
-};
-
-const warningStyle = {
- backgroundColor: "#ffd966",
- color: "#7f6000",
- padding: "1px 12px",
- borderRadius: "50px",
- fontSize: "12px",
- display: "inline-block",
- margin: 0,
+ margin: "0 0 16px 0",
+ fontFamily: fonts.serif,
+ fontSize: "26px",
+ lineHeight: "32px",
+ fontWeight: 400,
+ letterSpacing: "-0.01em",
+ color: colors.foreground,
};
diff --git a/email-templates/components/code-box.tsx b/email-templates/components/code-box.tsx
new file mode 100644
index 00000000..58c8a347
--- /dev/null
+++ b/email-templates/components/code-box.tsx
@@ -0,0 +1,39 @@
+import { Column, Row, Section, Text } from "react-email";
+import { colors, fonts, radius } from "./theme";
+
+interface CodeBoxProps {
+ code: string;
+}
+
+export const CodeBox = ({ code }: CodeBoxProps) => (
+
+);
+
+const boxStyle = {
+ margin: "8px 0 0 0",
+ backgroundColor: colors.muted,
+ border: `1px solid ${colors.border}`,
+ borderRadius: radius.box,
+};
+
+const cellStyle = {
+ padding: "20px 16px",
+ textAlign: "center" as const,
+};
+
+const codeStyle = {
+ margin: 0,
+ fontFamily: fonts.mono,
+ fontSize: "32px",
+ lineHeight: "40px",
+ fontWeight: 600,
+ letterSpacing: "8px",
+ paddingLeft: "8px",
+ color: colors.foreground,
+};
diff --git a/email-templates/components/details-list.tsx b/email-templates/components/details-list.tsx
new file mode 100644
index 00000000..810062d2
--- /dev/null
+++ b/email-templates/components/details-list.tsx
@@ -0,0 +1,54 @@
+import { Column, Row, Section } from "react-email";
+import { colors, radius } from "./theme";
+
+interface DetailsListProps {
+ items: { label: string; value: string }[];
+}
+
+// A key-value list inside a muted box, used for sign-in details and similar metadata
+// Each value sits below its label so long values never fight the label for width on narrow screens
+// Every item is its own Row so the plain-text build separates entries with a blank line
+export const DetailsList = ({ items }: DetailsListProps) => (
+
+ {items.map((item, index) => {
+ const isLast = index === items.length - 1;
+ return (
+
+
+ {item.label}
+
+ {item.value}
+
+
+ );
+ })}
+
+);
+
+const boxStyle = {
+ margin: "8px 0 0 0",
+ backgroundColor: colors.muted,
+ border: `1px solid ${colors.border}`,
+ borderRadius: radius.box,
+};
+
+const cellStyle = {
+ padding: "12px 16px",
+};
+
+const labelStyle = {
+ fontSize: "12px",
+ lineHeight: "18px",
+ color: colors.mutedForeground,
+};
+
+const valueStyle = {
+ fontSize: "14px",
+ lineHeight: "20px",
+ fontWeight: 500,
+ color: colors.foreground,
+};
+
+const dividerStyle = {
+ borderBottom: `1px solid ${colors.border}`,
+};
diff --git a/email-templates/components/text.tsx b/email-templates/components/text.tsx
new file mode 100644
index 00000000..aa36b192
--- /dev/null
+++ b/email-templates/components/text.tsx
@@ -0,0 +1,29 @@
+import { Text } from "react-email";
+import { colors } from "./theme";
+
+interface TextProps {
+ children: React.ReactNode;
+ style?: React.CSSProperties;
+}
+
+export const Paragraph = ({ children, style }: TextProps) => (
+ {children}
+);
+
+export const Muted = ({ children, style }: TextProps) => (
+ {children}
+);
+
+const paragraphStyle = {
+ margin: "0 0 16px 0",
+ fontSize: "15px",
+ lineHeight: "24px",
+ color: colors.text,
+};
+
+const mutedStyle = {
+ margin: "16px 0 0 0",
+ fontSize: "13px",
+ lineHeight: "20px",
+ color: colors.mutedForeground,
+};
diff --git a/email-templates/components/theme.ts b/email-templates/components/theme.ts
new file mode 100644
index 00000000..f2ae359a
--- /dev/null
+++ b/email-templates/components/theme.ts
@@ -0,0 +1,25 @@
+// Design tokens mirrored from the frontend's app.css (light theme) so emails match the app
+// Email clients have no CSS variable or oklch support, so the values are the resolved hex equivalents
+export const colors = {
+ background: "#f5f5f5",
+ card: "#ffffff",
+ border: "#e5e5e5",
+ muted: "#fafafa",
+ foreground: "#0a0a0a",
+ text: "#404040",
+ mutedForeground: "#737373",
+ primary: "#171717",
+ primaryForeground: "#ffffff",
+};
+
+export const fonts = {
+ sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
+ serif: "Gloock, Georgia, 'Times New Roman', serif",
+ mono: "'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
+};
+
+export const radius = {
+ card: "24px",
+ box: "16px",
+ pill: "9999px",
+};
diff --git a/email-templates/emails/api-key-expiring-soon.tsx b/email-templates/emails/api-key-expiring-soon.tsx
index 2a33987d..7eff7439 100644
--- a/email-templates/emails/api-key-expiring-soon.tsx
+++ b/email-templates/emails/api-key-expiring-soon.tsx
@@ -1,7 +1,11 @@
-import { Text } from "@react-email/components";
import { BaseTemplate } from "../components/base-template";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { Muted, Paragraph } from "../components/text";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface ApiKeyExpiringData {
name: string;
@@ -9,28 +13,30 @@ interface ApiKeyExpiringData {
expiresAt: string;
}
-interface ApiKeyExpiringEmailProps {
- logoURL: string;
- appName: string;
+interface ApiKeyExpiringEmailProps extends SharedProps {
data: ApiKeyExpiringData;
}
export const ApiKeyExpiringEmail = ({
- logoURL,
- appName,
data,
+ ...props
}: ApiKeyExpiringEmailProps) => (
-
-
-
- Hello {data.name},
- This is a reminder that your API key
- {data.apiKeyName}
- {" "}
- will expire on {data.expiresAt} .
-
+
+
+ Hello {data.name},
+
+ Your API key {data.apiKeyName} will expire on{" "}
+ {data.expiresAt} . Anything that uses this key will stop
+ working once it expires.
+
- Please generate a new API key if you need continued access.
+
+ To keep access, create a new API key in your {props.appName} account
+ settings before then.
+
);
@@ -48,8 +54,8 @@ ApiKeyExpiringEmail.TemplateProps = {
ApiKeyExpiringEmail.PreviewProps = {
...sharedPreviewProps,
data: {
- name: "Elias Schneider",
- apiKeyName: "My API Key",
- expiresAt: "September 30, 2024",
+ name: "Elias",
+ apiKeyName: "CI deploy key",
+ expiresAt: "2026-01-30 12:00:00 UTC",
},
};
diff --git a/email-templates/emails/email-verification.tsx b/email-templates/emails/email-verification.tsx
index 11e4d4c2..e5a95c1b 100644
--- a/email-templates/emails/email-verification.tsx
+++ b/email-templates/emails/email-verification.tsx
@@ -1,41 +1,59 @@
-import { Text } from "@react-email/components";
+import { Link } from "react-email";
import { BaseTemplate } from "../components/base-template";
import { Button } from "../components/button";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { Muted, Paragraph } from "../components/text";
+import { colors } from "../components/theme";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface EmailVerificationData {
userFullName: string;
verificationLink: string;
}
-interface EmailVerificationProps {
- logoURL: string;
- appName: string;
+interface EmailVerificationProps extends SharedProps {
data: EmailVerificationData;
}
export const EmailVerification = ({
- logoURL,
- appName,
data,
+ ...props
}: EmailVerificationProps) => (
-
-
+
+
+ Hello {data.userFullName},
+
+ Click the button below to confirm the email address for your{" "}
+ {props.appName} account. This link expires in 24 hours.
+
-
- Hello {data.userFullName},
- Click the button below to verify your email address for {appName}. This
- link will expire in 24 hours.
+ Verify email address
+
+
+ Or if you don't like clicking buttons, open this link:
-
-
- Verify
+
+ {data.verificationLink}
+
+
);
export default EmailVerification;
+const linkStyle = {
+ color: colors.mutedForeground,
+ textDecoration: "underline",
+ wordBreak: "break-all" as const,
+};
+
EmailVerification.TemplateProps = {
...sharedTemplateProps,
data: {
@@ -49,6 +67,6 @@ EmailVerification.PreviewProps = {
data: {
userFullName: "Tim Cook",
verificationLink:
- "https://localhost:1411/user/verify-email?code=abcdefg12345",
+ "https://id.example.com/user/verify-email?code=abcdefg12345",
},
};
diff --git a/email-templates/emails/login-with-new-device.tsx b/email-templates/emails/login-with-new-device.tsx
index fbf008da..93781c19 100644
--- a/email-templates/emails/login-with-new-device.tsx
+++ b/email-templates/emails/login-with-new-device.tsx
@@ -1,7 +1,12 @@
-import { Column, Heading, Row, Text } from "@react-email/components";
import { BaseTemplate } from "../components/base-template";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { DetailsList } from "../components/details-list";
+import { Muted, Paragraph } from "../components/text";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface SignInData {
location: string;
@@ -10,78 +15,44 @@ interface SignInData {
dateTime: string;
}
-interface NewSignInEmailProps {
- logoURL: string;
- appName: string;
+interface NewSignInEmailProps extends SharedProps {
data: SignInData;
}
-export const NewSignInEmail = ({
- logoURL,
- appName,
- data,
-}: NewSignInEmailProps) => (
-
-
-
- Your {appName} account was recently accessed from a new IP address or
- browser. If you recognize this activity, no further action is required.
-
-
- Details
-
+export const NewSignInEmail = ({ data, ...props }: NewSignInEmailProps) => (
+
+
+
+ Your {props.appName} account was recently accessed from a new IP address
+ or browser. If this was you, no further action is needed.
+
-
-
- Approximate Location
- {data.location}
-
-
- IP Address
- {data.ipAddress}
-
-
+
-
-
- Device
- {data.device}
-
-
- Sign-In Time
- {data.dateTime}
-
-
+
+ If you don't recognize this activity, review the passkeys in your{" "}
+ {props.appName} account settings and remove any you don't recognize.
+
);
export default NewSignInEmail;
-const detailsBoxStyle = {
- width: "225px",
-};
-
-const detailsLabelStyle = {
- margin: 0,
- fontSize: "12px",
- color: "gray",
-};
-
-const detailsBoxValueStyle = {
- margin: 0,
-};
-
NewSignInEmail.TemplateProps = {
...sharedTemplateProps,
data: {
- location: "{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}",
+ location:
+ "{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}",
ipAddress: "{{.Data.IPAddress}}",
device: "{{.Data.Device}}",
dateTime: '{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}',
@@ -92,8 +63,8 @@ NewSignInEmail.PreviewProps = {
...sharedPreviewProps,
data: {
location: "San Francisco, USA",
- ipAddress: "127.0.0.1",
+ ipAddress: "203.0.113.42",
device: "Chrome on macOS",
- dateTime: "2024-01-01 12:00 PM UTC",
+ dateTime: "January 2, 2026 at 3:04 PM UTC",
},
};
diff --git a/email-templates/emails/one-time-access.tsx b/email-templates/emails/one-time-access.tsx
index 5abed7c6..cd4fd935 100644
--- a/email-templates/emails/one-time-access.tsx
+++ b/email-templates/emails/one-time-access.tsx
@@ -1,8 +1,15 @@
-import { Link, Text } from "@react-email/components";
+import { Link } from "react-email";
import { BaseTemplate } from "../components/base-template";
import { Button } from "../components/button";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { CodeBox } from "../components/code-box";
+import { Muted, Paragraph } from "../components/text";
+import { colors } from "../components/theme";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface OneTimeAccessData {
code: string;
@@ -11,43 +18,44 @@ interface OneTimeAccessData {
expirationString: string;
}
-interface OneTimeAccessEmailProps {
- logoURL: string;
- appName: string;
+interface OneTimeAccessEmailProps extends SharedProps {
data: OneTimeAccessData;
}
export const OneTimeAccessEmail = ({
- logoURL,
- appName,
data,
+ ...props
}: OneTimeAccessEmailProps) => (
-
-
+
+
+
+ Use the code below to sign in to {props.appName}. It expires in{" "}
+ {data.expirationString}.
+
-
- Click the button below to sign in to {appName} with a login code.
-
- Or visit{" "}
+
+
+ Sign in
+
+
+ Or open{" "}
{data.loginLink}
{" "}
- and enter the code {data.code} .
-
-
- This code expires in {data.expirationString}.
-
-
- Sign In
+ and enter the code manually.
+
);
export default OneTimeAccessEmail;
const linkStyle = {
- color: "#000",
+ color: colors.mutedForeground,
textDecoration: "underline",
- fontFamily: "Arial, sans-serif",
+ wordBreak: "break-all" as const,
};
OneTimeAccessEmail.TemplateProps = {
@@ -64,8 +72,8 @@ OneTimeAccessEmail.PreviewProps = {
...sharedPreviewProps,
data: {
code: "123456",
- loginLink: "https://example.com/login",
- buttonCodeLink: "https://example.com/login?code=123456",
+ loginLink: "https://id.example.com/lc",
+ buttonCodeLink: "https://id.example.com/lc/123456",
expirationString: "15 minutes",
},
};
diff --git a/email-templates/emails/test.tsx b/email-templates/emails/test.tsx
index 497c49d6..9dd37cb4 100644
--- a/email-templates/emails/test.tsx
+++ b/email-templates/emails/test.tsx
@@ -1,17 +1,16 @@
-import { Text } from "@react-email/components";
import { BaseTemplate } from "../components/base-template";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { Paragraph } from "../components/text";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
-interface TestEmailProps {
- logoURL: string;
- appName: string;
-}
-
-export const TestEmail = ({ logoURL, appName }: TestEmailProps) => (
-
-
- Your email setup is working correctly!
+export const TestEmail = (props: SharedProps) => (
+
+
+ Your email setup is working correctly!
);
diff --git a/email-templates/package.json b/email-templates/package.json
index 11e1d514..93f1b18e 100644
--- a/email-templates/package.json
+++ b/email-templates/package.json
@@ -9,16 +9,16 @@
"export": "email export"
},
"dependencies": {
- "@react-email/components": "1.0.12",
- "react": "^19.2.6",
- "react-dom": "^19.2.6"
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0",
+ "react-email": "6.9.5"
},
"devDependencies": {
"@react-email/preview-server": "5.2.10",
- "@types/node": "^25.9.0",
- "@types/react": "^19.2.14",
- "@types/react-dom": "^19.2.3",
- "react-email": "6.9.5",
- "tsx": "^4.22.2"
+ "@react-email/ui": "6.9.5",
+ "@types/node": "^26.6.2",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
+ "tsx": "^4.23.13"
}
}
diff --git a/email-templates/props.ts b/email-templates/props.ts
index 90cc06c5..c4b83a6f 100644
--- a/email-templates/props.ts
+++ b/email-templates/props.ts
@@ -1,9 +1,17 @@
-export const sharedPreviewProps = {
+export interface SharedProps {
+ logoURL: string;
+ appName: string;
+ appURL: string;
+}
+
+export const sharedPreviewProps: SharedProps = {
logoURL: "https://pocket-id.org/img/logo.png",
appName: "Pocket ID",
+ appURL: "https://id.example.com",
};
-export const sharedTemplateProps = {
+export const sharedTemplateProps: SharedProps = {
logoURL: "{{.LogoURL}}",
appName: "{{.AppName}}",
+ appURL: "{{.AppURL}}",
};
diff --git a/frontend/package.json b/frontend/package.json
index 8c5fc09a..ca63520a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "pocket-id-frontend",
- "version": "2.15.0",
+ "version": "2.16.0",
"private": true,
"type": "module",
"scripts": {
@@ -53,6 +53,8 @@
"prettier": "^3.8.3",
"prettier-plugin-svelte": "^3.5.2",
"prettier-plugin-tailwindcss": "^0.8.0",
+ "rollup": "^4.63.3",
+ "rollup-plugin-sbom": "4.0.0",
"shadcn-svelte": "^1.3.0",
"svelte": "^5.55.8",
"svelte-check": "^4.4.8",
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 466871aa..75197930 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,13 +1,23 @@
import { paraglideVitePlugin } from '@inlang/paraglide-js';
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
-import { defineConfig } from 'vite';
+import sbom from 'rollup-plugin-sbom';
+import { defineConfig, type Plugin } from 'vite';
export default defineConfig(() => {
+ const frontendSbom = sbom({
+ includeWellKnown: false,
+ outDir: 'cyclonedx',
+ outFilename: 'frontend.cdx',
+ saveTimestamp: false
+ }) as Plugin;
+ frontendSbom.applyToEnvironment = (environment) => environment.name === 'client';
+
return {
plugins: [
sveltekit(),
tailwindcss(),
+ frontendSbom,
paraglideVitePlugin({
project: './project.inlang',
outdir: './src/lib/paraglide',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 87f71e8c..0254a45d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,33 +17,33 @@ importers:
email-templates:
dependencies:
- '@react-email/components':
- specifier: 1.0.12
- version: 1.0.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
react:
- specifier: ^19.2.6
+ specifier: ^19.3.0
version: 19.3.0
react-dom:
- specifier: ^19.2.6
+ specifier: ^19.3.0
version: 19.3.0(react@19.3.0)
- devDependencies:
- '@react-email/preview-server':
- specifier: 5.2.10
- version: 5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
- '@types/node':
- specifier: ^25.9.0
- version: 25.9.6
- '@types/react':
- specifier: ^19.2.14
- version: 19.3.0
- '@types/react-dom':
- specifier: ^19.2.3
- version: 19.3.0(@types/react@19.3.0)
react-email:
specifier: 6.9.5
version: 6.9.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ devDependencies:
+ '@react-email/preview-server':
+ specifier: 5.2.10
+ version: 5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ '@react-email/ui':
+ specifier: 6.9.5
+ version: 6.9.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ '@types/node':
+ specifier: ^26.6.2
+ version: 26.6.2
+ '@types/react':
+ specifier: ^19.3.0
+ version: 19.3.0
+ '@types/react-dom':
+ specifier: ^19.3.0
+ version: 19.3.0(@types/react@19.3.0)
tsx:
- specifier: ^4.22.2
+ specifier: ^4.23.13
version: 4.23.13
frontend:
@@ -160,6 +160,12 @@ importers:
prettier-plugin-tailwindcss:
specifier: ^0.8.0
version: 0.8.1(prettier-plugin-svelte@3.5.2(prettier@3.9.6)(svelte@5.57.0(@typescript-eslint/types@8.70.0)))(prettier@3.9.6)
+ rollup:
+ specifier: ^4.63.3
+ version: 4.63.3
+ rollup-plugin-sbom:
+ specifier: 4.0.0
+ version: 4.0.0(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(rolldown@1.2.8)(rollup@4.63.3)(vite@8.3.0(@types/node@25.9.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))
shadcn-svelte:
specifier: ^1.3.0
version: 1.6.1(svelte@5.57.0(@typescript-eslint/types@8.70.0))
@@ -273,159 +279,342 @@ packages:
'@cacheable/utils@2.5.0':
resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
+ '@cyclonedx/cyclonedx-library@10.1.0':
+ resolution: {integrity: sha512-4yq0KTQIA32UHJwFMDeY5xj2asp3Mk5lzx4JRVXLOb0YE8w1KeR61c1m/gJ4sjMXpiiEDfaITVQu8BLUuy7t3A==}
+ engines: {node: '>=20.18.0'}
+ peerDependencies:
+ ajv: ^8.12.0
+ ajv-formats: ^3.0.1
+ ajv-formats-draft2019: ^1.6.1
+ libxmljs2: ^0.35||^0.37
+ packageurl-js: '*'
+ spdx-expression-parse: '*'
+ xmlbuilder2: ^3.0.2||^4.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+ ajv-formats:
+ optional: true
+ ajv-formats-draft2019:
+ optional: true
+ libxmljs2:
+ optional: true
+ packageurl-js:
+ optional: true
+ spdx-expression-parse:
+ optional: true
+ xmlbuilder2:
+ optional: true
+
'@emnapi/runtime@1.11.3':
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
+ '@esbuild/aix-ppc64@0.28.1':
+ resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
'@esbuild/aix-ppc64@0.28.2':
resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
+ '@esbuild/android-arm64@0.28.1':
+ resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm64@0.28.2':
resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm@0.28.1':
+ resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-arm@0.28.2':
resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
+ '@esbuild/android-x64@0.28.1':
+ resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/android-x64@0.28.2':
resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
+ '@esbuild/darwin-arm64@0.28.1':
+ resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-arm64@0.28.2':
resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-x64@0.28.1':
+ resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.28.2':
resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
+ '@esbuild/freebsd-arm64@0.28.1':
+ resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-arm64@0.28.2':
resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.28.1':
+ resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.28.2':
resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
+ '@esbuild/linux-arm64@0.28.1':
+ resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm64@0.28.2':
resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm@0.28.1':
+ resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-arm@0.28.2':
resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
+ '@esbuild/linux-ia32@0.28.1':
+ resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-ia32@0.28.2':
resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.28.1':
+ resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.28.2':
resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-mips64el@0.28.1':
+ resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.28.2':
resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-ppc64@0.28.1':
+ resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.28.2':
resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-riscv64@0.28.1':
+ resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.28.2':
resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-s390x@0.28.1':
+ resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-s390x@0.28.2':
resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-x64@0.28.1':
+ resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/linux-x64@0.28.2':
resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
+ '@esbuild/netbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
'@esbuild/netbsd-arm64@0.28.2':
resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.28.1':
+ resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.28.2':
resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
+ '@esbuild/openbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
'@esbuild/openbsd-arm64@0.28.2':
resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.28.1':
+ resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.28.2':
resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
+ '@esbuild/openharmony-arm64@0.28.1':
+ resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
'@esbuild/openharmony-arm64@0.28.2':
resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
+ '@esbuild/sunos-x64@0.28.1':
+ resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/sunos-x64@0.28.2':
resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
+ '@esbuild/win32-arm64@0.28.1':
+ resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-arm64@0.28.2':
resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-ia32@0.28.1':
+ resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-ia32@0.28.2':
resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-x64@0.28.1':
+ resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@esbuild/win32-x64@0.28.2':
resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
engines: {node: '>=18'}
@@ -761,6 +950,13 @@ packages:
peerDependencies:
svelte: ^5
+ '@napi-rs/lzma-linux-x64-gnu@1.5.1':
+ resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
'@next/env@16.3.5':
resolution: {integrity: sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg==}
@@ -902,129 +1098,10 @@ packages:
'@poppinss/macroable@1.1.2':
resolution: {integrity: sha512-FAVBRzzWhYP5mA3lCwLH1A0fKBqq5anyjGet90Z81aRK5c/+LTGUE1zJhZrErjaenBSOOI9BVUs3WVmotneFQA==}
- '@react-email/body@0.3.0':
- resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/button@0.2.1':
- resolution: {integrity: sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/code-block@0.2.1':
- resolution: {integrity: sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/code-inline@0.0.6':
- resolution: {integrity: sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/column@0.0.14':
- resolution: {integrity: sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/components@1.0.12':
- resolution: {integrity: sha512-tH18JhPDWgE+3jnYkzyB6ZrZdfNnEsFe4PwmuXmlOw4NGIysP8wPY5aXZg++pTG9qUabXg1nzX/FGHGkObH8xQ==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/container@0.0.16':
- resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/font@0.0.10':
- resolution: {integrity: sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/head@0.0.13':
- resolution: {integrity: sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/heading@0.0.16':
- resolution: {integrity: sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/hr@0.0.12':
- resolution: {integrity: sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/html@0.0.12':
- resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/img@0.0.12':
- resolution: {integrity: sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/link@0.0.13':
- resolution: {integrity: sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/markdown@0.0.18':
- resolution: {integrity: sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
'@react-email/preview-server@5.2.10':
resolution: {integrity: sha512-cYi21KF+Z/HGXT8RpkQMNFFubBafxyoB9Hn/wrslfDNtdoews2MdsDo6XXKkZvDTRG9SxQN3HGk4v4aoQZc20g==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- '@react-email/preview@0.0.14':
- resolution: {integrity: sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/render@2.0.6':
- resolution: {integrity: sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog==}
- engines: {node: '>=20.0.0'}
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
-
'@react-email/render@2.1.0':
resolution: {integrity: sha512-F+zE3O6d6sW6Aj2UjvZAA17R7tJKM7kcq2mgV6k4HCT8jeLLFaVP2txMtH1lgqYFRMZ0Gxsd37q2PRyiXLXXxA==}
engines: {node: '>=20.0.0'}
@@ -1032,65 +1109,8 @@ packages:
react: ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
- '@react-email/row@0.0.13':
- resolution: {integrity: sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/section@0.0.17':
- resolution: {integrity: sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/tailwind@2.0.7':
- resolution: {integrity: sha512-kGw80weVFXikcnCXbigTGXGWQ0MRCSYNCudcdkWxebkWYd0FG6/NPoN3V1p/u68/4+NxZwYPVi2fhnp0x23HdA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- '@react-email/body': '>=0'
- '@react-email/button': '>=0'
- '@react-email/code-block': '>=0'
- '@react-email/code-inline': '>=0'
- '@react-email/container': '>=0'
- '@react-email/heading': '>=0'
- '@react-email/hr': '>=0'
- '@react-email/img': '>=0'
- '@react-email/link': '>=0'
- '@react-email/preview': '>=0'
- '@react-email/text': '>=0'
- react: ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@react-email/body':
- optional: true
- '@react-email/button':
- optional: true
- '@react-email/code-block':
- optional: true
- '@react-email/code-inline':
- optional: true
- '@react-email/container':
- optional: true
- '@react-email/heading':
- optional: true
- '@react-email/hr':
- optional: true
- '@react-email/img':
- optional: true
- '@react-email/link':
- optional: true
- '@react-email/preview':
- optional: true
-
- '@react-email/text@0.1.6':
- resolution: {integrity: sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
+ '@react-email/ui@6.9.5':
+ resolution: {integrity: sha512-UvE0yeSQCvdBm9BoEgvZ5vBi8abk02GbRE+4RQjjtxozy66D/O1GbUgK9RdB26+7EqQdOxdwkGLq5CMCiRO96A==}
'@rolldown/binding-android-arm-eabi@1.2.8':
resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==}
@@ -1191,6 +1211,144 @@ packages:
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+ '@rollup/rollup-android-arm-eabi@4.63.3':
+ resolution: {integrity: sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.63.3':
+ resolution: {integrity: sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.63.3':
+ resolution: {integrity: sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.63.3':
+ resolution: {integrity: sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.63.3':
+ resolution: {integrity: sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.63.3':
+ resolution: {integrity: sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.63.3':
+ resolution: {integrity: sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.63.3':
+ resolution: {integrity: sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-arm64-gnu@4.63.3':
+ resolution: {integrity: sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm64-musl@4.63.3':
+ resolution: {integrity: sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-loong64-gnu@4.63.3':
+ resolution: {integrity: sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-loong64-musl@4.63.3':
+ resolution: {integrity: sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.63.3':
+ resolution: {integrity: sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-ppc64-musl@4.63.3':
+ resolution: {integrity: sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.63.3':
+ resolution: {integrity: sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-riscv64-musl@4.63.3':
+ resolution: {integrity: sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-s390x-gnu@4.63.3':
+ resolution: {integrity: sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-gnu@4.63.3':
+ resolution: {integrity: sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-musl@4.63.3':
+ resolution: {integrity: sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-openbsd-x64@4.63.3':
+ resolution: {integrity: sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.63.3':
+ resolution: {integrity: sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.63.3':
+ resolution: {integrity: sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.63.3':
+ resolution: {integrity: sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.63.3':
+ resolution: {integrity: sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.63.3':
+ resolution: {integrity: sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==}
+ cpu: [x64]
+ os: [win32]
+
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
@@ -1367,6 +1525,9 @@ packages:
'@types/node@25.9.6':
resolution: {integrity: sha512-JR6Q/PV5DKFvjrGFVqQJdeG0qvsqQQLDa3TzFrqVwhqRXqwNaxPo2KYCtCQXpOdIulCouKwT7a6in9nFthBAzw==}
+ '@types/node@26.6.2':
+ resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==}
+
'@types/qrcode@1.5.6':
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
@@ -1792,6 +1953,11 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
+ esbuild@0.28.1:
+ resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
esbuild@0.28.2:
resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
engines: {node: '>=18'}
@@ -2022,6 +2188,10 @@ packages:
hookified@2.2.0:
resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==}
+ hosted-git-info@10.1.1:
+ resolution: {integrity: sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==}
+ engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
+
html-to-text@9.0.5:
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
engines: {node: '>=14'}
@@ -2428,6 +2598,10 @@ packages:
node-fetch-native@1.6.7:
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
+ normalize-package-data@9.0.0:
+ resolution: {integrity: sha512-la34cHOCAT6itNOYCvPi40XP8r0y6OHuNkooOfnfFUHdgy5MBw665qq5xsuS02AEC7WPQvhwvb7E9PI1gipplw==}
+ engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
+
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -2473,6 +2647,9 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
+ packageurl-js@2.0.1:
+ resolution: {integrity: sha512-N5ixXjzTy4QDQH0Q9YFjqIWd6zH6936Djpl2m9QNFmDv5Fum8q8BjkpAcHNMzOFE0IwQrFhJWex3AN6kS0OSwg==}
+
parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
@@ -2697,6 +2874,29 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
+ rollup-plugin-sbom@4.0.0:
+ resolution: {integrity: sha512-WwAq3ZZRv4He27yyExeV8WWZh4QeSqVnIahpMBGfsDX97bjDh2aBgbFLWp1COuYiBP9iHjeB3oaPMk8V8Dmpjg==}
+ engines: {node: '>=22.9.0'}
+ peerDependencies:
+ rolldown: ^1
+ rollup: ^4
+ vite: ^6 || ^7 || ^8
+ xmlbuilder2: ^3 || ^4
+ peerDependenciesMeta:
+ rolldown:
+ optional: true
+ rollup:
+ optional: true
+ vite:
+ optional: true
+ xmlbuilder2:
+ optional: true
+
+ rollup@4.63.3:
+ resolution: {integrity: sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
runed@0.23.4:
resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==}
peerDependencies:
@@ -2799,6 +2999,21 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ spdx-correct@3.2.0:
+ resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
+
+ spdx-exceptions@2.5.0:
+ resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
+
+ spdx-expression-parse@3.0.1:
+ resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
+
+ spdx-expression-parse@5.0.0:
+ resolution: {integrity: sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ==}
+
+ spdx-license-ids@3.0.24:
+ resolution: {integrity: sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -2996,6 +3211,9 @@ packages:
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
+ undici-types@8.9.0:
+ resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
+
unplugin@2.3.11:
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
engines: {node: '>=18.12.0'}
@@ -3029,6 +3247,9 @@ packages:
typescript:
optional: true
+ validate-npm-package-license@3.0.4:
+ resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+
validator@13.15.35:
resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==}
engines: {node: '>= 0.10'}
@@ -3240,86 +3461,171 @@ snapshots:
hashery: 1.5.1
keyv: 5.6.0
+ '@cyclonedx/cyclonedx-library@10.1.0(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(packageurl-js@2.0.1)(spdx-expression-parse@5.0.0)':
+ optionalDependencies:
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
+ packageurl-js: 2.0.1
+ spdx-expression-parse: 5.0.0
+
'@emnapi/runtime@1.11.3':
dependencies:
tslib: 2.8.1
optional: true
+ '@esbuild/aix-ppc64@0.28.1':
+ optional: true
+
'@esbuild/aix-ppc64@0.28.2':
optional: true
+ '@esbuild/android-arm64@0.28.1':
+ optional: true
+
'@esbuild/android-arm64@0.28.2':
optional: true
+ '@esbuild/android-arm@0.28.1':
+ optional: true
+
'@esbuild/android-arm@0.28.2':
optional: true
+ '@esbuild/android-x64@0.28.1':
+ optional: true
+
'@esbuild/android-x64@0.28.2':
optional: true
+ '@esbuild/darwin-arm64@0.28.1':
+ optional: true
+
'@esbuild/darwin-arm64@0.28.2':
optional: true
+ '@esbuild/darwin-x64@0.28.1':
+ optional: true
+
'@esbuild/darwin-x64@0.28.2':
optional: true
+ '@esbuild/freebsd-arm64@0.28.1':
+ optional: true
+
'@esbuild/freebsd-arm64@0.28.2':
optional: true
+ '@esbuild/freebsd-x64@0.28.1':
+ optional: true
+
'@esbuild/freebsd-x64@0.28.2':
optional: true
+ '@esbuild/linux-arm64@0.28.1':
+ optional: true
+
'@esbuild/linux-arm64@0.28.2':
optional: true
+ '@esbuild/linux-arm@0.28.1':
+ optional: true
+
'@esbuild/linux-arm@0.28.2':
optional: true
+ '@esbuild/linux-ia32@0.28.1':
+ optional: true
+
'@esbuild/linux-ia32@0.28.2':
optional: true
+ '@esbuild/linux-loong64@0.28.1':
+ optional: true
+
'@esbuild/linux-loong64@0.28.2':
optional: true
+ '@esbuild/linux-mips64el@0.28.1':
+ optional: true
+
'@esbuild/linux-mips64el@0.28.2':
optional: true
+ '@esbuild/linux-ppc64@0.28.1':
+ optional: true
+
'@esbuild/linux-ppc64@0.28.2':
optional: true
+ '@esbuild/linux-riscv64@0.28.1':
+ optional: true
+
'@esbuild/linux-riscv64@0.28.2':
optional: true
+ '@esbuild/linux-s390x@0.28.1':
+ optional: true
+
'@esbuild/linux-s390x@0.28.2':
optional: true
+ '@esbuild/linux-x64@0.28.1':
+ optional: true
+
'@esbuild/linux-x64@0.28.2':
optional: true
+ '@esbuild/netbsd-arm64@0.28.1':
+ optional: true
+
'@esbuild/netbsd-arm64@0.28.2':
optional: true
+ '@esbuild/netbsd-x64@0.28.1':
+ optional: true
+
'@esbuild/netbsd-x64@0.28.2':
optional: true
+ '@esbuild/openbsd-arm64@0.28.1':
+ optional: true
+
'@esbuild/openbsd-arm64@0.28.2':
optional: true
+ '@esbuild/openbsd-x64@0.28.1':
+ optional: true
+
'@esbuild/openbsd-x64@0.28.2':
optional: true
+ '@esbuild/openharmony-arm64@0.28.1':
+ optional: true
+
'@esbuild/openharmony-arm64@0.28.2':
optional: true
+ '@esbuild/sunos-x64@0.28.1':
+ optional: true
+
'@esbuild/sunos-x64@0.28.2':
optional: true
+ '@esbuild/win32-arm64@0.28.1':
+ optional: true
+
'@esbuild/win32-arm64@0.28.2':
optional: true
+ '@esbuild/win32-ia32@0.28.1':
+ optional: true
+
'@esbuild/win32-ia32@0.28.2':
optional: true
+ '@esbuild/win32-x64@0.28.1':
+ optional: true
+
'@esbuild/win32-x64@0.28.2':
optional: true
@@ -3599,6 +3905,9 @@ snapshots:
dependencies:
svelte: 5.57.0(@typescript-eslint/types@8.70.0)
+ '@napi-rs/lzma-linux-x64-gnu@1.5.1':
+ optional: true
+
'@next/env@16.3.5': {}
'@next/swc-darwin-arm64@16.3.5':
@@ -3713,94 +4022,10 @@ snapshots:
'@poppinss/macroable@1.1.2':
optional: true
- '@react-email/body@0.3.0(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/button@0.2.1(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/code-block@0.2.1(react@19.3.0)':
- dependencies:
- prismjs: 1.30.0
- react: 19.3.0
-
- '@react-email/code-inline@0.0.6(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/column@0.0.14(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/components@1.0.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
- dependencies:
- '@react-email/body': 0.3.0(react@19.3.0)
- '@react-email/button': 0.2.1(react@19.3.0)
- '@react-email/code-block': 0.2.1(react@19.3.0)
- '@react-email/code-inline': 0.0.6(react@19.3.0)
- '@react-email/column': 0.0.14(react@19.3.0)
- '@react-email/container': 0.0.16(react@19.3.0)
- '@react-email/font': 0.0.10(react@19.3.0)
- '@react-email/head': 0.0.13(react@19.3.0)
- '@react-email/heading': 0.0.16(react@19.3.0)
- '@react-email/hr': 0.0.12(react@19.3.0)
- '@react-email/html': 0.0.12(react@19.3.0)
- '@react-email/img': 0.0.12(react@19.3.0)
- '@react-email/link': 0.0.13(react@19.3.0)
- '@react-email/markdown': 0.0.18(react@19.3.0)
- '@react-email/preview': 0.0.14(react@19.3.0)
- '@react-email/render': 2.0.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
- '@react-email/row': 0.0.13(react@19.3.0)
- '@react-email/section': 0.0.17(react@19.3.0)
- '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.3.0))(@react-email/button@0.2.1(react@19.3.0))(@react-email/code-block@0.2.1(react@19.3.0))(@react-email/code-inline@0.0.6(react@19.3.0))(@react-email/container@0.0.16(react@19.3.0))(@react-email/heading@0.0.16(react@19.3.0))(@react-email/hr@0.0.12(react@19.3.0))(@react-email/img@0.0.12(react@19.3.0))(@react-email/link@0.0.13(react@19.3.0))(@react-email/preview@0.0.14(react@19.3.0))(@react-email/text@0.1.6(react@19.3.0))(react@19.3.0)
- '@react-email/text': 0.1.6(react@19.3.0)
- react: 19.3.0
- transitivePeerDependencies:
- - react-dom
-
- '@react-email/container@0.0.16(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/font@0.0.10(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/head@0.0.13(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/heading@0.0.16(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/hr@0.0.12(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/html@0.0.12(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/img@0.0.12(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/link@0.0.13(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/markdown@0.0.18(react@19.3.0)':
- dependencies:
- marked: 15.0.12
- react: 19.3.0
-
- '@react-email/preview-server@5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
+ '@react-email/preview-server@5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
dependencies:
esbuild: 0.28.2
- next: 16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ next: 16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
transitivePeerDependencies:
- '@babel/core'
- '@opentelemetry/api'
@@ -3812,17 +4037,6 @@ snapshots:
- react-dom
- sass
- '@react-email/preview@0.0.14(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/render@2.0.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
- dependencies:
- html-to-text: 9.0.5
- prettier: 3.9.6
- react: 19.3.0
- react-dom: 19.3.0(react@19.3.0)
-
'@react-email/render@2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
dependencies:
entities: 4.5.0
@@ -3832,34 +4046,20 @@ snapshots:
react: 19.3.0
react-dom: 19.3.0(react@19.3.0)
- '@react-email/row@0.0.13(react@19.3.0)':
+ '@react-email/ui@6.9.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
dependencies:
- react: 19.3.0
-
- '@react-email/section@0.0.17(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.3.0))(@react-email/button@0.2.1(react@19.3.0))(@react-email/code-block@0.2.1(react@19.3.0))(@react-email/code-inline@0.0.6(react@19.3.0))(@react-email/container@0.0.16(react@19.3.0))(@react-email/heading@0.0.16(react@19.3.0))(@react-email/hr@0.0.12(react@19.3.0))(@react-email/img@0.0.12(react@19.3.0))(@react-email/link@0.0.13(react@19.3.0))(@react-email/preview@0.0.14(react@19.3.0))(@react-email/text@0.1.6(react@19.3.0))(react@19.3.0)':
- dependencies:
- '@react-email/text': 0.1.6(react@19.3.0)
- react: 19.3.0
- tailwindcss: 4.3.3
- optionalDependencies:
- '@react-email/body': 0.3.0(react@19.3.0)
- '@react-email/button': 0.2.1(react@19.3.0)
- '@react-email/code-block': 0.2.1(react@19.3.0)
- '@react-email/code-inline': 0.0.6(react@19.3.0)
- '@react-email/container': 0.0.16(react@19.3.0)
- '@react-email/heading': 0.0.16(react@19.3.0)
- '@react-email/hr': 0.0.12(react@19.3.0)
- '@react-email/img': 0.0.12(react@19.3.0)
- '@react-email/link': 0.0.13(react@19.3.0)
- '@react-email/preview': 0.0.14(react@19.3.0)
-
- '@react-email/text@0.1.6(react@19.3.0)':
- dependencies:
- react: 19.3.0
+ esbuild: 0.28.1
+ next: 16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ transitivePeerDependencies:
+ - '@babel/core'
+ - '@opentelemetry/api'
+ - '@playwright/test'
+ - '@types/node'
+ - babel-plugin-macros
+ - babel-plugin-react-compiler
+ - react
+ - react-dom
+ - sass
'@rolldown/binding-android-arm-eabi@1.2.8':
optional: true
@@ -3908,6 +4108,81 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {}
+ '@rollup/rollup-android-arm-eabi@4.63.3':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.63.3':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.63.3':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.63.3':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.63.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.63.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.63.3':
+ optional: true
+
'@selderee/plugin-htmlparser2@0.11.0':
dependencies:
domhandler: 5.0.3
@@ -4048,7 +4323,7 @@ snapshots:
'@types/cors@2.8.19':
dependencies:
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
'@types/esrecurse@4.3.1': {}
@@ -4060,6 +4335,10 @@ snapshots:
dependencies:
undici-types: 7.24.6
+ '@types/node@26.6.2':
+ dependencies:
+ undici-types: 8.9.0
+
'@types/qrcode@1.5.6':
dependencies:
'@types/node': 25.9.6
@@ -4077,7 +4356,7 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
'@typeschema/class-validator@0.3.0(@types/json-schema@7.0.15)(class-validator@0.14.4)':
dependencies:
@@ -4486,7 +4765,7 @@ snapshots:
engine.io@6.6.10:
dependencies:
'@types/cors': 2.8.19
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
'@types/ws': 8.18.1
accepts: 1.3.8
cookie: 2.0.1
@@ -4523,6 +4802,35 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.4
+ esbuild@0.28.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.1
+ '@esbuild/android-arm': 0.28.1
+ '@esbuild/android-arm64': 0.28.1
+ '@esbuild/android-x64': 0.28.1
+ '@esbuild/darwin-arm64': 0.28.1
+ '@esbuild/darwin-x64': 0.28.1
+ '@esbuild/freebsd-arm64': 0.28.1
+ '@esbuild/freebsd-x64': 0.28.1
+ '@esbuild/linux-arm': 0.28.1
+ '@esbuild/linux-arm64': 0.28.1
+ '@esbuild/linux-ia32': 0.28.1
+ '@esbuild/linux-loong64': 0.28.1
+ '@esbuild/linux-mips64el': 0.28.1
+ '@esbuild/linux-ppc64': 0.28.1
+ '@esbuild/linux-riscv64': 0.28.1
+ '@esbuild/linux-s390x': 0.28.1
+ '@esbuild/linux-x64': 0.28.1
+ '@esbuild/netbsd-arm64': 0.28.1
+ '@esbuild/netbsd-x64': 0.28.1
+ '@esbuild/openbsd-arm64': 0.28.1
+ '@esbuild/openbsd-x64': 0.28.1
+ '@esbuild/openharmony-arm64': 0.28.1
+ '@esbuild/sunos-x64': 0.28.1
+ '@esbuild/win32-arm64': 0.28.1
+ '@esbuild/win32-ia32': 0.28.1
+ '@esbuild/win32-x64': 0.28.1
+
esbuild@0.28.2:
optionalDependencies:
'@esbuild/aix-ppc64': 0.28.2
@@ -4787,6 +5095,10 @@ snapshots:
hookified@2.2.0: {}
+ hosted-git-info@10.1.1:
+ dependencies:
+ lru-cache: 11.5.2
+
html-to-text@9.0.5:
dependencies:
'@selderee/plugin-htmlparser2': 0.11.0
@@ -5069,7 +5381,7 @@ snapshots:
negotiator@0.6.3: {}
- next@16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0):
+ next@16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0):
dependencies:
'@next/env': 16.3.5
'@swc/helpers': 0.5.23
@@ -5090,7 +5402,7 @@ snapshots:
'@next/swc-win32-x64-msvc': 16.3.5
'@opentelemetry/api': 1.9.1
'@playwright/test': 1.63.0
- sharp: 0.35.4(@types/node@25.9.6)
+ sharp: 0.35.4(@types/node@26.6.2)
transitivePeerDependencies:
- '@babel/core'
- '@types/node'
@@ -5098,6 +5410,12 @@ snapshots:
node-fetch-native@1.6.7: {}
+ normalize-package-data@9.0.0:
+ dependencies:
+ hosted-git-info: 10.1.1
+ semver: 7.8.5
+ validate-npm-package-license: 3.0.4
+
normalize-path@3.0.0: {}
normalize-url@8.1.1:
@@ -5140,6 +5458,8 @@ snapshots:
p-try@2.2.0: {}
+ packageurl-js@2.0.1: {}
+
parseley@0.12.1:
dependencies:
leac: 0.6.0
@@ -5319,6 +5639,54 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.2.8
'@rolldown/binding-win32-x64-msvc': 1.2.8
+ rollup-plugin-sbom@4.0.0(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(rolldown@1.2.8)(rollup@4.63.3)(vite@8.3.0(@types/node@25.9.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)):
+ dependencies:
+ '@cyclonedx/cyclonedx-library': 10.1.0(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(packageurl-js@2.0.1)(spdx-expression-parse@5.0.0)
+ normalize-package-data: 9.0.0
+ packageurl-js: 2.0.1
+ spdx-expression-parse: 5.0.0
+ optionalDependencies:
+ rolldown: 1.2.8
+ rollup: 4.63.3
+ vite: 8.3.0(@types/node@25.9.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)
+ transitivePeerDependencies:
+ - ajv
+ - ajv-formats
+ - ajv-formats-draft2019
+ - libxmljs2
+
+ rollup@4.63.3:
+ dependencies:
+ '@types/estree': 1.0.9
+ optionalDependencies:
+ '@napi-rs/lzma-linux-x64-gnu': 1.5.1
+ '@rollup/rollup-android-arm-eabi': 4.63.3
+ '@rollup/rollup-android-arm64': 4.63.3
+ '@rollup/rollup-darwin-arm64': 4.63.3
+ '@rollup/rollup-darwin-x64': 4.63.3
+ '@rollup/rollup-freebsd-arm64': 4.63.3
+ '@rollup/rollup-freebsd-x64': 4.63.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.63.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.63.3
+ '@rollup/rollup-linux-arm64-gnu': 4.63.3
+ '@rollup/rollup-linux-arm64-musl': 4.63.3
+ '@rollup/rollup-linux-loong64-gnu': 4.63.3
+ '@rollup/rollup-linux-loong64-musl': 4.63.3
+ '@rollup/rollup-linux-ppc64-gnu': 4.63.3
+ '@rollup/rollup-linux-ppc64-musl': 4.63.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.63.3
+ '@rollup/rollup-linux-riscv64-musl': 4.63.3
+ '@rollup/rollup-linux-s390x-gnu': 4.63.3
+ '@rollup/rollup-linux-x64-gnu': 4.63.3
+ '@rollup/rollup-linux-x64-musl': 4.63.3
+ '@rollup/rollup-openbsd-x64': 4.63.3
+ '@rollup/rollup-openharmony-arm64': 4.63.3
+ '@rollup/rollup-win32-arm64-msvc': 4.63.3
+ '@rollup/rollup-win32-ia32-msvc': 4.63.3
+ '@rollup/rollup-win32-x64-gnu': 4.63.3
+ '@rollup/rollup-win32-x64-msvc': 4.63.3
+ fsevents: 2.3.3
+
runed@0.23.4(svelte@5.57.0(@typescript-eslint/types@8.70.0)):
dependencies:
esm-env: 1.2.2
@@ -5377,7 +5745,7 @@ snapshots:
svelte: 5.57.0(@typescript-eslint/types@8.70.0)
tailwind-merge: 3.6.0
- sharp@0.35.4(@types/node@25.9.6):
+ sharp@0.35.4(@types/node@26.6.2):
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
@@ -5408,7 +5776,7 @@ snapshots:
'@img/sharp-win32-arm64': 0.35.4
'@img/sharp-win32-ia32': 0.35.4
'@img/sharp-win32-x64': 0.35.4
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
optional: true
shebang-command@2.0.0:
@@ -5457,6 +5825,25 @@ snapshots:
source-map-js@1.2.1: {}
+ spdx-correct@3.2.0:
+ dependencies:
+ spdx-expression-parse: 3.0.1
+ spdx-license-ids: 3.0.24
+
+ spdx-exceptions@2.5.0: {}
+
+ spdx-expression-parse@3.0.1:
+ dependencies:
+ spdx-exceptions: 2.5.0
+ spdx-license-ids: 3.0.24
+
+ spdx-expression-parse@5.0.0:
+ dependencies:
+ spdx-exceptions: 2.5.0
+ spdx-license-ids: 3.0.24
+
+ spdx-license-ids@3.0.24: {}
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -5673,6 +6060,8 @@ snapshots:
undici-types@7.24.6: {}
+ undici-types@8.9.0: {}
+
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -5699,6 +6088,11 @@ snapshots:
optionalDependencies:
typescript: 6.0.3
+ validate-npm-package-license@3.0.4:
+ dependencies:
+ spdx-correct: 3.2.0
+ spdx-expression-parse: 3.0.1
+
validator@13.15.35:
optional: true
diff --git a/scripts/development/create-release.sh b/scripts/development/create-release.sh
deleted file mode 100755
index 0692236b..00000000
--- a/scripts/development/create-release.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/bash
-
-# Check if the script is being run from the root of the project
-if [ ! -f .version ] || [ ! -f frontend/package.json ] || [ ! -f CHANGELOG.md ]; then
- echo "Error: This script must be run from the root of the project."
- exit 1
-fi
-
-# Check if git cliff is installed
-if ! command -v git-cliff &>/dev/null; then
- echo "Error: git cliff is not installed. Please install it from https://git-cliff.org/docs/installation."
- exit 1
-fi
-
-# Check if GitHub CLI is installed
-if ! command -v gh &>/dev/null; then
- echo "Error: GitHub CLI (gh) is not installed. Please install it and authenticate using 'gh auth login'."
- exit 1
-fi
-
-# Check if Snyk CLI is installed
-if ! command -v snyk &>/dev/null; then
- echo "Error: Snyk CLI is not installed. Please install it and authenticate using 'snyk auth'."
- exit 1
-fi
-
-# Check if we're on the main branch
-if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then
- echo "Error: This script must be run on the main branch."
- exit 1
-fi
-
-# Parse command line arguments
-FORCE_MAJOR=false
-for arg in "$@"; do
- case $arg in
- --major)
- FORCE_MAJOR=true
- shift
- ;;
- *)
- # Unknown option
- ;;
- esac
-done
-
-BUMP_ARGUMENTS=(--bumped-version --unreleased --offline)
-if [ "$FORCE_MAJOR" == true ]; then
- BUMP_ARGUMENTS+=(--bump major)
-fi
-
-# Calculate the next version from the unreleased conventional commits
-if ! NEW_VERSION=$(git cliff "${BUMP_ARGUMENTS[@]}"); then
- echo "Error: Could not calculate the next version."
- exit 1
-fi
-NEW_VERSION=${NEW_VERSION#v}
-
-if [ "$NEW_VERSION" == "$(cat .version)" ]; then
- echo "No commits requiring a version bump found since the latest release. No new release will be created."
- exit 0
-fi
-
-echo "Running Snyk dependency scan..."
-if ! snyk test --all-projects --dev --detection-depth=3 --strict-out-of-sync=false --severity-threshold=high; then
- echo "Error: Snyk detected high-severity vulnerable dependencies. Release creation aborted."
- exit 1
-fi
-
-# Confirm release creation
-read -p "This will create a new release with version $NEW_VERSION. Do you want to proceed? (y/n) " CONFIRM
-if [[ "$CONFIRM" != "y" ]]; then
- echo "Release process canceled."
- exit 1
-fi
-
-# Update the .version file with the new version
-echo $NEW_VERSION >.version
-git add .version
-
-# Update version in frontend/package.json
-jq --arg new_version "$NEW_VERSION" '.version = $new_version' frontend/package.json >frontend/package_tmp.json && mv frontend/package_tmp.json frontend/package.json
-pnpm --dir frontend exec prettier --write package.json
-git add frontend/package.json
-
-# Generate changelog
-echo "Generating changelog..."
-git cliff --github-token=$(gh auth token) --prepend CHANGELOG.md --tag "v$NEW_VERSION" --unreleased
-git add CHANGELOG.md
-
-# Commit the changes with the new version
-git commit -m "release: $NEW_VERSION"
-
-# Create a Git tag with the new version
-git tag "v$NEW_VERSION"
-
-# Push the commit and the tag to the repository
-git push
-git push --tags
-
-# Extract the changelog content for the latest release
-echo "Extracting changelog content for version $NEW_VERSION..."
-CHANGELOG=$(awk '/^## v[0-9]/ { if (found) exit; found=1; next } found' CHANGELOG.md)
-
-if [ -z "$CHANGELOG" ]; then
- echo "Error: Could not extract changelog for version $NEW_VERSION."
- exit 1
-fi
-
-# Create the release on GitHub
-echo "Creating GitHub release..."
-gh release create "v$NEW_VERSION" --title "v$NEW_VERSION" --notes "$CHANGELOG" --draft
-
-if [ $? -eq 0 ]; then
- echo "GitHub release created successfully."
-else
- echo "Error: Failed to create GitHub release."
- exit 1
-fi
-
-echo "Release process complete. New version: $NEW_VERSION"
diff --git a/scripts/development/generate-binary-sbom.sh b/scripts/development/generate-binary-sbom.sh
new file mode 100755
index 00000000..71fd5ce3
--- /dev/null
+++ b/scripts/development/generate-binary-sbom.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+set -eu
+
+artifact_path=$1
+document_path=$2
+source_name=$3
+source_version=$4
+frontend_sbom_path="../.tmp/frontend.cdx.json"
+
+work_dir=$(mktemp -d "${TMPDIR:-/tmp}/pocket-id-binary-sbom.XXXXXX")
+trap 'rm -rf "$work_dir"' EXIT
+
+cp "$artifact_path" "$work_dir/$(basename "$artifact_path")"
+cp "$frontend_sbom_path" "$work_dir/frontend.cdx.json"
+
+syft "dir:$work_dir" \
+ --select-catalogers "+sbom-cataloger" \
+ --source-name "$source_name" \
+ --source-version "$source_version" \
+ --output "spdx-json=$document_path" \
+ --enrich all