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
15 changes: 15 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Description

Please include a summary of what the PR does adding Please relevant motivation and context.

## Trello Ticket ID

Please add a link to the Trello ticket for the task if any.

## Checklist

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
- [ ] Any dependent changes have been merged and published in downstream modules
22 changes: 22 additions & 0 deletions .github/workflows/bin/create_envs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

set -e

# env variables
SANITY_STUDIO_PROJECT_ID="${SANITY_STUDIO_PROJECT_ID}"
SANITY_STUDIO_DATASET="${SANITY_STUDIO_DATASET}"

function create_env_file
{
echo SANITY_STUDIO_PROJECT_ID=$SANITY_STUDIO_PROJECT_ID >> .env
echo SANITY_STUDIO_DATASET=$SANITY_STUDIO_DATASET >> .env
echo NODE_ENV=production >> .env
}


function run
{
create_env_file
}

run
187 changes: 187 additions & 0 deletions .github/workflows/prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
name: Production Deployment

on:
release:
types:
- released
- prereleased
workflow_dispatch:

jobs:
build_and_deploy_staging:
outputs:
image: ${{ env.IMAGE }}
tag: ${{ env.TAG }}
release_version: ${{ env.version }}

runs-on: ubuntu-latest
env:
image: cranecloud/cranecloud-cms
namespace: cranecloud-prod

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: master

- name: Get version
id: version
run: |
if [[ $GITHUB_EVENT_NAME == "release" ]]; then
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "version=dev-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
fi

- name: Update CHANGELOG.md
if: github.event_name == 'release'
run: |
# Get the release version and date
VERSION=${GITHUB_REF#refs/tags/}
DATE=$(date +'%Y-%m-%d')

# Get the release notes from the GitHub release
RELEASE_NOTES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/releases/tags/$VERSION" | \
jq -r '.body')

# Create the new version section
NEW_SECTION="## [$VERSION] - $DATE\n\n"

# Parse the release notes and categorize them
if echo "$RELEASE_NOTES" | grep -q "### Added"; then
NEW_SECTION+="### Added\n"
NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Added/,/^$/p' | sed '1d')
NEW_SECTION+="\n"
fi

if echo "$RELEASE_NOTES" | grep -q "### Changed"; then
NEW_SECTION+="### Changed\n"
NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Changed/,/^$/p' | sed '1d')
NEW_SECTION+="\n"
fi

if echo "$RELEASE_NOTES" | grep -q "### Fixed"; then
NEW_SECTION+="### Fixed\n"
NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Fixed/,/^$/p' | sed '1d')
NEW_SECTION+="\n"
fi

if echo "$RELEASE_NOTES" | grep -q "### Security"; then
NEW_SECTION+="### Security\n"
NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Security/,/^$/p' | sed '1d')
NEW_SECTION+="\n"
fi

# Update the CHANGELOG.md
awk -v new="$NEW_SECTION" '
/^## \[Unreleased\]/ {
print;
print "";
print new;
next;
}
{ print }
' CHANGELOG.md > CHANGELOG.md.new

mv CHANGELOG.md.new CHANGELOG.md

# Commit and push the changes
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG.md for $VERSION"
git push

- name: Install (Buildx)
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login (GCP)
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.CREDENTIALS_JSON }}

- name: Install (Gcloud)
uses: google-github-actions/setup-gcloud@v2
with:
project_id: crane-cloud-274413
install_components: 'gke-gcloud-auth-plugin'

- name: Get Kubernetes credentials
run: |
gcloud container clusters get-credentials staging-cluster --zone us-central1-a

- id: meta
name: Tag
uses: docker/metadata-action@v3
with:
flavor: |
latest=auto
prefix=
images: ${{ env.image }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}

- name: Add Env vars
env:
SANITY_STUDIO_PROJECT_ID: ${{ secrets.SANITY_STUDIO_PROJECT_ID }}
SANITY_STUDIO_DATASET: ${{ secrets.SANITY_STUDIO_DATASET }}
run: |
chmod +x ./.github/workflows/bin/create_envs.sh
./.github/workflows/bin/create_envs.sh

- name: Build
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
labels: ${{ steps.meta.outputs.labels }}
push: true
tags: ${{ steps.meta.outputs.tags }}

- id: export
name: Export
uses: actions/github-script@v7
with:
script: |
const metadata = JSON.parse(`${{ steps.meta.outputs.json }}`)
const fullUrl = metadata.tags.find((t) => t.includes(':sha-'))
if (fullUrl == null) {
core.error('Unable to find sha tag of image')
} else {
const tag = fullUrl.split(':')[1]
core.exportVariable('IMAGE', fullUrl)
core.exportVariable('TAG', tag)
}

- name: Update deployment image
run: |
kubectl set image deployment/cranecloud-cms cranecloud-cms=${{ env.image }}:${{ env.TAG }} -n $namespace

- name: Verify deployment
run: |
echo "Waiting for deployment to roll out..."
kubectl rollout status deployment/cranecloud-cms -n $namespace --timeout=300s

echo "Verifying deployment health..."
kubectl get pods -n $namespace -l app=cranecloud-cms -o wide

# Add basic health check
POD_NAME=$(kubectl get pods -n $namespace -l app=cranecloud-cms -o jsonpath="{.items[0].metadata.name}")
kubectl exec -n $namespace $POD_NAME -- curl -f http://localhost:80/health || exit 1

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test

on:
push:
pull_request:

jobs:
test_and_report:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 23

- name: Setup Dependencies
run: yarn

- name: Run Test
run: yarn eslint
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM node:23-alpine as build_step

WORKDIR /app

COPY package.json yarn.lock* ./
RUN yarn install

COPY . /app

# Set production environment variables
ENV NODE_ENV=production

RUN yarn build


FROM nginx:1.25-alpine as production

COPY --from=build_step /app/dist /usr/share/nginx/html

COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ A modern content management system built with Sanity for managing CraneCloud com
1. **Clone the repository**

```bash
git clone <repository-url>
git clone https://github.com/crane-cloud/cranecloud-cms.git
cd cranecloud-cms
```

Expand All @@ -46,8 +46,6 @@ A modern content management system built with Sanity for managing CraneCloud com
- `yarn dev` - Start development server
- `yarn start` - Start production server
- `yarn build` - Build for production
- `yarn deploy` - Deploy to Sanity
- `yarn deploy-graphql` - Deploy GraphQL API

## 🌐 API Access

Expand Down
17 changes: 17 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
server {
listen 80;
server_name localhost;
include mime.types;
default_type application/octet-stream;

# Add MIME type for JavaScript modules
types {
application/javascript js mjs;
}

location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
55 changes: 55 additions & 0 deletions nginx2.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
server {
listen 80;
server_name localhost;

root /usr/share/nginx/html;
index index.html;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Handle JavaScript modules (.mjs files)
location ~* \.mjs$ {
add_header Content-Type "application/javascript; charset=utf-8";
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}

# Handle regular JavaScript files
location ~* \.js$ {
add_header Content-Type "application/javascript; charset=utf-8";
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}

# Handle CSS files
location ~* \.css$ {
add_header Content-Type "text/css; charset=utf-8";
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}

# Handle static assets
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}

# Handle Sanity static assets - serve from root
location /static/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable";
}

# Handle all other routes - serve index.html for SPA routing
location / {
try_files $uri $uri/ /index.html;

# Add security headers for HTML
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
}
}
Loading