backend-manager 5.0.134 → 5.0.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* POST /admin/post - Create blog post
|
|
3
3
|
* Admin/blogger endpoint to create blog posts via GitHub
|
|
4
|
+
* Uses Git Trees API to commit all files (images + post) in a single commit
|
|
4
5
|
*/
|
|
5
6
|
const moment = require('moment');
|
|
6
7
|
const jetpack = require('fs-jetpack');
|
|
@@ -15,7 +16,6 @@ const IMAGE_PATH_SRC = `src/assets/images/blog/post-{id}/`;
|
|
|
15
16
|
const IMAGE_REGEX = /(?:!\[(.*?)\]\((.*?)\))/img;
|
|
16
17
|
|
|
17
18
|
module.exports = async ({ assistant, Manager, user, settings, analytics }) => {
|
|
18
|
-
const fetch = Manager.require('wonderful-fetch');
|
|
19
19
|
|
|
20
20
|
// Require authentication
|
|
21
21
|
if (!user.authenticated) {
|
|
@@ -92,30 +92,47 @@ module.exports = async ({ assistant, Manager, user, settings, analytics }) => {
|
|
|
92
92
|
|
|
93
93
|
assistant.log('main(): Creating post...', settings);
|
|
94
94
|
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
return assistant.respond(
|
|
95
|
+
// Download all images and collect file data
|
|
96
|
+
const imageFiles = await downloadImages(assistant, settings).catch(e => e);
|
|
97
|
+
if (imageFiles instanceof Error) {
|
|
98
|
+
return assistant.respond(imageFiles.message, { code: 400 });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Rewrite body to use @post/ prefix for extracted images
|
|
102
|
-
for (const
|
|
103
|
-
|
|
102
|
+
for (const file of imageFiles) {
|
|
103
|
+
if (file.originalUrl) {
|
|
104
|
+
settings.body = settings.body.split(file.originalUrl).join(`@post/${file.filename}`);
|
|
105
|
+
}
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
//
|
|
108
|
+
// Generate post content from template
|
|
107
109
|
const formattedContent = powertools.template(
|
|
108
110
|
POST_TEMPLATE,
|
|
109
111
|
formatClone(settings),
|
|
110
112
|
);
|
|
111
113
|
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
// Build post file entry
|
|
115
|
+
const postFilename = `${settings.path}/${settings.date}-${settings.url}.md`;
|
|
116
|
+
const allFiles = [
|
|
117
|
+
...imageFiles.map(img => ({
|
|
118
|
+
path: img.githubPath,
|
|
119
|
+
content: img.base64,
|
|
120
|
+
encoding: 'base64',
|
|
121
|
+
})),
|
|
122
|
+
{
|
|
123
|
+
path: postFilename,
|
|
124
|
+
content: Buffer.from(formattedContent).toString('base64'),
|
|
125
|
+
encoding: 'base64',
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
// Commit all files in a single commit
|
|
130
|
+
const commitResult = await commitAll(assistant, octokit, settings, allFiles).catch(e => e);
|
|
131
|
+
if (commitResult instanceof Error) {
|
|
132
|
+
return assistant.respond(commitResult.message, { code: 500 });
|
|
116
133
|
}
|
|
117
134
|
|
|
118
|
-
assistant.log('main():
|
|
135
|
+
assistant.log('main(): commitAll', commitResult);
|
|
119
136
|
|
|
120
137
|
// Track analytics
|
|
121
138
|
analytics.event('admin/post', { action: 'create' });
|
|
@@ -123,9 +140,10 @@ module.exports = async ({ assistant, Manager, user, settings, analytics }) => {
|
|
|
123
140
|
return assistant.respond(settings);
|
|
124
141
|
};
|
|
125
142
|
|
|
126
|
-
// Helper:
|
|
127
|
-
async function
|
|
128
|
-
const
|
|
143
|
+
// Helper: Download all images and return file data (no GitHub uploads)
|
|
144
|
+
async function downloadImages(assistant, settings) {
|
|
145
|
+
const files = [];
|
|
146
|
+
const assetsPath = powertools.template(IMAGE_PATH_SRC, settings);
|
|
129
147
|
|
|
130
148
|
const matches = settings.body.matchAll(IMAGE_REGEX);
|
|
131
149
|
const images = Array.from(matches).map(match => ({
|
|
@@ -141,10 +159,10 @@ async function extractImages(assistant, octokit, settings) {
|
|
|
141
159
|
header: true,
|
|
142
160
|
});
|
|
143
161
|
|
|
144
|
-
assistant.log('
|
|
162
|
+
assistant.log('downloadImages(): images', images);
|
|
145
163
|
|
|
146
164
|
if (!images.length) {
|
|
147
|
-
return
|
|
165
|
+
return files;
|
|
148
166
|
}
|
|
149
167
|
|
|
150
168
|
for (let index = 0; index < images.length; index++) {
|
|
@@ -153,38 +171,29 @@ async function extractImages(assistant, octokit, settings) {
|
|
|
153
171
|
// Download image
|
|
154
172
|
const download = await downloadImage(assistant, image.src, image.alt).catch(e => e);
|
|
155
173
|
|
|
156
|
-
assistant.log('
|
|
174
|
+
assistant.log('downloadImages(): download', download);
|
|
157
175
|
|
|
158
176
|
if (download instanceof Error) {
|
|
159
177
|
if (image.header) {
|
|
160
178
|
throw download;
|
|
161
179
|
} else {
|
|
162
|
-
assistant.warn('
|
|
180
|
+
assistant.warn('downloadImages(): Skipping NON-HEADER image download due to error', download);
|
|
163
181
|
continue;
|
|
164
182
|
}
|
|
165
183
|
}
|
|
166
184
|
|
|
167
|
-
//
|
|
168
|
-
const
|
|
185
|
+
// Read file content as base64
|
|
186
|
+
const base64 = jetpack.read(download.path, 'buffer').toString('base64');
|
|
169
187
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
assistant.warn('extractImages(): Skipping NON-HEADER image upload due to error', upload);
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Track successfully uploaded non-header images for body rewriting
|
|
182
|
-
if (!image.header) {
|
|
183
|
-
urlMap.push({ originalUrl: image.src, localFilename: download.filename });
|
|
184
|
-
}
|
|
188
|
+
files.push({
|
|
189
|
+
githubPath: `${assetsPath}${download.filename}`,
|
|
190
|
+
filename: download.filename,
|
|
191
|
+
base64: base64,
|
|
192
|
+
originalUrl: image.header ? null : image.src,
|
|
193
|
+
});
|
|
185
194
|
}
|
|
186
195
|
|
|
187
|
-
return
|
|
196
|
+
return files;
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
// Helper: Download image
|
|
@@ -211,79 +220,95 @@ async function downloadImage(assistant, src, alt) {
|
|
|
211
220
|
return result;
|
|
212
221
|
}
|
|
213
222
|
|
|
214
|
-
// Helper:
|
|
215
|
-
async function
|
|
216
|
-
const filepath = image.path;
|
|
217
|
-
const filename = image.filename;
|
|
218
|
-
const assetsPath = powertools.template(IMAGE_PATH_SRC, settings);
|
|
223
|
+
// Helper: Commit all files (images + post) in a single commit using Git Trees API
|
|
224
|
+
async function commitAll(assistant, octokit, settings, files) {
|
|
219
225
|
const owner = settings.githubUser;
|
|
220
226
|
const repo = settings.githubRepo;
|
|
221
227
|
|
|
222
|
-
assistant.log('
|
|
223
|
-
assistant.log('uploadImage(): path', `${assetsPath}${filename}`);
|
|
228
|
+
assistant.log('commitAll(): Committing', files.length, 'files');
|
|
224
229
|
|
|
225
|
-
// Get
|
|
226
|
-
const
|
|
230
|
+
// Get the latest commit SHA on the default branch
|
|
231
|
+
const refResult = await octokit.rest.git.getRef({
|
|
227
232
|
owner: owner,
|
|
228
233
|
repo: repo,
|
|
229
|
-
|
|
230
|
-
}).catch(
|
|
234
|
+
ref: 'heads/master',
|
|
235
|
+
}).catch(() => {
|
|
236
|
+
// Try 'main' if 'master' fails
|
|
237
|
+
return octokit.rest.git.getRef({
|
|
238
|
+
owner: owner,
|
|
239
|
+
repo: repo,
|
|
240
|
+
ref: 'heads/main',
|
|
241
|
+
});
|
|
242
|
+
});
|
|
231
243
|
|
|
232
|
-
|
|
244
|
+
const latestCommitSha = refResult.data.object.sha;
|
|
245
|
+
const branch = refResult.data.ref;
|
|
233
246
|
|
|
234
|
-
|
|
235
|
-
throw existing;
|
|
236
|
-
}
|
|
247
|
+
assistant.log('commitAll(): Latest commit', latestCommitSha, 'on', branch);
|
|
237
248
|
|
|
238
|
-
//
|
|
239
|
-
const
|
|
249
|
+
// Get the tree SHA of the latest commit
|
|
250
|
+
const commitResult = await octokit.rest.git.getCommit({
|
|
240
251
|
owner: owner,
|
|
241
252
|
repo: repo,
|
|
242
|
-
|
|
243
|
-
sha: existing?.data?.sha || undefined,
|
|
244
|
-
message: `📦 admin/post:upload-image ${filename}`,
|
|
245
|
-
content: jetpack.read(filepath, 'buffer').toString('base64'),
|
|
253
|
+
commit_sha: latestCommitSha,
|
|
246
254
|
});
|
|
247
255
|
|
|
248
|
-
|
|
256
|
+
const baseTreeSha = commitResult.data.tree.sha;
|
|
249
257
|
|
|
250
|
-
|
|
251
|
-
|
|
258
|
+
// Create blobs for each file
|
|
259
|
+
const treeItems = [];
|
|
252
260
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
261
|
+
for (const file of files) {
|
|
262
|
+
const blob = await octokit.rest.git.createBlob({
|
|
263
|
+
owner: owner,
|
|
264
|
+
repo: repo,
|
|
265
|
+
content: file.content,
|
|
266
|
+
encoding: file.encoding,
|
|
267
|
+
});
|
|
258
268
|
|
|
259
|
-
|
|
269
|
+
assistant.log('commitAll(): Created blob for', file.path, blob.data.sha);
|
|
260
270
|
|
|
261
|
-
|
|
262
|
-
|
|
271
|
+
treeItems.push({
|
|
272
|
+
path: file.path,
|
|
273
|
+
mode: '100644',
|
|
274
|
+
type: 'blob',
|
|
275
|
+
sha: blob.data.sha,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Create a new tree with all files
|
|
280
|
+
const newTree = await octokit.rest.git.createTree({
|
|
263
281
|
owner: owner,
|
|
264
282
|
repo: repo,
|
|
265
|
-
|
|
266
|
-
|
|
283
|
+
base_tree: baseTreeSha,
|
|
284
|
+
tree: treeItems,
|
|
285
|
+
});
|
|
267
286
|
|
|
268
|
-
assistant.log('
|
|
287
|
+
assistant.log('commitAll(): Created tree', newTree.data.sha);
|
|
269
288
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
289
|
+
// Create the commit
|
|
290
|
+
const postPath = files[files.length - 1].path;
|
|
291
|
+
const newCommit = await octokit.rest.git.createCommit({
|
|
292
|
+
owner: owner,
|
|
293
|
+
repo: repo,
|
|
294
|
+
message: `📦 admin/post:create ${postPath}`,
|
|
295
|
+
tree: newTree.data.sha,
|
|
296
|
+
parents: [latestCommitSha],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
assistant.log('commitAll(): Created commit', newCommit.data.sha);
|
|
273
300
|
|
|
274
|
-
//
|
|
275
|
-
const
|
|
301
|
+
// Update the branch ref to point to the new commit
|
|
302
|
+
const updateResult = await octokit.rest.git.updateRef({
|
|
276
303
|
owner: owner,
|
|
277
304
|
repo: repo,
|
|
278
|
-
|
|
279
|
-
sha:
|
|
280
|
-
message: `📦 admin/post:upload-post ${filename}`,
|
|
281
|
-
content: Buffer.from(content).toString('base64'),
|
|
305
|
+
ref: branch.replace('refs/', ''),
|
|
306
|
+
sha: newCommit.data.sha,
|
|
282
307
|
});
|
|
283
308
|
|
|
284
|
-
assistant.log('
|
|
309
|
+
assistant.log('commitAll(): Updated ref', updateResult.data.object.sha);
|
|
285
310
|
|
|
286
|
-
return
|
|
311
|
+
return updateResult;
|
|
287
312
|
}
|
|
288
313
|
|
|
289
314
|
// Helper: Format clone for templating
|
|
@@ -100,5 +100,8 @@ module.exports = async ({ assistant, Manager, settings, analytics }) => {
|
|
|
100
100
|
id: parsed.post.id,
|
|
101
101
|
tags: parsed.post.tags,
|
|
102
102
|
categories: parsed.post.categories,
|
|
103
|
+
|
|
104
|
+
// Derived
|
|
105
|
+
headerImageURL: `${url.origin}/assets/images/blog/post-${parsed.post.id}/${filename}.jpg`,
|
|
103
106
|
});
|
|
104
107
|
};
|