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; } } declare module 'vue-router' { interface RouteMeta { headerCategory?: string; pronoun?: Pronoun; nounConvention?: WithKey; } } const config = await loadSuml('locale/_/config.suml'); const translations = await loadSuml('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 = Object.fromEntries(await Promise.all(codes.map(async (localeCode) => { return [localeCode, await loadSuml(`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', }, ], }, }, });