diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index bf7122640b..0000000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: Codespell - -on: - push: - branches: [master] - pull_request: - branches: [master] - -permissions: - contents: read - -jobs: - codespell: - name: Check for spelling errors - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Codespell - uses: codespell-project/actions-codespell@v2 - with: - only_warn: 1 - ignore_words_file: .codespellignore - exclude_file: src/main/resources/application-reserved-words.yml diff --git a/.github/workflows/typespec.yml b/.github/workflows/typespec.yml index 11bec5fdbe..8a68b2c11b 100644 --- a/.github/workflows/typespec.yml +++ b/.github/workflows/typespec.yml @@ -39,19 +39,3 @@ jobs: cd typespec tsp install tsp compile . - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # v1.7.0 - with: - aws-access-key-id: ${{secrets.TOWER_CI_AWS_ACCESS}} - aws-secret-access-key: ${{secrets.TOWER_CI_AWS_SECRET}} - aws-region: eu-west-1 - - - name : Login to Amazon ECR - id : login-ecr - uses : aws-actions/amazon-ecr-login@5a88a04c91d5c6f97aae0d9be790e64d9b1d47b7 # v1.7.1 - - - name: Release OpenAPI docs - if: "contains(github.event.head_commit.message, '[release]')" - run: | - bash typespec/tag-and-push-openapi.sh diff --git a/build.gradle b/build.gradle index ab30c44d2c..81c4b02e70 100644 --- a/build.gradle +++ b/build.gradle @@ -82,6 +82,8 @@ dependencies { // caching deps implementation 'io.micronaut.cache:micronaut-cache-core' implementation 'io.micronaut.cache:micronaut-cache-caffeine' + // swagger ui via webjars (served at /webjars/swagger-ui/*) + implementation 'org.webjars:swagger-ui:5.17.14' implementation 'io.micronaut.aws:micronaut-aws-parameter-store' implementation 'software.amazon.awssdk:ecr' implementation 'software.amazon.awssdk:ecrpublic' @@ -209,6 +211,72 @@ task buildInfo { doLast { buildInfo.dependsOn processResources compileGroovy.dependsOn buildInfo +// ---------------------------------------------------------------------------- +// OpenAPI / Swagger UI bundling +// +// Compiles the TypeSpec sources in `typespec/` to `openapi.yaml` and generates +// a Swagger UI page from `src/main/resources/swagger-ui-template.html`. +// Both artifacts are copied into `build/resources/main/public/openapi/` so they +// are bundled into the main Wave application container and served at /openapi/. +// +// This replaces the previous separate `wave/openapi` nginx container. +// ---------------------------------------------------------------------------- +def typespecDir = file('typespec') +def typespecOutput = file("${typespecDir}/tsp-output/@typespec/openapi3/openapi.yaml") +def openapiBuildDir = file("${buildDir}/generated/openapi") + +tasks.register('installTypeSpec', Exec) { + group = 'documentation' + description = 'Install TypeSpec compiler and dependencies via npm' + workingDir typespecDir + commandLine 'npm', 'install' + + inputs.file("${typespecDir}/package.json") + outputs.dir("${typespecDir}/node_modules") +} + +tasks.register('generateOpenApi', Exec) { + group = 'documentation' + description = 'Compile TypeSpec sources into openapi.yaml' + dependsOn 'installTypeSpec' + workingDir typespecDir + commandLine 'npx', 'tsp', 'compile', '.' + + inputs.files(fileTree(typespecDir) { + include 'main.tsp', 'routes.tsp', 'tspconfig.yaml', 'models/**/*.tsp' + }) + outputs.file(typespecOutput) +} + +tasks.register('generateSwaggerUI') { + group = 'documentation' + description = 'Stage openapi.yaml and a Swagger UI page under public/openapi' + dependsOn 'generateOpenApi' + + inputs.file(typespecOutput) + inputs.file('src/main/resources/swagger-ui-template.html') + outputs.dir(openapiBuildDir) + + doLast { + openapiBuildDir.mkdirs() + + // Copy spec and substitute the placeholder version (typespec emits `version: 0.0.0`) + def specText = typespecOutput.text.replace('version: 0.0.0', "version: ${version}") + new File(openapiBuildDir, 'openapi.yaml').text = specText + + // Render the Swagger UI page with the project version + def template = file('src/main/resources/swagger-ui-template.html').text + new File(openapiBuildDir, 'index.html').text = template.replace('${PROJECT_VERSION}', version.toString()) + } +} + +processResources { + dependsOn generateSwaggerUI + from(openapiBuildDir) { + into 'public/openapi' + } +} + jacoco { toolVersion '0.8.12' } diff --git a/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy b/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy index b086f08fbe..9b56fedb4c 100644 --- a/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy +++ b/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy @@ -60,11 +60,6 @@ class ServiceInfoController { : HttpResponse.badRequest() } - @Get("/openapi") - HttpResponse getOpenAPI() { - HttpResponse.redirect(URI.create("/openapi/")) - } - @Get(uri = "/favicon.ico", produces = MediaType.IMAGE_X_ICON) HttpResponse getFavicon() { final inputStream = getClass().getResourceAsStream("/io/seqera/wave/assets/wave.ico"); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 49bbd0cb96..cf9a7c891a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -32,6 +32,14 @@ micronaut: paths: 'classpath:io/seqera/wave/assets' mapping: '/assets/**' enabled: true + openapi: + paths: 'classpath:public/openapi' + mapping: '/openapi/**' + enabled: true + webjars: + paths: 'classpath:META-INF/resources/webjars' + mapping: '/webjars/**' + enabled: true # http client configuration # https://docs.micronaut.io/latest/guide/configurationreference.html#io.micronaut.http.client.DefaultHttpClientConfiguration http: diff --git a/src/main/resources/public/openapi/swagger-ui-init.js b/src/main/resources/public/openapi/swagger-ui-init.js new file mode 100644 index 0000000000..db9da68af3 --- /dev/null +++ b/src/main/resources/public/openapi/swagger-ui-init.js @@ -0,0 +1,15 @@ +window.onload = function () { + window.ui = SwaggerUIBundle({ + url: '/openapi/openapi.yaml', + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: 'StandaloneLayout' + }); +}; diff --git a/src/main/resources/public/openapi/swagger-ui-overrides.css b/src/main/resources/public/openapi/swagger-ui-overrides.css new file mode 100644 index 0000000000..91800aa621 --- /dev/null +++ b/src/main/resources/public/openapi/swagger-ui-overrides.css @@ -0,0 +1,27 @@ +html { + box-sizing: border-box; + overflow: -moz-scrollbars-vertical; + overflow-y: scroll; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +body { + margin: 0; + background: #fafafa; +} + +/* Hide Swagger branding */ +.swagger-ui .topbar { + display: none; +} + +.swagger-ui .info hgroup.main a { + display: none; +} + +.swagger-ui .info .title small { + display: none; +} diff --git a/src/main/resources/swagger-ui-template.html b/src/main/resources/swagger-ui-template.html new file mode 100644 index 0000000000..dbc0bb5bbd --- /dev/null +++ b/src/main/resources/swagger-ui-template.html @@ -0,0 +1,34 @@ + + + + +
+ +