├── tests ├── math.typ └── valid.typ ├── Dockerfile ├── .github └── workflows │ └── integration.yml ├── LICENSE ├── action.yml ├── entrypoint.py └── README.md /tests/math.typ: -------------------------------------------------------------------------------- 1 | #set page( 2 | width: auto, 3 | height: auto, 4 | background: rect(width: 100%, height: 100%, fill: rgb("ffe5b4")) 5 | ) 6 | #set text(28pt) 7 | 8 | $ e^(i pi) + 1 = 0 $ -------------------------------------------------------------------------------- /tests/valid.typ: -------------------------------------------------------------------------------- 1 | #set document(title: "Test file", author: "Louis Vignoli") 2 | 3 | = Test file 4 | 5 | _Some_ *text*. 6 | 7 | A function call: #lorem(20). 8 | 9 | Math: $ a^2 + b^2 = c^2. $ 10 | 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/typst/typst 2 | 3 | LABEL \ 4 | org.opencontainers.image.title="Typst GitHub action based on ghcr.io/typst/typst image" \ 5 | org.opencontainers.image.authors="Louis Vignoli " \ 6 | org.opencontainers.image.source="https://github.com/lvignoli/typst-action" 7 | 8 | RUN apk add python3 9 | 10 | COPY \ 11 | LICENSE \ 12 | README.md \ 13 | entrypoint.py \ 14 | /root/ 15 | 16 | ENTRYPOINT ["python3", "/root/entrypoint.py"] 17 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: Integration Test 2 | on: [push] 3 | jobs: 4 | build_multiple_files_with_options: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - name: Self test 9 | id: selftest 10 | uses: lvignoli/typst-action@develop 11 | with: 12 | source_file: | 13 | tests/valid.typ 14 | tests/math.typ 15 | options: | 16 | --font-path 17 | fonts/ 18 | -vv 19 | - name: Check existence of output file 20 | run: | 21 | test -e "tests/valid.pdf" 22 | test -e "tests/math.pdf" 23 | - name: upload artifacts 24 | uses: actions/upload-artifact@v4 25 | with: 26 | name: Output PDF files 27 | path: tests/*.pdf 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Louis Vignoli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Github Action for Typst 2 | description: GitHub Action to compile typst documents 3 | author: Louis Vignoli 4 | branding: 5 | icon: book-open 6 | color: blue 7 | 8 | inputs: 9 | source_file: 10 | description: > 11 | The typst file to be compiled. There can be multiple ones separated by new lines. 12 | required: true 13 | options: 14 | description: > 15 | Line separated array of global options to pass to typst. Compared to a CLI call, with space 16 | separated field, each file must be on its own lines. For example, to pass an additional font 17 | path using the --font-path global command, the CLI call is 18 | 19 | typst --font-path fonts main.typ 20 | 21 | For the action, one must use one line per argument to ensure correct parsing, in the following 22 | form: 23 | 24 | - name: Typst 25 | uses: lvignoli/typst-action@main 26 | with: 27 | source_file: main.typ 28 | options: | 29 | --font-path 30 | fonts 31 | required: false 32 | default: "" 33 | runs: 34 | using: docker 35 | image: Dockerfile 36 | args: 37 | - ${{ inputs.source_file }} 38 | - ${{ inputs.options }} 39 | -------------------------------------------------------------------------------- /entrypoint.py: -------------------------------------------------------------------------------- 1 | """Script to compile Typst source files.""" 2 | import logging 3 | import subprocess 4 | import sys 5 | 6 | 7 | def compile(filename: str, options: list[str]) -> bool: 8 | """Compiles a Typst file with the specified global options. 9 | 10 | Returns True if the typst command exited with status 0, False otherwise. 11 | """ 12 | command = ["typst", "compile"] + options + [filename] 13 | logging.debug("Running: " + " ".join(command)) 14 | 15 | result = subprocess.run(command, capture_output=True, text=True) 16 | try: 17 | result.check_returncode() 18 | except subprocess.CalledProcessError: 19 | logging.error(f"Compiling {filename} failed with stderr: \n {result.stderr}") 20 | return False 21 | 22 | return True 23 | 24 | 25 | def main(): 26 | 27 | logging.basicConfig(level=logging.INFO) 28 | 29 | # Parse the positional arguments, expected in the following form 30 | # 1. The Typst files to compile in a line separated string 31 | # 2. The global Typst CLI options, in a line separated string. It means each 32 | # whitespace separated field should be on its own line. 33 | source_files = sys.argv[1].splitlines() 34 | options = sys.argv[2].splitlines() 35 | 36 | version = subprocess.run( 37 | ["typst", "--version"], capture_output=True, text=True 38 | ).stdout 39 | logging.info(f"Using version {version}") 40 | 41 | success: dict[str, bool] = {} 42 | 43 | for filename in source_files: 44 | filename = filename.strip() 45 | if filename == "": 46 | continue 47 | logging.info(f"Compiling {filename}…") 48 | success[filename] = compile(filename, options) 49 | 50 | # Log status of each input files. 51 | for filename, status in success.items(): 52 | logging.info(f"{filename}: {'✔' if status else '❌'}") 53 | 54 | if not all(success.values()): 55 | sys.exit(1) 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typst GitHub action 2 | 3 | Build Typst documents using GitHub workflows. 4 | 5 | ## Minimal example 6 | 7 | The following `.github/workflows/build.yaml` action compiles `main.typ` to `main.pdf` on every push. 8 | 9 | ```yaml 10 | name: Build Typst document 11 | on: push 12 | 13 | jobs: 14 | build_typst_documents: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | - name: Typst 20 | uses: lvignoli/typst-action@main 21 | with: 22 | source_file: main.typ 23 | ``` 24 | 25 | ## Longer example 26 | 27 | Here we compile multiple files on each push, and publish all the PDFs in a tagged and timestamped release when the commit is tagged. 28 | 29 | ```yaml 30 | name: Build Typst document 31 | on: [push, workflow_dispatch] 32 | 33 | permissions: 34 | contents: write 35 | 36 | jobs: 37 | build: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v3 42 | 43 | - name: Typst 44 | uses: lvignoli/typst-action@main 45 | with: 46 | source_file: | 47 | first_file.typ 48 | second_file.typ 49 | third_and_final_file.typ 50 | 51 | - name: Upload PDF file 52 | uses: actions/upload-artifact@v4 53 | with: 54 | name: PDF 55 | path: *.pdf 56 | 57 | - name: Get current date 58 | id: date 59 | run: echo "DATE=$(date +%Y-%m-%d-%H:%M)" >> $GITHUB_ENV 60 | 61 | - name: Release 62 | uses: softprops/action-gh-release@v1 63 | if: github.ref_type == 'tag' 64 | with: 65 | name: "${{ github.ref_name }} — ${{ env.DATE }}" 66 | files: main.pdf 67 | 68 | ``` 69 | 70 | Repository [lvignoli/typst-action-example](https://github.com/lvignoli/typst-action-example) provides an example setup on a whole repo. 71 | 72 | ## Notes 73 | 74 | - This action runs on the docker image shipped with the latest Typst. 75 | As long as Typst is in v0, changes of the CLI API are to be expected, breaking the workflow. 76 | I'll update regularly. 77 | 78 | - I was hasty to tag for a v1. I have now deleted it. 79 | As long as Typst is not in a stable state, the action will stay in v0. 80 | You should use `lvignoli/typst-action@main` in the meantime. 81 | --------------------------------------------------------------------------------