web: Packagify live reload plugin. (#14134)

* web: Packagify live reload plugin.

* web: Use shared formatter.

* web: Format.

* web: Use project mode typecheck.

* web: Fix type errors.
This commit is contained in:
Teffen Ellis
2025-04-21 19:07:45 +02:00
committed by GitHub
parent ba686f6a93
commit ad733033d7
25 changed files with 1069 additions and 545 deletions

View File

@ -1,23 +0,0 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "consistent",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderParserPlugins": ["typescript", "jsx", "classProperties", "decorators-legacy"]
}

790
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -61,6 +61,9 @@
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@goauthentik/esbuild-plugin-live-reload": "^1.0.4",
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@hcaptcha/types": "^1.0.4",
"@lit/localize-tools": "^0.8.0",
"@rollup/plugin-replace": "^6.0.1",
@ -72,7 +75,7 @@
"@storybook/manager-api": "^8.3.4",
"@storybook/web-components": "^8.3.4",
"@storybook/web-components-vite": "^8.3.4",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "^5.60.15",
"@types/dompurify": "^3.0.5",
@ -95,7 +98,6 @@
"eslint": "^9.11.1",
"eslint-plugin-lit": "^1.15.0",
"eslint-plugin-wc": "^2.1.1",
"find-free-ports": "^3.1.1",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",
"globals": "^15.10.0",
@ -136,6 +138,7 @@
"axios": "^1.8.4"
}
},
"prettier": "@goauthentik/prettier-config",
"private": true,
"scripts": {
"build": "wireit",
@ -271,7 +274,7 @@
"command": "tsc --noEmit -p ./tests"
},
"lint:types": {
"command": "tsc --noEmit -p .",
"command": "NODE_OPTIONS=\"--max-old-space-size=3000\" tsc -b .",
"dependencies": [
"build-locales",
"lint:types:tests"

View File

@ -1,13 +1,18 @@
/// <reference types="./types.js" />
/**
* @file
* Client-side observer for ESBuild events.
* @file Client-side observer for ESBuild events.
*
* @import { Message as ESBuildMessage } from "esbuild";
*/
import type { Message as ESBuildMessage } from "esbuild";
const logPrefix = "👷 [ESBuild]";
const log = console.debug.bind(console, logPrefix);
type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void;
/**
* @template {unknown} [Data=unknown]
* @typedef {(event: MessageEvent) => void} BuildEventListener
*/
/**
* A client-side watcher for ESBuild.
@ -16,14 +21,13 @@ type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void;
* ESBuild may tree-shake it out of production builds.
*
* ```ts
* if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
* const { ESBuildObserver } = await import("@goauthentik/common/client");
*
* new ESBuildObserver(process.env.WATCHER_URL);
* if (process.env.NODE_ENV === "development") {
* await import("@goauthentik/esbuild-plugin-live-reload/client")
* .catch(() => console.warn("Failed to import watcher"))
* }
* ```
}
*
* @implements {Disposable}
*/
export class ESBuildObserver extends EventSource {
/**
@ -58,15 +62,19 @@ export class ESBuildObserver extends EventSource {
/**
* The interval for the keep-alive check.
* @type {ReturnType<typeof setInterval> | undefined}
*/
#keepAliveInterval: ReturnType<typeof setInterval> | undefined;
#keepAliveInterval;
#trackActivity = () => {
this.lastUpdatedAt = Date.now();
this.alive = true;
};
#startListener: BuildEventListener = () => {
/**
* @type {BuildEventListener}
*/
#startListener = () => {
this.#trackActivity();
log("⏰ Build started...");
};
@ -82,13 +90,18 @@ export class ESBuildObserver extends EventSource {
}
};
#errorListener: BuildEventListener<string> = (event) => {
/**
* @type {BuildEventListener<string>}
*/
#errorListener = (event) => {
this.#trackActivity();
// eslint-disable-next-line no-console
console.group(logPrefix, "⛔️⛔️⛔️ Build error...");
const esbuildErrorMessages: ESBuildMessage[] = JSON.parse(event.data);
/**
* @type {ESBuildMessage[]}
*/
const esbuildErrorMessages = JSON.parse(event.data);
for (const error of esbuildErrorMessages) {
console.warn(error.text);
@ -101,11 +114,13 @@ export class ESBuildObserver extends EventSource {
}
}
// eslint-disable-next-line no-console
console.groupEnd();
};
#endListener: BuildEventListener = () => {
/**
* @type {BuildEventListener}
*/
#endListener = () => {
cancelAnimationFrame(this.#reloadFrameID);
this.#trackActivity();
@ -126,12 +141,36 @@ export class ESBuildObserver extends EventSource {
});
};
#keepAliveListener: BuildEventListener = () => {
/**
* @type {BuildEventListener}
*/
#keepAliveListener = () => {
this.#trackActivity();
log("🏓 Keep-alive");
};
constructor(url: string | URL) {
/**
* Initialize the ESBuild observer.
* This should be called once in your application.
*
* @param {string | URL} [url]
* @returns {ESBuildObserver}
*/
static initialize = (url) => {
const esbuildObserver = new ESBuildObserver(url);
return esbuildObserver;
};
/**
*
* @param {string | URL} [url]
*/
constructor(url) {
if (!url) {
throw new TypeError("ESBuildObserver: Cannot construct without a URL");
}
super(url);
this.addEventListener("esbuild:start", this.#startListener);
@ -167,4 +206,14 @@ export class ESBuildObserver extends EventSource {
log("👋 Waiting for build to start...");
}, 15_000);
}
[Symbol.dispose]() {
return this.close();
}
dispose() {
return this[Symbol.dispose]();
}
}
export default ESBuildObserver;

View File

@ -0,0 +1,13 @@
/// <reference types="./types.js" />
/**
* @file Entry point for the ESBuild client-side observer.
*/
import { ESBuildObserver } from "./ESBuildObserver.js";
if (import.meta.env?.ESBUILD_WATCHER_URL) {
const buildObserver = new ESBuildObserver(import.meta.env.ESBUILD_WATCHER_URL);
window.addEventListener("beforeunload", () => {
buildObserver.dispose();
});
}

View File

@ -0,0 +1,18 @@
/**
* @file Import meta environment variables available via ESBuild.
*/
export {};
declare global {
interface ImportMeta {
readonly env: {
/**
* The injected watcher URL for ESBuild.
* This is used for live reloading in development mode.
*
* @format url
*/
ESBUILD_WATCHER_URL: string;
};
}
}

View File

@ -0,0 +1,2 @@
export * from "./client/index.js";
export * from "./plugin/index.js";

View File

@ -0,0 +1,53 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"description": "ESBuild plugin to watch for file changes and trigger client-side reloads.",
"version": "1.0.4",
"dependencies": {
"find-free-ports": "^3.1.1"
},
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/node": "^22.14.1",
"esbuild": "^0.25.0",
"prettier": "^3.3.3",
"typescript": "^5.6.2"
},
"engines": {
"node": ">=20.11"
},
"exports": {
"./package.json": "./package.json",
".": {
"types": "./out/index.d.ts",
"import": "./index.js"
},
"./client": {
"types": "./out/client/index.d.ts",
"import": "./client/index.js"
},
"./plugin": {
"types": "./out/plugin/index.d.ts",
"import": "./plugin/index.js"
}
},
"files": [
"./index.js",
"client/**/*",
"plugin/**/*",
"out/**/*"
],
"license": "MIT",
"main": "index.js",
"peerDependencies": {
"esbuild": "^0.25.0"
},
"prettier": "@goauthentik/prettier-config",
"private": true,
"publishConfig": {
"access": "public"
},
"type": "module",
"types": "./out/index.d.ts"
}

View File

@ -0,0 +1,243 @@
/**
* @file Live reload plugin for ESBuild.
*
* @import { ListenOptions } from "node:net";
* @import {Server as HTTPServer} from "node:http";
* @import {Server as HTTPSServer} from "node:https";
*/
import { findFreePorts } from "find-free-ports";
import * as http from "node:http";
import * as path from "node:path";
/**
* Serializes a custom event to a text stream.
* @param {Event} event
* @returns {string}
*/
export function serializeCustomEventToStream(event) {
// @ts-expect-error - TS doesn't know about the detail property
const data = event.detail ?? {};
const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`];
return eventContent.join("\n") + "\n\n";
}
const MIN_PORT = 1025;
const MAX_PORT = 65535;
/**
* Find a random port that is not in use, sufficiently far from the default port.
* @returns {Promise<number>}
*/
async function findDisparatePort() {
const startPort = Math.floor(Math.random() * (MAX_PORT - MIN_PORT + 1)) + MIN_PORT;
const wathcherPorts = await findFreePorts(1, {
startPort,
});
const [port] = wathcherPorts;
if (!port) {
throw new Error("No free ports available");
}
return port;
}
/**
* Event server initialization options.
*
* @typedef {Object} EventServerInit
*
* @property {string} pathname
* @property {EventTarget} dispatcher
* @property {string} [logPrefix]
*/
/**
* @typedef {(req: http.IncomingMessage, res: http.ServerResponse) => void} RequestHandler
*/
/**
* Create an event request handler.
* @param {EventServerInit} options
* @returns {RequestHandler}
* @category ESBuild
*/
export function createRequestHandler({ pathname, dispatcher, logPrefix = "Build Observer" }) {
// eslint-disable-next-line no-console
const log = console.log.bind(console, `[${logPrefix}]`);
/**
* @type {RequestHandler}
*/
const requestHandler = (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.url !== pathname) {
log(`🚫 Invalid request to ${req.url}`);
res.writeHead(404);
res.end();
return;
}
log("🔌 Client connected");
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
/**
* @param {Event} event
*/
const listener = (event) => {
const body = serializeCustomEventToStream(event);
res.write(body);
};
dispatcher.addEventListener("esbuild:start", listener);
dispatcher.addEventListener("esbuild:error", listener);
dispatcher.addEventListener("esbuild:end", listener);
req.on("close", () => {
log("🔌 Client disconnected");
clearInterval(keepAliveInterval);
dispatcher.removeEventListener("esbuild:start", listener);
dispatcher.removeEventListener("esbuild:error", listener);
dispatcher.removeEventListener("esbuild:end", listener);
});
const keepAliveInterval = setInterval(() => {
console.timeStamp("🏓 Keep-alive");
res.write("event: keep-alive\n\n");
res.write(serializeCustomEventToStream(new CustomEvent("esbuild:keep-alive")));
}, 15_000);
};
return requestHandler;
}
/**
* Options for the build observer plugin.
*
* @typedef {object} BuildObserverOptions
*
* @property {HTTPServer | HTTPSServer} [server]
* @property {ListenOptions} [listenOptions]
* @property {string | URL} [publicURL]
* @property {string} [logPrefix]
* @property {string} [relativeRoot]
*/
/**
* Creates a plugin that listens for build events and sends them to a server-sent event stream.
*
* @param {BuildObserverOptions} [options]
* @returns {import('esbuild').Plugin}
*/
export function liveReloadPlugin(options = {}) {
return {
name: "build-watcher",
setup: async (build) => {
const logPrefix = options.logPrefix || "Build Observer";
const timerLabel = `[${logPrefix}] 🏁`;
const relativeRoot = options.relativeRoot || process.cwd();
const dispatcher = new EventTarget();
/**
* @type {URL}
*/
let publicURL;
if (!options.publicURL) {
const port = await findDisparatePort();
publicURL = new URL(`http://localhost:${port}/events`);
} else {
publicURL =
typeof options.publicURL === "string"
? new URL(options.publicURL)
: options.publicURL;
}
build.initialOptions.define = {
...build.initialOptions.define,
"import.meta.env.ESBUILD_WATCHER_URL": JSON.stringify(publicURL.href),
};
const requestHandler = createRequestHandler({
pathname: publicURL.pathname,
dispatcher,
logPrefix,
});
const server = options.server || http.createServer(requestHandler);
const listenOptions = options.listenOptions || {
port: parseInt(publicURL.port, 10),
host: publicURL.hostname,
};
server.listen(listenOptions, () => {
// eslint-disable-next-line no-console
console.log(`[${logPrefix}] Listening`);
});
build.onDispose(() => {
server?.close();
});
build.onStart(() => {
console.time(timerLabel);
dispatcher.dispatchEvent(
new CustomEvent("esbuild:start", {
detail: new Date().toISOString(),
}),
);
});
build.onEnd((buildResult) => {
console.timeEnd(timerLabel);
if (!buildResult.errors.length) {
dispatcher.dispatchEvent(
new CustomEvent("esbuild:end", {
detail: new Date().toISOString(),
}),
);
return;
}
console.warn(`Build ended with ${buildResult.errors.length} errors`);
dispatcher.dispatchEvent(
new CustomEvent("esbuild:error", {
detail: buildResult.errors.map((error) => ({
...error,
location: error.location
? {
...error.location,
file: path.resolve(relativeRoot, error.location.file),
}
: null,
})),
}),
);
});
},
};
}

View File

@ -0,0 +1,10 @@
{
"extends": "@goauthentik/tsconfig",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"resolveJsonModule": true,
"baseUrl": ".",
"checkJs": true,
"emitDeclarationOnly": true
}
}

View File

@ -1,8 +1,13 @@
/**
* @file ESBuild script for building the authentik web UI.
*
* @import { BuildOptions } from "esbuild";
*/
import { liveReloadPlugin } from "@goauthentik/esbuild-plugin-live-reload/plugin";
import { execFileSync } from "child_process";
import { deepmerge } from "deepmerge-ts";
import esbuild from "esbuild";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
import findFreePorts from "find-free-ports";
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs";
import { globSync } from "glob";
import * as path from "path";
@ -11,7 +16,6 @@ import process from "process";
import { fileURLToPath } from "url";
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
import { buildObserverPlugin } from "./esbuild/build-observer-plugin.mjs";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
let authentikProjectRoot = path.join(__dirname, "..", "..");
@ -120,7 +124,7 @@ const BASE_ESBUILD_OPTIONS = {
splitting: true,
treeShaking: true,
external: ["*.woff", "*.woff2"],
tsconfig: "./tsconfig.json",
tsconfig: path.resolve(__dirname, "..", "tsconfig.build.json"),
loader: {
".css": "text",
},
@ -220,26 +224,17 @@ function doHelp() {
async function doWatch() {
console.log("Watching all entry points...");
const wathcherPorts = await findFreePorts(entryPoints.length);
const buildContexts = await Promise.all(
entryPoints.map((entryPoint, i) => {
const port = wathcherPorts[i];
const serverURL = new URL(`http://localhost:${port}/events`);
entryPoints.map((entryPoint) => {
return esbuild.context(
createEntryPointOptions(entryPoint, {
define: definitions,
plugins: [
buildObserverPlugin({
serverURL,
logPrefix: entryPoint[1],
liveReloadPlugin({
logPrefix: `Build Observer (${entryPoint[1]})`,
relativeRoot: path.join(__dirname, ".."),
}),
],
define: {
...definitions,
"process.env.WATCHER_URL": JSON.stringify(serverURL.toString()),
},
}),
);
}),

View File

@ -1,141 +0,0 @@
import * as http from "http";
import path from "path";
/**
* Serializes a custom event to a text stream.
* a
* @param {Event} event
* @returns {string}
*/
export function serializeCustomEventToStream(event) {
// @ts-expect-error - TS doesn't know about the detail property
const data = event.detail ?? {};
const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`];
return eventContent.join("\n") + "\n\n";
}
/**
* Options for the build observer plugin.
*
* @typedef {Object} BuildObserverOptions
*
* @property {URL} serverURL
* @property {string} logPrefix
* @property {string} relativeRoot
*/
/**
* Creates a plugin that listens for build events and sends them to a server-sent event stream.
*
* @param {BuildObserverOptions} options
* @returns {import('esbuild').Plugin}
*/
export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
const timerLabel = `[${logPrefix}] Build`;
const endpoint = serverURL.pathname;
const dispatcher = new EventTarget();
const eventServer = http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.url !== endpoint) {
console.log(`🚫 Invalid request to ${req.url}`);
res.writeHead(404);
res.end();
return;
}
console.log("🔌 Client connected");
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
/**
* @param {Event} event
*/
const listener = (event) => {
const body = serializeCustomEventToStream(event);
res.write(body);
};
dispatcher.addEventListener("esbuild:start", listener);
dispatcher.addEventListener("esbuild:error", listener);
dispatcher.addEventListener("esbuild:end", listener);
req.on("close", () => {
console.log("🔌 Client disconnected");
clearInterval(keepAliveInterval);
dispatcher.removeEventListener("esbuild:start", listener);
dispatcher.removeEventListener("esbuild:error", listener);
dispatcher.removeEventListener("esbuild:end", listener);
});
const keepAliveInterval = setInterval(() => {
console.timeStamp("🏓 Keep-alive");
res.write("event: keep-alive\n\n");
res.write(serializeCustomEventToStream(new CustomEvent("esbuild:keep-alive")));
}, 15_000);
});
return {
name: "build-watcher",
setup: (build) => {
eventServer.listen(parseInt(serverURL.port, 10), serverURL.hostname);
build.onDispose(() => {
eventServer.close();
});
build.onStart(() => {
console.time(timerLabel);
dispatcher.dispatchEvent(
new CustomEvent("esbuild:start", {
detail: new Date().toISOString(),
}),
);
});
build.onEnd((buildResult) => {
console.timeEnd(timerLabel);
if (!buildResult.errors.length) {
dispatcher.dispatchEvent(
new CustomEvent("esbuild:end", {
detail: new Date().toISOString(),
}),
);
return;
}
console.warn(`Build ended with ${buildResult.errors.length} errors`);
dispatcher.dispatchEvent(
new CustomEvent("esbuild:error", {
detail: buildResult.errors.map((error) => ({
...error,
location: error.location
? {
...error.location,
file: path.resolve(relativeRoot, error.location.file),
}
: null,
})),
}),
);
});
},
};
}

View File

@ -34,6 +34,10 @@ import { SessionUser, UiThemeEnum } from "@goauthentik/api";
import "./AdminSidebar";
if (process.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client");
}
@customElement("ak-interface-admin")
export class AdminInterface extends AuthenticatedInterface {
@property({ type: Boolean })
@ -119,16 +123,6 @@ export class AdminInterface extends AuthenticatedInterface {
}
}
async connectedCallback(): Promise<void> {
super.connectedCallback();
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
const { ESBuildObserver } = await import("@goauthentik/common/client");
new ESBuildObserver(process.env.WATCHER_URL);
}
}
render(): TemplateResult {
const sidebarClasses = {
"pf-m-light": this.activeTheme === UiThemeEnum.Light,

View File

@ -52,7 +52,6 @@ function renderRadiusOverview(rawProvider: OneOfProvider) {
}
function renderRACOverview(rawProvider: OneOfProvider) {
// @ts-expect-error TS6133
const _provider = rawProvider as RACProvider;
}

View File

@ -1,3 +1,4 @@
import type { Config as DOMPurifyConfig } from "dompurify";
import DOMPurify from "dompurify";
import { render } from "@lit-labs/ssr";
@ -6,9 +7,9 @@ import { TemplateResult, html } from "lit";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { until } from "lit/directives/until.js";
export const DOM_PURIFY_STRICT: DOMPurify.Config = {
export const DOM_PURIFY_STRICT = {
ALLOWED_TAGS: ["#text"],
};
} as const satisfies DOMPurifyConfig;
export async function renderStatic(input: TemplateResult): Promise<string> {
return await collectResult(render(input));

View File

@ -1,7 +1,7 @@
import { authentikVersionContext } from "@goauthentik/elements/AuthentikContexts";
import { consume } from "@lit/context";
import { Constructor } from "@lit/reactive-element/decorators/base";
import { Constructor } from "@lit/reactive-element/decorators/base.js";
import type { LitElement } from "lit";
import type { Version } from "@goauthentik/api";

View File

@ -2,7 +2,7 @@ import { AkControlElement } from "@goauthentik/elements/AkControlElement";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
import { msg } from "@lit/localize";
import { PropertyValues } from "@lit/reactive-element/reactive-element";
import { PropertyValues } from "@lit/reactive-element";
import { TemplateResult, css, html } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators.js";
import { map } from "lit/directives/map.js";

View File

@ -14,8 +14,6 @@ import "@goauthentik/flow/stages/password/PasswordStage";
// end of stage import
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
const { ESBuildObserver } = await import("@goauthentik/common/client");
new ESBuildObserver(process.env.WATCHER_URL);
if (process.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client");
}

View File

@ -43,6 +43,10 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import { CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api";
if (process.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client");
}
const customStyles = css`
.pf-c-page__main,
.pf-c-drawer__content,
@ -291,12 +295,6 @@ export class UserInterface extends AuthenticatedInterface {
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
const { ESBuildObserver } = await import("@goauthentik/common/client");
new ESBuildObserver(process.env.WATCHER_URL);
}
}
disconnectedCallback() {

View File

@ -1,71 +0,0 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"esModuleInterop": true,
"paths": {
"@goauthentik/docs/*": ["../website/docs/*"]
},
"types": [
"node",
"@wdio/mocha-framework",
"@wdio/types",
"expect-webdriverio",
"grecaptcha"
],
"jsx": "react-jsx",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"ES5",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ESNext",
"DOM",
"DOM.Iterable",
"WebWorker"
],
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"useDefineForClassFields": false,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noImplicitAny": true,
"plugins": [
{
"name": "ts-lit-plugin",
"strict": true,
"rules": {
"no-unknown-tag-name": "off",
"no-missing-import": "off",
"no-incompatible-type-binding": "off",
"no-unknown-property": "off",
"no-unknown-attribute": "off"
}
},
{
"name": "@genesiscommunitysuccess/custom-elements-lsp",
"designSystemPrefix": "ak-",
"parser": {
"timeout": 2000
}
}
]
},
"exclude": ["src/**/*.test.ts", "./tests"]
}

6
web/tsconfig.build.json Normal file
View File

@ -0,0 +1,6 @@
// @file TSConfig used by the web package during build.
{
"extends": "./tsconfig.json",
"exclude": ["src/**/*.test.ts", "./tests"]
}

View File

@ -1,7 +1,19 @@
// @file TSConfig used by the web package during development.
{
"extends": "./tsconfig.base.json",
"extends": "@goauthentik/tsconfig",
"compilerOptions": {
"types": ["@wdio/types", "grecaptcha", "node", "@wdio/mocha-framework", "expect-webdriverio"],
"allowSyntheticDefaultImports": true,
"emitDeclarationOnly": true,
"experimentalDecorators": true,
// See https://lit.dev/docs/components/properties/
"useDefineForClassFields": false,
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"baseUrl": ".",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
// TODO: We should enable this when when we're ready to enforce it.
"noUncheckedIndexedAccess": false,
"paths": {
"@goauthentik/admin/*": ["./src/admin/*"],
"@goauthentik/common/*": ["./src/common/*"],
@ -13,6 +25,41 @@
"@goauthentik/polyfill/*": ["./src/polyfill/*"],
"@goauthentik/standalone/*": ["./src/standalone/*"],
"@goauthentik/user/*": ["./src/user/*"]
},
"plugins": [
{
"name": "ts-lit-plugin",
"strict": true,
"rules": {
"no-unknown-tag-name": "off",
"no-missing-import": "off",
"no-incompatible-type-binding": "off",
"no-unknown-property": "off",
"no-unknown-attribute": "off"
}
},
{
"name": "@genesiscommunitysuccess/custom-elements-lsp",
"designSystemPrefix": "ak-",
"parser": {
"timeout": 2000
}
}
]
},
"exclude": [
// ---
"./out/**/*",
"./dist/**/*",
"src/**/*.test.ts",
"./tests",
// TODO: Remove after monorepo cleanup.
"src/**/*.comp.ts"
],
"references": [
{
"path": "./packages/esbuild-plugin-live-reload"
}
],
}

View File

@ -1,3 +1,4 @@
// @file TSConfig used during tests.
{
"compilerOptions": {
"baseUrl": ".",

26
web/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
/**
* @file Environment variables available via ESBuild.
*/
declare module "process" {
global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "production" | "development";
/**
*
* @todo Determine where this is used and if it is needed,
* give it a better name.
* @deprecated
*/
CWD: string;
/**
* @todo Determine where this is used and if it is needed,
* give it a better name.
* @deprecated
*/
AK_API_BASE_PATH: string;
}
}
}
}

View File

@ -336,6 +336,7 @@ export const config: Options.Testrunner = {
{ error: _error, result: _result, duration: _duration, passed: _passed, retries: _retries },
) {
if (lemmeSee) {
// @ts-expect-error TODO
await browser.pause(500);
}
},