我第一次负责维护一台生产服务器时,依靠的是我的前任留给我的一份检查表。这份清单包含了所有的维护步骤和相应的命令。在那些早期的日子里,我虔诚地复制每一条命令,在按下回车键之前,对每一个字符进行两次和三次检查。慢慢地,这些命令被我牢牢记住了,直到有一天我意识到我不需要这个检查表了。
但后来我的任务越来越多,我发现我无法快速输入命令。我试着用自定义脚本来缩短这个过程,但这并没有什么帮助。然后就开始出现险情,很快手动部署就变得可怕而不有趣了。这就是手动部署的现实;你总是离灾难只有一个错误的命令。为了寻找一个更安全、更有效的替代方案,我找到了持续部署。我很快发现,部署过程的自动化消除了输入错误命令的风险和其他与人类完成重复性任务有关的错误。
在本教程及其附录中,我将引导你使用CircleCI和Heroku为Deno项目建立一个CI/CD管道。Deno是一个简单、现代、安全的JavaScript和TypeScript的运行时。Deno为项目提供了这些优势。
- Deno默认是安全的。没有文件、网络或环境访问,除非你明确地启用它。
- 支持TypeScript的开箱即用。
- 只发送一个可执行文件。
前提条件
在你开始之前,确保这些项目已经安装在你的系统上。
- 一个最新的Deno安装。
对于资源库管理和持续集成/持续部署,你需要。
- 一个GitHub账户。你可以在这里创建一个。
- 一个CircleCI账户。你可以在这里创建一个。为了方便连接你的GitHub项目,你可以用你的GitHub账户注册。
- 一个Heroku账户。你可以在这里创建一个。
创建一个项目目录
创建一个新的目录来存放所有的项目文件。
mkdir deno_circleci_heroku
cd deno_circleci_heroku
设置Deno服务器
在这一部分,我们将使用Oak中间件框架来设置我们的Deno服务器。这是一个用于Deno的HTTP服务器的框架,与Koa和Express相当。首先,创建一个名为server.ts 的文件,并在其中添加这段代码。
import { Application, Router } from "https://deno.land/x/oak@v7.5.0/mod.ts";
import { parse } from "https://deno.land/std@0.99.0/flags/mod.ts";
const app = new Application();
const { args } = Deno;
const DEFAULT_PORT = 8000;
const port = parse(args).port ?? DEFAULT_PORT;
const router = new Router();
router.get("/", (context) => {
context.response.type = "application/json";
context.response.body = { data: "This API is under construction" };
});
app.addEventListener("error", (event) => {
console.error(event.error);
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen({ port });
console.log(`Server is running on port ${port}`);
export default app;
这个例子显示了一个基本的API,当请求被发送到索引路由时,会返回一个 "正在建设 "的消息。一旦我们的管道设置成功,我们将为这个端点添加实际功能。
请注意,我们以一种不寻常的方式声明我们正在监听的端口。这是因为在运行API时,端口是由Heroku提供的。你可以在这里阅读更多关于这方面的内容。该端口将在deno run 命令中提供,并将被解析和用于运行应用程序。如果没有指定端口(当应用程序在本地运行时),将使用8000端口。
运行应用程序
我们可以运行应用程序,看看我们到目前为止使用这个命令做了什么。
deno run --allow-net server.ts
导航到http://localhost:8000/ ,查看响应情况。

编写应用程序的测试
现在是时候为我们的API端点写一个测试案例了。创建一个名为server.test.ts 的新文件并添加这个。
import { superoak } from "https://deno.land/x/superoak@4.2.0/mod.ts";
import { delay } from "https://deno.land/x/delay@v0.2.0/mod.ts";
import app from "./server.ts";
Deno.test("it should return a JSON response with status code 200", async () => {
const request = await superoak(app);
await request
.get("/")
.expect(200)
.expect("Content-Type", /json/)
.expect({ data: "This API is under construction" });
});
// Forcefully exit the Deno process once all tests are done.
Deno.test({
name: "exit the process forcefully after all the tests are done\n",
async fn() {
await delay(1);
Deno.exit(0);
},
sanitizeExit: false,
});
在本地运行测试
导航到终端。从应用程序的根部,使用这个命令运行测试。
deno test --allow-net server.test.ts
你应该看到下面的响应。
test it should return a JSON response with status code 200 ... ok (28ms)
test exit the process forcefully after all the tests are done
...%
有了你的测试用例,你就可以设置你的管道了。
配置Heroku
在应用程序的根目录下,创建一个名为Procfile 的新文件。请注意,这个文件没有扩展名。
在Procfile ,添加以下命令。
web: deno run --allow-net server.ts --port=${PORT}
这个命令声明了我们应用程序的网络进程和启动时要执行的命令。请注意,我们正在传递port 参数,并将其与Heroku提供的PORT 环境变量绑定。
接下来要做的事情是在Heroku上创建一个新的应用程序。你可以在Heroku的仪表板上做这件事。点击新建,然后点击**新建应用程序。**填写表格。你可以使用你喜欢的名称和地区。

接下来,点击创建应用程序,完成创建过程。然后,你将被转到你新创建的应用程序的部署视图。
你的下一个任务是添加一个构建包。要做到这一点,单击 "设置"选项卡。在buildpacks部分,点击Add buildpack。

这时会出现一个表格,你可以选择一个官方支持的构建包,或者为你的构建包提供一个URL。目前,Heroku没有官方支持的Deno构建包,所以你需要提供一个Deno构建包的URL来代替。将https://github.com/chibat/heroku-buildpack-deno.git ,然后点击保存更改。

我们需要的最后一件事是一个API密钥。除了应用程序的名称,你将使用它来连接你的CircleCi管道到Heroku。要获得你的API密钥,打开账户设置页面,向下滚动到API密钥部分。

点击 "揭示"按钮,复制API密钥。把它保存在你以后可以轻松访问的地方。
配置CircleCI
接下来,我们需要添加CircleCI的管道配置。对于这个项目,管道将包括两个步骤。
- 构建和测试 - 在这一步,你将构建项目并运行应用程序的测试。如果任何测试失败,那么将显示一个错误信息,并终止该过程。
- 部署到Heroku - 如果构建和测试步骤成功完成,最新的变化将在这一步骤中部署到Heroku。
在你项目的根部,创建一个名为.circleci 的文件夹,并在其中创建一个名为config.yml 的文件。在新创建的文件中,添加这个配置。
# Use the latest 2.1 version of CircleCI pipeline process engine.
version: 2.1
orbs:
heroku: circleci/heroku@1.2
jobs:
build-and-test:
docker:
- image: denoland/deno:1.10.3
steps:
- checkout
- run: |
deno test --allow-net server.test.ts
workflows:
sample:
jobs:
- build-and-test
- heroku/deploy-via-git:
force: true # this parameter instructs the push to use a force flag when pushing to the heroku remote, see: https://devcenter.heroku.com/articles/git
requires:
- build-and-test
这个配置拉入了Heroku orbcircleci/heroku ,它自动让我们访问一组强大的Heroku作业和命令。其中一个作业是heroku/deploy-via-git ,它可以将你的应用程序从GitHub repo直接部署到你的Heroku账户。
配置中指定了一个名为build-and-test 的工作。这个作业检查最新的代码,使用Deno创建一个Docker镜像,并在Heroku中运行测试。server.test.ts
最后,该配置指定了一个工作流程,先运行build-and-test ,然后再运行heroku/deploy-via-git 。注意,有一个requires 选项,告诉CircleCI只有在构建工作完成后才运行deploy-via-git 工作。
接下来,我们需要在GitHub上建立一个仓库,并将该项目链接到CircleCI。查看推送你的项目到GitHub的说明。
接下来,登录你的CircleCI账户。如果你用GitHub账户注册,你所有的仓库都会在你的项目仪表板上显示出来。
点击你的deno_circleci_heroku 项目旁边的Set Up Project。
CircleCI会检测项目的config.yml 文件。点击使用现有的配置,然后开始构建。您的第一个工作流程将开始运行,但它将会失败!

部署过程失败是因为我们没有提供Heroku的API密钥。为了解决这个问题,点击项目设置按钮,然后点击环境变量菜单选项。添加两个新的变量。
-
对于
HEROKU_APP_NAME,添加你在Heroku中使用的应用程序名称。该名称将是deno-heroku-circleci,或者如果你创建了一个自定义的名称。 -
对于
HEROKU_API_KEY,输入你先前从账户设置页面检索的Heroku API密钥。
从头开始重新运行你的工作流,这一次你的工作流将成功运行。为了确认你的工作流是成功的,你可以在浏览器中打开你新部署的应用程序。你的应用程序的URL应该是这样的格式https://<HEROKU_APP_NAME>.herokuapp.com/ 。
应该会显示正在建设中的API响应。
实施索引路由
现在你的工作流已经启动并运行,你可以实现索引路由。对于这个API,它将返回一个硬编码用户的列表。在应用程序的根目录下,创建一个名为users.ts 的新文件,并添加此代码。
export default [
{
_id: "60d7b2ccfb294b7be115b6c4",
isActive: true,
picture: "http://placehold.it/32x32",
age: 22,
eyeColor: "green",
name: "Harding Taylor",
gender: "male",
email: "hardingtaylor@lexicondo.com",
phone: "+1 (839) 440-2917",
address: "686 Harman Street, Reno, New Jersey, 5152",
about:
"Nisi irure aliquip aliquip Lorem. Elit ullamco commodo laborum aliqua commodo Lorem occaecat pariatur aute est reprehenderit ad. Qui nostrud enim aliqua consequat sit duis pariatur ex consectetur aute elit ad commodo officia.\r\n",
registered: "2017-02-10T11:52:40 -01:00",
friends: [
{
id: 0,
name: "Donovan Nielsen",
},
{
id: 1,
name: "Barrera Hartman",
},
{
id: 2,
name: "Carmen Bean",
},
],
greeting: "Hello, Harding Taylor! You have 5 unread messages.",
favoriteFruit: "banana",
},
{
_id: "60d7b2cc411283f9ea9fdaff",
isActive: false,
picture: "http://placehold.it/32x32",
age: 25,
eyeColor: "blue",
name: "Charlene Gibson",
gender: "female",
email: "charlenegibson@lexicondo.com",
phone: "+1 (864) 446-3848",
address: "132 Columbus Place, Clarksburg, Delaware, 281",
about:
"Exercitation voluptate cillum cillum do et voluptate officia Lorem sint. Ullamco quis ullamco et mollit. Veniam et labore aliquip elit fugiat proident labore.\r\n",
registered: "2015-01-02T04:41:06 -01:00",
friends: [
{
id: 0,
name: "Morris Barrera",
},
{
id: 1,
name: "Aguilar Pearson",
},
{
id: 2,
name: "Denise Jacobs",
},
],
greeting: "Hello, Charlene Gibson! You have 5 unread messages.",
favoriteFruit: "strawberry",
},
{
_id: "60d7b2cc836c6ddb012508c1",
isActive: false,
picture: "http://placehold.it/32x32",
age: 27,
eyeColor: "blue",
name: "Lavonne Barton",
gender: "female",
email: "lavonnebarton@lexicondo.com",
phone: "+1 (958) 505-3633",
address: "403 Ruby Street, Wintersburg, Pennsylvania, 1063",
about:
"Nisi sit cillum aute qui sunt ipsum deserunt ut. Fugiat laboris cupidatat mollit exercitation proident ad ut laboris nostrud amet amet dolor. Excepteur qui ea amet ut reprehenderit magna et proident sunt eu duis velit. Aliquip culpa proident aliqua dolore aliqua sint cillum anim amet duis esse nisi eu. Amet anim fugiat irure sit velit ad excepteur exercitation Lorem cillum sit deserunt dolore irure. Officia pariatur aliquip aliquip officia sunt sit dolore duis excepteur proident. Labore elit sunt ea deserunt officia nostrud.\r\n",
registered: "2018-06-09T04:57:28 -01:00",
friends: [
{
id: 0,
name: "Barber Jimenez",
},
{
id: 1,
name: "Eliza Robbins",
},
{
id: 2,
name: "Charmaine Alexander",
},
],
greeting: "Hello, Lavonne Barton! You have 8 unread messages.",
favoriteFruit: "banana",
},
{
_id: "60d7b2cc112a8197462d9a8e",
isActive: true,
picture: "http://placehold.it/32x32",
age: 32,
eyeColor: "green",
name: "Lea Evans",
gender: "female",
email: "leaevans@lexicondo.com",
phone: "+1 (891) 524-3545",
address: "725 Dennett Place, Alamo, New York, 1382",
about:
"Laborum dolor labore reprehenderit voluptate laborum ullamco non dolore magna officia ex velit. Ex nostrud duis ullamco cillum commodo occaecat pariatur nostrud nostrud occaecat incididunt minim eu. Minim ut ea quis laboris sunt.\r\n",
registered: "2017-11-10T06:28:55 -01:00",
friends: [
{
id: 0,
name: "Fulton Oneal",
},
{
id: 1,
name: "House Watts",
},
{
id: 2,
name: "Isabel Melton",
},
],
greeting: "Hello, Lea Evans! You have 4 unread messages.",
favoriteFruit: "strawberry",
},
{
_id: "60d7b2cc3689b747a755bd89",
isActive: true,
picture: "http://placehold.it/32x32",
age: 30,
eyeColor: "green",
name: "Jocelyn Harper",
gender: "female",
email: "jocelynharper@lexicondo.com",
phone: "+1 (964) 464-2509",
address: "524 Banner Avenue, Brenton, Texas, 1373",
about:
"Aliqua consectetur anim incididunt eu aute minim proident esse. Commodo eu tempor cillum veniam duis incididunt cupidatat. Tempor qui eu incididunt nostrud amet velit quis amet consequat. Deserunt nostrud eu laboris commodo irure fugiat dolore nisi in consequat ea in ullamco duis.\r\n",
registered: "2015-04-10T11:13:10 -01:00",
friends: [
{
id: 0,
name: "Beatriz Carver",
},
{
id: 1,
name: "Gillespie Ferrell",
},
{
id: 2,
name: "Chris Boyer",
},
],
greeting: "Hello, Jocelyn Harper! You have 8 unread messages.",
favoriteFruit: "banana",
},
{
_id: "60d7b2cc03d9f88b8a833480",
isActive: true,
picture: "http://placehold.it/32x32",
age: 39,
eyeColor: "green",
name: "Sharpe Wallace",
gender: "male",
email: "sharpewallace@lexicondo.com",
phone: "+1 (979) 492-3250",
address: "522 Madison Place, Charco, Missouri, 2144",
about:
"Voluptate culpa labore excepteur ut commodo veniam elit ea consectetur laboris adipisicing. Adipisicing ea officia qui reprehenderit. Ut ipsum elit irure id nisi. Enim cupidatat ea ea veniam est et enim nisi tempor. Minim laboris ipsum et ipsum mollit exercitation est labore voluptate cillum in dolor. Nostrud dolore labore et reprehenderit.\r\n",
registered: "2017-11-12T10:09:50 -01:00",
friends: [
{
id: 0,
name: "Valenzuela Shelton",
},
{
id: 1,
name: "Margret Stuart",
},
{
id: 2,
name: "Rosie Nixon",
},
],
greeting: "Hello, Sharpe Wallace! You have 3 unread messages.",
favoriteFruit: "apple",
},
];
接下来,更新server.test.ts ,以匹配这段代码。
import { superoak } from "https://deno.land/x/superoak@4.2.0/mod.ts";
import { delay } from "https://deno.land/x/delay@v0.2.0/mod.ts";
import app from "./server.ts";
Deno.test(
"it should return a JSON response containing users with status code 200",
async () => {
const request = await superoak(app);
await request
.get("/")
.expect(200)
.expect("Content-Type", /json/)
.expect(/"users":/);
}
);
// Forcefully exit the Deno process once all tests are done.
Deno.test({
name: "exit the process forcefully after all the tests are done\n",
async fn() {
await delay(1);
Deno.exit(0);
},
sanitizeExit: false,
});
在server.ts ,用这段代码更新索引路由。
router.get("/", (context) => {
context.response.type = "application/json";
context.response.body = { users };
});
不要忘记从users.ts 中导入用户。
import users from "./users.ts";
提交你的代码并推送到你的Github资源库。
git add .
git commit -m "Implement index route"
git push origin main
当这些变化被推送到你的Github仓库后,回到你的CircleCI仪表盘,在那里新的工作流程正在运行。当一切都完成后,刷新你的Heroku应用程序。你新实施的路由将返回用户列表。

总结
在本教程中,我向你展示了如何使用GitHub、CircleCI和Heroku为Deno API建立CI/CD管道。
通过将发布新功能的过程自动化,大大降低了人为错误对生产环境造成负面影响的风险。此外,还为产品增加了一定程度的质量保证,因为新功能只有在通过指定的测试案例时才会被部署。
这些实践有助于创建一个更有效的软件管理流程,使部署的重复性、平凡的方面自动化,从而使你的团队可以专注于解决问题。
本教程的整个代码库可在GitHub上找到。
谢谢您的阅读!