Skip to content

Commit 6c6d696

Browse files
committed
chore: align monorepo automation for blog split
Add professional root scripts and expand CI/E2E workflows to run portfolio, blog, and api checks consistently. Harden CSP hash updates to fail on fatal config issues and preserve original line endings to avoid noisy diffs.
1 parent 3948402 commit 6c6d696

File tree

8 files changed

+112
-71
lines changed

8 files changed

+112
-71
lines changed

.github/actions/lint/action.yml

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -67,37 +67,11 @@ runs:
6767
id: astro
6868
if: inputs.check-astro == 'true'
6969
shell: bash
70-
working-directory: apps/portfolio
7170
run: |
72-
echo "🔍 Running Astro type checks..."
73-
# Ensure node_modules are installed so `astro` and other local binaries are available.
74-
# Make sure pnpm is available. Prefer Corepack if present.
75-
if command -v corepack >/dev/null 2>&1; then
76-
echo "⚙️ Corepack detected, preparing pnpm from packageManager..."
77-
# Use the packageManager version declared in package.json if possible
78-
if [ -f package.json ]; then
79-
PM=$(node -e "console.log(require('./package.json').packageManager||'pnpm@latest')") || PM=pnpm@latest
80-
echo "🔧 Preparing $PM"
81-
corepack prepare "$PM" --activate || true
82-
else
83-
corepack prepare pnpm@latest --activate || true
84-
fi
85-
elif command -v pnpm >/dev/null 2>&1; then
86-
echo "⚙️ pnpm already available"
87-
else
88-
echo "⚠️ pnpm and corepack not found, installing pnpm via npm..."
89-
npm install -g pnpm@latest || true
90-
fi
91-
92-
if [ -f pnpm-lock.yaml ] || [ -f package-lock.json ] || [ -f yarn.lock ]; then
93-
echo "📦 Installing dependencies (pnpm install)..."
94-
pnpm install --frozen-lockfile --ignore-scripts || pnpm install
95-
else
96-
echo "📦 No lockfile found, running pnpm install..."
97-
pnpm install
98-
fi
99-
100-
pnpm check:astro
71+
echo "🔍 Running Astro type checks for portfolio and blog..."
72+
pnpm install --frozen-lockfile --ignore-scripts || pnpm install
73+
pnpm --filter=portfolio check:astro
74+
pnpm --filter=blog check:astro
10175
STATUS=$?
10276
10377
if [ $STATUS -eq 0 ]; then
@@ -111,11 +85,11 @@ runs:
11185
11286
- name: Check accessibility
11387
shell: bash
114-
working-directory: apps/portfolio
11588
continue-on-error: true
11689
run: |
117-
echo "♿ Running accessibility checks..."
118-
pnpm check:a11y || echo "⚠️ Accessibility warnings found"
90+
echo "♿ Running accessibility checks for portfolio and blog..."
91+
pnpm --filter=portfolio check:a11y || echo "⚠️ Portfolio accessibility warnings found"
92+
pnpm --filter=blog check:a11y || echo "⚠️ Blog accessibility warnings found"
11993
12094
- name: Generate lint summary
12195
shell: bash

.github/actions/test-playwright/action.yml

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ description: 'Run Playwright E2E tests with intelligent browser caching'
33
author: 'yacosta738'
44

55
inputs:
6+
app:
7+
description: 'App to test (portfolio/blog)'
8+
required: false
9+
default: 'portfolio'
610
browser:
711
description: 'Browser to test (chromium/firefox/webkit/all)'
812
required: false
@@ -22,22 +26,22 @@ outputs:
2226
value: ${{ steps.run-tests.outputs.status }}
2327
report-path:
2428
description: 'Path to test report'
25-
value: 'apps/portfolio/playwright-report'
29+
value: apps/${{ inputs.app }}/playwright-report
2630

2731
runs:
2832
using: "composite"
2933
steps:
3034
- name: Get Playwright version
3135
id: playwright-version
3236
shell: bash
33-
working-directory: apps/portfolio
37+
working-directory: apps/${{ inputs.app }}
3438
run: |
3539
VERSION=$(jq -r '.devDependencies["@playwright/test"] // .dependencies["@playwright/test"] // "latest"' package.json)
3640
echo "version=$VERSION" >> $GITHUB_OUTPUT
3741
echo "🎭 Playwright version: $VERSION"
3842
- name: Install dependencies
3943
shell: bash
40-
working-directory: apps/portfolio
44+
working-directory: apps/${{ inputs.app }}
4145
run: pnpm install --frozen-lockfile
4246
- name: Cache Playwright browsers
4347
id: playwright-cache
@@ -52,7 +56,7 @@ runs:
5256
- name: Install Playwright browsers
5357
if: steps.playwright-cache.outputs.cache-hit != 'true'
5458
shell: bash
55-
working-directory: apps/portfolio
59+
working-directory: apps/${{ inputs.app }}
5660
run: |
5761
echo "📥 Installing Playwright browsers..."
5862
@@ -65,7 +69,7 @@ runs:
6569
- name: Ensure Playwright browsers (cached)
6670
if: steps.playwright-cache.outputs.cache-hit == 'true'
6771
shell: bash
68-
working-directory: apps/portfolio
72+
working-directory: apps/${{ inputs.app }}
6973
run: |
7074
echo "♻️ Using cached browsers, validating browser binaries and dependencies..."
7175
@@ -80,7 +84,7 @@ runs:
8084
- name: Run Playwright tests
8185
id: run-tests
8286
shell: bash
83-
working-directory: apps/portfolio
87+
working-directory: apps/${{ inputs.app }}
8488
env:
8589
CI: true
8690
PW_USE_PREVIEW: '1'
@@ -113,17 +117,17 @@ runs:
113117
if: always() && inputs.upload-results == 'true'
114118
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #v4
115119
with:
116-
name: playwright-report-${{ inputs.browser }}-${{ github.run_number }}
117-
path: apps/portfolio/playwright-report/
120+
name: playwright-report-${{ inputs.app }}-${{ inputs.browser }}-${{ github.run_number }}
121+
path: apps/${{ inputs.app }}/playwright-report/
118122
retention-days: 30
119123
if-no-files-found: warn
120124

121125
- name: Upload test traces
122126
if: failure() && inputs.upload-results == 'true'
123127
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #v4
124128
with:
125-
name: playwright-traces-${{ inputs.browser }}-${{ github.run_number }}
126-
path: apps/portfolio/test-results/
129+
name: playwright-traces-${{ inputs.app }}-${{ inputs.browser }}-${{ github.run_number }}
130+
path: apps/${{ inputs.app }}/test-results/
127131
retention-days: 30
128132
if-no-files-found: warn
129133

@@ -133,11 +137,12 @@ runs:
133137
run: |
134138
echo "## 🎭 E2E Tests Summary" >> $GITHUB_STEP_SUMMARY
135139
echo "" >> $GITHUB_STEP_SUMMARY
140+
echo "- **App**: ${{ inputs.app }}" >> $GITHUB_STEP_SUMMARY
136141
echo "- **Browser**: ${{ inputs.browser }}" >> $GITHUB_STEP_SUMMARY
137142
echo "- **Status**: ${{ steps.run-tests.outputs.status == 'success' && '✅ Passed' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY
138143
echo "- **Mode**: ${{ inputs.headed == 'true' && 'Headed' || 'Headless' }}" >> $GITHUB_STEP_SUMMARY
139144
140145
# Try to extract test statistics
141-
if [ -f "apps/portfolio/playwright-report/index.html" ]; then
146+
if [ -f "apps/${{ inputs.app }}/playwright-report/index.html" ]; then
142147
echo "- **Report**: Available in artifacts" >> $GITHUB_STEP_SUMMARY
143148
fi

.github/actions/test-unit/action.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ inputs:
88
required: false
99
default: 'true'
1010
project:
11-
description: 'Specific project to test (portfolio/api/all)'
11+
description: 'Specific project to test (portfolio/blog/api/all)'
1212
required: false
1313
default: 'all'
1414
upload-coverage:
@@ -39,13 +39,12 @@ runs:
3939
- name: Install dependencies
4040
shell: bash
4141
run: |
42+
pnpm install --frozen-lockfile
43+
4244
if [ "${{ inputs.project }}" == "all" ]; then
43-
pnpm install --frozen-lockfile
44-
cd apps/portfolio && pnpm install --frozen-lockfile
45-
cd ../api && pnpm install --frozen-lockfile
45+
echo "Installing workspace dependencies for all projects..."
4646
else
47-
cd apps/${{ inputs.project }}
48-
pnpm install --frozen-lockfile
47+
echo "Installing workspace dependencies for ${{ inputs.project }}..."
4948
fi
5049
- name: Run unit tests
5150
id: run-tests

.github/workflows/ci.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ permissions:
2828

2929
env:
3030
NODE_VERSION: '22'
31-
PNPM_VERSION: '10.18.2'
31+
PNPM_VERSION: '10.30.1'
3232

3333
jobs:
3434
# Job 1: Setup & Lint (fastest checks first)
@@ -67,7 +67,7 @@ jobs:
6767
strategy:
6868
fail-fast: false
6969
matrix:
70-
project: [portfolio, api]
70+
project: [portfolio, blog, api]
7171
steps:
7272
- name: Checkout code
7373
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
@@ -93,7 +93,7 @@ jobs:
9393
strategy:
9494
fail-fast: false
9595
matrix:
96-
project: [portfolio, api]
96+
project: [portfolio, blog, api]
9797
steps:
9898
- name: Checkout code
9999
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
@@ -116,6 +116,10 @@ jobs:
116116
with:
117117
dry-run: 'true'
118118

119+
- name: Build Blog
120+
if: matrix.project == 'blog'
121+
run: pnpm install --frozen-lockfile && pnpm --filter=blog build
122+
119123
# Job 4: E2E Tests (depends on build)
120124
e2e-tests:
121125
name: 🎭 E2E Tests
@@ -125,7 +129,15 @@ jobs:
125129
strategy:
126130
fail-fast: false
127131
matrix:
128-
browser: [chromium, firefox, webkit]
132+
include:
133+
- app: portfolio
134+
browser: chromium
135+
- app: portfolio
136+
browser: firefox
137+
- app: portfolio
138+
browser: webkit
139+
- app: blog
140+
browser: chromium
129141
steps:
130142
- name: Checkout code
131143
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
@@ -136,6 +148,7 @@ jobs:
136148
node-version: ${{ env.NODE_VERSION }}
137149

138150
- name: Download portfolio build
151+
if: matrix.app == 'portfolio'
139152
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
140153
with:
141154
name: portfolio-dist-${{ github.sha }}
@@ -145,6 +158,7 @@ jobs:
145158
- name: Run E2E tests
146159
uses: ./.github/actions/test-playwright
147160
with:
161+
app: ${{ matrix.app }}
148162
browser: ${{ matrix.browser }}
149163
upload-results: 'true'
150164

.github/workflows/playwright.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,22 @@ env:
3434

3535
jobs:
3636
test:
37-
name: E2E Tests - ${{ matrix.project }}
37+
name: E2E Tests - ${{ matrix.app }} (${{ matrix.browser }})
3838
runs-on: ubuntu-latest
3939
timeout-minutes: 60
4040

4141
strategy:
4242
fail-fast: false
4343
matrix:
44-
project: [chromium, firefox, webkit]
44+
include:
45+
- app: portfolio
46+
browser: chromium
47+
- app: portfolio
48+
browser: firefox
49+
- app: portfolio
50+
browser: webkit
51+
- app: blog
52+
browser: chromium
4553

4654
steps:
4755
- name: Checkout code
@@ -55,7 +63,8 @@ jobs:
5563
- name: Run Playwright E2E tests
5664
uses: ./.github/actions/test-playwright
5765
with:
58-
browser: ${{ matrix.project }}
66+
app: ${{ matrix.app }}
67+
browser: ${{ matrix.browser }}
5968
upload-results: 'true'
6069

6170
report:

apps/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"packageManager": "pnpm@10.30.1",
66
"scripts": {
77
"build": "echo '🚀 Validating configuration...' && wrangler deploy --dry-run",
8+
"check": "pnpm test:unit && pnpm build",
89
"deploy": "wrangler deploy",
910
"dev": "wrangler dev",
1011
"start": "wrangler dev",

apps/portfolio/scripts/update-csp-script-hashes.mjs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,20 @@ const extractHashesFromHtml = (html) => {
5959
const startTagIndex = lowerHtml.indexOf("<script", pos);
6060
if (startTagIndex === -1) break;
6161

62-
const endTagIndex = lowerHtml.indexOf("</script>", startTagIndex);
62+
let endTagIndex = lowerHtml.indexOf("</script>", startTagIndex);
63+
let endTagLength = 9;
64+
65+
if (endTagIndex === -1) {
66+
const closeTagPattern = /<\s*\/\s*script\b[^>]*>/gi;
67+
closeTagPattern.lastIndex = startTagIndex;
68+
const fallbackMatch = closeTagPattern.exec(lowerHtml);
69+
70+
if (fallbackMatch) {
71+
endTagIndex = fallbackMatch.index;
72+
endTagLength = fallbackMatch[0].length;
73+
}
74+
}
75+
6376
if (endTagIndex === -1) {
6477
pos = startTagIndex + 7;
6578
continue;
@@ -83,7 +96,7 @@ const extractHashesFromHtml = (html) => {
8396
hashes.add(hashInlineScript(content));
8497
}
8598
}
86-
pos = endTagIndex + 9;
99+
pos = endTagIndex + endTagLength;
87100
}
88101
return hashes;
89102
};
@@ -111,10 +124,11 @@ const main = () => {
111124

112125
if (!existsSync(headersPath)) {
113126
console.error(`_headers file not found at ${headersPath}`);
114-
return;
127+
process.exit(1);
115128
}
116129

117130
const headersContent = readFileSync(headersPath, "utf8");
131+
const separator = headersContent.includes("\r\n") ? "\r\n" : "\n";
118132
const lines = headersContent.split(/\r?\n/);
119133
let cspLineIndex = -1;
120134

@@ -127,7 +141,7 @@ const main = () => {
127141

128142
if (cspLineIndex === -1) {
129143
console.error("Could not find Content-Security-Policy line in _headers.");
130-
return;
144+
process.exit(1);
131145
}
132146

133147
const originalLine = lines[cspLineIndex];
@@ -151,7 +165,7 @@ const main = () => {
151165

152166
if (scriptSrcIndex === -1) {
153167
console.error("Could not find script-src directive in CSP.");
154-
return;
168+
process.exit(1);
155169
}
156170

157171
const scriptSrcDirective = directives[scriptSrcIndex];
@@ -172,7 +186,7 @@ const main = () => {
172186
const nextCspValue = `${directives.join("; ")};`;
173187
lines[cspLineIndex] = prefix + nextCspValue;
174188

175-
writeFileSync(headersPath, lines.join("\n"), "utf8");
189+
writeFileSync(headersPath, lines.join(separator), "utf8");
176190

177191
console.log(
178192
`Updated CSP script-src with ${allHashes.size} inline script hash(es) in ${headersPath}`,

0 commit comments

Comments
 (0)