diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6bb2b28b..ae7edc328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,6 @@ jobs: secrets: PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} with: - APT_PACKAGES: mkcert DRY_RUN: true build: name: Build @@ -37,6 +36,8 @@ jobs: permissions: packages: write with: + BUILD_ADD_HOSTS: | + app.localhost=127.0.0.1 BUILD_ARGUMENTS: ${{ (needs.release_semantic_dry.outputs.new_release_version != null) && format('RELEASE_NAME={0}', needs.release_semantic_dry.outputs.new_release_version) || '' }} TAG: ${{ (needs.release_semantic_dry.outputs.new_release_version != null) && needs.release_semantic_dry.outputs.new_release_version || '' }} secrets: @@ -50,5 +51,3 @@ jobs: id-token: write secrets: PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - with: - APT_PACKAGES: mkcert diff --git a/Dockerfile b/Dockerfile index af1957dda..d2ac93eec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -89,8 +89,8 @@ RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN,env=SENTRY_AUTH_TOKEN \ # FROM prepare AS build-static -# ARG NUXT_PUBLIC_SITE_URL=https://localhost:3002 -# ENV NUXT_PUBLIC_SITE_URL=${NUXT_PUBLIC_SITE_URL} +# ARG NUXT_PUBLIC_I18N_BASE_URL=https://localhost:3002 +# ENV NUXT_PUBLIC_I18N_BASE_URL=${NUXT_PUBLIC_I18N_BASE_URL} # ENV NODE_ENV=production # RUN pnpm --dir src run build:static diff --git a/src/config/modules/index.ts b/src/config/modules/index.ts index 54b6dc2b2..2a0393b83 100644 --- a/src/config/modules/index.ts +++ b/src/config/modules/index.ts @@ -20,10 +20,11 @@ export const modulesConfig: ReturnType = { fonts: { families: [ { + formats: ['ttf'], + global: true, name: 'Raleway', + provider: 'fontsource', weights: [400, 700], - global: true, - formats: ['ttf'], }, ], }, diff --git a/src/node/static/environment.ts b/src/node/static/environment.ts index b42ece7a4..886863be7 100644 --- a/src/node/static/environment.ts +++ b/src/node/static/environment.ts @@ -1,11 +1,11 @@ export const IS_IN_PRODUCTION = process.env.NODE_ENV === 'production' -export const IS_IN_STACK = !!process.env.NUXT_PUBLIC_SITE_URL +export const IS_IN_STACK = !!process.env.NUXT_PUBLIC_I18N_BASE_URL export const IS_IN_FRONTEND_DEVELOPMENT = !IS_IN_PRODUCTION && !IS_IN_STACK export const IS_NITRO_OPENAPI_ENABLED = !!process.env.NUXT_IS_NITRO_OPENAPI_ENABLED || false export const NUXT_PUBLIC_VIO_ENVIRONMENT = process.env.NODE_ENV export const SITE_URL = - process.env.NUXT_PUBLIC_SITE_URL || + process.env.NUXT_PUBLIC_I18N_BASE_URL || `https://${process.env.HOST || 'app.localhost'}:${process.env.PORT || '3000'}` export const SITE_URL_TYPED = new URL(SITE_URL) diff --git a/src/server/utils/constants.ts b/src/server/utils/constants.ts index ce5073cea..d90b1a42f 100644 --- a/src/server/utils/constants.ts +++ b/src/server/utils/constants.ts @@ -11,7 +11,7 @@ export const GET_CSP = ({ }) => { const domainTldPort = IS_IN_FRONTEND_DEVELOPMENT ? PRODUCTION_HOST - : siteUrl.host + : getRootHost(siteUrl.host) return defu( // if (isHttps(event.node.req)) { @@ -22,7 +22,7 @@ export const GET_CSP = ({ // app 'connect-src': [ 'blob:', // vue-advanced-cropper - `https://${domainTldPort}`, // `/api` requests + `https://app.${domainTldPort}`, // `/api` requests `https://postgraphile.${domainTldPort}`, // backend requests `https://tusd.${domainTldPort}`, // image upload requests 'https://nominatim.openstreetmap.org/search', // map's geocoder diff --git a/src/shared/utils/networking.ts b/src/shared/utils/networking.ts index 53b124452..ba3b48473 100644 --- a/src/shared/utils/networking.ts +++ b/src/shared/utils/networking.ts @@ -13,6 +13,18 @@ export const getHost = (event: H3Event) => { export { getIsSecure } from '~~/node/static' +export const getRootHost = (host: string) => { + const hostParts = host.split('.') + const hostPartsLast = hostParts[hostParts.length - 1] + + if (hostPartsLast && /^localhost(:[0-9]+)?$/.test(hostPartsLast)) + return hostPartsLast + + if (hostParts.length === 1) return hostParts[0] + + return `${hostParts[hostParts.length - 2]}.${hostPartsLast}` +} + export const getServiceHref = ({ host, isSsr = true, diff --git a/tests/e2e/fixtures/appTest.ts b/tests/e2e/fixtures/appTest.ts index 3519f70a1..d7deb15f3 100644 --- a/tests/e2e/fixtures/appTest.ts +++ b/tests/e2e/fixtures/appTest.ts @@ -45,19 +45,19 @@ export const appTest = test.extend<{ defaultPage: async ({ page, context }, use) => { await context.addCookies([ { - domain: 'localhost', + domain: 'app.localhost', name: TESTING_COOKIE_NAME, path: '/', value: 'true', }, { - domain: 'localhost', + domain: 'app.localhost', name: TIMEZONE_COOKIE_NAME, path: '/', value: TIMEZONE_DEFAULT, }, { - domain: 'localhost', + domain: 'app.localhost', name: COOKIE_CONTROL_CONSENT_COOKIE_NAME, path: '/', value: COOKIE_CONTROL_CONSENT_COOKIE_DEFAULT_VALUE, diff --git a/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Chrome-linux.json b/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Chrome-linux.json index 3916fec0d..46820ba27 100644 --- a/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Chrome-linux.json +++ b/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Chrome-linux.json @@ -1 +1 @@ -{"@context":"https://schema.org","@graph":[{"@id":"https://localhost:3001/#website","@type":"WebSite","description":"Find events, guests and friends 💙❤️💚","inLanguage":"en","name":"Vibetype","url":"https://localhost:3001/","workTranslation":{"@id":"https://localhost:3001/de#website"}},{"@id":"https://localhost:3001/#webpage","@type":"WebPage","description":"Find events, guests and friends 💙❤️💚","name":"Vibetype","url":"https://localhost:3001/","isPartOf":{"@id":"https://localhost:3001/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://localhost:3001/"]}]}]} \ No newline at end of file +{"@context":"https://schema.org","@graph":[{"@id":"https://app.localhost:3001/#website","@type":"WebSite","description":"Find events, guests and friends 💙❤️💚","inLanguage":"en","name":"Vibetype","url":"https://app.localhost:3001/","workTranslation":{"@id":"https://app.localhost:3001/de#website"}},{"@id":"https://app.localhost:3001/#webpage","@type":"WebPage","description":"Find events, guests and friends 💙❤️💚","name":"Vibetype","url":"https://app.localhost:3001/","isPartOf":{"@id":"https://app.localhost:3001/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://app.localhost:3001/"]}]}]} \ No newline at end of file diff --git a/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Safari-linux.json b/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Safari-linux.json index 3916fec0d..46820ba27 100644 --- a/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Safari-linux.json +++ b/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-Mobile-Safari-linux.json @@ -1 +1 @@ -{"@context":"https://schema.org","@graph":[{"@id":"https://localhost:3001/#website","@type":"WebSite","description":"Find events, guests and friends 💙❤️💚","inLanguage":"en","name":"Vibetype","url":"https://localhost:3001/","workTranslation":{"@id":"https://localhost:3001/de#website"}},{"@id":"https://localhost:3001/#webpage","@type":"WebPage","description":"Find events, guests and friends 💙❤️💚","name":"Vibetype","url":"https://localhost:3001/","isPartOf":{"@id":"https://localhost:3001/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://localhost:3001/"]}]}]} \ No newline at end of file +{"@context":"https://schema.org","@graph":[{"@id":"https://app.localhost:3001/#website","@type":"WebSite","description":"Find events, guests and friends 💙❤️💚","inLanguage":"en","name":"Vibetype","url":"https://app.localhost:3001/","workTranslation":{"@id":"https://app.localhost:3001/de#website"}},{"@id":"https://app.localhost:3001/#webpage","@type":"WebPage","description":"Find events, guests and friends 💙❤️💚","name":"Vibetype","url":"https://app.localhost:3001/","isPartOf":{"@id":"https://app.localhost:3001/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://app.localhost:3001/"]}]}]} \ No newline at end of file diff --git a/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-chromium-linux.json b/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-chromium-linux.json index 3916fec0d..46820ba27 100644 --- a/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-chromium-linux.json +++ b/tests/e2e/specs/pages/root/index.spec.ts-snapshots/schema-org-graph-node-chromium-linux.json @@ -1 +1 @@ -{"@context":"https://schema.org","@graph":[{"@id":"https://localhost:3001/#website","@type":"WebSite","description":"Find events, guests and friends 💙❤️💚","inLanguage":"en","name":"Vibetype","url":"https://localhost:3001/","workTranslation":{"@id":"https://localhost:3001/de#website"}},{"@id":"https://localhost:3001/#webpage","@type":"WebPage","description":"Find events, guests and friends 💙❤️💚","name":"Vibetype","url":"https://localhost:3001/","isPartOf":{"@id":"https://localhost:3001/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://localhost:3001/"]}]}]} \ No newline at end of file +{"@context":"https://schema.org","@graph":[{"@id":"https://app.localhost:3001/#website","@type":"WebSite","description":"Find events, guests and friends 💙❤️💚","inLanguage":"en","name":"Vibetype","url":"https://app.localhost:3001/","workTranslation":{"@id":"https://app.localhost:3001/de#website"}},{"@id":"https://app.localhost:3001/#webpage","@type":"WebPage","description":"Find events, guests and friends 💙❤️💚","name":"Vibetype","url":"https://app.localhost:3001/","isPartOf":{"@id":"https://app.localhost:3001/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://app.localhost:3001/"]}]}]} \ No newline at end of file diff --git a/tests/e2e/specs/server/headers.spec.ts b/tests/e2e/specs/server/headers.spec.ts index 74fff555c..65455946a 100644 --- a/tests/e2e/specs/server/headers.spec.ts +++ b/tests/e2e/specs/server/headers.spec.ts @@ -8,7 +8,7 @@ test.describe('headers middleware', () => { const HEADERS = { 'access-control-allow-origin': '*', - 'Content-Security-Policy': `base-uri 'none'; default-src 'none'; connect-src blob: https://localhost:3001 https://postgraphile.localhost:3001 https://tusd.localhost:3001 https://nominatim.openstreetmap.org/search https://cloudflareinsights.com https://firebaseinstallations.googleapis.com https://fcmregistrations.googleapis.com https://*.analytics.google.com https://*.google-analytics.com https://*.googletagmanager.com https://o4507213726154752.ingest.de.sentry.io/api/4507213736837200/envelope/ 'self'; font-src 'self' data:; form-action 'self'; frame-ancestors 'none'; frame-src https://challenges.cloudflare.com; img-src blob: https://tile.openstreetmap.org/ https://tusd.localhost:3001 https://media3.giphy.com/ https://www.gravatar.com/avatar/ https://*.google-analytics.com https://*.googletagmanager.com 'self' data:; manifest-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'wasm-unsafe-eval'; worker-src https://localhost:3001/sw.js blob:; script-src-elem https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js https://static.cloudflareinsights.com https://localhost:3001/cdn-cgi/ https://challenges.cloudflare.com https://*.googletagmanager.com 'nonce' https://localhost:3001/_nuxt/;`, + 'Content-Security-Policy': `base-uri 'none'; default-src 'none'; connect-src blob: https://app.localhost:3001 https://postgraphile.localhost:3001 https://tusd.localhost:3001 https://nominatim.openstreetmap.org/search https://cloudflareinsights.com https://firebaseinstallations.googleapis.com https://fcmregistrations.googleapis.com https://*.analytics.google.com https://*.google-analytics.com https://*.googletagmanager.com https://o4507213726154752.ingest.de.sentry.io/api/4507213736837200/envelope/ 'self'; font-src 'self' data:; form-action 'self'; frame-ancestors 'none'; frame-src https://challenges.cloudflare.com; img-src blob: https://tile.openstreetmap.org/ https://tusd.localhost:3001 https://media3.giphy.com/ https://www.gravatar.com/avatar/ https://*.google-analytics.com https://*.googletagmanager.com 'self' data:; manifest-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'wasm-unsafe-eval'; worker-src https://app.localhost:3001/sw.js blob:; script-src-elem https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js https://static.cloudflareinsights.com https://app.localhost:3001/cdn-cgi/ https://challenges.cloudflare.com https://*.googletagmanager.com 'nonce' https://app.localhost:3001/_nuxt/;`, 'content-type': 'text/html;charset=utf-8', 'Cross-Origin-Embedder-Policy': 'credentialless', 'Cross-Origin-Opener-Policy': 'same-origin', diff --git a/tests/e2e/specs/server/robots.spec.ts b/tests/e2e/specs/server/robots.spec.ts index 2ab8e2d6e..87bd39b42 100644 --- a/tests/e2e/specs/server/robots.spec.ts +++ b/tests/e2e/specs/server/robots.spec.ts @@ -10,14 +10,10 @@ testPageLoad(PAGE_PATH) test.describe('robots.txt', () => { test('content', async ({ request }) => { const resp = await request.get(PAGE_PATH) - expect( - (await resp.text()).replace( - new RegExp(SITE_URL, 'g'), - 'https://example.com', - ), - ).toMatchSnapshot( + const text = (await resp.text()).replaceAll(SITE_URL, 'https://example.com') + expect(text).toMatchSnapshot( `robots-txt-content-${ - process.env.NODE_ENV === 'production' ? 'production' : 'development' + process.env.VIO_SERVER === 'development' ? 'development' : 'production' }.txt`, ) }) diff --git a/tests/e2e/utils/constants.ts b/tests/e2e/utils/constants.ts index b725a3c9c..21c08b98f 100644 --- a/tests/e2e/utils/constants.ts +++ b/tests/e2e/utils/constants.ts @@ -29,5 +29,5 @@ export const PAGE_READY = async ({ } } export const SITE_URL = - process.env.NUXT_PUBLIC_SITE_URL || - `https://${process.env.HOST || 'localhost'}:${process.env.PORT || '3000'}` + process.env.NUXT_PUBLIC_I18N_BASE_URL || + `https://${process.env.HOST || 'app.localhost'}:${process.env.PORT || '3000'}` diff --git a/tests/e2e/utils/tests.ts b/tests/e2e/utils/tests.ts index 2ced21c0b..2c1df77ac 100644 --- a/tests/e2e/utils/tests.ts +++ b/tests/e2e/utils/tests.ts @@ -101,18 +101,7 @@ export const testMetadata = async ({ key: 'property', value: 'og:image', }, - // TODO: check for open graph image content differently - // { - // key: 'content', - // value: joinURL( - // SITE_URL, - // `/__og-image__/${ - // process.env.VIO_SERVER === 'static' ? 'static' : 'image' - // }`, - // path, - // '/og.png', - // ), - // }, + // content is checked below ], }, { @@ -145,18 +134,7 @@ export const testMetadata = async ({ key: 'name', value: 'twitter:image', }, - // TODO: check for open graph image content differently - // { - // key: 'content', - // value: joinURL( - // SITE_URL, - // `/__og-image__/${ - // process.env.VIO_SERVER === 'static' ? 'static' : 'image' - // }`, - // path, - // '/og.png', - // ), - // }, + // content is checked below ], }, { @@ -166,18 +144,7 @@ export const testMetadata = async ({ key: 'name', value: 'twitter:image:src', }, - // TODO: check for open graph image content differently - // { - // key: 'content', - // value: joinURL( - // SITE_URL, - // `/__og-image__/${ - // process.env.VIO_SERVER === 'static' ? 'static' : 'image' - // }`, - // path, - // '/og.png', - // ), - // }, + // content is checked below ], }, { @@ -325,9 +292,9 @@ export const testMetadata = async ({ { key: 'content', value: - process.env.NODE_ENV === 'production' - ? 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1' - : 'noindex, nofollow', + process.env.VIO_SERVER === 'development' + ? 'noindex, nofollow' + : 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1', }, ], }, @@ -531,6 +498,17 @@ export const testMetadata = async ({ // .innerText(), // ).toMatchSnapshot(`content-security-policy.txt`) // } + + for (const locator of [ + 'meta[property="og:image"]', + 'meta[name="twitter:image"]', + 'meta[name="twitter:image:src"]', + ]) { + const content = await page.locator(locator).getAttribute('content') + expect(content).toBeTruthy() + expect(content?.startsWith(SITE_URL)).toBeTruthy() + expect(content).toMatch(/(\/_og\/[ds]\/).+\.png$/) + } } export const testOgImage = (paths: { diff --git a/tests/package.json b/tests/package.json index 6303432b1..535cff717 100644 --- a/tests/package.json +++ b/tests/package.json @@ -29,16 +29,17 @@ "test:e2e": "playwright test", "test:e2e:docker:br": "pnpm run test:e2e:docker:build && pnpm run test:e2e:docker:run", "test:e2e:docker:build": "sudo docker build -t test-e2e_development --build-arg UID=$(id -u) --build-arg GID=$(id -g) --target test-e2e_development ..", - "test:e2e:docker:run": "sudo docker run --rm -v \"$PWD/..:/srv/app\" -v \"vibetype_playwright_data:/srv/app/node_modules\" -v \"$(pnpm store path):/srv/.pnpm-store\" test-e2e_development", + "test:e2e:docker:run": "sudo docker run --add-host=app.localhost:127.0.0.1 --rm -v \"$PWD/..:/srv/app\" -v \"vibetype_playwright_data:/srv/app/node_modules\" -v \"$(pnpm store path):/srv/.pnpm-store\" test-e2e_development", "test:e2e:docker:server:dev": "pnpm run test:e2e:docker:br pnpm --dir tests run test:e2e:server:dev", "test:e2e:docker:server:dev:update": "del-cli e2e/specs/**/*.png && pnpm run test:e2e:docker:server:dev --update-snapshots", "test:e2e:docker:server:node": "pnpm run test:e2e:docker:br pnpm --dir tests run test:e2e:server:node", "test:e2e:docker:server:node:update": "del-cli e2e/specs/**/*.png && pnpm run test:e2e:docker:server:node --update-snapshots", "test:e2e:docker:server:static": "pnpm run test:e2e:docker:br pnpm --dir tests run test:e2e:server:static", "test:e2e:docker:server:static:update": "del-cli e2e/specs/**/*.png && pnpm run test:e2e:docker:server:static --update-snapshots", - "test:e2e:server:dev": "cross-env PORT=3000 NUXT_PUBLIC_SITE_URL=https://localhost:3000 VIO_SERVER=development pnpm run test:e2e", - "test:e2e:server:node": "cross-env NODE_ENV=production NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" NUXT_PUBLIC_I18N_BASE_URL=https://localhost:3001 NUXT_PUBLIC_SITE_URL=https://localhost:3001 PORT=3001 VIO_SERVER=node pnpm run test:e2e", - "test:e2e:server:static": "cross-env NODE_ENV=production PORT=3002 NUXT_PUBLIC_SITE_URL=https://localhost:3002 VIO_SERVER=static pnpm run test:e2e" + "test:e2e:server:dev": "cross-env NUXT_PUBLIC_I18N_BASE_URL=https://app.localhost:3000 PORT=3000 VIO_SERVER=development pnpm run test:e2e", + "test:e2e:server:node": "cross-env NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" NUXT_PUBLIC_I18N_BASE_URL=https://app.localhost:3001 PORT=3001 VIO_SERVER=node pnpm run test:e2e", + "test:e2e:server:static": "cross-env NUXT_PUBLIC_I18N_BASE_URL=https://app.localhost:3002 PORT=3002 VIO_SERVER=static pnpm run test:e2e", + "test:e2e:ui": "pnpm run test:e2e --ui" }, "type": "module" }