diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..6d9047e --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,20 @@ +name: npm publish +on: + release: + types: published + workflow_dispatch: +concurrency: ${{ github.workflow }} +jobs: + npm-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts + cache: npm + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/npm-test.yml b/.github/workflows/npm-test.yml new file mode 100644 index 0000000..89ca712 --- /dev/null +++ b/.github/workflows/npm-test.yml @@ -0,0 +1,39 @@ +name: npm test +on: + push: + branches: "main" + paths-ignore: + - .gitignore + - LICENSE + - README.md + - .github/** + - "!.github/workflows/npm-test.yml" + pull_request: + branches: "main" + paths-ignore: + - .gitignore + - LICENSE + - README.md + - .github/** + - "!.github/workflows/npm-test.yml" +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true +jobs: + npm-test: + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-latest, node-version: "20" } + - { os: windows-latest, node-version: "18" } + - { os: macos-latest, node-version: "20" } + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + - run: npm ci + - run: npm test \ No newline at end of file diff --git a/.gitignore b/.gitignore index c6bba59..3ba3b45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# Ignore Typst downloads when in dev +typst-*.tar.xz +typst-*.zip +bin/typst + +#region https://github.com/github/gitignore/blob/main/Node.gitignore # Logs logs *.log @@ -128,3 +134,4 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +#endregion \ No newline at end of file diff --git a/LICENSE b/LICENSE index 07471cc..a7e77cb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,176 @@ -MIT License - -Copyright (c) 2023 Jacob Hummer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.md b/README.md index 0059de1..d801725 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,255 @@ -# typst.js -A new markup-based typesetting system that is powerful and easy to learn. +

+ Typst +

+ +

+ + Documentation + + Typst App + + Discord Server + + Apache-2 License + + Jobs at Typst +

+ +Typst is a new markup-based typesetting system that is designed to be as powerful +as LaTeX while being much easier to learn and use. Typst has: + +- Built-in markup for the most common formatting tasks +- Flexible functions for everything else +- A tightly integrated scripting system +- Math typesetting, bibliography management, and more +- Fast compile times thanks to incremental compilation +- Friendly error messages in case something goes wrong + +This repository contains the Typst compiler and its CLI, which is everything you +need to compile Typst documents locally. For the best writing experience, +consider signing up to our [collaborative online editor][app] for free. It is +currently in public beta. + +## Example +A [gentle introduction][tutorial] to Typst is available in our documentation. +However, if you want to see the power of Typst encapsulated in one image, here +it is: +

+ Example +

+ + +Let's dissect what's going on: + +- We use _set rules_ to configure element properties like the size of pages or + the numbering of headings. By setting the page height to `auto`, it scales to + fit the content. Set rules accommodate the most common configurations. If you + need full control, you can also use [show rules][show] to completely redefine + the appearance of an element. + +- We insert a heading with the `= Heading` syntax. One equals sign creates a top + level heading, two create a subheading and so on. Typst has more lightweight + markup like this, see the [syntax] reference for a full list. + +- [Mathematical equations][math] are enclosed in dollar signs. By adding extra + spaces around the contents of a equation, we can put it into a separate block. + Multi-letter identifiers are interpreted as Typst definitions and functions + unless put into quotes. This way, we don't need backslashes for things like + `floor` and `sqrt`. And `phi.alt` applies the `alt` modifier to the `phi` to + select a particular symbol variant. + +- Now, we get to some [scripting]. To input code into a Typst document, we can + write a hash followed by an expression. We define two variables and a + recursive function to compute the n-th fibonacci number. Then, we display the + results in a center-aligned table. The table function takes its cells + row-by-row. Therefore, we first pass the formulas `$F_1$` to `$F_8$` and then + the computed fibonacci numbers. We apply the spreading operator (`..`) to both + because they are arrays and we want to pass the arrays' items as individual + arguments. + +
+ Text version of the code example. + + ```typst + #set page(width: 10cm, height: auto) + #set heading(numbering: "1.") + + = Fibonacci sequence + The Fibonacci sequence is defined through the + recurrence relation $F_n = F_(n-1) + F_(n-2)$. + It can also be expressed in _closed form:_ + + $ F_n = round(1 / sqrt(5) phi.alt^n), quad + phi.alt = (1 + sqrt(5)) / 2 $ + + #let count = 8 + #let nums = range(1, count + 1) + #let fib(n) = ( + if n <= 2 { 1 } + else { fib(n - 1) + fib(n - 2) } + ) + + The first #count numbers of the sequence are: + + #align(center, table( + columns: count, + ..nums.map(n => $F_#n$), + ..nums.map(n => str(fib(n))), + )) + ``` +
+ +## Installation +Typst's CLI is available from different sources: + +- You can get sources and pre-built binaries for the latest release of Typst + from the [releases page][releases]. Download the archive for your platform and + place it in a directory that is in your `PATH`. To stay up to date with future + releases, you can simply run `typst update`. + +- You can install Typst through different package managers. Note that the + versions in the package managers might lag behind the latest release. + - Linux: View [Typst on Repology][repology] + - macOS: `brew install typst` + - Windows: `winget install --id Typst.Typst` + +- If you have a [Rust][rust] toolchain installed, you can also install the + latest development version with + `cargo install --git https://github.com/typst/typst typst-cli`. Note that this + will be a "nightly" version that may be broken or not yet properly documented. + +- Nix users can use the `typst` package with `nix-shell -p typst` or build and + run the bleeding edge version with `nix run github:typst/typst -- --version`. + +- Docker users can run a prebuilt image with + `docker run -it ghcr.io/typst/typst:latest`. + +## Usage +Once you have installed Typst, you can use it like this: +```sh +# Creates `file.pdf` in working directory. +typst compile file.typ + +# Creates PDF file at the desired path. +typst compile path/to/source.typ path/to/output.pdf +``` + +You can also watch source files and automatically recompile on changes. This is +faster than compiling from scratch each time because Typst has incremental +compilation. +```sh +# Watches source files and recompiles on changes. +typst watch file.typ +``` + +Typst further allows you to add custom font paths for your project and list all +of the fonts it discovered: +```sh +# Adds additional directories to search for fonts. +typst compile --font-path path/to/fonts file.typ + +# Lists all of the discovered fonts in the system and the given directory. +typst fonts --font-path path/to/fonts + +# Or via environment variable (Linux syntax). +TYPST_FONT_PATHS=path/to/fonts typst fonts +``` + +For other CLI subcommands and options, see below: +```sh +# Prints available subcommands and options. +typst help + +# Prints detailed usage of a subcommand. +typst help watch +``` + +If you prefer an integrated IDE-like experience with autocompletion and instant +preview, you can also check out the [Typst web app][app], which is currently in +public beta. + +## Community +The main place where the community gathers is our [Discord server][discord]. +Feel free to join there to ask questions, help out others, share cool things +you created with Typst, or just to chat. + +Aside from that there are a few places where you can find things built by +the community: + +- The official [package list](https://typst.app/docs/packages) +- The [Awesome Typst](https://github.com/qjcg/awesome-typst) repository + +If you had a bad experience in our community, please [reach out to us][contact]. + +## Contributing +We would love to see contributions from the community. If you experience bugs, +feel free to open an issue. If you would like to implement a new feature or bug +fix, please follow the steps outlined in the [contribution guide][contributing]. + +To build Typst yourself, first ensure that you have the +[latest stable Rust][rust] installed. Then, clone this repository and build the +CLI with the following commands: + +```sh +git clone https://github.com/typst/typst +cd typst +cargo build --release +``` + +The optimized binary will be stored in `target/release/`. + +Another good way to contribute is by [sharing packages][packages] with the +community. + +## Pronunciation and Spelling +IPA: /taɪpst/. "Ty" like in **Ty**pesetting and "pst" like in Hi**pst**er. When +writing about Typst, capitalize its name as a proper noun, with a capital "T". + +## Design Principles +All of Typst has been designed with three key goals in mind: Power, +simplicity, and performance. We think it's time for a system that matches the +power of LaTeX, is easy to learn and use, all while being fast enough to realize +instant preview. To achieve these goals, we follow three core design principles: + +- **Simplicity through Consistency:** + If you know how to do one thing in Typst, you should be able to transfer that + knowledge to other things. If there are multiple ways to do the same thing, + one of them should be at a different level of abstraction than the other. E.g. + it's okay that `= Introduction` and `#heading[Introduction]` do the same thing + because the former is just syntax sugar for the latter. + +- **Power through Composability:** + There are two ways to make something flexible: Have a knob for everything or + have a few knobs that you can combine in many ways. Typst is designed with the + second way in mind. We provide systems that you can compose in ways we've + never even thought of. TeX is also in the second category, but it's a bit + low-level and therefore people use LaTeX instead. But there, we don't really + have that much composability. Instead, there's a package for everything + (`\usepackage{knob}`). + +- **Performance through Incrementality:** + All Typst language features must accommodate for incremental compilation. + Luckily we have [`comemo`], a system for incremental compilation which does + most of the hard work in the background. + +[docs]: https://typst.app/docs/ +[app]: https://typst.app/ +[discord]: https://discord.gg/2uDybryKPe +[tutorial]: https://typst.app/docs/tutorial/ +[show]: https://typst.app/docs/reference/styling/#show-rules +[math]: https://typst.app/docs/reference/math/ +[syntax]: https://typst.app/docs/reference/syntax/ +[scripting]: https://typst.app/docs/reference/scripting/ +[rust]: https://rustup.rs/ +[releases]: https://github.com/typst/typst/releases/ +[repology]: https://repology.org/project/typst/versions +[contact]: https://typst.app/contact +[architecture]: https://github.com/typst/typst/blob/main/docs/dev/architecture.md +[contributing]: https://github.com/typst/typst/blob/main/CONTRIBUTING.md +[packages]: https://github.com/typst/packages/ +[`comemo`]: https://github.com/typst/comemo/ diff --git a/main.js b/main.js new file mode 100644 index 0000000..8c1f1fc --- /dev/null +++ b/main.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +// @ts-check +import { existsSync } from "node:fs"; +import { $ } from "execa"; +import { typstMetaInstall } from "./utils.js"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { readFile, writeFile } from "node:fs/promises"; + +const package_ = JSON.parse( + await readFile(new URL("./package.json", import.meta.url), "utf8") +); +const tag = `v${package_.version.match(/^\d+\.\d+\.\d+/)[0]}`; + +const ext = process.platform === "win32" ? ".exe" : ""; +const typst = fileURLToPath(new URL(`./bin/typst${ext}`, import.meta.url)); + +if (!existsSync(typst)) { + await typstMetaInstall(undefined, tag); +} + +const { exitCode, signal } = await $({ + stdio: "inherit", + reject: false, +})`${typst} ${process.argv.slice(2)}`; +if (signal) process.kill(process.pid, signal); +process.exit(exitCode ?? 100); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..33f46fe --- /dev/null +++ b/package-lock.json @@ -0,0 +1,208 @@ +{ + "name": "typst", + "version": "0.10.0-3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "typst", + "version": "0.10.0-3", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "execa": "^8.0.1" + }, + "bin": { + "typst": "main.js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d415993 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "typst", + "version": "0.10.0-3", + "description": "A new markup-based typesetting system that is powerful and easy to learn.", + "keywords": [ + "typesetting", + "markup", + "typesetter", + "typeset", + "typst", + "latex", + "markdown", + "pdf" + ], + "homepage": "https://typst.app/", + "bugs": "https://github.com/jcbhmr/typst.js/issues", + "repository": "github:jcbhmr/typst.js", + "license": "Apache-2.0", + "contributors": ["Jacob Hummer (https://jcbhmr.me/)"], + "type": "module", + "exports": null, + "bin": "main.js", + "files": [ + "main.js", + "postinstall.js", + "utils.js" + ], + "scripts": { + "postinstall": "node postinstall.js || true", + "test": "node main.js --help" + }, + "dependencies": { + "execa": "^8.0.1" + } +} \ No newline at end of file diff --git a/postinstall.js b/postinstall.js new file mode 100644 index 0000000..63d2b82 --- /dev/null +++ b/postinstall.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node +// @ts-check +import { existsSync } from "node:fs"; +import { typstMetaInstall } from "./utils.js"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { readFile, writeFile } from "node:fs/promises"; + +const package_ = JSON.parse( + await readFile(new URL("./package.json", import.meta.url), "utf8") +); +const tag = `v${package_.version.match(/^\d+\.\d+\.\d+/)[0]}`; + +const ext = process.platform === "win32" ? ".exe" : ""; +const typst = fileURLToPath(new URL(`./bin/typst${ext}`, import.meta.url)); + +if (!existsSync(typst)) { + await typstMetaInstall(undefined, tag); +} \ No newline at end of file diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..ebc371d --- /dev/null +++ b/utils.js @@ -0,0 +1,105 @@ +// @ts-check +import { fileURLToPath, pathToFileURL } from "node:url"; +import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; +import { Writable } from "node:stream"; +import { createWriteStream } from "node:fs"; +import { $ } from "execa"; + +/** + * + * @param {import('node:fs').PathLike} destRaw + * @param {string} [tag] + * @param {string} [osRaw] + * @param {string} [archRaw] + */ +export async function typstMetaInstall( + destRaw = process.cwd(), + tag = undefined, + osRaw = process.platform, + archRaw = process.arch +) { + const destPath = + destRaw instanceof URL ? fileURLToPath(destRaw) : `${destRaw}`; + const destFileURL = + destRaw instanceof URL ? destRaw : pathToFileURL(`${destRaw}`); + destFileURL.pathname.endsWith("/") || (destFileURL.pathname += "/"); + + if (tag === undefined) { + const response = await fetch( + `https://ungh.cc/repos/typst/typst/releases/latest` + ); + const json = await response.json(); + tag = json.release.tag; + } + + const os = { + Windows: "win32", + Linux: "linux", + macOS: "darwin", + win32: "win32", + linux: "linux", + darwin: "darwin", + }[osRaw]; + const arch = { + X64: "x64", + ARM64: "arm64", + ARM: "arm", + x64: "x64", + arm64: "arm64", + arm: "arm", + }[archRaw]; + + const archive = { + "darwin,arm64": "typst-aarch64-apple-darwin.tar.xz", + "linux,x64": "typst-x86_64-unknown-linux-musl.tar.xz", + "linux,arm": "typst-armv7-unknown-linux-musleabi.tar.xz", + "darwin,x64": "typst-x86_64-apple-darwin.tar.xz", + "win32,x64": "typst-x86_64-pc-windows-msvc.zip", + "linux,arm64": "typst-aarch64-unknown-linux-musl.tar.xz", + }[[os, arch].toString()]; + + const folder = { + "darwin,arm64": "typst-aarch64-apple-darwin", + "linux,x64": "typst-x86_64-unknown-linux-musl", + "linux,arm": "typst-armv7-unknown-linux-musleabi", + "darwin,x64": "typst-x86_64-apple-darwin", + "win32,x64": "typst-x86_64-pc-windows-msvc", + "linux,arm64": "typst-aarch64-unknown-linux-musl", + }[[os, arch].toString()]; + + const response = await fetch( + `https://github.com/typst/typst/releases/download/${tag}/${archive}` + ); + await mkdir(destFileURL, { recursive: true }); + const writable = Writable.toWeb( + createWriteStream(new URL(archive, destFileURL)) + ); + if (!response.body) throw new DOMException("No body", "NotSupportedError"); + await response.body.pipeTo(writable); + + if (archive.endsWith(".zip")) { + await $`powershell Expand-Archive -Path ${fileURLToPath( + new URL(archive, destFileURL) + )} -DestinationPath ${destPath} -Force`; + } else if (archive.endsWith(".tar.xz")) { + await $`tar -xJf ${fileURLToPath( + new URL(archive, destFileURL) + )} -C ${destPath}`; + } else { + throw new DOMException( + `Unsupported archive format: ${archive}`, + "NotSupportedError" + ); + } + + await rm(new URL(archive, destFileURL), { force: true }); + await mkdir(new URL("./bin/", destFileURL), { recursive: true }); + await rename( + new URL(`./${folder}/typst`, destFileURL), + new URL("./bin/typst", destFileURL) + ); + await rm(new URL(`./${folder}`, destFileURL), { + recursive: true, + force: true, + }); +} \ No newline at end of file