Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

permissions:
contents: read
actions: read
checks: write

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'gradle'
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: browser-actions/setup-chrome@v2
with:
chrome-version: 'stable'
install-chromedriver: true
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' }}
- name: Install Node modules
run: npm install
- name: Run tests
run: |
npm run hmr &
./gradlew --no-daemon jacocoTestReport
env:
HEADLESS: true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
- name: Publish Test Results
uses: dorny/test-reporter@v1
if: ${{ !cancelled() }}
with:
name: CI test result
path: build/test-results/test/*.xml
reporter: java-junit
87 changes: 87 additions & 0 deletions .github/workflows/create-release-and-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: create-release-and-docker

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Optional tag. Default to the tag of the selected branch.'
required: false
type: string


permissions:
contents: write

jobs:
create-release-and-docker:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'gradle'
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Get version
id: version
run: echo "VERSION=$(./gradlew -q printVersion)" >> "$GITHUB_OUTPUT"
- name: Validate the tag name
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ ! -z "${{ github.event.inputs.tag }}" ]; then
TAG=${{ github.event.inputs.tag }}
else
TAG=${GITHUB_REF#refs/tags/}
fi
if [[ ! "$TAG" =~ ^v ]]; then
echo "Error: Tag must start with 'v'"
exit 1
fi
TAG_VERSION=${TAG#v}
if [ "$TAG_VERSION" != "${{ steps.version.outputs.VERSION }}" ]; then
echo "Error: Git tag version ($TAG_VERSION) doesn't match project version (v${{ steps.version.outputs.VERSION }})"
exit 1
fi
- name: Install Node modules
run: npm install
- name: Build a publishable JAR
run: ./gradlew clean publish
- name: Upload Release Asset
uses: softprops/action-gh-release@v2
with:
draft: true
prerelease: true
files: ./build/staging-deploy/io/github/tanin47/embeddable-java-web-framework/**/*
overwrite_files: true
fail_on_unmatched_files: true
generate_release_notes: true
tag_name: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
- name: Log in to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
id: push
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
platforms: linux/amd64,linux/arm64
context: .
file: ./Dockerfile
push: true
tags: tanin47/embeddable-java-web-framework:${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
64 changes: 64 additions & 0 deletions .github/workflows/publish-jar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: publish-jar

on:
workflow_dispatch:
inputs:
tag:
description: 'Optional tag. Default to the tag of the selected branch.'
required: false
type: string

permissions:
contents: read

jobs:
publish-jar:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'gradle'
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Get version
id: version
run: echo "VERSION=$(./gradlew -q printVersion)" >> "$GITHUB_OUTPUT"
- name: Validate tag format and version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ ! -z "${{ github.event.inputs.tag }}" ]; then
TAG=${{ github.event.inputs.tag }}
else
TAG=${GITHUB_REF#refs/tags/}
fi
if [[ ! $TAG =~ ^v ]]; then
echo "Error: Tag must start with 'v'"
exit 1
fi
if [[ ! $TAG == v${{ steps.version.outputs.VERSION }} ]]; then
echo "Error: Tag version ($TAG) does not match project version (v${{ steps.version.outputs.VERSION }})"
exit 1
fi
- name: Install Node modules
run: npm install
- name: Build a publishable JAR
run: ./gradlew clean publish
- name: Publish to Sonatype
run: ./gradlew jreleaserDeploy
env:
JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_USERNAME }}
JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.JRELEASER_MAVENCENTRAL_PASSWORD }}
CI: true
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }}
JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }}
50 changes: 35 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ It is suitable for <ins>a sidecar-style website embeddable on a larger JVM syste
The main selling point of EJWF is that it comes with productive and useful conventions and libraries such as:

1. Support Typescripts + Svelte + Tailwind + DaisyUI with Hot-Reload Module (HMR).
2. Support hot-reloading Java through the plugin sbt-revolver.
3. Support packaging a fat JAR with [shading](https://stackoverflow.com/questions/13620281/what-is-the-maven-shade-plugin-used-for-and-why-would-you-want-to-relocate-java).
2. Support packaging a fat JAR with [shading](https://stackoverflow.com/questions/13620281/what-is-the-maven-shade-plugin-used-for-and-why-would-you-want-to-relocate-java).
The JAR is 350KB in size, has *zero* external dependencies, and eliminates any potential dependency conflict when embedding into another JVM system.
4. Avoid Java reflection and magic. This is largely a feature of [Minum](https://github.com/byronka/minum). Any potential runtime errors and conflicts are minimized, which is important when embedding into a larger system.
5. Browser tests are setup and ready to go.
3. Avoid Java reflection and magic. This is largely a feature of [Minum](https://github.com/byronka/minum). Any potential runtime errors and conflicts are minimized, which is important when embedding into a larger system.
4. Browser tests are setup and ready to go.
5. Github actions for testing, code coverage reporting, and publishing have been implemented.

In contrast, most of the lightweight web frameworks focus on being a bare metal web server serving HTML and JSON.
They don't provide support for any frontend framework like React or Svelte; you would have to do it yourself. This is exactly what EJWF provides.

Initially, EJWF was built as a foundation for [Backdoor](https://github.com/tanin47/backdoor), an embeddable sidecar-style JVM-based database administration tool, where
Initially, EJWF was built as a foundation for [embeddable-java-web-framework](https://github.com/tanin47/embeddable-java-web-framework), a self-hosted database querying and editing tool, where
you can embed it into your larger application like SpringBoot or PlayFramework.

How to develop
Expand All @@ -29,29 +29,49 @@ How to develop
3. On a separate terminal, run `npm run hmr` in order to hot-reload the frontend code changes.


Publish
--------
Publish JAR
------------

This flow has been set up as the Github Actions workflow: `publish-jar`.

EJWF is a template repository with collections of libraries and conventions. It's important that you understand
each build process and are able to customize to your needs.

Here's how you can build your fat JAR:

1. Run `./gradlew clean`. This step is IMPORTANT to clean out the previous versions.
2. Build the tailwindbase.css with: `./node_modules/.bin/postcss ./frontend/stylesheets/tailwindbase.css --config . --output ./src/main/resources/assets/stylesheets/tailwindbase.css`
3. Build the production Svelte code with: `ENABLE_SVELTE_CHECK=true ./node_modules/webpack/bin/webpack.js --config ./webpack.config.js --output-path ./src/main/resources/assets --mode production`
4. Build the fat JAR with: `./gradlew shadowJar`
1. Run `./gradlew clean publish`. This step is IMPORTANT to clean out the previous versions.

The far JAR is built at `./build/libs/embeddablee-java-web-framework-VERSION.jar`

You can run your server with: `java -jar ./build/libs/embeddable-java-web-framework-VERSION.jar`

To publish to a Maven repository, please follow the below steps:

1. Remove `./build/staging-deploy` by running `rm -rf ./build/staging-deploy`
2. Run `./gradlew publish`
3. Set up `~/.jreleaser/config.toml` with `JRELEASER_MAVENCENTRAL_USERNAME` and `JRELEASER_MAVENCENTRAL_PASSWORD`
4. Run `./gradlew jreleaserDeploy`
1. Set up `~/.jreleaser/config.toml` with `JRELEASER_MAVENCENTRAL_USERNAME` and `JRELEASER_MAVENCENTRAL_PASSWORD`
2. Run `./gradlew jreleaserDeploy`


Publish Docker
---------------

This flow has been set up as a part of the Github Actions workflow: `create-release-and-docker`.

1. Run `docker buildx build --platform linux/amd64,linux/arm64 -t embeddable-java-web-framework:v0.4.0 .`
2. Test locally with:
`docker run -p 9090:9090 --entrypoint "" embeddable-java-web-framework:v0.4.0 java -jar embeddable-java-web-framework-0.4.0.jar -port 9090`
3. Run: `docker tag embeddable-java-web-framework:v0.4.0 tanin47/embeddable-java-web-framework:v0.4.0`
4. Run: `docker push tanin47/embeddable-java-web-framework:v0.4.0`
5. Go to Render.com, sync the blueprint, and test that it works

Release a new version
----------------------

1. Create an empty release with a new tag. The tag must follow the format: `vX.Y.Z`.
2. Go to Actions and wait for the `create-release-and-docker` (which is triggered automatically) workflow to finish.
3. Test the docker with
`docker run -p 9090:9090 --entrypoint "" tanin47/embeddable-java-web-framework:v0.4.0 java -jar embeddable-java-web-framework-0.4.0.jar -port 9090`.
4. Go to Actions and trigger the workflow `publish-jar` on the tag `vX.Y.Z` in order to publish the JAR to Central
Sonatype.

Embed your website into a larger system
----------------------------------------
Expand Down
22 changes: 20 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.jreleaser.model.Active
import org.jreleaser.model.Signing.Mode

plugins {
`java-library`
application
`maven-publish`
jacoco
id("org.jreleaser") version "1.21.0"
id("com.gradleup.shadow") version "9.2.2"
}

group = "tanin.ejwf"
version = "0.4.0"
version = "1.0.0-rc1"

description = "Embeddable Java Web Framework (EJWF)"

Expand All @@ -29,6 +31,16 @@ java {
}
}

tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report

reports {
xml.required = true
csv.required = false
html.outputLocation = layout.buildDirectory.dir("jacocoHtml")
}
}

repositories {
mavenCentral()
}
Expand All @@ -47,8 +59,14 @@ tasks.named<Test>("test") {
maxHeapSize = "1G"

testLogging {
events("passed")
events("started", "passed", "skipped", "failed")
showStandardStreams = true
showStackTraces = true
showExceptions = true
showCauses = true
exceptionFormat = TestExceptionFormat.FULL
}

}

application {
Expand Down
File renamed without changes.
Loading
Loading