core: include version in built JS files (cherry-pick #9558) (#10148)

core: include version in built JS files (#9558)

* web: fix esbuild issue with style sheets

Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.

* core: include version in built JS files



* add fallback



* include build hash



* format



* fix stuff

why does this even work locally



* idk man node



* just not use import assertions



* web: add no-console, use proper dirname path

* web: retarget to use the base package.json file.

* web: encode path to root package.json using git

This is the most authoritative way of finding the root of the git project.

* use full version to match frontend



* add fallback for missing .git folder



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
Co-authored-by: Ken Sternberg <ken@goauthentik.io>
This commit is contained in:
gcp-cherry-pick-bot[bot]
2024-06-18 17:39:04 +09:00
committed by GitHub
parent b6157ecaf1
commit eef02f2892
14 changed files with 76 additions and 19 deletions

View File

@ -17,6 +17,8 @@ optional_value = final
[bumpversion:file:pyproject.toml] [bumpversion:file:pyproject.toml]
[bumpversion:file:package.json]
[bumpversion:file:docker-compose.yml] [bumpversion:file:docker-compose.yml]
[bumpversion:file:schema.yml] [bumpversion:file:schema.yml]

View File

@ -31,6 +31,7 @@ RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \ --mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
npm ci --include=dev npm ci --include=dev
COPY ./package.json /work
COPY ./web /work/web/ COPY ./web /work/web/
COPY ./website /work/website/ COPY ./website /work/website/
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api

View File

@ -1,13 +1,13 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load authentik_core %}
{% block title %} {% block title %}
API Browser - {{ brand.branding_title }} API Browser - {{ brand.branding_title }}
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<script src="{% static 'dist/standalone/api-browser/index.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/standalone/api-browser/index-%v.js" %}
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
{% endblock %} {% endblock %}

View File

@ -1,5 +1,6 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load authentik_core %}
<!DOCTYPE html> <!DOCTYPE html>
@ -14,8 +15,8 @@
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject> <link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
<script src="{% static 'dist/poly.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/poly-%v.js" %}
<script src="{% static 'dist/standalone/loading/index.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/standalone/loading/index-%v.js" %}
{% block head %} {% block head %}
{% endblock %} {% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" /> <meta name="sentry-trace" content="{{ sentry_trace }}" />

View File

@ -1,9 +1,9 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load authentik_core %}
{% block head %} {% block head %}
<script src="{% static 'dist/admin/AdminInterface.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/admin/AdminInterface-%v.js" %}
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
{% include "base/header_js.html" %} {% include "base/header_js.html" %}

View File

@ -1,6 +1,7 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load static %}
{% load authentik_core %}
{% block head_before %} {% block head_before %}
{{ block.super }} {{ block.super }}
@ -17,7 +18,7 @@ window.authentik.flow = {
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<script src="{% static 'dist/flow/FlowInterface.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/flow/FlowInterface-%v.js" %}
<style> <style>
:root { :root {
--ak-flow-background: url("{{ flow.background_url }}"); --ak-flow-background: url("{{ flow.background_url }}");

View File

@ -1,9 +1,9 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load authentik_core %}
{% block head %} {% block head %}
<script src="{% static 'dist/user/UserInterface.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/user/UserInterface-%v.js" %}
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %} {% include "base/header_js.html" %}

View File

View File

@ -0,0 +1,27 @@
"""authentik core tags"""
from django import template
from django.templatetags.static import static as static_loader
from django.utils.safestring import mark_safe
from authentik import get_full_version
register = template.Library()
@register.simple_tag()
def versioned_script(path: str) -> str:
"""Wrapper around {% static %} tag that supports setting the version"""
returned_lines = [
(
f'<script src="{static_loader(path.replace("%v", get_full_version()))}'
'" type="module"></script>'
),
# Legacy method of loading scripts used as a fallback, without the version in the filename
# TODO: Remove after 2024.6 or later
(
f'<script src="{static_loader(path.replace("-%v", ""))}?'
f'version={get_full_version()}" type="module"></script>'
),
]
return mark_safe("".join(returned_lines)) # nosec

View File

@ -1,9 +1,9 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load authentik_core %}
{% block head %} {% block head %}
<script src="{% static 'dist/enterprise/rac/index.js' %}?version={{ version }}" type="module"></script> {% versioned_script "dist/enterprise/rac/index-%v.js" %}
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon }}"> <link rel="icon" href="{{ tenant.branding_favicon }}">

View File

@ -1,7 +1,6 @@
"""root settings for authentik""" """root settings for authentik"""
import importlib import importlib
import os
from collections import OrderedDict from collections import OrderedDict
from hashlib import sha512 from hashlib import sha512
from pathlib import Path from pathlib import Path
@ -10,7 +9,7 @@ from celery.schedules import crontab
from django.conf import ImproperlyConfigured from django.conf import ImproperlyConfigured
from sentry_sdk import set_tag from sentry_sdk import set_tag
from authentik import ENV_GIT_HASH_KEY, __version__ from authentik import __version__
from authentik.lib.config import CONFIG, redis_url from authentik.lib.config import CONFIG, redis_url
from authentik.lib.logging import get_logger_config, structlog_configure from authentik.lib.logging import get_logger_config, structlog_configure
from authentik.lib.sentry import sentry_init from authentik.lib.sentry import sentry_init
@ -511,7 +510,6 @@ def _update_settings(app_path: str):
if DEBUG: if DEBUG:
CELERY["task_always_eager"] = True CELERY["task_always_eager"] = True
os.environ[ENV_GIT_HASH_KEY] = "dev"
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append( REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append(
"rest_framework.renderers.BrowsableAPIRenderer" "rest_framework.renderers.BrowsableAPIRenderer"
) )

View File

@ -1,5 +1,5 @@
{ {
"name": "@goauthentik/authentik", "name": "@goauthentik/authentik",
"version": "1.0.0", "version": "2024.4.2",
"private": true "private": true
} }

View File

@ -1,11 +1,12 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# Stage 1: Build website # Stage 1: Build web
FROM --platform=${BUILDPLATFORM} docker.io/node:22 as web-builder FROM --platform=${BUILDPLATFORM} docker.io/node:22 as web-builder
ENV NODE_ENV=production ENV NODE_ENV=production
WORKDIR /static WORKDIR /static
COPY package.json /
COPY web/package.json . COPY web/package.json .
COPY web/package-lock.json . COPY web/package-lock.json .
RUN --mount=type=bind,target=/static/package.json,src=./web/package.json \ RUN --mount=type=bind,target=/static/package.json,src=./web/package.json \

View File

@ -1,3 +1,4 @@
import { execFileSync } from "child_process";
import * as chokidar from "chokidar"; import * as chokidar from "chokidar";
import esbuild from "esbuild"; import esbuild from "esbuild";
import fs from "fs"; import fs from "fs";
@ -9,12 +10,25 @@ import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url)); const __dirname = fileURLToPath(new URL(".", import.meta.url));
let authentikProjectRoot = __dirname + "../";
try {
// Use the package.json file in the root folder, as it has the current version information.
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf8",
}).replace("\n", "");
} catch (exc) {
// We probably don't have a .git folder, which could happen in container builds
}
const rootPackage = JSON.parse(fs.readFileSync(path.join(authentikProjectRoot, "./package.json")));
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const isProdBuild = process.env.NODE_ENV === "production"; const isProdBuild = process.env.NODE_ENV === "production";
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const apiBasePath = process.env.AK_API_BASE_PATH || ""; const apiBasePath = process.env.AK_API_BASE_PATH || "";
const envGitHashKey = "GIT_BUILD_HASH";
const definitions = { const definitions = {
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"process.env.CWD": JSON.stringify(cwd()), "process.env.CWD": JSON.stringify(cwd()),
@ -80,8 +94,17 @@ const baseArgs = {
format: "esm", format: "esm",
}; };
function getVersion() {
let version = rootPackage.version;
if (process.env[envGitHashKey]) {
version = `${version}.${process.env[envGitHashKey]}`;
}
return version;
}
async function buildOneSource(source, dest) { async function buildOneSource(source, dest) {
const DIST = path.join(__dirname, "./dist", dest); const DIST = path.join(__dirname, "./dist", dest);
// eslint-disable-next-line no-console
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`); console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
try { try {
@ -89,13 +112,13 @@ async function buildOneSource(source, dest) {
await esbuild.build({ await esbuild.build({
...baseArgs, ...baseArgs,
entryPoints: [`./src/${source}`], entryPoints: [`./src/${source}`],
entryNames: `[dir]/[name]-${getVersion()}`,
outdir: DIST, outdir: DIST,
}); });
const end = Date.now(); const end = Date.now();
// eslint-disable-next-line no-console
console.log( console.log(
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${ `[${new Date(end).toISOString()}] Finished build for target ${source} in ${Date.now() - start}ms`,
Date.now() - start
}ms`,
); );
} catch (exc) { } catch (exc) {
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`); console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
@ -112,12 +135,14 @@ function debouncedBuild() {
clearTimeout(timeoutId); clearTimeout(timeoutId);
} }
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
// eslint-disable-next-line no-console
console.clear(); console.clear();
buildAuthentik(interfaces); buildAuthentik(interfaces);
}, 250); }, 250);
} }
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) { if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
// eslint-disable-next-line no-console
console.log(`Build the authentikUI console.log(`Build the authentikUI
options: options:
@ -129,6 +154,7 @@ options:
} }
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) { if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
// eslint-disable-next-line no-console
console.log("Watching ./src for changes"); console.log("Watching ./src for changes");
chokidar.watch("./src").on("all", (event, path) => { chokidar.watch("./src").on("all", (event, path) => {
if (!["add", "change", "unlink"].includes(event)) { if (!["add", "change", "unlink"].includes(event)) {