diff --git a/.diploi/helm/app-ingress.yaml b/.diploi/helm/app-ingress.yaml
new file mode 100644
index 0000000..2da84f0
--- /dev/null
+++ b/.diploi/helm/app-ingress.yaml
@@ -0,0 +1,22 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: ingress
+ annotations:
+ kubernetes.io/ingress.class: traefik
+spec:
+ tls:
+ - hosts:
+ - {{ .Values.hosts.app }}
+ secretName: tls-secret
+ rules:
+ - host: {{ .Values.hosts.app }}
+ http:
+ paths:
+ - path: '/'
+ pathType: Prefix
+ backend:
+ service:
+ name: app
+ port:
+ number: 3000
diff --git a/.diploi/helm/app-service.yaml b/.diploi/helm/app-service.yaml
new file mode 100644
index 0000000..4ec07f0
--- /dev/null
+++ b/.diploi/helm/app-service.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: app
+spec:
+ ports:
+ - port: 3000
+ name: app
+ selector:
+ app: app
diff --git a/.diploi/helm/app.yaml b/.diploi/helm/app.yaml
new file mode 100644
index 0000000..9876597
--- /dev/null
+++ b/.diploi/helm/app.yaml
@@ -0,0 +1,84 @@
+apiVersion: apps/v1
+{{- if eq .Values.stage "development"}}
+kind: StatefulSet
+{{- else }}
+kind: Deployment
+{{- end }}
+metadata:
+ name: app
+ labels:
+ app: app
+spec:
+ selector:
+ matchLabels:
+ app: app
+ {{- if eq .Values.stage "development"}}
+ serviceName: app
+ {{- else }}
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxUnavailable: 25%
+ maxSurge: 25%
+ {{- end }}
+ replicas: {{ ternary 1 0 .Values.enabled }}
+ template:
+ metadata:
+ labels:
+ app: app
+ spec:
+ terminationGracePeriodSeconds: 10
+ imagePullSecrets:
+ - name: diploi-pull-secret
+ {{- if eq .Values.stage "development"}}
+ initContainers:
+ - name: install-dependencies
+ image: {{ .Values.images.app }}
+ imagePullPolicy: Always
+ command: ['bun', 'install']
+ workingDir: /app/{{ .Values.identifier }}
+ volumeMounts:
+ - name: app-mount
+ mountPath: /app
+ {{- end }}
+ containers:
+ - name: app
+ image: {{ .Values.images.app }}
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 3000
+ {{- if eq .Values.stage "development" }}
+ workingDir: /app/{{ .Values.identifier }}
+ {{- end }}
+ env:
+ {{ range $key, $val := .Values.parameterGroupsEnabled }}
+ {{ if $val }}
+ - name: parameter_group_{{ $key }}_enabled
+ value: "1"
+ {{ end }}
+ {{ end }}
+ {{- range .Values.env }}
+ {{- if contains "app" .contexts }}
+ - name: {{ .identifier }}
+ value: {{ .value | quote }}
+ {{- end }}
+ {{- end }}
+ {{- range .Values.parameterGroups }}
+ - name: {{ .identifier }}
+ value: {{ .value | quote }}
+ {{- end }}
+ - name: APP_PUBLIC_URL
+ value: {{ .Values.hosts.app }}
+ - name: STAGE
+ value: {{ .Values.stage }}
+ volumeMounts:
+ {{- if hasKey .Values.storage "code" }}
+ - name: app-mount
+ mountPath: /app
+ {{- end }}
+ volumes:
+ {{- if hasKey .Values.storage "code" }}
+ - name: app-mount
+ hostPath:
+ path: {{ .Values.storage.code.hostPath }}
+ {{- end }}
diff --git a/.diploi/icon.svg b/.diploi/icon.svg
new file mode 100644
index 0000000..7ef1500
--- /dev/null
+++ b/.diploi/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4592d66
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+Dockerfile
+Dockerfile.dev
+.dockerignore
+node_modules
+npm-debug.log
+README.md
+.git
\ No newline at end of file
diff --git a/.github/workflows/Prebuild.yaml b/.github/workflows/Prebuild.yaml
new file mode 100644
index 0000000..5b76e2a
--- /dev/null
+++ b/.github/workflows/Prebuild.yaml
@@ -0,0 +1,95 @@
+name: Pre-Build
+
+on:
+ push:
+
+env:
+ REGISTRY: ghcr.io
+
+jobs:
+ production:
+ name: 'Production'
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ steps:
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ github.repository }}
+
+ - name: 'Login to GitHub Container Registry'
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set tags
+ id: set-tags
+ run: |
+ TAGS="${{ steps.meta.outputs.tags }}"
+ if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
+ TAGS="${TAGS},${{ env.REGISTRY }}/${{ github.repository }}:latest"
+ fi
+ echo "tags=$TAGS" >> $GITHUB_ENV
+
+ - name: Build and push
+ uses: docker/build-push-action@v6
+ with:
+ push: ${{ github.event_name != 'pull_request' }}
+ platforms: linux/arm64
+ tags: ${{ env.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+
+ development:
+ name: 'Development'
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ steps:
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ github.repository }}
+ flavor: |
+ suffix=-dev.
+
+ - name: 'Login to GitHub Container Registry'
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set tags
+ id: set-tags
+ run: |
+ TAGS="${{ steps.meta.outputs.tags }}"
+ if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
+ TAGS="${TAGS},${{ env.REGISTRY }}/${{ github.repository }}:latest"
+ fi
+ echo "tags=$TAGS" >> $GITHUB_ENV
+
+ - name: Build and push
+ uses: docker/build-push-action@v6
+ with:
+ file: Dockerfile.dev
+ push: ${{ github.event_name != 'pull_request' }}
+ platforms: linux/arm64
+ tags: ${{ env.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..07176e6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# typescript
+*.tsbuildinfo
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..998fe55
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,44 @@
+FROM oven/bun:1.2-slim AS base
+
+# This will be set by the GitHub action to the folder containing this component.
+ARG FOLDER=/app
+
+# Install dependencies into temp directory
+# This will cache them and speed up future builds
+FROM base AS install
+
+COPY . /app
+WORKDIR ${FOLDER}
+
+RUN mkdir -p /temp/dev
+COPY package.json bun.lock /temp/dev/
+RUN cd /temp/dev && bun install --frozen-lockfile
+
+RUN mkdir -p /temp/prod
+COPY package.json bun.lock /temp/prod/
+# Install with --production (exclude devDependencies)
+RUN cd /temp/prod && bun install --frozen-lockfile --production
+
+# Copy node_modules from temp directory
+FROM base AS prerelease
+COPY . /app
+WORKDIR ${FOLDER}
+COPY --from=install /temp/dev/node_modules node_modules
+
+ENV NODE_ENV=production
+RUN bun run build
+
+# Copy production dependencies and source code into final image
+FROM base AS release
+COPY --from=prerelease --chown=bun:bun /app /app
+WORKDIR ${FOLDER}
+
+ENV NODE_ENV=production
+
+USER bun
+
+EXPOSE 3000/tcp
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
+
+ENTRYPOINT ["bun", "start"]
diff --git a/Dockerfile.dev b/Dockerfile.dev
new file mode 100644
index 0000000..fe173b6
--- /dev/null
+++ b/Dockerfile.dev
@@ -0,0 +1,15 @@
+# This will be set by the GitHub action to the folder containing this component.
+ARG FOLDER=/app
+
+FROM oven/bun:1.2-slim AS base
+
+COPY . /app
+WORKDIR ${FOLDER}
+
+ENV NODE_ENV=development
+
+EXPOSE 3000/tcp
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
+
+ENTRYPOINT ["bun", "dev"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f2ef2d5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+
+
+# Bun Component for Diploi
+
+A generic Bun component that can be used to run any JS / TS app.
+
+Uses the official [oven/bun](https://hub.docker.com/r/oven/bun) Docker image.
+
+## Operation
+
+### Development
+
+Will run `bun install` when component is first initialized, and `bun dev` when deployment is started.
+
+### Production
+
+Will build a production ready image. Image runs `bun install` & `bun run build` when being created. Once the image runs, `bun start` is called.
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 0000000..78a8443
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,27 @@
+{
+ "lockfileVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "component-bun",
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ "peerDependencies": {
+ "typescript": "^5.7.0",
+ },
+ },
+ },
+ "packages": {
+ "@types/bun": ["@types/bun@1.2.1", "", { "dependencies": { "bun-types": "1.2.1" } }, "sha512-iiCeMAKMkft8EPQJxSbpVRD0DKqrh91w40zunNajce3nMNNFd/LnAquVisSZC+UpTMjDwtcdyzbWct08IvEqRA=="],
+
+ "@types/node": ["@types/node@22.12.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA=="],
+
+ "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
+
+ "bun-types": ["bun-types@1.2.1", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-p7bmXUWmrPWxhcbFVk7oUXM5jAGt94URaoa3qf4mz43MEhNAo/ot1urzBqctgvuq7y9YxkuN51u+/qm4BiIsHw=="],
+
+ "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
+
+ "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
+ }
+}
diff --git a/diploi.yaml b/diploi.yaml
new file mode 100644
index 0000000..36fcc28
--- /dev/null
+++ b/diploi.yaml
@@ -0,0 +1,27 @@
+diploiVersion: v1.0
+type: component
+name: Bun
+description: Official Diploi component for Bun
+
+contexts:
+ - name: app
+ identifier: app
+
+hosts:
+ - name: Bun
+ identifier: app
+ urlFormat: '[label].[default-domain]'
+
+connectionStrings:
+ - name: Internal Address
+ value: http://app.${DIPLOI_NAMESPACE}:3000
+ description: This address is for requests from within the deployment and is inaccessible externally.
+
+images:
+ - identifier: app
+ prebuildImage: ghcr.io/diploi/component-bun:[tag]
+
+logs:
+ - name: Bun Log
+ type: log
+ labelSelector: app=app
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fa9bbc9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "component-bun",
+ "module": "src/index.ts",
+ "type": "module",
+ "scripts": {
+ "dev": "bun --watch src/index.ts",
+ "build": "echo 'Add your build step here if one is required'",
+ "start": "bun run src/index.ts"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.7.0"
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..dc92da2
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,10 @@
+const port = Bun.env.PORT;
+const hostname = Bun.env.HOSTNAME;
+
+Bun.serve({
+ port,
+ hostname,
+ fetch(req) {
+ return new Response('Bun!');
+ },
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..238655f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ // Enable latest features
+ "lib": ["ESNext", "DOM"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "allowJs": true,
+
+ // Bundler mode
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+
+ // Best practices
+ "strict": true,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+
+ // Some stricter flags (disabled by default)
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noPropertyAccessFromIndexSignature": false
+ }
+}