Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 9 additions & 256 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#!/usr/bin/env node
import * as cache from "@actions/cache";
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import * as github from "@actions/github";
import { hashFiles } from "@actions/glob";
import * as tc from "@actions/tool-cache";
import fs from "fs";
import path from "path";
import * as os from "os";
import * as semver from "semver";

import { move } from "./move";
import {
cachePackages,
downloadZipLocalPackages,
downloadZipPreviewPackages,
} from "./package";

function getCompatibleInput(newParam: string, oldParams: string[]): string {
const value = core.getInput(newParam);
if (value) {
Expand All @@ -27,22 +30,6 @@ function getCompatibleInput(newParam: string, oldParams: string[]): string {
return "";
}

function move(src: string, dest: string) {
try {
fs.renameSync(src, dest);
} catch (error) {
try {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.cpSync(src, dest, { recursive: true, force: true });
fs.rmSync(src, { recursive: true, force: true });
} catch (error) {
core.warning(
`Failed to move '${src}' to '${dest}': ${(error as Error).message}.`,
);
}
}
}

async function listReleases(
octokit: any,
repoSet: { owner: string; repo: string },
Expand Down Expand Up @@ -128,7 +115,7 @@ async function downloadAndCacheTypst(version: string, executableName: string) {
);
if (process.platform == "win32") {
if (!found.endsWith(".zip")) {
fs.renameSync(
move(
found,
path.join(path.dirname(found), `${path.basename(found)}.zip`),
);
Expand All @@ -150,246 +137,12 @@ async function downloadAndCacheTypst(version: string, executableName: string) {
const destName =
process.platform === "win32" ? `${executableName}.exe` : executableName;
fs.copyFileSync(path.join(found, sourceName), path.join(found, standardName));
if (sourceName != destName) {
fs.renameSync(path.join(found, sourceName), path.join(found, destName));
}
move(path.join(found, sourceName), path.join(found, destName));
found = await tc.cacheDir(found, "typst", version);
core.info(`Typst ${version} added to cache at '${found}'.`);
return found;
}

const TYPST_PACKAGES_DIR = {
linux: () =>
path.join(
process.env.XDG_CACHE_HOME ||
(os.homedir() ? path.join(os.homedir(), ".cache") : undefined)!,
"typst/packages",
),
darwin: () =>
path.join(process.env.HOME!, "Library/Caches", "typst/packages"),
win32: () => path.join(process.env.LOCALAPPDATA!, "typst/packages"),
}[process.platform as string]!();

async function cachePackages(cachePackage: string) {
if (!fs.existsSync(cachePackage)) {
core.warning(
`Dependency path '${cachePackage}' not found. Skipping caching.`,
);
return;
}
const cacheDir = TYPST_PACKAGES_DIR + "/preview";
const hash = await hashFiles(cachePackage);
const primaryKey = `typst-preview-packages-${hash}`;
core.info(`Computed cache key: ${primaryKey}.`);
const cacheKey = await cache.restoreCache([cacheDir], primaryKey);
if (cacheKey != undefined) {
core.info(`✅ Packages restored from cache.`);
} else {
core.debug(`Cache miss. Compiling Typst packages.`);
await exec.exec(`typst compile ${cachePackage}`);
try {
let cacheId = await cache.saveCache([cacheDir], primaryKey);
core.info(`✅ Cache saved successfully with key: ${primaryKey}.`);
core.debug(`Cache ID: ${cacheId}`);
} catch (error) {
core.warning(`Failed to save cache: ${(error as Error).message}.`);
}
return;
}
}

function getPackageVersion(toml: string): string {
core.debug(`Reading package version from TOML file: '${toml}'.`);
let content;
try {
content = fs.readFileSync(toml, "utf-8");
core.info(`Successfully read TOML file: '${toml}'.`);
} catch (error) {
core.warning(
`Failed to read TOML file '${toml}': ${
(error as Error).message
}. Defaulting to version '0.0.0'.`,
);
return "0.0.0";
}
const lines = content.split(/\r?\n/);
let inPackageSection = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
const section = trimmed.slice(1, -1).trim();
inPackageSection = section === "package";
} else if (inPackageSection) {
const versionRegex = /^\s*version\s*=\s*"(\d+\.\d+\.\d+)"/;
const match = trimmed.match(versionRegex);
if (match) return match[1];
}
}
core.warning(
`Failed to find version in local package TOML file ${toml}. Package version will be 0.0.0.`,
);
return "0.0.0";
}

const packagesLocalDir = path.join(TYPST_PACKAGES_DIR, "/local");
const packagesPreviewDir = path.join(TYPST_PACKAGES_DIR, "/preview");

async function downloadZipPackage(
packagesDir: string,
name: string,
url: string,
) {
const packageDir = path.join(packagesDir, name);
if (!fs.existsSync(packageDir)) {
fs.mkdirSync(packageDir);
core.debug(`Created directory '${packageDir}' for package ${name}.`);
} else {
core.warning(
`Directory '${packageDir}' already exists. Check for duplicate package names.`,
);
}
core.info(`Downloading package ${name} from ${url}.`);
let packageResponse = await tc.downloadTool(url);
if (process.platform == "win32") {
if (!packageResponse.endsWith(".zip")) {
fs.renameSync(
packageResponse,
path.join(
path.dirname(packageResponse),
`${path.basename(packageResponse)}.zip`,
),
);
packageResponse = path.join(
path.dirname(packageResponse),
`${path.basename(packageResponse)}.zip`,
);
}
}
packageResponse = await tc.extractZip(packageResponse);
core.debug(`Extracted package ${name}.`);
const dirContent = await new Promise<string[]>((resolve, reject) => {
fs.readdir(packageResponse, (err, files) => {
if (err) reject(err);
else resolve(files);
});
});
if (dirContent.length === 1) {
const innerPath = path.join(packageResponse, dirContent[0]);
const stats = fs.statSync(innerPath);
if (stats.isDirectory()) {
const packageVersion = getPackageVersion(
path.join(innerPath, "typst.toml"),
);
move(innerPath, path.join(packageDir, packageVersion));
}
} else {
const packageVersion = getPackageVersion(
path.join(packageResponse, "typst.toml"),
);
move(packageResponse, path.join(packageDir, packageVersion));
}
core.info(`✅ Downloaded ${name} to '${packageDir}'`);
}

async function downloadZipLocalPackages(
localPackage: string,
cacheLocalPackages: boolean,
) {
if (!fs.existsSync(localPackage)) {
core.warning(
`Zip packages path '${localPackage}' not found. Skipping downloading.`,
);
return;
}
if (cacheLocalPackages) {
const hash = await hashFiles(localPackage);
const primaryKey = `typst-local-packages-${hash}`;
core.info(`Computed cache key: ${primaryKey}.`);
const cacheKey = await cache.restoreCache([packagesLocalDir], primaryKey);
if (cacheKey != undefined) {
core.info(`✅ Local packages restored from cache.`);
return;
}
core.debug(`Cache miss. Downloading local packages.`);
}
let packages;
try {
packages = JSON.parse(fs.readFileSync(localPackage, "utf8"));
} catch (error) {
core.warning(
`Failed to parse local-packages json file: ${
(error as Error).message
}. Skipping downloading.`,
);
return;
}
core.info(`Downloading Zip @local packages.`);
if (!fs.existsSync(packagesLocalDir)) {
fs.mkdirSync(packagesLocalDir, { recursive: true });
core.debug(`Created Zip @local packages directory: '${packagesLocalDir}'.`);
}
await Promise.all(
Object.entries(packages.local).map(([key, value]) => {
if (typeof value === "string") {
return downloadZipPackage(packagesLocalDir, key, value);
} else {
core.warning(`Invalid package URL for ${key}: Expected a string.`);
return Promise.resolve();
}
}),
);
if (cacheLocalPackages) {
try {
const hash = await hashFiles(localPackage);
const primaryKey = `typst-local-packages-${hash}`;
let cacheId = await cache.saveCache([packagesLocalDir], primaryKey);
core.info(`✅ Cache saved successfully with key: ${primaryKey}.`);
core.debug(`Cache ID: ${cacheId}`);
} catch (error) {
core.warning(`Failed to save cache: ${(error as Error).message}.`);
}
}
return;
}

async function downloadZipPreviewPackages(previewPackages: string) {
if (!fs.existsSync(previewPackages)) {
core.warning(
`Zip packages path '${previewPackages}' not found. Skipping downloading.`,
);
return;
}
let packages;
try {
packages = JSON.parse(fs.readFileSync(previewPackages, "utf8"));
} catch (error) {
core.warning(
`Failed to parse local-packages json file: ${
(error as Error).message
}. Skipping downloading.`,
);
return;
}
core.info(`Downloading Zip @preview packages.`);
if (!fs.existsSync(packagesPreviewDir)) {
fs.mkdirSync(packagesPreviewDir, { recursive: true });
core.debug(
`Created Zip @preview packages directory: '${packagesPreviewDir}'.`,
);
}
await Promise.all(
Object.entries(packages.preview).map(([key, value]) => {
if (typeof value === "string") {
return downloadZipPackage(packagesPreviewDir, key, value);
} else {
core.warning(`Invalid package URL for ${key}: Expected a string.`);
return Promise.resolve();
}
}),
);
return;
}

const token = core.getInput("token");
const octokit = token
? github.getOctokit(token, { baseUrl: "https://api.github.com" })
Expand Down
22 changes: 22 additions & 0 deletions src/move.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node
import { setFailed } from "@actions/core";
import fs from "fs";
import path from "path";

export function move(src: string, dest: string) {
if (src != dest) {
try {
fs.renameSync(src, dest);
} catch (error) {
try {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.cpSync(src, dest, { recursive: true, force: true });
fs.rmSync(src, { recursive: true, force: true });
} catch (error) {
setFailed(
`Failed to move '${src}' to '${dest}': ${(error as Error).message}.`,
);
}
}
}
}
Loading