mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-07-09 16:09:13 +00:00
fix(ui): tolerate legacy npm run start override + add official Helm chart (#6683)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b5ad775942
commit
4d0b721966
12 changed files with 342 additions and 2 deletions
|
|
@ -132,3 +132,6 @@ repos:
|
|||
rev: v0.17.2
|
||||
hooks:
|
||||
- id: yamlfmt
|
||||
# Helm chart templates contain Go templating ({{ ... }}) and are not
|
||||
# valid standalone YAML, so yamlfmt cannot parse them.
|
||||
exclude: ^k8s/charts/.*/templates/
|
||||
|
|
|
|||
10
k8s/charts/skyvern-ui/.helmignore
Normal file
10
k8s/charts/skyvern-ui/.helmignore
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Patterns to ignore when building packages.
|
||||
.DS_Store
|
||||
.git/
|
||||
.gitignore
|
||||
*.tmproj
|
||||
*.tmp
|
||||
*.bak
|
||||
*.orig
|
||||
.idea/
|
||||
.vscode/
|
||||
14
k8s/charts/skyvern-ui/Chart.yaml
Normal file
14
k8s/charts/skyvern-ui/Chart.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: v2
|
||||
name: skyvern-ui
|
||||
description: Skyvern UI — prebuilt frontend bundle served with runtime env injection
|
||||
type: application
|
||||
# Chart version — bump on chart changes.
|
||||
version: 0.1.0
|
||||
# Default Skyvern UI image version this chart targets.
|
||||
appVersion: "v1.0.42"
|
||||
home: https://www.skyvern.com
|
||||
sources:
|
||||
- https://github.com/Skyvern-AI/skyvern
|
||||
maintainers:
|
||||
- name: Skyvern
|
||||
url: https://www.skyvern.com
|
||||
28
k8s/charts/skyvern-ui/templates/NOTES.txt
Normal file
28
k8s/charts/skyvern-ui/templates/NOTES.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
Skyvern UI ({{ .Values.image.repository }}:{{ .Values.image.tag }}) has been deployed.
|
||||
|
||||
IMPORTANT — do NOT override the container command.
|
||||
This chart deliberately runs the image's own entrypoint, which injects the
|
||||
VITE_* runtime config into the prebuilt bundle and serves it. Adding a
|
||||
`command:`/`args:` such as ["npm","run","start"] forces a runtime build that
|
||||
the production image cannot perform and will crashloop ("tsc: not found").
|
||||
|
||||
Access the UI:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
http(s)://{{ (index .Values.ingress.hosts 0).host }}
|
||||
{{- else if eq .Values.service.type "ClusterIP" }}
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "skyvern-ui.fullname" . }} 8080:{{ .Values.service.port }}
|
||||
then open http://localhost:8080
|
||||
{{- else }}
|
||||
Service type {{ .Values.service.type }} — resolve the external address with:
|
||||
kubectl --namespace {{ .Release.Namespace }} get svc {{ include "skyvern-ui.fullname" . }}
|
||||
{{- end }}
|
||||
|
||||
Backend wiring (set in values.yaml -> config):
|
||||
VITE_API_BASE_URL = {{ .Values.config.apiBaseUrl }}
|
||||
VITE_WSS_BASE_URL = {{ .Values.config.wssBaseUrl }}
|
||||
VITE_ARTIFACT_API_BASE_URL = {{ .Values.config.artifactApiBaseUrl }}
|
||||
{{- if and (not .Values.apiKey.value) (not .Values.apiKey.existingSecret) }}
|
||||
|
||||
WARNING: no API key configured (apiKey.value / apiKey.existingSecret). The UI
|
||||
will start but cannot authenticate to the backend until one is provided.
|
||||
{{- end }}
|
||||
48
k8s/charts/skyvern-ui/templates/_helpers.tpl
Normal file
48
k8s/charts/skyvern-ui/templates/_helpers.tpl
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{{/* Expand the name of the chart. */}}
|
||||
{{- define "skyvern-ui.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Create a default fully qualified app name. */}}
|
||||
{{- define "skyvern-ui.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Chart name and version as used by the chart label. */}}
|
||||
{{- define "skyvern-ui.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Common labels */}}
|
||||
{{- define "skyvern-ui.labels" -}}
|
||||
helm.sh/chart: {{ include "skyvern-ui.chart" . }}
|
||||
{{ include "skyvern-ui.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Selector labels */}}
|
||||
{{- define "skyvern-ui.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "skyvern-ui.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Name of the Secret holding the API key (chart-managed or external). */}}
|
||||
{{- define "skyvern-ui.apiKeySecretName" -}}
|
||||
{{- if .Values.apiKey.existingSecret }}
|
||||
{{- .Values.apiKey.existingSecret }}
|
||||
{{- else }}
|
||||
{{- include "skyvern-ui.fullname" . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
14
k8s/charts/skyvern-ui/templates/configmap.yaml
Normal file
14
k8s/charts/skyvern-ui/templates/configmap.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "skyvern-ui.fullname" . }}
|
||||
labels:
|
||||
{{- include "skyvern-ui.labels" . | nindent 4 }}
|
||||
data:
|
||||
VITE_API_BASE_URL: {{ .Values.config.apiBaseUrl | quote }}
|
||||
VITE_WSS_BASE_URL: {{ .Values.config.wssBaseUrl | quote }}
|
||||
VITE_ARTIFACT_API_BASE_URL: {{ .Values.config.artifactApiBaseUrl | quote }}
|
||||
VITE_BROWSER_STREAMING_MODE: {{ .Values.config.browserStreamingMode | quote }}
|
||||
VITE_ENABLE_CODE_BLOCK: {{ .Values.config.enableCodeBlock | quote }}
|
||||
VITE_ENABLE_LOG_ARTIFACTS: {{ .Values.config.enableLogArtifacts | quote }}
|
||||
VITE_ENABLE_2FA_NOTIFICATIONS: {{ .Values.config.enable2faNotifications | quote }}
|
||||
85
k8s/charts/skyvern-ui/templates/deployment.yaml
Normal file
85
k8s/charts/skyvern-ui/templates/deployment.yaml
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "skyvern-ui.fullname" . }}
|
||||
labels:
|
||||
{{- include "skyvern-ui.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "skyvern-ui.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
# Roll pods when the injected runtime config changes so new env applies.
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "skyvern-ui.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: skyvern-ui
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
# NOTE: intentionally NO `command`/`args`. The image entrypoint injects
|
||||
# VITE_* runtime config into the prebuilt bundle and serves it. Setting
|
||||
# a command (e.g. ["npm","run","start"]) forces a runtime build the
|
||||
# production image cannot do and crashloops with "tsc: not found".
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "skyvern-ui.fullname" . }}
|
||||
env:
|
||||
- name: VITE_SKYVERN_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "skyvern-ui.apiKeySecretName" . }}
|
||||
key: {{ .Values.apiKey.existingSecretKey }}
|
||||
optional: true
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: artifact
|
||||
containerPort: 9090
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
37
k8s/charts/skyvern-ui/templates/ingress.yaml
Normal file
37
k8s/charts/skyvern-ui/templates/ingress.yaml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "skyvern-ui.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "skyvern-ui.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.ingress.className }}
|
||||
ingressClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
11
k8s/charts/skyvern-ui/templates/secret.yaml
Normal file
11
k8s/charts/skyvern-ui/templates/secret.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{{- if and .Values.apiKey.value (not .Values.apiKey.existingSecret) }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "skyvern-ui.fullname" . }}
|
||||
labels:
|
||||
{{- include "skyvern-ui.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{ .Values.apiKey.existingSecretKey }}: {{ .Values.apiKey.value | quote }}
|
||||
{{- end }}
|
||||
19
k8s/charts/skyvern-ui/templates/service.yaml
Normal file
19
k8s/charts/skyvern-ui/templates/service.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "skyvern-ui.fullname" . }}
|
||||
labels:
|
||||
{{- include "skyvern-ui.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
selector:
|
||||
{{- include "skyvern-ui.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
- name: artifact
|
||||
port: {{ .Values.service.artifactPort }}
|
||||
targetPort: artifact
|
||||
protocol: TCP
|
||||
71
k8s/charts/skyvern-ui/values.yaml
Normal file
71
k8s/charts/skyvern-ui/values.yaml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Default values for skyvern-ui.
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: public.ecr.aws/skyvern/skyvern-ui
|
||||
# Pin to a specific version. Do NOT use "latest" in production: a floating tag
|
||||
# means a pod restart can silently pull a newly published image, which is
|
||||
# exactly how a known-good deployment can drift onto a different build.
|
||||
tag: "v1.0.42"
|
||||
pullPolicy: IfNotPresent
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
# -----------------------------------------------------------------------------
|
||||
# IMPORTANT: do not set a container `command`/`args` for the UI.
|
||||
#
|
||||
# The image ships a PREBUILT static bundle. Its entrypoint injects the VITE_*
|
||||
# runtime config below into the already-built assets at container start and then
|
||||
# serves them. Overriding the command (e.g. `npm run start`) forces a runtime
|
||||
# `tsc && vite build`, which the lean production image cannot perform — the
|
||||
# container will crashloop with "tsc: not found". Let the image entrypoint run.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Runtime config injected into the prebuilt bundle by the image entrypoint.
|
||||
config:
|
||||
apiBaseUrl: "http://skyvern:8000/api/v1"
|
||||
wssBaseUrl: "ws://skyvern:8000/api/v1"
|
||||
artifactApiBaseUrl: "http://skyvern:9090"
|
||||
browserStreamingMode: "vnc"
|
||||
enableCodeBlock: "false"
|
||||
# NOTE: enableLogArtifacts is honored at runtime only on images that ship the
|
||||
# log-artifacts placeholder in the bundle. On older images this flag is fixed
|
||||
# at build time; setting it here is forward-compatible and harmless.
|
||||
enableLogArtifacts: "false"
|
||||
enable2faNotifications: "false"
|
||||
# API key the UI uses to call the backend. Provide ONE of:
|
||||
# - apiKey.value: inline (templated into a Secret managed by this chart)
|
||||
# - apiKey.existingSecret (+ existingSecretKey): reference a pre-existing Secret
|
||||
# If neither is set, the env is left unset (the UI logs a warning and starts).
|
||||
apiKey:
|
||||
value: ""
|
||||
existingSecret: ""
|
||||
existingSecretKey: "VITE_SKYVERN_API_KEY"
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
artifactPort: 9090
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
hosts:
|
||||
- host: skyvern-ui.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls: []
|
||||
# Sized for SERVING static assets, not building. A runtime build would need
|
||||
# ~4Gi (Vite/rollup) — another reason not to override the command.
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
podAnnotations: {}
|
||||
podSecurityContext: {}
|
||||
securityContext: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
|
@ -14,9 +14,9 @@
|
|||
"artifact-server": "node artifactServer.js",
|
||||
"run-artifact-server": "node artifactServer.js",
|
||||
"start-local": "npm run dev & npm run artifact-server",
|
||||
"serve": "npm run build && node localServer.js",
|
||||
"serve": "if [ -d dist.template ]; then echo '[skyvern-ui] prebuilt bundle detected; injecting runtime env and serving without rebuild' && exec bash ./entrypoint-skyvernui.sh; else npm run build && node localServer.js; fi",
|
||||
"test": "vitest run",
|
||||
"start": "npm run serve & npm run run-artifact-server"
|
||||
"start": "if [ -d dist.template ]; then echo '[skyvern-ui] prebuilt bundle detected; injecting runtime env and serving without rebuild' && exec bash ./entrypoint-skyvernui.sh; else npm run serve & npm run run-artifact-server; fi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.61.8",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue