summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src/package.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-cli/src/package.rs')
-rw-r--r--crates/typst-cli/src/package.rs81
1 files changed, 64 insertions, 17 deletions
diff --git a/crates/typst-cli/src/package.rs b/crates/typst-cli/src/package.rs
index 8141ad19..7d3f2264 100644
--- a/crates/typst-cli/src/package.rs
+++ b/crates/typst-cli/src/package.rs
@@ -5,12 +5,16 @@ use std::path::{Path, PathBuf};
use codespan_reporting::term::{self, termcolor};
use ecow::eco_format;
use termcolor::WriteColor;
-use typst::diag::{PackageError, PackageResult};
-use typst::syntax::PackageSpec;
+use typst::diag::{bail, PackageError, PackageResult, StrResult};
+use typst::syntax::package::{
+ PackageInfo, PackageSpec, PackageVersion, VersionlessPackageSpec,
+};
-use crate::download::download_with_progress;
+use crate::download::{download, download_with_progress};
use crate::terminal;
+const HOST: &str = "https://packages.typst.org";
+
/// Make a package available in the on-disk cache.
pub fn prepare_package(spec: &PackageSpec) -> PackageResult<PathBuf> {
let subdir =
@@ -25,30 +29,59 @@ pub fn prepare_package(spec: &PackageSpec) -> PackageResult<PathBuf> {
if let Some(cache_dir) = dirs::cache_dir() {
let dir = cache_dir.join(&subdir);
+ if dir.exists() {
+ return Ok(dir);
+ }
// Download from network if it doesn't exist yet.
- if spec.namespace == "preview" && !dir.exists() {
+ if spec.namespace == "preview" {
download_package(spec, &dir)?;
- }
-
- if dir.exists() {
- return Ok(dir);
+ if dir.exists() {
+ return Ok(dir);
+ }
}
}
Err(PackageError::NotFound(spec.clone()))
}
+/// Try to determine the latest version of a package.
+pub fn determine_latest_version(
+ spec: &VersionlessPackageSpec,
+) -> StrResult<PackageVersion> {
+ if spec.namespace == "preview" {
+ // For `@preview`, download the package index and find the latest
+ // version.
+ download_index()?
+ .iter()
+ .filter(|package| package.name == spec.name)
+ .map(|package| package.version)
+ .max()
+ .ok_or_else(|| eco_format!("failed to find package {spec}"))
+ } else {
+ // For other namespaces, search locally. We only search in the data
+ // directory and not the cache directory, because the latter is not
+ // intended for storage of local packages.
+ let subdir = format!("typst/packages/{}/{}", spec.namespace, spec.name);
+ dirs::data_dir()
+ .into_iter()
+ .flat_map(|dir| std::fs::read_dir(dir.join(&subdir)).ok())
+ .flatten()
+ .filter_map(|entry| entry.ok())
+ .map(|entry| entry.path())
+ .filter_map(|path| path.file_name()?.to_string_lossy().parse().ok())
+ .max()
+ .ok_or_else(|| eco_format!("please specify the desired version"))
+ }
+}
+
/// Download a package over the network.
fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()> {
// The `@preview` namespace is the only namespace that supports on-demand
// fetching.
assert_eq!(spec.namespace, "preview");
- let url = format!(
- "https://packages.typst.org/preview/{}-{}.tar.gz",
- spec.name, spec.version
- );
+ let url = format!("{HOST}/preview/{}-{}.tar.gz", spec.name, spec.version);
print_downloading(spec).unwrap();
@@ -67,14 +100,28 @@ fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()>
})
}
+/// Download the `@preview` package index.
+fn download_index() -> StrResult<Vec<PackageInfo>> {
+ let url = format!("{HOST}/preview/index.json");
+ match download(&url) {
+ Ok(response) => response
+ .into_json()
+ .map_err(|err| eco_format!("failed to parse package index: {err}")),
+ Err(ureq::Error::Status(404, _)) => {
+ bail!("failed to fetch package index (not found)")
+ }
+ Err(err) => bail!("failed to fetch package index ({err})"),
+ }
+}
+
/// Print that a package downloading is happening.
fn print_downloading(spec: &PackageSpec) -> io::Result<()> {
let styles = term::Styles::default();
- let mut term_out = terminal::out();
- term_out.set_color(&styles.header_help)?;
- write!(term_out, "downloading")?;
+ let mut out = terminal::out();
+ out.set_color(&styles.header_help)?;
+ write!(out, "downloading")?;
- term_out.reset()?;
- writeln!(term_out, " {spec}")
+ out.reset()?;
+ writeln!(out, " {spec}")
}