diff --git a/JenkinsFile b/JenkinsFile new file mode 100644 index 0000000..a19c24e --- /dev/null +++ b/JenkinsFile @@ -0,0 +1,116 @@ +pipeline { + agent any + + environment { + DEPLOY_BASE = "/srv/jasonpoage.com" + GIT_REPO = 'git@git.jasonpoage.com:jason/expressjs-blog.git' + } + + options { + timestamps() + ansiColor('xterm') + } + + parameters { + string(name: 'DEPLOY_BRANCH', defaultValue: 'main', description: 'Branch to deploy') + } + + stages { + + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Prepare Environment') { + steps { + script { + env.ENV_FILE = "${DEPLOY_BASE}/${params.DEPLOY_BRANCH}.env" + env.DEPLOY_PATH = "${DEPLOY_BASE}/expressjs-blog-${params.DEPLOY_BRANCH}" + env.LOG_FILE = "${DEPLOY_BASE}/logs/jenkins-${params.DEPLOY_BRANCH}-${env.BUILD_ID}.log" + } + sh """ + cp "$ENV_FILE" .env + """ + } + } + + stage('Install Dependencies') { + steps { + sh ''' + if git diff --name-only HEAD~1 | grep -qE "(package.json|yarn.lock)"; then + echo "Dependency files changed. Running yarn..." + yarn + else + echo "Skipping yarn install." + fi + ''' + } + } + + stage('Initialize Submodules') { + steps { + sh ''' + git submodule update --init --recursive --force + ''' + } + } + + stage('Build CSS') { + steps { + sh ''' + yarn --production=false combine:css + ''' + } + } + + stage('Run Tests') { + steps { + sh ''' + npm run test:postreceive + ''' + } + } + + stage('Deploy') { + steps { + script { + if (params.DEPLOY_BRANCH == 'testing') { + sh """ + rm -rf "$DEPLOY_PATH" || true + mv "$WORKSPACE" "$DEPLOY_PATH" + ln -f "$ENV_FILE" "$DEPLOY_PATH/.env" + """ + } else { + sh """ + mkdir -p "$DEPLOY_PATH" + git fetch origin + git reset --hard origin/${params.DEPLOY_BRANCH} + git submodule update --init --recursive --force + ln -f "$ENV_FILE" "$DEPLOY_PATH/.env" + yarn combine:css + """ + } + } + } + } + + stage('Restart Service') { + steps { + sh """ + systemctl --user restart express-blog@${params.DEPLOY_BRANCH}.service + """ + } + } + } + + post { + failure { + sh 'echo "Deployment failed for branch ${params.DEPLOY_BRANCH}"' + } + success { + sh 'echo "Deployment succeeded for branch ${params.DEPLOY_BRANCH}"' + } + } +} diff --git a/content b/content index 0428693..7163548 160000 --- a/content +++ b/content @@ -1 +1 @@ -Subproject commit 04286935c52a3ac07361d7582816471d7d97c52a +Subproject commit 7163548b4acfc83a555242892896c637781ac28d diff --git a/public/css/docs.css b/public/css/docs.css new file mode 100644 index 0000000..660ca66 --- /dev/null +++ b/public/css/docs.css @@ -0,0 +1,133 @@ +body { + font-family: monospace, monospace; + background: #f9f9f9; + color: #222; + margin: 1rem; +} + +h1, +h2, +h3 { + margin-top: 1.5rem; +} + +pre { + background: #eee; + padding: 1rem; + overflow-x: auto; +} + +table { + border-collapse: collapse; + width: 100%; + margin-top: 1rem; +} + +th, +td { + border: 1px solid #ccc; + padding: 0.5rem; + text-align: left; + vertical-align: top; +} + +th { + background: #ddd; +} + +.section { + margin-bottom: 2rem; +} + +.key { + font-weight: bold; +} + +.list { + margin: 0.25rem 0 1rem 1.5rem; +} + +/* General sidebar container */ +.sidebar { + width: 250px; + background-color: #f8f9fa; + border-right: 1px solid #e0e0e0; + padding: 1em; + font-size: 0.95rem; + font-family: Arial, sans-serif; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); + position: relative; +} + +/* Reset list styles */ +.sidebar nav ul { + list-style: none; + padding-left: 0; + margin: 0; +} + +/* Top-level path entry */ +.docs-path-item { + margin-bottom: 1rem; +} + +/* Path summary label (e.g. path name) */ +.docs-path-label { + font-weight: 700; + font-size: 1.1rem; + color: #2c3e50; + cursor: pointer; + padding: 0.2rem 0; + display: block; +} + +/* Module links under a path */ +.docs-module-list { + margin-top: 0.3rem; + padding-left: 1rem; +} + +.docs-module-item a { + color: #2c3e50; + text-decoration: none; + display: block; + padding: 0.2rem 0; + transition: color 0.2s ease-in-out; + font-weight: 400; +} + +.docs-module-item a:hover { + color: #1a73e8; + text-decoration: underline; +} + +.docs-module-item.active a { + font-weight: 600; + color: #1a73e8; + text-decoration: underline; +} + +/* Expandable
arrow alignment */ +.docs-path-item details > summary { + list-style: none; +} + +.docs-path-item details[open] > summary::marker, +.docs-path-item summary::marker { + display: none; +} +.doc-object { + padding-left: 1rem; + margin-bottom: 1rem; + border-left: 2px solid #ccc; +} + +.doc-entry { + margin: 0.5em 0; +} + +.doc-entry strong { + display: inline-block; + min-width: 160px; + color: #2c3e50; +} diff --git a/public/js/docs.js b/public/js/docs.js new file mode 100644 index 0000000..875f842 --- /dev/null +++ b/public/js/docs.js @@ -0,0 +1,6 @@ +Handlebars.registerHelper( + "isObject", + (v) => v && typeof v === "object" && !Array.isArray(v) +); +Handlebars.registerHelper("isArray", Array.isArray); +Handlebars.registerHelper("json", (ctx) => JSON.stringify(ctx, null, 2));