website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
2f32cac
— pyrossh
2026-07-07T13:48:50+05:30
feat: add content/ directory and full deploy script with R2 sync
- content/eyecandy-golang-error-reporting.md +89 -0
- content/gopibot-to-the-rescue.md +134 -0
- content/react-powertools-swr.md +209 -0
- package.json +1 -0
- scripts/deploy-all.sh +23 -1
content/eyecandy-golang-error-reporting.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Eyecandy golang error reporting
|
|
3
|
+
description: Better error message logging in golang
|
|
4
|
+
pubDate: 2016-09-17
|
|
5
|
+
tags:
|
|
6
|
+
- golang
|
|
7
|
+
- error
|
|
8
|
+
- formatting
|
|
9
|
+
published: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
We at playlyfe wanted to get an email report as soon as an error occurred on our production servers. Since golang does not have
|
|
13
|
+
stack traces with its inbuilt error mechanism we had to find a quick and simple solution which wouldn’t require too much refactoring
|
|
14
|
+
of our existing codebase. So this is how we went about accomplishing this task. First we decided to wrap our errors so that we can get the runtime stack whenever an error occurs.
|
|
15
|
+
|
|
16
|
+
We first started using this library https://github.com/go-errors/errors
|
|
17
|
+
but soon found out that it wasn’t exactly suited for our use case. So we created this minimalistic and easy approach to wrap all our
|
|
18
|
+
existing errors. First we decided to wrap our errors so that we can get the runtime stack whenever an error occurs.
|
|
19
|
+
|
|
20
|
+
```go
|
|
21
|
+
package utils
|
|
22
|
+
|
|
23
|
+
import (
|
|
24
|
+
"bytes"
|
|
25
|
+
"database/sql"
|
|
26
|
+
"runtime"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
type WrappedError struct {
|
|
30
|
+
Err error
|
|
31
|
+
StackTrace string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func(e * WrappedError) Error() string {
|
|
35
|
+
return e.Err.Error()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func E(e error) error {
|
|
39
|
+
switch e.(type) {
|
|
40
|
+
case *WrappedError:
|
|
41
|
+
return e
|
|
42
|
+
case nil:
|
|
43
|
+
return nil
|
|
44
|
+
default:
|
|
45
|
+
stackTrace := make([]byte , 1 << 16)
|
|
46
|
+
runtime.Stack(stackTrace, false)
|
|
47
|
+
buffer := &bytes.Buffer{}
|
|
48
|
+
for _ , a := range stackTrace {
|
|
49
|
+
if a != 0 {
|
|
50
|
+
buffer.WriteByte(a)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return &WrappedError {
|
|
54
|
+
Err: e,
|
|
55
|
+
StackTrace: buffer.String(),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
But then just sending the error stack in plain format to our emails wasn’t going to be nice to read at all. We needed more
|
|
62
|
+
information like context and session information and things like that. On top of that I also wanted to make our stack traces
|
|
63
|
+
prettier so that it would easier to figure out where the error started from.
|
|
64
|
+
|
|
65
|
+
So to parse the error stack trace I found this cool library which does that for and on top of that it also themes it very well
|
|
66
|
+
https://github.com/maruel/panicparse
|
|
67
|
+
|
|
68
|
+
But then it didn’t properly expose an API to do it properly and after a few dabblings here,
|
|
69
|
+
https://github.com/maruel/panicparse/issues/8
|
|
70
|
+
with the developer and +1’s we got a proper api which I could use.
|
|
71
|
+
And now I haz got a prettier stack traces like this,
|
|
72
|
+
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
So great I got ANSI coloring setup and the errors look great in our console but what about our
|
|
76
|
+
mails. Of course this wasn’t going to work since emails primarily render text and HTML only, and
|
|
77
|
+
ANSI color codes was going to make our messages a mess.
|
|
78
|
+
|
|
79
|
+
So then I went about digging github for an ANSI terminal codes to HTML converter so that it would
|
|
80
|
+
look exactly like this in my mail. And then I found this cool go library which does that,
|
|
81
|
+
https://github.com/buildkite/terminal
|
|
82
|
+
|
|
83
|
+
Now all emails require inline CSS or else they wouldn’t work so then I had to find out a way to do that too.
|
|
84
|
+
And this was it,
|
|
85
|
+
https://github.com/aymerick/douceur
|
|
86
|
+
|
|
87
|
+
Finally after messing around with so many libraries I got around to getting it to work and this is how it looks in my email,
|
|
88
|
+
|
|
89
|
+

|
content/gopibot-to-the-rescue.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Gopibot to the rescue
|
|
3
|
+
description: A slackbot for deploying your applications (chatops)
|
|
4
|
+
pubDate: 2017-04-19
|
|
5
|
+
tags:
|
|
6
|
+
- nodejs
|
|
7
|
+
- slack
|
|
8
|
+
- bot
|
|
9
|
+
- chatops
|
|
10
|
+
published: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
I was one of the developers who had access to our QA and Prod servers and the other person was the Head of Engineering and he is generally a busy guy.
|
|
14
|
+
So whenever there is a change that needs to be deployed everyone comes to me and tells me to deploy their microservice/frontend to the QA and blatantly
|
|
15
|
+
interrupts my awesome coding cycle.
|
|
16
|
+
|
|
17
|
+
Alright then, I break off from my flow, ssh into the server and start running the deploy command. And all of you jsdev wannabes who have worked with react and webpack will know the horrors about deploying frontend code right. It takes forever so I have to wait there looking at the console along with the dev who wanted me to deploy it (lets call him @kokill for now). So @kokill and I patiently wait for the webpack build to finish. 1m , 2m, 3m and WTH 15m. And then its built and the new frontend is deployed to QA. YES! Now I can continue with my work. But wait then some other dev comes likes call him (@D-Ne0) and he asks to deploy something else and again the same process of ssh’ing the server and another wait. This got repetitive and irritating.
|
|
18
|
+
|
|
19
|
+
Then I started searching for solutions to the problem and looked high and low and thought that CI/CD is the only thing that can solve this problem. But then I saw something new called ChatOps where developers have chatbots to talk to automate this manual work. Just like we have bots these days to help you out in your work like getting your laundry, grocery and making orders.
|
|
20
|
+
|
|
21
|
+
So I decided to take a shot at this in my free time. And it seems it was simpler than I thought and decided to use Slack our primary team communication platform. We used it daily for everything and I thought why not have a specific channel just where the bot resides and people could talk to the bot.
|
|
22
|
+
|
|
23
|
+
Since we are typically a nodejs shop I decided to find a way to send messages to a slack bot. And slack has this really great sdk for nodejs.
|
|
24
|
+
https://github.com/slackapi/node-slack-sdk
|
|
25
|
+
First I went and created the bot in my slack team settings. And then wrote a script which would allow it to read messages from the channel it was added.
|
|
26
|
+
|
|
27
|
+
Here is the simple script,
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const RtmClient = require("@slack/client").RtmClient;
|
|
31
|
+
const RTM_EVENTS = require("@slack/client").RTM_EVENTS;
|
|
32
|
+
const CLIENT_EVENTS = require("@slack/client").CLIENT_EVENTS;
|
|
33
|
+
const bot_token = process.env.SLACK_BOT_TOKEN;
|
|
34
|
+
|
|
35
|
+
const rtm = new RtmClient(bot_token);
|
|
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",
|
|
38
|
+
};
|
|
39
|
+
let deploymentInProgress = false;
|
|
40
|
+
let counter = 0;
|
|
41
|
+
|
|
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
|
+
}
|
|
114
|
+
});
|
|
115
|
+
console.log("Starting deploybot");
|
|
116
|
+
rtm.start();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
So what the bot does is when someone mentions the bot with a command to run. It first checks if the command is defined in our COMMANDS map and then if
|
|
120
|
+
it is, it executes the corresponding shell command for it on our QA server and then gives back progress/error/finished messages back to the channel so
|
|
121
|
+
that everyone will be notified that someone had done a deployment. This is how it looks like,
|
|
122
|
+
|
|
123
|
+
Anyways to just have a boring bot that just runs boring commands was kinda boring. I thought of spicing up the bot interaction by making it say weird
|
|
124
|
+
things if you keep giving it invalid commands. Making it more of a life like bot.
|
|
125
|
+
|
|
126
|
+
Initially the bot was called deploybot and had a rocket icon but then there was our QA/Bug creator/Hell Raiser/Injoker in our team so I thought creating
|
|
127
|
+
a mini him would be better and give the bot a real person’s personality and it worked and people kind a started talking to bot some random stuff and
|
|
128
|
+
all.
|
|
129
|
+
|
|
130
|
+

|
|
131
|
+
|
|
132
|
+
Further on we can maybe introduce natural language processing and deep learning to make the bot learn from our messages and not just take a single
|
|
133
|
+
command. Like instead of me saying @gopibot cfm I can say @gopibot please deploy our cashflow server or please revert the deployment to the previous
|
|
134
|
+
version and things like that.
|
content/react-powertools-swr.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "React Powertools: SWR"
|
|
3
|
+
description: A react library that makes it easier to fetch data
|
|
4
|
+
pubDate: 2024-08-16
|
|
5
|
+
tags:
|
|
6
|
+
- react
|
|
7
|
+
- frontend
|
|
8
|
+
- hooks
|
|
9
|
+
- swr
|
|
10
|
+
- fetch
|
|
11
|
+
published: true
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
The **SWR** library provides hooks which help in facilitating fetching and revalidating data so that the UI will be always fast and reactive. It uses the stale-while-revalidate, a HTTP cache invalidation strategy, to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.
|
|
15
|
+
|
|
16
|
+
You can install it using this command: `npm i swr`
|
|
17
|
+
|
|
18
|
+
### useSWR
|
|
19
|
+
|
|
20
|
+
This hook exposes few options to customise the fetching/revalidation logic,
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR(key, fetcher, options);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Parameters**
|
|
27
|
+
|
|
28
|
+
- `key`: a unique key string for the request
|
|
29
|
+
- `fetcher`: a Promise-returning function to fetch your data
|
|
30
|
+
- `options`: an object of options for this SWR hook
|
|
31
|
+
|
|
32
|
+
**Return values**
|
|
33
|
+
|
|
34
|
+
- `data`: data for the given key resolved by `fetcher`
|
|
35
|
+
- `error`: error thrown by `fetcher`
|
|
36
|
+
- `isLoading`: if there's an ongoing request and no "loaded data" or state data
|
|
37
|
+
- `isValidating`: if there's a revalidation request happening
|
|
38
|
+
- `mutate(data?, options?)`: function to mutate the cached data
|
|
39
|
+
|
|
40
|
+
> Before SWR
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { useState, useEffect } from "react";
|
|
44
|
+
|
|
45
|
+
const useUser = (id: string) => {
|
|
46
|
+
const [user, setUser] = useState(null);
|
|
47
|
+
const [loading, setLoading] = useState(false);
|
|
48
|
+
const [error, setError] = useState(null);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
fetch(`/users/${id}`)
|
|
54
|
+
.then((res) => res.json())
|
|
55
|
+
.then((data) => {
|
|
56
|
+
setUser(data);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
})
|
|
59
|
+
.catch((err) => {
|
|
60
|
+
setError(err);
|
|
61
|
+
setLoading(false);
|
|
62
|
+
});
|
|
63
|
+
}, [id]);
|
|
64
|
+
return {
|
|
65
|
+
user,
|
|
66
|
+
loading,
|
|
67
|
+
error,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> After SWR
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import useSWR from "swr";
|
|
76
|
+
|
|
77
|
+
const fetcher = (...args) => fetch(...args).then((res) => res.json());
|
|
78
|
+
|
|
79
|
+
const useUser = (id: string) => {
|
|
80
|
+
const { data, error, isLoading } = useSWR(`/users/${id}`, fetcher);
|
|
81
|
+
return {
|
|
82
|
+
user: data,
|
|
83
|
+
isLoading,
|
|
84
|
+
error: error,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Configuration
|
|
90
|
+
|
|
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
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { SWRConfig } from "swr";
|
|
95
|
+
|
|
96
|
+
const fetcher = (...args) => fetch(...args).then((res) => res.json());
|
|
97
|
+
|
|
98
|
+
function App() {
|
|
99
|
+
return (
|
|
100
|
+
<SWRConfig value={{ fetcher: fetcher, provider: () => new Map() }}>
|
|
101
|
+
<Page />
|
|
102
|
+
</SWRConfig>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Automatic Revalidation
|
|
108
|
+
|
|
109
|
+
You can use the options parameter in the API to configure automatic revalidation of your components based on different criteria. This can be done at a global level using SWRConfig provider or at a local hook level using the options parameter.
|
|
110
|
+
|
|
111
|
+
- `revalidateIfStale`: automatically revalidate even if there is stale data
|
|
112
|
+
- `revalidateOnMount`: enable or disable automatic revalidation when component is mounted
|
|
113
|
+
- `revalidateOnFocus`: automatically revalidate when window gets focused
|
|
114
|
+
- `revalidateOnReconnect`: automatically revalidate when the browser regains a network connection
|
|
115
|
+
- `refreshInterval`: automatically revalidate every interval in milliseconds
|
|
116
|
+
|
|
117
|
+
There are many more options apart from these.
|
|
118
|
+
|
|
119
|
+
### Manual Revalidation
|
|
120
|
+
|
|
121
|
+
There are 2 ways to trigger a revalidation request manually,
|
|
122
|
+
|
|
123
|
+
**1.** You can use the **mutate** function returned by the **useSWR** hook to trigger a revalidation of the data
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import useSWR from "swr";
|
|
127
|
+
|
|
128
|
+
const Profile = () => {
|
|
129
|
+
const { data, error, isLoading, mutate } = useSWR(`/users/1`);
|
|
130
|
+
if (error) return <div>failed to load</div>;
|
|
131
|
+
if (isLoading) return <div className="text">loading...</div>;
|
|
132
|
+
return (
|
|
133
|
+
<div>
|
|
134
|
+
<div>{JSON.stringify(data, null, 2)}</div>
|
|
135
|
+
<button onClick={() => mutate()}>Update User</button>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
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
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
mutate({ ...data, name: "John Doe" });
|
|
145
|
+
```
|
|
146
|
+
|
|
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
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { useSWRConfig } from "swr";
|
|
151
|
+
// or import { mutate } from "swr"
|
|
152
|
+
|
|
153
|
+
const UpdateButton = () => {
|
|
154
|
+
const { mutate } = useSWRConfig();
|
|
155
|
+
return (
|
|
156
|
+
<div>
|
|
157
|
+
<button onClick={() => mutate(`/users/1`)}>Update User</button>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Here as well you can do optimistic updates similarly,
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
mutate(`/users/1`, { ...data, name: "John Doe" });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### **useSWRMutation**
|
|
170
|
+
|
|
171
|
+
This hook makes it easier to handle update requests and provides necessary state
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import useSWRMutation from "swr/mutation";
|
|
175
|
+
|
|
176
|
+
async function updateUser(url, data) {
|
|
177
|
+
await fetch(url, {
|
|
178
|
+
method: "POST",
|
|
179
|
+
body: JSON.stringify(data),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function Profile() {
|
|
184
|
+
const { data, error, isMutating, trigger } = useSWRMutation(
|
|
185
|
+
"/api/user/update",
|
|
186
|
+
updateUser,
|
|
187
|
+
options,
|
|
188
|
+
);
|
|
189
|
+
return (
|
|
190
|
+
<button onClick={() => trigger({ name: "John Doe" })}>
|
|
191
|
+
{isMutating ? "Updating..." : "Update User"}
|
|
192
|
+
</button>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Parameters**
|
|
198
|
+
|
|
199
|
+
- `key`: a unique key string for the request
|
|
200
|
+
- `fetcher(key, { arg })`: an async function for remote mutation
|
|
201
|
+
- `options`: an optional object to configure revalidation and optimistic updates
|
|
202
|
+
|
|
203
|
+
**Returns**
|
|
204
|
+
|
|
205
|
+
- `data`: data for the given key returned from the update request
|
|
206
|
+
- `error`: error thrown by the request
|
|
207
|
+
- `trigger(arg, options)`: a function to trigger a remote mutation
|
|
208
|
+
- `reset`: a function to reset the state
|
|
209
|
+
- `isMutating`: if there's an ongoing update request
|
package.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"dev:cv": "wrangler dev --config packages/workers/cv/wrangler.jsonc --port 8783",
|
|
10
10
|
"typecheck": "tsc --noEmit",
|
|
11
11
|
"deploy:all": "bash scripts/deploy-all.sh",
|
|
12
|
+
"deploy": "bash scripts/deploy-all.sh",
|
|
12
13
|
"deploy:worker": "# Usage: wrangler deploy --config packages/workers/<name>/wrangler.jsonc"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
scripts/deploy-all.sh
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
echo "=== Step 1: Upload blog content to R2 ==="
|
|
5
|
+
for file in content/*.md content/**/*.md 2>/dev/null; do
|
|
6
|
+
if [ -f "$file" ]; then
|
|
7
|
+
rclone copy "$file" r2:pyrossh-repos-prd/content/
|
|
8
|
+
echo " Uploaded: $file"
|
|
9
|
+
fi
|
|
10
|
+
done
|
|
11
|
+
|
|
12
|
+
echo ""
|
|
13
|
+
echo "=== Step 2: Upload assets to R2 ==="
|
|
14
|
+
rclone sync assets/ r2:pyrossh-repos-prd/assets/ --progress
|
|
15
|
+
echo " Assets synced."
|
|
16
|
+
|
|
17
|
+
echo ""
|
|
18
|
+
echo "=== Step 3: Sync git repos to R2 ==="
|
|
19
|
+
if [ -d repos ]; then
|
|
20
|
+
rclone sync -P repos r2:pyrossh-repos-prd
|
|
21
|
+
echo " Repos synced."
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo ""
|
|
25
|
+
echo "=== Step 4: Deploy Workers ==="
|
|
4
26
|
find packages/workers -name 'wrangler.jsonc' -not -path '*/dev-router/*' -not -path '*/server-error/*' | while read -r config; do
|
|
5
27
|
name=$(basename "$(dirname "$config")")
|
|
6
28
|
dir=$(dirname "$config")
|
|
7
|
-
echo "
|
|
29
|
+
echo " Deploying: $name ($config)"
|
|
8
30
|
wrangler deploy --config "$config"
|
|
9
31
|
echo ""
|
|
10
32
|
done
|