PronounsPage/nuxt.config.ts
2025-04-22 14:29:10 +02:00

327 lines
10 KiB
TypeScript

import childProcess from 'node:child_process';
import { promisify } from 'node:util';
import path from 'path';
import { resolvePath } from '@nuxt/kit';
import yamlPlugin from '@rollup/plugin-yaml';
import { sentryVitePlugin } from '@sentry/vite-plugin';
import { defineNuxtConfig } from 'nuxt/config';
import type { RouteMeta } from 'vue-router';
import type { Config } from './locale/config.ts';
import localeDescriptions from './locale/locales.ts';
import type { Translations } from './locale/translations.ts';
import mdPlugin from './plugins/rollup/md.ts';
import sumlPlugin from './plugins/rollup/suml.ts';
import tsvPlugin from './plugins/rollup/tsv.ts';
import { loadSuml } from './server/loader.ts';
import type { Pronoun } from '~/src/classes.ts';
import type { NounConvention } from '~/src/nouns.ts';
const exec = promisify(childProcess.exec);
const version = (await exec('git log -n 1 --pretty=format:"%H"')).stdout;
interface TranslatedRoute {
paths: string[];
meta?: RouteMeta;
}
declare module 'nuxt/app' {
interface PageMeta {
translatedPaths?: (config: Config) => string[] | Record<string, TranslatedRoute>;
}
}
declare module 'vue-router' {
interface RouteMeta {
headerCategory?: string;
pronoun?: Pronoun;
nounConvention?: WithKey<NounConvention>;
}
}
const config = await loadSuml<Config>('locale/_/config.suml');
const translations = await loadSuml<Translations>('locale/_/translations.suml');
const locale = config.locale;
const title = translations.title;
const description = translations.description;
const colour = '#C71585';
const codes = ['_', ...localeDescriptions.map((localeDescription) => localeDescription.code)];
const configByLocale: Record<string, Config> = Object.fromEntries(await Promise.all(codes.map(async (localeCode) => {
return [localeCode, await loadSuml<Config>(`locale/${localeCode}/config.suml`)];
})));
let __dirname = new URL('.', import.meta.url).pathname;
if (process.platform === 'win32') {
// A small hack, for Windows can't have nice things.
__dirname = __dirname.slice(1);
}
const esBuildOptions = {
supported: {
'top-level-await': true,
},
};
const hostname = process.env.HOST ?? '0.0.0.0';
const port = parseInt(process.env.PORT ?? '3000');
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'@nuxtjs/plausible',
'@vite-pwa/nuxt',
'@nuxt/test-utils/module',
'@nuxt/eslint',
],
devtools: {
enabled: true,
},
css: [
'~/assets/style.scss',
],
vue: {
compilerOptions: {
whitespace: 'preserve',
},
},
runtimeConfig: {
version,
public: {
domainBase: undefined as string | undefined,
env: 'home',
publicKey: '',
turnstileSitekey: '',
cloudfront: '',
heartbeatLink: '',
shopifyStorefrontToken: '',
plausible: {
// Disables automatic tracking of page views, meaning we have to do it manually
// If it's not done manually, a privacy issue occurs, which we *do not want*
autoPageviews: false,
},
sentry: {
dsn: '',
},
},
},
// effectively disabling the feature, see: https://nuxt.com/docs/api/nuxt-config#ignoreprefix
ignorePrefix: 'IGNORE',
sourcemap: true,
devServer: {
host: hostname, // listen on any host name
port,
},
features: {
inlineStyles: false,
},
compatibilityDate: '2024-07-06',
nitro: {
rollupConfig: {
external: [
'canvas',
'sharp',
],
},
esbuild: {
options: esBuildOptions,
},
preset: 'node-cluster',
storage: {
calendar: {
driver: 'fs-lite',
base: 'calendar',
},
},
devStorage: {
session: {
driver: 'fs',
base: './.data/session',
},
},
},
serverHandlers: [
{
route: '/api/**',
handler: '~/server/index.ts',
},
],
vite: {
plugins: [
mdPlugin(),
sumlPlugin(),
tsvPlugin(),
yamlPlugin(),
sentryVitePlugin({
disable: !process.env.SENTRY_AUTH_TOKEN,
telemetry: false,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
],
css: {
preprocessorOptions: {
scss: {
api: 'legacy',
},
},
},
define: {
'import.meta.env.APP_ENV': JSON.stringify(process.env.APP_ENV || process.env.NODE_ENV),
'import.meta.env.RUN_SNAPSHOT_TESTS': JSON.stringify(process.env.RUN_SNAPSHOT_TESTS),
'import.meta.env.VERSION': JSON.stringify(version),
},
resolve: {
alias: {
'~@fortawesome': path.resolve(__dirname, 'node_modules/@fortawesome'),
'~@vuepic': path.resolve(__dirname, 'node_modules/@vuepic'),
'~avris-sorter': path.resolve(__dirname, 'node_modules/avris-sorter'),
'~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'),
},
},
esbuild: esBuildOptions,
optimizeDeps: {
include: [
// list dependencies which trigger “optimized dependencies changed. reloading”
// https://github.com/nuxt/nuxt/discussions/27700
'@floating-ui/vue',
'@vuepic/vue-datepicker',
'@vueuse/core',
'avris-columnist',
'avris-futurus',
'avris-sorter',
'chart.js/auto',
'clipboard',
'generic-diff',
'html2canvas',
'markdown-it',
'markdown-it-mark',
'markdown-it-sub',
'markdown-it-sup',
'nepali-calendar-js',
'qr-code-styling',
'suml',
'uuid',
'vuedraggable',
'zh_cn_zh_tw',
],
},
server: {
watch: {
ignored: ['**/test/output/**'],
},
},
},
typescript: {
typeCheck: !process.env.RUN_SNAPSHOT_TESTS &&
(process.env.APP_ENV || process.env.NODE_ENV) !== 'production',
tsConfig: {
compilerOptions: {
baseUrl: './',
allowImportingTsExtensions: true,
},
},
},
postcss: {
plugins: {
'autoprefixer': {},
'cssnano': {},
'postcss-rtl': {},
},
},
hooks: {
async 'pages:extend'(routes) {
for (const code of codes) {
for (const subroute of configByLocale[code].nouns.subroutes || []) {
routes.push({
path: `/${encodeURIComponent(subroute)}`,
name: `nouns-${code}-${subroute}`,
file: await resolvePath(`~/locale/${code}/nouns/${subroute}.vue`),
});
}
}
},
async 'nitro:config'(nitroConfig) {
nitroConfig.virtual ??= {};
// workaround to have a dynamic import with variables which currently does not work in Rollup
// https://github.com/rollup/rollup/issues/2097
nitroConfig.virtual['#virtual/calendar/events.ts'] = () => {
const imports = localeDescriptions.map((locale) => {
return `${locale.code}: async () =>
(await import('~/locale/${locale.code}/calendar/events.ts')).default`;
});
return `export default {${imports.join(', ')}};`;
};
nitroConfig.publicAssets ??= [];
nitroConfig.publicAssets.push(...(await Promise.all(codes.map(async (code) => [{
baseURL: `/docs/${code}`,
dir: await resolvePath(`~/locale/${code}/docs`),
maxAge: 0,
}, {
baseURL: `/img/${code}`,
dir: await resolvePath(`~/locale/${code}/img`),
maxAge: 0,
}]))).flat());
},
},
eslint: {
config: {
stylistic: {
indent: 4,
semi: true,
braceStyle: '1tbs',
arrowParens: true,
},
},
},
pwa: {
registerType: 'autoUpdate',
manifest: {
name: title,
short_name: title,
description,
lang: locale,
background_color: '#ffffff',
theme_color: colour,
// icons generated via `pwa-assets-generator --preset minimal-2023 public/logo/logo-primary.svg`
// see https://vite-pwa-org.netlify.app/assets-generator/cli
icons: [
{
src: 'pwa-64x64.png',
sizes: '64x64',
type: 'image/png',
},
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: 'maskable-icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
workbox: {
runtimeCaching: [
{
urlPattern: /^\/@/,
handler: 'NetworkFirst',
},
],
},
},
});