mirror of
https://github.com/tale/headplane.git
synced 2026-08-18 00:57:54 +00:00
feat: switch to oxc linting/formatting
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"experimentalSortImports": {},
|
||||||
|
"experimentalTailwindcss": {},
|
||||||
|
"ignorePatterns": [],
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
+144
@@ -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": []
|
||||||
|
}
|
||||||
+54
-56
@@ -1,67 +1,65 @@
|
|||||||
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(
|
||||||
request: Request,
|
request: Request,
|
||||||
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} />,
|
||||||
{
|
{
|
||||||
[readyOption]() {
|
[readyOption]() {
|
||||||
shellRendered = true;
|
shellRendered = true;
|
||||||
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, {
|
||||||
headers: responseHeaders,
|
headers: responseHeaders,
|
||||||
status: responseStatusCode,
|
status: responseStatusCode,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
pipe(body);
|
pipe(body);
|
||||||
},
|
},
|
||||||
onShellError(error: unknown) {
|
onShellError(error: unknown) {
|
||||||
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
|
// reject and get logged in handleDocumentRequest.
|
||||||
// reject and get logged in handleDocumentRequest.
|
if (shellRendered) {
|
||||||
if (shellRendered) {
|
console.error(error);
|
||||||
console.error(error);
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// Abort the rendering stream after the `streamTimeout` so it has tine to
|
// Abort the rendering stream after the `streamTimeout` so it has tine to
|
||||||
// flush down the rejected boundaries
|
// flush down the rejected boundaries
|
||||||
setTimeout(abort, streamTimeout + 1000);
|
setTimeout(abort, streamTimeout + 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+225
-242
@@ -1,293 +1,276 @@
|
|||||||
/** 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
const users = await api.getUsers();
|
const users = await api.getUsers();
|
||||||
|
|
||||||
// MARK: This assumes that a user has authenticated with Headscale first
|
// MARK: This assumes that a user has authenticated with Headscale first
|
||||||
// Since the only way to enforce permissions via ACLs is to generate a
|
// Since the only way to enforce permissions via ACLs is to generate a
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
return subject === session.user.subject;
|
return subject === session.user.subject;
|
||||||
});
|
});
|
||||||
|
|
||||||
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(
|
||||||
lookup.id,
|
lookup.id,
|
||||||
true, // ephemeral
|
true, // ephemeral
|
||||||
false, // reusable
|
false, // reusable
|
||||||
new Date(Date.now() + 60 * 1000), // expiration: 1 minute
|
new Date(Date.now() + 60 * 1000), // expiration: 1 minute
|
||||||
null, // aclTags
|
null, // aclTags
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Enable config to enforce generate_authkeys capability
|
// TODO: Enable config to enforce generate_authkeys capability
|
||||||
// For now, any user is capable of WebSSH connections
|
// For now, any user is capable of WebSSH connections
|
||||||
// const check = await context.sessions.check(
|
// const check = await context.sessions.check(
|
||||||
// request,
|
// request,
|
||||||
// Capabilities.generate_authkeys,
|
// Capabilities.generate_authkeys,
|
||||||
// );
|
// );
|
||||||
|
|
||||||
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) {
|
||||||
return {
|
return {
|
||||||
ipnDetails: undefined,
|
ipnDetails: undefined,
|
||||||
sshDetails: {
|
sshDetails: {
|
||||||
username,
|
username,
|
||||||
hostname,
|
hostname,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're making a request to <url>/key?v=116 to check the CORS headers
|
// We're making a request to <url>/key?v=116 to check the CORS headers
|
||||||
const u = context.config.headscale.public_url ?? context.config.headscale.url;
|
const u = context.config.headscale.public_url ?? context.config.headscale.url;
|
||||||
// const res = await fetch(`${u}/key?v=116`, {
|
// const res = await fetch(`${u}/key?v=116`, {
|
||||||
// method: 'GET',
|
// method: 'GET',
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// const corsOrigin = res.headers.get('Access-Control-Allow-Origin');
|
// const corsOrigin = res.headers.get('Access-Control-Allow-Origin');
|
||||||
// const corsMethods = res.headers.get('Access-Control-Allow-Methods');
|
// const corsMethods = res.headers.get('Access-Control-Allow-Methods');
|
||||||
// const corsHeaders = res.headers.get('Access-Control-Allow-Headers');
|
// const corsHeaders = res.headers.get('Access-Control-Allow-Headers');
|
||||||
// console.log(corsOrigin, corsMethods, corsHeaders);
|
// console.log(corsOrigin, corsMethods, corsHeaders);
|
||||||
|
|
||||||
// if (!corsOrigin || !corsMethods || !corsHeaders) {
|
// if (!corsOrigin || !corsMethods || !corsHeaders) {
|
||||||
// throw data(
|
// throw data(
|
||||||
// 'Headscale server does not have the required CORS headers for WebSSH',
|
// 'Headscale server does not have the required CORS headers for WebSSH',
|
||||||
// 500,
|
// 500,
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const nodes = await api.getNodes();
|
const nodes = await api.getNodes();
|
||||||
const lookupNode = nodes.find((n) => n.givenName === hostname);
|
const lookupNode = nodes.find((n) => n.givenName === hostname);
|
||||||
if (!lookupNode) {
|
if (!lookupNode) {
|
||||||
throw data(`Node with hostname ${hostname} not found`, 404);
|
throw data(`Node with hostname ${hostname} not found`, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last thing is keeping track of the ephemeral node in the database
|
// Last thing is keeping track of the ephemeral node in the database
|
||||||
// because Headscale doesn't automatically delete ephemeral nodes???
|
// because Headscale doesn't automatically delete ephemeral nodes???
|
||||||
const [_ephemeralNode] = await context.db
|
const [_ephemeralNode] = await context.db
|
||||||
.insert(ephemeralNodes)
|
.insert(ephemeralNodes)
|
||||||
.values({
|
.values({
|
||||||
auth_key: preAuthKey.key,
|
auth_key: preAuthKey.key,
|
||||||
} satisfies EphemeralNodeInsert)
|
} satisfies EphemeralNodeInsert)
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ipnDetails: {
|
ipnDetails: {
|
||||||
PreAuthKey: preAuthKey.key,
|
PreAuthKey: preAuthKey.key,
|
||||||
Hostname: generateHostname(username),
|
Hostname: generateHostname(username),
|
||||||
ControlURL: u,
|
ControlURL: u,
|
||||||
},
|
},
|
||||||
|
|
||||||
sshDetails: {
|
sshDetails: {
|
||||||
username,
|
username,
|
||||||
hostname,
|
hostname,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateHostname(username: string) {
|
function generateHostname(username: string) {
|
||||||
const adjective = faker.word.adjective({
|
const adjective = faker.word.adjective({
|
||||||
length: {
|
length: {
|
||||||
min: 3,
|
min: 3,
|
||||||
max: 6,
|
max: 6,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const noun = faker.word.noun({
|
const noun = faker.word.noun({
|
||||||
length: {
|
length: {
|
||||||
min: 3,
|
min: 3,
|
||||||
max: 6,
|
max: 6,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return `ssh-${adjective}-${noun}-${username}`;
|
return `ssh-${adjective}-${noun}-${username}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
.update(ephemeralNodes)
|
.update(ephemeralNodes)
|
||||||
.set({
|
.set({
|
||||||
node_key: nodeKey,
|
node_key: nodeKey,
|
||||||
})
|
})
|
||||||
.where(eq(ephemeralNodes.auth_key, authKey));
|
.where(eq(ephemeralNodes.auth_key, authKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
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 },
|
const submit = useSubmit();
|
||||||
}: Route.ComponentProps) {
|
const { pause } = useLiveData();
|
||||||
const submit = useSubmit();
|
|
||||||
const { pause } = useLiveData();
|
|
||||||
|
|
||||||
const [ipn, setIpn] = useState<TsWasmNet | null>(null);
|
const [ipn, setIpn] = useState<TsWasmNet | null>(null);
|
||||||
const [nodeKey, setNodeKey] = useState<string | null>(null);
|
const [nodeKey, setNodeKey] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ipnDetails) {
|
if (!ipnDetails) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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.run(value.instance);
|
||||||
go.importObject,
|
const handle = TsWasmNet(ipnDetails, {
|
||||||
).then((value) => {
|
NotifyState: (state) => {
|
||||||
go.run(value.instance);
|
console.log("State changed:", state);
|
||||||
const handle = TsWasmNet(ipnDetails, {
|
if (state === "Running") {
|
||||||
NotifyState: (state) => {
|
setIpn(handle);
|
||||||
console.log('State changed:', state);
|
}
|
||||||
if (state === 'Running') {
|
},
|
||||||
setIpn(handle);
|
NotifyNetMap: (netmap) => {
|
||||||
}
|
// Only set NodeKey if it is not already set and then
|
||||||
},
|
// also dispatch that to the backend to track the
|
||||||
NotifyNetMap: (netmap) => {
|
// ephemeral node.
|
||||||
// Only set NodeKey if it is not already set and then
|
//
|
||||||
// also dispatch that to the backend to track the
|
// We open an SSE connection to the backend
|
||||||
// ephemeral node.
|
// so that when the connection is closed,
|
||||||
//
|
// the backend can delete the ephemeral node.
|
||||||
// We open an SSE connection to the backend
|
if (nodeKey === null) {
|
||||||
// so that when the connection is closed,
|
setNodeKey(netmap.NodeKey);
|
||||||
// the backend can delete the ephemeral node.
|
submit(
|
||||||
if (nodeKey === null) {
|
{
|
||||||
setNodeKey(netmap.NodeKey);
|
node_key: netmap.NodeKey,
|
||||||
submit(
|
auth_key: ipnDetails.PreAuthKey,
|
||||||
{
|
},
|
||||||
node_key: netmap.NodeKey,
|
{ method: "POST" },
|
||||||
auth_key: ipnDetails.PreAuthKey,
|
);
|
||||||
},
|
}
|
||||||
{ method: 'POST' },
|
},
|
||||||
);
|
NotifyBrowseToURL: (url) => {
|
||||||
}
|
console.log("Browse to URL:", url);
|
||||||
},
|
},
|
||||||
NotifyBrowseToURL: (url) => {
|
NotifyPanicRecover: (message) => {
|
||||||
console.log('Browse to URL:', url);
|
console.error("Panic recover:", message);
|
||||||
},
|
},
|
||||||
NotifyPanicRecover: (message) => {
|
});
|
||||||
console.error('Panic recover:', message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
handle.Start();
|
handle.Start();
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!sshDetails.username) {
|
if (!sshDetails.username) {
|
||||||
return <UserPrompt hostname={sshDetails.hostname} />;
|
return <UserPrompt hostname={sshDetails.hostname} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
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}
|
</div>
|
||||||
ipn={ipn}
|
)}
|
||||||
username={sshDetails.username}
|
</div>
|
||||||
/>
|
);
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-77
@@ -4,115 +4,109 @@
|
|||||||
@plugin "tailwindcss-react-aria-components";
|
@plugin "tailwindcss-react-aria-components";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--blur-xs: 2px;
|
--blur-xs: 2px;
|
||||||
|
|
||||||
--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;
|
||||||
|
|
||||||
--color-main-50: #f8fafc;
|
--color-main-50: #f8fafc;
|
||||||
--color-main-100: #f1f5f9;
|
--color-main-100: #f1f5f9;
|
||||||
--color-main-200: #e2e8f0;
|
--color-main-200: #e2e8f0;
|
||||||
--color-main-300: #cbd5e1;
|
--color-main-300: #cbd5e1;
|
||||||
--color-main-400: #94a3b8;
|
--color-main-400: #94a3b8;
|
||||||
--color-main-500: #64748b;
|
--color-main-500: #64748b;
|
||||||
--color-main-600: #475569;
|
--color-main-600: #475569;
|
||||||
--color-main-700: #334155;
|
--color-main-700: #334155;
|
||||||
--color-main-800: #1e293b;
|
--color-main-800: #1e293b;
|
||||||
--color-main-900: #0f172a;
|
--color-main-900: #0f172a;
|
||||||
--color-main-950: #020617;
|
--color-main-950: #020617;
|
||||||
|
|
||||||
--color-ui-50: #fafafa;
|
--color-ui-50: #fafafa;
|
||||||
--color-ui-100: #f5f5f5;
|
--color-ui-100: #f5f5f5;
|
||||||
--color-ui-200: #e5e5e5;
|
--color-ui-200: #e5e5e5;
|
||||||
--color-ui-300: #d4d4d4;
|
--color-ui-300: #d4d4d4;
|
||||||
--color-ui-400: #a3a3a3;
|
--color-ui-400: #a3a3a3;
|
||||||
--color-ui-500: #737373;
|
--color-ui-500: #737373;
|
||||||
--color-ui-600: #525252;
|
--color-ui-600: #525252;
|
||||||
--color-ui-700: #404040;
|
--color-ui-700: #404040;
|
||||||
--color-ui-800: #262626;
|
--color-ui-800: #262626;
|
||||||
--color-ui-900: #171717;
|
--color-ui-900: #171717;
|
||||||
--color-ui-950: #0a0a0a;
|
--color-ui-950: #0a0a0a;
|
||||||
|
|
||||||
--color-headplane-50: #f2f2f2;
|
--color-headplane-50: #f2f2f2;
|
||||||
--color-headplane-100: #e6e6e6;
|
--color-headplane-100: #e6e6e6;
|
||||||
--color-headplane-200: #cccccc;
|
--color-headplane-200: #cccccc;
|
||||||
--color-headplane-300: #b3b3b3;
|
--color-headplane-300: #b3b3b3;
|
||||||
--color-headplane-400: #999999;
|
--color-headplane-400: #999999;
|
||||||
--color-headplane-500: #808080;
|
--color-headplane-500: #808080;
|
||||||
--color-headplane-600: #666666;
|
--color-headplane-600: #666666;
|
||||||
--color-headplane-700: #4d4d4d;
|
--color-headplane-700: #4d4d4d;
|
||||||
--color-headplane-800: #343434;
|
--color-headplane-800: #343434;
|
||||||
--color-headplane-900: #1a1a1a;
|
--color-headplane-900: #1a1a1a;
|
||||||
--color-headplane-950: #0d0d0d;
|
--color-headplane-950: #0d0d0d;
|
||||||
|
|
||||||
--animate-loading: loader 0.8s infinite ease-in-out;
|
--animate-loading: loader 0.8s infinite ease-in-out;
|
||||||
|
|
||||||
@keyframes loader {
|
@keyframes loader {
|
||||||
from {
|
from {
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility container {
|
@utility container {
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
padding-inline: 1rem;
|
padding-inline: 1rem;
|
||||||
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;
|
}
|
||||||
}
|
@media (width >= theme(--breakpoint-lg)) {
|
||||||
|
padding-inline: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
|
@media (width >= theme(--breakpoint-xl)) {
|
||||||
@media (width >= theme(--breakpoint-lg)) {
|
padding-inline: 5rem;
|
||||||
padding-inline: 4rem;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
|
@media (width >= theme(--breakpoint-2xl)) {
|
||||||
@media (width >= theme(--breakpoint-xl)) {
|
padding-inline: 6rem;
|
||||||
padding-inline: 5rem;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* biome-ignore lint/correctness/noUnknownFunction: Tailwind CSS */
|
|
||||||
@media (width >= theme(--breakpoint-2xl)) {
|
|
||||||
padding-inline: 6rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (scrollbar-gutter: stable) {
|
@supports (scrollbar-gutter: stable) {
|
||||||
html {
|
html {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-merge-theme {
|
.cm-merge-theme {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-mergeView {
|
.cm-mergeView {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-mergeViewEditors {
|
.cm-mergeViewEditors {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-mergeViewEditor {
|
.cm-mergeViewEditor {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Weirdest class name characters but ok */
|
/* Weirdest class name characters but ok */
|
||||||
.cm-mergeView .ͼ1 .cm-scroller,
|
.cm-mergeView .ͼ1 .cm-scroller,
|
||||||
.cm-mergeView .ͼ1 {
|
.cm-mergeView .ͼ1 {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|||||||
-48
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+24
-20
@@ -6,39 +6,42 @@ 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
|
||||||
that you encounter from misconfiguration or usage. Those belong in the
|
that you encounter from misconfiguration or usage. Those belong in the
|
||||||
[discussions](https://github.com/tale/headplane/discussions) section.
|
[discussions](https://github.com/tale/headplane/discussions) section.
|
||||||
|
|
||||||
- **Documentation/Examples**: If you find any issues in the documentation or
|
- **Documentation/Examples**: If you find any issues in the documentation or
|
||||||
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
|
||||||
into multiple PRs if they are unrelated to each other.
|
into multiple PRs if they are unrelated to each other.
|
||||||
|
|
||||||
- **No Project/Tooling Changes**: Unless there is a very good reason to do so,
|
- **No Project/Tooling Changes**: Unless there is a very good reason to do so,
|
||||||
I will not accept changes to the project structure, build system, or tooling
|
I will not accept changes to the project structure, build system, or tooling
|
||||||
used to develop Headplane. This includes things like changing the package
|
used to develop Headplane. This includes things like changing the package
|
||||||
manager, docker environment, or CI/CD.
|
manager, docker environment, or CI/CD.
|
||||||
|
|
||||||
- **Minimal Breaking Changes**: I will not accept any changes that break any
|
- **Minimal Breaking Changes**: I will not accept any changes that break any
|
||||||
existing functionality or change the API unless there is a very good reason
|
existing functionality or change the API unless there is a very good reason
|
||||||
to do so. If you want to make a breaking change, please open an issue first
|
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
@@ -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"
|
||||||
|
|||||||
+120
-117
@@ -1,119 +1,122 @@
|
|||||||
{
|
{
|
||||||
"name": "headplane",
|
"name": "headplane",
|
||||||
"private": true,
|
"version": "0.6.1",
|
||||||
"sideEffects": false,
|
"private": true,
|
||||||
"version": "0.6.1",
|
"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",
|
||||||
"dev": "HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev",
|
"dev": "HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev",
|
||||||
"start": "node build/server/index.js",
|
"start": "node build/server/index.js",
|
||||||
"typecheck": "react-router typegen && tsgo",
|
"typecheck": "react-router typegen && tsgo",
|
||||||
"test:unit": "vitest run --project unit",
|
"test:unit": "vitest run --project unit",
|
||||||
"test:integration": "vitest run --project integration",
|
"test:integration": "vitest run --project integration",
|
||||||
"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",
|
||||||
"dependencies": {
|
"format": "oxfmt"
|
||||||
"@libsql/client": "0.15.15"
|
},
|
||||||
},
|
"dependencies": {
|
||||||
"devDependencies": {
|
"@libsql/client": "0.15.15"
|
||||||
"@biomejs/biome": "^2.3.5",
|
},
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"devDependencies": {
|
||||||
"@dnd-kit/modifiers": "^7.0.0",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/modifiers": "^7.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@faker-js/faker": "9.9.0",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@faker-js/faker": "9.9.0",
|
||||||
"@iconify/react": "^6.0.2",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@kubernetes/client-node": "^1.4.0",
|
"@iconify/react": "^6.0.2",
|
||||||
"@react-aria/toast": "3.0.8",
|
"@kubernetes/client-node": "^1.4.0",
|
||||||
"@react-router/dev": "^7.9.6",
|
"@react-aria/toast": "3.0.8",
|
||||||
"@react-router/node": "^7.9.6",
|
"@react-router/dev": "^7.9.6",
|
||||||
"@react-stately/toast": "3.1.2",
|
"@react-router/node": "^7.9.6",
|
||||||
"@readme/openapi-parser": "^5.2.1",
|
"@react-stately/toast": "3.1.2",
|
||||||
"@shopify/lang-jsonc": "^1.0.1",
|
"@readme/openapi-parser": "^5.2.1",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@shopify/lang-jsonc": "^1.0.1",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@types/node": "^24.10.1",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/react": "^19.2.5",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react": "^19.2.5",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251203.1",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@uiw/codemirror-theme-github": "4.25.1",
|
"@typescript/native-preview": "7.0.0-dev.20251203.1",
|
||||||
"@uiw/codemirror-theme-xcode": "4.25.3",
|
"@uiw/codemirror-theme-github": "4.25.1",
|
||||||
"@uiw/react-codemirror": "4.25.3",
|
"@uiw/codemirror-theme-xcode": "4.25.3",
|
||||||
"@xterm/addon-clipboard": "^0.1.0",
|
"@uiw/react-codemirror": "4.25.3",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-clipboard": "^0.1.0",
|
||||||
"@xterm/addon-unicode11": "^0.8.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-unicode11": "^0.8.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
"arktype": "^2.1.26",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"clsx": "^2.1.1",
|
"arktype": "^2.1.26",
|
||||||
"drizzle-kit": "^0.31.7",
|
"clsx": "^2.1.1",
|
||||||
"drizzle-orm": "0.44.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
"isbot": "5.1.32",
|
"drizzle-orm": "0.44.7",
|
||||||
"jose": "6.1.2",
|
"isbot": "5.1.32",
|
||||||
"js-yaml": "^4.1.1",
|
"jose": "6.1.2",
|
||||||
"lefthook": "^2.0.4",
|
"js-yaml": "^4.1.1",
|
||||||
"lucide-react": "^0.540.0",
|
"lefthook": "^2.0.4",
|
||||||
"mime": "^4.1.0",
|
"lucide-react": "^0.540.0",
|
||||||
"openapi-types": "^12.1.3",
|
"mime": "^4.1.0",
|
||||||
"openid-client": "6.8.1",
|
"openapi-types": "^12.1.3",
|
||||||
"postcss": "^8.5.6",
|
"openid-client": "6.8.1",
|
||||||
"react": "19.2.0",
|
"oxfmt": "^0.24.0",
|
||||||
"react-aria": "3.44.0",
|
"oxlint": "^1.39.0",
|
||||||
"react-codemirror-merge": "4.25.3",
|
"oxlint-tsgolint": "^0.11.1",
|
||||||
"react-dom": "19.2.0",
|
"postcss": "^8.5.6",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react": "19.2.0",
|
||||||
"react-router": "^7.9.6",
|
"react-aria": "3.44.0",
|
||||||
"react-router-dom": "^7.9.6",
|
"react-codemirror-merge": "4.25.3",
|
||||||
"react-router-hono-server": "2.22.0",
|
"react-dom": "19.2.0",
|
||||||
"react-stately": "3.42.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
"remix-utils": "^9.0.0",
|
"react-router": "^7.9.6",
|
||||||
"tailwind-merge": "3.4.0",
|
"react-router-dom": "^7.9.6",
|
||||||
"tailwindcss": "^4.1.17",
|
"react-router-hono-server": "2.22.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"react-stately": "3.42.0",
|
||||||
"tailwindcss-react-aria-components": "^2.0.1",
|
"remix-utils": "^9.0.0",
|
||||||
"testcontainers": "^11.8.1",
|
"tailwind-merge": "3.4.0",
|
||||||
"tsx": "^4.20.6",
|
"tailwindcss": "^4.1.17",
|
||||||
"typescript": "^5.9.3",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"ulidx": "2.4.1",
|
"tailwindcss-react-aria-components": "^2.0.1",
|
||||||
"undici": "7.16.0",
|
"testcontainers": "^11.8.1",
|
||||||
"usehooks-ts": "^3.1.1",
|
"tsx": "^4.20.6",
|
||||||
"vite": "8.0.0-beta.0",
|
"typescript": "^5.9.3",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"ulidx": "2.4.1",
|
||||||
"vitepress": "next",
|
"undici": "7.16.0",
|
||||||
"vitest": "^4.0.9",
|
"usehooks-ts": "^3.1.1",
|
||||||
"yaml": "2.8.1"
|
"vite": "8.0.0-beta.0",
|
||||||
},
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"packageManager": "pnpm@10.4.0",
|
"vitepress": "next",
|
||||||
"engines": {
|
"vitest": "^4.0.9",
|
||||||
"node": ">=22.18 <23",
|
"yaml": "2.8.1"
|
||||||
"pnpm": ">=10.4 <11"
|
},
|
||||||
},
|
"engines": {
|
||||||
"pnpm": {
|
"node": ">=22.18 <23",
|
||||||
"supportedArchitectures": {
|
"pnpm": ">=10.4 <11"
|
||||||
"os": [
|
},
|
||||||
"current",
|
"packageManager": "pnpm@10.4.0",
|
||||||
"linux"
|
"pnpm": {
|
||||||
],
|
"supportedArchitectures": {
|
||||||
"cpu": [
|
"os": [
|
||||||
"x64",
|
"current",
|
||||||
"arm64"
|
"linux"
|
||||||
]
|
],
|
||||||
},
|
"cpu": [
|
||||||
"onlyBuiltDependencies": [
|
"x64",
|
||||||
"@biomejs/biome",
|
"arm64"
|
||||||
"@tailwindcss/oxide",
|
]
|
||||||
"better-sqlite3",
|
},
|
||||||
"esbuild",
|
"onlyBuiltDependencies": [
|
||||||
"lefthook"
|
"@tailwindcss/oxide",
|
||||||
],
|
"better-sqlite3",
|
||||||
"patchedDependencies": {
|
"esbuild",
|
||||||
"react-router-hono-server": "patches/react-router-hono-server.patch"
|
"lefthook"
|
||||||
}
|
],
|
||||||
}
|
"patchedDependencies": {
|
||||||
|
"react-router-hono-server": "patches/react-router-hono-server.patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+244
-91
@@ -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: {}
|
||||||
|
|||||||
@@ -1,83 +1,81 @@
|
|||||||
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');
|
|
||||||
const config = {
|
|
||||||
server: {
|
|
||||||
cookie_secret_path: '/secrets/cookie_secret.txt',
|
|
||||||
},
|
|
||||||
} as PartialHeadplaneConfigWithPaths;
|
|
||||||
|
|
||||||
await loadConfigKeyPaths(config);
|
import { ConfigError } from "~/server/config/error";
|
||||||
expect(config.server?.cookie_secret).toBe('supersecretcookievalue');
|
import { loadConfigKeyPaths } from "~/server/config/load";
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error for missing secret file', async () => {
|
import { createFakeFile } from "../setup/overlay-fs";
|
||||||
const config = {
|
|
||||||
server: {
|
|
||||||
cookie_secret_path: '/secrets/missing_cookie_secret.txt',
|
|
||||||
},
|
|
||||||
} as PartialHeadplaneConfigWithPaths;
|
|
||||||
|
|
||||||
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
|
describe("Configuration secret path handling", () => {
|
||||||
ConfigError.from('MISSING_SECRET_FILE', {
|
test("should correctly substitute server.cookie_secret", async () => {
|
||||||
pathKey: 'server.cookie_secret_path',
|
createFakeFile("/secrets/cookie_secret.txt", "supersecretcookievalue");
|
||||||
filePath: '/secrets/missing_cookie_secret.txt',
|
const config = {
|
||||||
}),
|
server: {
|
||||||
);
|
cookie_secret_path: "/secrets/cookie_secret.txt",
|
||||||
});
|
},
|
||||||
|
} as PartialHeadplaneConfigWithPaths;
|
||||||
|
|
||||||
test('should throw error for conflicting secret path and field', async () => {
|
await loadConfigKeyPaths(config);
|
||||||
const config = {
|
expect(config.server?.cookie_secret).toBe("supersecretcookievalue");
|
||||||
server: {
|
});
|
||||||
cookie_secret: 'explicitsecretvalue',
|
|
||||||
cookie_secret_path: '/secrets/cookie_secret.txt',
|
|
||||||
},
|
|
||||||
} as PartialHeadplaneConfigWithPaths;
|
|
||||||
|
|
||||||
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
|
test("should throw error for missing secret file", async () => {
|
||||||
ConfigError.from('CONFLICTING_SECRET_PATH_FIELD', {
|
const config = {
|
||||||
fieldName: 'server.cookie_secret',
|
server: {
|
||||||
}),
|
cookie_secret_path: "/secrets/missing_cookie_secret.txt",
|
||||||
);
|
},
|
||||||
});
|
} as PartialHeadplaneConfigWithPaths;
|
||||||
|
|
||||||
test('should correctly interpolate env vars in secret paths', async () => {
|
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
|
||||||
process.env.HP_TEST_COOKIE_SECRET_FILE = 'cookie_secret.txt';
|
ConfigError.from("MISSING_SECRET_FILE", {
|
||||||
createFakeFile(
|
pathKey: "server.cookie_secret_path",
|
||||||
`/secrets/${process.env.HP_TEST_COOKIE_SECRET_FILE}`,
|
filePath: "/secrets/missing_cookie_secret.txt",
|
||||||
'envvarsecretvalue',
|
}),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const config = {
|
test("should throw error for conflicting secret path and field", async () => {
|
||||||
server: {
|
const config = {
|
||||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
|
server: {
|
||||||
cookie_secret_path: '/secrets/${HP_TEST_COOKIE_SECRET_FILE}',
|
cookie_secret: "explicitsecretvalue",
|
||||||
},
|
cookie_secret_path: "/secrets/cookie_secret.txt",
|
||||||
} as PartialHeadplaneConfigWithPaths;
|
},
|
||||||
|
} as PartialHeadplaneConfigWithPaths;
|
||||||
|
|
||||||
await loadConfigKeyPaths(config);
|
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
|
||||||
expect(config.server?.cookie_secret).toBe('envvarsecretvalue');
|
ConfigError.from("CONFLICTING_SECRET_PATH_FIELD", {
|
||||||
});
|
fieldName: "server.cookie_secret",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('should throw error for missing interpolated env var in secret path', async () => {
|
test("should correctly interpolate env vars in secret paths", async () => {
|
||||||
const config = {
|
process.env.HP_TEST_COOKIE_SECRET_FILE = "cookie_secret.txt";
|
||||||
server: {
|
createFakeFile(`/secrets/${process.env.HP_TEST_COOKIE_SECRET_FILE}`, "envvarsecretvalue");
|
||||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test supports interpolation
|
|
||||||
cookie_secret_path: '/secrets/${MISSING_ENV_VAR}',
|
|
||||||
},
|
|
||||||
} as PartialHeadplaneConfigWithPaths;
|
|
||||||
|
|
||||||
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
|
const config = {
|
||||||
ConfigError.from('MISSING_INTERPOLATION_VARIABLE', {
|
server: {
|
||||||
pathKey: 'server.cookie_secret_path',
|
cookie_secret_path: "/secrets/${HP_TEST_COOKIE_SECRET_FILE}",
|
||||||
variableName: 'MISSING_ENV_VAR',
|
},
|
||||||
}),
|
} as PartialHeadplaneConfigWithPaths;
|
||||||
);
|
|
||||||
});
|
await loadConfigKeyPaths(config);
|
||||||
|
expect(config.server?.cookie_secret).toBe("envvarsecretvalue");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should throw error for missing interpolated env var in secret path", async () => {
|
||||||
|
const config = {
|
||||||
|
server: {
|
||||||
|
cookie_secret_path: "/secrets/${MISSING_ENV_VAR}",
|
||||||
|
},
|
||||||
|
} as PartialHeadplaneConfigWithPaths;
|
||||||
|
|
||||||
|
await expect(loadConfigKeyPaths(config)).rejects.toMatchObject(
|
||||||
|
ConfigError.from("MISSING_INTERPOLATION_VARIABLE", {
|
||||||
|
pathKey: "server.cookie_secret_path",
|
||||||
|
variableName: "MISSING_ENV_VAR",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user