Skip to content
Closed
15 changes: 8 additions & 7 deletions .ObservabilityClerk/telemetry.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Telemetry Standardization Journal

| Date | Module | Events Standardized |
| :--------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| 2025-05-14 | `src/utils/github-releases.ts` | `github_releases_invalid_url`, `github_releases_request_failed`, `github_releases_validation_failed`, `github_releases_request_error` |
| 2025-05-14 | `src/components/Chat.astro` | `chat_client_error` |
| 2025-05-14 | `src/pages/api/chat.ts` | `chat_api_json_parse_error`, `chat_api_run_error` |
| 2025-05-14 | `src/utils/chat-stream.ts` | `chat_stream_parse_error` |
| 2025-05-14 | `src/__tests__/*.test.ts` | Expanded unit tests to cover 100% of telemetry branches (including non-Error rejection cases). |
| Date | Module | Events Standardized |
| :--------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
| 2025-05-14 | `src/utils/github-releases.ts` | `github_releases_invalid_url`, `github_releases_request_failed`, `github_releases_validation_failed`, `github_releases_request_error` |
| 2025-05-14 | `src/components/Chat.astro` | `chat_client_error` |
| 2025-05-14 | `src/pages/api/chat.ts` | `chat_api_json_parse_error`, `chat_api_run_error`, `chat_api_rate_limit_exceeded` |
| 2025-05-14 | `src/utils/chat-stream.ts` | `chat_stream_parse_error` |
| 2025-05-14 | `scripts/release.js` | `release_script_start`, `release_script_no_tags`, `release_script_git_log_error`, `release_script_output_error`, `release_script_complete` |
| 2025-05-14 | `src/__tests__/*.test.ts` | Expanded unit tests to cover 100% of telemetry branches (including non-Error rejection cases). |
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
run: npm run lint

- name: Security Audit
run: npm audit
run: npm audit --audit-level=critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/ci.yml"
echo "== Lines 45-55 of $FILE =="
nl -ba "$FILE" | sed -n '45,55p'

echo
echo "== All npm audit invocations in .github/workflows =="
rg -n --hidden --no-ignore-vcs "npm audit" .github/workflows || true

echo
echo "== All audit-level flags in .github/workflows =="
rg -n --hidden --no-ignore-vcs "audit-level" .github/workflows || true

Repository: alehar9320/astro-cloudflare-personal-website

Length of output: 195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/ci.yml"

echo "== Show lines 45-55 of $FILE (with numbers, using awk) =="
awk 'NR>=45 && NR<=55 {printf "%d:%s\n", NR, $0}' "$FILE" || true

echo
echo "== All npm audit invocations in .github/workflows =="
rg -n --hidden --no-ignore-vcs "npm audit" .github/workflows || true

echo
echo "== All audit-level flags in .github/workflows =="
rg -n --hidden --no-ignore-vcs "audit-level" .github/workflows || true

Repository: alehar9320/astro-cloudflare-personal-website

Length of output: 772


Tighten the npm audit security gate in CI
In .github/workflows/ci.yml line 50, CI runs npm audit --audit-level=critical, which only fails the job on critical advisories—so high vulnerabilities can pass. Use --audit-level=high (or higher) to keep the pre-merge gate effective.

Suggested fix
-        run: npm audit --audit-level=critical
+        run: npm audit --audit-level=high
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: npm audit --audit-level=critical
run: npm audit --audit-level=high
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 50, The CI job currently invokes the npm
audit step using the exact command string "run: npm audit
--audit-level=critical"; change that command to use a stricter audit level
(e.g., replace --audit-level=critical with --audit-level=high) so the workflow
fails on high or above vulnerabilities; update the workflow step that runs the
npm audit command to use the new flag and ensure any job names or comments
referencing the audit severity are adjusted accordingly.


# ─── Build & Test ─────────────────────────────────────────────────────────────
# Runs in parallel with quality to keep total wall-clock time low.
Expand Down
52 changes: 48 additions & 4 deletions __tests__/release.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ describe('release script', () => {

afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
vi.resetAllMocks();
delete process.env.GITHUB_OUTPUT;
vi.resetModules();
});

it('writes the version to GITHUB_OUTPUT without mutating tracked files', async () => {
process.env.GITHUB_OUTPUT = 'mock_github_output';
vi.mocked(child_process.execSync).mockReturnValue(Buffer.from('v1.0.0'));
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

await import('../scripts/release.js?t=' + Date.now());

Expand All @@ -58,6 +60,9 @@ describe('release script', () => {
expect.stringContaining('version=2023.01.01.1200')
);
expect(fsMock.default.writeFileSync).not.toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(
expect.objectContaining({ event: 'release_script_start', version: '2023.01.01.1200' })
);
});

it('formats changelog output from commits since the last tag', async () => {
Expand Down Expand Up @@ -100,13 +105,20 @@ describe('release script', () => {
if (typeof cmd === 'string' && cmd.includes('describe --tags')) throw new Error('No tags');
return Buffer.from('feat: initial release');
});
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

await import('../scripts/release.js?t=' + (Date.now() + 3));

expect(fsMock.default.appendFileSync).toHaveBeenCalledWith(
'mock_github_output',
expect.stringContaining('- feat: initial release')
);
expect(consoleSpy).toHaveBeenCalledWith(
expect.objectContaining({
event: 'release_script_no_tags',
error: expect.stringContaining('No tags'),
})
);
});

it('falls back to no documented changes when git log is unavailable', async () => {
Expand All @@ -116,32 +128,64 @@ describe('release script', () => {
if (typeof cmd === 'string' && cmd.includes('log')) throw new Error('Git log failed');
return Buffer.from('');
});
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

await import('../scripts/release.js?t=' + (Date.now() + 4));

expect(fsMock.default.appendFileSync).toHaveBeenCalledWith(
'mock_github_output',
expect.stringContaining('- No documented changes.')
);
expect(consoleSpy).toHaveBeenCalledWith(
expect.objectContaining({
event: 'release_script_git_log_error',
error: expect.stringContaining('Git log failed'),
})
);
});

it('exits when writing GitHub output fails', async () => {
process.env.GITHUB_OUTPUT = 'invalid_path';
vi.mocked(child_process.execSync).mockReturnValue(Buffer.from('v1.0.0'));
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
fsMock.default.appendFileSync.mockImplementation(() => {
throw new Error('Write failed');
});

const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);

await import('../scripts/release.js?t=' + (Date.now() + 5));

expect(exitSpy).toHaveBeenCalledWith(1);
exitSpy.mockRestore();
expect(consoleSpy).toHaveBeenCalledWith(
expect.objectContaining({
event: 'release_script_output_error',
error: expect.stringContaining('Write failed'),
})
);
});

it('handles non-Error exceptions in telemetry logging', async () => {
process.env.GITHUB_OUTPUT = 'mock_github_output';
vi.mocked(child_process.execSync).mockImplementation((cmd) => {
if (typeof cmd === 'string' && cmd.includes('describe --tags')) throw 'Fatal Git Error';
if (typeof cmd === 'string' && cmd.includes('log')) return Buffer.from('feat: test');
return Buffer.from('');
});

const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

await import('../scripts/release.js?t=' + (Date.now() + 7));

expect(consoleSpy).toHaveBeenCalledWith(
expect.objectContaining({
event: 'release_script_no_tags',
error: 'Fatal Git Error',
})
);
});
it('skips writing to GITHUB_OUTPUT if the environment variable is not set', async () => {
delete process.env.GITHUB_OUTPUT;
vi.resetModules();
vi.mocked(child_process.execSync).mockReturnValue(Buffer.from('v1.0.0'));

await import('../scripts/release.js?t=' + (Date.now() + 6));
Expand Down
17 changes: 9 additions & 8 deletions scripts/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ const HH = String(now.getUTCHours()).padStart(2, '0');
const mm = String(now.getUTCMinutes()).padStart(2, '0');
const version = `${YYYY}.${MM}.${DD}.${HH}${mm}`;

console.log(`Bumping version to ${version}`);
console.log({ event: 'release_script_start', version });

let lastTag = '';
try {
lastTag = execSync('git describe --tags --abbrev=0', { stdio: 'pipe' }).toString().trim();
} catch (e) {
console.log('No previous tags found. This might be the first release.');
console.debug('Git describe error:', e.message);
const error = String(e);
console.log({ event: 'release_script_no_tags', error });
}

let commits = '';
Expand All @@ -27,8 +27,8 @@ try {
commits = execSync('git log --oneline', { stdio: 'pipe' }).toString().trim();
}
} catch (e) {
console.warn('Could not fetch git commits. Ensure you are in a git repository with history.');
console.debug('Git log error:', e.message);
const error = String(e);
console.warn({ event: 'release_script_git_log_error', error });
}

let changelog = '';
Expand All @@ -55,12 +55,13 @@ if (githubOutput) {
try {
fs.appendFileSync(githubOutput, `version=${version}\n`);
fs.appendFileSync(githubOutput, `changelog<<EOF\n${changelog}EOF\n`);
console.log('Outputs written to GITHUB_OUTPUT');
console.log({ event: 'release_script_output_success' });
} catch (e) {
console.error(`Failed to write to GITHUB_OUTPUT: ${e.message}`);
const error = String(e);
console.error({ event: 'release_script_output_error', error });
process.exit(1);
}
}

console.log(`\n::notice title=Release Version::${version}`);
console.log(`Successfully prepared version ${version}`);
console.log({ event: 'release_script_complete', version });
1 change: 1 addition & 0 deletions src/pages/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
}

if (currentCount >= 20) {
console.warn({ event: 'chat_api_rate_limit_exceeded' });
// 20 requests per hour limit
return jsonError('Rate limit exceeded. Try again in an hour.', 429);
}
Expand Down
8 changes: 7 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ export default defineConfig({
'sentry.client.config.ts',
'scripts/release.js',
],
exclude: ['src/**/*.d.ts', 'src/content/**', 'src/data/**', 'src/styles/**'],
exclude: [
'src/**/*.astro',
'src/**/*.d.ts',
'src/content/**',
'src/data/**',
'src/styles/**',
],
},
},
});
Loading