Continuing from last post, I have completed my feature that produces optimized images, create a remote repository and make a commit with the files. My PR may be updated, but I think the change would not be so big from now.
So the main challenge was how I can make one click to create a repo and make a commit. I tried to use octokit.rest
library. I'm not sure what I did wrong, but it didn't read the parameters that I passed. But GitHub API version works for me. And the post How to push files programatically to a repository using Octokit with Typescript was a huge help for me. My version is not so different from this post except that I use JavaScript
and create multiple blobs in the middle of the process.
First I create a remote repo with createRepo
function below.
const createRepo = async (octokit, username, repoName) => {
try {
await octokit.rest.repos.createForAuthenticatedUser({
name: repoName ? repoName : generateUniqueName(username),
description: 'Your repository generated using my-photohub',
private: false,
auto_init: true,
});
return true;
} catch (err) {
console.error(err);
return false;
}
};
Then, I produce optimized images with compressor.js
.
const compressImage = (file, option) => {
return new Promise((resolve, reject) => {
new Compressor(file, {
width: option.width,
quality: 0.6,
success: (result) => {
const fileName =
file.name.replace(/\.[^/.]+$/, '') +
'_' +
option.width +
'.' +
option.mimeType.substring(option.mimeType.indexOf('/') + 1);
resolve(new File([result], fileName, { type: option.mimeType }));
},
error: (err) => {
console.error(err.message);
reject(err);
},
});
});
};
export const createOptimizedImages = async (originalImages) => {
if (!originalImages) {
return;
}
let compressFilesPromises = [];
compressorOptions.forEach((option) => {
for (const image of originalImages) {
compressFilesPromises.push(compressImage(image, option));
}
});
return Promise.all(compressFilesPromises);
};
You can change image quality or pass different options. I set customized mimeType
and width
by passing option parameter here. You can find more usages from compressor.js
original documentation.
Then I need to create a blob from each file with the function below.
const createBlobForFile = async (octokit, username, repoName, files) => {
let blobDataPromises = [];
for (const file of files) {
let reader = new FileReader();
await reader.readAsDataURL(file);
const promise = new Promise((resolve, reject) => {
reader.onload = async () => {
const base64Data = reader.result;
const blobData = octokit.request(`POST /repos/${username}/${repoName}/git/blobs`, {
owner: username,
repo: repoName,
content: base64Data,
});
resolve(blobData);
};
reader.onerror = reject;
});
blobDataPromises.push(promise);
}
return Promise.all(blobDataPromises);
};
So this function returns an array of blob object in the same order from files.
NOTE: You can also use
BlobURL
asDataURL
is very long and it has a length limit.
reader.readAsArrayBuffer(file); ... const imageBuffer = reader.result; const byteArray = new Uint8Array(imageBuffer); const blob = new Blob([byteArray]); const BlobURL = URL.createObjectURL(blob);
In the blob, it doesn't have a file name that I can use as a path in next function. So I added getPathNamesFromFile()
.
const getPathNamesFromFile = (convertedFiles) => {
let pathNames = [];
for (const file of convertedFiles) {
pathNames.push(file.name);
}
return pathNames;
};
Next step is that I need to get current existing commit from the repo that I just created so that I can add a new commit as a child. Here's the function.
const createNewCommit = async (
octokit,
username,
repoName,
message,
currentTreeSha,
currentCommitSha
) =>
(
await octokit.request(`POST /repos/${username}/${repoName}/git/commits`, {
owner: username,
repo: repoName,
message: message,
tree: currentTreeSha,
parents: [currentCommitSha],
})
).data;
The function is pretty obvious as if I'm making an actual commit. Then, I need to create trees to add. You can find details of git tree object
here. Basically blob
is a file and tree
is a directory in git
. You can check your git
folder with this command.
❯ git cat-file -p main^{tree} ─╯
...
040000 tree f808c5b1a9eaa36928f49ac82b8b2ed85c53af45 .vscode
100644 blob 3ee245e88d83084868e58ba369200acc8354f9df CONTRIBUTING.md
...
100644 blob 8202e8c8292a2681114b67099e5f47c5aa0e13e4 package.json
040000 tree 2ce2a0fdb03a853cc6c855ebc46865cd20d3afaf public
040000 tree e3ef837d95be8981e826c7ad683101475f2b6528 src
...
And you would have noticed numbers in front of tree
and blob
. This number is an associated mode with the file. 100644
means a normal file. You would have figured 040000
is a directory. 100755
is an executable file and 120000
is a symbolic link.
And the last step is to set the branch to this new commit.
const setBranchToCommit = (octokit, username, repoName, branch = `main`, commitSha) =>
octokit.request(`PATCH /repos/${username}/${repoName}/git/refs/heads/${branch}`, {
owner: username,
repo: repoName,
ref: `heads/${branch}`,
sha: commitSha,
});
Conclusion
I spent many days to figure this out. But in the end, I was so happy to see my code working as intended. I was on the issue that my array of promises in createBlobForFile()
returned an empty array for many hours. I could have fixed the problem if I tried to think more rational about the behaviour and root cause. I was adding each promise inside reader.onload
. So the outer function just bypassed the code block and returned an empty array immediately. It was a lot of trials and learning. Moreover, I'm so glad that I set very fundamental of this app.
Top comments (0)