1
1
import process from "node:process" ;
2
2
import { Buffer } from "node:buffer" ;
3
+ import makeDebug from "debug" ;
3
4
import { IndiekitError } from "@indiekit/error" ;
4
5
6
+ const debug = makeDebug ( `indiekit-store:github` ) ;
7
+
5
8
const defaults = {
6
9
baseUrl : "https://api.github.com" ,
7
10
branch : "main" ,
8
11
token : process . env . GITHUB_TOKEN ,
9
12
} ;
10
13
14
+ const crudErrorMessage = ( { error, operation, filePath, branch, repo } ) => {
15
+ const summary = `Could not ${ operation } file ${ filePath } in repo ${ repo } , branch ${ branch } ` ;
16
+
17
+ const details = [
18
+ `Original error message: ${ error . message } ` ,
19
+ `Ensure the GitHub token is not expired and has the necessary permissions` ,
20
+ `You can check your tokens here: https://github.com/settings/tokens` ,
21
+ ] ;
22
+ if ( operation !== "create" ) {
23
+ details . push ( `Ensure the file exists` ) ;
24
+ }
25
+
26
+ return `${ summary } . ${ details . join ( ". " ) } ` ;
27
+ } ;
28
+
11
29
export default class GithubStore {
12
30
/**
13
31
* @param {object } [options] - Plug-in options
@@ -100,11 +118,28 @@ export default class GithubStore {
100
118
* @see {@link https://docs.github.com/en/rest/repos/contents#create-or-update-file-contents }
101
119
*/
102
120
async createFile ( filePath , content , { message } ) {
103
- const createResponse = await this . #client( filePath , "PUT" , {
104
- branch : this . options . branch ,
105
- content : Buffer . from ( content ) . toString ( "base64" ) ,
106
- message,
107
- } ) ;
121
+ const { branch, repo } = this . options ;
122
+
123
+ let createResponse ;
124
+ try {
125
+ debug ( `Try creating file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
126
+ createResponse = await this . #client( filePath , "PUT" , {
127
+ branch,
128
+ content : Buffer . from ( content ) . toString ( "base64" ) ,
129
+ message,
130
+ } ) ;
131
+ debug ( `Created file ${ filePath } ` ) ;
132
+ } catch ( error ) {
133
+ const message = crudErrorMessage ( {
134
+ error,
135
+ operation : "create" ,
136
+ filePath,
137
+ repo,
138
+ branch,
139
+ } ) ;
140
+ debug ( message ) ;
141
+ throw new Error ( message ) ;
142
+ }
108
143
109
144
const file = await createResponse . json ( ) ;
110
145
@@ -118,9 +153,24 @@ export default class GithubStore {
118
153
* @see {@link https://docs.github.com/en/rest/repos/contents#get-repository-content }
119
154
*/
120
155
async readFile ( filePath ) {
121
- const readResponse = await this . #client(
122
- `${ filePath } ?ref=${ this . options . branch } ` ,
123
- ) ;
156
+ const { branch, repo } = this . options ;
157
+
158
+ let readResponse ;
159
+ try {
160
+ debug ( `Try reading file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
161
+ readResponse = await this . #client( `${ filePath } ?ref=${ branch } ` ) ;
162
+ } catch ( error ) {
163
+ const message = crudErrorMessage ( {
164
+ error,
165
+ operation : "read" ,
166
+ filePath,
167
+ repo,
168
+ branch,
169
+ } ) ;
170
+ debug ( message ) ;
171
+ throw new Error ( message ) ;
172
+ }
173
+
124
174
const { content } = await readResponse . json ( ) ;
125
175
126
176
return Buffer . from ( content , "base64" ) . toString ( "utf8" ) ;
@@ -137,22 +187,55 @@ export default class GithubStore {
137
187
* @see {@link https://docs.github.com/en/rest/repos/contents#create-or-update-file-contents }
138
188
*/
139
189
async updateFile ( filePath , content , { message, newPath } ) {
140
- const readResponse = await this . #client(
141
- `${ filePath } ?ref=${ this . options . branch } ` ,
142
- ) ;
190
+ const { branch, repo } = this . options ;
191
+
192
+ let readResponse ;
193
+ try {
194
+ debug ( `Try reading file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
195
+ readResponse = await this . #client( `${ filePath } ?ref=${ branch } ` ) ;
196
+ } catch ( error ) {
197
+ const message = crudErrorMessage ( {
198
+ error,
199
+ operation : "read" ,
200
+ filePath,
201
+ repo,
202
+ branch,
203
+ } ) ;
204
+ debug ( message ) ;
205
+ throw new Error ( message ) ;
206
+ }
207
+
143
208
const { sha } = await readResponse . json ( ) ;
144
209
const updateFilePath = newPath || filePath ;
145
- const updateResponse = await this . #client( updateFilePath , "PUT" , {
146
- branch : this . options . branch ,
147
- content : Buffer . from ( content ) . toString ( "base64" ) ,
148
- message,
149
- sha : sha || false ,
150
- } ) ;
210
+
211
+ let updateResponse ;
212
+ try {
213
+ debug ( `Try updating file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
214
+ updateResponse = await this . #client( updateFilePath , "PUT" , {
215
+ branch,
216
+ content : Buffer . from ( content ) . toString ( "base64" ) ,
217
+ message,
218
+ sha : sha || false ,
219
+ } ) ;
220
+ debug ( `Updated file ${ filePath } ` ) ;
221
+ } catch ( error ) {
222
+ const message = crudErrorMessage ( {
223
+ error,
224
+ operation : "update" ,
225
+ filePath,
226
+ repo,
227
+ branch,
228
+ } ) ;
229
+ debug ( message ) ;
230
+ throw new Error ( message ) ;
231
+ }
151
232
152
233
const file = await updateResponse . json ( ) ;
153
234
154
235
if ( newPath ) {
236
+ debug ( `Try deleting file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
155
237
await this . deleteFile ( filePath , { message } ) ;
238
+ debug ( `Deleted file ${ filePath } ` ) ;
156
239
}
157
240
158
241
return file . content . html_url ;
@@ -167,21 +250,60 @@ export default class GithubStore {
167
250
* @see {@link https://docs.github.com/en/rest/repos/contents#delete-a-file }
168
251
*/
169
252
async deleteFile ( filePath , { message } ) {
170
- const readResponse = await this . #client(
171
- `${ filePath } ?ref=${ this . options . branch } ` ,
172
- ) ;
253
+ const repo = this . options . repo ;
254
+ const branch = this . options . branch ;
255
+
256
+ let readResponse ;
257
+ try {
258
+ debug ( `Try reading file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
259
+ readResponse = await this . #client( `${ filePath } ?ref=${ branch } ` ) ;
260
+ } catch ( error ) {
261
+ const message = crudErrorMessage ( {
262
+ error,
263
+ operation : "read" ,
264
+ filePath,
265
+ repo,
266
+ branch,
267
+ } ) ;
268
+ debug ( message ) ;
269
+ throw new Error ( message ) ;
270
+ }
271
+
173
272
const { sha } = await readResponse . json ( ) ;
174
273
175
- await this . #client( filePath , "DELETE" , {
176
- branch : this . options . branch ,
177
- message,
178
- sha,
179
- } ) ;
274
+ try {
275
+ debug ( `Try deleting file ${ filePath } in repo ${ repo } , branch ${ branch } ` ) ;
276
+ await this . #client( filePath , "DELETE" , {
277
+ branch,
278
+ message,
279
+ sha,
280
+ } ) ;
281
+ debug ( `Deleted file ${ filePath } ` ) ;
282
+ } catch ( error ) {
283
+ const message = crudErrorMessage ( {
284
+ error,
285
+ operation : "delete" ,
286
+ filePath,
287
+ repo,
288
+ branch,
289
+ } ) ;
290
+ debug ( message ) ;
291
+ throw new Error ( message ) ;
292
+ }
180
293
181
294
return true ;
182
295
}
183
296
184
297
init ( Indiekit ) {
298
+ const required_configs = [ "baseUrl" , "branch" , "repo" , "token" , "user" ] ;
299
+ for ( const required of required_configs ) {
300
+ if ( ! this . options [ required ] ) {
301
+ const message = `Could not initialize ${ this . name } : ${ required } not set. See https://www.npmjs.com/package/@indiekit/store-github for details.` ;
302
+ debug ( message ) ;
303
+ console . error ( message ) ;
304
+ throw new Error ( message ) ;
305
+ }
306
+ }
185
307
Indiekit . addStore ( this ) ;
186
308
}
187
309
}
0 commit comments