diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 380bb6ce..54860d18 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,10 @@ name: Build Affected on: workflow_call: + inputs: + base: + required: true + type: string jobs: build: @@ -29,57 +33,12 @@ jobs: run: npm ci - name: Build Affected - run: npx nx affected:build --configuration=production --exclude=ark-cacher-node,conan-cacher --base=origin/development --parallel=2 + run: npx nx affected:build --configuration=production --base=${{ inputs.base }} --parallel=2 - # Iterate through the dist folder and for any folder in dist/apps that has the suffix "-nest", - # copy the root package.json to the /app/ folder. - # - # This is necessary because as of this version of Nx, it cannot reliably generate a package.json for NestJS applications. - # and the more common issue is missing required database drivers. - - name: Inject Package JSON + - name: Copy Files Needed For Image Build run: | - shopt -s nullglob - - # Check if dist/apps exists - if [ ! -d dist/apps ]; then - echo "dist/apps does not exist" - exit 0 - fi - - for d in dist/apps/*-nest; do - echo "Copying package.json to $d" - cp package.json "$d" - done - - echo "" - echo "Listing dist/apps" - ls dist/apps/ - - shopt -u nullglob - - # Static angular applications can have environment variables injected at container runtime using the - # tokenSubstitute.sh script. This script is copied to the dist/apps/-angular folder. - # - - name: Inject Token Subsitution Script - run: | - shopt -s nullglob - - # Check if dist/apps exists - if [ ! -d dist/apps ]; then - echo "dist/apps does not exist" - exit 0 - fi - - for d in dist/apps/*-angular; do - echo "Copying tokenSubstitute.sh to $d" - cp scripts/ci/tokenSubstitute.sh "$d" - done - - echo "" - echo "Listing dist/apps" - ls dist/apps/ - - shopt -u nullglob + mkdir -p dist + cp -r package.json package-lock.json dist/ - name: Upload Build Artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fdf2a637..a52dba1a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,6 +2,10 @@ name: Lint Affected on: workflow_call: + inputs: + base: + required: true + type: string jobs: lint: @@ -30,4 +34,4 @@ jobs: run: npm ci - name: Lint Affected - run: npx nx affected:lint --configuration=production --exclude=ark-cacher-node,conan-cacher --base=origin/development --parallel=2 + run: npx nx affected:lint --configuration=production --base=${{ inputs.base }} --parallel=2 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2eccd24..f64ee9da 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,10 +4,36 @@ on: push: jobs: + set-base: + name: Set Base Reference + runs-on: ubuntu-latest + outputs: + base: ${{ steps.set-base.outputs.base }} + steps: + - name: Set Base Reference + id: set-base + run: | + if [[ "${{ github.ref_name }}" == "development" || "${{ github.ref_name }}" == "master" ]]; then + echo "base=HEAD~1" >> $GITHUB_OUTPUT + else + echo "base=origin/development" >> $GITHUB_OUTPUT + fi + lint: name: Lint + needs: set-base uses: ./.github/workflows/lint.yml + with: + base: ${{ needs.set-base.outputs.base }} + build: name: Build - needs: lint + needs: [lint, set-base] uses: ./.github/workflows/build.yml + with: + base: ${{ needs.set-base.outputs.base }} + + publish: + name: Publish + needs: [lint, build] + uses: ./.github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b8748862..952b28cf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,8 +1,7 @@ name: Build and Publish Images on: - pull_request: - types: [labeled] + workflow_call: env: REGISTRY: ghcr.io @@ -10,30 +9,37 @@ env: jobs: list-publishable: - # Only run this job if pull request label is equal to "release" - # If this condition fails, build-and-publish will not run - if: ${{ github.event.label.name == 'release' }} runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} + matrix: ${{ steps.set-matrix.outputs.matrix || '[]' }} steps: - name: 'Download artifacts' - uses: dawidd6/action-download-artifact@v2 + uses: actions/download-artifact@v4 with: - workflow: main.yml - name: ${{ github.event.pull_request.head.sha }} - path: artifacts + name: ${{ github.sha }} + - name: 'Check for produced artifacts' + id: check-apps + run: | + if [ -d "./apps" ]; then + echo "✅ Apps directory found - proceeding with image build matrix generation" + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "⚠️ Apps directory not found - skipping image builds" + echo "exists=false" >> $GITHUB_OUTPUT + fi - name: 'Set matrix' id: set-matrix + if: steps.check-apps.outputs.exists == 'true' # Run script to generate matrix by getting a list of all directories that have a dockerfile as json array in the /artifact directory run: | - cd ./artifacts/apps + cd ./apps JASON=$(find . -name "Dockerfile" -exec dirname {} \; | sed 's/^\.\///' | jq -R -s -c 'split("\n")[:-1]') echo $JASON echo "matrix=$JASON" >> $GITHUB_OUTPUT build-and-push: needs: list-publishable runs-on: ubuntu-latest + if: ${{ needs.list-publishable.outputs.matrix != '[]' }} permissions: contents: read packages: write @@ -42,12 +48,9 @@ jobs: image: ${{ fromJson(needs.list-publishable.outputs.matrix) }} steps: - name: 'Download artifacts' - uses: dawidd6/action-download-artifact@v2 + uses: actions/download-artifact@v4 with: - workflow: main.yml - name: ${{ github.event.pull_request.head.sha }} - path: artifacts - + name: ${{ github.sha }} - name: Docker login uses: docker/login-action@v3 with: @@ -59,18 +62,27 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/Supreme-Gaming/${{ matrix.image }} + images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.image }} flavor: | latest=true tags: | - type=ref,event=branch - type=ref,event=pr + type=raw,value=stable,enable=${{ github.ref_name == 'development' }} + type=raw,value=stable,enable=${{ github.ref_name == 'master' }} + type=raw,value=${{ github.ref_name }},enable=${{ github.ref_name != 'development' && github.ref_name != 'master' }} type=sha + - name: Copy required image build files + run: | + cp package.json package-lock.json apps/${{ matrix.image }} + - name: Build and push Docker images uses: docker/build-push-action@v6 with: - context: ./artifacts/apps/${{ matrix.image }} + context: ./apps/${{ matrix.image }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} + GIT_TAG=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} + GIT_COMMIT=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} diff --git a/apps/supremegaming-angular/src/.nginx/nginx.conf b/apps/supremegaming-angular/src/.nginx/nginx.conf index 153cdb67..8d69432d 100644 --- a/apps/supremegaming-angular/src/.nginx/nginx.conf +++ b/apps/supremegaming-angular/src/.nginx/nginx.conf @@ -1,31 +1,48 @@ server { - listen 80; - listen [::]:80; - server_name localhost; + listen 80 default_server; + listen [::]:80 default_server; + server_name _; - #access_log /var/log/nginx/host.access.log main; + root /usr/share/nginx/html; + index index.html index.htm; - location / { - root /usr/share/nginx/html; - index index.html index.htm; - - # Route all requests to index.html for an SPA - try_files $uri $uri/ /index.html; + # 1) Long-cache immutable assets (hash-named) + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + access_log off; + expires off; + add_header Cache-Control "public, max-age=31536000, immutable" always; } - #error_page 404 /404.html; + # 2) Never cache the HTML shell and web app manifests + location = /index.html { + expires off; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + } + location = /manifest.webmanifest { + expires off; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + } + location = /ngsw.json { + expires off; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + } - # redirect server error pages to the static page /50x.html - # - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; + # 3) SPA routing: send unknown paths to index.html + location / { + try_files $uri $uri/ /index.html; + # No need to repeat headers here; the internal redirect to =/index.html + # ensures the HTML no-cache policy above is applied. } - # deny access to .htaccess files, if Apache's document root - # concurs with nginx's one - # - #location ~ /\.ht { - # deny all; - #} -} + # Errors + error_page 500 502 503 504 /50x.html; + location = /50x.html { } + + # Optional hardening: + # add_header Referrer-Policy "no-referrer-when-downgrade" always; + # add_header X-Content-Type-Options "nosniff" always; + # add_header X-Frame-Options "SAMEORIGIN" always; +} \ No newline at end of file diff --git a/apps/supremegaming-angular/src/Dockerfile b/apps/supremegaming-angular/src/Dockerfile index 4d0b055a..2a886174 100644 --- a/apps/supremegaming-angular/src/Dockerfile +++ b/apps/supremegaming-angular/src/Dockerfile @@ -1,10 +1,7 @@ -FROM nginx:1.23.2-alpine - -RUN apk add --no-cache bash +FROM ghcr.io/tamugeoinnovation/nginx-base:latest COPY . /usr/share/nginx/html COPY .nginx/nginx.conf /etc/nginx/conf.d/default.conf -COPY --chmod=0755 tokenSubstitute.sh /usr/share/nginx/html/tokenSubstitute.sh - -ENTRYPOINT ["/usr/share/nginx/html/tokenSubstitute.sh"] \ No newline at end of file +# Only run token substitution during build without starting nginx +RUN /usr/local/bin/tokenSubstitute.sh diff --git a/apps/supremegaming-angular/src/app/app.module.ts b/apps/supremegaming-angular/src/app/app.module.ts index 6f25ae21..35400387 100644 --- a/apps/supremegaming-angular/src/app/app.module.ts +++ b/apps/supremegaming-angular/src/app/app.module.ts @@ -4,7 +4,7 @@ import { HttpClientModule } from '@angular/common/http'; import * as WebFont from 'webfontloader'; -import { EnvironmentModule } from '@supremegaming/common/ngx'; +import { EnvironmentModule, metadata } from '@supremegaming/common/ngx'; import { UiSkeletonModule } from '@supremegaming/ui'; import { AppRoutingModule } from './app-routing.module'; @@ -27,7 +27,7 @@ WebFont.load({ AppRoutingModule, UiSkeletonModule, HttpClientModule, - EnvironmentModule.forRoot(environment), + EnvironmentModule.forRoot({ ...environment, metadata }), HttpClientModule, ], declarations: [AppComponent], diff --git a/libs/common/ngx/src/index.ts b/libs/common/ngx/src/index.ts index d5d218db..f7c4a4d4 100644 --- a/libs/common/ngx/src/index.ts +++ b/libs/common/ngx/src/index.ts @@ -4,3 +4,5 @@ export * from './lib/modules/pipes/pipes.module'; export * from './lib/modules/meta/meta.module'; export * from './lib/modules/meta/meta.service'; export * from './lib/modules/meta/components/metadata/metadata.component'; + +export * from './lib/constants/build-traceability.constants'; diff --git a/libs/common/ngx/src/lib/constants/build-traceability.constants.ts b/libs/common/ngx/src/lib/constants/build-traceability.constants.ts new file mode 100644 index 00000000..c8db6783 --- /dev/null +++ b/libs/common/ngx/src/lib/constants/build-traceability.constants.ts @@ -0,0 +1,17 @@ +export const metadata = { + buildDate: '___BUILD_DATE___', + gitCommit: '___GIT_COMMIT___', + gitTag: '___GIT_TAG___', + containerName: '___CONTAINER_NAME___', + nodeName: '___NODE_NAME___', + environment: '___ENVIRONMENT_MODE___', +}; + +export interface ReleaseMetadata { + buildDate: string; + gitCommit: string; + gitTag: string; + containerName: string; + nodeName: string; + environment: string; +} diff --git a/libs/common/ngx/src/lib/modules/environment/environment.service.ts b/libs/common/ngx/src/lib/modules/environment/environment.service.ts index 016f83fb..dcb1a6d5 100644 --- a/libs/common/ngx/src/lib/modules/environment/environment.service.ts +++ b/libs/common/ngx/src/lib/modules/environment/environment.service.ts @@ -7,7 +7,22 @@ export const ENVIRONMENT = 'environment'; providedIn: 'root', }) export class EnvironmentService { - constructor(@Inject(ENVIRONMENT) private env) {} + constructor(@Inject(ENVIRONMENT) private env) { + if (env) { + if (env.metadata) { + // eslint-disable-next-line no-restricted-syntax + console.info(`Environment module initialized.\n`); + // eslint-disable-next-line no-restricted-syntax + console.info(`\n +Build Date: ${env.metadata.buildDate} +Git Commit: ${env.metadata.gitCommit} +Git Tag: ${env.metadata.gitTag} +Container Name: ${env.metadata.containerName} +Node Name: ${env.metadata.nodeName}\n + `); + } + } + } /** * Returns an environment value lookup by token. diff --git a/libs/common/ngx/src/lib/modules/meta/components/metadata/metadata.component.ts b/libs/common/ngx/src/lib/modules/meta/components/metadata/metadata.component.ts index f803c9e7..1f30f93d 100644 --- a/libs/common/ngx/src/lib/modules/meta/components/metadata/metadata.component.ts +++ b/libs/common/ngx/src/lib/modules/meta/components/metadata/metadata.component.ts @@ -3,13 +3,19 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { filter, map } from 'rxjs/operators'; import { MetaService } from '../../meta.service'; +import { EnvironmentService } from '../../../environment/environment.service'; @Component({ selector: 'supremegaming-metadata', template: '', }) export class MetadataComponent implements OnInit { - constructor(private readonly router: Router, private readonly route: ActivatedRoute, private readonly meta: MetaService) {} + constructor( + private readonly router: Router, + private readonly route: ActivatedRoute, + private readonly meta: MetaService, + private readonly env: EnvironmentService + ) {} public ngOnInit() { this.router.events diff --git a/libs/ui/src/lib/modules/skeleton/components/header/header.component.html b/libs/ui/src/lib/modules/skeleton/components/header/header.component.html index d0a82a92..4028761e 100644 --- a/libs/ui/src/lib/modules/skeleton/components/header/header.component.html +++ b/libs/ui/src/lib/modules/skeleton/components/header/header.component.html @@ -123,7 +123,7 @@

S -
  • +