PageSourceSearch

https://threejsnotes.netlify.app/assets/js/0e769686.05ff6105.js

js threejsnotes.netlify.app collected 2026-10-03 11:30:41 UTC 89,007 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[370],{7274:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var t=s(4848),i=s(8453);const o={},r=void 0,a={id:"devops/github-actions",title:"github-actions",description:"Basics",source:"@site/docs/devops/07-github-actions.md",sourceDirName:"devops",slug:"/devops/github-actions",permalink:"/docs/devops/github-actions",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:7,frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"ansible",permalink:"/docs/devops/ansible"},next:{title:"kubernetes",permalink:"/docs/devops/kubernetes"}},l={},c=[{value:"Basics",id:"basics",level:2},{value:"The workflow of workflows",id:"the-workflow-of-workflows",level:3},{value:"Jobs and steps in depth",id:"jobs-and-steps-in-depth",level:3},{value:"Running jobs sequentially",id:"running-jobs-sequentially",level:4},{value:"Setting runner defaults",id:"setting-runner-defaults",level:4},{value:"Variables in workflows (ENV, secrets, context)",id:"variables-in-workflows-env-secrets-context",level:4},{value:"Job outputs",id:"job-outputs",level:4},{value:"step outputs",id:"step-outputs",level:4},{value:"Matrix",id:"matrix",level:4},{value:"Conditionally running steps",id:"conditionally-running-steps",level:4},{value:"Conditionally running jobs",id:"conditionally-running-jobs",level:4},{value:"Caching",id:"caching",level:4},{value:"Creating jobs from containers",id:"creating-jobs-from-containers",level:4},{value:"triggers",id:"triggers",level:3},{value:"How to skip triggering workflows",id:"how-to-skip-triggering-workflows",level:4},{value:"Push trigger",id:"push-trigger",level:4},{value:"Cron Scheduling",id:"cron-scheduling",level:4},{value:"Artifacts",id:"artifacts",level:2},{value:"Creating artifacts",id:"creating-artifacts",level:3},{value:"Downloading artifacts",id:"downloading-artifacts",level:3},{value:"Using docker",id:"using-docker",level:2},{value:"Building a runner environment from docker",id:"building-a-runner-environment-from-docker",level:3},{value:"Running jobs with containers",id:"running-jobs-with-containers",level:3},{value:"Reusable Workflows",id:"reusable-workflows",level:2},{value:"basics",id:"basics-1",level:3},{value:"Accepting inputs to a reusable workflow",id:"accepting-inputs-to-a-reusable-workflow",level:3},{value:"Creating reusable workflow outputs",id:"creating-reusable-workflow-outputs",level:3},{value:"Custom actions",id:"custom-actions",level:2},{value:"Composite actions",id:"composite-actions",level:3},{value:"Basics",id:"basics-2",level:4},{value:"inputs",id:"inputs",level:4},{value:"outputs",id:"outputs",level:4},{value:"Custom actions with docker",id:"custom-actions-with-docker",level:3},{value:"Creating the custom action",id:"creating-the-custom-action",level:4},{value:"Using environment variables",id:"using-environment-variables",level:4},{value:"Build outputs",id:"build-outputs",level:4},{value:"Artifacts",id:"artifacts-1",level:4},{value:"Custom actions with javascript",id:"custom-actions-with-javascript",level:3},{value:"javascript actions api",id:"javascript-actions-api",level:4},{value:"<code>github</code> context object",id:"github-context-object",level:4},{value:"Running shell commands",id:"running-shell-commands",level:4},{value:"Integrations",id:"integrations",level:2},{value:"Sending email notifications",id:"sending-email-notifications",level:3},{value:"ACT",id:"act",level:2},{value:"basic usage",id:"basic-usage",level:3}
1,{value:"With specific triggers",id:"with-specific-triggers",level:3}];function d(e){const n={code:"code",div:"div",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",path:"path",pre:"pre",strong:"strong",svg:"svg",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h2,{id:"basics",children:"Basics"}),"\n",(0,t.jsxs)(n.p,{children:["A github action is a CI/CD pipeline made from ",(0,t.jsx)(n.strong,{children:"workflows"}),", where each workflow can run several ",(0,t.jsx)(n.strong,{children:"jobs"})," when ",(0,t.jsx)(n.strong,{children:"triggers"})," are triggered:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"jobs"}),": The main thing behind CI/CD - you test your code, build, deploy. Each job runs in a new ephemeral VM where you can get access to your codebase and do stuff with it"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"triggers"}),": These are events that the workflow listens for (like pull request or push to main), and when triggered, the workflow runs all the jobs it has."]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"the-workflow-of-workflows",children:"The workflow of workflows"}),"\n",(0,t.jsxs)(n.p,{children:["A workflow is a single yaml file in your ",(0,t.jsx)(n.code,{children:".github/workflows"})," directory."]}),"\n",(0,t.jsx)(n.p,{children:"There are three very important rules to keep in mind about the behavior of a workflow:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"All jobs in a workflow run in parallel by default."}),"\n",(0,t.jsx)(n.li,{children:"All the steps inside a job are run sequentially."}),"\n",(0,t.jsx)(n.li,{children:"Each job runs on its own VM and when it clones your repo, it sets the current working directory of the VM to the root of the project."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"This is what a basic workflow will look like."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: workflow learning\non: \n\tpush: \n\t\tbranches: ["main"]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    env:\n      GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}\n    steps:\n\t  # 1. copy repo code into vm\n      - uses: actions/checkout@v4\n      # 2. install node\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n\t  # 3. install dependencies\n      - name: install\n\t    run: npm ci\n\t  # 4. build\n      - name: build\n\t    run: npm run build\n\t  # 5. test\n      - name: test\n\t    run: npm run test\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"anatomy of a job"})}),"\n",(0,t.jsxs)(n.p,{children:["All jobs live under the ",(0,t.jsx)(n.code,{children:"job"})," key and must de initialized with a job name:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n\t<job-name-1>:\n\t\t# steps, etc.\n\t<job-name-2>:\n\t\t# steps, etc.\n\t<job-name-3>:\n\t\t# steps, etc.\n"})}),"\n",(0,t.jsxs)(n.p,{children:["At the ",(0,t.jsx)(n.code,{children:"jobs.<job-name>"})," level, you have several important keys:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"runs-on"}),": The OS VM to use for the job. ",(0,t.jsx)(n.code,{children:"ubuntu-latest"})," is a good choice because it's linux."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"env"}),": any environment variables to set"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"steps"}),": the steps of the job"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"anatomy of steps"})}),"\n",(0,t.jsxs)(n.p,{children:["Each job is made of multiple steps defined in the ",(0,t.jsx)(n.code,{children:"steps"})," key and can either run linux commands on the job's VM or use predefined github actions from ",(0,t.jsx)(n.strong,{children:"github marketplace"})," to do stuff."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"uses"}),": To use an action in a step, use the ",(0,t.jsx)(n.code,{children:"uses"})," keyword and pass in a github action as the value."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"with"}),": When actions need configuration, you provide those configuration steps using the ",(0,t.jsx)(n.code,{children:"with"})," keyword."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"steps: \n  - name: checkout\n    uses: actions/checkout@v3\n\t\twith: \n\t\t\t# some configuration\n"})}),"\n",(0,t.jsx)(n.h3,{id:"jobs-and-steps-in-depth",children:"Jobs and steps in depth"}),"\n",(0,t.jsx)(n.h4,{id:"running-jobs-sequentially",children:"Running jobs sequentially"}),"\n",(0,t.jsx)(n.h4,{id:"setting-runner-defaults",children:"Setting runner defaults"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"defaults"})," root level key lets you set default settings and configurations for your workflow. For example, the ",(0,t.jsx)(n.code,{children:"defaults.run"})," key allows you to configure the default settings for all runners:"]}),"\n",(0,t.jsx)(n.p,{children:"You can set the default working directory in your repo for which your jobs run using this code. This is useful for monorepos where you want to do the bulk of the work in a subfolder."}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"defaults.run"}),": configurations for runners","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"working-directory"}),": sets the current working directory for all runners after they check out your co
1de"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"shell"}),": sets the shell language for the VM. Just set this to ",(0,t.jsx)(n.code,{children:"bash"}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"# define triggers...\n\ndefaults:\n\trun:\n\t\tworking-directory: ./server # will run all code with cwd = ./server\n\t\tshell: bash\n\n# define jobs...\n"})}),"\n",(0,t.jsx)(n.h4,{id:"variables-in-workflows-env-secrets-context",children:"Variables in workflows (ENV, secrets, context)"}),"\n",(0,t.jsxs)(n.p,{children:["You can evaluate variables inside a ",(0,t.jsx)(n.code,{children:"${{ }}"})," expression."]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"contexts"})}),"\n",(0,t.jsx)(n.p,{children:"Contexts are a way to access information about workflow runs, variables, runner environments, jobs, and steps. Each context is an object that contains properties, which can be strings or other objects."}),"\n",(0,t.jsx)(n.p,{children:"Here are the different types of global context objects you have access to:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"github"})}),": Information about the workflow run and GitHub event that triggered the workflow. This is one of the most commonly used contexts."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"env"})}),": Access to environment variables defined in the workflow, repository, or organization."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"job"})}),": Information about the current job being executed."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"steps"})}),": Information about the steps within a job, including outputs and results."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"secrets"})}),": Access to secrets stored in the repository or organization."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"strategy"})}),": Information about the matrix strategy used for parallel job execution."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"matrix"})}),": Information about the current matrix configuration in a matrix strategy."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"needs"})}),": Information about the status and outputs of jobs that are dependencies of the current job."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"vars"})}),": Access to custom variables defined at the repository, organization, or environment levels."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["This is what the ",(0,t.jsx)(n.code,{children:"github"})," context object looks like:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n  "event": {\n    "repository": {\n      "default_branch": "master"\n    }\n  },\n  "event_path": "/var/run/act/workflow/event.json",\n  "workflow": "practice.yaml",\n  "run_attempt": "1",\n  "run_id": "1",\n  "run_number": "1",\n  "actor": "nektos/act",\n  "repository": "HHMI-HQ/SCT-health-dashboard",\n  "event_name": "workflow_dispatch",\n  "sha": "603f3a431b5a272f82075192b7318e105591598d",\n  "ref": "refs/heads/k8-express-mongo-setup",\n  "ref_name": "k8-express-mongo-setup",\n  "ref_type": "branch",\n  "head_ref": "",\n  "base_ref": "",\n  "token": "",\n  "workspace": "/Users/mallicka/Documents/bruhdil/work/health-dashboard",\n  "action": "",\n  "action_path": "",\n  "action_ref": "",\n  "action_repository": "",\n  "job": "learn-variables",\n  "job_name": "",\n  "repository_owner": "HHMI-HQ",\n  "retention_days": "0",\n  "runner_perflog": "/dev/null",\n  "runner_tracking_id": "",\n  "server_url": "https://github.com",\n  "api_url": "https://api.github.com",\n  "graphql_url": "https://api.github.com/graphql"\n}"\n'})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.event_name"}),": The name of the event that triggered the workflow (e.g.,\xa0",(0,t.jsx)(n.code,{children:"push"}),",\xa0",(0,t.jsx)(n.code,{children:"pull_request"}),",\xa0",(0,t.jsx)(n.code,{children:"schedule"}),")."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.sha"}),": The commit SHA that triggered the workflow."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.ref"}),": The branch or tag that triggered the workflow (e.g.,\xa0",(0,t.jsx)(n.code,{children:"refs/heads/main"}),",\xa0",(0,t.jsx)(n.code,{children:"refs/tags/v1.0.0"}),")."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.repository"}),": The owner and repository name (e.g.,\xa0",(0,t.jsx)(n.code,{children:"octocat/Spoon-Knife"}),")."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.event"}),": The full event payload that triggered the workflow. This is a complex object that contains a lot of information specific to the event type."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.workflow"}),": The name of the workflow file."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.run_id"}),": A unique ID for the workflow run."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.run_number"}),": A sequential number for the workflow run."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.actor"}),": The username of the person or app that initiated the workflow run."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.head_ref"}),": The branch name for pull request events."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"github.base_ref"}),": The base branch name for pull request events."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"functions"})}),"\n",(0,t.jsx)(n.p,{children:"And here are global functions available for you to invoke:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"toJSON(variable)"})," : a function that can you can pass a context or any other variable into, and it will convert that object into a readable JSON string format."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"hashFiles(globPath)"}),": a function that takes in a glob pattern to match filepaths and hashes those files and returns a hash string."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"env variables"})}),"\n",(0,t.jsxs)(n.p,{children:["You can define environment variables using the ",(0,t.jsx)(n.code,{children:"env"})," key, and you can define it either at the ",(0,t.jsx)(n.strong,{children:"workflow level, job level,"})," or ",(0,t.jsx)(n.strong,{children:"step level:"})]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"workflow level:"})," The environment variables are shared across jobs"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "Deploy"\non: [push]\n\n# environment variables shared across jobs\nenv:\n  NODE_ENV: production\n\njobs:\n\t# ...\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"job level:"})," The environment variables are local to each job"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "Deploy"\non: [push]\n\njobs:\n\tbuild: \n\t\t# environment variables specific to build job\n\t\tenv:\n\t\t  NODE_ENV: production\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"step level:"})," The environment variables is local to that step and if using an action, those env variables will be supplied to those actions."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "Deploy"\non: push\n\njobs:\n\tbuild:\n\t\truns-on: ubuntu-latest\n\t\tsteps:\n\t\t\t- name: "Say hello"\n\t\t\t\tenv:\n\t\t\t\t\tFIRST_NAME: "hellooo"\n\t\t\t\trun: echo "Hello, my name is $FIRST_NAME"\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"You can also set repo-wide environment variables in your repo settings."}),"\n",(0,t.jsxs)(n.p,{children:["You can then access those environment variables on the ",(0,t.jsx)(n.code,{children:"env"})," context object and through github expression syntax:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'jobs:\n\tbuild: \n\t\tenv:\n\t\t  NODE_ENV: production\n\t\tsteps: \n\t\t\t- runs: echo "${{ env.NODE_ENV }}"\n'})}),"\n",(0,t.jsxs)(n.p,{children:["There are built in environment variables that workflow runners have access to, but since they are loaded in the shell environment rather than set through the ",(0,t.jsx)(n.code,{children:"env"})," key, you have to access them with bash variable interpolation using the ",(0,t.jsx)(n.code,{children:"$"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"GITHUB_SHA"}),": the sha commit that is currently being used in the workflow"]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'env:\n  MY_VARIABLE: "Hello, World!"\n\njobs:\n  print_env:\n    runs-on: ubuntu-latest\n    env:\n      JOB_VARIABLE: "This is a job-level variable"\n    steps:\n      - name: Print Environment Variables\n        env:\n          STEP_VARIABLE: "This is a step-level variable"\n        # Accessing system environment variable through $GITHUB_SHA\n        run: |\n          echo "Workflow Variable: ${{ env.MY_VARIABLE }}"\n          echo "Job Variable: ${{ env.JOB_VARIABLE }}"\n          echo "Step Variable: ${{ env.STEP_VARIABLE }}"\n          echo "System Variable: $GITHUB_SHA" \n          \n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"secrets + environments"})}),"\n",(0,t.jsxs)(n.p,{children:["You can access secrets you set on a github repo through the ",(0,t.jsx)(n.code,{children:"secrets"})," context object:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'jobs:\n  example-job:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Retrieve secret\n        env:\n          super_secret: ${{ secrets.SUPERSECRET }}\n        run: |\n          example-command "$super_secret"\n'})}),"\n",(0,t.jsx)(n.p,{children:"If you want to have different values for your secret environment variables depending on whether you\u2019re in a dev, testing, or production environment, you can do that with environment secrets:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Go to repository settings, and then to ",(0,t.jsx)(n.strong,{children:"environments"})]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Create a new environment, and note the name for later. Add environment secrets"}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["For a job in workflow, you can specify the environment in your repo to use using the ",(0,t.jsx)(n.code,{children:"environment"})," key at the ",(0,t.jsx)(n.code,{children:"jobs.<job-name>"})," level."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["You can then access secrets specific to that environment through the ",(0,t.jsx)(n.code,{children:"secrets"})," context object."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'jobs:\n  test:\n    runs-on: ubuntu-latest\n# use the environment named "testing" we created\n# automatically we get all the environment secrets from that environment\n    environment: testing\n\t\tsteps:\n      - name: "display testing environment"\n        run: echo "${{ secrets.NODE_ENV }}"\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.div,{className:"markdown-alert markdown-alert-note",dir:"auto",children:["\n",(0,t.jsxs)(n.p,{className:"markdown-alert-title",dir:"auto",children:[(0,t.jsx)(n.svg,{className:"octicon",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:(0,t.jsx)(n.path,{d:"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"})}),"NOTE"]}),"\n",(0,t.jsx)(n.p,{children:"The main use case for using environments is to have different env vars and secrets for development and production builds."}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"job-outputs",children:"Job outputs"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Job outputs"})," are single values that jobs output much like artifacts, but can then be used by other jobs. This is used for more programmatic purposes."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"build:\n\t# this key specifies all outputs for a job\n  outputs:\n\t\t# create a job output named `name`\n    name: \n\t\t\tdescription: \"username of the person running this workflow\"\n\t\t\tvalue: ${{ steps.mallick.outputs.output-var }}\n  steps:\n    - name: create job output\n      id: mallick\n# we redirect the key-value pair into github output to set an output value\n      run: echo 'output-var=\"mallick bruh\"' >> $GITHUB_OUTPUT ';'\n"})}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["To create a job output, first we need to set a value for it using the echo key-value pair syntax where in a step, we redirect the echo command into the ",(0,t.jsx)(n.code,{children:"$GITHUB_OUTPUT"})," variable"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"run: echo 'myvarname=\"some value\"' >> $GITHUB_OUTPUT ';'\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Set an ",(0,t.jsx)(n.code,{children:"id"})," on the step where you create the value and redirect it. This will be used later to access the step"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["At the ",(0,t.jsx)(n.code,{children:"job.<job-name>"})," level, create an ",(0,t.jsx)(n.code,{children:"outputs"})," key and specify the name of the job output(s) you want to create. Use github expression syntax to access the specified step, and then set its output value through the ",(0,t.jsx)(n.code,{children:"steps.<step-id>.outputs.<output-name>"})," syntax"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n\tjob_name:\n       outputs:\n\t        output_name: ${{ steps.mallick.outputs.output-var }}\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"Using job outputs in other jobs"})}),"\n",(0,t.jsxs)(n.p,{children:["You can access other jobs through the ",(0,t.jsx)(n.code,{children:"needs"})," context object on a job, which is an object of all the jobs that this job is dependent on. You refer to a specific job through the ",(0,t.jsx)(n.code,{children:"needs.<job-name>"})," property, which returns a ",(0,t.jsx)(n.code,{children:"job"})," context object."]}),"\n",(0,t.jsxs)(n.p,{children:["You can get the all the outputs from the ",(0,t.jsx)(n.code,{children:"needs.<job-name>.outputs"})," syntax:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'deploy:\n    needs: build\n    steps:\n      - name: get job output\n\t\t\t\t# access the "name" job output from the build job\n        run: echo ${{ needs.build.outputs.name }}\n'})}),"\n",(0,t.jsx)(n.h4,{id:"step-outputs",children:"step outputs"}),"\n",(0,t.jsx)(n.p,{children:"Step outputs are just like job outputs except they are stored at the steps level, meaning only steps within the same job can access those step outputs."}),"\n",(0,t.jsxs)(n.p,{children:["To create a step output, you need to echo a key-value pair to the ",(0,t.jsx)(n.code,{children:"$GITHUB_OUTPUT"})," runner environment variable. The basic syntax is as follows:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.js
1x)(n.code,{className:"language-yaml",children:'echo "VAR_NAME=VALUE" >> $GITHUB_OUTPUT\n'})}),"\n",(0,t.jsxs)(n.p,{children:["To access the step as a variable, you must assign an ",(0,t.jsx)(n.code,{children:"id"})," property to the step so you can refer to it programmatically through ",(0,t.jsx)(n.code,{children:"steps.<step-id>"})," syntax."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'- name: Step that sets an output\n  id: example_step\n  run: echo "greeting=hello" >> $GITHUB_OUTPUT\n'})}),"\n",(0,t.jsx)(n.p,{children:"Then you would interpolate the step output by accessing steps through this sort of syntax:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"steps.<step-id>.outputs.<output-name>\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'- name: Use the output\n  run: echo "${{ steps.example_step.outputs.greeting }}"\n'})}),"\n",(0,t.jsx)(n.p,{children:"And here's a complete example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'jobs:\n  demo:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Set an output\n        id: set_output\n        run: echo "color=blue" >> $GITHUB_OUTPUT\n\n      - name: Use output\n        run: echo "Chosen color is ${{ steps.set_output.outputs.color }}"\n'})}),"\n",(0,t.jsx)(n.h4,{id:"matrix",children:"Matrix"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"strategy"})," key lives at the ",(0,t.jsx)(n.code,{children:"job.<job-name>"})," level and lets you define strategies for the job. The most important strategy here is the ",(0,t.jsx)(n.strong,{children:"matrix"})," strategy, which creates permutations of variables and the job will be rerun for each permutation."]}),"\n",(0,t.jsx)(n.p,{children:"A basic example is as follows:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["We define the ",(0,t.jsx)(n.code,{children:"os"})," variable as a matrix variable, meaning that it will take a value from the array you pass in."]}),"\n",(0,t.jsxs)(n.li,{children:["We can refer to matrix variables through the ",(0,t.jsx)(n.code,{children:"matrix.variableName"})," syntax, and use variable interpolation to extract the value."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [macos-latest, windows-latest, ubuntu-18.04]\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The example above demonstrates how we will run the job for each value in the ",(0,t.jsx)(n.code,{children:"matrix.os"})," array, and since we interpolate ",(0,t.jsx)(n.code,{children:"${{ matrix.os }}"})," for the ",(0,t.jsx)(n.code,{children:"runs-on"})," key, we will run through the different OSs:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"The 1st job will run on mac"}),"\n",(0,t.jsx)(n.li,{children:"The 2nd job will run on windows"}),"\n",(0,t.jsx)(n.li,{children:"The 3rd job will run on ubuntu"}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        node-version: [8.x, 10.x, 12.x]\n        os: [macos-latest, windows-latest, ubuntu-18.04]\n    steps:\n    - uses: actions/checkout@v1\n    # install node through versions 8, 10, 12.\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v1\n      with:\n        node-version: ${{ matrix.node-version }}\n    - name: npm install, build, and test\n      run: |\n        npm ci\n        npm run build --if-present\n        npm test\n      env:\n        CI: true\n"})}),"\n",(0,t.jsx)(n.h4,{id:"conditionally-running-steps",children:"Conditionally running steps"}),"\n",(0,t.jsx)(n.p,{children:"The default behavior of a workflow is that if one steps fails, that entire job is aborted and so are any other jobs dependent on that job."}),"\n",(0,t.jsxs)(n.p,{children:["We can use the ",(0,t.jsx)(n.code,{children:"if"})," conditional key to bypass this and skip steps or run steps. If the condition in an ",(0,t.jsx)(n.code,{children:"if"})," block is true, then the step runs. If the conditional is false, then the step fails."]}),"\n",(0,t.jsx)(n.p,{children:"Now we can conditionally run jobs and steps and even ignore errors and continue running a job."}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"special conditional functions"})}),"\n",(0,t.jsx)(n.p,{children:"We use these special conditional functions to run steps conditionally even if another step failed. These functions override the default behavior of the workflow aborting when a step fails."}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"failure()"})," : returns true when any previous step or job in the workflow failed"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"success()"})," : returns true when all previous steps or jobs in the workflow passed"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"cancelled()"})," : returns true when user manually cancels workflow"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"always()"})," : always returns true, meaning that any conditional statement using this function will always run, even if the workflow was cancelled or failed."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"running a step if another step failed"})}),"\n",(0,t.jsx)(n.p,{children:"We can run steps conditionally by using these two concepts:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["Refer to other steps by supplying them with an ",(0,t.jsx)(n.code,{children:"id"})," property"]}),"\n",(0,t.jsxs)(n.li,{children:["Use ",(0,t.jsx)(n.code,{children:"if"})," key for conditionals and you MUST use one of the 4 special conditional functions in your if condition."]}),"\n",(0,t.jsxs)(n.li,{children:["You also have access to the basic conditional operators ",(0,t.jsx)(n.code,{children:"=="}),", ",(0,t.jsx)(n.code,{children:"!="}),", ",(0,t.jsx)(n.code,{children:"&&"}),", ",(0,t.jsx)(n.code,{children:"||"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"steps"})," context object has additional properties for dealing with conditionals:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"steps.<step-id>.outcome"})," : returns the outcome of the step, whether it succeeded, failed, or was skipped."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"steps.<step-id>.conclusion"})," : returns the outcome of the step, whether it succeeded, failed, or was skipped."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"The two above conditional properties have the same output values:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"'failure'"}),": the specified step failed"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"'success'"}
1),": the specified step succeeded"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"'skip'"}),": the specified step was skipped (it wasn't run or)"]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"steps:\n  - name: test\n    run: npm run test\n    id: test-step  # identifier for specific step. \n\n  - name: failed test\n\t# run if workflow up to this point failed and test-step step failed\n    if: failure() && steps.test-step.outcome == 'failure'\n    run: echo \"test failed\"\n"})}),"\n",(0,t.jsxs)(n.div,{className:"markdown-alert markdown-alert-important",dir:"auto",children:["\n",(0,t.jsxs)(n.p,{className:"markdown-alert-title",dir:"auto",children:[(0,t.jsx)(n.svg,{className:"octicon",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:(0,t.jsx)(n.path,{d:"M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"})}),"IMPORTANT"]}),"\n",(0,t.jsxs)(n.p,{children:["The most important thing to understand here is that a step will only run if a job fails if it includes the ",(0,t.jsx)(n.code,{children:"failure()"})," conditional function invocation in its conditional."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["You can use the ",(0,t.jsx)(n.code,{children:"continue-on-error"})," boolean property on the step level, which allows you to continue the workflow even if the specified step fails. This overrides the need for a conditional."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'jobs:\n  example:\n    runs-on: ubuntu-latest\n    steps:\n\t  # if this step fails, the workflow will continue\n      - name: This might fail\n        run: exit 1\n        continue-on-error: true\n      # this step will always run\n      - name: This will still run\n        run: echo "The previous step failed, but workflow continues."\n'})}),"\n",(0,t.jsx)(n.h4,{id:"conditionally-running-jobs",children:"Conditionally running jobs"}),"\n",(0,t.jsxs)(n.p,{children:["We can use the ",(0,t.jsx)(n.code,{children:"if"})," key at the job level to create conditional jobs. But since jobs are run in parallel, if we\u2019re listening for failure, we need to depend on other jobs first so we only run the failing conditional job if the jobs we depend on actually fail."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs: \n\t# ... jobs: lint, test, deploy\n\trun-if-any-other-job-fails: \n\t\tneeds: [lint, test, deploy]\n\t\t# run job only if any of the jobs in `needs` fail first.\n\t\tif: failure()\n\t\tsteps:\n\t\t\t# define steps ...\n"})}),"\n",(0,t.jsxs)(n.p,{children:["You can also use the ",(0,t.jsx)(n.code,{children:"continue-on-error"})," property at the job level to specify to continue running the job even if other jobs fail. This is especially useful for matrix strategies:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n  test:\n    strategy:\n      matrix:\n        node: [14, 16, 18]\n    runs-on: ubuntu-latest\n    continue-on-error: ${{ matrix.node == 14 }}  # only node 14 can fail without failing the workflow\n    steps:\n      - run: node --version\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Here's the summary of how to use ",(0,t.jsx)(n.code,{children:"continue-on-error"}),":"]}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Level"}),(0,t.jsx)(n.th,{children:"Syntax Example"}),(0,t.jsx)(n.th,{children:"Behavior"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Step"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"continue-on-error: true"})}),(0,t.jsx)(n.td,{children:"Step failure doesn\u2019t stop job"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Job"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"continue-on-error: true"})}),(0,t.jsx)(n.td,{children:"Job failure doesn\u2019t stop workflow"})]})]})]}),"\n",(0,t.jsx)(n.h4,{id:"caching",children:"Caching"}),"\n",(0,t.jsxs)(n.p,{children:["Just use the code below to cache node modules. It uses the ",(0,t.jsx)(n.code,{children:"actions/cache@v4"})," action to cache dependencies and only invalidate the cache if any of the arguments to the ",(0,t.jsx)(n.code,{children:"hashFiles()"})," global function changes."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["In this case, we cache based on the ",(0,t.jsx)(n.code,{children:"package-lock.json"}
1),". If that changes, the cache is invalidated. If it doesn't change, then we fetch from cache."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"  steps:\n\t  # 1. use action to cache ~/.npm unless package-lock.json changes\n    - name: Cache dependencies\n      id: cache\n      uses: actions/cache@v4\n      with:\n        path: ~/.npm\n        key: deps-node-modules-${{ hashFiles('**/package-lock.json') }}\n    - name: Install dependencies\n    \n\t    # 2. conditional step to run installation, only if not cache hit\n      if: steps.cache.outputs.cache-hit != 'true'\n      run: npm ci\n"})}),"\n",(0,t.jsxs)(n.p,{children:["There's an even easier shorthand for caching now: just use the ",(0,t.jsx)(n.code,{children:"setup-node@v4"})," official action, and specify that you want to cache dependencies using with ",(0,t.jsx)(n.code,{children:"with.cache"})," configuration and setting that to ",(0,t.jsx)(n.code,{children:'"npm"'}),"."]}),"\n",(0,t.jsx)(n.p,{children:"This evens let you skip all the song and dance of conditionally running the install step on a cache miss."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'jobs:\n\tjob_name:\n\t\tsteps:\n\t\t\t# checkout ...\n\t      - name: Set up Node.js\n\t        uses: actions/setup-node@v4\n\t        with:\n\t          node-version: "24"\n\t          cache: "npm"\n\t\t  - run: npm ci\n'})}),"\n",(0,t.jsx)(n.h4,{id:"creating-jobs-from-containers",children:"Creating jobs from containers"}),"\n",(0,t.jsx)(n.p,{children:"The advantage of using containers is that you get to customize your environment more than you can with the runners. You can choose which OS to use and which software to download, giving you complete control over your code execution environment."}),"\n",(0,t.jsx)(n.p,{children:"Using containers in a workflow is just having a base image to build off of if you want more control over your environment, but it doesn\u2019t mean the source code gets copied. You can only do that with custom actions."}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"container"})," key is a job level key that determines the public dockerhub image to use to build the runner from. It replaces the ",(0,t.jsx)(n.code,{children:"runs-on"})," job level key since the docker image will automatically specify the OS. All the subsequent commands will be run in the container."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n\ttest:\n\t\tcontainer:\n\t\t\timage: <dockerhub-image-name>\n"})}),"\n",(0,t.jsx)(n.h3,{id:"triggers",children:"triggers"}),"\n",(0,t.jsxs)(n.p,{children:["The most basic way to include triggers on your workflow is to refer to them in an array of triggers that you then pass to the ",(0,t.jsx)(n.code,{children:"on"})," key."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"on: [push]\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Simply setting the value of ",(0,t.jsx)(n.code,{children:"on"})," to an array of trigger names is the most basic way of including a trigger, and works in the broadest number of situations."]}),"\n",(0,t.jsx)(n.p,{children:"However, you can do extra config for a trigger, which helps you filter by event filters and activity types:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"event filters"}),": scopes the event down to be triggered on specific resources, like certain branches for pushes and pull requests. These are specified on the ",(0,t.jsx)(n.code,{children:"on.<event>.types"})," key."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"activity types"}),": scopes the event down to certain lifecycles in the event, like reopening or closing a pull request instead of just the generic event."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Here is an example of both:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'on:\n  label:\n\t# event filters for event "label"\n    types:\n      - created\n  push:\n    # activity types for event "push"\n    branches:\n      - main\n'})}),"\n",(0,t.jsx)(n.p,{children:"Whenever we do extra configuration for a trigger, we have to add the triggers on multiple lines below instead of smushing it into one array."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"on:\n\tpush:\n\t\tbranches: [main]\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Here are a list of the main triggers, which live under the root level ",(0,t.jsx)(n.code,{children:"on"})," key:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"push"}),": The push triggers when there is a push on a specified branch"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"pull_request"}),": triggers when there is a pull request to a specified branch"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"schedule"}),": triggers at a specific time based on a string of cron syntax"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Every single trigger has different event filters and activity types, so you should specify them accordingly."}),"\n",(0,t.jsx)(n.h4,{id:"how-to-skip-triggering-workflows",children:"How to skip triggering workflows"}),"\n",(0,t.jsx)(n.p,{children:"In order to skip triggering workflows on a commit or push, you can use these tactics:"}),"\n",(0,t.jsx)(n.p,{children:"If you want to make commits on your code and push them up without triggering a workflow, you can add special text to your commit workflow to do so:"}),"\n",(0,t.jsxs)(n.p,{children:["Adding in ",(0,t.jsx)(n.code,{children:"[skip ci]"})," or ",(0,t.jsx)(n.code,{children:"[skip actions]"})," verbatim inside your commit message will prevent running a workflow on the code you push."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'git commit -m "just added some bullshit don\'t run workflow [skip actions]"\n'})}),"\n",(0,t.jsx)(n.h4,{id:"push-trigger",children:"Push trigger"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"event filters"})}),"\n",(0,t.jsx)(n.p,{children:"Here are the event filters for the push trigger:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"branches"}),": a list of branches to listen for pushes to","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"paths-ignore"}),": a list of filepaths to ignore when pushing to a branch. This means that if those ignored files change during the new push, they won't trigger the workflow."]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"tags"}),": a list of tags to listen for pushes to"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"branches-ignore"}),": a list of branches to ignore pushes to"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"tags-ignore"}),": a list of tags to ignore pushes to"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["In all of these event filters, you can use glob syntax to refer to the branches or tags, meaning that something like ",(0,t.jsx)(n.code,{children:"dev-*"})," would refer to all branches starting with ",(0,t.jsx)(n.code,{children:"dev-"})," prefix."]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"pushing to certain branches example"})}),"\n",(0,t.jsx)(n.p,{children:"Here is an example of configuing the push trigger to only execute on certain branches:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Only listen to pushes on the ",(0,t.jsx)(n.code,{children:"main"})," branch."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"paths-ignore"})," event filter takes in an array of filepaths to ignore, meaning that the push event will not be triggered even if you make changes to those ignored files"]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'on:\n  push:\n    branches: [main]\n\t\tpaths-ignore: ["README.md", "LICENSE"]\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"pushing to tags example"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'on:\n  push:\n    tags: ["*"]\n'})}),"\n",(0,t.jsx)(n.h4,{id:"cron-scheduling",children:"Cron Scheduling"}),"\n",(0,t.jsxs)(n.p,{children:["By listening to the ",(0,t.jsx)(n.code,{children:"schedule"})," workflow trigger, you can pass in a cron event so that your github action runs jobs repeatedly based on a schedule."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"name: run every day\n\non:\n\tschedule: \n\t\t- cron: '0 0 * * *'\n"})}),"\n",(0,t.jsx)(n.h2,{id:"artifacts",children:"Artifacts"}),"\n",(0,t.jsxs)(n.p,{children:["When a workflow outputs some asset like an app binary or executable, that is called a ",(0,t.jsx)(n.strong,{children:"job artifact"}),"."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"We specify the filepaths we want to upload as artifacts, and then those files and folders get zipped up and ready to download on github."}),"\n",(0,t.jsx)(n.li,{children:"We can then download those job artifacts or use those in other jobs."}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"creating-artifacts",children:"Creating artifacts"}),"\n",(0,t.jsx)(n.p,{children:"Here is what an upload artifact step looks like:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["Use the ",(0,t.jsx)(n.code,{children:"actions/upload-artifact@v4"})," action"]}),"\n",(0,t.jsxs)(n.li,{children:["Configure the custom action with the ",(0,t.jsx)(n.code,{children:"with"})," key.","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"name"})," : the artifact name. This will be an important identifier used to download the same artifact"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"path"})," : the folder or file as a filepath to the content you want to upload as an artifact, relative to the current working directory"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: upload artifact\n  uses: actions/upload-artifact@v4\n  with:\n    name: vite-website\n    path: dist\n"})}),"\n",(0,t.jsxs)(n.p,{children:["By using the ",(0,t.jsx)(n.code,{children:"|"})," operator on the first line, you can write multi-line code and even upload multiple paths into one zip as a single artifact"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: upload artifact\n  uses: actions/upload-artifact@v3 \t# use upload artifact actions\n  with:\n    name: vite-website\t# artifact identifier/name\n\t\t# upload both dist and README.md as one artifact zip file\n    path: |  \t\t\n\t\t\tdist\n\t\t\tREADME.md\n"})}),"\n",(0,t.jsx)(n.h3,{id:"downloading-artifacts",children:"Downloading artifacts"}),"\n",(0,t.jsxs)(n.p,{children:["You can use the ",(0,t.jsx)(n.code,{children:"actions/download-artifact@v4"})," action to download an artifact that was uploaded previously. You can only download artifacts that were uploaded during the same workflow run. When you download a file, you can reference it by its uploaded artifact name."]}),"\n",(0,t.jsxs)(n.div,{className:"markdown-alert markdown-alert-note",dir:"auto",children:["\n",(0,t.jsxs)(n.p,{className:"markdown-alert-title",dir:"auto",children:[(0,t.jsx)(n.svg,{className:"octicon",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:(0,t.jsx)(n.path,{d:"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"})}),"NOTE"]}),"\n",(0,t.jsx)(n.p,{children:"downloading the artifact through the action automatically unzips the artifact as well."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Here are the properties we can configure while using that action through the ",(0,t.jsx)(n.code,{children:"with"})," key:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"name"})," : specifies which artifact we want to download by the artifact name we specify earlier when we uploaded them. ",(0,t.jsx)(n.strong,{children:"Required"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"path"}),": the directory path relative to the cwd inside the runner we want to unzip the artifact to. If the directory doesn't exist, it gets created. By default, the\xa0",(0,t.jsx)(n.code,{children:"download-artifact"}),"\xa0action downloads artifacts to the workspace directory that the step is executing in. You can use the\xa0",(0,t.jsx)(n.code,{children:"path"}),"\xa0input parameter to specify a different download directory."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      # ... build steps ...\n      - uses: actions/upload-artifact@v4\n        with:\n          name: build\n          path: ./output/\n\n  test:\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          name: build\n          path: ./output/\n      # ... test steps using the build artifact ...\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Here's another basic example where the ",(0,t.jsx)(n.code,{children:"vite-website"})," artifact gets unzipped, and all the files pop into the root directory:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: get artifact\n        uses: actions/download-artifact@v4\n        with:\n\t\t\t\t\t# downloads the artifact with the vite-website identifier\n          name: vite-website\n"})}),"\n",(0,t.jsx)(n.h2,{id:"using-docker",children:"Using docker"}),"\n",(0,t.jsx)(n.h3,{id:"building-a-runner-environment-from-docker",children:"Building a runner environment from docker"}),"\n",(0,t.jsx)(n.p,{children:"If you need extra configuration or software that doesn't come with popular github actions (stuff like deno or bun), then you can configure your runner to pull from a base docker image and use that as its base."}),"\n",(0,t.jsxs)(n.p,{children:["You specify the container configuration through the ",(0,t.jsx)(n.code,{children:"container"})," key on the ",(0,t.jsx)(n.code,{children:"jobs.<job-name>"})," level:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"container"}),": container configuration for the job's runner","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"image"}),": the base image to pull from"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"env"}),": a list of environment variables to pass into the container."]}
1),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n\tjob-name:\n\t\tcontainer:\n\t\t\timage: node:20\n"})}),"\n",(0,t.jsx)(n.h3,{id:"running-jobs-with-containers",children:"Running jobs with containers"}),"\n",(0,t.jsxs)(n.p,{children:["To run multiple-container apps in actions (why would you tho), you can use the ",(0,t.jsx)(n.code,{children:"services"})," key on a job, which lets you pull down a docker image and run it:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n  container-job:\n    runs-on: ubuntu-latest\n    container: node:20-bookworm-slim\n    services:\n      postgres:\n        image: postgres\n    steps:\n      - name: Check out repository code\n        uses: actions/checkout@v4\n      - name: Install dependencies\n        run: npm ci\n      - name: Connect to PostgreSQL\n        run: node client.js\n        env:\n          POSTGRES_HOST: postgres\n          POSTGRES_PORT: 5432\n"})}),"\n",(0,t.jsx)(n.h2,{id:"reusable-workflows",children:"Reusable Workflows"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Reusable workflows"})," are workflows that other workflows can use in their jobs. You define this reusable workflow in its separate YAML file, and then the other workflows have jobs that make calls to that workflow."]}),"\n",(0,t.jsx)(n.p,{children:"Here are the main important things to realize about reusable workflows:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Yaml files that listen to the ",(0,t.jsx)(n.code,{children:"workflow_call"})," trigger become reusable workflows."]}),"\n",(0,t.jsx)(n.li,{children:"A reusable workflow is just like a normal workflow (except for the trigger), so they can have multiple jobs, use job outputs, etc."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"If a job uses a reusable workflow, it can't have any other steps or use any other jobs. The reusable workflow is essentially just a straight up replacement."}),"\n",(0,t.jsx)(n.h3,{id:"basics-1",children:"basics"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Create a yaml workflow file and listen to the ",(0,t.jsx)(n.code,{children:"workflow_call"})," trigger. This makes it a reusable workflow"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'# reusable-workflow.yaml\nname: reusable\non: workflow_call\njobs:\n  confirm_deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: confirm\n        run: echo "Deploying confirmed! you did it!"\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["In the jobs that use this workflow, use the ",(0,t.jsx)(n.code,{children:"uses"})," keyword and pass in the path to the reusable workflow yaml file. It should be a relative path relative to the project root, since that is the CWD of the runner."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n\tdeploy:\n\t    needs: build\n\t    uses: ./.github/workflows/reusable.yaml\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"accepting-inputs-to-a-reusable-workflow",children:"Accepting inputs to a reusable workflow"}),"\n",(0,t.jsxs)(n.p,{children:["Reusable workflows are very similar to actions. We can pass in inputs and dynamic variables using the ",(0,t.jsx)(n.code,{children:"with"})," keyword just like how we configure actions."]}),"\n",(0,t.jsx)(n.p,{children:"But there are necessary steps to accept input into the reusable workflow first:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Specify the ",(0,t.jsx)(n.code,{children:"inputs"})," key under the ",(0,t.jsx)(n.code,{children:"workflow_call"})," trigger in the reusable workflow. This allows us to create and describe parameters for the reusable workflow"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="reusable.yaml"',children:'# define inputs\n\nname: reusable\non:\n  workflow_call:\n    # specify inputs to reusable workflow, specified by the `with` key\n    inputs:\n      # list of all parameters and how to configure them\n      username:\n\t\t\t\t# we made inputs.username an optional string parameter\n        description: "
1name of the person running the workflow"\n        required: false\n        type: string\n        default: "just that guy, you know?"\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Now all the input parameters live on the ",(0,t.jsx)(n.code,{children:"inputs"})," context object. Use that dynamically in our reusable workflow by using github expressions"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="reusable.yaml"',children:'# reusable workflow jobs\n\njobs:\n  confirm_deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: confirm\n      \n        # parameters can be accessed from inputs context object\n        run: echo "Deploying confirmed, ${{ inputs.username }}! you did it!"\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["In the job that uses the reusable workflow, make sure to pass the values for the input parameters by using the ",(0,t.jsx)(n.code,{children:"with"})," keyword:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="mainworkflow.yaml"',children:'deploy:\n    needs: build\n    uses: ./.github/workflows/reusable.yaml\n    with:\n      username: "poopy butthole is just that guy"\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"creating-reusable-workflow-outputs",children:"Creating reusable workflow outputs"}),"\n",(0,t.jsxs)(n.p,{children:["Reusable workflows are like substitutions for an entire job, so we can access their output by using the jobs context through the ",(0,t.jsx)(n.code,{children:"needs"})," object."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Define the ",(0,t.jsx)(n.code,{children:"outputs"})," key at the job level. Assign a value for the output by redirecting to ",(0,t.jsx)(n.code,{children:"$GITHUB_OUTPUT"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="reusable.yaml"',children:'# .github/reusable-workflows/reusable.yaml\nname: reusable\non: workflow_call\n\njobs:\n\tdeploy:\n\t\toutputs:\n\t\t\tmy-output: ${{ steps.set-result.outputs.my-output }}\n\t\truns-on: ubuntu-latest\n\t\tsteps:\n\t\t\t- uses: actions/checkout@v4\n\t\t\t- name: Set job output\n\t\t\t\t# 1. set id for step\n\t\t\t\tid: set-result\n\t\t\t\t# 2. set value for job output\n\t\t\t\trun: echo "my-output=deployed" >> $GITHUB_OUTPUT\n\t\t\t\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Access the reusable workflow\u2019s output by accessing the ",(0,t.jsx)(n.code,{children:"needs.<job-name>.outputs.<output-name>"})," syntax."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="main.yaml"',children:'\njobs:\n\t# 1. job that uses reusable workflow\n\tdeploy:\n\t\tuses: ./.github/reusable-workflows/reusable.yaml\n\tpost-deploy:\n\t\tneeds: deploy\n\t\tsteps:\n\t\t\t- name: "print deploy output"\n\t\t\t\trun: "Deploy output is ${{ needs.deploy.outputs.my-output }}"\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"custom-actions",children:"Custom actions"}),"\n",(0,t.jsx)(n.p,{children:"There are three different types of custom actions:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"javascript actions:"})," Runs a javascript file of your choice. Basically just writing JS"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"docker actions:"})," Runs a Docker image you specify"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"composite actions:"})," Similar to reusable workflows where you combine multiple ",(0,t.jsx)(n.code,{children:"runs"})," and ",(0,t.jsx)(n.code,{children:"uses"})," commands to create reusable actions"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Custom actions are ",(0,t.jsx)(n.code,{children:"action.yaml"})," files that you can use in your workflow code. Each custom action file needs these three root level keys:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"name"}),": the name of the action. Think of it like a short description"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"description"}),": the action description"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"runs"}),": the configuration for the action","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"using"}),":  specifies the type of custom action the action will be.","\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"'composite'"})," for composite"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"'docker'"})," for docker"]}
1),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"'node18'"}),": for node 18 actions"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"You also have these additional keys you can set on the root level of creating a custom action:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"env"}),": any environment variable pairs to pass into the action and make available."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Here's basic decision making for when to use each:"}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Type"}),(0,t.jsx)(n.th,{children:"Runs on"}),(0,t.jsx)(n.th,{children:"Language/Env"}),(0,t.jsx)(n.th,{children:"Use Case"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"JS/TS"}),(0,t.jsx)(n.td,{children:"Node.js runner"}),(0,t.jsx)(n.td,{children:"JS/TS"}),(0,t.jsx)(n.td,{children:"GitHub API, logic, quick scripts"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Docker"}),(0,t.jsx)(n.td,{children:"Any (via container)"}),(0,t.jsx)(n.td,{children:"Any"}),(0,t.jsx)(n.td,{children:"Custom tools, non-Node languages"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Composite"}),(0,t.jsx)(n.td,{children:"Runner shell"}),(0,t.jsx)(n.td,{children:"Shell, YAML"}),(0,t.jsx)(n.td,{children:"Orchestration, chaining actions"})]})]})]}),"\n",(0,t.jsx)(n.h3,{id:"composite-actions",children:"Composite actions"}),"\n",(0,t.jsx)(n.p,{children:"Composite actions can be used to make certain workflow steps reusable, or they can be shoved into their own repository and then published on the github marketplace."}),"\n",(0,t.jsxs)(n.div,{className:"markdown-alert markdown-alert-note",dir:"auto",children:["\n",(0,t.jsxs)(n.p,{className:"markdown-alert-title",dir:"auto",children:[(0,t.jsx)(n.svg,{className:"octicon",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:(0,t.jsx)(n.path,{d:"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"})}),"NOTE"]}),"\n",(0,t.jsx)(n.p,{children:"You should think of a composite action as just copying and pasting steps from the custom action into the actual workflow, which means you can skip common steps like checking out. They can be truly reusable."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Here are the steps to follow when creating a local composite action:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["In the ",(0,t.jsx)(n.code,{children:".github"})," folder, create another folder that will house all of your actions. The folder name doesn\u2019t matter, but just name it ",(0,t.jsx)(n.code,{children:"actions"})]}),"\n",(0,t.jsxs)(n.li,{children:["In the ",(0,t.jsx)(n.code,{children:".github/actions"})," folder, create another folder, which will house your custom action. Then create a yaml file, which ",(0,t.jsx)(n.strong,{children:"must"})," be called ",(0,t.jsx)(n.code,{children:"action.yaml"})," . This is your custom action."]}),"\n",(0,t.jsxs)(n.li,{children:["You can use the custom action by just using the ",(0,t.jsx)(n.code,{children:"uses"})," keyword and passing the path to the custom action, relative to the project root folder."]}),"\n"]}),"\n",(0,t.jsxs)(n.div,{className:"markdown-alert markdown-alert-important",dir:"auto",children:["\n",(0,t.jsxs)(n.p,{className:"markdown-alert-title",dir:"auto",children:[(0,t.jsx)(n.svg,{className:"octicon",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:(0,t.jsx)(n.path,{d:"M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"})}),"IMPORTANT"]}),"\n",(0,t.jsxs)(n.p,{children:["You need to checkout the code before calling any custom actions because the code you checkout includes getting the actions that live ",(0,t.jsx)(n.code,{children:".github"})," folder (you use the custom action by referring to its filepath within your project code), so you can\u2019t call the action that lives in your code unless:"]}),"\n",(0,t.jsx)(n.p,{children:"a) you checked out the code first\nb) the action lives online in the github marketplace"}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"basics-2",children:"Basics"}),"\n",(0,t.jsxs)(n.p,{children:["This is a composite action since the ",(0,t.jsx)(n.code,{children:"runs.using"})," key is set to ",(0,t.jsx)(n.code,{children:'"composite"'})," and we specify actions steps through ",(0,t.jsx)(n.code,{children:"run.steps"}),"."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="action.yaml"',children:'# ./.github/actions/install-deps/action.yaml\n\nname: "Get + cache dependencies"\ndescription: "Get and cache dependencies for faster builds"\n\n# required key. Basically what steps to run in the actions\nruns:\n  using: "composite"  # required to define as composite action\n  steps:\n    - name: Cache dependencies\n      id: cache\n      uses: actions/cache@v4\n      with:\n        path: ~/.npm\n        key: deps-node-modules-${{ hashFiles(\'**/package-lock.json\') }}\n    - name: Install dependencies\n      if: steps.cache.outputs.cache-hit != \'true\'\n      run: npm ci\n      shell: bash  # shell: bash required when using `runs` command\n'})}),"\n",(0,t.jsxs)(n.p,{children:["You can use the custom action by just using the ",(0,t.jsx)(n.code,{children:"uses"})," keyword and passing the path to the custom action, relative to the project root folder."]}
1),"\n",(0,t.jsxs)(n.p,{children:["If a custom action lives under the ",(0,t.jsx)(n.code,{children:".github/actions"})," folder and has a name of ",(0,t.jsx)(n.code,{children:"myaction.yaml"}),", then the path you would refer to would be ",(0,t.jsx)(n.code,{children:"/.github/actions/myaction"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n\t\t\t# must check out code in workflow. Cannot be extracted to custom action\n      - name: Get code\n        uses: actions/checkout@v3\n      - name: Install + Cache\n\t\t\t\t# path must be relative to project root\n        uses: ./.github/actions/install-deps\n"})}),"\n",(0,t.jsx)(n.h4,{id:"inputs",children:"inputs"}),"\n",(0,t.jsxs)(n.p,{children:["Inputs to custom actions are specified by the ",(0,t.jsx)(n.code,{children:"with"})," key when using the action. We define what configuration we allow with the ",(0,t.jsx)(n.code,{children:"inputs"})," key in our custom action:"]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Describe input variables with the ",(0,t.jsx)(n.code,{children:"inputs"})," key in our custom action, then use them somewhere in our code for dynamic ability"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="action.yaml"',children:'name: "Get + cache dependencies"\ndescription: "Get and cache dependencies for faster builds"\n# key to describe all inputs\ninputs:\n should-use-cache:\n\t# not required, with a default of "true"\n   description: "whether or not to use the cache"\n   required: false\n   default: "true"\n\nruns:\n  using: "composite"\n  steps:\n    - name: Cache dependencies\n      id: cache\n      uses: actions/cache@v3\n      with:\n        path: node_modules\n        key: deps-node-modules-${{ hashFiles(\'**/package-lock.json\') }}\n    - name: Install dependencies\n      id: install\n\t\t\t# if user does not want to cache, then just install\n      if: inputs.should-use-cache == \'false\'\n      run: npm ci\n      shell: bash\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["To use inputs in the custom action, use the ",(0,t.jsx)(n.code,{children:"with"})," key to configure the input variables you pass into the custom action"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="main.yaml"',children:"jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Get code\n        uses: actions/checkout@v3\n      - name: Install + Cache\n        uses: ./.github/actions/install-deps\n        id: custom-action\n\t\t\t\t# use the with key to specify which input variable to configure\n        with:\n          should-use-cache: true\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"outputs",children:"outputs"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["You can specify any outputs for the custom action with the ",(0,t.jsx)(n.code,{children:"outputs"})," root level key."]}),"\n",(0,t.jsxs)(n.li,{children:["Set a value for the output by echoing a key value pair into ",(0,t.jsx)(n.code,{children:"$GITHUB_OUTPUT"})]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="action.yaml"',children:'name: "Get + cache dependencies"\ndescription: "Get and cache dependencies for faster builds"\noutputs:\n\t# 1. specify outputs.cache-was-used\n  cache-was-used:\n    description: "whether or not the cache was used"\n    value: ${{steps.install.outputs.cache-used}}\n\nruns:\n  using: "composite"\n  steps:\n    - name: Cache dependencies\n      id: cache\n      uses: actions/cache@v3\n      with:\n        path: node_modules\n        key: deps-node-modules-${{ hashFiles(\'**/package-lock.json\') }}\n    - name: Install dependencies\n      id: install\n      if: steps.cache.outputs.cache-hit != \'true\'\n\t  # 2. redirect the cache-was-used=false pair into github output\n      run: npm ci && echo "cache-was-used=\'false\'" >> $GITHUB_OUTPUT\n      shell: bash\n'})}),"\n",(0,t.jsx)(n.p,{children:"This is how we would access the output from the custom action, as normal."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="main.yaml"',children:"jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Get code  \n        uses: actions/checkout@v3\n      - name: Install + Cache\n        uses: ./.github/actions/install-deps\n        \n\t\t\t\t# need id so we can grab step with steps.<id>.outputs syntax\n        id: custom-action\n\n      - name: Confirm cache\n        run: echo 'Cache used? ${{ steps.custom-action.outputs.cache-was-used }}''\n"})}),"\n",(0,t.jsx)(n.h3,{id:"custom-actions-with-docker",children:"Custom actions with docker"}),"\n",(0,t.jsx)(n.p,{children:"With custom docker actions, you can create a custom action that builds and runs a Docker container. This has the following advantages:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Being able to create custom actions in any language you want, not just javascript."}),"\n",(0,t.jsx)(n.li,{children:"Being able to download other software to use in the custom action"}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"creating-the-custom-action",children:"Creating the custom action"}),"\n",(0,t.jsx)(n.p,{children:"When writing the dockerfile, use these basic rules:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Do not use the ",(0,t.jsx)(n.code,{children:"WORKDIR"})," command. GIthub does that behind the scenes."]}),"\n",(0,t.jsx)(n.li,{children:"Only focus on installation of dependencies, and instead run a bash file for the docker 
1container entrypoint."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"And here are the steps:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Create a ",(0,t.jsx)(n.code,{children:"action.yaml"})," file, make sure to specify it uses docker"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"# .github/actions/test/action.yaml\nname: 'Custom docker action'\ndescription: 'Installs code and runs a test on it'\n\nruns:\n\tusing: 'docker'     # specifies we want to make a docker action\n\timage: 'Dockerfile' # path to dockerfile\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Create a dockerfile that the action will use. It\u2019s common practice to copy over a bash script into the container and then run it."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-dockerfile",children:'FROM node:20-alpine as builder\n\n# 1. essential downloads\nRUN apk update\nRUN apk add bash\nRUN apk add curl\nRUN apk --no-cache add ca-certificates wget\nRUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub <https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub>\nRUN wget <https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk>
1\nRUN apk add --no-cache --force-overwrite glibc-2.28-r0.apk\n\n# 2. install bun\nRUN npm install -g bun\n\n# 3. install python, ffmpeg, and yt-dlp\nRUN apk add --no-cache python3 py3-pip\nRUN apk add --no-cache ffmpeg\nRUN apk -U add yt-dlp\n\n# 4. copy entrypoint.sh, which we will run\nCOPY entrypoint.sh /entrypoint.sh\n\n# 5. copy everything in my project folder into the container\nCOPY ../../../* ./\n\n# 6. run bash script\nENTRYPOINT [ "bash", "/entrypoint.sh" ]\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Create the bash script that does stuff"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"bun install\nnpm install --prefix frontend\nbun test\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"And then this is how you would use that custom action in your workkflow:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "first workflow in a while: running and testing"\non: push\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: "Checkout code"\n        uses: actions/checkout@v4\n      - uses: ./.github/actions/test  # run custom docker action\n'})}),"\n",(0,t.jsx)(n.h4,{id:"using-environment-variables",children:"Using environment variables"}),"\n",(0,t.jsxs)(n.p,{children:["You can set environment variables for a custom action by using the ",(0,t.jsx)(n.code,{children:"env"})," key. These are then plugged in as environment variables your container will have."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "Test API code"\ndescription: "Run tests on the API code"\n\nruns:\n  using: "docker"\n  image: "Dockerfile"\n  env:\n    SERVER_MODE: "development"\n    PORT: "3000"\n    VITE_API_URL_DEV: "<http://localhost:3000>"\n    GITHUB_WORKSPACE: "${{ github.workspace }}"\n'})}),"\n",(0,t.jsx)(n.h4,{id:"build-outputs",children:"Build outputs"}),"\n",(0,t.jsxs)(n.p,{children:["THe working directory everything is set in is the ",(0,t.jsx)(n.code,{children:"/github/workspace"})," directory. Here are the basic things we do:"]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Specify the output keys we will create in the custom action"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "Test API code"\ndescription: "Run tests on the API code"\n\nruns:\n  using: "docker"\n  image: "Dockerfile"\n  env:\n    SERVER_MODE: "development"\n    PORT: "3000"\n    VITE_API_URL_DEV: "<http://localhost:3000>"\n    \n# specify single output as frontend-build-path\noutputs:\n  frontend-build-path:\n    description: "The path to the frontend build"\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Provide a value for the output keys in our code that gets run in the container using special syntax."}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["The ",(0,t.jsx)(n.code,{children:'echo "name=value" >> $GITHUB_OUTPUT'})," syntax is how we add outputs to a custom action, where ",(0,t.jsx)(n.code,{children:"name"})," is the output key name, and ",(0,t.jsx)(n.code,{children:"value"})," is the value to set for that specific output key."]}),"\n",(0,t.jsxs)(n.li,{children:["In the example below, we set the ",(0,t.jsx)(n.code,{children:"frontend-build-path"})," output key to the vite build destination."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'echo "PWD is $(pwd)"\n\nbun install\nnpm install --prefix frontend\nbun test\n\nnpm run build --prefix frontend\n\necho "building was success. Here is path to frontend dist: $(pwd)/dist"\n# assign value to frontend-build-path output key\necho "frontend-build-path=$(pwd)/dist" >> $GITHUB_OUTPUT\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"GRab out the value of the otuput by assigning an id to the step that used the custom action."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'- uses: ./.github/actions/test\n\t# 1. assign id\n  id: testing-docker   \n  \n- name: "testing output"\n\t# 2. use steps.<id>.outputs.<output-name> syntax\n\trun: "the output is ${{ steps.testing-docker.outputs.frontend-build-path}}"\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"artifacts"})}),"\n",(0,t.jsx)(n.h4,{id:"artifacts-1",children:"Artifacts"}),"\n",(0,t.jsxs)(n.p,{children:["To create an artifact from a path we create during the custom docker action, we can only download an artifact of the folderpath living in the ",(0,t.jsx)(n.code,{children:"$GITHUB_WORKSPACE"})," environment variable or ",(0,t.jsx)(n.code,{children:"github.workspace"}
1)," context object. This is the only way, so we often do some cleanup by deleting files and folders we don\u2019t want to be included int he artifact beforehand."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Artifact code"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'name: "first workflow in a while: running and testing"\non: push\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: "Checkout code"\n        uses: actions/checkout@v4\n        # 1. use custom action, which runs bash file\n      - uses: ./.github/actions/test\n        id: testing-docker\n        # 2. use actions/upload-artifact@v4 to upload artifact\n      - name: download vite build\n        uses: actions/upload-artifact@v4\n        id: download-artifact\n        with:\n\t        # 3. this must always be path\n          path: ${{ github.workspace }}\n          name: vite-build\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Delete unwanted files and folders in ",(0,t.jsx)(n.code,{children:"entrypoint.sh"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'echo "PWD is $(pwd)"\n\nbun install\nnpm install --prefix frontend\nbun test\n\nnpm run build --prefix frontend\n\necho "building was success. Here is path to frontend dist: $(pwd)/dist"\necho "frontend-build-path=$(pwd)/dist" >> $GITHUB_OUTPUT\necho "GITHUB OUTPUT: $GITHUB_OUTPUT"\n\n# remove all files except dist directory\nrm -rf .devcontainer\nrm -rf api\nrm -rf .github\nrm -rf routes\nrm -rf tests\nrm -rf frontend/node_modules\nrm -rf node_modules\nfind . -maxdepth 1 -type f -not -name \'dist\' -exec rm -rf {} \\\\;\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"custom-actions-with-javascript",children:"Custom actions with javascript"}),"\n",(0,t.jsx)(n.p,{children:"JavaScript actions point to a javascript file, and will run the code in that file as the action."}),"\n",(0,t.jsx)(n.p,{children:"Similar to building a composite action, this is how to register a JavaScript action:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Create an ",(0,t.jsx)(n.code,{children:"action.yaml"})," somewhere in your ",(0,t.jsx)(n.code,{children:".github"})," directory that is not in the workflows folder."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Use the ",(0,t.jsx)(n.code,{children:"main"})," key to specify the local filepath to the main javascript file you will run for the action:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'name: "JS action"\ndescription: "My first JavaScript action"\nruns: \n\tusing: "node18"  # which version of node to run on\n\tmain: "main.js" # points to main.js in the same folder for running action\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"javascript-actions-api",children:"javascript actions api"}),"\n",(0,t.jsx)(n.p,{children:"This is what the API looks like for javascript actions:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"You need to install the three dependencies below, and you need to commit a separate node modules folder that has the dependencies of your custom js action."}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const core = require('@actions/core');\nconst github == require('@actions/github');\nconst exec = require('@actions/exec');\n\nconsole.log('Hello from JS Action!');\ncore.setOutput('greeting', 'Hello from JS Action!');\n"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"logging"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"core.info('Info message');      // Standard log\ncore.warning('Warning message'); // Yellow warning\ncore.error('Error message');     // Red error\n\ncore.startGroup('Group title');\ncore.info('This is grouped');\ncore.endGroup();\n"})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"core.notice(message)"}),": logs a message to the github actions logs"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"core.info(message)"}),": logs the message"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"processes"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"core.exportVariable('MY_ENV', 'value'); // Sets $MY_ENV for following steps\ncore.setFailed('Something went wrong'); // Marks the step as failed\nprocess.exit(1); // Also marks as failed, but less graceful\n"})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"core.setFailed(message)"}),": fails the action and logs the error message"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"core.exportVariable(key, value)"}
1),": exports the env variable to the shell so that subsequent steps can access that env variable"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"inputs and outputs"})}),"\n",(0,t.jsx)(n.p,{children:"You can programmatically accept action inputs and produce action outputs through js as well."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const name = core.getInput('name');\ncore.setOutput('greeting', `Hello, ${name}!`);\n"})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"core.getInput(key)"}),": gets the specified action input"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"core.setOutput(key, value)"}),": creates an action/step output"]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"github-context-object",children:[(0,t.jsx)(n.code,{children:"github"})," context object"]}),"\n",(0,t.jsxs)(n.p,{children:["the ",(0,t.jsx)(n.code,{children:"@actions/github"})," library lets you access the ",(0,t.jsx)(n.code,{children:"github"})," context object and also make requests to the github API using octokit."]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"getting github context"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const context = github.context;\nconsole.log(context.payload.pull_request.title); // If event is pull_request\nconsole.log(context.repo.owner, context.repo.repo);\n"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"rest api"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const token = core.getInput('github-token');\nconst octokit = github.getOctokit(token);\n\nconst { data: issues } = await octokit.rest.issues.listForRepo({\n  owner: context.repo.owner,\n  repo: context.repo.repo,\n  state: 'open'\n});\nissues.forEach(issue => core.info(`#${issue.number}: ${issue.title}`));\n"})}),"\n",(0,t.jsx)(n.h4,{id:"running-shell-commands",children:"Running shell commands"}),"\n",(0,t.jsxs)(n.p,{children:["with the ",(0,t.jsx)(n.code,{children:"@actions/exec"})," library, we can run shell commands in our runner through javascript:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"exec.exec(\"echo 'hello world'\")\n"})}),"\n",(0,t.jsx)(n.h2,{id:"integrations",children:"Integrations"}),"\n",(0,t.jsx)(n.h3,{id:"sending-email-notifications",children:"Sending email notifications"}),"\n",(0,t.jsx)(n.h2,{id:"act",children:"ACT"}),"\n",(0,t.jsxs)(n.p,{children:["You can use the ",(0,t.jsx)(n.code,{children:"act"})," CLI tool easily to test your github actions locally. You can choose to download a medium-size version of the docker image."]}),"\n",(0,t.jsx)(n.h3,{id:"basic-usage",children:"basic usage"}),"\n",(0,t.jsxs)(n.p,{children:["Running the ",(0,t.jsx)(n.code,{children:"act"})," command in your project looks for workflow files from the root level and runs all workflows locally."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"act\n"})}),"\n",(0,t.jsx)(n.p,{children:"If running on a mac, you may need to specify to use linux architecture to avoid issues:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"act --container-architecture linux/amd64\n"})}),"\n",(0,t.jsx)(n.h3,{id:"with-specific-triggers",children:"With specific triggers"}),"\n",(0,t.jsxs)(n.p,{children:["You can choose to run only specific triggers with the ",(0,t.jsx)(n.code,{children:"act <trigger>"})," command"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"act <trigger>\n"})}),"\n",(0,t.jsxs)(n.p,{children:["For example, the ",(0,t.jsx)(n.code,{children:"act workflow_dispatch"})," command would run only the workflows with a ",(0,t.jsx)(n.code,{children:"workflow_dispatch"})," trigger."]})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453:(e,n,s)=>{s.d(n,{R:()=>r,x:()=>a});var t=s(6540);const i={},o=t.createContext(i);function r(e){const n=t.useContext(o);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.