Skip to content
Draft
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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,22 @@ jobs:
pnpm verify:content

- name: Build static site
env:
SITE_URL: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'https://seungjun.sh' || '' }}
ALLOW_INDEXING: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.ALLOW_INDEXING || '' }}
GA4_DEPLOY_TARGET: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'production' || '' }}
GA4_PRODUCTION_ENABLED: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.GA4_PRODUCTION_ENABLED == 'true' && 'true' || '' }}
GA4_PRODUCTION_MEASUREMENT_ID: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.GA4_PRODUCTION_ENABLED == 'true' && vars.GA4_PRODUCTION_MEASUREMENT_ID || '' }}
PUBLIC_GA_MEASUREMENT_ID: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.GA4_PRODUCTION_ENABLED == 'true' && vars.GA4_PRODUCTION_MEASUREMENT_ID || '' }}
run: pnpm build

- name: Verify GA4 analytics guards
env:
GA4_EXPECTED_TARGET: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.GA4_PRODUCTION_ENABLED == 'true' && vars.ALLOW_INDEXING == 'true' && 'production' || 'disabled' }}
run: |
node scripts/verify-analytics.mjs
node scripts/verify-analytics-runtime.mjs

- name: Verify D1 search, retired paths and security guards
run: |
pnpm verify:search
Expand Down
9 changes: 8 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ export default defineConfig({
rehypePlugins: [rehypeContent, highlight],
}),
}),
...(configuredSite ? [sitemap()] : []),
...(configuredSite
? [
sitemap({
filter: (page) =>
!new URL(page).pathname.startsWith('/ga-validation/'),
}),
]
: []),
],
markdown: {
syntaxHighlight: false,
Expand Down
932 changes: 932 additions & 0 deletions docs/analytics-implementation-plan.md

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions docs/analytics-privacy-handoff.md

Large diffs are not rendered by default.

194 changes: 194 additions & 0 deletions docs/analytics-privacy-operations-proposal.md

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions scripts/prepare-ga-validation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';

const root = path.resolve(import.meta.dirname, '..');
const dist = path.join(root, 'dist');
const output = path.join(root, '.wrangler/ga-validation-assets');
const templatePath = 'ga-validation/template/index.html';
const template = fs.readFileSync(path.join(dist, templatePath), 'utf8');
assert.match(template, /data-template-analytics-environment="validation"/);
assert.match(template, /data-template-analytics-measurement-id="G-8SFTFGKZ9Y"/);
assert.match(template, /noindex,nofollow/);
assert.doesNotMatch(
template,
/astro-island|G-RQ6456HXLD|data-template-indexable/
);
fs.rmSync(output, { recursive: true, force: true });
fs.mkdirSync(path.join(output, 'ga-validation/template'), { recursive: true });
fs.writeFileSync(path.join(output, templatePath), template);
const copied = new Set();
// Follow only this synthetic template's assets; never copy blog HTML or images.
function copyReferences(text) {
for (const [, relative] of text.matchAll(
/(?:\/|\.\/)_astro\/([A-Za-z0-9_.-]+\.(?:js|css|woff2))/g
))
copyAsset(relative);
}
function copyAsset(name) {
if (copied.has(name)) return;
copied.add(name);
const file = path.join(dist, '_astro', name);
let content = fs.readFileSync(file);
// Validation uses system fonts; don't add another third-party request.
if (name.endsWith('.css'))
content = Buffer.from(
content.toString().replace(/@import\s+url\([^)]*\);?/g, '')
);
fs.mkdirSync(path.join(output, '_astro'), { recursive: true });
fs.writeFileSync(path.join(output, '_astro', name), content);
if (!name.endsWith('.woff2')) {
const text = content.toString();
copyReferences(text);
for (const [, sibling] of text.matchAll(
/["']\.\/([A-Za-z0-9_.-]+\.(?:js|css|woff2))["']/g
))
copyAsset(sibling);
}
}
copyReferences(template);
assert.ok(copied.size > 0, 'The shared analytics bundle is included');
console.log(
`PASS: isolated validation template and ${copied.size} referenced assets; no real article/image assets`
);
Loading
Loading