diff --git a/bin/lint-staged.config.mjs b/bin/lint-staged.config.mjs new file mode 100644 index 000000000000..074f27c0bc3f --- /dev/null +++ b/bin/lint-staged.config.mjs @@ -0,0 +1,19 @@ +import { lstat } from 'fs/promises'; + +async function filterSymlinks(files) { + const checks = await Promise.all( + files.map(async file => + lstat(file) + .then(s => s && !s.isSymbolicLink() && file) + .catch(() => null), + ), + ); + return checks.filter(Boolean); +} + +export default { + '*.{json,scss,ts}': async files => { + const regularFiles = await filterSymlinks(files); + return regularFiles.length ? `prettier --write ${regularFiles.join(' ')}` : 'true'; + }, +}; diff --git a/package.json b/package.json index ea51ab3d0667..8578cb317bfa 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,6 @@ "pnpm": "^9" }, "//": "NOTE: lint-staged patterns must stay in sync with bin/git-hooks/pre-commit!", - "lint-staged": { - "*.{json,scss,ts}": "prettier --write" - }, "dependencies": { "@types/lichess": "workspace:*", "@types/node": "^22.7.4", @@ -47,6 +44,7 @@ "add-hooks": "git config --add core.hooksPath bin/git-hooks", "remove-hooks": "git config --unset core.hooksPath bin/git-hooks", "lint": "eslint --cache", + "lint-staged": "lint-staged --config bin/lint-staged.config.mjs", "journal": "journalctl --user -fu lila -o cat", "metals": "tail -F .metals/metals.log | stdbuf -oL cut -c 21- | rg -v '(notification for request|handleCancellation)'", "serverlog": "pnpm journal & pnpm metals", diff --git a/ui/.build/readme b/ui/.build/readme index 4159e1aeff02..094de78d3129 100644 --- a/ui/.build/readme +++ b/ui/.build/readme @@ -1,36 +1,39 @@ -ui/build was lovingly crafted from a single block of wood as a personal gift to you. +Usage: + ui/build # are top level directories in ui + # if no packages are specified, all will be processed + # multiple short options can be preceded by a single dash - ./build # packages are top level directories in ui - one letter options can be consolidated after a single dash (e.g. -cdw) - if no packages are specified, all will be processed +Recommended: + ui/build -cdw # clean, build debug, and watch for changes with clean rebuilds Options: -h, --help show this help and exit -w, --watch build and watch for changes - -c, --clean-build build fresh artifacts + -c, --clean-build clean all non-i18n build artifacts and build fresh -p, --prod build minified assets (prod builds) -n, --no-install don't run pnpm install -d, --debug build assets with site.debug = true -l, --log= monkey patch console log functions in javascript manifest to POST log messages to or localhost:8666 (default). if used with --watch, the ui/build process will listen for http on 8666 and display received json as 'web' in build logs - --clean clean all build artifacts, including translation/js, and exit - --update update ui/build's node_modules + --update update ui/.build/node_modules with pnpm install --no-color don't use color in logs --no-time don't log the time --no-context don't log the context - --tsc run tsc, any of [--tsc, --sass, --esbuild, --copies] will disable the others + +Exclusive Options: (any of these will disable other functions) + --clean clean all build artifacts, including i18n/translation, and exit + --tsc run tsc on {package}/tsconfig.json and dependencies --sass run sass on {package}/css/build/*.scss and dependencies --esbuild run esbuild (given in {package}/package.json/lichess/bundles array) - --copies run copies (given in {package}/package.json/lichess/sync objects) + --sync run sync copies (given in {package}/package.json/lichess/sync objects) + --i18n build @types/lichess/i18n.d.ts and translation/js files Examples: - ./build -w # build and watch for changes <- this is the most common use case - ./build -wc # clean build, watch <- recommended for maintainers - ./build -w --log=/path # build, watch, and patch js console POST to ${location.origin}/path. - # ui/build listens on 8666, displays received json as 'web' over stdout ./build -np # no pnpm install, build minified ./build analyse site msg # build analyse, site, and msg packages (as opposed to everything) ./build -w dasher chart # watch mode for dasher and chart packages ./build --tsc -w # watch mode but type checking only ./build --sass msg notify # build css only for msg and notify packages + ./build -w -l=/path # build, watch, and patch js console with POST to ${location.origin}/path. + # ui/build listens on 8666, displays received json as 'web' over stdout diff --git a/ui/.build/src/algo.ts b/ui/.build/src/algo.ts new file mode 120000 index 000000000000..22cd5bb3aaf4 --- /dev/null +++ b/ui/.build/src/algo.ts @@ -0,0 +1 @@ +../../common/src/algo.ts \ No newline at end of file diff --git a/ui/.build/src/build.ts b/ui/.build/src/build.ts index 62b32e256531..b43ecdb5d76b 100644 --- a/ui/.build/src/build.ts +++ b/ui/.build/src/build.ts @@ -5,7 +5,7 @@ import { parsePackages } from './parse.ts'; import { tsc, stopTsc } from './tsc.ts'; import { sass, stopSass } from './sass.ts'; import { esbuild, stopEsbuild } from './esbuild.ts'; -import { copies, stopCopies } from './copies.ts'; +import { sync, stopSync } from './sync.ts'; import { monitor, stopMonitor } from './monitor.ts'; import { writeManifest } from './manifest.ts'; import { clean } from './clean.ts'; @@ -38,7 +38,7 @@ export async function build(pkgs: string[]): Promise { fs.promises.mkdir(env.buildTempDir), ]); - await Promise.all([sass(), copies(), i18n()]); + await Promise.all([sass(), sync(), i18n()]); await esbuild(tsc()); monitor(pkgs); } @@ -47,7 +47,7 @@ export async function stop(): Promise { stopMonitor(); stopSass(); stopTsc(); - stopCopies(); + stopSync(); stopI18n(); await stopEsbuild(); } @@ -71,8 +71,6 @@ export function prePackage(pkg: Package | undefined): void { }); } -export const quantize = (n?: number, factor = 2000) => Math.floor((n ?? 0) / factor) * factor; - function depsOne(pkgName: string): Package[] { const collect = (dep: string): string[] => [...(env.deps.get(dep) || []).flatMap(d => collect(d)), dep]; return unique(collect(pkgName).map(name => env.packages.get(name))); diff --git a/ui/.build/src/i18n.ts b/ui/.build/src/i18n.ts index f8fd0f3d860a..0da4a106a2f7 100644 --- a/ui/.build/src/i18n.ts +++ b/ui/.build/src/i18n.ts @@ -4,7 +4,7 @@ import { XMLParser } from 'fast-xml-parser'; import { env, colors as c } from './main.ts'; import { globArray } from './parse.ts'; import { i18nManifest } from './manifest.ts'; -import { quantize } from './build.ts'; +import { quantize, zip } from './algo.ts'; import { transform } from 'esbuild'; type Plural = { [key in 'zero' | 'one' | 'two' | 'few' | 'many' | 'other']?: string }; @@ -192,15 +192,6 @@ function parseXml(xmlData: string): Map { return new Map([...i18nMap.entries()].sort(([a], [b]) => a.localeCompare(b))); } -function zip(arr1: T[], arr2: U[]): [T, U][] { - const length = Math.min(arr1.length, arr2.length); - const result: [T, U][] = []; - for (let i = 0; i < length; i++) { - result.push([arr1[i], arr2[i]]); - } - return result; -} - async function min(js: string): Promise { return (await transform(js, { minify: true, loader: 'js' })).code; } diff --git a/ui/.build/src/main.ts b/ui/.build/src/main.ts index 9089eb806e42..0e62cd6a1688 100644 --- a/ui/.build/src/main.ts +++ b/ui/.build/src/main.ts @@ -10,7 +10,7 @@ const args: Record = { '--tsc': '', '--sass': '', '--esbuild': '', - '--copies': '', + '--sync': '', '--i18n': '', '--no-color': '', '--no-time': '', @@ -48,13 +48,13 @@ export function main(): void { .filter(x => x.startsWith('--') && !Object.keys(args).includes(x.split('=')[0])) .forEach(arg => env.exit(`Unknown argument '${arg}'`)); - if (['--tsc', '--sass', '--esbuild', '--copies', '--i18n'].filter(x => argv.includes(x)).length) { + if (['--tsc', '--sass', '--esbuild', '--sync', '--i18n'].filter(x => argv.includes(x)).length) { // including one or more of these disables the others if (!argv.includes('--sass')) env.exitCode.set('sass', false); if (!argv.includes('--tsc')) env.exitCode.set('tsc', false); if (!argv.includes('--esbuild')) env.exitCode.set('esbuild', false); env.i18n = argv.includes('--i18n'); - env.copies = argv.includes('--copies'); + env.sync = argv.includes('--sync'); } if (argv.includes('--no-color')) env.color = undefined; if (argv.includes('--no-time')) env.logTime = false; @@ -134,7 +134,7 @@ class Env { remoteLog: string | boolean = false; rgb = false; install = true; - copies = true; + sync = true; i18n = true; exitCode: Map = new Map(); startTime: number | undefined = Date.now(); @@ -147,7 +147,6 @@ class Env { esbuild: 'blue', }; - constructor() {} get sass(): boolean { return this.exitCode.get('sass') !== false; } @@ -256,9 +255,7 @@ class Env { this.log( `${code === 0 ? 'Done' : colors.red('Failed')}` + (this.watch ? ` - ${colors.grey('Watching')}...` : ''), - { - ctx: ctx, - }, + { ctx }, ); if (allDone) { if (!err) postBuild(); @@ -266,9 +263,7 @@ class Env { this.log(`Done in ${colors.green((Date.now() - this.startTime) / 1000 + '')}s`); this.startTime = undefined; // it's pointless to time subsequent builds, they are too fast } - if (!env.watch && err) { - process.exitCode = err; - } + if (!env.watch && err) process.exitCode = err; } } diff --git a/ui/.build/src/manifest.ts b/ui/.build/src/manifest.ts index f7879044dfda..627a02c213c4 100644 --- a/ui/.build/src/manifest.ts +++ b/ui/.build/src/manifest.ts @@ -5,7 +5,7 @@ import crypto from 'node:crypto'; import es from 'esbuild'; import { env, colors as c, warnMark } from './main.ts'; import { globArray, globArrays } from './parse.ts'; -import { isUnmanagedAsset } from './copies.ts'; +import { isUnmanagedAsset } from './sync.ts'; import { allSources } from './sass.ts'; import { jsLogger } from './console.ts'; diff --git a/ui/.build/src/copies.ts b/ui/.build/src/sync.ts similarity index 95% rename from ui/.build/src/copies.ts rename to ui/.build/src/sync.ts index 19883033f054..aeebb1de5d18 100644 --- a/ui/.build/src/copies.ts +++ b/ui/.build/src/sync.ts @@ -3,20 +3,20 @@ import path from 'node:path'; import { globArray, globArrays } from './parse.ts'; import { hashedManifest, writeManifest } from './manifest.ts'; import { type Sync, env, errorMark, colors as c } from './main.ts'; -import { quantize } from './build.ts'; +import { quantize } from './algo.ts'; const syncWatch: fs.FSWatcher[] = []; let watchTimeout: NodeJS.Timeout | undefined; -export function stopCopies(): void { +export function stopSync(): void { clearTimeout(watchTimeout); watchTimeout = undefined; for (const watcher of syncWatch) watcher.close(); syncWatch.length = 0; } -export async function copies(): Promise { - if (!env.copies) return; +export async function sync(): Promise { + if (!env.sync) return; const watched = new Map(); const updated = new Set(); diff --git a/ui/.build/src/tscWorker.ts b/ui/.build/src/tscWorker.ts index 6cf516e83e2d..6469f2dc15b7 100644 --- a/ui/.build/src/tscWorker.ts +++ b/ui/.build/src/tscWorker.ts @@ -16,7 +16,6 @@ export interface Message { export interface ErrorMessage extends Message { type: 'error'; data: { - project: string; code: number; text: string; file: string; diff --git a/ui/common/src/algo.ts b/ui/common/src/algo.ts index 13f08345931f..0e3dd94a872c 100644 --- a/ui/common/src/algo.ts +++ b/ui/common/src/algo.ts @@ -1,6 +1,6 @@ export const randomToken = (): string => { try { - const data = window.crypto.getRandomValues(new Uint8Array(9)); + const data = globalThis.crypto.getRandomValues(new Uint8Array(9)); return btoa(String.fromCharCode(...data)).replace(/[/+]/g, '_'); } catch (_) { return Math.random().toString(36).slice(2, 12); @@ -11,6 +11,10 @@ export function clamp(value: number, bounds: { min?: number; max?: number }): nu return Math.max(bounds.min ?? -Infinity, Math.min(value, bounds.max ?? Infinity)); } +export function quantize(n?: number, factor = 2000): number { + return Math.floor((n ?? 0) / factor) * factor; +} + export function shuffle(array: T[]): T[] { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1));