# sk-react-toolkit The shared code of the web tools built as a Fastify server in TypeScript and a React application with MUI built by Vite, such as azure-platform's manage-ui and sql-query-webui. It is TypeScript source: Node 26 runs the server helpers, and the application's Vite build compiles the web code. ## Using it The package is `@koszewscy/sk-react-toolkit`, published to the npm registry of Gitea, `https://gitea.koszewscy.waw.pl/api/packages/koszewscy/npm/`. An application imports it as `sk-react-toolkit`, by an alias. Its `.npmrc` names the registry of the scope: ```ini @koszewscy:registry=https://gitea.koszewscy.waw.pl/api/packages/koszewscy/npm/ ``` ```json { "dependencies": { "sk-react-toolkit": "npm:@koszewscy/sk-react-toolkit@^0.1.0" } } ``` While the toolkit and an application change together, the application depends on the checkout next to its own repository instead, the TypeScript source: ```json { "dependencies": { "sk-react-toolkit": "file:../sk-react-toolkit" } } ``` npm links the checkout into `node_modules`, and `npm ci` keeps the link. The web application adds the toolkit's resolve options and font plugin to its `vite.config.ts`, so React, MUI and Emotion come from its own `node_modules`, and allows the dev server to read the checkout: ```ts import { font, fontChoice, TOOLKIT_RESOLVE } from 'sk-react-toolkit/vite' import { defineConfig, loadEnv } from 'vite' export default defineConfig(({ mode }) => ({ plugins: [react(), font(fontChoice(loadEnv(mode, import.meta.dirname)))], resolve: TOOLKIT_RESOLVE, server: { fs: { allow: ['..', '../../sk-react-toolkit'] } }, })) ``` The peer dependencies are the application's: `fastify` and `@fastify/static` for the server helpers; `react`, `react-dom`, `@mui/material`, `@mui/icons-material` and Emotion for the web code; `vite` for the Vite helpers. ## sk-react-toolkit/server | Export | Purpose | | --- | --- | | `HttpError` | an error answered with its status and `{ error }`, with `problems` and a `detail` message for a refused write | | `strict(properties)` | the JSON schema of an object with exactly these properties, all required | | `errorHandler(log)` | Fastify's error handler: `{ error }`, or `{ error, problems, message }`; unexpected 5xx errors logged with their stack | | `notFoundHandler(webDir, buildHint)` | 404 for `/api` paths, `index.html` for the others, 503 with the hint while the frontend is not built | | `createLog`, `timestampsOn`, `QUIET`, `Log` | the log of a server, with optional timestamps and requests; `QUIET` for tests | | `fail(message)` | prints the error in red and ends the process with status 2 | | `gitVersion(dir, variable)` | `git describe` of the checkout, else the environment variable, else `dev` | | `exitOnSignals(close)` | closes the server on Ctrl-C and SIGTERM, ending the process after 5 seconds at most | | `readJsonObject(path, options)` | a JSON object file and its version, the SHA-256 of its bytes; optionally `{}` for a missing file | ## sk-react-toolkit/web | Export | Purpose | | --- | --- | | `request`, `ApiError`, `Validation` | a JSON call to the backend, its error with the problems of a refused write, and the answer of a validate endpoint | | `appTheme(colorSchemes?)`, `renderApp(options)` | the theme (light, dark and system, Regular font, small text fields) and the startup that retries the first call while showing Connecting | | `REGULAR`, `MONOSPACED`, `verbatim` | the font styles and the attributes of inputs the browser must not change | | `Card` | a centered panel with a title and a message | | `ThemeMenu` | the button cycling light, dark and system theme | | `SwitcherMenu`, `SwitcherItem` | a button naming the item chosen, whose menu chooses another, such as a page or a connection | | `TextInput` | a text field with clear and show/hide buttons | | `RowDialog`, `useValidation` | a dialog that edits a value validated by the server as it changes, and the validation hook | | `createBrowserSettings(keys, event)` | switches kept in localStorage and followed across tabs | ## sk-react-toolkit/vite | Export | Purpose | | --- | --- | | `font(choice)`, `fontChoice(env)`, `FONTS` | the font plugin of `VITE_FONT`: `cdn` (Google Fonts links, the default), `bundled` (`@fontsource/roboto`, a dependency of the application) or `none` | | `TOOLKIT_RESOLVE` | the resolve options that keep one copy of React, MUI and Emotion | ## Development `npm install` installs the peer dependencies as development dependencies; `npm run typecheck` and `npm run lint` check the code. `npm run build` (`build.mjs`) builds the published package in `dist/`: the code compiled to JavaScript with its declarations, README.md, CHANGELOG.md and a `package.json` whose exports name the compiled files. ## Releases `./release.mjs` commits, tags the version of the newest CHANGELOG.md entry and pushes `main` with the tag; `-n` prints the git commands instead. The version of a release of the code, `x.y.z`, is that of `package.json`, so a release raises both. A tag `x.y.z-n` releases changes that are not code changes; its version is already published, so its run publishes nothing. The `Release` workflow, `.gitea/workflows/release.yml`, builds and publishes the package on Gitea Actions for each tag and on demand: it installs the dependencies, typechecks and lints the code, builds the package and publishes `dist/` to the npm registry of the owner `CI_OWNER` on the server `CI_URL`, with `CI_TOKEN`, a token with the package scope; all three are secrets of the repository. A version already published is skipped, as the registry refuses the same version again.