JavaScript >> Javascript 文檔 >  >> Node.js

如何通過 Node.js 克隆和同步 Github 存儲庫

如何在 Node.js 中通過 child_process.execSync() 使用 git clone 命令克隆一個 Github repo 並以編程方式同步最新的更改。

開始使用

因為我們為本教程編寫的代碼是“獨立的”(意味著它不是更大的應用程序或項目的一部分),所以我們將從頭開始創建一個 Node.js 項目。如果您的計算機上尚未安裝 Node.js,請先閱讀本教程,然後再返回此處。

在計算機上安裝 Node.js 後,從計算機上的項目文件夾(例如,~/projects ),為我們的工作創建一個新文件夾:

終端

mkdir clone

接下來,cd 進入該目錄並創建一個 index.js 文件(這是我們編寫教程代碼的地方):

終端

cd clone && touch index.js

接下來,我們要安裝兩個依賴,dotenvexpress

終端

npm i dotenv express

第一個將使我們能夠訪問 dotenv 幫助我們在 Node.js process.env 上設置環境變量的包 對象和第二個對象 Express,將用於啟動演示服務器。

最後一步:在 package.json 為您創建的文件,請確保添加字段 "type": "module" 作為財產。這將啟用 ESModules 支持並允許我們使用 import 如下代碼所示。

有了這些,我們就可以開始了。

從 Github 獲取個人訪問令牌

在深入研究代碼之前,我們想從 Github 獲取個人訪問令牌。這將允許我們克隆公共 使用我們將在下面學習的模式的私有存儲庫。

如果您還沒有 Github 帳戶,可以通過此鏈接註冊。如果你 擁有一個帳戶,確保您已登錄,然後點擊導航右上角的頭像,然後從彈出的菜單中選擇靠近菜單底部的“設置”選項。

在下一頁的左側導航中,靠近底部,選擇“開發人員設置”選項。在下一頁的左側導航中,選擇“個人訪問令牌”選項。最後,在結果頁面中,單擊“生成新令牌”按鈕。

在下一頁的“備註”字段中,為令牌指定一個與您正在構建的應用程序相關的名稱(例如,“clone repo tutorial”或“repo cloner”)。

對於“到期”,設置您認為合適的任何值。 如果您只是為了好玩而實施本教程,明智的做法是將其設置為盡可能低的值 .

在“選擇範圍”下,選中“回購”旁邊的框以選擇所有與回購相關的範圍。這些“範圍”告訴 Github 你在使用這個令牌時可以訪問什麼。本教程只需要“repo”,但您可以隨意自定義令牌的範圍以滿足您應用的需求。

最後,在屏幕底部,點擊綠色的“生成令牌”按鈕。

注意 :注意這裡。生成令牌後,它將臨時顯示在淺綠色框中,旁邊有一個複制按鈕。 Github 不會再向您顯示此令牌 .建議您將其複制並使用“Github Personal Access Token ”之類的名稱將其存儲在密碼管理器中,其中 <note> 應替換為您在上一頁的“備註”字段中鍵入的名稱。

安全存儲令牌後,我們就可以開始執行代碼了。

設置 .env 文件

之前,我們安裝了一個名為 dotenv 的包 .該軟件包旨在幫助您將環境變量加載到 process.env Node.js 中的對象。為此,dotenv 要求您提供文件 .env 在項目的根目錄。使用我們剛剛在 Github 上生成的個人訪問令牌,我們要創建這個 .env 在我們項目的根目錄下添加文件並添加以下內容:

.env

PERSONAL_ACCESS_TOKEN="<Paste Your Token Here>"

在這個文件中,我們要添加一行 PERSONAL_ACCESS_TOKEN="" ,在雙引號中粘貼我們從 Github 獲得的令牌。接下來,我們要打開index.js 在我們項目的根目錄下添加文件並添加以下內容:

/index.js

import 'dotenv/config';

注意 :這必須在我們文件的最頂部。當這段代碼運行時,它會調用 config() dotenv 中的函數 將定位 .env 的包 我們剛剛創建的文件並將其內容加載到 process.env .一旦完成,我們可以期望有一個類似 process.env.PERSONAL_ACCESS_TOKEN 的值 在我們的應用程序中可用。

現在就是這樣。我們稍後會使用這個值。接下來,還是在index.js 文件,我們要為 Express.js 服務器設置骨架。

設置 Express 服務器和路由

為了觸發 repo 的克隆,現在,我們想要設置一個 Express.js 服務器,其中包含我們可以在瀏覽器中訪問的路由,指定 Github 用戶名、repo 和(可選)我們要克隆的分支名稱.

/index.js

import 'dotenv/config';
import express from "express";

const app = express();

app.get('/repos/clone/:username/:repo', (req, res) => {
  // We'll handle the clone here...
});

app.listen(3000, () => {
  console.log('App running at http://localhost:3000');
});

在我們的 import 'dotenv/config'; 正下方 行,接下來,我們要導入 express 來自 express 我們之前安裝的軟件包。在此之下,我們想通過調用導出的 express() 創建一個 Express 服務器實例 函數並將生成的實例存儲在變量 app 中 .

app 代表我們的 Express 服務器實例。在它上面,我們要調用兩個方法:.get().listen() . .get() 方法允許我們定義一個路由,該路由指定一個 URL 模式以及當對我們服務器的請求的 URL 匹配時要調用的處理函數 那種模式。

在這裡,我們調用 app.get() 將該 URL 模式作為字符串 /repos/clone/:username/:repo 傳遞 , 其中 :username:repo 是所謂的路由參數。這些是我們 URL 中的“變量”,允許我們重用相同的 URL 模式,同時期待不同的輸入。

例如,這條路線可以作為 /repos/clone/cheatcode/joystick 訪問 或 /repos/clone/motdotla/dotenv 甚至是 /repos/clone/microsoft/vscode .在最後一個示例中,microsoft 將被識別為 usernamevscode 將被識別為 repo .

在我們編寫代碼之前,在分配給 app.get() 的第二個參數的處理函數中克隆我們的 repo ,在我們文件的底部,我們要確保啟動我們的 Express.js 服務器,並為其提供一個運行端口號。為此,我們調用 app.listen() ,傳遞我們要用作第一個參數的端口號。作為第二個參數,我們傳遞一個回調函數在服務器啟動後觸發(我們添加一個 console.log() 在我們的終端中向我們發送啟動信號)。

/index.js

import 'dotenv/config';
import express from "express";
import fs from 'fs';
import cloneAndPullRepo from './cloneAndPullRepo.js';

const app = express();

app.get('/repos/clone/:username/:repo', (req, res) => {
  const username = req?.params?.username;
  const repo = req?.params?.repo;
  const repoPath = `${username}/${repo}`;
  const repoExists = fs.existsSync(`repos/${repoPath}`);
  const confirmation = repoExists ? `Pulling ${repoPath}...` : `Cloning ${repoPath}...`;

  cloneAndPullRepo(repoExists, username, repo, req?.query?.branch);
  
  res.status(200).send(confirmation);
});

app.listen(3000, () => {
  console.log('App running at http://localhost:3000');
});

開始我們的實際實現,我們希望將注意力集中在作為第二個參數傳遞給 app.get() 的處理函數中 .

在這裡,我們正在組織執行克隆所需的信息。從我們的路由參數(這裡是“params”)中,我們想要得到 usernamerepo 我們的 URL 的一部分。為此,我們只需訪問 req.params Express 自動提供給我們的對象。我們期望 req.params.usernamereq.params.repo 被定義,因為我們可以看到在我們的 URL 中聲明了這些參數(任何以 : 為前綴的 我們的 URL 中的冒號被捕獲為參數)。

在這裡,我們存儲 usernamerepo 來自 req.params 在同名變量中。有了這些,接下來,我們設置repoPath 這是 username 的組合 和 repo , 由 / 分隔 正斜杠(模仿你在 Github 上訪問的 URL)。

有了這些信息,接下來,我們檢查 repos 中是否已經存在一個文件夾 我們打算將所有 repos 存儲在我們項目的根目錄中的文件夾(這不存在,但會在我們第一次克隆 repo 時由 Git 自動創建)。

在下一行,如果它確實 存在,我們想向請求發出信號,表明我們正在拉動 回購(意思是,拉最新的變化),如果它沒有 存在,我們想發回信號,我們是第一次克隆它。我們將描述任一場景的字符串存儲在變量 confirmation 中 .

我們可以看到這個confirmation 變量通過 res 發送回原始請求 快遞給我們的物品。在這裡,我們說“將 HTTP 狀態碼設置為 200(成功),然後發送 confirmation 字符串作為響應正文。”

在此之上,我們關心的部分,我們調用了一個不存在的函數 cloneAndPullRepo() 它將接受我們剛剛定義的變量,然後克隆一個新的 repo 或為現有的 repo 拉取更改。請注意,我們傳遞了我們預定義的 repoExists , username , 和 repo 變量作為前三個參數,但我們在最後添加了一個。

或者,我們希望讓我們的用戶能夠為他們的 repo 拉一個特定的分支。因為這是可選 (意味著它可能存在也可能不存在),我們希望將其作為 查詢 來支持 範圍。這與路由參數的不同之處在於它 指示路由是否匹配 一個網址。它只是作為元數據添加到 URL 的末尾(例如,/repos/clone/cheatcode/joystick?branch=development )。

然而,就像路由參數一樣,Express 也會為我們解析這些查詢參數,並將它們存儲在 req.query 目的。到預期的cloneAndPullRepo() 函數,我們傳遞 req.query.branch 作為最後的論點。

有了所有這些,現在,讓我們跳到克隆和拉動步驟。我們想在文件 cloneAndPullRepo.js 頂部附近的預期路徑處創建一個文件 .

連接克隆和拉取函數

現在,在一個新文件中,我們想要連接一個函數,負責執行我們的 repo 的克隆或拉取。

/cloneAndPullRepo.js

import child_process from 'child_process';

export default (repoExists = false, username = '', repo = '', branch = 'master') => {
  if (!repoExists) {
    child_process.execSync(`git clone https://${username}:${process.env.PERSONAL_ACCESS_TOKEN}@github.com/${username}/${repo}.git repos/${username}/${repo}`);
  } else {
    child_process.execSync(`cd repos/${username}/${repo} && git pull origin ${branch} --rebase`);
  }
}

由於代碼有限,我們在此處添加了文件的完整源代碼。讓我們一步一步來。

首先,在我們文件的底部,我們要創建一個函數的默認導出(這是我們預期在 index.js 中存在的函數 )。該函數應該考慮 repoExists , username 我們要克隆(或拉取)的 repo 的名稱,以及 repo 的名稱 我們想要克隆,並且可能是一個 branch .

對於每個參數,我們設置一個默認值,重要的兩個是 repoExists 默認設置為 falsebranch 默認設置為 master .

查看代碼——確認 child_process 的導入 從內置 Node.js child_process 開始 被動打包——如果 repoExists錯誤 ,我們要調用child_process.execSync() 該函數允許我們在 Node.js 中運行與我們的操作系統相關的命令(就像我們在終端窗口中一樣)。

這裡,execSync 意味著我們正在使用 同步 child_process.exec() 的版本 功能。這樣做是為了確保克隆適用於我們的示例,但是,您可能希望使用異步 .exec() 方法,這樣,當被調用時,代碼在運行時不會阻塞 Node.js。

專注於什麼 我們傳遞給 .execSync() ,我們使用 JavaScript 字符串插值傳遞一個長命令,將我們的變量嵌入到 git clone 我們要運行的命令:

`git clone https://${username}:${process.env.PERSONAL_ACCESS_TOKEN}@github.com/${username}/${repo}.git repos/${username}/${repo}`

大部分內容應該是不言自明的,但是,我們想提請注意 process.env.PERSONAL_ACCESS_TOKEN 部分。這是我們之前通過 dotenv 設置的值 包和我們的 .env 文件。在這裡,我們將它作為我們想要驗證 git clone 的密碼傳遞 請求(Github 將識別此訪問令牌,這要歸功於其前綴 ghp_ 身份並將其與我們的帳戶相關聯)。

例如,假設我們訪問了 URL http://localhost:3000/repos/clone/cheatcode/joystick 在我們的瀏覽器中,我們希望上面的代碼生成這樣的字符串:

git clone https://cheatcode:[email protected]/cheatcode/joystick.git repos/cheatcode/joystick

這行現在說的是“我們要克隆 cheatcode/joystick 使用用戶名 cheatcode 的 repo 使用密碼 ghp_xxx 進入 repos/cheatcode/joystick 我們應用程序中的文件夾。”

當它運行時,Git 會注意到 repos 文件夾還不存在並創建它,以及我們的用戶名 cheatcode 的文件夾 然後在那個中 , 一個包含我們 repo 的文件夾 名稱(我們項目的代碼將被克隆的地方)。

/cloneAndPullRepo.js

import child_process from 'child_process';

export default (repoExists = false, username = '', repo = '', branch = 'master') => {
  if (!repoExists) {
    child_process.execSync(`git clone https://${username}:${process.env.PERSONAL_ACCESS_TOKEN}@github.com/${username}/${repo}.git repos/${username}/${repo}`);
  } else {
    child_process.execSync(`cd repos/${username}/${repo} && git pull origin ${branch} --rebase`);
  }
}

專注於函數的第二部分,if repoExiststrue ,我們想回退到 else 語句,再次使用 .execSync() ,但是,這次運行兩個命令:cd 將目錄“更改”到現有的 repos/username/repo 文件夾,然後是 git pull origin ${branch} --rebase 拉取指定 branch 的最新更改 (默認的 master 或作為查詢參數傳遞給我們 URL 的任何內容)。

而已。有了所有這些,現在,如果我們啟動我們的應用程序並在我們的 URL 中傳遞現有 Github 存儲庫的用戶名和存儲庫名稱(或者是公共的,或者,如果是私有的,我們可以訪問的),我們應該觸發 cloneAndPullRepo() 函數並查看下載到我們項目中的 repo。

總結

在本教程中,我們學習瞭如何使用 Node.js 克隆 Github 存儲庫。我們學習瞭如何設置 Express.js 服務器,以及可以調用函數的路由,該函數可以克隆一個新的 repo,或者提取一個現有的 repo。為了進行克隆或拉取,我們學習瞭如何使用 child_process.execSync() 功能。


Tutorial JavaScript 教程
  1. Vue 3 教程(適用於 Vue 2 用戶)

  2. 2 分鐘介紹 GreenSock 動畫

  3. 亞馬遜Alexa免費贓物?

  4. DynamoDB 流

  5. React 中本地狀態的 apollo-link-state 教程

  6. 反應性能指南

  7. Node + Express 會話過期?

  1. 根據對比度動態改變字體顏色

  2. 如何將兩個函數添加在一起並存儲在 JavaScript 中的另一個函數(第三個函數)中?

  3. 對象屬性IsEnumerable() 方法

  4. IE、Safari 等的 input[type=time] polyfill

  5. 在 Javascript 中實現優先級隊列的最佳方式

  6. 什麼時候應該使用 .innerHTML,什麼時候應該在 JavaScript 中使用 document.write

  7. 如何選擇最裡面的元素?

  1. 帶有 React 組件的 Laravel 視圖

  2. 圖像分類 - JavaScript 中的機器學習

  3. 如何使用 TypeGraphQL 和 TypeORM 構建 GraphQL API

  4. 如何使用 Node.js 獲取用戶在網頁上花費的時間?