website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


60e8429pyrossh 2026-07-04T15:14:01+05:30
style: fix formatting issues from merge
.alchemy/prd/repos-bucket-prd.json CHANGED
@@ -33,4 +33,4 @@
33
33
  "force": false
34
34
  }
35
35
  }
36
- }
36
+ }
.alchemy/prd/rewrite-rules-prd.json CHANGED
@@ -88,10 +88,7 @@
88
88
  "status": "pending",
89
89
  "paused": false,
90
90
  "accountId": "dfdb8ca4e419990852eb214c5b50bd5e",
91
- "nameservers": [
92
- "glen.ns.cloudflare.com",
91
+ "nameservers": ["glen.ns.cloudflare.com", "riya.ns.cloudflare.com"],
93
- "riya.ns.cloudflare.com"
94
- ],
95
92
  "originalNameservers": [
96
93
  "ns-1020.awsdns-63.net",
97
94
  "ns-1447.awsdns-52.org",
@@ -160,4 +157,4 @@
160
157
  }
161
158
  ]
162
159
  }
163
- }
160
+ }
.alchemy/prd/website-bucket-prd.json CHANGED
@@ -26,17 +26,13 @@
26
26
  "remote": false,
27
27
  "isDeployed": true
28
28
  },
29
- "domains": [
30
- "pyrossh.dev"
29
+ "domains": ["pyrossh.dev"]
31
- ]
32
30
  },
33
31
  "props": {
34
32
  "name": "pyrossh-website-prd",
35
- "domains": [
36
- "pyrossh.dev"
33
+ "domains": ["pyrossh.dev"],
37
- ],
38
34
  "dev": {
39
35
  "force": false
40
36
  }
41
37
  }
42
- }
38
+ }
.alchemy/prd/website-bucket-prd/pyrossh.dev.json CHANGED
@@ -27,4 +27,4 @@
27
27
  "isDeployed": true
28
28
  }
29
29
  }
30
- }
30
+ }
.alchemy/prd/zone-prd.json CHANGED
@@ -19,10 +19,7 @@
19
19
  "status": "pending",
20
20
  "paused": false,
21
21
  "accountId": "dfdb8ca4e419990852eb214c5b50bd5e",
22
- "nameservers": [
23
- "glen.ns.cloudflare.com",
22
+ "nameservers": ["glen.ns.cloudflare.com", "riya.ns.cloudflare.com"],
24
- "riya.ns.cloudflare.com"
25
- ],
26
23
  "originalNameservers": [
27
24
  "ns-1020.awsdns-63.net",
28
25
  "ns-1447.awsdns-52.org",
@@ -62,4 +59,4 @@
62
59
  "http3": "on"
63
60
  }
64
61
  }
65
- }
62
+ }
.vscode/extensions.json CHANGED
@@ -7,4 +7,4 @@
7
7
  "myriad-dreamin.tinymist"
8
8
  ],
9
9
  "unwantedRecommendations": []
10
- }
10
+ }
README.md CHANGED
@@ -44,9 +44,9 @@ Any static assets, like images, can be placed in the `public/` directory.
44
44
 
45
45
  All commands are run from the root of the project, from a terminal:
46
46
 
47
- | Command | Action |
47
+ | Command | Action |
48
- | :------------------------ | :----------------------------------------------- |
48
+ | :-------------------- | :----------------------------------------------- |
49
- | `bun install` | Installs dependencies |
49
+ | `bun install` | Installs dependencies |
50
50
  | `bun dev` | Starts local dev server at `localhost:4321` |
51
51
  | `bun build` | Build your production site to `./dist/` |
52
52
  | `bun preview` | Preview your build locally, before deploying |
ec.config.mjs CHANGED
@@ -1,36 +1,36 @@
1
- import { defineEcConfig } from 'astro-expressive-code';
1
+ import { defineEcConfig } from "astro-expressive-code";
2
2
 
3
3
  export default defineEcConfig({
4
4
  // themes: [],
5
5
  shiki: {
6
6
  langAlias: {
7
- h: 'c',
7
+ h: "c",
8
- ino: 'c',
8
+ ino: "c",
9
- mm: 'c',
9
+ mm: "c",
10
- plum: 'txt',
10
+ plum: "txt",
11
- mi: 'txt',
11
+ mi: "txt",
12
- scm: 'txt',
12
+ scm: "txt",
13
- podspec: 'txt',
13
+ podspec: "txt",
14
- lock: 'txt',
14
+ lock: "txt",
15
- xcconfig: 'txt',
15
+ xcconfig: "txt",
16
- plist: 'xml',
16
+ plist: "xml",
17
- xcworkspacedata: 'xml',
17
+ xcworkspacedata: "xml",
18
- xcsettings: 'xml',
18
+ xcsettings: "xml",
19
- storyboard: 'txt',
19
+ storyboard: "txt",
20
- pbxproj: 'txt',
20
+ pbxproj: "txt",
21
- mjs: 'javascript',
21
+ mjs: "javascript",
22
- snap: 'javascript',
22
+ snap: "javascript",
23
- gradle: 'groovy',
23
+ gradle: "groovy",
24
- class: 'java',
24
+ class: "java",
25
- mod: 'go',
25
+ mod: "go",
26
- sum: 'go',
26
+ sum: "go",
27
27
  },
28
28
  },
29
29
  styleOverrides: {
30
- codeFontSize: '0.8rem',
30
+ codeFontSize: "0.8rem",
31
- uiFontSize: '0.8rem',
31
+ uiFontSize: "0.8rem",
32
32
  // frames: {
33
33
  // shadowColor: '#124',
34
34
  // },
35
35
  },
36
- })
36
+ });
playwright.config.ts CHANGED
@@ -1,25 +1,25 @@
1
- import { defineConfig, devices } from '@playwright/test';
1
+ import { defineConfig, devices } from "@playwright/test";
2
2
 
3
3
  export default defineConfig({
4
- testDir: './tests',
4
+ testDir: "./tests",
5
5
  fullyParallel: true,
6
6
  forbidOnly: !!process.env.CI,
7
7
  retries: process.env.CI ? 2 : 0,
8
8
  workers: process.env.CI ? 1 : undefined,
9
- reporter: 'html',
9
+ reporter: "html",
10
10
  use: {
11
- baseURL: 'http://localhost:4321',
11
+ baseURL: "http://localhost:4321",
12
- trace: 'on-first-retry',
12
+ trace: "on-first-retry",
13
13
  },
14
14
  projects: [
15
15
  {
16
- name: 'chromium',
16
+ name: "chromium",
17
- use: { ...devices['Desktop Chrome'] },
17
+ use: { ...devices["Desktop Chrome"] },
18
18
  },
19
19
  ],
20
20
  webServer: {
21
- command: 'bun run dev',
21
+ command: "bun run dev",
22
- url: 'http://localhost:4321',
22
+ url: "http://localhost:4321",
23
23
  reuseExistingServer: !process.env.CI,
24
24
  },
25
25
  });
pnpm-workspace.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  allowBuilds:
2
- '@tensorflow/tfjs-node': set this to true or false
2
+ "@tensorflow/tfjs-node": set this to true or false
3
3
  core-js: set this to true or false
4
4
  es5-ext: set this to true or false
5
5
  esbuild: set this to true or false
scripts/apply-cloudflare-rules.ts CHANGED
@@ -34,10 +34,7 @@ type Rule = {
34
34
  enabled?: boolean;
35
35
  };
36
36
 
37
- const cloudflare = async <T>(
38
- path: string,
39
- init?: RequestInit & { allowNotFound?: boolean },
37
+ const cloudflare = async <T>(path: string, init?: RequestInit & { allowNotFound?: boolean }) => {
40
- ) => {
41
38
  const response = await fetch(`${apiBase}${path}`, {
42
39
  method: init?.method,
43
40
  body: init?.body,
@@ -54,9 +51,7 @@ const cloudflare = async <T>(
54
51
  }
55
52
 
56
53
  if (!response.ok || !payload.success) {
57
- const message = payload.errors
58
- ?.map((error) => `${error.code}: ${error.message}`)
54
+ const message = payload.errors?.map((error) => `${error.code}: ${error.message}`).join("; ");
59
- .join("; ");
60
55
  throw new Error(message || `Cloudflare API request failed: ${response.status}`);
61
56
  }
62
57
 
@@ -68,9 +63,7 @@ const getZoneId = async () => {
68
63
  return process.env.CLOUDFLARE_ZONE_ID;
69
64
  }
70
65
 
71
- const zones = await cloudflare<Zone[]>(
72
- `/zones?name=${encodeURIComponent(zoneName)}`,
66
+ const zones = await cloudflare<Zone[]>(`/zones?name=${encodeURIComponent(zoneName)}`);
73
- );
74
67
  const zone = zones.find((item) => item.name === zoneName);
75
68
 
76
69
  if (!zone) {
@@ -120,9 +113,7 @@ const makeRewriteRules = (host: string): Rule[] => [
120
113
  ];
121
114
 
122
115
  const rewriteRules = makeRewriteRules(zoneName);
123
- const rewriteRuleDescriptions = new Set(
124
- rewriteRules.map((rule) => rule.description),
116
+ const rewriteRuleDescriptions = new Set(rewriteRules.map((rule) => rule.description));
125
- );
126
117
 
127
118
  const makeRulesetBody = (rules: Rule[], existingRuleset?: Ruleset) => ({
128
119
  name: rulesetName,
@@ -138,9 +129,7 @@ const existingRuleset = await cloudflare<Ruleset>(entrypointPath, {
138
129
  });
139
130
  const existingRules = existingRuleset?.rules ?? [];
140
131
  const nextRules = [
141
- ...existingRules.filter(
142
- (rule) => !rewriteRuleDescriptions.has(rule.description),
132
+ ...existingRules.filter((rule) => !rewriteRuleDescriptions.has(rule.description)),
143
- ),
144
133
  ...rewriteRules,
145
134
  ];
146
135
 
src/actions/index.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import { defineAction } from "astro:actions";
2
2
  import { z } from "astro/zod";
3
- import {
4
- addGitBugIssueComment,
5
- createGitBugIssue,
6
- setGitBugIssueState,
7
- } from "@/utils/gitBug";
3
+ import { addGitBugIssueComment, createGitBugIssue, setGitBugIssueState } from "@/utils/gitBug";
8
4
 
9
5
  const repoId = z.string().regex(/^[a-zA-Z0-9._-]+$/);
10
6
  const issueId = z.string().regex(/^[a-f0-9]{16}$/);
@@ -55,12 +51,7 @@ export const server = {
55
51
  author: z.string().trim().optional(),
56
52
  }),
57
53
  handler: async (input) => {
58
- return await setGitBugIssueState(
54
+ return await setGitBugIssueState(input.repoId, input.issueId, input.state, input.author);
59
- input.repoId,
60
- input.issueId,
61
- input.state,
62
- input.author,
63
- );
64
55
  },
65
56
  }),
66
57
  };
src/components/HeaderLink.astro CHANGED
@@ -5,7 +5,7 @@ type Props = HTMLAttributes<"a">;
5
5
 
6
6
  const { href, class: className, ...props } = Astro.props;
7
7
  const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, "");
8
- const subpath = pathname.match(/[^\/]+/g);
8
+ const subpath = pathname.match(/[^/]+/g);
9
9
  const isActive = href === pathname || href === "/" + (subpath?.[0] || "");
10
10
  const isExternal = href.startsWith("http");
11
11
  const target = isExternal ? "_blank" : "_parent";
src/consts.ts CHANGED
@@ -8,80 +8,86 @@ import zenSvg from "@/assets/logos/zen.svg";
8
8
  import brunoPng from "@/assets/logos/bruno.png";
9
9
  // import hyectorPng from "@/assets/logos/hyvector.png";
10
10
 
11
- export const SITE_TITLE = 'pyrossh';
11
+ export const SITE_TITLE = "pyrossh";
12
- export const SITE_DESCRIPTION = 'Welcome to my website!';
12
+ export const SITE_DESCRIPTION = "Welcome to my website!";
13
13
  export const REPOS = [
14
14
  {
15
15
  title: "rust-embed",
16
+ description:
16
- description: "rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.",
17
+ "rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.",
17
- tags: ['rust', 'proc-macro', 'http']
18
+ tags: ["rust", "proc-macro", "http"],
18
19
  },
19
20
  {
20
21
  title: "website",
21
22
  description: "木 Personal website of pyrossh. Built with astrojs, shiki, vite.",
22
- tags: ['astro', 'js', 'html', 'css']
23
+ tags: ["astro", "js", "html", "css"],
23
24
  },
24
25
  {
25
26
  title: "plum",
26
27
  description: "A statically typed, imperative programming language inspired by rust, python",
27
- tags: ['treesitter', 'compiler', 'wasm']
28
+ tags: ["treesitter", "compiler", "wasm"],
28
29
  },
29
30
  {
30
31
  title: "edge-city",
32
+ description:
31
- description: "edge-city is a next level meta-framework for react that runs only on edge runtimes",
33
+ "edge-city is a next level meta-framework for react that runs only on edge runtimes",
32
- tags: ['react', 'js', 'ssr']
34
+ tags: ["react", "js", "ssr"],
33
35
  },
34
36
  {
35
37
  title: "gromer",
38
+ description:
36
- description: "gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.",
39
+ "gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.",
37
- tags: ['golang', 'htmx', 'ssr']
40
+ tags: ["golang", "htmx", "ssr"],
38
41
  },
39
42
  {
40
43
  title: "atoms-element",
44
+ description:
41
- description: "A simple web component library for defining your custom elements. It works on both client and server.",
45
+ "A simple web component library for defining your custom elements. It works on both client and server.",
42
- tags: ['js']
46
+ tags: ["js"],
43
47
  },
44
48
  {
45
49
  title: "atoms-state",
46
50
  description: "Simple State management for react",
47
- tags: ['js', 'react', 'flux']
51
+ tags: ["js", "react", "flux"],
48
52
  },
49
53
  {
50
54
  title: "remote-monitor",
51
55
  description: "Remote Monitoring and Control using GSM-SMS",
52
- tags: ['c++', 'teensy', 'arduino']
56
+ tags: ["c++", "teensy", "arduino"],
53
57
  },
54
58
  {
55
59
  title: "only-bible-app",
60
+ description:
56
- description: "The only bible app you will ever need. No ads. No in-app purchases. No distractions.",
61
+ "The only bible app you will ever need. No ads. No in-app purchases. No distractions.",
57
- tags: ['kotlin', 'android', 'ios']
62
+ tags: ["kotlin", "android", "ios"],
58
63
  },
59
64
  {
60
65
  title: "gdx-studio",
66
+ description:
61
- description: "An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop",
67
+ "An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop",
62
- tags: ['libgdx', 'java', 'desktop']
68
+ tags: ["libgdx", "java", "desktop"],
63
69
  },
64
70
  {
65
71
  title: "rp2350",
66
72
  description: "code to drive rp2350",
67
- tags: ['zig', 'raspberry-pi']
73
+ tags: ["zig", "raspberry-pi"],
68
74
  },
69
75
  {
70
76
  title: "config",
71
77
  description: "Common configuration",
72
- tags: ['brew', 'nushell']
78
+ tags: ["brew", "nushell"],
73
79
  },
74
80
  {
75
81
  title: "sabel-ide",
76
82
  description: "sabel-ide",
77
- tags: ['python', 'qt']
83
+ tags: ["python", "qt"],
78
84
  },
79
85
  {
80
86
  title: "tide-jsx",
81
87
  description: "Tide + JSX",
82
- tags: ['rust', 'proc-macro', 'jsx']
88
+ tags: ["rust", "proc-macro", "jsx"],
83
89
  },
84
- ]
90
+ ];
85
91
  export const TOOLS = [
86
92
  {
87
93
  name: "Stats",
@@ -127,11 +133,11 @@ export const TOOLS = [
127
133
  name: "Secretive",
128
134
  link: "https://github.com/maxgoedjen/secretive",
129
135
  image: null,
130
- }
136
+ },
131
137
  // TODO: check if this is useful as well
132
138
  // {
133
139
  // name: "Hyvector",
134
140
  // link: "https://www.hyvector.com/",
135
141
  // image: import("@/assets/logos/hyvector.png"),
136
142
  // },
137
- ]
143
+ ];
src/content/gopibot-to-the-rescue.md CHANGED
@@ -27,92 +27,92 @@ First I went and created the bot in my slack team settings. And then wrote a scr
27
27
  Here is the simple script,
28
28
 
29
29
  ```js
30
- const RtmClient = require('@slack/client').RtmClient;
30
+ const RtmClient = require("@slack/client").RtmClient;
31
- const RTM_EVENTS = require('@slack/client').RTM_EVENTS;
31
+ const RTM_EVENTS = require("@slack/client").RTM_EVENTS;
32
- const CLIENT_EVENTS = require('@slack/client').CLIENT_EVENTS;
32
+ const CLIENT_EVENTS = require("@slack/client").CLIENT_EVENTS;
33
33
  const bot_token = process.env.SLACK_BOT_TOKEN;
34
34
 
35
35
  const rtm = new RtmClient(bot_token);
36
36
  const COMMANDS = {
37
- web: 'ssh -i qa.pem user@url docker pull image-name && docker rm -f container-id && docker run -d image-name',
37
+ web: "ssh -i qa.pem user@url docker pull image-name && docker rm -f container-id && docker run -d image-name",
38
38
  };
39
39
  let deploymentInProgress = false;
40
40
  let counter = 0;
41
41
 
42
42
  rtm.on(RTM_EVENTS.MESSAGE, (event) => {
43
- console.log('Got event', event);
44
- if (
45
- (event.subtype === 'message_changed' || event.subtype === 'message_deleted') &&
46
- event.text &&
47
- event.text.indexOf('$slackBotId') > -1
48
- ) {
49
- return rtm.sendMessage(
50
- 'Please dont change the message and expect me to correct your past mistakes',
51
- event.channel,
52
- );
53
- }
54
- if (event.subtype) {
55
- return;
56
- }
57
- if (event.type === 'message' && event.text && event.text.indexOf('$slackBotId') > -1) {
58
- if (deploymentInProgress === true) {
59
- counter = counter + 1;
60
- if (counter > 3) {
61
- counter = 0;
62
- return rtm.sendMessage(
63
- "Stop bugging me noob or I'll tell to raise you bugs",
64
- event.channel,
65
- );
66
- }
67
- return rtm.sendMessage('I am already processing a deploy request please wait', event.channel);
68
- }
69
- var input = event.text.trim().replace('$slackBotId ', '');
70
- console.log('Got input', input);
71
- const arr = input.split(' ');
72
- arr.forEach((word) => {
73
- word = word.replace(/\s/g, '');
74
- });
75
- const currCommand = arr[0];
76
- if (COMMANDS.indexOf(currCommand) > -1) {
77
- rtm.sendMessage('Starting to deploy ${currCommand}', event.channel);
78
- const ssh = spawn(COMMANDS[currCommand]);
79
- deploymentInProgress = true;
80
- ssh.stdout.on('data', (data) => {
81
- rtm.sendMessage(data, event.channel);
82
- });
83
- ssh.stderr.on('data', (data) => {
84
- rtm.sendMessage(data, event.channel);
85
- });
86
- ssh.on('close', (code) => {
87
- deploymentInProgress = false;
88
- if (code === 0) {
89
- console.log('Deployed Successfully', currCommand);
90
- rtm.sendMessage('Deployed Successfully ' + currCommand, event.channel);
91
- } else {
92
- console.log('child process exited with code ', code);
93
- rtm.sendMessage('child process exited with code ' + code);
94
- }
95
- });
96
- return;
97
- } else {
98
- counter = counter + 1;
99
- if (counter > 3) {
100
- counter = 0;
101
- return rtm.sendMessage(
102
- "Stop bugging me noob or I'll tell <@U30TXGLS1|gopi> to raise you bugs",
103
- event.channel,
104
- );
105
- }
106
- return rtm.sendMessage(
107
- `command '${event.text} ' not found.You need to specify one of these commands [${COMMANDS.map(
108
- (v, k) => k,
109
- ).join(',')} ]`,
110
- event.channel,
111
- );
112
- }
113
- }
43
+ console.log("Got event", event);
44
+ if (
45
+ (event.subtype === "message_changed" || event.subtype === "message_deleted") &&
46
+ event.text &&
47
+ event.text.indexOf("$slackBotId") > -1
48
+ ) {
49
+ return rtm.sendMessage(
50
+ "Please dont change the message and expect me to correct your past mistakes",
51
+ event.channel,
52
+ );
53
+ }
54
+ if (event.subtype) {
55
+ return;
56
+ }
57
+ if (event.type === "message" && event.text && event.text.indexOf("$slackBotId") > -1) {
58
+ if (deploymentInProgress === true) {
59
+ counter = counter + 1;
60
+ if (counter > 3) {
61
+ counter = 0;
62
+ return rtm.sendMessage(
63
+ "Stop bugging me noob or I'll tell to raise you bugs",
64
+ event.channel,
65
+ );
66
+ }
67
+ return rtm.sendMessage("I am already processing a deploy request please wait", event.channel);
68
+ }
69
+ var input = event.text.trim().replace("$slackBotId ", "");
70
+ console.log("Got input", input);
71
+ const arr = input.split(" ");
72
+ arr.forEach((word) => {
73
+ word = word.replace(/\s/g, "");
74
+ });
75
+ const currCommand = arr[0];
76
+ if (COMMANDS.indexOf(currCommand) > -1) {
77
+ rtm.sendMessage("Starting to deploy ${currCommand}", event.channel);
78
+ const ssh = spawn(COMMANDS[currCommand]);
79
+ deploymentInProgress = true;
80
+ ssh.stdout.on("data", (data) => {
81
+ rtm.sendMessage(data, event.channel);
82
+ });
83
+ ssh.stderr.on("data", (data) => {
84
+ rtm.sendMessage(data, event.channel);
85
+ });
86
+ ssh.on("close", (code) => {
87
+ deploymentInProgress = false;
88
+ if (code === 0) {
89
+ console.log("Deployed Successfully", currCommand);
90
+ rtm.sendMessage("Deployed Successfully " + currCommand, event.channel);
91
+ } else {
92
+ console.log("child process exited with code ", code);
93
+ rtm.sendMessage("child process exited with code " + code);
94
+ }
95
+ });
96
+ return;
97
+ } else {
98
+ counter = counter + 1;
99
+ if (counter > 3) {
100
+ counter = 0;
101
+ return rtm.sendMessage(
102
+ "Stop bugging me noob or I'll tell <@U30TXGLS1|gopi> to raise you bugs",
103
+ event.channel,
104
+ );
105
+ }
106
+ return rtm.sendMessage(
107
+ `command '${event.text} ' not found.You need to specify one of these commands [${COMMANDS.map(
108
+ (v, k) => k,
109
+ ).join(",")} ]`,
110
+ event.channel,
111
+ );
112
+ }
113
+ }
114
114
  });
115
- console.log('Starting deploybot');
115
+ console.log("Starting deploybot");
116
116
  rtm.start();
117
117
  ```
118
118
 
src/content/react-powertools-swr.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: 'React Powertools: SWR'
2
+ title: "React Powertools: SWR"
3
3
  description: A react library that makes it easier to fetch data
4
4
  pubDate: 2024-08-16
5
5
  tags:
@@ -40,49 +40,49 @@ const { data, error, isLoading, isValidating, mutate } = useSWR(key, fetcher, op
40
40
  > Before SWR
41
41
 
42
42
  ```tsx
43
- import { useState, useEffect } from 'react';
43
+ import { useState, useEffect } from "react";
44
44
 
45
45
  const useUser = (id: string) => {
46
- const [user, setUser] = useState(null);
46
+ const [user, setUser] = useState(null);
47
- const [loading, setLoading] = useState(false);
47
+ const [loading, setLoading] = useState(false);
48
- const [error, setError] = useState(null);
48
+ const [error, setError] = useState(null);
49
-
49
+
50
- useEffect(() => {
50
+ useEffect(() => {
51
- setLoading(true);
51
+ setLoading(true);
52
- setError(null);
52
+ setError(null);
53
- fetch(`/users/${id}`)
53
+ fetch(`/users/${id}`)
54
- .then((res) => res.json())
54
+ .then((res) => res.json())
55
- .then((data) => {
55
+ .then((data) => {
56
- setUser(data);
56
+ setUser(data);
57
- setLoading(false);
57
+ setLoading(false);
58
- })
58
+ })
59
- .catch((err) => {
59
+ .catch((err) => {
60
- setError(err);
60
+ setError(err);
61
- setLoading(false);
61
+ setLoading(false);
62
- });
62
+ });
63
- }, [id]);
63
+ }, [id]);
64
- return {
64
+ return {
65
- user,
65
+ user,
66
- loading,
66
+ loading,
67
- error,
67
+ error,
68
- };
68
+ };
69
69
  };
70
70
  ```
71
71
 
72
72
  > After SWR
73
73
 
74
74
  ```tsx
75
- import useSWR from 'swr';
75
+ import useSWR from "swr";
76
76
 
77
77
  const fetcher = (...args) => fetch(...args).then((res) => res.json());
78
78
 
79
79
  const useUser = (id: string) => {
80
- const { data, error, isLoading } = useSWR(`/users/${id}`, fetcher);
80
+ const { data, error, isLoading } = useSWR(`/users/${id}`, fetcher);
81
- return {
81
+ return {
82
- user: data,
82
+ user: data,
83
- isLoading,
83
+ isLoading,
84
- error: error,
84
+ error: error,
85
- };
85
+ };
86
86
  };
87
87
  ```
88
88
 
@@ -91,16 +91,16 @@ const useUser = (id: string) => {
91
91
  You can configure a global fetcher function so that you don't need pass the fetcher on every hook call using the `SWRConfig` Provider at the root app level. The library uses a global cache to store and share data across all components, you can also customise this behaviour with the `provider` option.
92
92
 
93
93
  ```tsx
94
- import { SWRConfig } from 'swr';
94
+ import { SWRConfig } from "swr";
95
95
 
96
96
  const fetcher = (...args) => fetch(...args).then((res) => res.json());
97
97
 
98
98
  function App() {
99
- return (
99
+ return (
100
- <SWRConfig value={{ fetcher: fetcher, provider: () => new Map() }}>
100
+ <SWRConfig value={{ fetcher: fetcher, provider: () => new Map() }}>
101
- <Page />
101
+ <Page />
102
- </SWRConfig>
102
+ </SWRConfig>
103
- );
103
+ );
104
104
  }
105
105
  ```
106
106
 
@@ -123,47 +123,47 @@ There are 2 ways to trigger a revalidation request manually,
123
123
  **1.** You can use the **mutate** function returned by the **useSWR** hook to trigger a revalidation of the data
124
124
 
125
125
  ```tsx
126
- import useSWR from 'swr';
126
+ import useSWR from "swr";
127
127
 
128
128
  const Profile = () => {
129
- const { data, error, isLoading, mutate } = useSWR(`/users/1`);
129
+ const { data, error, isLoading, mutate } = useSWR(`/users/1`);
130
- if (error) return <div>failed to load</div>;
130
+ if (error) return <div>failed to load</div>;
131
- if (isLoading) return <div className="text">loading...</div>;
131
+ if (isLoading) return <div className="text">loading...</div>;
132
- return (
132
+ return (
133
- <div>
133
+ <div>
134
- <div>{JSON.stringify(data, null, 2)}</div>
134
+ <div>{JSON.stringify(data, null, 2)}</div>
135
- <button onClick={() => mutate()}>Update User</button>
135
+ <button onClick={() => mutate()}>Update User</button>
136
- </div>
136
+ </div>
137
- );
137
+ );
138
138
  };
139
139
  ```
140
140
 
141
141
  This can be used to implement optimistic updates as well if you know what data needs to change and once the revalidation is complete the cache gets updated with new data from the server.
142
142
 
143
143
  ```tsx
144
- mutate({ ...data, name: 'John Doe' });
144
+ mutate({ ...data, name: "John Doe" });
145
145
  ```
146
146
 
147
147
  **2.** If you need to update the cache from another component which doesn't have access to the **useSWR** hook you can use the **mutate** function returned by the **useSWRConfig** to get access to the cache. Here you would need to provide the key to trigger a revalidation of the request.
148
148
 
149
149
  ```tsx
150
- import { useSWRConfig } from 'swr';
150
+ import { useSWRConfig } from "swr";
151
151
  // or import { mutate } from "swr"
152
152
 
153
153
  const UpdateButton = () => {
154
- const { mutate } = useSWRConfig();
154
+ const { mutate } = useSWRConfig();
155
- return (
155
+ return (
156
- <div>
156
+ <div>
157
- <button onClick={() => mutate(`/users/1`)}>Update User</button>
157
+ <button onClick={() => mutate(`/users/1`)}>Update User</button>
158
- </div>
158
+ </div>
159
- );
159
+ );
160
160
  };
161
161
  ```
162
162
 
163
163
  Here as well you can do optimistic updates similarly,
164
164
 
165
165
  ```tsx
166
- mutate(`/users/1`, { ...data, name: 'John Doe' });
166
+ mutate(`/users/1`, { ...data, name: "John Doe" });
167
167
  ```
168
168
 
169
169
  ### **useSWRMutation**
@@ -171,37 +171,39 @@ mutate(`/users/1`, { ...data, name: 'John Doe' });
171
171
  This hook makes it easier to handle update requests and provides necessary state
172
172
 
173
173
  ```tsx
174
- import useSWRMutation from 'swr/mutation';
174
+ import useSWRMutation from "swr/mutation";
175
175
 
176
176
  async function updateUser(url, data) {
177
- await fetch(url, {
177
+ await fetch(url, {
178
- method: 'POST',
178
+ method: "POST",
179
- body: JSON.stringify(data),
179
+ body: JSON.stringify(data),
180
- });
180
+ });
181
181
  }
182
182
 
183
183
  function Profile() {
184
- const { data, error, isMutating, trigger } = useSWRMutation(
184
+ const { data, error, isMutating, trigger } = useSWRMutation(
185
- '/api/user/update',
185
+ "/api/user/update",
186
- updateUser,
186
+ updateUser,
187
- options,
187
+ options,
188
- );
188
+ );
189
- return (
189
+ return (
190
- <button onClick={() => trigger({ name: 'John Doe' })}>
190
+ <button onClick={() => trigger({ name: "John Doe" })}>
191
- {isMutating ? 'Updating...' : 'Update User'}
191
+ {isMutating ? "Updating..." : "Update User"}
192
- </button>
192
+ </button>
193
- );
193
+ );
194
194
  }
195
195
  ```
196
196
 
197
197
  **Parameters**
198
+
198
- * `key`: a unique key string for the request
199
+ - `key`: a unique key string for the request
199
- * `fetcher(key, { arg })`: an async function for remote mutation
200
+ - `fetcher(key, { arg })`: an async function for remote mutation
200
- * `options`: an optional object to configure revalidation and optimistic updates
201
+ - `options`: an optional object to configure revalidation and optimistic updates
201
202
 
202
203
  **Returns**
204
+
203
- - `data`: data for the given key returned from the update request
205
+ - `data`: data for the given key returned from the update request
204
- - `error`: error thrown by the request
206
+ - `error`: error thrown by the request
205
- - `trigger(arg, options)`: a function to trigger a remote mutation
207
+ - `trigger(arg, options)`: a function to trigger a remote mutation
206
- - `reset`: a function to reset the state
208
+ - `reset`: a function to reset the state
207
- - `isMutating`: if there's an ongoing update request
209
+ - `isMutating`: if there's an ongoing update request
src/pages/robots.txt.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { APIRoute } from 'astro';
1
+ import type { APIRoute } from "astro";
2
2
 
3
3
  const getRobotsTxt = (sitemapURL: URL) =>
4
4
  `User-agent: *
@@ -8,6 +8,6 @@ Sitemap: ${sitemapURL.href}
8
8
  `;
9
9
 
10
10
  export const GET: APIRoute = ({ site }) => {
11
- const sitemapURL = new URL('sitemap-index.xml', site);
11
+ const sitemapURL = new URL("sitemap-index.xml", site);
12
12
  return new Response(getRobotsTxt(sitemapURL));
13
- };
13
+ };
src/utils/commit.ts CHANGED
@@ -15,22 +15,36 @@ export interface Commit {
15
15
  tags: string[];
16
16
  }
17
17
 
18
- export const collectCommits = (item: { hash: any; message: any; body: any; author_name: any; author_email: any; date: any; refs: string; }) => ({
18
+ export const collectCommits = (item: {
19
+ hash: any;
20
+ message: any;
21
+ body: any;
22
+ author_name: any;
23
+ author_email: any;
24
+ date: any;
25
+ refs: string;
26
+ }) => ({
19
27
  hash: item.hash,
20
28
  message: item.message,
21
29
  body: `${item.message}\n\n${item.body}`,
22
30
  author_name: item.author_name,
23
31
  author_email: item.author_email,
24
32
  date: item.date,
33
+ branches: item.refs
34
+ .split(",")
25
- branches: item.refs.split(",").filter((ref: string) => !ref.includes("origin")).map((ref) => ref.trim()),
35
+ .filter((ref: string) => !ref.includes("origin"))
36
+ .map((ref) => ref.trim()),
37
+ tags: item.refs
38
+ .split(",")
39
+ .filter((ref: string) => ref.includes("tag: "))
26
- tags: item.refs.split(",").filter((ref: string) => ref.includes("tag: ")).map((ref) => ref.replace("tag: ", "")),
40
+ .map((ref) => ref.replace("tag: ", "")),
27
41
  });
28
42
 
29
43
  export const getCommits = async (repoPath: string): Promise<Commit[]> => {
30
44
  const git = simpleGit(repoPath);
31
45
  const commits = await git.log(["--branches", "--tags"]);
32
46
  return commits.all.map(collectCommits);
33
- }
47
+ };
34
48
 
35
49
  export const getFiles = async (repoPath: string): Promise<FileNode[]> => {
36
50
  const git = simpleGit(repoPath);
@@ -40,23 +54,21 @@ export const getFiles = async (repoPath: string): Promise<FileNode[]> => {
40
54
  try {
41
55
  const stat = await fs.stat(`${repoPath}/${p}`);
42
56
  const ext = path.extname(p).replace(".", "");
43
- const name = p.endsWith("index.html")
44
- ? p.replace("index.html", "index_html")
57
+ const name = p.endsWith("index.html") ? p.replace("index.html", "index_html") : p;
45
- : p;
46
58
  files.push({
47
59
  name,
48
60
  ext,
49
61
  size: stat.size,
50
62
  isDirectory: stat.isDirectory(),
51
63
  absolutePath: `${repoPath}/${p}`,
52
- })
64
+ });
53
65
  } catch (e) {
54
66
  // Ignore files not found
55
67
  console.log(e);
56
68
  }
57
69
  }
58
70
  return files;
59
- }
71
+ };
60
72
 
61
73
  export const getFileHistory = async (repoPath: string, filePath: string): Promise<Commit[]> => {
62
74
  const git = simpleGit(repoPath);
@@ -64,18 +76,18 @@ export const getFileHistory = async (repoPath: string, filePath: string): Promis
64
76
  const isLarge = stat.size > 1024 * 512;
65
77
  const history = isLarge ? { all: [] } : await git.log([filePath.replace(`${repoPath}/`, "")]);
66
78
  return history.all.map(collectCommits);
67
- }
79
+ };
68
80
 
69
81
  export const generateHTMLDiff = async (repoPath: string, hash: string): Promise<string> => {
70
82
  const git = simpleGit(repoPath);
71
- let diff = '';
83
+ let diff = "";
72
84
  try {
73
85
  diff = await git.diff([`${hash}^..${hash}`]);
74
86
  } catch (e) {
75
87
  diff = await git.diff([hash]);
76
88
  }
77
89
  if (diff.length > 1024 * 512) {
78
- return '<p>Diff too large to display.</p>';
90
+ return "<p>Diff too large to display.</p>";
79
91
  }
80
92
  const diffJson = parse(diff);
81
93
  const diffHtml = html(diffJson, {
@@ -83,4 +95,4 @@ export const generateHTMLDiff = async (repoPath: string, hash: string): Promise<
83
95
  matching: "lines",
84
96
  });
85
97
  return diffHtml;
86
- }
98
+ };
src/utils/content.ts CHANGED
@@ -75,15 +75,11 @@ export const getRepos = (): RuntimeRepo[] =>
75
75
  },
76
76
  }));
77
77
 
78
- export const getRepo = (repoId?: string) =>
79
- getRepos().find((repo) => repo.id === repoId);
78
+ export const getRepo = (repoId?: string) => getRepos().find((repo) => repo.id === repoId);
80
79
 
81
80
  export const getRepoReadmeHtml = async (repo: RuntimeRepo) => {
82
81
  try {
83
- const readme = await fs.readFile(
84
- path.join(repo.data.absolutePath, "README.md"),
82
+ const readme = await fs.readFile(path.join(repo.data.absolutePath, "README.md"), "utf-8");
85
- "utf-8",
86
- );
87
83
  return await renderMarkdown(readme);
88
84
  } catch {
89
85
  return "";
@@ -105,12 +101,8 @@ export const getPosts = async (): Promise<RuntimePost[]> => {
105
101
  title: String(parsed.data.title ?? ""),
106
102
  description: String(parsed.data.description ?? ""),
107
103
  pubDate: toDate(parsed.data.pubDate),
108
- updatedDate: parsed.data.updatedDate
104
+ updatedDate: parsed.data.updatedDate ? toDate(parsed.data.updatedDate) : undefined,
109
- ? toDate(parsed.data.updatedDate)
110
- : undefined,
111
- heroImage: parsed.data.heroImage
105
+ heroImage: parsed.data.heroImage ? String(parsed.data.heroImage) : undefined,
112
- ? String(parsed.data.heroImage)
113
- : undefined,
114
106
  },
115
107
  body: parsed.content,
116
108
  html: await renderMarkdown(parsed.content),
src/utils/files.ts CHANGED
@@ -16,7 +16,7 @@ export const sortAll = (a: FileNode, b: FileNode): number => {
16
16
  if (a.isDirectory && !b.isDirectory) return -1;
17
17
  if (!a.isDirectory && b.isDirectory) return 1;
18
18
  return a.name.localeCompare(b.name);
19
- }
19
+ };
20
20
  export const sortChildren = (nodes: FileNode[]) => {
21
21
  for (const node of nodes) {
22
22
  if (node.isDirectory && node.children) {
@@ -31,15 +31,15 @@ export const buildFileTree = (files: any[]): FileNode[] => {
31
31
  const root: FileNode[] = [];
32
32
 
33
33
  for (const file of files) {
34
- const parts = file.name.split('/');
34
+ const parts = file.name.split("/");
35
35
  let currentLevel = root;
36
36
 
37
37
  for (let i = 0; i < parts.length; i++) {
38
38
  const part = parts[i];
39
39
  const isLastPart = i === parts.length - 1;
40
- const currentPath = parts.slice(0, i + 1).join('/');
40
+ const currentPath = parts.slice(0, i + 1).join("/");
41
41
 
42
- let existingNode = currentLevel.find(node => node.name === part);
42
+ let existingNode = currentLevel.find((node) => node.name === part);
43
43
 
44
44
  if (!existingNode) {
45
45
  const newNode: FileNode = {
@@ -66,7 +66,7 @@ export const buildFileTree = (files: any[]): FileNode[] => {
66
66
  }
67
67
 
68
68
  return root;
69
- }
69
+ };
70
70
 
71
71
  export const checkFileExists = async (filePath: string) => {
72
72
  try {
@@ -75,7 +75,7 @@ export const checkFileExists = async (filePath: string) => {
75
75
  } catch (error) {
76
76
  return false; // File does not exist or cannot be accessed
77
77
  }
78
- }
78
+ };
79
79
 
80
80
  export const resolveFolderIcon = (foldername: string) => {
81
81
  for (const item of folderExtensions.supported) {
@@ -84,25 +84,25 @@ export const resolveFolderIcon = (foldername: string) => {
84
84
  }
85
85
  }
86
86
  return folderExtensions.default.folder?.icon;
87
- }
87
+ };
88
88
 
89
89
  export const resolveFileIcon = (filename: string, fileExt: string) => {
90
- const baseName = filename.split('/').pop() || filename;
90
+ const baseName = filename.split("/").pop() || filename;
91
-
91
+
92
92
  // First check for exact filename matches
93
93
  for (const item of fileExtensions.supported) {
94
94
  if (item.filename && item.extensions?.includes(baseName)) {
95
95
  return item.icon;
96
96
  }
97
97
  }
98
-
98
+
99
99
  // Then check for extension matches (more specific than language extensions)
100
100
  for (const item of fileExtensions.supported) {
101
101
  if (item.extensions?.includes(fileExt)) {
102
102
  return item.icon;
103
103
  }
104
104
  }
105
-
105
+
106
106
  // Check for language-specific extensions, prioritizing exact language ID matches
107
107
  for (const item of fileExtensions.supported) {
108
108
  for (const lang of item.languages ?? []) {
@@ -114,7 +114,7 @@ export const resolveFileIcon = (filename: string, fileExt: string) => {
114
114
  }
115
115
  }
116
116
  }
117
-
117
+
118
118
  // Fallback to any language extension match
119
119
  for (const item of fileExtensions.supported) {
120
120
  for (const lang of item.languages ?? []) {
@@ -123,6 +123,6 @@ export const resolveFileIcon = (filename: string, fileExt: string) => {
123
123
  }
124
124
  }
125
125
  }
126
-
126
+
127
127
  return fileExtensions.default.file?.icon;
128
- }
128
+ };
src/utils/gitBug.ts CHANGED
@@ -112,19 +112,15 @@ const assertNotSpam = async (input: {
112
112
 
113
113
  const getR2Config = () => {
114
114
  const bucket =
115
- process.env.REPOS_R2_BUCKET ??
115
+ process.env.REPOS_R2_BUCKET ?? process.env.R2_BUCKET ?? process.env.AWS_BUCKET_NAME;
116
- process.env.R2_BUCKET ??
117
- process.env.AWS_BUCKET_NAME;
118
116
  const endpoint =
119
117
  process.env.R2_ENDPOINT ??
120
118
  process.env.AWS_ENDPOINT_URL_S3 ??
121
119
  (process.env.R2_ACCOUNT_ID
122
120
  ? `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`
123
121
  : undefined);
124
- const accessKeyId =
125
- process.env.R2_ACCESS_KEY_ID ?? process.env.AWS_ACCESS_KEY_ID;
122
+ const accessKeyId = process.env.R2_ACCESS_KEY_ID ?? process.env.AWS_ACCESS_KEY_ID;
126
- const secretAccessKey =
127
- process.env.R2_SECRET_ACCESS_KEY ?? process.env.AWS_SECRET_ACCESS_KEY;
123
+ const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY ?? process.env.AWS_SECRET_ACCESS_KEY;
128
124
 
129
125
  if (!bucket || !endpoint || !accessKeyId || !secretAccessKey) {
130
126
  throw new Error(
@@ -164,12 +160,7 @@ const getObjectJson = async <T>(key: string) => {
164
160
  const body = await object.Body?.transformToString();
165
161
  return body ? (JSON.parse(body) as T) : undefined;
166
162
  } catch (error) {
167
- if (
168
- error &&
169
- typeof error === "object" &&
163
+ if (error && typeof error === "object" && "name" in error && error.name === "NoSuchKey") {
170
- "name" in error &&
171
- error.name === "NoSuchKey"
172
- ) {
173
164
  return undefined;
174
165
  }
175
166
 
@@ -212,12 +203,8 @@ export const getGitBugIssues = async (repoId: string): Promise<GitBugIssuesResul
212
203
  }),
213
204
  );
214
205
 
215
- issues.push(
216
- ...pageIssues.filter((issue): issue is GitBugIssue => Boolean(issue)),
206
+ issues.push(...pageIssues.filter((issue): issue is GitBugIssue => Boolean(issue)));
217
- );
218
- continuationToken = page.IsTruncated
207
+ continuationToken = page.IsTruncated ? page.NextContinuationToken : undefined;
219
- ? page.NextContinuationToken
220
- : undefined;
221
208
  } while (continuationToken);
222
209
 
223
210
  return {
tests/home.spec.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { expect, test } from '@playwright/test';
1
+ import { expect, test } from "@playwright/test";
2
2
 
3
- test('index page has expected h1', async ({ page }) => {
3
+ test("index page has expected h1", async ({ page }) => {
4
- await page.goto('/');
4
+ await page.goto("/");
5
- await expect(page.getByRole('heading', { name: 'Hello!' })).toBeVisible();
5
+ await expect(page.getByRole("heading", { name: "Hello!" })).toBeVisible();
6
6
  });
tests/rss.spec.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { expect, test } from '@playwright/test';
1
+ import { expect, test } from "@playwright/test";
2
2
 
3
- test('/rss.xml is valid', async ({ page }) => {
3
+ test("/rss.xml is valid", async ({ page }) => {
4
- const response = await page.goto('/rss.xml');
4
+ const response = await page.goto("/rss.xml");
5
5
  expect(response?.status()).toBe(200);
6
6
  });