feat(frontend): embed SPA assets into app binary at compile time
- Add libs/frontend crate: build.rs runs pnpm build, copies dist/ to
OUT_DIR/dist_blobs/, generates frontend.rs with lazy_static! map
- libs/api/dist.rs serves embedded assets via serve_frontend handler
- Register /{path:.*} SPA fallback in route.rs (after /api/*)
- Remove frontend container from deploy: docker/frontend.Dockerfile,
deploy/templates/frontend-*.yaml, values.yaml frontend section
- Update ingress: gitdata.ai root now routes to app service
- Update scripts: build.js removes frontend step, deploy.js removes frontend
This commit is contained in:
parent
3de4fff11d
commit
7e42139989
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -564,8 +564,10 @@ dependencies = [
|
||||
"config",
|
||||
"db",
|
||||
"email",
|
||||
"frontend",
|
||||
"futures",
|
||||
"git",
|
||||
"mime_guess2",
|
||||
"models",
|
||||
"queue",
|
||||
"room",
|
||||
@ -2544,6 +2546,14 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frontend"
|
||||
version = "0.2.9"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@ -4364,6 +4374,7 @@ checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"phf_shared",
|
||||
"unicase",
|
||||
]
|
||||
@ -5042,6 +5053,16 @@ dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
@ -6311,6 +6332,15 @@ dependencies = [
|
||||
"cipher 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
@ -8040,6 +8070,16 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
@ -8266,6 +8306,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@ -8765,6 +8814,10 @@ dependencies = [
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "workspace"
|
||||
version = "0.2.9"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"libs/frontend",
|
||||
"libs/models",
|
||||
"libs/session",
|
||||
"libs/git",
|
||||
@ -30,6 +31,7 @@ resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
models = { path = "libs/models" }
|
||||
frontend = { path = "libs/frontend" }
|
||||
session = { path = "libs/session" }
|
||||
git = { path = "libs/git" }
|
||||
email = { path = "libs/email" }
|
||||
@ -140,11 +142,14 @@ hostname = "0.4"
|
||||
utoipa = { version = "5.4.0", features = ["chrono", "uuid"] }
|
||||
rust_decimal = "1.40.0"
|
||||
walkdir = "2.5.0"
|
||||
lazy_static = "1.5"
|
||||
moka = "0.12.15"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
serde_yaml = "0.9.33"
|
||||
serde_bytes = "0.11.19"
|
||||
phf = "0.13.1"
|
||||
phf_codegen = "0.13.1"
|
||||
base64 = "0.22.1"
|
||||
|
||||
|
||||
@ -184,3 +189,22 @@ opt-level = 3
|
||||
|
||||
[profile.dev.package.num-bigint-dig]
|
||||
opt-level = 3
|
||||
|
||||
|
||||
[package]
|
||||
name = "workspace"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
description.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
homepage.workspace = true
|
||||
license.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
documentation.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
crate-type = ["lib"]
|
||||
@ -1,86 +0,0 @@
|
||||
{{- if .Values.frontend.enabled -}}
|
||||
{{- $fullName := include "gitdata.fullname" . -}}
|
||||
{{- $ns := include "gitdata.namespace" . -}}
|
||||
{{- $svc := .Values.frontend -}}
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ $fullName }}-frontend
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ $fullName }}-frontend
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion }}
|
||||
spec:
|
||||
replicas: {{ $svc.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ $fullName }}-frontend
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ $fullName }}-frontend
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
spec:
|
||||
{{- if $.Values.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- range $.Values.image.pullSecrets }}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: frontend
|
||||
image: "{{ $.Values.image.registry }}/{{ $svc.image.repository }}:{{ $svc.image.tag }}"
|
||||
imagePullPolicy: {{ $svc.image.pullPolicy | default $.Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: {{ $svc.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ $svc.livenessProbe.periodSeconds }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: {{ $svc.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ $svc.readinessProbe.periodSeconds }}
|
||||
{{- with $svc.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with $svc.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with $svc.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $fullName }}-frontend
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ $fullName }}-frontend
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
spec:
|
||||
type: {{ $svc.service.type }}
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
selector:
|
||||
app.kubernetes.io/name: {{ $fullName }}-frontend
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@ -1,61 +0,0 @@
|
||||
{{- if .Values.frontend.ingress.enabled -}}
|
||||
{{- $fullName := include "gitdata.fullname" . -}}
|
||||
{{- $ns := include "gitdata.namespace" . -}}
|
||||
{{- $ing := .Values.frontend.ingress -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}-frontend
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ $fullName }}-frontend
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ $ing.clusterIssuer | default "cloudflare-acme-cluster-issuer" }}
|
||||
{{- if $ing.annotations }}
|
||||
{{ toYaml $ing.annotations | indent 4 }}
|
||||
{{- end }}
|
||||
{{- if not (hasKey ($ing.annotations | default dict) "nginx.ingress.kubernetes.io/proxy-body-size") }}
|
||||
{{- if or (not $ing.className) (eq $ing.className "nginx") (contains "nginx" $ing.className) }}
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if $ing.className }}
|
||||
ingressClassName: {{ $ing.className }}
|
||||
{{- end }}
|
||||
{{- if $ing.tls }}
|
||||
tls:
|
||||
{{- range $ing.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
tls:
|
||||
{{- range $ing.hosts }}
|
||||
- hosts:
|
||||
- {{ .host | quote }}
|
||||
secretName: {{ $fullName }}-frontend-tls
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range $ing.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType | default "Prefix" }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}-frontend
|
||||
port:
|
||||
number: 80
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@ -24,7 +24,7 @@ spec:
|
||||
- static.gitdata.ai
|
||||
secretName: {{ $fullName }}-tls
|
||||
rules:
|
||||
# Frontend (default), with /api and /ws routed to app
|
||||
# SPA (embedded in app), with /api and /ws
|
||||
- host: gitdata.ai
|
||||
http:
|
||||
paths:
|
||||
@ -46,9 +46,9 @@ spec:
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}-frontend
|
||||
name: {{ $fullName }}-app
|
||||
port:
|
||||
number: 80
|
||||
number: {{ .Values.app.service.port }}
|
||||
- host: api.gitdata.ai
|
||||
http:
|
||||
paths:
|
||||
|
||||
@ -123,41 +123,7 @@ qdrant:
|
||||
apiKey: APP_QDRANT_API_KEY
|
||||
|
||||
# =============================================================================
|
||||
# Frontend - React SPA
|
||||
# =============================================================================
|
||||
frontend:
|
||||
enabled: true
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: frontend
|
||||
tag: latest
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
# =============================================================================
|
||||
# App – main web/API service
|
||||
# App – main web/API service (includes embedded SPA)
|
||||
# =============================================================================
|
||||
app:
|
||||
enabled: true
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
# Runtime only — frontend built externally via pnpm
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy pre-built SPA assets (pnpm build outputs to dist/)
|
||||
COPY dist /usr/share/nginx/html
|
||||
|
||||
# nginx configuration for SPA
|
||||
RUN echo 'resolver 10.96.0.10 valid=10s ipv6=off; \
|
||||
server { \
|
||||
listen 80; \
|
||||
server_name _; \
|
||||
root /usr/share/nginx/html; \
|
||||
index index.html; \
|
||||
location / { \
|
||||
try_files $uri $uri/ /index.html; \
|
||||
} \
|
||||
location /api/ { \
|
||||
set $upstream_app http://app:8080; \
|
||||
proxy_pass $upstream_app/api/; \
|
||||
proxy_set_header Host $host; \
|
||||
proxy_set_header X-Real-IP $remote_addr; \
|
||||
} \
|
||||
location /ws/ { \
|
||||
set $upstream_app http://app:8080; \
|
||||
proxy_pass $upstream_app/ws/; \
|
||||
proxy_http_version 1.1; \
|
||||
proxy_set_header Upgrade $http_upgrade; \
|
||||
proxy_set_header Connection "upgrade"; \
|
||||
proxy_set_header Host $host; \
|
||||
} \
|
||||
}' > /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
||||
1
lib.rs
Normal file
1
lib.rs
Normal file
@ -0,0 +1 @@
|
||||
// Frontend embedding is handled by libs/frontend crate.
|
||||
@ -27,6 +27,7 @@ slog = { workspace = true }
|
||||
service = { workspace = true }
|
||||
session = { workspace = true }
|
||||
git = { workspace = true }
|
||||
frontend = { workspace = true }
|
||||
models = { workspace = true }
|
||||
room = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@ -43,5 +44,6 @@ tokio-stream = { workspace = true, features = ["sync"] }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "rt"] }
|
||||
chrono = { workspace = true }
|
||||
mime_guess2 = { workspace = true, features = ["phf-map"] }
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
1
libs/api/build.rs
Normal file
1
libs/api/build.rs
Normal file
@ -0,0 +1 @@
|
||||
fn main() {}
|
||||
29
libs/api/dist.rs
Normal file
29
libs/api/dist.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use actix_web::{web, HttpResponse};
|
||||
use mime_guess2::MimeGuess;
|
||||
|
||||
pub async fn serve_frontend(path: web::Path<String>) -> HttpResponse {
|
||||
let path = path.into_inner();
|
||||
let path = if path.is_empty() || path == "/" {
|
||||
"index.html"
|
||||
} else {
|
||||
&path
|
||||
};
|
||||
|
||||
match frontend::get_frontend_asset(path) {
|
||||
Some(data) => {
|
||||
let mime = MimeGuess::from_path(path).first_or_octet_stream();
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime.as_ref())
|
||||
.body(data.to_vec())
|
||||
}
|
||||
None => {
|
||||
// Fallback to index.html for SPA routing
|
||||
match frontend::get_frontend_asset("index.html") {
|
||||
Some(data) => HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(data.to_vec()),
|
||||
None => HttpResponse::NotFound().finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,3 +14,4 @@ pub mod user;
|
||||
pub mod workspace;
|
||||
|
||||
pub use error::{api_success, ApiError, ApiResponse};
|
||||
pub mod dist;
|
||||
|
||||
@ -26,4 +26,7 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) {
|
||||
.configure(crate::room::init_room_routes)
|
||||
.configure(crate::skill::init_skill_routes),
|
||||
);
|
||||
|
||||
// SPA fallback — must be registered last so /api/* takes precedence
|
||||
cfg.route("/{path:.*}", web::get().to(crate::dist::serve_frontend));
|
||||
}
|
||||
|
||||
10
libs/frontend/Cargo.toml
Normal file
10
libs/frontend/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "frontend"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
lazy_static.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
walkdir.workspace = true
|
||||
80
libs/frontend/build.rs
Normal file
80
libs/frontend/build.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use std::{env, fs, path::PathBuf, process::Command};
|
||||
|
||||
fn run_pnpm(args: &[&str], cwd: &str) {
|
||||
let mut cmd = if cfg!(target_os = "windows") {
|
||||
let mut c = Command::new("cmd");
|
||||
c.args(["/C", "pnpm"]);
|
||||
c
|
||||
} else {
|
||||
Command::new("pnpm")
|
||||
};
|
||||
|
||||
let status = cmd
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.status()
|
||||
.expect("failed to run pnpm");
|
||||
|
||||
if !status.success() {
|
||||
panic!("pnpm command failed: {:?}", args);
|
||||
}
|
||||
}
|
||||
|
||||
fn find_all_file(path: PathBuf) -> Vec<PathBuf> {
|
||||
let mut files = vec![];
|
||||
for entry in fs::read_dir(path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
files.push(path);
|
||||
} else if path.is_dir() {
|
||||
files.extend(find_all_file(path));
|
||||
}
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=../../package.json");
|
||||
println!("cargo:rerun-if-changed=../../pnpm-lock.yaml");
|
||||
println!("cargo:rerun-if-changed=../../tsconfig.json");
|
||||
println!("cargo:rerun-if-changed=../../src/");
|
||||
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let project_root = manifest_dir.parent().unwrap().parent().unwrap();
|
||||
|
||||
let node_modules = project_root.join("node_modules");
|
||||
let _cache_file = node_modules.join(".cache_hash");
|
||||
|
||||
// Build frontend using pnpm in project root
|
||||
println!("cargo:warning=Building frontend...");
|
||||
run_pnpm(&["run", "build"], project_root.to_str().unwrap());
|
||||
|
||||
// Embed dist/ into OUT_DIR as blob files + generated .rs
|
||||
let dist = project_root.join("dist");
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let blob_dir = out_dir.join("dist_blobs");
|
||||
fs::create_dir_all(&blob_dir).unwrap();
|
||||
|
||||
let mut strs = vec![];
|
||||
for file in find_all_file(dist.clone()) {
|
||||
let key = file.strip_prefix(&dist).unwrap()
|
||||
.components()
|
||||
.collect::<PathBuf>()
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
let safe_name = key.replace('/', "_").replace('\\', "_");
|
||||
let blob_path = blob_dir.join(&safe_name);
|
||||
fs::copy(&file, &blob_path).unwrap();
|
||||
let key_literal = format!("\"{}\"", key.replace('"', "\\\""));
|
||||
strs.push(format!(" ({}, include_bytes!(\"dist_blobs/{}\")),", key_literal, safe_name));
|
||||
}
|
||||
|
||||
let out_file = out_dir.join("frontend.rs");
|
||||
let content = format!(
|
||||
"lazy_static::lazy_static! {{\n pub static ref FRONTEND: Vec<(&'static str, &'static [u8])> = vec![\n{} ];\n}}\n",
|
||||
strs.join("\n")
|
||||
);
|
||||
fs::write(&out_file, content).unwrap();
|
||||
println!("cargo:include={}", out_file.display());
|
||||
}
|
||||
8
libs/frontend/src/lib.rs
Normal file
8
libs/frontend/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! Embedded frontend static assets, built via pnpm and embedded at compile time.
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/frontend.rs"));
|
||||
|
||||
/// Returns the embedded frontend static asset for the given path, or `None` if not found.
|
||||
pub fn get_frontend_asset(path: &str) -> Option<&'static [u8]> {
|
||||
FRONTEND.iter().find(|(k, _)| *k == path).map(|(_, v)| *v)
|
||||
}
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -26,9 +26,6 @@ importers:
|
||||
'@gitgraph/react':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(react@19.2.4)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.4(@types/react@19.2.14)(react@19.2.4)
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(jiti@2.6.1))
|
||||
|
||||
@ -27,14 +27,12 @@ const TAG = process.env.TAG || GIT_SHA_SHORT;
|
||||
const BUILD_TARGET = process.env.TARGET || 'x86_64-unknown-linux-gnu';
|
||||
|
||||
const RUST_SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'operator', 'static'];
|
||||
const FRONTEND_SERVICE = 'frontend';
|
||||
const ALL_SERVICES = [...RUST_SERVICES, FRONTEND_SERVICE];
|
||||
const ALL_SERVICES = RUST_SERVICES;
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const targets = args.length > 0 ? args : ALL_SERVICES;
|
||||
|
||||
const rustTargets = targets.filter(s => RUST_SERVICES.includes(s));
|
||||
const needsFrontend = targets.includes(FRONTEND_SERVICE);
|
||||
const rustTargets = targets;
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
|
||||
console.log(`\n=== Build Configuration ===`);
|
||||
@ -65,26 +63,7 @@ if (rustTargets.length > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Build frontend (frontend source is at repo root)
|
||||
if (needsFrontend) {
|
||||
console.log(`\n==> Step 2: Building frontend`);
|
||||
if (!fs.existsSync(path.join(rootDir, 'vite.config.ts'))) {
|
||||
console.error(`\n [FAIL] vite.config.ts not found`);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
execSync(
|
||||
`corepack enable && corepack prepare pnpm@10 --activate && pnpm install --frozen-lockfile && pnpm build`,
|
||||
{ stdio: 'inherit', cwd: rootDir }
|
||||
);
|
||||
console.log(` [OK] Frontend built`);
|
||||
} catch (error) {
|
||||
console.error(` [FAIL] Frontend build failed`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Build Docker images
|
||||
// Step 2: Build Docker images (frontend is embedded in app at compile time via libs/frontend)
|
||||
console.log(`\n==> Step 3: Building Docker images`);
|
||||
|
||||
for (const service of targets) {
|
||||
|
||||
@ -31,7 +31,7 @@ const args = process.argv.slice(2);
|
||||
const isDryRun = args.includes('--dry-run');
|
||||
const runMigrate = args.includes('--migrate');
|
||||
|
||||
const SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'operator', 'static', 'frontend'];
|
||||
const SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'operator', 'static'];
|
||||
|
||||
// Validate kubeconfig
|
||||
if (!fs.existsSync(KUBECONFIG)) {
|
||||
@ -61,7 +61,6 @@ const setValues = [
|
||||
`gitHook.image.tag=${TAG}`,
|
||||
`operator.image.tag=${TAG}`,
|
||||
`static.image.tag=${TAG}`,
|
||||
`frontend.image.tag=${TAG}`,
|
||||
];
|
||||
|
||||
if (runMigrate) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user