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 @@ +Bun Logo \ 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 @@ +icon + +# 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 + } +}