feat: add workflow for promoting master to production with testing and deployment steps
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m23s

This commit is contained in:
2026-04-26 14:33:51 -04:00
parent c5306a15d1
commit 2b2f4f46c0
12 changed files with 47 additions and 26 deletions

View File

@@ -155,7 +155,7 @@ jobs:
working-directory: frontend working-directory: frontend
env: env:
CI: "true" CI: "true"
PLAYWRIGHT_BASE_URL: "http://localhost:5173" PLAYWRIGHT_BASE_URL: "https://localhost:5173"
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -2,20 +2,20 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "oh4sQB_Q1Se4xydZHTIolU6FhvlKPIlDPsxfd_qe4wQ", "value": "v-K0IrmQf50MVvYCdmQfnUor8Q8e9KURnPnyVPYRN8Q",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1784867824.852037, "expires": 1785003958.597918,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI4NGY1Y2I3NS05NWU1LTRjMTItOTExOS02MzdkYjJlMWY1MGYiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjR9.7-d4EvJEYtYSi_5ZFiU7tuxgc4ilVMBxhwzgAQZNTvY", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI0NDg5ZjZhNS1mZWEyLTQ0ZGEtYTczNy00MDQwNTQyMmU0YTEiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcyMjg4NTh9.V7gsUwZ-mjcRqCIwI2ppFVrGD16R18JJDQ5wsv_ul1k",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": 1777092724.851439, "expires": 1777228858.597862,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Lax" "sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1777091824594}" "value": "{\"type\":\"logout\",\"at\":1777227958453}"
}, },
{ {
"name": "parentAuth", "name": "parentAuth",
"value": "{\"expiresAt\":1777264625262}" "value": "{\"expiresAt\":1777400758775}"
} }
] ]
} }

View File

@@ -2,20 +2,20 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "lIhurtvmo0oGcimN_8v59h9y3CrglEAloQO1QFw8RsQ", "value": "s8cSiajWyuCcjv9zXjqsKrmEK8cRPp-VOWV74Cz_ueQ",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1784867824.995155, "expires": 1785003958.599099,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiOWYzYTljNWMtM2IwMy00YTZlLTliNmUtYjE0MTQzZWNhMzNkIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc3MDkyNzI0fQ.UB5MCIZ2PHbn3UyohNzmFbOCb3TR-KHxJPWB9qkWwCQ", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiYmE3MDNmMWItMzg0Zi00MzBjLWJiNjYtN2Q0YWRhMWIwYjRiIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc3MjI4ODU4fQ.FnEEIVv2wchkrJhDiRuNiDBF-lIPkyZUU2UwLmLZVHM",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": 1777092724.99406, "expires": 1777228858.599061,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Lax" "sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1777091824687}" "value": "{\"type\":\"logout\",\"at\":1777227958453}"
}, },
{ {
"name": "parentAuth", "name": "parentAuth",
"value": "{\"expiresAt\":1777264625511}" "value": "{\"expiresAt\":1777400758775}"
} }
] ]
} }

View File

@@ -2,20 +2,20 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "uMExObHukEdFnlszorcDq2XlbRW0AeIV71x_vSovEF4", "value": "caYfAayTk40YWkRvN82a9_mGQ_-wXy6JdBBaD_UhLcQ",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1784867821.107956, "expires": 1785003956.884209,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiI2ZWE5YWZmZC1jMGExLTRkMzUtOGJmYS1mZDBkOWY5NzFmOWQiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjF9.rb64zKqS6uXqkgX_4kke37Rc6om68elt4Qa24Fy9NPo", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiI3YjcxMGU0ZC02ZTExLTRjMjctODllNS1jZTcxNGZhOTgwZTIiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcyMjg4NTZ9.wJCrQ9Y5nbVpqd6yCeCHcpcpGpg4YqavqlsHbU8kDPE",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": 1777092721.107403, "expires": 1777228856.884169,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Lax" "sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1777091820880}" "value": "{\"type\":\"logout\",\"at\":1777227956772}"
}, },
{ {
"name": "parentAuth", "name": "parentAuth",
"value": "{\"expiresAt\":1777264621275}" "value": "{\"expiresAt\":1777400757015}"
} }
] ]
} }

View File

@@ -5,6 +5,7 @@ import { test, expect, type APIRequestContext } from '@playwright/test'
const CHILD_NAME = 'DigestApproveChoreChild' const CHILD_NAME = 'DigestApproveChoreChild'
const CHORE_NAME = 'DigestApproveChoreChore' const CHORE_NAME = 'DigestApproveChoreChore'
const CHORE_POINTS = 8 const CHORE_POINTS = 8
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
async function createChild(request: APIRequestContext, name: string): Promise<string> { async function createChild(request: APIRequestContext, name: string): Promise<string> {
const pre = await request.get('/api/child/list') const pre = await request.get('/api/child/list')
@@ -81,7 +82,7 @@ test.describe('Digest Action Token — Approve Chore', () => {
}) => { }) => {
const unauthCtx = await playwright.request.newContext({ const unauthCtx = await playwright.request.newContext({
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
baseURL: 'https://localhost:5173', baseURL: DIGEST_ACTION_BASE_URL,
}) })
const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, { const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, {
maxRedirects: 0, maxRedirects: 0,

View File

@@ -6,6 +6,7 @@ const CHILD_NAME = 'DigestApproveRewardChild'
const REWARD_NAME = 'DigestApproveRewardReward' const REWARD_NAME = 'DigestApproveRewardReward'
const REWARD_COST = 15 const REWARD_COST = 15
const INITIAL_POINTS = 50 const INITIAL_POINTS = 50
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
async function createChild( async function createChild(
request: APIRequestContext, request: APIRequestContext,
@@ -78,7 +79,7 @@ test.describe('Digest Action Token — Approve Reward', () => {
}) => { }) => {
const unauthCtx = await playwright.request.newContext({ const unauthCtx = await playwright.request.newContext({
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
baseURL: 'https://localhost:5173', baseURL: DIGEST_ACTION_BASE_URL,
}) })
const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, { const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, {
maxRedirects: 0, maxRedirects: 0,

View File

@@ -5,6 +5,7 @@ import { test, expect, type APIRequestContext } from '@playwright/test'
const CHILD_NAME = 'DigestDenyChoreChild' const CHILD_NAME = 'DigestDenyChoreChild'
const CHORE_NAME = 'DigestDenyChoreChore' const CHORE_NAME = 'DigestDenyChoreChore'
const CHORE_POINTS = 8 const CHORE_POINTS = 8
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
async function createChild(request: APIRequestContext, name: string): Promise<string> { async function createChild(request: APIRequestContext, name: string): Promise<string> {
const pre = await request.get('/api/child/list') const pre = await request.get('/api/child/list')
@@ -71,7 +72,7 @@ test.describe('Digest Action Token — Deny Chore', () => {
}) => { }) => {
const unauthCtx = await playwright.request.newContext({ const unauthCtx = await playwright.request.newContext({
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
baseURL: 'https://localhost:5173', baseURL: DIGEST_ACTION_BASE_URL,
}) })
const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, { const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, {
maxRedirects: 0, maxRedirects: 0,

View File

@@ -6,6 +6,7 @@ const CHILD_NAME = 'DigestDenyRewardChild'
const REWARD_NAME = 'DigestDenyRewardReward' const REWARD_NAME = 'DigestDenyRewardReward'
const REWARD_COST = 15 const REWARD_COST = 15
const INITIAL_POINTS = 50 const INITIAL_POINTS = 50
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
async function createChild( async function createChild(
request: APIRequestContext, request: APIRequestContext,
@@ -78,7 +79,7 @@ test.describe('Digest Action Token — Deny Reward', () => {
}) => { }) => {
const unauthCtx = await playwright.request.newContext({ const unauthCtx = await playwright.request.newContext({
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
baseURL: 'https://localhost:5173', baseURL: DIGEST_ACTION_BASE_URL,
}) })
const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, { const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, {
maxRedirects: 0, maxRedirects: 0,

View File

@@ -5,6 +5,7 @@ import { test, expect, type APIRequestContext } from '@playwright/test'
const CHILD_NAME = 'DigestErrorChild' const CHILD_NAME = 'DigestErrorChild'
const CHORE_NAME = 'DigestErrorChore' const CHORE_NAME = 'DigestErrorChore'
const CHORE_POINTS = 5 const CHORE_POINTS = 5
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
async function createChild(request: APIRequestContext, name: string): Promise<string> { async function createChild(request: APIRequestContext, name: string): Promise<string> {
const pre = await request.get('/api/child/list') const pre = await request.get('/api/child/list')
@@ -33,7 +34,7 @@ async function createTask(
async function getUnauthContext(playwright: any) { async function getUnauthContext(playwright: any) {
return playwright.request.newContext({ return playwright.request.newContext({
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
baseURL: 'https://localhost:5173', baseURL: DIGEST_ACTION_BASE_URL,
}) })
} }

View File

@@ -16,6 +16,7 @@
"@tsconfig/node22": "^22.0.2", "@tsconfig/node22": "^22.0.2",
"@types/jsdom": "^27.0.0", "@types/jsdom": "^27.0.0",
"@types/node": "^22.18.11", "@types/node": "^22.18.11",
"@vitejs/plugin-basic-ssl": "^2.3.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vitest/eslint-plugin": "^1.3.23", "@vitest/eslint-plugin": "^1.3.23",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
@@ -2171,6 +2172,19 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/@vitejs/plugin-basic-ssl": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz",
"integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"peerDependencies": {
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",

View File

@@ -26,6 +26,7 @@
"@tsconfig/node22": "^22.0.2", "@tsconfig/node22": "^22.0.2",
"@types/jsdom": "^27.0.0", "@types/jsdom": "^27.0.0",
"@types/node": "^22.18.11", "@types/node": "^22.18.11",
"@vitejs/plugin-basic-ssl": "^2.3.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vitest/eslint-plugin": "^1.3.23", "@vitest/eslint-plugin": "^1.3.23",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",

View File

@@ -1,6 +1,7 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import basicSsl from '@vitejs/plugin-basic-ssl'
import fs from 'fs' import fs from 'fs'
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
@@ -13,10 +14,10 @@ export default defineConfig(({ mode }) => {
: undefined : undefined
return { return {
plugins: [vue()], plugins: [vue(), basicSsl()],
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
https: httpsConfig, https: httpsConfig ?? true,
proxy: { proxy: {
'/api': { '/api': {
target: `http://${backendHost}:5000`, target: `http://${backendHost}:5000`,