{"id":392,"date":"2026-08-12T08:34:11","date_gmt":"2026-08-12T00:34:11","guid":{"rendered":"https:\/\/numsimlab.com\/?p=392"},"modified":"2026-08-12T08:34:11","modified_gmt":"2026-08-12T00:34:11","slug":"ci-github-actions","status":"publish","type":"post","link":"https:\/\/numsimlab.com\/?p=392","title":{"rendered":"CI &amp; GitHub Actions"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>\u6301\u7eed\u96c6\u6210\u4e0e GitHub Actions \u5b8c\u6574\u6307\u5357 \u00b7 CI &#038; GitHub Actions Guide<\/title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{scroll-behavior:smooth}\n:root{\n  --bg:#f8f9fa;--bg2:#fff;--ink:#1a1a2e;--muted:#6c7086;--rule:#e2e4eb;\n  --accent:#2563eb;--accent2:#16a34a;--accent3:#dc2626;--accent4:#d97706;--accent5:#7c3aed;\n  --card-bg:#fff;--code-bg:#f1f3f5;--nav-bg:rgba(255,255,255,0.85);\n  --tag-g:#dcfce7;--tag-g-t:#166534;--tag-b:#dbeafe;--tag-b-t:#1e40af;\n  --tag-r:#fee2e2;--tag-r-t:#991b1b;--tag-y:#fef9c3;--tag-y-t:#713f12;--tag-p:#f3e8ff;--tag-p-t:#581c87;\n  --shadow:0 1px 3px rgba(0,0,.08);--radius:8px;--max-w:940px\n}\n[data-theme=\"dark\"]{\n  --bg:#0b0b1a;--bg2:#16162a;--ink:#e2e4f0;--muted:#7f8396;--rule:#26263e;\n  --accent:#60a5fa;--accent2:#4ade80;--accent3:#f87171;--accent4:#fbbf24;--accent5:#a78bfa;\n  --card-bg:#16162a;--code-bg:#1e1e36;--nav-bg:rgba(22,22,42,0.85);\n  --tag-g:#14532d;--tag-g-t:#bbf7d0;--tag-b:#1e3a5f;--tag-b-t:#bfdbfe;\n  --tag-r:#7f1d1d;--tag-r-t:#fecaca;--tag-y:#713f12;--tag-y-t:#fef08a;\n  --tag-p:#3b1f6e;--tag-p-t:#e9d5ff;\n  --shadow:0 1px 3px rgba(0,0,.3)\n}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Noto Sans SC\",\"PingFang SC\",\"Microsoft YaHei\",sans-serif;background:var(--bg);color:var(--ink);line-height:1.7;font-size:15px;transition:background .3s,color .3s}\n\/* Progress Bar *\/\n.progress-bar{position:fixed;top:0;left:0;height:3px;background:linear-gradient(90deg,var(--accent),var(--accent5));width:0;z-index:200;transition:width .1s}\n\/* Header *\/\nheader{position:fixed;top:0;left:0;right:0;z-index:100;background:var(--nav-bg);border-bottom:1px solid var(--rule);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:background .3s}\n.header-inner{max-width:var(--max-w);margin:0 auto;display:flex;align-items:center;justify-content:space-between;padding:0 1.5rem;height:56px}\n.logo{font-weight:700;font-size:.95rem;color:var(--accent);display:flex;align-items:center;gap:.4rem}\n.controls{display:flex;align-items:center;gap:.5rem}\n.lang-switch{display:flex;border:1px solid var(--rule);border-radius:6px;overflow:hidden}\n.lang-switch button{background:0 0;border:none;color:var(--muted);padding:.3rem .7rem;font-size:.8rem;cursor:pointer;transition:all .2s;font-family:inherit}\n.lang-switch button.active{background:var(--accent);color:#fff}\n.lang-switch button:not(.active):hover{color:var(--ink);background:var(--rule)}\n.theme-toggle{background:0 0;border:1px solid var(--rule);border-radius:6px;color:var(--muted);width:34px;height:34px;font-size:1rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}\n.theme-toggle:hover{color:var(--ink);background:var(--rule)}\n\/* Main *\/\nmain{max-width:var(--max-w);margin:0 auto;padding:80px 1.5rem 3rem}\n.lang-section{display:none}.lang-section.active{display:block}\nh1{font-size:1.9rem;font-weight:800;line-height:1.3;margin-bottom:.5rem;letter-spacing:-.02em}\nh1 .sub{display:block;font-size:.85rem;font-weight:400;color:var(--muted);margin-top:.25rem;letter-spacing:0}\nh2{font-size:1.35rem;font-weight:700;margin:2.5rem 0 1rem;padding-bottom:.5rem;border-bottom:2px solid var(--accent);letter-spacing:-.01em}\nh3{font-size:1.05rem;font-weight:600;margin:1.5rem 0 .6rem;color:var(--accent)}\nh4{font-size:.95rem;font-weight:600;margin:1rem 0 .4rem;color:var(--ink)}\np{margin-bottom:.8rem}\na{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}\n\/* Hero *\/\n.hero{text-align:center;padding:2rem 0 1rem}\n.hero p{font-size:1rem;color:var(--muted);max-width:700px;margin:0 auto}\n\/* Tags *\/\n.tag{display:inline-block;padding:.12rem .5rem;border-radius:4px;font-size:.72rem;font-weight:600;margin-right:.25rem;white-space:nowrap}\n.tag-g{background:var(--tag-g);color:var(--tag-g-t)}.tag-b{background:var(--tag-b);color:var(--tag-b-t)}\n.tag-r{background:var(--tag-r);color:var(--tag-r-t)}.tag-y{background:var(--tag-y);color:var(--tag-y-t)}.tag-p{background:var(--tag-p);color:var(--tag-p-t)}\n\/* Card *\/\n.card{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.25rem;margin-bottom:1rem;box-shadow:var(--shadow)}\n\/* Info Box *\/\n.info-box{background:var(--bg);border-left:4px solid var(--accent);padding:.9rem 1.1rem;margin:1rem 0;border-radius:0 var(--radius) var(--radius) 0;font-size:.88rem}\n.info-box strong{color:var(--accent)}\n.info-box.warn{border-left-color:var(--accent4)}.info-box.warn strong{color:var(--accent4)}\n.info-box.danger{border-left-color:var(--accent3)}.info-box.danger strong{color:var(--accent3)}\n.info-box.success{border-left-color:var(--accent2)}.info-box.success strong{color:var(--accent2)}\n\/* TOC *\/\n.toc{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.15rem 1.4rem;margin-bottom:2rem;box-shadow:var(--shadow)}\n.toc h3{margin:0 0 .5rem;font-size:.9rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}\n.toc ol{padding-left:1.1rem;columns:2;column-gap:2rem}\n.toc li{margin-bottom:.25rem;font-size:.88rem}.toc a{color:var(--ink)}.toc a:hover{color:var(--accent);text-decoration:none}\n\/* Code *\/\npre.code-block{background:var(--code-bg);padding:.85rem 1rem;border-radius:var(--radius);overflow-x:auto;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.82rem;line-height:1.5;margin:.6rem 0;border:1px solid var(--rule)}\ncode,.inline-code{background:var(--code-bg);padding:.12rem .4rem;border-radius:4px;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.83rem;border:1px solid var(--rule)}\n\/* Table *\/\n.table-wrap{overflow-x:auto;margin:1.2rem 0}\ntable{width:100%;border-collapse:collapse;font-size:.82rem}\ntable caption{caption-side:top;text-align:center;font-size:.8rem;color:var(--muted);margin-bottom:.5rem;font-weight:600}\nthead th{background:var(--accent);color:#fff;padding:.6rem .5rem;text-align:center;font-weight:600;white-space:nowrap}\nthead th:first-child{border-radius:6px 0 0 0}thead th:last-child{border-radius:0 6px 0 0}\ntbody td{padding:.55rem .5rem;border-bottom:1px solid var(--rule);text-align:center;vertical-align:middle}\ntbody tr:nth-child(even){background:var(--bg)}\ntbody td:first-child{text-align:left;font-weight:600}\n\/* Terminal *\/\n.terminal{background:#1a1a2e;color:#a8e6cf;border-radius:var(--radius);padding:.9rem 1rem;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.82rem;line-height:1.6;margin:.6rem 0;overflow-x:auto;border:1px solid #333}\n.terminal .prompt{color:#60a5fa}.terminal .comment{color:#6c7086;font-style:italic}.terminal .output{color:#e2e4f0}\n[data-theme=\"dark\"] .terminal{background:#08081a}\n\/* Steps *\/\n.steps{counter-reset:step;list-style:none;padding:0}\n.steps li{counter-increment:step;position:relative;padding:.85rem 1rem .85rem 2.8rem;margin-bottom:.5rem;border-left:2px solid var(--rule);border-radius:0 var(--radius) var(--radius) 0;background:var(--card-bg);box-shadow:var(--shadow)}\n.steps li::before{content:counter(step);position:absolute;left:-13px;top:.85rem;width:26px;height:26px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-size:.78rem;font-weight:700}\n.steps li .cmd{display:block;background:var(--code-bg);padding:.4rem .7rem;border-radius:5px;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.82rem;margin:.3rem 0;overflow-x:auto;border:1px solid var(--rule)}\n.steps li .note{display:block;color:var(--muted);font-size:.82rem;margin-top:.2rem}\n\/* Flow Diagram *\/\n.flow-diagram{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.25rem;margin:1.25rem 0;text-align:center;box-shadow:var(--shadow)}\n.flow-row{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:.35rem;margin:.4rem 0}\n.flow-item{display:inline-block;padding:.4rem .8rem;border-radius:6px;font-weight:600;font-size:.82rem;text-align:center;background:var(--accent);color:#fff;min-width:60px}\n.flow-item.green{background:var(--accent2)}.flow-item.red{background:var(--accent3)}.flow-item.yellow{background:var(--accent4);color:#1a1a2e}.flow-item.purple{background:var(--accent5)}\n.flow-arrow{color:var(--muted);font-size:1rem}\n\/* Grid layouts *\/\n.cmd-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:.75rem;margin:1rem 0}\n.cmd-card{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:.9rem;box-shadow:var(--shadow)}\n.cmd-card h4{margin:0 0 .35rem;font-size:.88rem;color:var(--accent)}\n.cmd-card .cmd{font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.8rem;background:var(--code-bg);padding:.3rem .5rem;border-radius:4px;display:block;margin-bottom:.3rem;border:1px solid var(--rule)}\n.cmd-card .desc{font-size:.8rem;color:var(--muted)}\n.two-col{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem;margin:1.25rem 0}\n.col{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.25rem;box-shadow:var(--shadow)}\n.col h4{margin-top:0}\n\/* Compare Box *\/\n.compare-box{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin:1rem 0}\n.compare-col{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1rem;box-shadow:var(--shadow)}\n.compare-col h4{margin:0 0 .4rem;font-size:.88rem}\n.compare-col.bad h4{color:var(--accent3)}\n.compare-col.good h4{color:var(--accent2)}\npre.example{background:var(--code-bg);padding:.6rem .8rem;border-radius:6px;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.78rem;line-height:1.5;margin:.4rem 0;border:1px solid var(--rule);color:var(--ink);overflow-x:auto}\n\/* Figure *\/\nfigure{text-align:center;margin:1.5rem 0}\nfigcaption{font-size:.8rem;color:var(--muted);margin-top:.5rem}\nfigure img{max-width:100%;height:auto;border-radius:var(--radius);border:1px solid var(--rule)}\n\/* Badges *\/\n.ci-badge{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:4px;font-size:.78rem;font-weight:600;margin:0 .15rem}\n.ci-badge.c{background:var(--tag-b);color:var(--tag-b-t)}\n.ci-badge.i{background:var(--tag-g);color:var(--tag-g-t)}\n.ci-badge.d{background:var(--tag-p);color:var(--tag-p-t)}\n\/* Pipeline visualization *\/\n.pipeline-viz{display:flex;flex-wrap:wrap;gap:0;justify-content:center;align-items:stretch;margin:1.5rem 0}\n.pipeline-stage{flex:1;min-width:120px;text-align:center;padding:.8rem .5rem;background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);box-shadow:var(--shadow);position:relative}\n.pipeline-stage .stage-icon{font-size:1.5rem;margin-bottom:.3rem}\n.pipeline-stage .stage-name{font-weight:700;font-size:.82rem;color:var(--accent)}\n.pipeline-stage .stage-desc{font-size:.72rem;color:var(--muted);margin-top:.2rem}\n.pipeline-arrow{display:flex;align-items:center;color:var(--accent);font-size:1.2rem;padding:0 .3rem}\n\/* Citations *\/\nsup a{color:var(--accent);text-decoration:none;font-size:.75em;font-weight:600}\nsup a:hover{text-decoration:underline}\nfooter{border-top:2px solid var(--rule);margin-top:3rem;padding-top:1.5rem}\nfooter .sources h2{font-size:1.1rem;border-bottom:none;margin-bottom:.8rem}\nfooter .sources ol{padding-left:1.2rem;font-size:.85rem;color:var(--muted)}\nfooter .sources li{margin-bottom:.5rem;overflow-wrap:break-word;word-break:break-all}\nfooter .sources .src-title{color:var(--ink);word-break:normal}\nfooter .sources .src-url{display:block;margin-top:.15rem;font-size:.82rem;color:var(--accent);word-break:break-all}\nfooter .sources a{word-break:break-all}\n\/* Back to top *\/\n.back-top{position:fixed;bottom:2rem;right:2rem;width:42px;height:42px;border-radius:50%;background:var(--accent);color:#fff;border:none;cursor:pointer;font-size:1.2rem;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .3s,transform .3s;z-index:99;box-shadow:var(--shadow)}\n.back-top.visible{opacity:1;transform:translateY(0)}\n.back-top:hover{transform:translateY(-3px)}\n\/* Responsive *\/\n@media(max-width:640px){\n  h1{font-size:1.4rem}.toc ol{columns:1}.header-inner{padding:0 1rem}main{padding:72px 1rem 2rem}\n  .steps li{padding-left:2.4rem}.steps li::before{left:-11px;width:22px;height:22px;font-size:.7rem}\n  .two-col{grid-template-columns:1fr}.cmd-grid{grid-template-columns:1fr}.compare-box{grid-template-columns:1fr}\n  .pipeline-viz{flex-direction:column}.pipeline-arrow{transform:rotate(90deg);padding:.3rem 0}\n}\n<\/style>\n<\/head>\n<body>\n<div class=\"progress-bar\" id=\"progressBar\"><\/div>\n<header>\n  <div class=\"header-inner\">\n    <div class=\"logo\"><span>\ud83d\udd04<\/span> CI &#038; GitHub Actions<\/div>\n    <div class=\"controls\">\n      <div class=\"lang-switch\">\n        <button class=\"active\" onclick=\"setLang('zh')\" id=\"btn-zh\">\u4e2d\u6587<\/button>\n        <button onclick=\"setLang('en')\" id=\"btn-en\">English<\/button>\n      <\/div>\n      <button class=\"theme-toggle\" onclick=\"toggleTheme()\" title=\"\u5207\u6362\u660e\u6697\u4e3b\u9898\">&#9788;<\/button>\n    <\/div>\n  <\/div>\n<\/header>\n<main>\n\n<!-- ======== \u4e2d\u6587\u7248 ======== -->\n<div class=\"lang-section active\" id=\"lang-zh\">\n<div class=\"hero\">\n  <h1>\u6301\u7eed\u96c6\u6210\u4e0e GitHub Actions \u5b8c\u6574\u6307\u5357<span class=\"sub\">\u4ece CI \u6838\u5fc3\u6982\u5ff5\u5230 GitHub Actions \u5b9e\u6218\u914d\u7f6e\u7684\u5168\u9762\u89e3\u6790<\/span><\/h1>\n  <p>\u7406\u89e3\u6301\u7eed\u96c6\u6210\uff08CI\uff09\u7684\u539f\u7406\u3001\u4ef7\u503c\u4e0e\u5b9e\u8df5\uff0c\u638c\u63e1 GitHub Actions \u5de5\u4f5c\u6d41\u914d\u7f6e\u3001YAML \u8bed\u6cd5\u3001\u77e9\u9635\u6d4b\u8bd5\u4e0e\u7f13\u5b58\u7b56\u7565<\/p>\n<\/div>\n\n<div class=\"toc\">\n  <h3>\u76ee\u5f55<\/h3>\n  <ol>\n    <li><a href=\"#zh-1\">\u4e00\u3001CI \u6982\u8ff0\uff1a\u4ec0\u4e48\u662f\u6301\u7eed\u96c6\u6210<\/a><\/li>\n    <li><a href=\"#zh-2\">\u4e8c\u3001CI \u7684\u6838\u5fc3\u4ef7\u503c\u4e0e\u539f\u5219<\/a><\/li>\n    <li><a href=\"#zh-3\">\u4e09\u3001GitHub Actions \u57fa\u7840<\/a><\/li>\n    <li><a href=\"#zh-4\">\u56db\u3001Workflow YAML \u7ed3\u6784\u8be6\u89e3<\/a><\/li>\n    <li><a href=\"#zh-5\">\u4e94\u3001\u6784\u5efa\u7b2c\u4e00\u4e2a CI \u6d41\u6c34\u7ebf<\/a><\/li>\n    <li><a href=\"#zh-6\">\u516d\u3001\u9ad8\u7ea7\u7279\u6027\uff1a\u77e9\u9635\u3001\u7f13\u5b58\u4e0e\u5236\u54c1<\/a><\/li>\n    <li><a href=\"#zh-7\">\u4e03\u3001CI \u6700\u4f73\u5b9e\u8df5\u4e0e\u5b89\u5168<\/a><\/li>\n    <li><a href=\"#zh-8\">\u516b\u3001\u5e38\u89c1\u9677\u9631\u4e0e\u89e3\u51b3\u65b9\u6848<\/a><\/li>\n    <li><a href=\"#zh-9\">\u4e5d\u3001CI \u4e0e CD \u7684\u5173\u7cfb<\/a><\/li>\n    <li><a href=\"#zh-10\">\u5341\u3001\u5b8c\u6574\u5b9e\u6218\u793a\u4f8b<\/a><\/li>\n  <\/ol>\n<\/div>\n\n<!-- \u4e00\u3001CI \u6982\u8ff0 -->\n<h2 id=\"zh-1\">\u4e00\u3001CI \u6982\u8ff0\uff1a\u4ec0\u4e48\u662f\u6301\u7eed\u96c6\u6210<\/h2>\n\n<p><strong>\u6301\u7eed\u96c6\u6210<\/strong>\uff08Continuous Integration\uff0c\u7b80\u79f0 <strong>CI<\/strong>\uff09\u662f\u4e00\u79cd\u8f6f\u4ef6\u5f00\u53d1\u5b9e\u8df5\uff0c\u8981\u6c42\u5f00\u53d1\u8005\u9891\u7e41\u5730\u5c06\u4ee3\u7801\u53d8\u66f4\u5408\u5e76\u5230\u5171\u4eab\u4ed3\u5e93\u4e2d\u2014\u2014\u901a\u5e38\u6bcf\u5929\u591a\u6b21\u3002\u6bcf\u6b21\u5408\u5e76\u90fd\u4f1a\u89e6\u53d1\u81ea\u52a8\u5316\u7684\u6784\u5efa\u548c\u6d4b\u8bd5\u6d41\u7a0b\uff0c\u4ee5\u9a8c\u8bc1\u65b0\u4ee3\u7801\u662f\u5426\u80fd\u4e0e\u73b0\u6709\u4ee3\u7801\u5e93\u6b63\u786e\u534f\u4f5c<sup><a href=\"#cite-1\">[1]<\/a><\/sup>\u3002<\/p>\n\n<div class=\"info-box\">\n  <strong>\u6838\u5fc3\u5b9a\u4e49\uff1a<\/strong>CI \u7684\u672c\u8d28\u662f&#8221;\u9891\u7e41\u96c6\u6210 + \u81ea\u52a8\u5316\u9a8c\u8bc1&#8221;\u3002\u5b83\u4e0d\u662f\u67d0\u4e2a\u5177\u4f53\u5de5\u5177\uff0c\u800c\u662f\u4e00\u79cd\u5de5\u7a0b\u5b9e\u8df5\u7406\u5ff5\u2014\u2014\u901a\u8fc7\u81ea\u52a8\u5316\u624b\u6bb5\uff0c\u5728\u4ee3\u7801\u53d8\u66f4\u8fdb\u5165\u4e3b\u5e72\u4e4b\u524d\u5c31\u53d1\u73b0\u96c6\u6210\u95ee\u9898\uff0c\u800c\u975e\u7b49\u5230\u53d1\u5e03\u524d\u624d\u96c6\u4e2d\u5904\u7406\u3002\n<\/div>\n\n<h3>1.1 CI \u89e3\u51b3\u7684\u6838\u5fc3\u95ee\u9898<\/h3>\n\n<p>\u5728\u4f20\u7edf\u5f00\u53d1\u6a21\u5f0f\u4e2d\uff0c\u591a\u540d\u5f00\u53d1\u8005\u53ef\u80fd\u5728\u5404\u81ea\u7684\u5206\u652f\u4e0a\u5de5\u4f5c\u6570\u5468\uff0c\u6700\u540e\u624d\u5c1d\u8bd5\u5408\u5e76\u3002\u8fd9\u79cd&#8221;\u5ef6\u8fdf\u96c6\u6210&#8221;\u4f1a\u5bfc\u81f4\u4e25\u91cd\u7684<strong>\u5408\u5e76\u5730\u72f1<\/strong>\uff08Merge Hell\uff09\uff1a\u4ee3\u7801\u51b2\u7a81\u5de8\u5927\u3001\u96c6\u6210\u9519\u8bef\u6df1\u57cb\u3001\u8c03\u8bd5\u56f0\u96be\u3001\u53d1\u5e03\u5ef6\u8fdf\u3002CI \u901a\u8fc7&#8221;\u5c11\u91cf\u591a\u6b21&#8221;\u7684\u96c6\u6210\u7b56\u7565\u4ece\u6839\u672c\u4e0a\u6539\u53d8\u4e86\u8fd9\u4e00\u56f0\u5c40<sup><a href=\"#cite-2\">[2]<\/a><\/sup>\u3002<\/p>\n\n<figure id=\"fig-1\">\n  <div class=\"flow-diagram\" style=\"border:none;box-shadow:none;padding:0\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item\">\u5f00\u53d1\u8005\u63d0\u4ea4<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item purple\">\u89e6\u53d1 CI<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item\">\u81ea\u52a8\u6784\u5efa<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item green\">\u81ea\u52a8\u6d4b\u8bd5<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item yellow\">\u8d28\u91cf\u68c0\u67e5<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item green\">\u901a\u8fc7\/\u5931\u8d25\u53cd\u9988<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 1<\/strong> CI \u7684\u57fa\u672c\u5de5\u4f5c\u6d41\u7a0b\uff1a\u4ece\u4ee3\u7801\u63d0\u4ea4\u5230\u53cd\u9988\u7684\u81ea\u52a8\u5316\u95ed\u73af<\/figcaption>\n<\/figure>\n\n<h3>1.2 CI \u7684\u5386\u53f2\u6f14\u8fdb<\/h3>\n\n<p>CI \u7684\u6982\u5ff5\u6700\u65e9\u7531 Martin Fowler \u548c ThoughtWorks \u56e2\u961f\u5728 2000 \u5e74\u4ee3\u521d\u671f\u7cfb\u7edf\u5316\u63d0\u51fa\u3002\u5176\u6f14\u5316\u7ecf\u5386\u4e86\u51e0\u4e2a\u5173\u952e\u9636\u6bb5<sup><a href=\"#cite-3\">[3]<\/a><\/sup>\uff1a<\/p>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>\u8868 1<\/strong> CI \u5de5\u5177\u4e0e\u7406\u5ff5\u7684\u5386\u53f2\u6f14\u8fdb<\/caption>\n    <thead>\n      <tr><th>\u65f6\u671f<\/th><th>\u9636\u6bb5<\/th><th>\u4ee3\u8868\u6027\u5de5\u5177\/\u7406\u5ff5<\/th><th>\u6838\u5fc3\u7279\u5f81<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td>2000 \u5e74\u524d<\/td><td>\u624b\u52a8\u96c6\u6210<\/td><td>\u624b\u52a8\u6784\u5efa\u3001\u811a\u672c<\/td><td>\u96c6\u6210\u665a\u3001\u51b2\u7a81\u591a\u3001\u4eba\u5de5\u64cd\u4f5c<\/td><\/tr>\n      <tr><td>2001-2010<\/td><td>CI \u8bde\u751f<\/td><td>CruiseControl\u3001Hudson<\/td><td>\u81ea\u52a8\u6784\u5efa\u3001\u6bcf\u65e5\u6784\u5efa<\/td><\/tr>\n      <tr><td>2011-2018<\/td><td>CI \u6210\u719f<\/td><td>Jenkins\u3001Travis CI<\/td><td>\u63d2\u4ef6\u751f\u6001\u3001\u6d41\u6c34\u7ebf\u5373\u4ee3\u7801<\/td><\/tr>\n      <tr><td>2019-\u81f3\u4eca<\/td><td>\u4e91\u539f\u751f CI<\/td><td>GitHub Actions\u3001GitLab CI<\/td><td>\u4e0e\u4ee3\u7801\u4ed3\u5e93\u6df1\u5ea6\u96c6\u6210\u3001YAML \u58f0\u660e\u5f0f<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h3>1.3 CI \u7684\u6838\u5fc3\u8981\u7d20<\/h3>\n\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udccb \u7248\u672c\u63a7\u5236<\/h4>\n    <span class=\"desc\">\u6240\u6709\u4ee3\u7801\u96c6\u4e2d\u7ba1\u7406\uff0c\u5206\u652f\u7b56\u7565\u6e05\u6670\uff08\u5982 Git Flow\u3001Trunk-based Development\uff09<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd28 \u81ea\u52a8\u5316\u6784\u5efa<\/h4>\n    <span class=\"desc\">\u4e00\u6761\u547d\u4ee4\u5b8c\u6210\u7f16\u8bd1\u3001\u6253\u5305\uff0c\u65e0\u9700\u4eba\u5de5\u5e72\u9884<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83e\uddea \u81ea\u52a8\u5316\u6d4b\u8bd5<\/h4>\n    <span class=\"desc\">\u5355\u5143\u6d4b\u8bd5\u3001\u96c6\u6210\u6d4b\u8bd5\u3001\u9759\u6001\u5206\u6790\u81ea\u52a8\u6267\u884c<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd14 \u5feb\u901f\u53cd\u9988<\/h4>\n    <span class=\"desc\">\u6784\u5efa\/\u6d4b\u8bd5\u7ed3\u679c\u5728\u6570\u5206\u949f\u5185\u53cd\u9988\u7ed9\u5f00\u53d1\u8005<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udce6 \u5236\u54c1\u7ba1\u7406<\/h4>\n    <span class=\"desc\">\u6784\u5efa\u4ea7\u7269\u7edf\u4e00\u5b58\u50a8\u3001\u7248\u672c\u5316\u7ba1\u7406<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83c\udf0d \u4e00\u81f4\u73af\u5883<\/h4>\n    <span class=\"desc\">CI \u8fd0\u884c\u73af\u5883\u4e0e\u751f\u4ea7\u73af\u5883\u4fdd\u6301\u4e00\u81f4<\/span>\n  <\/div>\n<\/div>\n\n<!-- \u4e8c\u3001CI \u7684\u6838\u5fc3\u4ef7\u503c\u4e0e\u539f\u5219 -->\n<h2 id=\"zh-2\">\u4e8c\u3001CI \u7684\u6838\u5fc3\u4ef7\u503c\u4e0e\u539f\u5219<\/h2>\n\n<h3>2.1 CI \u5e26\u6765\u7684\u6838\u5fc3\u4ef7\u503c<\/h3>\n\n<p>CI \u7684\u4ef7\u503c\u4e0d\u4ec5\u5728\u4e8e&#8221;\u81ea\u52a8\u5316&#8221;\uff0c\u66f4\u5728\u4e8e\u5b83\u4ece\u6839\u672c\u4e0a\u6539\u53d8\u4e86\u56e2\u961f\u7684\u534f\u4f5c\u6a21\u5f0f\u548c\u4ee3\u7801\u8d28\u91cf\u7684\u4fdd\u969c\u65b9\u5f0f<sup><a href=\"#cite-4\">[4]<\/a><\/sup>\uff1a<\/p>\n\n<div class=\"two-col\">\n  <div class=\"col\">\n    <h4>\u2705 \u5c3d\u65e9\u53d1\u73b0\u7f3a\u9677<\/h4>\n    <p>\u6bcf\u6b21\u63d0\u4ea4\u90fd\u89e6\u53d1\u6d4b\u8bd5\uff0c\u95ee\u9898\u5728\u5f15\u5165\u540e\u6570\u5206\u949f\u5185\u88ab\u53d1\u73b0\uff0c\u800c\u975e\u6570\u5468\u540e\u7684\u96c6\u6210\u9636\u6bb5\u3002\u4fee\u590d\u6210\u672c\u4e0e\u53d1\u73b0\u65f6\u95f4\u6210\u6b63\u6bd4\u2014\u2014\u8d8a\u65e9\u53d1\u73b0\uff0c\u4fee\u590d\u8d8a\u4fbf\u5b9c\u3002<\/p>\n  <\/div>\n  <div class=\"col\">\n    <h4>\u2705 \u51cf\u5c11\u96c6\u6210\u51b2\u7a81<\/h4>\n    <p>\u9891\u7e41\u5408\u5e76\u610f\u5473\u7740\u6bcf\u6b21\u53d8\u66f4\u7684\u4f53\u91cf\u5f88\u5c0f\uff0c\u51b2\u7a81\u51e0\u4e4e\u4e0d\u5b58\u5728\u3002\u56e2\u961f\u4e0d\u518d\u9700\u8981\u5728\u53d1\u5e03\u524d\u82b1\u6570\u5929\u89e3\u51b3\u5408\u5e76\u51b2\u7a81\u3002<\/p>\n  <\/div>\n<\/div>\n\n<div class=\"two-col\">\n  <div class=\"col\">\n    <h4>\u2705 \u6301\u7eed\u7684\u8d28\u91cf\u4fe1\u5fc3<\/h4>\n    <p>\u4e3b\u5e72\u5206\u652f\u59cb\u7ec8\u5904\u4e8e&#8221;\u53ef\u90e8\u7f72&#8221;\u72b6\u6001\u3002\u4efb\u4f55\u65f6\u5019\u90fd\u53ef\u4ee5\u4ece\u4e3b\u5e72\u62c9\u53d6\u4ee3\u7801\u5e76\u53d1\u5e03\uff0c\u56e0\u4e3a CI \u5df2\u7ecf\u9a8c\u8bc1\u4e86\u5b83\u7684\u6b63\u786e\u6027\u3002<\/p>\n  <\/div>\n  <div class=\"col\">\n    <h4>\u2705 \u52a0\u901f\u4ea4\u4ed8\u8282\u594f<\/h4>\n    <p>\u81ea\u52a8\u5316\u53d6\u4ee3\u4e86\u624b\u52a8\u6784\u5efa\u548c\u6d4b\u8bd5\uff0c\u5f00\u53d1\u8005\u5c06\u65f6\u95f4\u96c6\u4e2d\u5728\u7f16\u5199\u529f\u80fd\u4ee3\u7801\u4e0a\u3002\u53d1\u5e03\u9891\u7387\u4ece&#8221;\u6bcf\u6708\u4e00\u6b21&#8221;\u63d0\u5347\u5230&#8221;\u6bcf\u65e5\u591a\u6b21&#8221;\u3002<\/p>\n  <\/div>\n<\/div>\n\n<h3>2.2 CI \u7684\u516d\u5927\u57fa\u672c\u539f\u5219<\/h3>\n\n<figure id=\"fig-2\">\n  <div class=\"pipeline-viz\">\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd00<\/div>\n      <div class=\"stage-name\">\u5355\u4e00\u6e90\u7801\u5e93<\/div>\n      <div class=\"stage-desc\">\u6240\u6709\u4eba\u4ece\u540c\u4e00\u4ed3\u5e93\u5de5\u4f5c<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\u26a1<\/div>\n      <div class=\"stage-name\">\u9891\u7e41\u63d0\u4ea4<\/div>\n      <div class=\"stage-desc\">\u81f3\u5c11\u6bcf\u5929\u4e00\u6b21<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83e\udd16<\/div>\n      <div class=\"stage-name\">\u81ea\u52a8\u6784\u5efa<\/div>\n      <div class=\"stage-desc\">\u65e0\u4eba\u5de5\u5e72\u9884<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83e\uddea<\/div>\n      <div class=\"stage-name\">\u81ea\u52a8\u6d4b\u8bd5<\/div>\n      <div class=\"stage-desc\">\u5168\u9762\u8986\u76d6<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd14<\/div>\n      <div class=\"stage-name\">\u5feb\u901f\u53cd\u9988<\/div>\n      <div class=\"stage-desc\">\u5206\u949f\u7ea7\u54cd\u5e94<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83c\udf10<\/div>\n      <div class=\"stage-name\">\u4e00\u81f4\u73af\u5883<\/div>\n      <div class=\"stage-desc\">\u4e0e\u751f\u4ea7\u4e00\u81f4<\/div>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 2<\/strong> \u6301\u7eed\u96c6\u6210\u7684\u516d\u5927\u6838\u5fc3\u539f\u5219<\/figcaption>\n<\/figure>\n\n<div class=\"info-box success\">\n  <strong>\u5173\u952e\u6307\u6807\uff1a<\/strong>\u4e00\u4e2a\u5065\u5eb7\u7684 CI \u6d41\u6c34\u7ebf\u5e94\u5728 <strong>10 \u5206\u949f\u5185<\/strong>\u5b8c\u6210\u6784\u5efa\u548c\u6d4b\u8bd5\u3002\u5982\u679c\u8d85\u8fc7 30 \u5206\u949f\uff0c\u5f00\u53d1\u8005\u4f1a\u5f00\u59cb\u5ffd\u7565\u53cd\u9988\uff0cCI \u7684\u4ef7\u503c\u5927\u6253\u6298\u6263<sup><a href=\"#cite-2\">[2]<\/a><\/sup>\u3002\n<\/div>\n\n<h3>2.3 CI \u6d41\u6c34\u7ebf\u7684\u6807\u51c6\u9636\u6bb5<\/h3>\n\n<p>\u4e00\u6761\u5b8c\u6574\u7684 CI \u6d41\u6c34\u7ebf\u901a\u5e38\u5305\u542b\u4ee5\u4e0b\u9636\u6bb5\uff0c\u6bcf\u4e2a\u9636\u6bb5\u90fd\u5e94\u81ea\u52a8\u5316\uff1a<\/p>\n\n<ol class=\"steps\">\n  <li>\n    <strong>\u4ee3\u7801\u68c0\u51fa\uff08Checkout\uff09<\/strong>\n    <span class=\"note\">\u4ece\u7248\u672c\u63a7\u5236\u4ed3\u5e93\u62c9\u53d6\u6700\u65b0\u4ee3\u7801\u5230 CI \u8fd0\u884c\u73af\u5883<\/span>\n  <\/li>\n  <li>\n    <strong>\u4f9d\u8d56\u5b89\u88c5\uff08Install\uff09<\/strong>\n    <span class=\"note\">\u5b89\u88c5\u9879\u76ee\u6240\u9700\u7684\u7b2c\u4e09\u65b9\u5e93\u548c\u5de5\u5177\u94fe<\/span>\n  <\/li>\n  <li>\n    <strong>\u4ee3\u7801\u68c0\u67e5\uff08Lint\uff09<\/strong>\n    <span class=\"note\">\u9759\u6001\u5206\u6790\u3001\u4ee3\u7801\u98ce\u683c\u68c0\u67e5\u3001\u7c7b\u578b\u68c0\u67e5<\/span>\n  <\/li>\n  <li>\n    <strong>\u7f16\u8bd1\u6784\u5efa\uff08Build\uff09<\/strong>\n    <span class=\"note\">\u7f16\u8bd1\u6e90\u4ee3\u7801\u3001\u6253\u5305\u8d44\u6e90\u3001\u751f\u6210\u53ef\u6267\u884c\u6587\u4ef6<\/span>\n  <\/li>\n  <li>\n    <strong>\u6d4b\u8bd5\u9a8c\u8bc1\uff08Test\uff09<\/strong>\n    <span class=\"note\">\u5355\u5143\u6d4b\u8bd5\u3001\u96c6\u6210\u6d4b\u8bd5\u3001\u7aef\u5230\u7aef\u6d4b\u8bd5<\/span>\n  <\/li>\n  <li>\n    <strong>\u5b89\u5168\u626b\u63cf\uff08Security\uff09<\/strong>\n    <span class=\"note\">\u4f9d\u8d56\u6f0f\u6d1e\u626b\u63cf\u3001\u5bc6\u94a5\u6cc4\u9732\u68c0\u6d4b\u3001SAST<\/span>\n  <\/li>\n  <li>\n    <strong>\u5236\u54c1\u53d1\u5e03\uff08Artifact\uff09<\/strong>\n    <span class=\"note\">\u4e0a\u4f20\u6784\u5efa\u4ea7\u7269\u5230\u5236\u54c1\u4ed3\u5e93\uff0c\u4f9b\u540e\u7eed\u90e8\u7f72\u4f7f\u7528<\/span>\n  <\/li>\n<\/ol>\n\n<!-- \u4e09\u3001GitHub Actions \u57fa\u7840 -->\n<h2 id=\"zh-3\">\u4e09\u3001GitHub Actions \u57fa\u7840<\/h2>\n\n<p><strong>GitHub Actions<\/strong> \u662f GitHub \u5185\u7f6e\u7684 CI\/CD \u5e73\u53f0\uff0c\u4e8e 2018 \u5e74\u63a8\u51fa\u3002\u5b83\u76f4\u63a5\u96c6\u6210\u5728 GitHub \u4ed3\u5e93\u4e2d\uff0c\u5f00\u53d1\u8005\u65e0\u9700\u7ef4\u62a4\u989d\u5916\u7684 CI \u670d\u52a1\u5668\uff0c\u53ea\u9700\u5728\u4ed3\u5e93\u4e2d\u6dfb\u52a0 YAML \u914d\u7f6e\u6587\u4ef6\u5373\u53ef\u5b9e\u73b0\u5b8c\u6574\u7684\u81ea\u52a8\u5316\u6d41\u6c34\u7ebf<sup><a href=\"#cite-5\">[5]<\/a><\/sup>\u3002<\/p>\n\n<h3>3.1 \u6838\u5fc3\u6982\u5ff5<\/h3>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>\u8868 2<\/strong> GitHub Actions \u6838\u5fc3\u6982\u5ff5\u672f\u8bed\u8868<\/caption>\n    <thead>\n      <tr><th>\u6982\u5ff5<\/th><th>\u8bf4\u660e<\/th><th>\u7c7b\u6bd4<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td>Workflow\uff08\u5de5\u4f5c\u6d41\uff09<\/td><td>\u4e00\u4e2a\u5b8c\u6574\u7684\u81ea\u52a8\u5316\u6d41\u7a0b\uff0c\u5b9a\u4e49\u5728 YAML \u6587\u4ef6\u4e2d<\/td><td>\u4e00\u6761\u6d41\u6c34\u7ebf<\/td><\/tr>\n      <tr><td>Event\uff08\u4e8b\u4ef6\uff09<\/td><td>\u89e6\u53d1\u5de5\u4f5c\u6d41\u7684\u6761\u4ef6\uff0c\u5982 push\u3001pull_request<\/td><td>\u542f\u52a8\u6309\u94ae<\/td><\/tr>\n      <tr><td>Job\uff08\u4f5c\u4e1a\uff09<\/td><td>\u5de5\u4f5c\u6d41\u4e2d\u7684\u4e00\u4e2a\u6267\u884c\u5355\u5143\uff0c\u5305\u542b\u591a\u4e2a\u6b65\u9aa4<\/td><td>\u6d41\u6c34\u7ebf\u7684\u4e00\u4e2a\u5de5\u4f4d<\/td><\/tr>\n      <tr><td>Step\uff08\u6b65\u9aa4\uff09<\/td><td>Job \u4e2d\u7684\u5177\u4f53\u64cd\u4f5c\uff0c\u53ef\u4ee5\u662f\u547d\u4ee4\u6216 Action<\/td><td>\u5de5\u4f4d\u4e0a\u7684\u4e00\u4e2a\u64cd\u4f5c<\/td><\/tr>\n      <tr><td>Action\uff08\u52a8\u4f5c\uff09<\/td><td>\u53ef\u590d\u7528\u7684\u81ea\u5b9a\u4e49\u7ec4\u4ef6\uff0c\u7c7b\u4f3c\u51fd\u6570<\/td><td>\u4e00\u4e2a\u5de5\u5177\u51fd\u6570<\/td><\/tr>\n      <tr><td>Runner\uff08\u8fd0\u884c\u5668\uff09<\/td><td>\u6267\u884c Job \u7684\u670d\u52a1\u5668\u73af\u5883<\/td><td>\u6267\u884c\u64cd\u4f5c\u7684\u673a\u5668<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h3>3.2 GitHub Actions \u7684\u67b6\u6784<\/h3>\n\n<figure id=\"fig-3\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item purple\">\u4e8b\u4ef6\u89e6\u53d1<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item\">Workflow<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item green\">Job 1<\/span>\n      <span class=\"flow-arrow\">\u2194<\/span>\n      <span class=\"flow-item green\">Job 2<\/span>\n    <\/div>\n    <div class=\"flow-row\" style=\"margin-top:.5rem\">\n      <span class=\"flow-arrow\">\u2193<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item yellow\">Step 1<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item yellow\">Step 2<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item yellow\">Step 3<\/span>\n    <\/div>\n    <div class=\"flow-row\" style=\"margin-top:.5rem\">\n      <span class=\"flow-arrow\">\u2193<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item red\">Runner\uff08ubuntu-latest\uff09<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 3<\/strong> GitHub Actions \u7684\u5c42\u6b21\u7ed3\u6784\uff1a\u4e8b\u4ef6 \u2192 \u5de5\u4f5c\u6d41 \u2192 \u4f5c\u4e1a \u2192 \u6b65\u9aa4 \u2192 \u8fd0\u884c\u5668<\/figcaption>\n<\/figure>\n\n<h3>3.3 \u4e3a\u4ec0\u4e48\u9009\u62e9 GitHub Actions<\/h3>\n\n<div class=\"compare-box\">\n  <div class=\"compare-col good\">\n    <h4>\u2705 GitHub Actions \u4f18\u52bf<\/h4>\n    <p><strong>\u96f6\u8fd0\u7ef4\uff1a<\/strong>\u65e0\u9700\u642d\u5efa\u548c\u7ef4\u62a4 CI \u670d\u52a1\u5668\uff0cGitHub \u6258\u7ba1\u4e00\u5207\u3002<\/p>\n    <p><strong>\u6df1\u5ea6\u96c6\u6210\uff1a<\/strong>\u4e0e GitHub \u4ed3\u5e93\u3001PR\u3001Issues \u65e0\u7f1d\u8854\u63a5\u3002<\/p>\n    <p><strong>\u58f0\u660e\u5f0f\u914d\u7f6e\uff1a<\/strong>YAML \u6587\u4ef6\u5373\u6d41\u6c34\u7ebf\uff0c\u53ef\u7248\u672c\u63a7\u5236\u3002<\/p>\n    <p><strong>\u514d\u8d39\u989d\u5ea6\uff1a<\/strong>\u516c\u5f00\u4ed3\u5e93\u65e0\u9650\u514d\u8d39\uff0c\u79c1\u6709\u4ed3\u5e93\u6bcf\u6708 2000 \u5206\u949f\u3002<\/p>\n    <p><strong>\u751f\u6001\u4e30\u5bcc\uff1a<\/strong>Marketplace \u4e0a\u6709\u6570\u4e07\u4e2a\u73b0\u6210 Action\u3002<\/p>\n  <\/div>\n  <div class=\"compare-col bad\">\n    <h4>\u26a0\ufe0f \u9700\u8981\u6ce8\u610f\u7684\u5c40\u9650<\/h4>\n    <p><strong>\u8fd0\u884c\u65f6\u95f4\u9650\u5236\uff1a<\/strong>\u5355\u4e2a Job \u6700\u591a\u8fd0\u884c 6 \u5c0f\u65f6\u3002<\/p>\n    <p><strong>\u5e76\u53d1\u9650\u5236\uff1a<\/strong>\u514d\u8d39\u8d26\u6237\u6700\u591a 20 \u4e2a\u5e76\u53d1 Job\u3002<\/p>\n    <p><strong>\u81ea\u5b9a\u4e49\u73af\u5883\uff1a<\/strong>\u9700\u8981\u81ea\u6258\u7ba1 Runner \u624d\u80fd\u5b8c\u5168\u63a7\u5236\u73af\u5883\u3002<\/p>\n    <p><strong>\u8c03\u8bd5\u53d7\u9650\uff1a<\/strong>\u65e0\u6cd5\u76f4\u63a5 SSH \u5230 Runner \u8c03\u8bd5\u3002<\/p>\n    <p><strong>\u4f9b\u5e94\u5546\u9501\u5b9a\uff1a<\/strong>\u914d\u7f6e\u4e0e GitHub \u5e73\u53f0\u7ed1\u5b9a\u3002<\/p>\n  <\/div>\n<\/div>\n\n<!-- \u56db\u3001Workflow YAML \u7ed3\u6784\u8be6\u89e3 -->\n<h2 id=\"zh-4\">\u56db\u3001Workflow YAML \u7ed3\u6784\u8be6\u89e3<\/h2>\n\n<p>GitHub Actions \u5de5\u4f5c\u6d41\u4f7f\u7528 YAML \u8bed\u6cd5\u5b9a\u4e49\u3002\u7406\u89e3 YAML \u7ed3\u6784\u662f\u7f16\u5199 CI \u914d\u7f6e\u7684\u57fa\u7840<sup><a href=\"#cite-6\">[6]<\/a><\/sup>\u3002<\/p>\n\n<h3>4.1 \u57fa\u672c\u7ed3\u6784<\/h3>\n\n<pre class=\"code-block\">name: CI Pipeline          <span style=\"color:var(--muted)\"># \u5de5\u4f5c\u6d41\u540d\u79f0\uff08\u663e\u793a\u5728 GitHub UI\uff09<\/span>\non:                         <span style=\"color:var(--muted)\"># \u89e6\u53d1\u4e8b\u4ef6<\/span>\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nenv:                        <span style=\"color:var(--muted)\"># \u5168\u5c40\u73af\u5883\u53d8\u91cf<\/span>\n  NODE_VERSION: \"20\"\n\njobs:                       <span style=\"color:var(--muted)\"># \u4f5c\u4e1a\u5b9a\u4e49<\/span>\n  build:                    <span style=\"color:var(--muted)\"># Job ID<\/span>\n    runs-on: ubuntu-latest  <span style=\"color:var(--muted)\"># \u8fd0\u884c\u73af\u5883<\/span>\n    steps:                  <span style=\"color:var(--muted)\"># \u6b65\u9aa4\u5e8f\u5217<\/span>\n      - name: Checkout\n        uses: actions\/checkout@v4\n      - name: Setup Node\n        uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n      - name: Install\n        run: npm ci\n      - name: Test\n        run: npm test<\/pre>\n\n<h3>4.2 \u6838\u5fc3\u8bed\u6cd5\u5143\u7d20<\/h3>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>\u8868 3<\/strong> Workflow YAML \u6838\u5fc3\u8bed\u6cd5\u5143\u7d20<\/caption>\n    <thead>\n      <tr><th>\u5143\u7d20<\/th><th>\u7528\u9014<\/th><th>\u662f\u5426\u5fc5\u9700<\/th><th>\u793a\u4f8b<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td><code>name<\/code><\/td><td>\u5de5\u4f5c\u6d41\u663e\u793a\u540d\u79f0<\/td><td>\u53ef\u9009<\/td><td><code>name: \"Build & Test\"<\/code><\/td><\/tr>\n      <tr><td><code>on<\/code><\/td><td>\u4e8b\u4ef6\u89e6\u53d1\u5668<\/td><td><strong>\u5fc5\u9700<\/strong><\/td><td><code>on: [push, pull_request]<\/code><\/td><\/tr>\n      <tr><td><code>jobs<\/code><\/td><td>\u4f5c\u4e1a\u96c6\u5408<\/td><td><strong>\u5fc5\u9700<\/strong><\/td><td><code>jobs: build: ...<\/code><\/td><\/tr>\n      <tr><td><code>runs-on<\/code><\/td><td>\u8fd0\u884c\u5668\u73af\u5883<\/td><td><strong>\u5fc5\u9700<\/strong><\/td><td><code>runs-on: ubuntu-latest<\/code><\/td><\/tr>\n      <tr><td><code>steps<\/code><\/td><td>\u6b65\u9aa4\u5e8f\u5217<\/td><td><strong>\u5fc5\u9700<\/strong><\/td><td><code>steps: - name: ...<\/code><\/td><\/tr>\n      <tr><td><code>uses<\/code><\/td><td>\u5f15\u7528\u9884\u6784\u5efa Action<\/td><td>\u53ef\u9009<\/td><td><code>uses: actions\/checkout@v4<\/code><\/td><\/tr>\n      <tr><td><code>run<\/code><\/td><td>\u6267\u884c Shell \u547d\u4ee4<\/td><td>\u53ef\u9009<\/td><td><code>run: npm test<\/code><\/td><\/tr>\n      <tr><td><code>with<\/code><\/td><td>\u4f20\u9012\u53c2\u6570\u7ed9 Action<\/td><td>\u53ef\u9009<\/td><td><code>with: node-version: 20<\/code><\/td><\/tr>\n      <tr><td><code>env<\/code><\/td><td>\u73af\u5883\u53d8\u91cf<\/td><td>\u53ef\u9009<\/td><td><code>env: CI: true<\/code><\/td><\/tr>\n      <tr><td><code>needs<\/code><\/td><td>Job \u4f9d\u8d56\u5173\u7cfb<\/td><td>\u53ef\u9009<\/td><td><code>needs: [test]<\/code><\/td><\/tr>\n      <tr><td><code>if<\/code><\/td><td>\u6761\u4ef6\u6267\u884c<\/td><td>\u53ef\u9009<\/td><td><code>if: github.ref == 'refs\/heads\/main'<\/code><\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h3>4.3 \u4e8b\u4ef6\u89e6\u53d1\u5668\u8be6\u89e3<\/h3>\n\n<pre class=\"code-block\">on:\n  <span style=\"color:var(--muted)\"># \u63a8\u9001\u5230\u6307\u5b9a\u5206\u652f\u65f6\u89e6\u53d1<\/span>\n  push:\n    branches: [ main, develop ]\n    paths: [ 'src\/**', 'tests\/**' ]        <span style=\"color:var(--muted)\"># \u4ec5\u6307\u5b9a\u8def\u5f84\u53d8\u66f4\u65f6\u89e6\u53d1<\/span>\n    paths-ignore: [ 'docs\/**', '*.md' ]     <span style=\"color:var(--muted)\"># \u5ffd\u7565\u6307\u5b9a\u8def\u5f84<\/span>\n\n  <span style=\"color:var(--muted)\"># Pull Request \u4e8b\u4ef6<\/span>\n  pull_request:\n    branches: [ main ]\n    types: [ opened, synchronize, reopened ]\n\n  <span style=\"color:var(--muted)\"># \u5b9a\u65f6\u89e6\u53d1\uff08cron \u8bed\u6cd5\uff09<\/span>\n  schedule:\n    - cron: \"0 2 * * 1\"   <span style=\"color:var(--muted)\"># \u6bcf\u5468\u4e00\u51cc\u6668 2 \u70b9 UTC<\/span>\n\n  <span style=\"color:var(--muted)\"># \u624b\u52a8\u89e6\u53d1<\/span>\n  workflow_dispatch:\n    inputs:\n      environment:\n        description: '\u90e8\u7f72\u73af\u5883'\n        default: 'staging'\n        type: choice\n        options: [ staging, production ]\n\n  <span style=\"color:var(--muted)\"># \u5176\u4ed6\u4ed3\u5e93\u4e8b\u4ef6<\/span>\n  issues:\n    types: [ opened ]\n  release:\n    types: [ published ]<\/pre>\n\n<h3>4.4 \u8868\u8fbe\u5f0f\u4e0e\u4e0a\u4e0b\u6587<\/h3>\n\n<p>GitHub Actions \u4f7f\u7528 <code>${{ }}<\/code> \u8bed\u6cd5\u8bbf\u95ee\u4e0a\u4e0b\u6587\u53d8\u91cf\u548c\u6267\u884c\u8868\u8fbe\u5f0f\uff1a<\/p>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># \u5e38\u7528\u4e0a\u4e0b\u6587<\/span>\n${{ github.ref }}              <span style=\"color:var(--muted)\"># \u5f53\u524d\u5206\u652f\u5f15\u7528<\/span>\n${{ github.event_name }}       <span style=\"color:var(--muted)\"># \u89e6\u53d1\u4e8b\u4ef6\u540d\u79f0<\/span>\n${{ github.sha }}              <span style=\"color:var(--muted)\"># \u63d0\u4ea4 SHA<\/span>\n${{ env.NODE_VERSION }}        <span style=\"color:var(--muted)\"># \u73af\u5883\u53d8\u91cf<\/span>\n${{ secrets.API_KEY }}         <span style=\"color:var(--muted)\"># \u52a0\u5bc6\u5bc6\u94a5<\/span>\n${{ vars.ENV_NAME }}           <span style=\"color:var(--muted)\"># \u4ed3\u5e93\u53d8\u91cf\uff08\u975e\u654f\u611f\uff09<\/span>\n${{ matrix.node }}             <span style=\"color:var(--muted)\"># \u77e9\u9635\u53d8\u91cf<\/span>\n${{ steps.build.outputs.ver }} <span style=\"color:var(--muted)\"># \u524d\u5e8f\u6b65\u9aa4\u8f93\u51fa<\/span>\n\n<span style=\"color:var(--muted)\"># \u6761\u4ef6\u8868\u8fbe\u5f0f<\/span>\nif: ${{ github.ref == 'refs\/heads\/main' && success() }}\nif: ${{ failure() }}\nif: ${{ always() }}\nif: ${{ cancelled() }}<\/pre>\n\n<!-- \u4e94\u3001\u6784\u5efa\u7b2c\u4e00\u4e2a CI \u6d41\u6c34\u7ebf -->\n<h2 id=\"zh-5\">\u4e94\u3001\u6784\u5efa\u7b2c\u4e00\u4e2a CI \u6d41\u6c34\u7ebf<\/h2>\n\n<h3>5.1 Node.js \u9879\u76ee CI \u793a\u4f8b<\/h3>\n\n<p>\u4ee5\u4e0b\u662f\u4e00\u4e2a\u5b8c\u6574\u7684 Node.js \u9879\u76ee CI \u5de5\u4f5c\u6d41\uff0c\u6db5\u76d6\u4ee3\u7801\u68c0\u51fa\u3001\u4f9d\u8d56\u5b89\u88c5\u3001\u4ee3\u7801\u68c0\u67e5\u3001\u6d4b\u8bd5\u548c\u6784\u5efa<sup><a href=\"#cite-7\">[7]<\/a><\/sup>\uff1a<\/p>\n\n<pre class=\"code-block\">name: Node.js CI\n\non:\n  push:\n    branches: [ main, develop ]\n  pull_request:\n    branches: [ main ]\n\nenv:\n  NODE_VERSION: \"20\"\n  CI: true\n\njobs:\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run lint\n        run: npm run lint\n\n      - name: Run tests\n        run: npm run test:coverage\n        env:\n          NODE_ENV: test\n\n      - name: Upload coverage\n        uses: actions\/upload-artifact@v4\n        if: always()\n        with:\n          name: coverage-report\n          path: coverage\/\n          retention-days: 30\n\n  build:\n    name: Build\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n      - run: npm ci\n      - run: npm run build\n      - name: Upload build artifact\n        uses: actions\/upload-artifact@v4\n        with:\n          name: dist\n          path: dist\/<\/pre>\n\n<h3>5.2 Python \u9879\u76ee CI \u793a\u4f8b<\/h3>\n\n<pre class=\"code-block\">name: Python CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\"]\n\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Setup Python\n        uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Cache pip\n        uses: actions\/cache@v4\n        with:\n          path: ~\/.cache\/pip\n          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n          restore-keys: ${{ runner.os }}-pip-\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r requirements.txt\n          pip install ruff pytest\n\n      - name: Lint with ruff\n        run: ruff check .\n\n      - name: Test with pytest\n        run: pytest --cov=src --cov-report=xml\n\n      - name: Upload coverage\n        uses: codecov\/codecov-action@v4\n        if: always()\n        with:\n          file: .\/coverage.xml<\/pre>\n\n<h3>5.3 \u6587\u4ef6\u653e\u7f6e\u4f4d\u7f6e<\/h3>\n\n<div class=\"info-box\">\n  <strong>\u76ee\u5f55\u7ed3\u6784\uff1a<\/strong>\u5de5\u4f5c\u6d41\u6587\u4ef6\u5fc5\u987b\u653e\u5728\u4ed3\u5e93\u7684 <code>.github\/workflows\/<\/code> \u76ee\u5f55\u4e0b\uff0c\u6587\u4ef6\u540d\u4ee5 <code>.yml<\/code> \u6216 <code>.yaml<\/code> \u7ed3\u5c3e\u3002GitHub \u4f1a\u81ea\u52a8\u8bc6\u522b\u5e76\u6267\u884c\u3002\n<\/div>\n\n<pre class=\"code-block\">my-project\/\n\u251c\u2500\u2500 .github\/\n\u2502   \u2514\u2500\u2500 workflows\/\n\u2502       \u251c\u2500\u2500 ci.yml              <span style=\"color:var(--muted)\"># \u4e3b CI \u5de5\u4f5c\u6d41<\/span>\n\u2502       \u251c\u2500\u2500 deploy.yml          <span style=\"color:var(--muted)\"># \u90e8\u7f72\u5de5\u4f5c\u6d41<\/span>\n\u2502       \u2514\u2500\u2500 nightly-tests.yml   <span style=\"color:var(--muted)\"># \u591c\u95f4\u5b8c\u6574\u6d4b\u8bd5<\/span>\n\u251c\u2500\u2500 src\/\n\u251c\u2500\u2500 tests\/\n\u251c\u2500\u2500 package.json\n\u2514\u2500\u2500 README.md<\/pre>\n\n<!-- \u516d\u3001\u9ad8\u7ea7\u7279\u6027 -->\n<h2 id=\"zh-6\">\u516d\u3001\u9ad8\u7ea7\u7279\u6027\uff1a\u77e9\u9635\u3001\u7f13\u5b58\u4e0e\u5236\u54c1<\/h2>\n\n<h3>6.1 \u77e9\u9635\u6d4b\u8bd5\uff08Matrix Strategy\uff09<\/h3>\n\n<p>\u77e9\u9635\u7b56\u7565\u5141\u8bb8\u5728\u5355\u4e2a Job \u5b9a\u4e49\u4e2d\u81ea\u52a8\u751f\u6210\u591a\u4e2a\u8fd0\u884c\u5b9e\u4f8b\uff0c\u7528\u4e8e\u6d4b\u8bd5\u4e0d\u540c\u64cd\u4f5c\u7cfb\u7edf\u3001\u8bed\u8a00\u7248\u672c\u6216\u914d\u7f6e\u7ec4\u5408<sup><a href=\"#cite-8\">[8]<\/a><\/sup>\uff1a<\/p>\n\n<pre class=\"code-block\">jobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false        <span style=\"color:var(--muted)\"># \u4e00\u4e2a\u5931\u8d25\u4e0d\u53d6\u6d88\u5176\u4ed6<\/span>\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        node: [18, 20, 22]\n        exclude:\n          - os: macos-latest\n            node: 18          <span style=\"color:var(--muted)\"># \u6392\u9664\u7279\u5b9a\u7ec4\u5408<\/span>\n        include:\n          - os: ubuntu-latest\n            node: 22\n            experimental: true\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n      - run: npm ci\n      - run: npm test<\/pre>\n\n<figure id=\"fig-4\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item\">Ubuntu + Node 18<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item green\">Ubuntu + Node 20<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item yellow\">Ubuntu + Node 22<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item\">Windows + Node 18<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item green\">Windows + Node 20<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item yellow\">Windows + Node 22<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item green\">macOS + Node 20<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item yellow\">macOS + Node 22<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 4<\/strong> \u77e9\u9635\u7b56\u7565\u5c55\u5f00\u540e\u7684\u5e76\u884c\u6d4b\u8bd5\u7ec4\u5408\uff088 \u4e2a\u5e76\u884c Job\uff09<\/figcaption>\n<\/figure>\n\n<h3>6.2 \u4f9d\u8d56\u7f13\u5b58<\/h3>\n\n<p>\u7f13\u5b58\u53ef\u4ee5\u5927\u5e45\u7f29\u77ed CI \u8fd0\u884c\u65f6\u95f4\uff0c\u907f\u514d\u6bcf\u6b21\u90fd\u91cd\u65b0\u4e0b\u8f7d\u4f9d\u8d56<sup><a href=\"#cite-9\">[9]<\/a><\/sup>\uff1a<\/p>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># \u65b9\u5f0f\u4e00\uff1a\u4f7f\u7528 setup-node \u5185\u7f6e\u7f13\u5b58\uff08\u63a8\u8350\uff09<\/span>\n- uses: actions\/setup-node@v4\n  with:\n    node-version: 20\n    cache: 'npm'              <span style=\"color:var(--muted)\"># \u81ea\u52a8\u7f13\u5b58 npm<\/span>\n\n<span style=\"color:var(--muted)\"># \u65b9\u5f0f\u4e8c\uff1a\u4f7f\u7528 actions\/cache \u624b\u52a8\u7f13\u5b58<\/span>\n- name: Cache pip packages\n  uses: actions\/cache@v4\n  with:\n    path: ~\/.cache\/pip\n    key: ${{ runner.os }}-pip-${{ hashFiles('**\/requirements.txt') }}\n    restore-keys: |\n      ${{ runner.os }}-pip-\n\n<span style=\"color:var(--muted)\"># \u65b9\u5f0f\u4e09\uff1a\u7f13\u5b58 Maven \u4f9d\u8d56<\/span>\n- name: Cache Maven\n  uses: actions\/cache@v4\n  with:\n    path: ~\/.m2\/repository\n    key: ${{ runner.os }}-maven-${{ hashFiles('**\/pom.xml') }}<\/pre>\n\n<div class=\"info-box success\">\n  <strong>\u7f13\u5b58\u6548\u679c\uff1a<\/strong>\u9996\u6b21\u8fd0\u884c\u65f6\u7f13\u5b58 miss\uff0c\u4f1a\u5b8c\u6574\u4e0b\u8f7d\u4f9d\u8d56\uff08\u53ef\u80fd\u9700\u8981 2-3 \u5206\u949f\uff09\u3002\u540e\u7eed\u8fd0\u884c\u547d\u4e2d\u7f13\u5b58\u540e\uff0c\u4f9d\u8d56\u6062\u590d\u4ec5\u9700 10-30 \u79d2\u3002<strong>\u7f13\u5b58\u952e<\/strong>\u5e94\u5305\u542b\u4f9d\u8d56\u6587\u4ef6\u7684\u54c8\u5e0c\u503c\uff0c\u786e\u4fdd\u4f9d\u8d56\u53d8\u66f4\u65f6\u81ea\u52a8\u5237\u65b0\u7f13\u5b58\u3002\n<\/div>\n\n<h3>6.3 \u6784\u5efa\u5236\u54c1\u7ba1\u7406<\/h3>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># \u4e0a\u4f20\u6784\u5efa\u5236\u54c1<\/span>\n- name: Upload build artifact\n  uses: actions\/upload-artifact@v4\n  with:\n    name: dist-${{ github.sha }}\n    path: |\n      dist\/\n      package.json\n    retention-days: 30        <span style=\"color:var(--muted)\"># \u4fdd\u7559 30 \u5929<\/span>\n\n<span style=\"color:var(--muted)\"># \u5728\u53e6\u4e00\u4e2a Job \u4e2d\u4e0b\u8f7d\u5236\u54c1<\/span>\n- name: Download artifact\n  uses: actions\/download-artifact@v4\n  with:\n    name: dist-${{ github.sha }}\n    path: .\/build<\/pre>\n\n<h3>6.4 \u53ef\u590d\u7528\u5de5\u4f5c\u6d41\uff08Reusable Workflows\uff09<\/h3>\n\n<p>\u53ef\u590d\u7528\u5de5\u4f5c\u6d41\u5141\u8bb8\u5c06\u901a\u7528 CI \u903b\u8f91\u5c01\u88c5\u4e3a\u4e00\u4e2a\u72ec\u7acb\u6587\u4ef6\uff0c\u7136\u540e\u5728\u591a\u4e2a\u4ed3\u5e93\u6216\u5de5\u4f5c\u6d41\u4e2d\u8c03\u7528\uff0c\u5b9e\u73b0 DRY\uff08\u4e0d\u8981\u91cd\u590d\u81ea\u5df1\uff09\u539f\u5219<sup><a href=\"#cite-10\">[10]<\/a><\/sup>\uff1a<\/p>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># .github\/workflows\/reusable-test.yml<\/span>\nname: Reusable Test Workflow\non:\n  workflow_call:              <span style=\"color:var(--muted)\"># \u58f0\u660e\u4e3a\u53ef\u88ab\u8c03\u7528<\/span>\n    inputs:\n      python-version:\n        required: false\n        default: \"3.12\"\n        type: string\n    secrets:\n      API_KEY:\n        required: false\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ inputs.python-version }}\n      - run: pip install -r requirements.txt\n      - run: pytest<\/pre>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># \u8c03\u7528\u53ef\u590d\u7528\u5de5\u4f5c\u6d41<\/span>\njobs:\n  call-tests:\n    uses: .\/.github\/workflows\/reusable-test.yml\n    with:\n      python-version: \"3.11\"\n    secrets:\n      API_KEY: ${{ secrets.API_KEY }}<\/pre>\n\n<h3>6.5 \u5e76\u884c\u4e0e\u4e32\u884c Job<\/h3>\n\n<figure id=\"fig-5\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item purple\">Lint<\/span>\n      <span class=\"flow-arrow\">\u2198<\/span>\n      <span class=\"flow-arrow\" style=\"margin:0 2rem\"><\/span>\n      <span class=\"flow-item green\">Test (\u5e76\u884c)<\/span>\n      <span class=\"flow-arrow\">\u2198<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-arrow\" style=\"margin-right:2rem\">\u2198<\/span>\n      <span class=\"flow-item green\">Build (\u5e76\u884c)<\/span>\n      <span class=\"flow-arrow\">\u2198<\/span>\n      <span class=\"flow-item yellow\">Deploy<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item purple\">Security Scan<\/span>\n      <span class=\"flow-arrow\">\u2197<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 5<\/strong> Job \u7684\u5e76\u884c\u4e0e\u4e32\u884c\uff1aLint \/ Test \/ Build \/ Security Scan \u5e76\u884c\u6267\u884c\uff0c\u5168\u90e8\u901a\u8fc7\u540e\u89e6\u53d1 Deploy<\/figcaption>\n<\/figure>\n\n<pre class=\"code-block\">jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  test:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  build:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  security:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  deploy:\n    needs: [lint, test, build, security]   <span style=\"color:var(--muted)\"># \u7b49\u5f85\u524d 4 \u4e2a\u5168\u90e8\u5b8c\u6210<\/span>\n    if: github.ref == 'refs\/heads\/main'     <span style=\"color:var(--muted)\"># \u4ec5\u4e3b\u5206\u652f\u90e8\u7f72<\/span>\n    runs-on: ubuntu-latest\n    steps: [ ... ]<\/pre>\n\n<!-- \u4e03\u3001CI \u6700\u4f73\u5b9e\u8df5\u4e0e\u5b89\u5168 -->\n<h2 id=\"zh-7\">\u4e03\u3001CI \u6700\u4f73\u5b9e\u8df5\u4e0e\u5b89\u5168<\/h2>\n\n<h3>7.1 \u6548\u7387\u4f18\u5316<\/h3>\n\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\">\n    <h4>\ud83c\udfaf \u9650\u5b9a\u89e6\u53d1\u8def\u5f84<\/h4>\n    <span class=\"cmd\">paths \/ paths-ignore<\/span>\n    <span class=\"desc\">\u6587\u6863\u53d8\u66f4\u4e0d\u5e94\u89e6\u53d1\u5b8c\u6574 CI\uff0c\u7528 paths \u8fc7\u6ee4<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udcbe \u542f\u7528\u7f13\u5b58<\/h4>\n    <span class=\"cmd\">cache: &#8216;npm&#8217;<\/span>\n    <span class=\"desc\">\u7f13\u5b58\u4f9d\u8d56\u53ef\u5c06 CI \u65f6\u95f4\u7f29\u77ed 50-70%<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd00 \u5e76\u884c Job<\/h4>\n    <span class=\"cmd\">needs: []<\/span>\n    <span class=\"desc\">\u65e0\u4f9d\u8d56\u7684 Job \u5e76\u884c\u6267\u884c\uff0c\u7f29\u77ed\u603b\u65f6\u95f4<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udce6 \u77e9\u9635\u5206\u7247<\/h4>\n    <span class=\"cmd\">matrix.shard<\/span>\n    <span class=\"desc\">\u5927\u91cf\u6d4b\u8bd5\u7528\u4f8b\u5206\u7247\u5e76\u884c\u8fd0\u884c<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u23f1\ufe0f \u8d85\u65f6\u8bbe\u7f6e<\/h4>\n    <span class=\"cmd\">timeout-minutes: 15<\/span>\n    <span class=\"desc\">\u907f\u514d\u5361\u6b7b Job \u6d6a\u8d39\u989d\u5ea6<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd04 \u53ef\u590d\u7528\u5de5\u4f5c\u6d41<\/h4>\n    <span class=\"cmd\">workflow_call<\/span>\n    <span class=\"desc\">\u591a\u4ed3\u5e93\u5171\u4eab CI \u914d\u7f6e\uff0c\u51cf\u5c11\u91cd\u590d<\/span>\n  <\/div>\n<\/div>\n\n<h3>7.2 \u5b89\u5168\u5b9e\u8df5<\/h3>\n\n<div class=\"info-box danger\">\n  <strong>\u5b89\u5168\u8b66\u544a\uff1a<\/strong>CI \u5de5\u4f5c\u6d41\u901a\u5e38\u62e5\u6709\u8bbf\u95ee\u4ee3\u7801\u4ed3\u5e93\u3001\u5bc6\u94a5\u548c\u90e8\u7f72\u73af\u5883\u7684\u6743\u9650\u3002\u914d\u7f6e\u4e0d\u5f53\u53ef\u80fd\u5bfc\u81f4\u5bc6\u94a5\u6cc4\u9732\u6216\u4f9b\u5e94\u94fe\u653b\u51fb\u3002\u4ee5\u4e0b\u5b89\u5168\u63aa\u65bd\u662f\u5fc5\u987b\u7684<sup><a href=\"#cite-6\">[6]<\/a><\/sup>\u3002\n<\/div>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># 1. \u6700\u5c0f\u6743\u9650\u539f\u5219<\/span>\npermissions:\n  contents: read\n  security-events: write\n  pull-requests: write\n\n<span style=\"color:var(--muted)\"># 2. \u4f7f\u7528 Secrets \u7ba1\u7406\u654f\u611f\u4fe1\u606f<\/span>\nenv:\n  API_KEY: ${{ secrets.API_KEY }}      <span style=\"color:var(--muted)\"># \u52a0\u5bc6\u5b58\u50a8<\/span>\n  DB_URL: ${{ secrets.PROD_DB_URL }}\n\n<span style=\"color:var(--muted)\"># 3. \u56fa\u5b9a Action \u7248\u672c\uff08\u4f7f\u7528 commit SHA \u800c\u975e\u6807\u7b7e\uff09<\/span>\n- uses: actions\/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  <span style=\"color:var(--muted)\"># SHA \u9501\u5b9a<\/span>\n  <span style=\"color:var(--muted)\"># \u800c\u975e uses: actions\/checkout@v4\uff08\u6807\u7b7e\u53ef\u88ab\u79fb\u52a8\uff09<\/span>\n\n<span style=\"color:var(--muted)\"># 4. \u7b2c\u4e09\u65b9 Action \u5ba1\u8ba1<\/span>\n<span style=\"color:var(--muted)\"># \u4f7f\u7528\u524d\u68c0\u67e5 Action \u7684\u4ed3\u5e93 star \u6570\u3001\u7ef4\u62a4\u72b6\u6001\u3001\u4ee3\u7801\u5ba1\u67e5<\/span>\n\n<span style=\"color:var(--muted)\"># 5. \u4f7f\u7528 Environment \u4fdd\u62a4\u90e8\u7f72<\/span>\njobs:\n  deploy:\n    environment: production            <span style=\"color:var(--muted)\"># \u9700\u8981\u5ba1\u6279\u624d\u80fd\u8fd0\u884c<\/span>\n    runs-on: ubuntu-latest<\/pre>\n\n<div class=\"compare-box\">\n  <div class=\"compare-col good\">\n    <h4>\u2705 \u5b89\u5168\u6700\u4f73\u5b9e\u8df5<\/h4>\n    <p>\u4f7f\u7528 <code>permissions<\/code> \u58f0\u660e\u6700\u5c0f\u6743\u9650<\/p>\n    <p>\u5bc6\u94a5\u901a\u8fc7 <code>secrets<\/code> \u5f15\u7528\uff0c\u4e0d\u786c\u7f16\u7801<\/p>\n    <p>\u4f7f\u7528 Environment \u4fdd\u62a4\u751f\u4ea7\u90e8\u7f72<\/p>\n    <p>\u56fa\u5b9a Action \u5230 commit SHA<\/p>\n    <p>\u5b9a\u671f\u5ba1\u8ba1\u7b2c\u4e09\u65b9 Action \u4f9d\u8d56<\/p>\n    <p>\u542f\u7528\u4f9d\u8d56\u6f0f\u6d1e\u81ea\u52a8\u626b\u63cf\uff08Dependabot\uff09<\/p>\n  <\/div>\n  <div class=\"compare-col bad\">\n    <h4>\u274c \u5371\u9669\u505a\u6cd5<\/h4>\n    <p>\u4f7f\u7528\u9ed8\u8ba4\u7684 <code>write<\/code> \u6743\u9650<\/p>\n    <p>\u5c06\u5bc6\u94a5\u660e\u6587\u5199\u5165 YAML \u6216\u73af\u5883\u53d8\u91cf<\/p>\n    <p>\u4efb\u4f55\u4eba\u90fd\u80fd\u89e6\u53d1\u751f\u4ea7\u90e8\u7f72<\/p>\n    <p>\u4f7f\u7528 <code>@main<\/code> \u6216 <code>@latest<\/code> \u6807\u7b7e<\/p>\n    <p>\u4e0d\u5ba1\u67e5\u76f4\u63a5\u4f7f\u7528\u964c\u751f Action<\/p>\n    <p>\u5ffd\u7565\u4f9d\u8d56\u6f0f\u6d1e\u544a\u8b66<\/p>\n  <\/div>\n<\/div>\n\n<h3>7.3 \u6d4b\u8bd5\u5206\u5c42\u7b56\u7565<\/h3>\n\n<figure id=\"fig-6\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item\" style=\"min-width:400px;background:var(--accent)\">\u5355\u5143\u6d4b\u8bd5\uff08\u5feb\u901f\u3001\u5927\u91cf\u3001CI \u5fc5\u8dd1\uff09<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item green\" style=\"min-width:340px\">\u96c6\u6210\u6d4b\u8bd5\uff08\u4e2d\u901f\u3001CI \u5fc5\u8dd1\uff09<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item yellow\" style=\"min-width:240px;color:#1a1a2e\">\u7aef\u5230\u7aef\u6d4b\u8bd5\uff08\u6162\u3001\u591c\u95f4\u8dd1\uff09<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 6<\/strong> \u6d4b\u8bd5\u91d1\u5b57\u5854\uff1aCI \u5e94\u4f18\u5148\u8fd0\u884c\u5feb\u901f\u6d4b\u8bd5\uff0c\u6162\u6d4b\u8bd5\u653e\u5230\u591c\u95f4\u6267\u884c<\/figcaption>\n<\/figure>\n\n<!-- \u516b\u3001\u5e38\u89c1\u9677\u9631\u4e0e\u89e3\u51b3\u65b9\u6848 -->\n<h2 id=\"zh-8\">\u516b\u3001\u5e38\u89c1\u9677\u9631\u4e0e\u89e3\u51b3\u65b9\u6848<\/h2>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>\u8868 4<\/strong> CI \u5e38\u89c1\u9677\u9631\u4e0e\u89e3\u51b3\u65b9\u6848<\/caption>\n    <thead>\n      <tr><th>\u9677\u9631<\/th><th>\u95ee\u9898\u8868\u73b0<\/th><th>\u89e3\u51b3\u65b9\u6848<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>CI \u65f6\u95f4\u8fc7\u957f<\/td>\n        <td>\u6784\u5efa\u8d85\u8fc7 30 \u5206\u949f\uff0c\u5f00\u53d1\u8005\u5ffd\u7565\u53cd\u9988<\/td>\n        <td>\u5e76\u884c Job\u3001\u542f\u7528\u7f13\u5b58\u3001\u5206\u7247\u6d4b\u8bd5\u3001\u5206\u5c42\u6d4b\u8bd5\u7b56\u7565<\/td>\n      <\/tr>\n      <tr>\n        <td>\u4e0d\u7a33\u5b9a\u6d4b\u8bd5<\/td>\n        <td>\u540c\u4e00\u4ee3\u7801\u6709\u65f6\u901a\u8fc7\u6709\u65f6\u5931\u8d25<\/td>\n        <td>\u9694\u79bb\u6d4b\u8bd5\u73af\u5883\u3001\u4fee\u590d\u7ade\u6001\u6761\u4ef6\u3001\u6807\u8bb0\u5e76\u8ffd\u8e2a flaky test<\/td>\n      <\/tr>\n      <tr>\n        <td>\u5bc6\u94a5\u6cc4\u9732<\/td>\n        <td>\u5bc6\u94a5\u51fa\u73b0\u5728\u65e5\u5fd7\u6216\u5236\u54c1\u4e2d<\/td>\n        <td>\u4f7f\u7528 secrets\u3001\u914d\u7f6e <code>permissions<\/code>\u3001\u542f\u7528\u5bc6\u94a5\u626b\u63cf<\/td>\n      <\/tr>\n      <tr>\n        <td>\u8fc7\u5ea6\u89e6\u53d1<\/td>\n        <td>\u6587\u6863\u4fee\u6539\u4e5f\u89e6\u53d1\u5b8c\u6574 CI\uff0c\u6d6a\u8d39\u989d\u5ea6<\/td>\n        <td>\u4f7f\u7528 <code>paths-ignore<\/code> \u8fc7\u6ee4\u975e\u4ee3\u7801\u53d8\u66f4<\/td>\n      <\/tr>\n      <tr>\n        <td>\u5355\u70b9\u6545\u969c<\/td>\n        <td>\u6240\u6709\u6b65\u9aa4\u5728\u4e00\u4e2a Job \u4e2d\uff0c\u4e00\u6b65\u5931\u8d25\u5168\u90e8\u91cd\u6765<\/td>\n        <td>\u62c6\u5206\u4e3a\u591a\u4e2a\u72ec\u7acb Job\uff0c\u5229\u7528 <code>needs<\/code> \u4e32\u8054<\/td>\n      <\/tr>\n      <tr>\n        <td>\u5ffd\u7565 CI \u5931\u8d25<\/td>\n        <td>\u56e2\u961f\u4e60\u60ef\u6027\u5ffd\u7565\u7ea2\u8272\u6784\u5efa<\/td>\n        <td>\u5f3a\u5236 PR \u68c0\u67e5\u901a\u8fc7\u624d\u80fd\u5408\u5e76\uff08Branch Protection Rules\uff09<\/td>\n      <\/tr>\n      <tr>\n        <td>\u73af\u5883\u4e0d\u4e00\u81f4<\/td>\n        <td>CI \u901a\u8fc7\u4f46\u672c\u5730\/\u751f\u4ea7\u5931\u8d25<\/td>\n        <td>\u4f7f\u7528 Docker \u5bb9\u5668\u7edf\u4e00\u73af\u5883\uff0cCI \u4e0e\u751f\u4ea7\u73af\u5883\u4e00\u81f4<\/td>\n      <\/tr>\n      <tr>\n        <td>\u7f13\u5b58\u8fc7\u671f<\/td>\n        <td>\u7f13\u5b58\u672a\u547d\u4e2d\u5bfc\u81f4\u6bcf\u6b21\u5b8c\u6574\u4e0b\u8f7d<\/td>\n        <td>\u7f13\u5b58\u952e\u5305\u542b\u4f9d\u8d56\u6587\u4ef6\u54c8\u5e0c\uff0c\u786e\u4fdd\u6b63\u786e\u5237\u65b0<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<div class=\"info-box warn\">\n  <strong>\u5173\u952e\u5efa\u8bae\uff1a<\/strong>\u542f\u7528 GitHub \u7684 <strong>Branch Protection Rules<\/strong>\uff0c\u8981\u6c42 PR \u5fc5\u987b\u901a\u8fc7 CI \u68c0\u67e5\u624d\u80fd\u5408\u5e76\u3002\u8fd9\u662f\u786e\u4fdd CI \u4ef7\u503c\u843d\u5730\u7684\u5236\u5ea6\u4fdd\u969c\u2014\u2014\u5982\u679c CI \u7ed3\u679c\u53ef\u4ee5\u88ab\u5ffd\u7565\uff0cCI \u5c31\u5f62\u540c\u865a\u8bbe\u3002\n<\/div>\n\n<!-- \u4e5d\u3001CI \u4e0e CD \u7684\u5173\u7cfb -->\n<h2 id=\"zh-9\">\u4e5d\u3001CI \u4e0e CD \u7684\u5173\u7cfb<\/h2>\n\n<p>CI \u662f CI\/CD \u6d41\u6c34\u7ebf\u7684\u524d\u534a\u90e8\u5206\u3002\u7406\u89e3 CI \u4e0e CD \u7684\u533a\u522b\u548c\u8054\u7cfb\uff0c\u6709\u52a9\u4e8e\u6784\u5efa\u5b8c\u6574\u7684\u81ea\u52a8\u5316\u4ea4\u4ed8\u4f53\u7cfb<sup><a href=\"#cite-4\">[4]<\/a><\/sup>\uff1a<\/p>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>\u8868 5<\/strong> CI\u3001Continuous Delivery \u4e0e Continuous Deployment \u5bf9\u6bd4<\/caption>\n    <thead>\n      <tr><th>\u7ef4\u5ea6<\/th><th>CI\uff08\u6301\u7eed\u96c6\u6210\uff09<\/th><th>Continuous Delivery\uff08\u6301\u7eed\u4ea4\u4ed8\uff09<\/th><th>Continuous Deployment\uff08\u6301\u7eed\u90e8\u7f72\uff09<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td>\u6838\u5fc3\u76ee\u6807<\/td><td>\u9a8c\u8bc1\u4ee3\u7801\u6b63\u786e\u6027<\/td><td>\u786e\u4fdd\u4ee3\u7801\u53ef\u968f\u65f6\u53d1\u5e03<\/td><td>\u81ea\u52a8\u53d1\u5e03\u5230\u751f\u4ea7<\/td><\/tr>\n      <tr><td>\u81ea\u52a8\u5316\u8303\u56f4<\/td><td>\u6784\u5efa + \u6d4b\u8bd5<\/td><td>\u6784\u5efa + \u6d4b\u8bd5 + \u53d1\u5e03\u51c6\u5907<\/td><td>\u5168\u6d41\u7a0b\u81ea\u52a8\u5316<\/td><\/tr>\n      <tr><td>\u4eba\u5de5\u4ecb\u5165<\/td><td>\u5f00\u53d1\u8005\u63d0\u4ea4\u4ee3\u7801<\/td><td>\u4eba\u5de5\u70b9\u51fb&#8221;\u53d1\u5e03&#8221;\u6309\u94ae<\/td><td>\u65e0\uff08\u901a\u8fc7\u6d4b\u8bd5\u5373\u53d1\u5e03\uff09<\/td><\/tr>\n      <tr><td>\u53d1\u5e03\u9891\u7387<\/td><td>\u4e0d\u6d89\u53ca\u53d1\u5e03<\/td><td>\u6309\u9700\uff08\u6bcf\u5929\/\u6bcf\u5468\uff09<\/td><td>\u9ad8\u9891\uff08\u6bcf\u5929\u591a\u6b21\uff09<\/td><\/tr>\n      <tr><td>\u98ce\u9669\u7b49\u7ea7<\/td><td>\u4f4e\uff08\u4ec5\u5f71\u54cd CI \u73af\u5883\uff09<\/td><td>\u4e2d\uff08\u5f71\u54cd\u9884\u53d1\u5e03\u73af\u5883\uff09<\/td><td>\u9ad8\uff08\u76f4\u63a5\u5f71\u54cd\u7528\u6237\uff09<\/td><\/tr>\n      <tr><td>\u9002\u7528\u573a\u666f<\/td><td>\u6240\u6709\u9879\u76ee<\/td><td>\u9700\u8981\u53d1\u5e03\u5ba1\u6279\u7684\u9879\u76ee<\/td><td>\u6210\u719f\u56e2\u961f\u3001\u9ad8\u81ea\u52a8\u5316\u9879\u76ee<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<figure id=\"fig-7\">\n  <div class=\"pipeline-viz\">\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd00<\/div>\n      <div class=\"stage-name\">\u63d0\u4ea4<\/div>\n      <div class=\"stage-desc\">CI \u8d77\u70b9<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd28<\/div>\n      <div class=\"stage-name\">\u6784\u5efa<\/div>\n      <div class=\"stage-desc\">\u7f16\u8bd1\u6253\u5305<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83e\uddea<\/div>\n      <div class=\"stage-name\">\u6d4b\u8bd5<\/div>\n      <div class=\"stage-desc\">CI \u7ec8\u70b9<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udce6<\/div>\n      <div class=\"stage-name\">\u6682\u5b58<\/div>\n      <div class=\"stage-desc\">CD \u8d77\u70b9<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\u2705<\/div>\n      <div class=\"stage-name\">\u5ba1\u6279<\/div>\n      <div class=\"stage-desc\">Delivery<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\ude80<\/div>\n      <div class=\"stage-name\">\u53d1\u5e03<\/div>\n      <div class=\"stage-desc\">Deployment<\/div>\n    <\/div>\n  <\/div>\n  <figcaption><strong>\u56fe 7<\/strong> CI\/CD \u5168\u6d41\u7a0b\uff1aCI \u8d1f\u8d23\u524d\u4e09\u4e2a\u9636\u6bb5\uff0cCD \u8d1f\u8d23\u540e\u7eed\u53d1\u5e03\u6d41\u7a0b<\/figcaption>\n<\/figure>\n\n<!-- \u5341\u3001\u5b8c\u6574\u5b9e\u6218\u793a\u4f8b -->\n<h2 id=\"zh-10\">\u5341\u3001\u5b8c\u6574\u5b9e\u6218\u793a\u4f8b<\/h2>\n\n<h3>10.1 \u5168\u6808\u9879\u76ee CI \u914d\u7f6e<\/h3>\n\n<p>\u4ee5\u4e0b\u662f\u4e00\u4e2a\u6db5\u76d6\u524d\u540e\u7aef\u7684\u5b8c\u6574 CI \u914d\u7f6e\uff0c\u6574\u5408\u4e86\u672c\u6587\u4ecb\u7ecd\u7684\u6240\u6709\u6838\u5fc3\u6982\u5ff5\uff1a<\/p>\n\n<pre class=\"code-block\">name: Full-Stack CI\n\non:\n  push:\n    branches: [ main, develop ]\n    paths-ignore: [ 'docs\/**', '*.md' ]\n  pull_request:\n    branches: [ main ]\n\npermissions:\n  contents: read\n  security-events: write\n  pull-requests: write\n\nenv:\n  NODE_VERSION: \"20\"\n  PYTHON_VERSION: \"3.12\"\n\njobs:\n  <span style=\"color:var(--accent)\"># \u2500\u2500 \u524d\u7aef \u2500\u2500<\/span>\n  frontend-lint:\n    name: Frontend Lint\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n          cache-dependency-path: frontend\/package-lock.json\n      - run: cd frontend && npm ci\n      - run: cd frontend && npm run lint\n      - run: cd frontend && npm run type-check\n\n  frontend-test:\n    name: Frontend Test\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    strategy:\n      fail-fast: false\n      matrix:\n        shard: [1, 2, 3, 4]\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n          cache-dependency-path: frontend\/package-lock.json\n      - run: cd frontend && npm ci\n      - run: cd frontend && npx vitest run --shard=${{ matrix.shard }}\/4\n\n  frontend-build:\n    name: Frontend Build\n    needs: [frontend-lint, frontend-test]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n          cache-dependency-path: frontend\/package-lock.json\n      - run: cd frontend && npm ci\n      - run: cd frontend && npm run build\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: frontend-dist\n          path: frontend\/dist\/\n\n  <span style=\"color:var(--accent2)\"># \u2500\u2500 \u540e\u7aef \u2500\u2500<\/span>\n  backend-test:\n    name: Backend Test\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_PASSWORD: test\n        ports: [ '5432:5432' ]\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ env.Python_VERSION }}\n      - run: pip install -r backend\/requirements.txt\n      - run: cd backend && pytest --cov --cov-report=xml\n        env:\n          DATABASE_URL: postgresql:\/\/postgres:test@localhost:5432\/test\n      - uses: codecov\/codecov-action@v4\n        with:\n          file: backend\/coverage.xml\n\n  <span style=\"color:var(--accent4)\"># \u2500\u2500 \u5b89\u5168\u626b\u63cf \u2500\u2500<\/span>\n  security-scan:\n    name: Security Scan\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Run CodeQL\n        uses: github\/codeql-action\/init@v3\n        with:\n          languages: javascript, python\n      - uses: github\/codeql-action\/analyze@v3\n      - name: Dependency scan\n        uses: aquasecurity\/trivy-action@master\n        with:\n          scan-type: fs\n          scan-ref: .\n\n  <span style=\"color:var(--accent5)\"># \u2500\u2500 \u6c47\u603b \u2500\u2500<\/span>\n  report:\n    name: CI Report\n    needs: [frontend-build, backend-test, security-scan]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check results\n        run: |\n          echo \"Frontend Build: ${{ needs.frontend-build.result }}\"\n          echo \"Backend Test: ${{ needs.backend-test.result }}\"\n          echo \"Security: ${{ needs.security-scan.result }}\"\n          if [[ \"${{ needs.frontend-build.result }}\" != \"success\" ]] || \\\n             [[ \"${{ needs.backend-test.result }}\" != \"success\" ]] || \\\n             [[ \"${{ needs.security-scan.result }}\" != \"success\" ]]; then\n            exit 1\n          fi<\/pre>\n\n<h3>10.2 \u5feb\u901f\u542f\u52a8\u6e05\u5355<\/h3>\n\n<ol class=\"steps\">\n  <li>\n    <strong>\u521b\u5efa\u5de5\u4f5c\u6d41\u76ee\u5f55<\/strong>\n    <span class=\"cmd\">mkdir -p .github\/workflows<\/span>\n    <span class=\"note\">\u5728\u4ed3\u5e93\u6839\u76ee\u5f55\u521b\u5efa GitHub Actions \u914d\u7f6e\u76ee\u5f55<\/span>\n  <\/li>\n  <li>\n    <strong>\u7f16\u5199 CI \u914d\u7f6e\u6587\u4ef6<\/strong>\n    <span class=\"cmd\">touch .github\/workflows\/ci.yml<\/span>\n    <span class=\"note\">\u53c2\u8003\u7b2c\u4e94\u7ae0\u7684\u6a21\u677f\u7f16\u5199 YAML \u914d\u7f6e<\/span>\n  <\/li>\n  <li>\n    <strong>\u63d0\u4ea4\u5e76\u63a8\u9001<\/strong>\n    <span class=\"cmd\">git add .github\/workflows\/ci.yml &#038;&#038; git commit -m &#8220;Add CI&#8221; &#038;&#038; git push<\/span>\n    <span class=\"note\">\u63a8\u9001\u540e GitHub \u81ea\u52a8\u8bc6\u522b\u5e76\u6267\u884c\u5de5\u4f5c\u6d41<\/span>\n  <\/li>\n  <li>\n    <strong>\u67e5\u770b\u8fd0\u884c\u7ed3\u679c<\/strong>\n    <span class=\"cmd\">\u5728 GitHub \u4ed3\u5e93\u9875\u9762\u70b9\u51fb Actions \u6807\u7b7e\u9875<\/span>\n    <span class=\"note\">\u5b9e\u65f6\u67e5\u770b\u6bcf\u4e2a Job \u548c Step \u7684\u6267\u884c\u72b6\u6001\u4e0e\u65e5\u5fd7<\/span>\n  <\/li>\n  <li>\n    <strong>\u914d\u7f6e\u5206\u652f\u4fdd\u62a4<\/strong>\n    <span class=\"cmd\">Settings \u2192 Branches \u2192 Add rule<\/span>\n    <span class=\"note\">\u8981\u6c42 CI \u901a\u8fc7\u624d\u80fd\u5408\u5e76 PR\uff0c\u786e\u4fdd CI \u4ef7\u503c\u843d\u5730<\/span>\n  <\/li>\n<\/ol>\n\n<div class=\"info-box success\">\n  <strong>\u603b\u7ed3\uff1a<\/strong>CI \u7684\u672c\u8d28\u662f&#8221;\u9891\u7e41\u96c6\u6210 + \u81ea\u52a8\u5316\u9a8c\u8bc1&#8221;\u3002GitHub Actions \u8ba9 CI \u914d\u7f6e\u53d8\u5f97\u58f0\u660e\u5f0f\u3001\u53ef\u7248\u672c\u5316\u3001\u4e0e\u4ee3\u7801\u4ed3\u5e93\u6df1\u5ea6\u96c6\u6210\u3002\u638c\u63e1 YAML \u8bed\u6cd5\u3001\u77e9\u9635\u7b56\u7565\u3001\u7f13\u5b58\u673a\u5236\u548c\u5b89\u5168\u5b9e\u8df5\uff0c\u5c31\u80fd\u6784\u5efa\u51fa\u9ad8\u6548\u3001\u5b89\u5168\u3001\u53ef\u7ef4\u62a4\u7684 CI \u6d41\u6c34\u7ebf\uff0c\u4ece\u6839\u672c\u4e0a\u63d0\u5347\u56e2\u961f\u7684\u5f00\u53d1\u6548\u7387\u548c\u4ee3\u7801\u8d28\u91cf\u3002\n<\/div>\n\n<\/div><!-- end lang-zh -->\n\n<!-- ======== English Version ======== -->\n<div class=\"lang-section\" id=\"lang-en\">\n<div class=\"hero\">\n  <h1>Complete Guide to CI &#038; GitHub Actions<span class=\"sub\">From Core CI Concepts to GitHub Actions Practical Configuration<\/span><\/h1>\n  <p>Understand Continuous Integration (CI) principles, value, and practices; master GitHub Actions workflow configuration, YAML syntax, matrix testing, and caching strategies<\/p>\n<\/div>\n\n<div class=\"toc\">\n  <h3>Contents<\/h3>\n  <ol>\n    <li><a href=\"#en-1\">1. CI Overview: What is Continuous Integration<\/a><\/li>\n    <li><a href=\"#en-2\">2. Core Value and Principles of CI<\/a><\/li>\n    <li><a href=\"#en-3\">3. GitHub Actions Fundamentals<\/a><\/li>\n    <li><a href=\"#en-4\">4. Workflow YAML Structure Deep Dive<\/a><\/li>\n    <li><a href=\"#en-5\">5. Building Your First CI Pipeline<\/a><\/li>\n    <li><a href=\"#en-6\">6. Advanced Features: Matrix, Caching &#038; Artifacts<\/a><\/li>\n    <li><a href=\"#en-7\">7. CI Best Practices and Security<\/a><\/li>\n    <li><a href=\"#en-8\">8. Common Pitfalls and Solutions<\/a><\/li>\n    <li><a href=\"#en-9\">9. Relationship Between CI and CD<\/a><\/li>\n    <li><a href=\"#en-10\">10. Complete Practical Example<\/a><\/li>\n  <\/ol>\n<\/div>\n\n<!-- 1. CI Overview -->\n<h2 id=\"en-1\">1. CI Overview: What is Continuous Integration<\/h2>\n\n<p><strong>Continuous Integration<\/strong> (CI) is a software development practice where developers frequently merge code changes into a shared repository\u2014often multiple times a day. Each merge triggers an automated build and test sequence that verifies the new code works correctly with the existing codebase<sup><a href=\"#cite-1\">[1]<\/a><\/sup>.<\/p>\n\n<div class=\"info-box\">\n  <strong>Core Definition:<\/strong> The essence of CI is &#8220;frequent integration + automated verification.&#8221; It is not a specific tool but an engineering practice\u2014using automation to catch integration problems before code changes enter the main branch, rather than dealing with them right before release.\n<\/div>\n\n<h3>1.1 The Core Problem CI Solves<\/h3>\n\n<p>In traditional development, developers might work on separate branches for weeks before attempting to merge. This &#8220;deferred integration&#8221; leads to severe <strong>Merge Hell<\/strong>: massive code conflicts, deeply buried integration errors, difficult debugging, and delayed releases. CI fundamentally changes this through a &#8220;small and frequent&#8221; integration strategy<sup><a href=\"#cite-2\">[2]<\/a><\/sup>.<\/p>\n\n<figure id=\"fig-1-en\">\n  <div class=\"flow-diagram\" style=\"border:none;box-shadow:none;padding:0\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item\">Developer Push<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item purple\">Trigger CI<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item\">Auto Build<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item green\">Auto Test<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item yellow\">Quality Check<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item green\">Pass\/Fail Feedback<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 1<\/strong> Basic CI workflow: the automated loop from code commit to feedback<\/figcaption>\n<\/figure>\n\n<h3>1.2 Historical Evolution of CI<\/h3>\n\n<p>The concept of CI was systematically introduced by Martin Fowler and the ThoughtWorks team in the early 2000s. Its evolution has gone through several key stages<sup><a href=\"#cite-3\">[3]<\/a><\/sup>:<\/p>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>Table 1<\/strong> Historical evolution of CI tools and concepts<\/caption>\n    <thead>\n      <tr><th>Era<\/th><th>Stage<\/th><th>Representative Tools<\/th><th>Key Characteristics<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td>Pre-2000<\/td><td>Manual Integration<\/td><td>Manual builds, scripts<\/td><td>Late integration, many conflicts, manual operations<\/td><\/tr>\n      <tr><td>2001-2010<\/td><td>CI Emergence<\/td><td>CruiseControl, Hudson<\/td><td>Automated builds, daily builds<\/td><\/tr>\n      <tr><td>2011-2018<\/td><td>CI Maturity<\/td><td>Jenkins, Travis CI<\/td><td>Plugin ecosystem, pipeline as code<\/td><\/tr>\n      <tr><td>2019-Present<\/td><td>Cloud-Native CI<\/td><td>GitHub Actions, GitLab CI<\/td><td>Deep repo integration, YAML declarative<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h3>1.3 Core Elements of CI<\/h3>\n\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udccb Version Control<\/h4>\n    <span class=\"desc\">Centralized code management with clear branching strategy (Git Flow, Trunk-based)<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd28 Automated Build<\/h4>\n    <span class=\"desc\">One command to compile and package, no manual intervention<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83e\uddea Automated Testing<\/h4>\n    <span class=\"desc\">Unit tests, integration tests, static analysis run automatically<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd14 Fast Feedback<\/h4>\n    <span class=\"desc\">Build\/test results returned to developers within minutes<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udce6 Artifact Management<\/h4>\n    <span class=\"desc\">Build outputs stored and versioned centrally<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83c\udf0d Consistent Environment<\/h4>\n    <span class=\"desc\">CI environment matches production environment<\/span>\n  <\/div>\n<\/div>\n\n<!-- 2. Core Value and Principles -->\n<h2 id=\"en-2\">2. Core Value and Principles of CI<\/h2>\n\n<h3>2.1 Core Value CI Brings<\/h3>\n\n<p>The value of CI goes beyond &#8220;automation&#8221;\u2014it fundamentally changes team collaboration patterns and code quality assurance<sup><a href=\"#cite-4\">[4]<\/a><\/sup>:<\/p>\n\n<div class=\"two-col\">\n  <div class=\"col\">\n    <h4>\u2705 Early Defect Detection<\/h4>\n    <p>Each commit triggers tests, so problems are found within minutes of introduction, not weeks later during integration. Fix cost is proportional to detection time\u2014earlier is cheaper.<\/p>\n  <\/div>\n  <div class=\"col\">\n    <h4>\u2705 Reduced Integration Conflicts<\/h4>\n    <p>Frequent merges mean each change is small, virtually eliminating conflicts. Teams no longer spend days resolving merge conflicts before releases.<\/p>\n  <\/div>\n<\/div>\n\n<div class=\"two-col\">\n  <div class=\"col\">\n    <h4>\u2705 Continuous Quality Confidence<\/h4>\n    <p>The main branch is always in a &#8220;deployable&#8221; state. You can pull and release from main at any time because CI has verified its correctness.<\/p>\n  <\/div>\n  <div class=\"col\">\n    <h4>\u2705 Accelerated Delivery<\/h4>\n    <p>Automation replaces manual builds and tests, letting developers focus on writing features. Release frequency jumps from &#8220;monthly&#8221; to &#8220;multiple times daily.&#8221;<\/p>\n  <\/div>\n<\/div>\n\n<h3>2.2 Six Fundamental Principles of CI<\/h3>\n\n<figure id=\"fig-2-en\">\n  <div class=\"pipeline-viz\">\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd00<\/div>\n      <div class=\"stage-name\">Single Source<\/div>\n      <div class=\"stage-desc\">One repo for all<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\u26a1<\/div>\n      <div class=\"stage-name\">Frequent Commits<\/div>\n      <div class=\"stage-desc\">At least daily<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83e\udd16<\/div>\n      <div class=\"stage-name\">Auto Build<\/div>\n      <div class=\"stage-desc\">No manual steps<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83e\uddea<\/div>\n      <div class=\"stage-name\">Auto Test<\/div>\n      <div class=\"stage-desc\">Comprehensive<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd14<\/div>\n      <div class=\"stage-name\">Fast Feedback<\/div>\n      <div class=\"stage-desc\">Minutes<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83c\udf10<\/div>\n      <div class=\"stage-name\">Consistent Env<\/div>\n      <div class=\"stage-desc\">Match production<\/div>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 2<\/strong> Six core principles of Continuous Integration<\/figcaption>\n<\/figure>\n\n<div class=\"info-box success\">\n  <strong>Key Metric:<\/strong> A healthy CI pipeline should complete builds and tests within <strong>10 minutes<\/strong>. If it exceeds 30 minutes, developers start ignoring feedback, and CI&#8217;s value drops significantly<sup><a href=\"#cite-2\">[2]<\/a><\/sup>.\n<\/div>\n\n<h3>2.3 Standard CI Pipeline Stages<\/h3>\n\n<p>A complete CI pipeline typically includes these stages, each automated:<\/p>\n\n<ol class=\"steps\">\n  <li>\n    <strong>Checkout<\/strong>\n    <span class=\"note\">Pull latest code from version control to CI environment<\/span>\n  <\/li>\n  <li>\n    <strong>Install Dependencies<\/strong>\n    <span class=\"note\">Install required third-party libraries and toolchains<\/span>\n  <\/li>\n  <li>\n    <strong>Lint<\/strong>\n    <span class=\"note\">Static analysis, code style checks, type checking<\/span>\n  <\/li>\n  <li>\n    <strong>Build<\/strong>\n    <span class=\"note\">Compile source code, package resources, generate executables<\/span>\n  <\/li>\n  <li>\n    <strong>Test<\/strong>\n    <span class=\"note\">Unit tests, integration tests, end-to-end tests<\/span>\n  <\/li>\n  <li>\n    <strong>Security Scan<\/strong>\n    <span class=\"note\">Dependency vulnerability scanning, secret detection, SAST<\/span>\n  <\/li>\n  <li>\n    <strong>Artifact Publishing<\/strong>\n    <span class=\"note\">Upload build outputs to artifact repository for deployment<\/span>\n  <\/li>\n<\/ol>\n\n<!-- 3. GitHub Actions Fundamentals -->\n<h2 id=\"en-3\">3. GitHub Actions Fundamentals<\/h2>\n\n<p><strong>GitHub Actions<\/strong> is GitHub&#8217;s built-in CI\/CD platform, launched in 2018. It integrates directly into GitHub repositories\u2014developers don&#8217;t need to maintain a separate CI server. Simply adding YAML configuration files to the repository enables a complete automation pipeline<sup><a href=\"#cite-5\">[5]<\/a><\/sup>.<\/p>\n\n<h3>3.1 Core Concepts<\/h3>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>Table 2<\/strong> GitHub Actions core concept glossary<\/caption>\n    <thead>\n      <tr><th>Concept<\/th><th>Description<\/th><th>Analogy<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td>Workflow<\/td><td>A complete automation process defined in YAML<\/td><td>A pipeline<\/td><\/tr>\n      <tr><td>Event<\/td><td>Condition that triggers the workflow (push, pull_request)<\/td><td>Start button<\/td><\/tr>\n      <tr><td>Job<\/td><td>An execution unit containing multiple steps<\/td><td>A workstation<\/td><\/tr>\n      <tr><td>Step<\/td><td>A specific action within a job (command or Action)<\/td><td>An operation<\/td><\/tr>\n      <tr><td>Action<\/td><td>A reusable custom component, like a function<\/td><td>A utility function<\/td><\/tr>\n      <tr><td>Runner<\/td><td>The server environment executing jobs<\/td><td>The machine<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h3>3.2 GitHub Actions Architecture<\/h3>\n\n<figure id=\"fig-3-en\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item purple\">Event Trigger<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item\">Workflow<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item green\">Job 1<\/span>\n      <span class=\"flow-arrow\">\u2194<\/span>\n      <span class=\"flow-item green\">Job 2<\/span>\n    <\/div>\n    <div class=\"flow-row\" style=\"margin-top:.5rem\">\n      <span class=\"flow-arrow\">\u2193<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item yellow\">Step 1<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item yellow\">Step 2<\/span>\n      <span class=\"flow-arrow\">\u2192<\/span>\n      <span class=\"flow-item yellow\">Step 3<\/span>\n    <\/div>\n    <div class=\"flow-row\" style=\"margin-top:.5rem\">\n      <span class=\"flow-arrow\">\u2193<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item red\">Runner (ubuntu-latest)<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 3<\/strong> GitHub Actions hierarchy: Event \u2192 Workflow \u2192 Job \u2192 Step \u2192 Runner<\/figcaption>\n<\/figure>\n\n<h3>3.3 Why Choose GitHub Actions<\/h3>\n\n<div class=\"compare-box\">\n  <div class=\"compare-col good\">\n    <h4>\u2705 Advantages<\/h4>\n    <p><strong>Zero ops:<\/strong> No CI server to build or maintain.<\/p>\n    <p><strong>Deep integration:<\/strong> Seamless with repos, PRs, Issues.<\/p>\n    <p><strong>Declarative:<\/strong> YAML is the pipeline, version-controlled.<\/p>\n    <p><strong>Free tier:<\/strong> Unlimited for public repos, 2000 min\/month for private.<\/p>\n    <p><strong>Rich ecosystem:<\/strong> Thousands of Actions on Marketplace.<\/p>\n  <\/div>\n  <div class=\"compare-col bad\">\n    <h4>\u26a0\ufe0f Limitations<\/h4>\n    <p><strong>Runtime limit:<\/strong> Single job max 6 hours.<\/p>\n    <p><strong>Concurrency:<\/strong> Free accounts max 20 concurrent jobs.<\/p>\n    <p><strong>Custom env:<\/strong> Self-hosted runners needed for full control.<\/p>\n    <p><strong>Debugging:<\/strong> Cannot SSH directly into runners.<\/p>\n    <p><strong>Vendor lock-in:<\/strong> Config tied to GitHub platform.<\/p>\n  <\/div>\n<\/div>\n\n<!-- 4. Workflow YAML Structure -->\n<h2 id=\"en-4\">4. Workflow YAML Structure Deep Dive<\/h2>\n\n<p>GitHub Actions workflows are defined using YAML syntax. Understanding the YAML structure is the foundation of writing CI configurations<sup><a href=\"#cite-6\">[6]<\/a><\/sup>.<\/p>\n\n<h3>4.1 Basic Structure<\/h3>\n\n<pre class=\"code-block\">name: CI Pipeline          <span style=\"color:var(--muted)\"># Workflow name (shown in GitHub UI)<\/span>\non:                         <span style=\"color:var(--muted)\"># Trigger events<\/span>\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nenv:                        <span style=\"color:var(--muted)\"># Global environment variables<\/span>\n  NODE_VERSION: \"20\"\n\njobs:                       <span style=\"color:var(--muted)\"># Job definitions<\/span>\n  build:                    <span style=\"color:var(--muted)\"># Job ID<\/span>\n    runs-on: ubuntu-latest  <span style=\"color:var(--muted)\"># Runner environment<\/span>\n    steps:                  <span style=\"color:var(--muted)\"># Step sequence<\/span>\n      - name: Checkout\n        uses: actions\/checkout@v4\n      - name: Setup Node\n        uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n      - name: Install\n        run: npm ci\n      - name: Test\n        run: npm test<\/pre>\n\n<h3>4.2 Core Syntax Elements<\/h3>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>Table 3<\/strong> Workflow YAML core syntax elements<\/caption>\n    <thead>\n      <tr><th>Element<\/th><th>Purpose<\/th><th>Required<\/th><th>Example<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td><code>name<\/code><\/td><td>Workflow display name<\/td><td>Optional<\/td><td><code>name: \"Build & Test\"<\/code><\/td><\/tr>\n      <tr><td><code>on<\/code><\/td><td>Event triggers<\/td><td><strong>Required<\/strong><\/td><td><code>on: [push, pull_request]<\/code><\/td><\/tr>\n      <tr><td><code>jobs<\/code><\/td><td>Job collection<\/td><td><strong>Required<\/strong><\/td><td><code>jobs: build: ...<\/code><\/td><\/tr>\n      <tr><td><code>runs-on<\/code><\/td><td>Runner environment<\/td><td><strong>Required<\/strong><\/td><td><code>runs-on: ubuntu-latest<\/code><\/td><\/tr>\n      <tr><td><code>steps<\/code><\/td><td>Step sequence<\/td><td><strong>Required<\/strong><\/td><td><code>steps: - name: ...<\/code><\/td><\/tr>\n      <tr><td><code>uses<\/code><\/td><td>Reference pre-built Action<\/td><td>Optional<\/td><td><code>uses: actions\/checkout@v4<\/code><\/td><\/tr>\n      <tr><td><code>run<\/code><\/td><td>Execute shell command<\/td><td>Optional<\/td><td><code>run: npm test<\/code><\/td><\/tr>\n      <tr><td><code>with<\/code><\/td><td>Pass parameters to Action<\/td><td>Optional<\/td><td><code>with: node-version: 20<\/code><\/td><\/tr>\n      <tr><td><code>env<\/code><\/td><td>Environment variables<\/td><td>Optional<\/td><td><code>env: CI: true<\/code><\/td><\/tr>\n      <tr><td><code>needs<\/code><\/td><td>Job dependencies<\/td><td>Optional<\/td><td><code>needs: [test]<\/code><\/td><\/tr>\n      <tr><td><code>if<\/code><\/td><td>Conditional execution<\/td><td>Optional<\/td><td><code>if: github.ref == 'refs\/heads\/main'<\/code><\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<h3>4.3 Event Triggers Explained<\/h3>\n\n<pre class=\"code-block\">on:\n  <span style=\"color:var(--muted)\"># Trigger on push to specific branches<\/span>\n  push:\n    branches: [ main, develop ]\n    paths: [ 'src\/**', 'tests\/**' ]        <span style=\"color:var(--muted)\"># Only trigger on specified paths<\/span>\n    paths-ignore: [ 'docs\/**', '*.md' ]     <span style=\"color:var(--muted)\"># Ignore specified paths<\/span>\n\n  <span style=\"color:var(--muted)\"># Pull Request events<\/span>\n  pull_request:\n    branches: [ main ]\n    types: [ opened, synchronize, reopened ]\n\n  <span style=\"color:var(--muted)\"># Scheduled trigger (cron syntax)<\/span>\n  schedule:\n    - cron: \"0 2 * * 1\"   <span style=\"color:var(--muted)\"># Every Monday 2 AM UTC<\/span>\n\n  <span style=\"color:var(--muted)\"># Manual trigger<\/span>\n  workflow_dispatch:\n    inputs:\n      environment:\n        description: 'Deployment environment'\n        default: 'staging'\n        type: choice\n        options: [ staging, production ]\n\n  <span style=\"color:var(--muted)\"># Other repository events<\/span>\n  issues:\n    types: [ opened ]\n  release:\n    types: [ published ]<\/pre>\n\n<h3>4.4 Expressions and Contexts<\/h3>\n\n<p>GitHub Actions uses <code>${{ }}<\/code> syntax to access context variables and evaluate expressions:<\/p>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># Common contexts<\/span>\n${{ github.ref }}              <span style=\"color:var(--muted)\"># Current branch ref<\/span>\n${{ github.event_name }}       <span style=\"color:var(--muted)\"># Triggering event name<\/span>\n${{ github.sha }}              <span style=\"color:var(--muted)\"># Commit SHA<\/span>\n${{ env.NODE_VERSION }}        <span style=\"color:var(--muted)\"># Environment variable<\/span>\n${{ secrets.API_KEY }}         <span style=\"color:var(--muted)\"># Encrypted secret<\/span>\n${{ vars.ENV_NAME }}           <span style=\"color:var(--muted)\"># Repository variable (non-sensitive)<\/span>\n${{ matrix.node }}             <span style=\"color:var(--muted)\"># Matrix variable<\/span>\n${{ steps.build.outputs.ver }} <span style=\"color:var(--muted)\"># Previous step output<\/span>\n\n<span style=\"color:var(--muted)\"># Conditional expressions<\/span>\nif: ${{ github.ref == 'refs\/heads\/main' && success() }}\nif: ${{ failure() }}\nif: ${{ always() }}\nif: ${{ cancelled() }}<\/pre>\n\n<!-- 5. Building First CI Pipeline -->\n<h2 id=\"en-5\">5. Building Your First CI Pipeline<\/h2>\n\n<h3>5.1 Node.js Project CI Example<\/h3>\n\n<p>Here is a complete Node.js CI workflow covering checkout, dependency installation, linting, testing, and building<sup><a href=\"#cite-7\">[7]<\/a><\/sup>:<\/p>\n\n<pre class=\"code-block\">name: Node.js CI\n\non:\n  push:\n    branches: [ main, develop ]\n  pull_request:\n    branches: [ main ]\n\nenv:\n  NODE_VERSION: \"20\"\n  CI: true\n\njobs:\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run lint\n        run: npm run lint\n\n      - name: Run tests\n        run: npm run test:coverage\n        env:\n          NODE_ENV: test\n\n      - name: Upload coverage\n        uses: actions\/upload-artifact@v4\n        if: always()\n        with:\n          name: coverage-report\n          path: coverage\/\n          retention-days: 30\n\n  build:\n    name: Build\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n      - run: npm ci\n      - run: npm run build\n      - name: Upload build artifact\n        uses: actions\/upload-artifact@v4\n        with:\n          name: dist\n          path: dist\/<\/pre>\n\n<h3>5.2 Python Project CI Example<\/h3>\n\n<pre class=\"code-block\">name: Python CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\"]\n\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Setup Python\n        uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Cache pip\n        uses: actions\/cache@v4\n        with:\n          path: ~\/.cache\/pip\n          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n          restore-keys: ${{ runner.os }}-pip-\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r requirements.txt\n          pip install ruff pytest\n\n      - name: Lint with ruff\n        run: ruff check .\n\n      - name: Test with pytest\n        run: pytest --cov=src --cov-report=xml\n\n      - name: Upload coverage\n        uses: codecov\/codecov-action@v4\n        if: always()\n        with:\n          file: .\/coverage.xml<\/pre>\n\n<h3>5.3 File Placement<\/h3>\n\n<div class=\"info-box\">\n  <strong>Directory Structure:<\/strong> Workflow files must be placed in the <code>.github\/workflows\/<\/code> directory at the repository root, with filenames ending in <code>.yml<\/code> or <code>.yaml<\/code>. GitHub automatically detects and executes them.\n<\/div>\n\n<pre class=\"code-block\">my-project\/\n\u251c\u2500\u2500 .github\/\n\u2502   \u2514\u2500\u2500 workflows\/\n\u2502       \u251c\u2500\u2500 ci.yml              <span style=\"color:var(--muted)\"># Main CI workflow<\/span>\n\u2502       \u251c\u2500\u2500 deploy.yml          <span style=\"color:var(--muted)\"># Deployment workflow<\/span>\n\u2502       \u2514\u2500\u2500 nightly-tests.yml   <span style=\"color:var(--muted)\"># Nightly full tests<\/span>\n\u251c\u2500\u2500 src\/\n\u251c\u2500\u2500 tests\/\n\u251c\u2500\u2500 package.json\n\u2514\u2500\u2500 README.md<\/pre>\n\n<!-- 6. Advanced Features -->\n<h2 id=\"en-6\">6. Advanced Features: Matrix, Caching &#038; Artifacts<\/h2>\n\n<h3>6.1 Matrix Testing Strategy<\/h3>\n\n<p>The matrix strategy allows a single job definition to automatically generate multiple run instances for testing different OS, language versions, or configuration combinations<sup><a href=\"#cite-8\">[8]<\/a><\/sup>:<\/p>\n\n<pre class=\"code-block\">jobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false        <span style=\"color:var(--muted)\"># Don't cancel others on failure<\/span>\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        node: [18, 20, 22]\n        exclude:\n          - os: macos-latest\n            node: 18          <span style=\"color:var(--muted)\"># Exclude specific combos<\/span>\n        include:\n          - os: ubuntu-latest\n            node: 22\n            experimental: true\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n      - run: npm ci\n      - run: npm test<\/pre>\n\n<figure id=\"fig-4-en\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item\">Ubuntu + Node 18<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item green\">Ubuntu + Node 20<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item yellow\">Ubuntu + Node 22<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item\">Windows + Node 18<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item green\">Windows + Node 20<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item yellow\">Windows + Node 22<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item green\">macOS + Node 20<\/span>\n      <span class=\"flow-arrow\">|<\/span>\n      <span class=\"flow-item yellow\">macOS + Node 22<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 4<\/strong> Matrix strategy expanded into parallel test combinations (8 parallel jobs)<\/figcaption>\n<\/figure>\n\n<h3>6.2 Dependency Caching<\/h3>\n\n<p>Caching can significantly reduce CI runtime by avoiding re-downloading dependencies each time<sup><a href=\"#cite-9\">[9]<\/a><\/sup>:<\/p>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># Method 1: Built-in caching in setup-node (recommended)<\/span>\n- uses: actions\/setup-node@v4\n  with:\n    node-version: 20\n    cache: 'npm'              <span style=\"color:var(--muted)\"># Auto-cache npm<\/span>\n\n<span style=\"color:var(--muted)\"># Method 2: Manual caching with actions\/cache<\/span>\n- name: Cache pip packages\n  uses: actions\/cache@v4\n  with:\n    path: ~\/.cache\/pip\n    key: ${{ runner.os }}-pip-${{ hashFiles('**\/requirements.txt') }}\n    restore-keys: |\n      ${{ runner.os }}-pip-\n\n<span style=\"color:var(--muted)\"># Method 3: Cache Maven dependencies<\/span>\n- name: Cache Maven\n  uses: actions\/cache@v4\n  with:\n    path: ~\/.m2\/repository\n    key: ${{ runner.os }}-maven-${{ hashFiles('**\/pom.xml') }}<\/pre>\n\n<div class=\"info-box success\">\n  <strong>Cache Effect:<\/strong> First run is a cache miss, downloading all dependencies (may take 2-3 minutes). Subsequent runs hit the cache, restoring dependencies in 10-30 seconds. The <strong>cache key<\/strong> should include the hash of dependency files to auto-refresh when dependencies change.\n<\/div>\n\n<h3>6.3 Build Artifact Management<\/h3>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># Upload build artifact<\/span>\n- name: Upload build artifact\n  uses: actions\/upload-artifact@v4\n  with:\n    name: dist-${{ github.sha }}\n    path: |\n      dist\/\n      package.json\n    retention-days: 30        <span style=\"color:var(--muted)\"># Retain for 30 days<\/span>\n\n<span style=\"color:var(--muted)\"># Download artifact in another job<\/span>\n- name: Download artifact\n  uses: actions\/download-artifact@v4\n  with:\n    name: dist-${{ github.sha }}\n    path: .\/build<\/pre>\n\n<h3>6.4 Reusable Workflows<\/h3>\n\n<p>Reusable workflows allow encapsulating common CI logic into a standalone file, then calling it from multiple repositories or workflows, achieving the DRY principle<sup><a href=\"#cite-10\">[10]<\/a><\/sup>:<\/p>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># .github\/workflows\/reusable-test.yml<\/span>\nname: Reusable Test Workflow\non:\n  workflow_call:              <span style=\"color:var(--muted)\"># Declare as callable<\/span>\n    inputs:\n      python-version:\n        required: false\n        default: \"3.12\"\n        type: string\n    secrets:\n      API_KEY:\n        required: false\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ inputs.python-version }}\n      - run: pip install -r requirements.txt\n      - run: pytest<\/pre>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># Call the reusable workflow<\/span>\njobs:\n  call-tests:\n    uses: .\/.github\/workflows\/reusable-test.yml\n    with:\n      python-version: \"3.11\"\n    secrets:\n      API_KEY: ${{ secrets.API_KEY }}<\/pre>\n\n<h3>6.5 Parallel and Sequential Jobs<\/h3>\n\n<figure id=\"fig-5-en\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item purple\">Lint<\/span>\n      <span class=\"flow-arrow\">\u2198<\/span>\n      <span class=\"flow-arrow\" style=\"margin:0 2rem\"><\/span>\n      <span class=\"flow-item green\">Test (parallel)<\/span>\n      <span class=\"flow-arrow\">\u2198<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-arrow\" style=\"margin-right:2rem\">\u2198<\/span>\n      <span class=\"flow-item green\">Build (parallel)<\/span>\n      <span class=\"flow-arrow\">\u2198<\/span>\n      <span class=\"flow-item yellow\">Deploy<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item purple\">Security Scan<\/span>\n      <span class=\"flow-arrow\">\u2197<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 5<\/strong> Parallel and sequential jobs: Lint \/ Test \/ Build \/ Security Scan run in parallel, then Deploy triggers after all pass<\/figcaption>\n<\/figure>\n\n<pre class=\"code-block\">jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  test:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  build:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  security:\n    runs-on: ubuntu-latest\n    steps: [ ... ]\n\n  deploy:\n    needs: [lint, test, build, security]   <span style=\"color:var(--muted)\"># Wait for all 4 to complete<\/span>\n    if: github.ref == 'refs\/heads\/main'     <span style=\"color:var(--muted)\"># Only deploy from main<\/span>\n    runs-on: ubuntu-latest\n    steps: [ ... ]<\/pre>\n\n<!-- 7. Best Practices and Security -->\n<h2 id=\"en-7\">7. CI Best Practices and Security<\/h2>\n\n<h3>7.1 Efficiency Optimization<\/h3>\n\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\">\n    <h4>\ud83c\udfaf Limit Trigger Paths<\/h4>\n    <span class=\"cmd\">paths \/ paths-ignore<\/span>\n    <span class=\"desc\">Doc changes shouldn&#8217;t trigger full CI<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udcbe Enable Caching<\/h4>\n    <span class=\"cmd\">cache: &#8216;npm&#8217;<\/span>\n    <span class=\"desc\">Caching can cut CI time by 50-70%<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd00 Parallel Jobs<\/h4>\n    <span class=\"cmd\">needs: []<\/span>\n    <span class=\"desc\">Independent jobs run in parallel<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udce6 Matrix Sharding<\/h4>\n    <span class=\"cmd\">matrix.shard<\/span>\n    <span class=\"desc\">Split large test suites across shards<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u23f1\ufe0f Timeout Settings<\/h4>\n    <span class=\"cmd\">timeout-minutes: 15<\/span>\n    <span class=\"desc\">Prevent stuck jobs from wasting quota<\/span>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\ud83d\udd04 Reusable Workflows<\/h4>\n    <span class=\"cmd\">workflow_call<\/span>\n    <span class=\"desc\">Share CI config across repos, reduce duplication<\/span>\n  <\/div>\n<\/div>\n\n<h3>7.2 Security Practices<\/h3>\n\n<div class=\"info-box danger\">\n  <strong>Security Warning:<\/strong> CI workflows typically have access to code repositories, secrets, and deployment environments. Misconfiguration can lead to secret leakage or supply chain attacks. The following security measures are essential<sup><a href=\"#cite-6\">[6]<\/a><\/sup>.\n<\/div>\n\n<pre class=\"code-block\"><span style=\"color:var(--muted)\"># 1. Principle of least privilege<\/span>\npermissions:\n  contents: read\n  security-events: write\n  pull-requests: write\n\n<span style=\"color:var(--muted)\"># 2. Use Secrets for sensitive data<\/span>\nenv:\n  API_KEY: ${{ secrets.API_KEY }}      <span style=\"color:var(--muted)\"># Encrypted storage<\/span>\n  DB_URL: ${{ secrets.PROD_DB_URL }}\n\n<span style=\"color:var(--muted)\"># 3. Pin Action versions (use commit SHA, not tags)<\/span>\n- uses: actions\/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  <span style=\"color:var(--muted)\"># SHA pinned<\/span>\n  <span style=\"color:var(--muted)\"># Not uses: actions\/checkout@v4 (tags can be moved)<\/span>\n\n<span style=\"color:var(--muted)\"># 4. Audit third-party Actions<\/span>\n<span style=\"color:var(--muted)\"># Check repo stars, maintenance status, code review before use<\/span>\n\n<span style=\"color:var(--muted)\"># 5. Use Environment protection for deployment<\/span>\njobs:\n  deploy:\n    environment: production            <span style=\"color:var(--muted)\"># Requires approval to run<\/span>\n    runs-on: ubuntu-latest<\/pre>\n\n<div class=\"compare-box\">\n  <div class=\"compare-col good\">\n    <h4>\u2705 Security Best Practices<\/h4>\n    <p>Declare minimal permissions with <code>permissions<\/code><\/p>\n    <p>Reference secrets, never hardcode<\/p>\n    <p>Protect production deploys with Environments<\/p>\n    <p>Pin Actions to commit SHA<\/p>\n    <p>Regularly audit third-party Action dependencies<\/p>\n    <p>Enable automated vulnerability scanning (Dependabot)<\/p>\n  <\/div>\n  <div class=\"compare-col bad\">\n    <h4>\u274c Dangerous Practices<\/h4>\n    <p>Using default <code>write<\/code> permissions<\/p>\n    <p>Plaintext secrets in YAML or env vars<\/p>\n    <p>Anyone can trigger production deployment<\/p>\n    <p>Using <code>@main<\/code> or <code>@latest<\/code> tags<\/p>\n    <p>Using unfamiliar Actions without review<\/p>\n    <p>Ignoring dependency vulnerability alerts<\/p>\n  <\/div>\n<\/div>\n\n<h3>7.3 Test Layering Strategy<\/h3>\n\n<figure id=\"fig-6-en\">\n  <div class=\"flow-diagram\">\n    <div class=\"flow-row\">\n      <span class=\"flow-item\" style=\"min-width:400px;background:var(--accent)\">Unit Tests (fast, many, always in CI)<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item green\" style=\"min-width:340px\">Integration Tests (medium, in CI)<\/span>\n    <\/div>\n    <div class=\"flow-row\">\n      <span class=\"flow-item yellow\" style=\"min-width:240px;color:#1a1a2e\">E2E Tests (slow, nightly)<\/span>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 6<\/strong> Testing pyramid: CI should prioritize fast tests; slow tests run nightly<\/figcaption>\n<\/figure>\n\n<!-- 8. Common Pitfalls -->\n<h2 id=\"en-8\">8. Common Pitfalls and Solutions<\/h2>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>Table 4<\/strong> Common CI pitfalls and solutions<\/caption>\n    <thead>\n      <tr><th>Pitfall<\/th><th>Symptom<\/th><th>Solution<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>Long CI Time<\/td>\n        <td>Builds exceed 30 min, developers ignore feedback<\/td>\n        <td>Parallel jobs, enable caching, test sharding, layered testing<\/td>\n      <\/tr>\n      <tr>\n        <td>Flaky Tests<\/td>\n        <td>Same code passes sometimes, fails others<\/td>\n        <td>Isolate test env, fix race conditions, track flaky tests<\/td>\n      <\/tr>\n      <tr>\n        <td>Secret Leakage<\/td>\n        <td>Secrets appear in logs or artifacts<\/td>\n        <td>Use secrets, configure <code>permissions<\/code>, enable secret scanning<\/td>\n      <\/tr>\n      <tr>\n        <td>Over-triggering<\/td>\n        <td>Doc changes trigger full CI, wasting quota<\/td>\n        <td>Filter non-code changes with <code>paths-ignore<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>Single Point of Failure<\/td>\n        <td>All steps in one job; one failure restarts all<\/td>\n        <td>Split into independent jobs, chain with <code>needs<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>Ignoring CI Failures<\/td>\n        <td>Team habitually ignores red builds<\/td>\n        <td>Enforce PR checks via Branch Protection Rules<\/td>\n      <\/tr>\n      <tr>\n        <td>Environment Inconsistency<\/td>\n        <td>CI passes but local\/prod fails<\/td>\n        <td>Use Docker containers to unify environments<\/td>\n      <\/tr>\n      <tr>\n        <td>Cache Expiry<\/td>\n        <td>Cache misses cause full downloads each time<\/td>\n        <td>Include dependency file hash in cache key<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<div class=\"info-box warn\">\n  <strong>Key Advice:<\/strong> Enable GitHub&#8217;s <strong>Branch Protection Rules<\/strong> to require CI checks to pass before merging PRs. This is the institutional guarantee that CI delivers value\u2014if CI results can be ignored, CI is effectively useless.\n<\/div>\n\n<!-- 9. CI and CD Relationship -->\n<h2 id=\"en-9\">9. Relationship Between CI and CD<\/h2>\n\n<p>CI is the first half of the CI\/CD pipeline. Understanding the distinction and connection between CI and CD helps build a complete automated delivery system<sup><a href=\"#cite-4\">[4]<\/a><\/sup>:<\/p>\n\n<div class=\"table-wrap\">\n  <table>\n    <caption><strong>Table 5<\/strong> Comparison of CI, Continuous Delivery, and Continuous Deployment<\/caption>\n    <thead>\n      <tr><th>Dimension<\/th><th>CI<\/th><th>Continuous Delivery<\/th><th>Continuous Deployment<\/th><\/tr>\n    <\/thead>\n    <tbody>\n      <tr><td>Core Goal<\/td><td>Verify code correctness<\/td><td>Ensure code is always releasable<\/td><td>Auto-deploy to production<\/td><\/tr>\n      <tr><td>Automation Scope<\/td><td>Build + Test<\/td><td>Build + Test + Release prep<\/td><td>Full pipeline automated<\/td><\/tr>\n      <tr><td>Manual Intervention<\/td><td>Developer commits code<\/td><td>Manual &#8220;deploy&#8221; button click<\/td><td>None (passes tests = deploys)<\/td><\/tr>\n      <tr><td>Release Frequency<\/td><td>No releases<\/td><td>On demand (daily\/weekly)<\/td><td>High frequency (multiple\/day)<\/td><\/tr>\n      <tr><td>Risk Level<\/td><td>Low (CI env only)<\/td><td>Medium (staging env)<\/td><td>High (direct user impact)<\/td><\/tr>\n      <tr><td>Use Case<\/td><td>All projects<\/td><td>Projects needing release approval<\/td><td>Mature teams, high automation<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n\n<figure id=\"fig-7-en\">\n  <div class=\"pipeline-viz\">\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd00<\/div>\n      <div class=\"stage-name\">Commit<\/div>\n      <div class=\"stage-desc\">CI start<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udd28<\/div>\n      <div class=\"stage-name\">Build<\/div>\n      <div class=\"stage-desc\">Compile<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83e\uddea<\/div>\n      <div class=\"stage-name\">Test<\/div>\n      <div class=\"stage-desc\">CI end<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\udce6<\/div>\n      <div class=\"stage-name\">Stage<\/div>\n      <div class=\"stage-desc\">CD start<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\u2705<\/div>\n      <div class=\"stage-name\">Approve<\/div>\n      <div class=\"stage-desc\">Delivery<\/div>\n    <\/div>\n    <div class=\"pipeline-arrow\">\u2192<\/div>\n    <div class=\"pipeline-stage\">\n      <div class=\"stage-icon\">\ud83d\ude80<\/div>\n      <div class=\"stage-name\">Deploy<\/div>\n      <div class=\"stage-desc\">Deployment<\/div>\n    <\/div>\n  <\/div>\n  <figcaption><strong>Figure 7<\/strong> Full CI\/CD flow: CI handles the first three stages, CD handles the rest<\/figcaption>\n<\/figure>\n\n<!-- 10. Complete Example -->\n<h2 id=\"en-10\">10. Complete Practical Example<\/h2>\n\n<h3>10.1 Full-Stack Project CI Configuration<\/h3>\n\n<p>Below is a complete CI configuration for a full-stack project, integrating all core concepts from this guide:<\/p>\n\n<pre class=\"code-block\">name: Full-Stack CI\n\non:\n  push:\n    branches: [ main, develop ]\n    paths-ignore: [ 'docs\/**', '*.md' ]\n  pull_request:\n    branches: [ main ]\n\npermissions:\n  contents: read\n  security-events: write\n  pull-requests: write\n\nenv:\n  NODE_VERSION: \"20\"\n  PYTHON_VERSION: \"3.12\"\n\njobs:\n  <span style=\"color:var(--accent)\"># \u2500\u2500 Frontend \u2500\u2500<\/span>\n  frontend-lint:\n    name: Frontend Lint\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n          cache-dependency-path: frontend\/package-lock.json\n      - run: cd frontend && npm ci\n      - run: cd frontend && npm run lint\n      - run: cd frontend && npm run type-check\n\n  frontend-test:\n    name: Frontend Test\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    strategy:\n      fail-fast: false\n      matrix:\n        shard: [1, 2, 3, 4]\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n          cache-dependency-path: frontend\/package-lock.json\n      - run: cd frontend && npm ci\n      - run: cd frontend && npx vitest run --shard=${{ matrix.shard }}\/4\n\n  frontend-build:\n    name: Frontend Build\n    needs: [frontend-lint, frontend-test]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n          cache-dependency-path: frontend\/package-lock.json\n      - run: cd frontend && npm ci\n      - run: cd frontend && npm run build\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: frontend-dist\n          path: frontend\/dist\/\n\n  <span style=\"color:var(--accent2)\"># \u2500\u2500 Backend \u2500\u2500<\/span>\n  backend-test:\n    name: Backend Test\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_PASSWORD: test\n        ports: [ '5432:5432' ]\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - run: pip install -r backend\/requirements.txt\n      - run: cd backend && pytest --cov --cov-report=xml\n        env:\n          DATABASE_URL: postgresql:\/\/postgres:test@localhost:5432\/test\n      - uses: codecov\/codecov-action@v4\n        with:\n          file: backend\/coverage.xml\n\n  <span style=\"color:var(--accent4)\"># \u2500\u2500 Security Scan \u2500\u2500<\/span>\n  security-scan:\n    name: Security Scan\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Run CodeQL\n        uses: github\/codeql-action\/init@v3\n        with:\n          languages: javascript, python\n      - uses: github\/codeql-action\/analyze@v3\n      - name: Dependency scan\n        uses: aquasecurity\/trivy-action@master\n        with:\n          scan-type: fs\n          scan-ref: .\n\n  <span style=\"color:var(--accent5)\"># \u2500\u2500 Summary \u2500\u2500<\/span>\n  report:\n    name: CI Report\n    needs: [frontend-build, backend-test, security-scan]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check results\n        run: |\n          echo \"Frontend Build: ${{ needs.frontend-build.result }}\"\n          echo \"Backend Test: ${{ needs.backend-test.result }}\"\n          echo \"Security: ${{ needs.security-scan.result }}\"\n          if [[ \"${{ needs.frontend-build.result }}\" != \"success\" ]] || \\\n             [[ \"${{ needs.backend-test.result }}\" != \"success\" ]] || \\\n             [[ \"${{ needs.security-scan.result }}\" != \"success\" ]]; then\n            exit 1\n          fi<\/pre>\n\n<h3>10.2 Quick Start Checklist<\/h3>\n\n<ol class=\"steps\">\n  <li>\n    <strong>Create workflow directory<\/strong>\n    <span class=\"cmd\">mkdir -p .github\/workflows<\/span>\n    <span class=\"note\">Create the GitHub Actions config directory at repo root<\/span>\n  <\/li>\n  <li>\n    <strong>Write CI configuration<\/strong>\n    <span class=\"cmd\">touch .github\/workflows\/ci.yml<\/span>\n    <span class=\"note\">Use the templates from Chapter 5 as a starting point<\/span>\n  <\/li>\n  <li>\n    <strong>Commit and push<\/strong>\n    <span class=\"cmd\">git add .github\/workflows\/ci.yml &#038;&#038; git commit -m &#8220;Add CI&#8221; &#038;&#038; git push<\/span>\n    <span class=\"note\">GitHub auto-detects and executes the workflow on push<\/span>\n  <\/li>\n  <li>\n    <strong>View results<\/strong>\n    <span class=\"cmd\">Click the Actions tab in your GitHub repo<\/span>\n    <span class=\"note\">View real-time status and logs for each job and step<\/span>\n  <\/li>\n  <li>\n    <strong>Configure branch protection<\/strong>\n    <span class=\"cmd\">Settings \u2192 Branches \u2192 Add rule<\/span>\n    <span class=\"note\">Require CI to pass before merging PRs to enforce CI value<\/span>\n  <\/li>\n<\/ol>\n\n<div class=\"info-box success\">\n  <strong>Summary:<\/strong> The essence of CI is &#8220;frequent integration + automated verification.&#8221; GitHub Actions makes CI configuration declarative, version-controlled, and deeply integrated with code repositories. Mastering YAML syntax, matrix strategies, caching mechanisms, and security practices enables building efficient, secure, and maintainable CI pipelines that fundamentally improve team development efficiency and code quality.\n<\/div>\n\n<\/div><!-- end lang-en -->\n\n<!-- Sources -->\n<footer>\n  <div class=\"sources\">\n    <h2>Sources \/ \u53c2\u8003\u6765\u6e90<\/h2>\n    <ol>\n      <li id=\"cite-1\">\n        <span class=\"src-title\">CircleCI, What is continuous integration (CI)? \u2014 Definition, benefits, how CI works, best practices.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/circleci.com\/continuous-integration\/\" target=\"_blank\" rel=\"noopener\">https:\/\/circleci.com\/continuous-integration\/<\/a>\n      <\/li>\n      <li id=\"cite-2\">\n        <span class=\"src-title\">Martin Fowler, Continuous Integration \u2014 Foundational article on CI principles, practices, and the &#8220;ten-minute build&#8221; guideline.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/martinfowler.com\/articles\/continuousIntegration.html\" target=\"_blank\" rel=\"noopener\">https:\/\/martinfowler.com\/articles\/continuousIntegration.html<\/a>\n      <\/li>\n      <li id=\"cite-3\">\n        <span class=\"src-title\">IJSRA, Implementing continuous integration and continuous deployment (CI\/CD) pipelines \u2014 Academic paper on CI\/CD history and implementation.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/ijsra.net\/sites\/default\/files\/fulltext_pdf\/IJSRA-2021-0073.pdf\" target=\"_blank\" rel=\"noopener\">https:\/\/ijsra.net\/sites\/default\/files\/fulltext_pdf\/IJSRA-2021-0073.pdf<\/a>\n      <\/li>\n      <li id=\"cite-4\">\n        <span class=\"src-title\">GitHub Resources, What is CI\/CD? \u2014 Definitions of CI, Continuous Delivery, and Continuous Deployment.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/github.com\/resources\/articles\/devops\/ci-cd\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/resources\/articles\/devops\/ci-cd<\/a>\n      <\/li>\n      <li id=\"cite-5\">\n        <span class=\"src-title\">GitHub Docs, Workflow syntax for GitHub Actions \u2014 Official reference for YAML workflow configuration.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/docs.github.com\/en\/actions\/using-workflows\/workflow-syntax-for-github-actions\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.github.com\/en\/actions\/using-workflows\/workflow-syntax-for-github-actions<\/a>\n      <\/li>\n      <li id=\"cite-6\">\n        <span class=\"src-title\">Microsoft Learn, Describe standard workflow syntax elements \u2014 Training module on GitHub Actions YAML syntax, permissions, and security.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/learn.microsoft.com\/en-us\/training\/modules\/introduction-to-github-actions\/5-describe-standard-workflow-syntax-elements\" target=\"_blank\" rel=\"noopener\">https:\/\/learn.microsoft.com\/en-us\/training\/modules\/introduction-to-github-actions\/5-describe-standard-workflow-syntax-elements<\/a>\n      <\/li>\n      <li id=\"cite-7\">\n        <span class=\"src-title\">GitHub Docs, Building and testing Node.js \u2014 Official guide for Node.js CI with GitHub Actions.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/docs.github.com\/en\/actions\/automating-builds-and-tests\/building-and-testing-nodejs\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.github.com\/en\/actions\/automating-builds-and-tests\/building-and-testing-nodejs<\/a>\n      <\/li>\n      <li id=\"cite-8\">\n        <span class=\"src-title\">GitHub Docs, Reuse workflows \u2014 Matrix strategy and reusable workflow documentation.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/docs.github.com\/en\/actions\/how-tos\/reuse-automations\/reuse-workflows\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.github.com\/en\/actions\/how-tos\/reuse-automations\/reuse-workflows<\/a>\n      <\/li>\n      <li id=\"cite-9\">\n        <span class=\"src-title\">GitHub Docs, Caching dependencies to speed up workflows \u2014 Official caching guide for dependencies.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/docs.github.com\/en\/actions\/using-workflows\/caching-dependencies-to-speed-up-workflows\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.github.com\/en\/actions\/using-workflows\/caching-dependencies-to-speed-up-workflows<\/a>\n      <\/li>\n      <li id=\"cite-10\">\n        <span class=\"src-title\">GitHub Awesome Copilot, GitHub Actions CI\/CD Best Practices \u2014 Community-maintained best practices for CI\/CD workflows.<\/span>\n        <a class=\"src-url\" href=\"https:\/\/github.com\/github\/awesome-copilot\/blob\/main\/instructions\/github-actions-ci-cd-best-practices.instructions.md\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/github\/awesome-copilot\/blob\/main\/instructions\/github-actions-ci-cd-best-practices.instructions.md<\/a>\n      <\/li>\n    <\/ol>\n  <\/div>\n<\/footer>\n\n<\/main>\n\n<button class=\"back-top\" onclick=\"scrollToTop()\" title=\"Back to top\">\u2191<\/button>\n\n<script>\n\/\/ Language switching\nfunction setLang(lang) {\n  document.querySelectorAll('.lang-section').forEach(s => s.classList.remove('active'));\n  document.getElementById('lang-' + lang).classList.add('active');\n  document.getElementById('btn-zh').classList.toggle('active', lang === 'zh');\n  document.getElementById('btn-en').classList.toggle('active', lang === 'en');\n  localStorage.setItem('ci-lang', lang);\n  window.scrollTo(0, 0);\n}\n\n\/\/ Theme switching\nfunction toggleTheme() {\n  const current = document.documentElement.getAttribute('data-theme');\n  const next = current === 'dark' ? 'light' : 'dark';\n  document.documentElement.setAttribute('data-theme', next === 'light' ? '' : 'dark');\n  localStorage.setItem('ci-theme', next);\n  updateThemeButton(next);\n}\n\nfunction updateThemeButton(theme) {\n  const btn = document.querySelector('.theme-toggle');\n  btn.innerHTML = theme === 'dark' ? '\u263e' : '&#9788;';\n}\n\n\/\/ Scroll to top\nfunction scrollToTop() {\n  window.scrollTo({ top: 0, behavior: 'smooth' });\n}\n\n\/\/ Progress bar\nwindow.addEventListener('scroll', function() {\n  const scrolled = window.scrollY;\n  const height = document.documentElement.scrollHeight - window.innerHeight;\n  const progress = (scrolled \/ height) * 100;\n  document.getElementById('progressBar').style.width = progress + '%';\n\n  \/\/ Back to top button\n  const backBtn = document.querySelector('.back-top');\n  if (scrolled > 300) {\n    backBtn.classList.add('visible');\n  } else {\n    backBtn.classList.remove('visible');\n  }\n});\n\n\/\/ Initialize from localStorage\n(function() {\n  const savedLang = localStorage.getItem('ci-lang');\n  if (savedLang === 'en') {\n    setLang('en');\n  }\n\n  const savedTheme = localStorage.getItem('ci-theme');\n  if (savedTheme === 'dark') {\n    document.documentElement.setAttribute('data-theme', 'dark');\n    updateThemeButton('dark');\n  }\n})();\n<\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>\u6301\u7eed\u96c6\u6210\u4e0e GitHub Actions \u5b8c\u6574\u6307\u5357 \u00b7 CI &#038; GitHub Actions Gu [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-392","post","type-post","status-publish","format-standard","hentry","category-ai"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/392","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=392"}],"version-history":[{"count":1,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/392\/revisions"}],"predecessor-version":[{"id":393,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/392\/revisions\/393"}],"wp:attachment":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=392"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=392"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=392"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}