Skip to content

Commit a7a24ee

Browse files
authored
Keep repository language stats aligned with TypeScript source (#105)
Move the OAuth success page into a TypeScript-owned module so the repo no longer tracks a standalone HTML source file, while preserving the built dist HTML artifact and the local OAuth callback response behavior. Add narrow Linguist overrides for helper JS and shell scripts instead of broad script rewrites so the GitHub language bar reflects the package''s actual source surface without expanding scope. Constraint: User requested a GitHub language-bar outcome without a broad repo rewrite Rejected: Rewrite helper JS and shell scripts into TypeScript | unnecessary scope expansion for a repo-statistics goal Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep helper-script Linguist overrides narrow; do not hide runtime source files behind broad patterns Tested: npx vitest run test/copy-oauth-success.test.ts test/oauth-server.integration.test.ts test/server.unit.test.ts Tested: npm run lint Tested: npm run typecheck Tested: npm run build Tested: npm test Not-tested: GitHub repo language API refresh on default branch before merge Co-authored-by: ndycode <ndycode@users.noreply.github.com>
1 parent 4ba99b2 commit a7a24ee

8 files changed

Lines changed: 47 additions & 42 deletions

File tree

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
eslint.config.js linguist-detectable=false
2+
scripts/*.js linguist-detectable=false
3+
scripts/*.sh linguist-detectable=false

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ OpenCode plugin: intercepts OpenAI SDK calls, routes through ChatGPT Codex backe
4343
- Source: root `index.ts` + `lib/`; `dist/` is generated output.
4444
- ESLint flat config: `no-explicit-any` enforced, unused args prefixed `_`.
4545
- Tests relax lint rules (see `eslint.config.js`).
46-
- Build copies `lib/oauth-success.html` to `dist/lib/` via `scripts/copy-oauth-success.js`.
46+
- Build emits `dist/lib/oauth-success.html` from the TypeScript source via `scripts/copy-oauth-success.js`.
4747
- ESM only (`"type": "module"`), Node >= 18.
4848

4949
## ANTI-PATTERNS (THIS PROJECT)

lib/auth/server.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import http from "node:http";
2-
import fs from "node:fs";
3-
import path from "node:path";
4-
import { fileURLToPath } from "node:url";
52
import type { OAuthServerInfo } from "../types.js";
63
import { logError, logWarn } from "../logger.js";
4+
import { oauthSuccessHtml } from "../oauth-success.js";
75
import {
86
OAUTH_CALLBACK_BIND_URL,
97
OAUTH_CALLBACK_LOOPBACK_HOST,
108
OAUTH_CALLBACK_PATH,
119
OAUTH_CALLBACK_PORT,
1210
} from "../runtime-contracts.js";
1311

14-
// Resolve path to oauth-success.html (one level up from auth/ subfolder)
15-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
16-
const successHtml = fs.readFileSync(path.join(__dirname, "..", "oauth-success.html"), "utf-8");
17-
1812
/**
1913
* Start a small local HTTP server that waits for /auth/callback and returns the code
2014
* @param options - OAuth state for validation
@@ -46,7 +40,7 @@ export function startLocalOAuthServer({ state }: { state: string }): Promise<OAu
4640
res.setHeader("X-Frame-Options", "DENY");
4741
res.setHeader("X-Content-Type-Options", "nosniff");
4842
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'none'");
49-
res.end(successHtml);
43+
res.end(oauthSuccessHtml);
5044
(server as http.Server & { _lastCode?: string })._lastCode = code;
5145
} catch (err) {
5246
logError(`Request handler error: ${(err as Error)?.message ?? String(err)}`);
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
export const oauthSuccessHtml = String.raw`<!DOCTYPE html>
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8">
@@ -642,7 +642,7 @@
642642
if (isNearMouse) {
643643
// Matrix green color when near mouse
644644
const intensity = 1 - (distanceFromMouse / hoverRadius);
645-
ctx.fillStyle = `rgba(0, 255, 65, ${intensity * 0.95})`;
645+
ctx.fillStyle = \`rgba(0, 255, 65, \${intensity * 0.95})\`;
646646
ctx.shadowBlur = 20 * intensity;
647647
ctx.shadowColor = '#00ff41';
648648
} else {
@@ -705,8 +705,9 @@
705705
const moveY = (e.clientY - window.innerHeight / 2) / 50;
706706
707707
document.querySelector('.container').style.transform =
708-
`translate(${moveX}px, ${moveY}px)`;
708+
\`translate(\${moveX}px, \${moveY}px)\`;
709709
});
710710
</script>
711711
</body>
712712
</html>
713+
`;

scripts/copy-oauth-success.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { promises as fs } from "node:fs";
22
import { dirname, join, resolve } from "node:path";
3-
import { fileURLToPath } from "node:url";
3+
import { fileURLToPath, pathToFileURL } from "node:url";
44

55
const __filename = fileURLToPath(import.meta.url);
66
const __dirname = dirname(__filename);
@@ -11,20 +11,30 @@ function normalizePathForCompare(path) {
1111
}
1212

1313
function getDefaultPaths() {
14-
const src = join(__dirname, "..", "lib", "oauth-success.html");
14+
const modulePath = join(__dirname, "..", "dist", "lib", "oauth-success.js");
1515
const dest = join(__dirname, "..", "dist", "lib", "oauth-success.html");
16-
return { src, dest };
16+
return { modulePath, dest };
17+
}
18+
19+
async function loadOAuthSuccessHtml(modulePath) {
20+
const moduleUrl = pathToFileURL(modulePath).href;
21+
const mod = await import(moduleUrl);
22+
if (typeof mod.oauthSuccessHtml !== "string") {
23+
throw new TypeError(`Expected oauthSuccessHtml string export from ${modulePath}`);
24+
}
25+
return mod.oauthSuccessHtml;
1726
}
1827

1928
export async function copyOAuthSuccessHtml(options = {}) {
2029
const defaults = getDefaultPaths();
21-
const src = options.src ?? defaults.src;
30+
const modulePath = options.modulePath ?? defaults.modulePath;
2231
const dest = options.dest ?? defaults.dest;
32+
const html = options.html ?? (await loadOAuthSuccessHtml(modulePath));
2333

2434
await fs.mkdir(dirname(dest), { recursive: true });
25-
await fs.copyFile(src, dest);
35+
await fs.writeFile(dest, html, "utf-8");
2636

27-
return { src, dest };
37+
return { modulePath, dest };
2838
}
2939

3040
const isDirectRun = (() => {

test/copy-oauth-success.test.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
import { describe, it, expect } from "vitest";
2-
import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
2+
import { mkdtemp, readFile, rm } from "node:fs/promises";
33
import { join } from "node:path";
44
import { tmpdir } from "node:os";
55

66
describe("copy-oauth-success script", () => {
7-
it("exports copyOAuthSuccessHtml() for reuse/testing", async () => {
8-
const mod = await import("../scripts/copy-oauth-success.js");
9-
expect(typeof mod.copyOAuthSuccessHtml).toBe("function");
10-
});
7+
it("exports copyOAuthSuccessHtml() for reuse/testing", async () => {
8+
const mod = await import("../scripts/copy-oauth-success.js");
9+
expect(typeof mod.copyOAuthSuccessHtml).toBe("function");
10+
});
1111

12-
it("copies oauth-success.html to the requested destination", async () => {
13-
const mod = await import("../scripts/copy-oauth-success.js");
12+
it("copies oauth-success.html to the requested destination", async () => {
13+
const mod = await import("../scripts/copy-oauth-success.js");
1414

15-
const root = await mkdtemp(join(tmpdir(), "opencode-oauth-success-"));
16-
const src = join(root, "oauth-success.html");
17-
const dest = join(root, "dist", "lib", "oauth-success.html");
15+
const root = await mkdtemp(join(tmpdir(), "opencode-oauth-success-"));
16+
const dest = join(root, "dist", "lib", "oauth-success.html");
1817

19-
try {
20-
const html = "<!doctype html><html><body>ok</body></html>";
21-
await writeFile(src, html, "utf-8");
18+
try {
19+
const html = "<!doctype html><html><body>ok</body></html>";
2220

23-
await mod.copyOAuthSuccessHtml({ src, dest });
21+
await mod.copyOAuthSuccessHtml({ html, dest });
2422

25-
const copied = await readFile(dest, "utf-8");
26-
expect(copied).toBe(html);
27-
} finally {
28-
await rm(root, { recursive: true, force: true });
29-
}
30-
});
23+
const copied = await readFile(dest, "utf-8");
24+
expect(copied).toBe(html);
25+
} finally {
26+
await rm(root, { recursive: true, force: true });
27+
}
28+
});
3129
});

test/oauth-server.integration.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ describe("OAuth Server Integration", () => {
3030
const response = await fetch(callbackUrl);
3131
expect(response.status).toBe(200);
3232
expect(response.headers.get("content-type")).toContain("text/html");
33+
expect(await response.text()).toContain("ACCESS GRANTED");
3334

3435
// Server should have captured the code
3536
const result = await serverInfo.waitForCode(testState);

test/server.unit.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ vi.mock('node:http', () => {
2727
};
2828
});
2929

30-
vi.mock('node:fs', () => ({
31-
default: {
32-
readFileSync: vi.fn(() => '<html>Success</html>'),
33-
},
30+
vi.mock('../lib/oauth-success.js', () => ({
31+
oauthSuccessHtml: '<html>Success</html>',
3432
}));
3533

3634
vi.mock('../lib/logger.js', () => ({

0 commit comments

Comments
 (0)