diff --git a/new/README.md b/new/README.md index bf17824db..ceba156d6 100644 --- a/new/README.md +++ b/new/README.md @@ -9,7 +9,7 @@ This is the working directory for the Pronouns.page rewrite. Run `setup.mjs` for a quick setup. It'll symlink some directories and will work as the equivalent of `make switch` et al. The reason for making this a script is that make was causing problems for Windows users. -This script does not yet depend on a package manager, so you're free to use whatever you like, provided it'll play nice +This script does not yet depend on a package manager, so you're free to use whatever you like, provided it'll play nice with the remaining setup. ### Package managers diff --git a/new/backend/nodemon.json b/new/backend/nodemon.json index 288fa4264..175e13129 100644 --- a/new/backend/nodemon.json +++ b/new/backend/nodemon.json @@ -1,7 +1,3 @@ { - "watch": [ - "dist/**", - "package.json", - "tsconfig.json" - ] -} \ No newline at end of file + "watch": ["dist/**", "package.json", "tsconfig.json"] +} diff --git a/new/backend/src/config.ts b/new/backend/src/config.ts index 956aa55c3..885efebfc 100644 --- a/new/backend/src/config.ts +++ b/new/backend/src/config.ts @@ -1,5 +1,10 @@ import "dotenv/config"; -import { identity, parseBool, parseIntOrThrow, toLowerCase } from "@pronounspage/common/util"; +import { + identity, + parseBool, + parseIntOrThrow, + toLowerCase, +} from "@pronounspage/common/util"; import * as path from "node:path"; import * as fs from "node:fs"; import type { log } from "#self/log"; @@ -11,7 +16,7 @@ export enum Environment { export interface Config { environment: Environment; - logLevel?: typeof log["level"]; + logLevel?: (typeof log)["level"]; http: { baseUrl: string; host: string; @@ -27,17 +32,17 @@ export interface Config { function envVarOrDefault( key: string, parse: (value: string) => T, - defaultValue: T, + defaultValue: T ): T; function envVarOrDefault( key: string, parse: (value: string) => T, - defaultValue?: undefined, + defaultValue?: undefined ): T | undefined; function envVarOrDefault( key: string, parse: (value: string) => T, - defaultValue: T | undefined = undefined, + defaultValue: T | undefined = undefined ): T | undefined { const value = process.env[key]; return value == null ? defaultValue : parse(String(value)); @@ -73,7 +78,7 @@ export function loadConfigFromEnv(): Config { environment: envVarOrDefault( "ENVIRONMENT", parseEnvironment, - Environment.DEVELOPMENT, + Environment.DEVELOPMENT ), logLevel: envVarOrDefault("LOG_LEVEL", toLowerCase, undefined), http: { @@ -84,8 +89,16 @@ export function loadConfigFromEnv(): Config { security: { secret: envVarNotNull("SECRET", identity), }, - localeDataPath: envVarOrDefault("LOCALE_DATA_PATH", parsePath, "../locales"), - allowUnpublishedLocales: envVarOrDefault("ALLOW_UNPUBLISHED_LOCALES", parseBool, false), + localeDataPath: envVarOrDefault( + "LOCALE_DATA_PATH", + parsePath, + "../locales" + ), + allowUnpublishedLocales: envVarOrDefault( + "ALLOW_UNPUBLISHED_LOCALES", + parseBool, + false + ), }; } @@ -99,8 +112,11 @@ export function validateConfig(config: Config): string | undefined { if (secretLower === "changeme") { return `You didn't change the secret? The secret is quite literally "${secretLower}"`; } - if (secretLower === "changemeonprod!" && config.environment === Environment.PRODUCTION) { - return `Hey now, this is production! You have to change the secret on prod! Look, it says so: ${config.security.secret}` + if ( + secretLower === "changemeonprod!" && + config.environment === Environment.PRODUCTION + ) { + return `Hey now, this is production! You have to change the secret on prod! Look, it says so: ${config.security.secret}`; } return undefined; diff --git a/new/backend/src/global.d.ts b/new/backend/src/global.d.ts index d598b7135..b6fb58942 100644 --- a/new/backend/src/global.d.ts +++ b/new/backend/src/global.d.ts @@ -19,12 +19,15 @@ type Log = typeof log; type TypeProvider = TypeBoxTypeProvider; declare global { - type AppInstance = FastifyInstance; - type AppPluginAsync> = FastifyPluginAsync; - type AppReply = FastifyReply -} \ No newline at end of file + type AppPluginAsync< + Options extends FastifyPluginOptions = Record, + > = FastifyPluginAsync; + type AppReply = FastifyReply; +} diff --git a/new/backend/src/index.ts b/new/backend/src/index.ts index 8b3179c84..9dd1729e4 100644 --- a/new/backend/src/index.ts +++ b/new/backend/src/index.ts @@ -2,7 +2,11 @@ import { startServer } from "#self/server"; import { getConfig, validateConfig } from "#self/config"; import log from "#self/log"; import { exit, pid, ppid } from "node:process"; -import { getActiveLocaleDescriptions, loadAllLocales, loadLocaleDescriptions } from "./locales.js"; +import { + getActiveLocaleDescriptions, + loadAllLocales, + loadLocaleDescriptions, +} from "./locales.js"; log.info(`Pronouns.page Backend Server - PID: ${pid}, PPID: ${ppid}`); @@ -21,13 +25,17 @@ try { const localeDescriptions = await loadLocaleDescriptions(); const active = getActiveLocaleDescriptions(); await loadAllLocales(); - log.info(`${localeDescriptions.length} locale(s) are registered, of which ${active.length} are active`) + log.info( + `${localeDescriptions.length} locale(s) are registered, of which ${active.length} are active` + ); } catch (e) { log.error(`Could not load locales: ${e}`); exit(1); } const memUsage = process.memoryUsage(); -const memUsagePercent = memUsage.heapUsed / memUsage.heapTotal * 100; -log.info(`Finished preparations - system stats: MEM ${memUsagePercent.toFixed(2)}%`); +const memUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100; +log.info( + `Finished preparations - system stats: MEM ${memUsagePercent.toFixed(2)}%` +); await startServer(); diff --git a/new/backend/src/locales.ts b/new/backend/src/locales.ts index eb1cf590c..f8bdfb496 100644 --- a/new/backend/src/locales.ts +++ b/new/backend/src/locales.ts @@ -8,7 +8,12 @@ import * as suml from "@pronounspage/common/suml"; import fsp from "node:fs/promises"; import log from "#self/log"; import * as csv from "csv-parse/sync"; -import { capitalizeFirstLetter, compileTemplate, isNotBlank, parseBool } from "@pronounspage/common/util"; +import { + capitalizeFirstLetter, + compileTemplate, + isNotBlank, + parseBool, +} from "@pronounspage/common/util"; export interface LocaleDescription { code: string; @@ -25,10 +30,19 @@ let loadedDescriptions: Array; let indexedDescriptions: Record; let activeLocaleDescriptions: Array | undefined; -export async function loadLocaleDescriptions(): Promise> { +export async function loadLocaleDescriptions(): Promise< + Array +> { if (loadedDescriptions == undefined) { - loadedDescriptions = (await import("file://" + path.resolve(getConfig().localeDataPath, "locales.js"))).default; - indexedDescriptions = Object.fromEntries(loadedDescriptions.map((v) => [v.code, v])); + loadedDescriptions = ( + await import( + "file://" + + path.resolve(getConfig().localeDataPath, "locales.js") + ) + ).default; + indexedDescriptions = Object.fromEntries( + loadedDescriptions.map((v) => [v.code, v]) + ); activeLocaleDescriptions = undefined; } return loadedDescriptions; @@ -70,7 +84,7 @@ export interface Pronoun { thirdForm?: string; smallForm?: string; - forms: Record; + forms: Record; } export interface Example { @@ -93,8 +107,14 @@ export class Locale { } private async readFile(parts: Array): Promise; - private async readFile(parts: Array, parser: (value: string) => T): Promise; - private async readFile(pathParts: Array, parser?: (value: string) => T) { + private async readFile( + parts: Array, + parser: (value: string) => T + ): Promise; + private async readFile( + pathParts: Array, + parser?: (value: string) => T + ) { // Some may complain about this const filePath = this.path(pathParts); try { @@ -106,7 +126,9 @@ export class Locale { return data; } } catch (e) { - const err = new Error(`[${this.code}] Could not load file ${filePath}: ${e}`); + const err = new Error( + `[${this.code}] Could not load file ${filePath}: ${e}` + ); err.cause = e; throw err; } @@ -118,11 +140,12 @@ export class Locale { log.trace(`[${this.code}] Importing file ${filePath} for locale`); return await import(`file://${filePath}`); } catch (e) { - const err = new Error(`[${this.code}] Could not import file ${filePath}: ${e}`); + const err = new Error( + `[${this.code}] Could not import file ${filePath}: ${e}` + ); err.cause = e; throw err; } - } private _config: unknown | null = null; @@ -134,22 +157,29 @@ export class Locale { // private _morphemes: Array; public async load() { - const tsvParse = (data: string) => csv.parse(data, { - columns: true, - cast: false, - relaxColumnCount: true, - relaxQuotes: true, - delimiter: "\t", - }); + const tsvParse = (data: string) => + csv.parse(data, { + columns: true, + cast: false, + relaxColumnCount: true, + relaxQuotes: true, + delimiter: "\t", + }); this._config = await this.readFile(["config.suml"], suml.parse); - this._translations = await this.readFile(["translations.suml"], suml.parse); + this._translations = await this.readFile( + ["translations.suml"], + suml.parse + ); // NOTE(tecc): This currently doesn't work because the morphemes.js files (as well as many others) rely on ESM syntax. // This is both inconsistent with the locales.js and expectedTranslations.js files, // and causes Node to throw an error. // this._morphemes = (await this.importFile("pronouns/morphemes.js")).default; this._pronouns = []; this._pronounsByAlias = {}; - const pronouns = await this.readFile(["pronouns/pronouns.tsv"], tsvParse); + const pronouns = await this.readFile( + ["pronouns/pronouns.tsv"], + tsvParse + ); for (const pronoun of pronouns) { const partial: Partial = { forms: {} }; for (const [key, value] of Object.entries(pronoun)) { @@ -191,7 +221,10 @@ export class Locale { this._pronounsByAlias[key] = i; } } - this._examples = await this.readFile(["pronouns/examples.tsv"], tsvParse); + this._examples = await this.readFile( + ["pronouns/examples.tsv"], + tsvParse + ); } public get code() { @@ -220,7 +253,10 @@ export class Locale { return this._examples; } } -export function examplesFor(pronoun: Pronoun, examples: Array): Array { +export function examplesFor( + pronoun: Pronoun, + examples: Array +): Array { const finished = []; const templating: Record = {}; @@ -257,7 +293,10 @@ export function getLocale(localeCode: string): Locale | null { } let locale = loadedLocales[localeCode]; if (locale == null) { - locale = new Locale(localeCode, path.resolve(getConfig().localeDataPath, localeCode)); + locale = new Locale( + localeCode, + path.resolve(getConfig().localeDataPath, localeCode) + ); loadedLocales[localeCode] = locale; } return locale; @@ -269,9 +308,15 @@ export async function loadAllLocales() { for (const description of descriptions) { const locale = getLocale(description.code); if (locale) { - promises.push(locale.load().then(() => log.debug(`Successfully loaded locale ${locale.code}`))); + promises.push( + locale + .load() + .then(() => + log.debug(`Successfully loaded locale ${locale.code}`) + ) + ); } } await Promise.all(promises); log.info("All active locales have been loaded into memory"); -} \ No newline at end of file +} diff --git a/new/backend/src/log.ts b/new/backend/src/log.ts index 881883db9..58fbe87af 100644 --- a/new/backend/src/log.ts +++ b/new/backend/src/log.ts @@ -17,11 +17,10 @@ const loggerConfigurations = { }, } satisfies Record; - let environment, logLevel; try { const config = getConfig(); - environment = config.environment + environment = config.environment; logLevel = config.logLevel; } catch (e) { environment = Environment.DEVELOPMENT; diff --git a/new/backend/src/server.ts b/new/backend/src/server.ts index 4cb1398b7..29b176889 100644 --- a/new/backend/src/server.ts +++ b/new/backend/src/server.ts @@ -9,9 +9,9 @@ export async function startServer() { logger: log, ajv: { customOptions: { - coerceTypes: "array" - } - } + coerceTypes: "array", + }, + }, }); let start: Date; diff --git a/new/backend/src/server/v1.ts b/new/backend/src/server/v1.ts index 7f5430dfe..3a6b0968b 100644 --- a/new/backend/src/server/v1.ts +++ b/new/backend/src/server/v1.ts @@ -18,7 +18,7 @@ export interface ApiErrorOptions { export function error(options: T) { const response: Record = { code: options.code, - message: options.message + message: options.message, }; if (options.detail) { response.detail = options.detail; @@ -26,7 +26,10 @@ export function error(options: T) { return response; } -export function replyError(reply: AppReply, options: T): AppReply { +export function replyError( + reply: AppReply, + options: T +): AppReply { const response = error(options); return reply.status(options.code).send(response); } @@ -34,12 +37,12 @@ export function replyError(reply: AppReply, options: const ApiError = { INVALID_LOCALE: { code: 404, - message: "Invalid locale" + message: "Invalid locale", }, UNKNOWN_PRONOUN: { code: 404, - message: "We aren't aware of any such pronoun. Perhaps there's a typo?" - } + message: "We aren't aware of any such pronoun. Perhaps there's a typo?", + }, } satisfies Record; /* @@ -50,12 +53,12 @@ const ApiError = { export const routes = async function (app: AppInstance) { const localeSpecific = Type.Object({ - locale: Type.String() + locale: Type.String(), }); function transformPronoun(pronoun: Pronoun, examples: Array) { const morphemes: Record = {}; - const pronunciations: Record = {}; + const pronunciations: Record = {}; for (const [name, form] of Object.entries(pronoun.forms)) { morphemes[name] = form.written; pronunciations[name] = form.pronounced; @@ -74,7 +77,7 @@ export const routes = async function (app: AppInstance) { thirdForm: pronoun.thirdForm ?? null, smallForm: pronoun.smallForm ?? null, sourcesInfo: pronoun.source ?? null, - examples: examplesFor(pronoun, examples) + examples: examplesFor(pronoun, examples), }; } @@ -82,7 +85,7 @@ export const routes = async function (app: AppInstance) { "/:locale/pronouns", { schema: { - params: localeSpecific + params: localeSpecific, }, }, (request, reply) => { @@ -93,7 +96,10 @@ export const routes = async function (app: AppInstance) { const obj: Record = {}; for (const pronoun of locale.pronouns) { - obj[pronoun.keys[0]] = transformPronoun(pronoun, locale.examples); + obj[pronoun.keys[0]] = transformPronoun( + pronoun, + locale.examples + ); } return obj; @@ -104,10 +110,13 @@ export const routes = async function (app: AppInstance) { "/:locale/pronouns/*", { schema: { - params: Type.Intersect([localeSpecific, Type.Object({ "*": Type.String() })]), + params: Type.Intersect([ + localeSpecific, + Type.Object({ "*": Type.String() }), + ]), querystring: Type.Object({ - 'examples[]': Type.Array(Type.String(), { default: [] }) - }) + "examples[]": Type.Array(Type.String(), { default: [] }), + }), }, }, (req, reply) => { @@ -124,7 +133,6 @@ export const routes = async function (app: AppInstance) { const examples = req.query["examples[]"]; - return transformPronoun(found, locale.examples); } ); diff --git a/new/common/src/global.d.ts b/new/common/src/global.d.ts index 8ac8ebd25..a63d12f77 100644 --- a/new/common/src/global.d.ts +++ b/new/common/src/global.d.ts @@ -1 +1 @@ -declare module "suml"; \ No newline at end of file +declare module "suml"; diff --git a/new/common/src/suml.ts b/new/common/src/suml.ts index fbbe6a288..eb7983e17 100644 --- a/new/common/src/suml.ts +++ b/new/common/src/suml.ts @@ -2,7 +2,7 @@ import * as SumlImpl from "suml"; declare module "suml"; -console.log(SumlImpl) +console.log(SumlImpl); const instance = new SumlImpl.default(); /** @@ -18,4 +18,4 @@ export function parse(value: string): unknown { */ export function dump(value: unknown): string { return instance.dump(value); -} \ No newline at end of file +} diff --git a/new/common/src/util.ts b/new/common/src/util.ts index 6653c04f7..d4941d3cd 100644 --- a/new/common/src/util.ts +++ b/new/common/src/util.ts @@ -4,7 +4,9 @@ export function identity(value: T): T { // Generally, don't use this function to define commonly used functions. // Prefer to write a manual implementation. -export function not>(f: (...args: Args) => boolean): (...args: Args) => boolean { +export function not>( + f: (...args: Args) => boolean +): (...args: Args) => boolean { return (...args: Args) => !f(...args); } export function isNotNull(value: T | null | undefined): value is T { @@ -24,12 +26,14 @@ export const isBlank = not(isNotBlank); // This is unnecessarily typed but I don't really care, it works doesn't it? export function toLowerCase(value: S): Lowercase { - return value.toLowerCase() as Lowercase + return value.toLowerCase() as Lowercase; } export function parseIntOrThrow(value: string, radix?: number): number { if (radix != null && (radix < 2 || radix > 36)) { - throw new Error(`invalid radix ${radix} - must be between 2 and 36 (inclusive)`); + throw new Error( + `invalid radix ${radix} - must be between 2 and 36 (inclusive)` + ); } const parsed = parseInt(value, radix); if (isNaN(parsed)) { @@ -62,7 +66,10 @@ export function capitalizeFirstLetter(value: string) { return value.charAt(0).toUpperCase() + value.substring(1); } -export function compileTemplate(template: string, values: Record): string { +export function compileTemplate( + template: string, + values: Record +): string { let string = ""; let key = null; for (let i = 0; i < template.length; i++) { @@ -93,4 +100,4 @@ export function compileTemplate(template: string, values: Record string = string.replace(`{${key}}`, value); }*/ return string; -} \ No newline at end of file +} diff --git a/new/setup.mjs b/new/setup.mjs index 8a28efb8b..154d3cfcf 100644 --- a/new/setup.mjs +++ b/new/setup.mjs @@ -2,32 +2,40 @@ import fsp from "node:fs/promises"; import fs from "node:fs"; import path from "node:path"; -console.log("Setting up project") +console.log("Setting up project"); const url = new URL(import.meta.url); -const projectDir = path.dirname(path.resolve(url.pathname.split('/').filter((v) => v && v.length > 0).join(path.sep))); +const projectDir = path.dirname( + path.resolve( + url.pathname + .split("/") + .filter((v) => v && v.length > 0) + .join(path.sep) + ) +); const legacyDir = path.resolve(projectDir, ".."); console.log(` Project directory: ${projectDir} Legacy directory: ${legacyDir} -`) +`); async function symlink(to, from) { if (!fs.existsSync(from)) { - console.log(` Creating symlink from ${from} to ${to}`) - await fsp.symlink(to, from, "dir") + console.log(` Creating symlink from ${from} to ${to}`); + await fsp.symlink(to, from, "dir"); } else { - console.log(` File ${from} already exists - not symlinking to ${to}`) + console.log(` File ${from} already exists - not symlinking to ${to}`); } } // Create temporary symlinks // This step may very well be removed in the future. { - const to = path.resolve(legacyDir, "locale"), from = path.resolve(projectDir, "locales"); + const to = path.resolve(legacyDir, "locale"), + from = path.resolve(projectDir, "locales"); await symlink(to, from); } console.log(` -Done!`) \ No newline at end of file +Done!`);