feat: switch to oxc linting/formatting

This commit is contained in:
Aarnav Tale
2026-01-15 22:05:29 -05:00
parent 0df4130217
commit 034ba25ce3
11 changed files with 967 additions and 724 deletions
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"endOfLine": "lf",
"experimentalSortImports": {},
"experimentalTailwindcss": {},
"ignorePatterns": [],
"printWidth": 100,
"tabWidth": 2,
"useTabs": false
}
+144
View File
@@ -0,0 +1,144 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["unicorn", "typescript", "oxc"],
"categories": {},
"rules": {
"constructor-super": "warn",
"for-direction": "warn",
"no-async-promise-executor": "warn",
"no-caller": "warn",
"no-class-assign": "warn",
"no-compare-neg-zero": "warn",
"no-cond-assign": "warn",
"no-const-assign": "warn",
"no-constant-binary-expression": "warn",
"no-constant-condition": "warn",
"no-control-regex": "warn",
"no-debugger": "warn",
"no-delete-var": "warn",
"no-dupe-class-members": "warn",
"no-dupe-else-if": "warn",
"no-dupe-keys": "warn",
"no-duplicate-case": "warn",
"no-empty-character-class": "warn",
"no-empty-pattern": "warn",
"no-empty-static-block": "warn",
"no-eval": "warn",
"no-ex-assign": "warn",
"no-extra-boolean-cast": "warn",
"no-func-assign": "warn",
"no-global-assign": "warn",
"no-import-assign": "warn",
"no-invalid-regexp": "warn",
"no-irregular-whitespace": "warn",
"no-loss-of-precision": "warn",
"no-new-native-nonconstructor": "warn",
"no-nonoctal-decimal-escape": "warn",
"no-obj-calls": "warn",
"no-self-assign": "warn",
"no-setter-return": "warn",
"no-shadow-restricted-names": "warn",
"no-sparse-arrays": "warn",
"no-this-before-super": "warn",
"no-unassigned-vars": "warn",
"no-unsafe-finally": "warn",
"no-unsafe-negation": "warn",
"no-unsafe-optional-chaining": "warn",
"no-unused-expressions": "warn",
"no-unused-labels": "warn",
"no-unused-private-class-members": "warn",
"no-unused-vars": "warn",
"no-useless-backreference": "warn",
"no-useless-catch": "warn",
"no-useless-escape": "warn",
"no-useless-rename": "warn",
"no-with": "warn",
"require-yield": "warn",
"use-isnan": "warn",
"valid-typeof": "warn",
"oxc/bad-array-method-on-arguments": "warn",
"oxc/bad-char-at-comparison": "warn",
"oxc/bad-comparison-sequence": "warn",
"oxc/bad-min-max-func": "warn",
"oxc/bad-object-literal-comparison": "warn",
"oxc/bad-replace-all-arg": "warn",
"oxc/const-comparisons": "warn",
"oxc/double-comparisons": "warn",
"oxc/erasing-op": "warn",
"oxc/missing-throw": "warn",
"oxc/number-arg-out-of-range": "warn",
"oxc/only-used-in-recursion": "warn",
"oxc/uninvoked-array-callback": "warn",
"typescript/await-thenable": "warn",
"typescript/no-array-delete": "warn",
"typescript/no-base-to-string": "warn",
"typescript/no-duplicate-enum-values": "warn",
"typescript/no-duplicate-type-constituents": "warn",
"typescript/no-extra-non-null-assertion": "warn",
"typescript/no-floating-promises": "warn",
"typescript/no-for-in-array": "warn",
"typescript/no-implied-eval": "warn",
"typescript/no-meaningless-void-operator": "warn",
"typescript/no-misused-new": "warn",
"typescript/no-misused-spread": "warn",
"typescript/no-non-null-asserted-optional-chain": "warn",
"typescript/no-redundant-type-constituents": "warn",
"typescript/no-this-alias": "warn",
"typescript/no-unnecessary-parameter-property-assignment": "warn",
"typescript/no-unsafe-declaration-merging": "warn",
"typescript/no-unsafe-unary-minus": "warn",
"typescript/no-useless-empty-export": "warn",
"typescript/no-wrapper-object-types": "warn",
"typescript/prefer-as-const": "warn",
"typescript/require-array-sort-compare": "warn",
"typescript/restrict-template-expressions": "warn",
"typescript/triple-slash-reference": "warn",
"typescript/unbound-method": "warn",
"unicorn/no-await-in-promise-methods": "warn",
"unicorn/no-empty-file": "warn",
"unicorn/no-invalid-fetch-options": "warn",
"unicorn/no-invalid-remove-event-listener": "warn",
"unicorn/no-new-array": "warn",
"unicorn/no-single-promise-in-promise-methods": "warn",
"unicorn/no-thenable": "warn",
"unicorn/no-unnecessary-await": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-spread": "warn",
"unicorn/prefer-set-size": "warn",
"unicorn/prefer-string-starts-ends-with": "warn"
},
"settings": {
"jsx-a11y": {
"polymorphicPropName": null,
"components": {},
"attributes": {}
},
"next": {
"rootDir": []
},
"react": {
"formComponents": [],
"linkComponents": [],
"version": null
},
"jsdoc": {
"ignorePrivate": false,
"ignoreInternal": false,
"ignoreReplacesDocs": true,
"overrideReplacesDocs": true,
"augmentsExtendsReplacesDocs": false,
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"vitest": {
"typecheck": false
}
},
"env": {
"builtin": true
},
"globals": {},
"ignorePatterns": []
}
+12 -14
View File
@@ -1,10 +1,11 @@
import { PassThrough } from 'node:stream'; import type { RenderToPipeableStreamOptions } from "react-dom/server";
import { createReadableStreamFromReadable } from '@react-router/node'; import type { AppLoadContext, EntryContext } from "react-router";
import { isbot } from 'isbot';
import type { RenderToPipeableStreamOptions } from 'react-dom/server'; import { createReadableStreamFromReadable } from "@react-router/node";
import { renderToPipeableStream } from 'react-dom/server'; import { isbot } from "isbot";
import type { AppLoadContext, EntryContext } from 'react-router'; import { PassThrough } from "node:stream";
import { ServerRouter } from 'react-router'; import { renderToPipeableStream } from "react-dom/server";
import { ServerRouter } from "react-router";
export const streamTimeout = 5_000; export const streamTimeout = 5_000;
export default function handleRequest( export default function handleRequest(
@@ -12,18 +13,16 @@ export default function handleRequest(
responseStatusCode: number, responseStatusCode: number,
responseHeaders: Headers, responseHeaders: Headers,
routerContext: EntryContext, routerContext: EntryContext,
loadContext: AppLoadContext, _loadContext: AppLoadContext,
) { ) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let shellRendered = false; let shellRendered = false;
const userAgent = request.headers.get('user-agent'); const userAgent = request.headers.get("user-agent");
// Ensure requests from bots and SPA Mode renders wait for all content to load before responding // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
const readyOption: keyof RenderToPipeableStreamOptions = const readyOption: keyof RenderToPipeableStreamOptions =
(userAgent && isbot(userAgent)) || routerContext.isSpaMode (userAgent && isbot(userAgent)) || routerContext.isSpaMode ? "onAllReady" : "onShellReady";
? 'onAllReady'
: 'onShellReady';
const { pipe, abort } = renderToPipeableStream( const { pipe, abort } = renderToPipeableStream(
<ServerRouter context={routerContext} url={request.url} />, <ServerRouter context={routerContext} url={request.url} />,
@@ -33,7 +32,7 @@ export default function handleRequest(
const body = new PassThrough(); const body = new PassThrough();
const stream = createReadableStreamFromReadable(body); const stream = createReadableStreamFromReadable(body);
responseHeaders.set('Content-Type', 'text/html'); responseHeaders.set("Content-Type", "text/html");
resolve( resolve(
new Response(stream, { new Response(stream, {
@@ -48,7 +47,6 @@ export default function handleRequest(
reject(error); reject(error);
}, },
onError(error: unknown) { onError(error: unknown) {
// biome-ignore lint/style/noParameterAssign: Lazy
responseStatusCode = 500; responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log // Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll // errors encountered during initial shell rendering since they'll
+50 -67
View File
@@ -1,16 +1,17 @@
/** biome-ignore-all lint/correctness/noNestedComponentDefinitions: Wtf? */ import { faker } from "@faker-js/faker";
import { eq } from "drizzle-orm";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { data, type ShouldRevalidateFunction, useSubmit } from "react-router";
import { ExternalScriptsHandle } from "remix-utils/external-scripts";
import { faker } from '@faker-js/faker'; import { EphemeralNodeInsert, ephemeralNodes } from "~/server/db/schema";
import { eq } from 'drizzle-orm'; import { useLiveData } from "~/utils/live-data";
import { Loader2 } from 'lucide-react';
import { useEffect, useState } from 'react'; import type { Route } from "./+types/console";
import { data, type ShouldRevalidateFunction, useSubmit } from 'react-router';
import { ExternalScriptsHandle } from 'remix-utils/external-scripts'; import UserPrompt from "./user-prompt";
import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema'; import XTerm from "./xterm.client";
import { useLiveData } from '~/utils/live-data';
import type { Route } from './+types/console';
import UserPrompt from './user-prompt';
import XTerm from './xterm.client';
export const shouldRevalidate: ShouldRevalidateFunction = () => { export const shouldRevalidate: ShouldRevalidateFunction = () => {
return false; return false;
@@ -18,28 +19,25 @@ export const shouldRevalidate: ShouldRevalidateFunction = () => {
export async function loader({ request, context }: Route.LoaderArgs) { export async function loader({ request, context }: Route.LoaderArgs) {
const origin = new URL(request.url).origin; const origin = new URL(request.url).origin;
const assets = ['/wasm_exec.js', '/hp_ssh.wasm']; const assets = ["/wasm_exec.js", "/hp_ssh.wasm"];
const missing: string[] = []; const missing: string[] = [];
for (const file of assets) { for (const file of assets) {
const res = await fetch(`${origin}${file}`, { method: 'HEAD' }); const res = await fetch(`${origin}${file}`, { method: "HEAD" });
if (!res.ok) missing.push(file); if (!res.ok) missing.push(file);
} }
if (missing.length > 0) { if (missing.length > 0) {
throw data('WebSSH is not configured in this build.', 405); throw data("WebSSH is not configured in this build.", 405);
} }
if (!context.agents?.agentID()) { if (!context.agents?.agentID()) {
throw data( throw data("WebSSH is only available with the Headplane agent integration", 400);
'WebSSH is only available with the Headplane agent integration',
400,
);
} }
const session = await context.sessions.auth(request); const session = await context.sessions.auth(request);
if (session.user.subject === 'unknown-non-oauth') { if (session.user.subject === "unknown-non-oauth") {
throw data('Only OAuth users are allowed to use WebSSH', 403); throw data("Only OAuth users are allowed to use WebSSH", 403);
} }
const api = context.hsApi.getRuntimeClient(session.api_key); const api = context.hsApi.getRuntimeClient(session.api_key);
@@ -50,7 +48,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
// pre-authkey which REQUIRES a user ID, meaning the user has to have // pre-authkey which REQUIRES a user ID, meaning the user has to have
// authenticated with Headscale first. // authenticated with Headscale first.
const lookup = users.find((u) => { const lookup = users.find((u) => {
const subject = u.providerId?.split('/').pop(); const subject = u.providerId?.split("/").pop();
if (!subject) { if (!subject) {
return false; return false;
} }
@@ -58,10 +56,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
}); });
if (!lookup) { if (!lookup) {
throw data( throw data(`User with subject ${session.user.subject} not found within Headscale`, 404);
`User with subject ${session.user.subject} not found within Headscale`,
404,
);
} }
const preAuthKey = await api.createPreAuthKey( const preAuthKey = await api.createPreAuthKey(
@@ -80,10 +75,10 @@ export async function loader({ request, context }: Route.LoaderArgs) {
// ); // );
const qp = new URL(request.url).searchParams; const qp = new URL(request.url).searchParams;
const username = qp.get('username') || undefined; const username = qp.get("username") || undefined;
const hostname = qp.get('hostname') || undefined; const hostname = qp.get("hostname") || undefined;
if (!hostname) { if (!hostname) {
throw data('Missing required parameter: hostname', 400); throw data("Missing required parameter: hostname", 400);
} }
if (!username) { if (!username) {
@@ -164,22 +159,19 @@ function generateHostname(username: string) {
export async function action({ request, context }: Route.ActionArgs) { export async function action({ request, context }: Route.ActionArgs) {
await context.sessions.auth(request); await context.sessions.auth(request);
if (!context.agents?.agentID()) { if (!context.agents?.agentID()) {
throw data( throw data("WebSSH is only available with the Headplane agent integration", 400);
'WebSSH is only available with the Headplane agent integration',
400,
);
} }
const form = await request.formData(); const form = await request.formData();
const nodeKey = form.get('node_key'); const nodeKey = form.get("node_key");
const authKey = form.get('auth_key'); const authKey = form.get("auth_key");
if (nodeKey === null || typeof nodeKey !== 'string') { if (nodeKey === null || typeof nodeKey !== "string") {
throw data('Missing node_key', 400); throw data("Missing node_key", 400);
} }
if (authKey === null || typeof authKey !== 'string') { if (authKey === null || typeof authKey !== "string") {
throw data('Missing auth_key', 400); throw data("Missing auth_key", 400);
} }
await context.db await context.db
@@ -192,27 +184,25 @@ export async function action({ request, context }: Route.ActionArgs) {
export const links: Route.LinksFunction = () => [ export const links: Route.LinksFunction = () => [
{ {
rel: 'preload', rel: "preload",
href: '/hp_ssh.wasm', href: "/hp_ssh.wasm",
as: 'fetch', as: "fetch",
type: 'application/wasm', type: "application/wasm",
crossOrigin: 'anonymous', crossOrigin: "anonymous",
}, },
]; ];
export const handle: ExternalScriptsHandle = { export const handle: ExternalScriptsHandle = {
scripts: [ scripts: [
{ {
src: '/wasm_exec.js', src: "/wasm_exec.js",
crossOrigin: 'anonymous', crossOrigin: "anonymous",
preload: true, preload: true,
}, },
], ],
}; };
export default function Page({ export default function Page({ loaderData: { ipnDetails, sshDetails } }: Route.ComponentProps) {
loaderData: { ipnDetails, sshDetails },
}: Route.ComponentProps) {
const submit = useSubmit(); const submit = useSubmit();
const { pause } = useLiveData(); const { pause } = useLiveData();
@@ -226,15 +216,12 @@ export default function Page({
pause(); pause();
const go = new Go(); // Go is defined by wasm_exec.js const go = new Go(); // Go is defined by wasm_exec.js
WebAssembly.instantiateStreaming( WebAssembly.instantiateStreaming(fetch("/hp_ssh.wasm"), go.importObject).then((value) => {
fetch('/hp_ssh.wasm'),
go.importObject,
).then((value) => {
go.run(value.instance); go.run(value.instance);
const handle = TsWasmNet(ipnDetails, { const handle = TsWasmNet(ipnDetails, {
NotifyState: (state) => { NotifyState: (state) => {
console.log('State changed:', state); console.log("State changed:", state);
if (state === 'Running') { if (state === "Running") {
setIpn(handle); setIpn(handle);
} }
}, },
@@ -253,15 +240,15 @@ export default function Page({
node_key: netmap.NodeKey, node_key: netmap.NodeKey,
auth_key: ipnDetails.PreAuthKey, auth_key: ipnDetails.PreAuthKey,
}, },
{ method: 'POST' }, { method: "POST" },
); );
} }
}, },
NotifyBrowseToURL: (url) => { NotifyBrowseToURL: (url) => {
console.log('Browse to URL:', url); console.log("Browse to URL:", url);
}, },
NotifyPanicRecover: (message) => { NotifyPanicRecover: (message) => {
console.error('Panic recover:', message); console.error("Panic recover:", message);
}, },
}); });
@@ -274,18 +261,14 @@ export default function Page({
} }
return ( return (
<div className="w-screen h-screen bg-headplane-900"> <div className="bg-headplane-900 h-screen w-screen">
{ipn === null ? ( {ipn === null ? (
<div className="mx-auto h-screen flex items-center justify-center"> <div className="mx-auto flex h-screen items-center justify-center">
<Loader2 className="animate-spin size-10 text-headplane-50" /> <Loader2 className="text-headplane-50 size-10 animate-spin" />
</div> </div>
) : ( ) : (
<div className="flex flex-col h-screen"> <div className="flex h-screen flex-col">
<XTerm <XTerm hostname={sshDetails.hostname} ipn={ipn} username={sshDetails.username} />
hostname={sshDetails.hostname}
ipn={ipn}
username={sshDetails.username}
/>
</div> </div>
)} )}
</div> </div>
+1 -7
View File
@@ -8,8 +8,7 @@
--height-editor: calc(100vh - 20rem); --height-editor: calc(100vh - 20rem);
--font-sans: Inter, -apple-system, BlinkMacSystemFont, Helvetica, Arial, --font-sans: Inter, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
sans-serif;
--transition-duration-25: 25ms; --transition-duration-25: 25ms;
--transition-duration-50: 50ms; --transition-duration-50: 50ms;
@@ -68,22 +67,17 @@
width: 100%; width: 100%;
max-width: 96rem; /* 1536px - constrain on large displays */ max-width: 96rem; /* 1536px - constrain on large displays */
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
@media (width >= theme(--breakpoint-sm)) { @media (width >= theme(--breakpoint-sm)) {
padding-inline: 2rem; padding-inline: 2rem;
} }
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
@media (width >= theme(--breakpoint-lg)) { @media (width >= theme(--breakpoint-lg)) {
padding-inline: 4rem; padding-inline: 4rem;
} }
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
@media (width >= theme(--breakpoint-xl)) { @media (width >= theme(--breakpoint-xl)) {
padding-inline: 5rem; padding-inline: 5rem;
} }
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
@media (width >= theme(--breakpoint-2xl)) { @media (width >= theme(--breakpoint-2xl)) {
padding-inline: 6rem; padding-inline: 6rem;
} }
-48
View File
@@ -1,48 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"includes": ["**", "!app/wasm_exec.js"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"lineWidth": 80,
"lineEnding": "lf"
},
"assist": {
"actions": {
"source": {
"organizeImports": "on",
"useSortedAttributes": "on"
}
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"useImportType": "off",
"noNonNullAssertion": "off"
},
"correctness": {
"useExhaustiveDependencies": "off"
},
"suspicious": {
"noExplicitAny": "warn"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double"
}
}
}
+7 -3
View File
@@ -6,6 +6,7 @@ sole maintainer, I have a few guidelines which help make it significantly easier
for me to review and merge your contributions. for me to review and merge your contributions.
## Contribution Types ## Contribution Types
- **Bug Reports/Feature Requests**: If you find a bug, please open an - **Bug Reports/Feature Requests**: If you find a bug, please open an
[issue](https://github.com/tale/headplane/issues) using one of the predefined [issue](https://github.com/tale/headplane/issues) using one of the predefined
templates. Issues are only for bug reports and feature requests, not problems templates. Issues are only for bug reports and feature requests, not problems
@@ -16,11 +17,12 @@ that you encounter from misconfiguration or usage. Those belong in the
would like to contribute examples for setting up Headplane, please open a PR would like to contribute examples for setting up Headplane, please open a PR
and I will review it and possibly make changes. and I will review it and possibly make changes.
- **Code Contributions**: Code contributions are done via PRs but *must* be - **Code Contributions**: Code contributions are done via PRs but _must_ be
linked to an issue or a feature request (you can make one if it doesn't exist). linked to an issue or a feature request (you can make one if it doesn't exist).
Please tag them with the appropriate labels when opening an issue or PR. Please tag them with the appropriate labels when opening an issue or PR.
### Code Contribution Restrictions ### Code Contribution Restrictions
- **No Large Refactors**: I am not interested in large refactors of the codebase - **No Large Refactors**: I am not interested in large refactors of the codebase
because I've already had do to this a lot and it becomes a headache to review, because I've already had do to this a lot and it becomes a headache to review,
maintain, and merge. This also means you should split up multiple contributions maintain, and merge. This also means you should split up multiple contributions
@@ -37,8 +39,9 @@ to do so. If you want to make a breaking change, please open an issue first
and discuss it with me. and discuss it with me.
### Code Style ### Code Style
This is very easy and self-explanatory. [Biome](https://biomejs.dev) is used as
a linter and formatter for the TypeScript, while Go's default formatting and This is very easy and self-explanatory. [oxlint/oxfmt](https://oxc.rs) is used
as a linter and formatter for the TypeScript, while Go's default formatting and
lint tooling are used within `agent/` code. I've setup a git hook to run lint tooling are used within `agent/` code. I've setup a git hook to run
before commit to make these changes automatically. before commit to make these changes automatically.
@@ -47,6 +50,7 @@ before commit to make these changes automatically.
> but instead we can discuss them and see if we can come to a compromise. > but instead we can discuss them and see if we can come to a compromise.
### Contribution Tooling ### Contribution Tooling
If you plan to work with the WASM SSH agent, you will need to install If you plan to work with the WASM SSH agent, you will need to install
`mkcert` or an equivalent to create certificates in `./test/caddy/certs`. `mkcert` or an equivalent to create certificates in `./test/caddy/certs`.
It expects a `localhost.pem` and `localhost-key.pem` file to be present in that It expects a `localhost.pem` and `localhost-key.pem` file to be present in that
+6 -2
View File
@@ -1,8 +1,12 @@
pre-commit: pre-commit:
commands: commands:
check: lint:
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files} run: pnpm lint --fix {staged_files}
stage_fixed: true
format:
glob: "*"
run: pnpm format {staged_files}
stage_fixed: true stage_fixed: true
check_go: check_go:
glob: "*.go" glob: "*.go"
+9 -6
View File
@@ -1,9 +1,9 @@
{ {
"name": "headplane", "name": "headplane",
"private": true,
"sideEffects": false,
"version": "0.6.1", "version": "0.6.1",
"private": true,
"type": "module", "type": "module",
"sideEffects": false,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"build": "react-router build", "build": "react-router build",
@@ -15,13 +15,14 @@
"docs:dev": "vitepress dev docs", "docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs", "docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs", "docs:preview": "vitepress preview docs",
"gen:hashes": "tsx tests/generate-openapi-hashes.ts" "gen:hashes": "tsx tests/generate-openapi-hashes.ts",
"lint": "oxlint",
"format": "oxfmt"
}, },
"dependencies": { "dependencies": {
"@libsql/client": "0.15.15" "@libsql/client": "0.15.15"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.5",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
@@ -62,6 +63,9 @@
"mime": "^4.1.0", "mime": "^4.1.0",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"openid-client": "6.8.1", "openid-client": "6.8.1",
"oxfmt": "^0.24.0",
"oxlint": "^1.39.0",
"oxlint-tsgolint": "^0.11.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"react": "19.2.0", "react": "19.2.0",
"react-aria": "3.44.0", "react-aria": "3.44.0",
@@ -89,11 +93,11 @@
"vitest": "^4.0.9", "vitest": "^4.0.9",
"yaml": "2.8.1" "yaml": "2.8.1"
}, },
"packageManager": "pnpm@10.4.0",
"engines": { "engines": {
"node": ">=22.18 <23", "node": ">=22.18 <23",
"pnpm": ">=10.4 <11" "pnpm": ">=10.4 <11"
}, },
"packageManager": "pnpm@10.4.0",
"pnpm": { "pnpm": {
"supportedArchitectures": { "supportedArchitectures": {
"os": [ "os": [
@@ -106,7 +110,6 @@
] ]
}, },
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@biomejs/biome",
"@tailwindcss/oxide", "@tailwindcss/oxide",
"better-sqlite3", "better-sqlite3",
"esbuild", "esbuild",
+244 -91
View File
@@ -17,9 +17,6 @@ importers:
specifier: 0.15.15 specifier: 0.15.15
version: 0.15.15(bufferutil@4.0.9)(utf-8-validate@5.0.10) version: 0.15.15(bufferutil@4.0.9)(utf-8-validate@5.0.10)
devDependencies: devDependencies:
'@biomejs/biome':
specifier: ^2.3.5
version: 2.3.5
'@dnd-kit/core': '@dnd-kit/core':
specifier: ^6.3.1 specifier: ^6.3.1
version: 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -140,6 +137,15 @@ importers:
openid-client: openid-client:
specifier: 6.8.1 specifier: 6.8.1
version: 6.8.1 version: 6.8.1
oxfmt:
specifier: ^0.24.0
version: 0.24.0
oxlint:
specifier: ^1.39.0
version: 1.39.0(oxlint-tsgolint@0.11.1)
oxlint-tsgolint:
specifier: ^0.11.1
version: 0.11.1
postcss: postcss:
specifier: ^8.5.6 specifier: ^8.5.6
version: 8.5.6 version: 8.5.6
@@ -389,59 +395,6 @@ packages:
'@balena/dockerignore@1.0.2': '@balena/dockerignore@1.0.2':
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
'@biomejs/biome@2.3.5':
resolution: {integrity: sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@2.3.5':
resolution: {integrity: sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@2.3.5':
resolution: {integrity: sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@2.3.5':
resolution: {integrity: sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-arm64@2.3.5':
resolution: {integrity: sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@2.3.5':
resolution: {integrity: sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-linux-x64@2.3.5':
resolution: {integrity: sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@2.3.5':
resolution: {integrity: sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@2.3.5':
resolution: {integrity: sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
'@codemirror/autocomplete@6.18.2': '@codemirror/autocomplete@6.18.2':
resolution: {integrity: sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==} resolution: {integrity: sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==}
peerDependencies: peerDependencies:
@@ -1230,6 +1183,116 @@ packages:
'@oxc-project/types@0.101.0': '@oxc-project/types@0.101.0':
resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==} resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==}
'@oxfmt/darwin-arm64@0.24.0':
resolution: {integrity: sha512-aYXuGf/yq8nsyEcHindGhiz9I+GEqLkVq8CfPbd+6VE259CpPEH+CaGHEO1j6vIOmNr8KHRq+IAjeRO2uJpb8A==}
cpu: [arm64]
os: [darwin]
'@oxfmt/darwin-x64@0.24.0':
resolution: {integrity: sha512-vs3b8Bs53hbiNvcNeBilzE/+IhDTWKjSBB3v/ztr664nZk65j0xr+5IHMBNz3CFppmX7o/aBta2PxY+t+4KoPg==}
cpu: [x64]
os: [darwin]
'@oxfmt/linux-arm64-gnu@0.24.0':
resolution: {integrity: sha512-ItPDOPoQ0wLj/s8osc5ch57uUcA1Wk8r0YdO8vLRpXA3UNg7KPOm1vdbkIZRRiSUphZcuX5ioOEetEK8H7RlTw==}
cpu: [arm64]
os: [linux]
'@oxfmt/linux-arm64-musl@0.24.0':
resolution: {integrity: sha512-JkQO3WnQjQTJONx8nxdgVBfl6BBFfpp9bKhChYhWeakwJdr7QPOAWJ/v3FGZfr0TbqINwnNR74aVZayDDRyXEA==}
cpu: [arm64]
os: [linux]
'@oxfmt/linux-x64-gnu@0.24.0':
resolution: {integrity: sha512-N/SXlFO+2kak5gMt0oxApi0WXQDhwA0PShR0UbkY0PwtHjfSiDqJSOumyNqgQVoroKr1GNnoRmUqjZIz6DKIcw==}
cpu: [x64]
os: [linux]
'@oxfmt/linux-x64-musl@0.24.0':
resolution: {integrity: sha512-WM0pek5YDCQf50XQ7GLCE9sMBCMPW/NPAEPH/Hx6Qyir37lEsP4rUmSECo/QFNTU6KBc9NnsviAyJruWPpCMXw==}
cpu: [x64]
os: [linux]
'@oxfmt/win32-arm64@0.24.0':
resolution: {integrity: sha512-vFCseli1KWtwdHrVlT/jWfZ8jP8oYpnPPEjI23mPLW8K/6GEJmmvy0PZP5NpWUFNTzX0lqie58XnrATJYAe9Xw==}
cpu: [arm64]
os: [win32]
'@oxfmt/win32-x64@0.24.0':
resolution: {integrity: sha512-0tmlNzcyewAnauNeBCq0xmAkmiKzl+H09p0IdHy+QKrTQdtixtf+AOjDAADbRfihkS+heF15Pjc4IyJMdAAJjw==}
cpu: [x64]
os: [win32]
'@oxlint-tsgolint/darwin-arm64@0.11.1':
resolution: {integrity: sha512-UJIOFeJZpFTJIGS+bMdFXcvjslvnXBEouMvzynfQD7RTazcFIRLbokYgEbhrN2P6B352Ut1TUtvR0CLAp/9QfA==}
cpu: [arm64]
os: [darwin]
'@oxlint-tsgolint/darwin-x64@0.11.1':
resolution: {integrity: sha512-68O8YvexIm+ISZKl2vBFII1dMfLrteDyPcuCIecDuiBIj2tV0KYq13zpSCMz4dvJUWJW6RmOOGZKrkkvOAy6uQ==}
cpu: [x64]
os: [darwin]
'@oxlint-tsgolint/linux-arm64@0.11.1':
resolution: {integrity: sha512-hXBInrFxPNbPPbPQYozo8YpSsFFYdtHBWRUiLMxul71vTy1CdSA7H5Qq2KbrKomr/ASmhvIDVAQZxh9hIJNHMA==}
cpu: [arm64]
os: [linux]
'@oxlint-tsgolint/linux-x64@0.11.1':
resolution: {integrity: sha512-aMaGctlwrJhaIQPOdVJR+AGHZGPm4D1pJ457l0SqZt4dLXAhuUt2ene6cUUGF+864R7bDyFVGZqbZHODYpENyA==}
cpu: [x64]
os: [linux]
'@oxlint-tsgolint/win32-arm64@0.11.1':
resolution: {integrity: sha512-ipOs6kKo8fz5n5LSHvcbyZFmEpEIsh2m7+B03RW3jGjBEPMiXb4PfKNuxnusFYTtJM9WaR3bCVm5UxeJTA8r3w==}
cpu: [arm64]
os: [win32]
'@oxlint-tsgolint/win32-x64@0.11.1':
resolution: {integrity: sha512-m2apsAXg6qU3ulQG45W/qshyEpOjoL+uaQyXJG5dBoDoa66XPtCaSkBlKltD0EwGu0aoB8lM4I5I3OzQ6raNhw==}
cpu: [x64]
os: [win32]
'@oxlint/darwin-arm64@1.39.0':
resolution: {integrity: sha512-lT3hNhIa02xCujI6YGgjmYGg3Ht/X9ag5ipUVETaMpx5Rd4BbTNWUPif1WN1YZHxt3KLCIqaAe7zVhatv83HOQ==}
cpu: [arm64]
os: [darwin]
'@oxlint/darwin-x64@1.39.0':
resolution: {integrity: sha512-UT+rfTWd+Yr7iJeSLd/7nF8X4gTYssKh+n77hxl6Oilp3NnG1CKRHxZDy3o3lIBnwgzJkdyUAiYWO1bTMXQ1lA==}
cpu: [x64]
os: [darwin]
'@oxlint/linux-arm64-gnu@1.39.0':
resolution: {integrity: sha512-qocBkvS2V6rH0t9AT3DfQunMnj3xkM7srs5/Ycj2j5ZqMoaWd/FxHNVJDFP++35roKSvsRJoS0mtA8/77jqm6Q==}
cpu: [arm64]
os: [linux]
'@oxlint/linux-arm64-musl@1.39.0':
resolution: {integrity: sha512-arZzAc1PPcz9epvGBBCMHICeyQloKtHX3eoOe62B3Dskn7gf6Q14wnDHr1r9Vp4vtcBATNq6HlKV14smdlC/qA==}
cpu: [arm64]
os: [linux]
'@oxlint/linux-x64-gnu@1.39.0':
resolution: {integrity: sha512-ZVt5qsECpuNprdWxAPpDBwoixr1VTcZ4qAEQA2l/wmFyVPDYFD3oBY/SWACNnWBddMrswjTg9O8ALxYWoEpmXw==}
cpu: [x64]
os: [linux]
'@oxlint/linux-x64-musl@1.39.0':
resolution: {integrity: sha512-pB0hlGyKPbxr9NMIV783lD6cWL3MpaqnZRM9MWni4yBdHPTKyFNYdg5hGD0Bwg+UP4S2rOevq/+OO9x9Bi7E6g==}
cpu: [x64]
os: [linux]
'@oxlint/win32-arm64@1.39.0':
resolution: {integrity: sha512-Gg2SFaJohI9+tIQVKXlPw3FsPQFi/eCSWiCgwPtPn5uzQxHRTeQEZKuluz1fuzR5U70TXubb2liZi4Dgl8LJQA==}
cpu: [arm64]
os: [win32]
'@oxlint/win32-x64@1.39.0':
resolution: {integrity: sha512-sbi25lfj74hH+6qQtb7s1wEvd1j8OQbTaH8v3xTcDjrwm579Cyh0HBv1YSZ2+gsnVwfVDiCTL1D0JsNqYXszVA==}
cpu: [x64]
os: [win32]
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -3601,6 +3664,25 @@ packages:
openid-client@6.8.1: openid-client@6.8.1:
resolution: {integrity: sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==} resolution: {integrity: sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==}
oxfmt@0.24.0:
resolution: {integrity: sha512-UjeM3Peez8Tl7IJ9s5UwAoZSiDRMww7BEc21gDYxLq3S3/KqJnM3mjNxsoSHgmBvSeX6RBhoVc2MfC/+96RdSw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
oxlint-tsgolint@0.11.1:
resolution: {integrity: sha512-WulCp+0/6RvpM4zPv+dAXybf03QvRA8ATxaBlmj4XMIQqTs5jeq3cUTk48WCt4CpLwKhyyGZPHmjLl1KHQ/cvA==}
hasBin: true
oxlint@1.39.0:
resolution: {integrity: sha512-wSiLr0wjG+KTU6c1LpVoQk7JZ7l8HCKlAkVDVTJKWmCGazsNxexxnOXl7dsar92mQcRnzko5g077ggP3RINSjA==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
oxlint-tsgolint: '>=0.10.0'
peerDependenciesMeta:
oxlint-tsgolint:
optional: true
p-map@7.0.4: p-map@7.0.4:
resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -4077,6 +4159,10 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tinypool@2.0.0:
resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==}
engines: {node: ^20.0.0 || >=22.0.0}
tinyrainbow@3.0.3: tinyrainbow@3.0.3:
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@@ -4662,41 +4748,6 @@ snapshots:
'@balena/dockerignore@1.0.2': {} '@balena/dockerignore@1.0.2': {}
'@biomejs/biome@2.3.5':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 2.3.5
'@biomejs/cli-darwin-x64': 2.3.5
'@biomejs/cli-linux-arm64': 2.3.5
'@biomejs/cli-linux-arm64-musl': 2.3.5
'@biomejs/cli-linux-x64': 2.3.5
'@biomejs/cli-linux-x64-musl': 2.3.5
'@biomejs/cli-win32-arm64': 2.3.5
'@biomejs/cli-win32-x64': 2.3.5
'@biomejs/cli-darwin-arm64@2.3.5':
optional: true
'@biomejs/cli-darwin-x64@2.3.5':
optional: true
'@biomejs/cli-linux-arm64-musl@2.3.5':
optional: true
'@biomejs/cli-linux-arm64@2.3.5':
optional: true
'@biomejs/cli-linux-x64-musl@2.3.5':
optional: true
'@biomejs/cli-linux-x64@2.3.5':
optional: true
'@biomejs/cli-win32-arm64@2.3.5':
optional: true
'@biomejs/cli-win32-x64@2.3.5':
optional: true
'@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.7)(@lezer/common@1.3.0)': '@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.7)(@lezer/common@1.3.0)':
dependencies: dependencies:
'@codemirror/language': 6.11.3 '@codemirror/language': 6.11.3
@@ -5363,6 +5414,72 @@ snapshots:
'@oxc-project/types@0.101.0': {} '@oxc-project/types@0.101.0': {}
'@oxfmt/darwin-arm64@0.24.0':
optional: true
'@oxfmt/darwin-x64@0.24.0':
optional: true
'@oxfmt/linux-arm64-gnu@0.24.0':
optional: true
'@oxfmt/linux-arm64-musl@0.24.0':
optional: true
'@oxfmt/linux-x64-gnu@0.24.0':
optional: true
'@oxfmt/linux-x64-musl@0.24.0':
optional: true
'@oxfmt/win32-arm64@0.24.0':
optional: true
'@oxfmt/win32-x64@0.24.0':
optional: true
'@oxlint-tsgolint/darwin-arm64@0.11.1':
optional: true
'@oxlint-tsgolint/darwin-x64@0.11.1':
optional: true
'@oxlint-tsgolint/linux-arm64@0.11.1':
optional: true
'@oxlint-tsgolint/linux-x64@0.11.1':
optional: true
'@oxlint-tsgolint/win32-arm64@0.11.1':
optional: true
'@oxlint-tsgolint/win32-x64@0.11.1':
optional: true
'@oxlint/darwin-arm64@1.39.0':
optional: true
'@oxlint/darwin-x64@1.39.0':
optional: true
'@oxlint/linux-arm64-gnu@1.39.0':
optional: true
'@oxlint/linux-arm64-musl@1.39.0':
optional: true
'@oxlint/linux-x64-gnu@1.39.0':
optional: true
'@oxlint/linux-x64-musl@1.39.0':
optional: true
'@oxlint/win32-arm64@1.39.0':
optional: true
'@oxlint/win32-x64@1.39.0':
optional: true
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@@ -8043,6 +8160,40 @@ snapshots:
jose: 6.1.2 jose: 6.1.2
oauth4webapi: 3.8.2 oauth4webapi: 3.8.2
oxfmt@0.24.0:
dependencies:
tinypool: 2.0.0
optionalDependencies:
'@oxfmt/darwin-arm64': 0.24.0
'@oxfmt/darwin-x64': 0.24.0
'@oxfmt/linux-arm64-gnu': 0.24.0
'@oxfmt/linux-arm64-musl': 0.24.0
'@oxfmt/linux-x64-gnu': 0.24.0
'@oxfmt/linux-x64-musl': 0.24.0
'@oxfmt/win32-arm64': 0.24.0
'@oxfmt/win32-x64': 0.24.0
oxlint-tsgolint@0.11.1:
optionalDependencies:
'@oxlint-tsgolint/darwin-arm64': 0.11.1
'@oxlint-tsgolint/darwin-x64': 0.11.1
'@oxlint-tsgolint/linux-arm64': 0.11.1
'@oxlint-tsgolint/linux-x64': 0.11.1
'@oxlint-tsgolint/win32-arm64': 0.11.1
'@oxlint-tsgolint/win32-x64': 0.11.1
oxlint@1.39.0(oxlint-tsgolint@0.11.1):
optionalDependencies:
'@oxlint/darwin-arm64': 1.39.0
'@oxlint/darwin-x64': 1.39.0
'@oxlint/linux-arm64-gnu': 1.39.0
'@oxlint/linux-arm64-musl': 1.39.0
'@oxlint/linux-x64-gnu': 1.39.0
'@oxlint/linux-x64-musl': 1.39.0
'@oxlint/win32-arm64': 1.39.0
'@oxlint/win32-x64': 1.39.0
oxlint-tsgolint: 0.11.1
p-map@7.0.4: {} p-map@7.0.4: {}
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
@@ -8653,6 +8804,8 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3 picomatch: 4.0.3
tinypool@2.0.0: {}
tinyrainbow@3.0.3: {} tinyrainbow@3.0.3: {}
tmp@0.2.5: {} tmp@0.2.5: {}
+33 -35
View File
@@ -1,82 +1,80 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from "vitest";
import type { PartialHeadplaneConfigWithPaths } from '~/server/config/config-schema';
import { ConfigError } from '~/server/config/error';
import { loadConfigKeyPaths } from '~/server/config/load';
import { createFakeFile } from '../setup/overlay-fs';
describe('Configuration secret path handling', () => { import type { PartialHeadplaneConfigWithPaths } from "~/server/config/config-schema";
test('should correctly substitute server.cookie_secret', async () => {
createFakeFile('/secrets/cookie_secret.txt', 'supersecretcookievalue'); import { ConfigError } from "~/server/config/error";
import { loadConfigKeyPaths } from "~/server/config/load";
import { createFakeFile } from "../setup/overlay-fs";
describe("Configuration secret path handling", () => {
test("should correctly substitute server.cookie_secret", async () => {
createFakeFile("/secrets/cookie_secret.txt", "supersecretcookievalue");
const config = { const config = {
server: { server: {
cookie_secret_path: '/secrets/cookie_secret.txt', cookie_secret_path: "/secrets/cookie_secret.txt",
}, },
} as PartialHeadplaneConfigWithPaths; } as PartialHeadplaneConfigWithPaths;
await loadConfigKeyPaths(config); await loadConfigKeyPaths(config);
expect(config.server?.cookie_secret).toBe('supersecretcookievalue'); expect(config.server?.cookie_secret).toBe("supersecretcookievalue");
}); });
test('should throw error for missing secret file', async () => { test("should throw error for missing secret file", async () => {
const config = { const config = {
server: { server: {
cookie_secret_path: '/secrets/missing_cookie_secret.txt', cookie_secret_path: "/secrets/missing_cookie_secret.txt",
}, },
} as PartialHeadplaneConfigWithPaths; } as PartialHeadplaneConfigWithPaths;
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject( await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
ConfigError.from('MISSING_SECRET_FILE', { ConfigError.from("MISSING_SECRET_FILE", {
pathKey: 'server.cookie_secret_path', pathKey: "server.cookie_secret_path",
filePath: '/secrets/missing_cookie_secret.txt', filePath: "/secrets/missing_cookie_secret.txt",
}), }),
); );
}); });
test('should throw error for conflicting secret path and field', async () => { test("should throw error for conflicting secret path and field", async () => {
const config = { const config = {
server: { server: {
cookie_secret: 'explicitsecretvalue', cookie_secret: "explicitsecretvalue",
cookie_secret_path: '/secrets/cookie_secret.txt', cookie_secret_path: "/secrets/cookie_secret.txt",
}, },
} as PartialHeadplaneConfigWithPaths; } as PartialHeadplaneConfigWithPaths;
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject( await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
ConfigError.from('CONFLICTING_SECRET_PATH_FIELD', { ConfigError.from("CONFLICTING_SECRET_PATH_FIELD", {
fieldName: 'server.cookie_secret', fieldName: "server.cookie_secret",
}), }),
); );
}); });
test('should correctly interpolate env vars in secret paths', async () => { test("should correctly interpolate env vars in secret paths", async () => {
process.env.HP_TEST_COOKIE_SECRET_FILE = 'cookie_secret.txt'; process.env.HP_TEST_COOKIE_SECRET_FILE = "cookie_secret.txt";
createFakeFile( createFakeFile(`/secrets/${process.env.HP_TEST_COOKIE_SECRET_FILE}`, "envvarsecretvalue");
`/secrets/${process.env.HP_TEST_COOKIE_SECRET_FILE}`,
'envvarsecretvalue',
);
const config = { const config = {
server: { server: {
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation cookie_secret_path: "/secrets/${HP_TEST_COOKIE_SECRET_FILE}",
cookie_secret_path: '/secrets/${HP_TEST_COOKIE_SECRET_FILE}',
}, },
} as PartialHeadplaneConfigWithPaths; } as PartialHeadplaneConfigWithPaths;
await loadConfigKeyPaths(config); await loadConfigKeyPaths(config);
expect(config.server?.cookie_secret).toBe('envvarsecretvalue'); expect(config.server?.cookie_secret).toBe("envvarsecretvalue");
}); });
test('should throw error for missing interpolated env var in secret path', async () => { test("should throw error for missing interpolated env var in secret path", async () => {
const config = { const config = {
server: { server: {
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation cookie_secret_path: "/secrets/${MISSING_ENV_VAR}",
cookie_secret_path: '/secrets/${MISSING_ENV_VAR}',
}, },
} as PartialHeadplaneConfigWithPaths; } as PartialHeadplaneConfigWithPaths;
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject( await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
ConfigError.from('MISSING_INTERPOLATION_VARIABLE', { ConfigError.from("MISSING_INTERPOLATION_VARIABLE", {
pathKey: 'server.cookie_secret_path', pathKey: "server.cookie_secret_path",
variableName: 'MISSING_ENV_VAR', variableName: "MISSING_ENV_VAR",
}), }),
); );
}); });