diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6665967..0613aa7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,20 +1,26 @@ name: Test on: push: - branches: "main" + branches: [ main ] paths-ignore: + - .gitattributes - .gitignore - - README*.md + - '**.md' - LICENSE - .github/** - - "!.github/workflows/test-action.yml" + - '!.github/workflows/test.yml' pull_request: paths-ignore: + - .gitattributes - .gitignore - - README*.md + - '**.md' - LICENSE - .github/** - - "!.github/workflows/test-action.yml" + - '!.github/workflows/test.yml' + types: + - opened + - synchronize + - reopened workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -26,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: '20' cache: npm - run: npm ci - run: npm run build @@ -49,10 +55,10 @@ jobs: path: ./dist - uses: ./ with: - cache-dependency-path: test/basic-requirements.typ + cache-dependency-path: test/basic/requirements.typ - run: typst --version - - run: typst compile test/basic.typ - test-local-packages: + - run: typst compile test/basic/main.typ + test-zip-packages: runs-on: ${{ matrix.os }} needs: build strategy: @@ -67,7 +73,8 @@ jobs: path: ./dist - uses: ./ with: - local-packages: test/local-packages.json + typst-version: v0.13 + zip-packages: test/zip-packages/requirements.json cache-local-packages: true - run: typst --version - - run: typst compile test/local-packages.typ + - run: typst compile test/zip-packages/main.typ diff --git a/README.md b/README.md index ace61d2..1667409 100644 --- a/README.md +++ b/README.md @@ -70,30 +70,32 @@ jobs: #### ZIP archive packages download -- **`local-packages`:** Used to specify the path to a JSON file containing names and ZIP archive URLs of packages as local packages under the `local` key. -- **`cache-local-packages`:** When `true`, local packages set by `local-packages` will be cached independently of `@preview` packages. +- **`zip-packages`:** Used to specify the path to a JSON file containing names and ZIP archive URLs of packages. +- **`cache-local-packages`:** When `true`, local packages set by `zip-packages` will be cached independently of `@preview` packages. ```yaml # Example workflow YAML file - uses: typst-community/setup-typst@v4 with: - local-packages: packages.json + zip-packages: requirements.json cache-local-packages: true ``` ```js -// Example JSON file (packages.json) +// Example JSON file (requirements.json) { + "preview": { + "algorithmic": "https://github.com/typst-community/typst-algorithmic/archive/refs/tags/v1.0.0.zip" + }, "local": { - "glossarium": "https://github.com/typst-community/glossarium/archive/refs/tags/v0.5.4.zip", - "touying": "https://github.com/touying-typ/touying/archive/refs/tags/0.6.1.zip" + "glossarium": "https://github.com/typst-community/glossarium/archive/refs/tags/v0.5.7.zip" } } ``` > [!NOTE] > - For links to download GitHub repositories, please refer to [_Downloading source code archives_]. -> - The namespace for local packages is `local`. The SemVer versions of local packages are read from its `typst.toml`. +> - The supported namespaces are only `local` and `preview`. The SemVer versions of packages are read from its `typst.toml`. #### Token diff --git a/README_zh-Hans-CN.md b/README_zh-Hans-CN.md index 3bff7a6..078d9e9 100644 --- a/README_zh-Hans-CN.md +++ b/README_zh-Hans-CN.md @@ -7,7 +7,7 @@ 此操作为 GitHub Actions 用户提供以下功能: - **安装**指定版本的 Typst -- **缓存**依赖的[包] +- **缓存**依赖的 [包] - **下载** ZIP 压缩文件作为本地包 ```yaml @@ -70,30 +70,32 @@ jobs: #### ZIP 压缩文件作为包下载 -- **`local-packages`:** 指向一个在 `local` 键下有包名称与对应 ZIP 压缩文件 URL 的 JSON 文件。 -- **`cache-local-packages`:** 当设置为 `true` 时,在 `local-packages` 中设定的包将被缓存(缓存独立于 `@preview` 包)。 +- **`zip-packages`:** 指向一个含包名称及其对应 ZIP 压缩文件 URL 的 JSON 文件。 +- **`cache-local-packages`:** 当设置为 `true` 时,在 `zip-packages` 中设定的 `local` 包将被缓存(缓存独立于 `@preview` 包)。 ```yaml # Example workflow YAML file - uses: typst-community/setup-typst@v4 with: - local-packages: packages.json + zip-packages: requirements.json cache-local-packages: true ``` ```js -// Example JSON file (packages.json) +// Example JSON file (requirements.json) { + "preview": { + "algorithmic": "https://github.com/typst-community/typst-algorithmic/archive/refs/tags/v1.0.0.zip" + }, "local": { - "glossarium": "https://github.com/typst-community/glossarium/archive/refs/tags/v0.5.4.zip", - "touying": "https://github.com/touying-typ/touying/archive/refs/tags/0.6.1.zip" + "glossarium": "https://github.com/typst-community/glossarium/archive/refs/tags/v0.5.7.zip" } } ``` > [!NOTE] -> - 对于下载 GitHub 存储库需要的链接,请参阅 [《下载源代码存档》]。 -> - 本地包的命名空间为 `local`,SemVer 版本号从 `typst.toml` 读取。 +> - 对于下载 GitHub 存储库需要的链接,请参阅[《下载源代码存档》]。 +> - 仅支持将 ZIP 包下载到命名空间 `local` 或 `preview`。SemVer 版本号从 `typst.toml` 读取。 #### 令牌 @@ -108,7 +110,7 @@ jobs: #### 上传到工作流构件 -如果需要从工作流存储和共享数据,可以使用[构件]。 +如果需要从工作流存储和共享数据,可以使用 [构件]。 ```yaml - uses: typst-community/setup-typst@v4 diff --git a/action.yml b/action.yml index d3ff507..caa4a02 100644 --- a/action.yml +++ b/action.yml @@ -15,8 +15,11 @@ inputs: cache-dependency-path: description: "Used to specify the path to a Typst file containing lines of 'import' keyword." required: false + zip-packages: + description: "Used to specify the path to a JSON file containing names and ZIP archive URLs of packages." + required: false local-packages: - description: "Used to specify the path to a JSON file containing names and ZIP archive URLs of packages as local packages under the 'local' key." + description: "[Deprecated] Used to specify the path to a JSON file containing names and ZIP archive URLs of packages." required: false cache-local-packages: description: "When 'true', local packages set by 'local-packages' will be cached independently of '@preview' packages." diff --git a/src/main.ts b/src/main.ts index e1214ac..0b13242 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,23 @@ import path from "path"; import * as os from "os"; import * as semver from "semver"; +function getCompatibleInput(newParam: string, oldParams: string[]): string { + const value = core.getInput(newParam); + if (value) { + return value; + } + for (const oldParam of oldParams) { + const oldValue = core.getInput(oldParam); + if (oldValue) { + core.warning( + `Parameter "${oldParam}" is deprecated, please use "${newParam}" instead.`, + ); + return oldValue; + } + } + return ""; +} + async function move(src: string, dest: string) { try { fs.renameSync(src, dest); @@ -20,7 +37,7 @@ async function move(src: string, dest: string) { fs.rmSync(src, { recursive: true, force: true }); } catch (error) { core.warning( - `Failed to move '${src}' to '${dest}': ${(error as Error).message}.` + `Failed to move '${src}' to '${dest}': ${(error as Error).message}.`, ); } } @@ -28,17 +45,17 @@ async function move(src: string, dest: string) { async function listReleases( octokit: any, - repoSet: { owner: string; repo: string } + repoSet: { owner: string; repo: string }, ) { core.info( - `Fetching releases list for repository ${repoSet.owner}/${repoSet.repo}.` + `Fetching releases list for repository ${repoSet.owner}/${repoSet.repo}.`, ); if (octokit) { return await octokit.paginate(octokit.rest.repos.listReleases, repoSet); } else { const releasesUrl = `https://api.github.com/repos/${repoSet.owner}/${repoSet.repo}/releases`; core.debug( - `Fetching releases list from ${releasesUrl} without authentication.` + `Fetching releases list from ${releasesUrl} without authentication.`, ); const releasesResponse = await tc.downloadTool(releasesUrl); try { @@ -46,17 +63,17 @@ async function listReleases( return JSON.parse(fs.readFileSync(releasesResponse, "utf8")); } catch (error) { core.setFailed( - `Failed to parse releases from ${releasesUrl}: ${(error as Error).message}. This may be caused by API rate limit exceeded.` + `Failed to parse releases from ${releasesUrl}: ${(error as Error).message}. This may be caused by API rate limit exceeded.`, ); process.exit(1); } } } -async function getVersionExact( +async function getExactVersion( releases: any[], version: string, - allowPrereleases: boolean + allowPrereleases: boolean, ) { const versions = releases .map((release) => release.tag_name.slice(1)) @@ -66,7 +83,7 @@ async function getVersionExact( version === "latest" ? "*" : version, { includePrerelease: allowPrereleases, - } + }, ); if (resolvedVersion) { core.info(`Resolved Typst version: ${resolvedVersion}.`); @@ -97,7 +114,7 @@ async function downloadAndCacheTypst(version: string) { } else { target = { darwin: "x86_64-apple-darwin", - linux: "x86_64-unknown-linux-gnu" + linux: "x86_64-unknown-linux-gnu", }[process.platform.toString()]!; archiveExt = ".tar.gz"; } @@ -105,19 +122,23 @@ async function downloadAndCacheTypst(version: string) { const file = `${folder}${archiveExt}`; core.debug(`Determined target: ${target}, archive extension: ${archiveExt}.`); let found = await tc.downloadTool( - `https://github.com/typst/typst/releases/download/v${version}/${file}` + `https://github.com/typst/typst/releases/download/v${version}/${file}`, ); if (process.platform == "win32") { if (!found.endsWith(".zip")) { fs.renameSync( found, - path.join(path.dirname(found), `${path.basename(found)}.zip`) + path.join(path.dirname(found), `${path.basename(found)}.zip`), ); found = path.join(path.dirname(found), `${path.basename(found)}.zip`); } found = await tc.extractZip(found); } else { - found = await tc.extractTar(found, undefined, semver.gte(version, "0.3.0") ? "xJ" : "xz"); + found = await tc.extractTar( + found, + undefined, + semver.gte(version, "0.3.0") ? "xJ" : "xz", + ); core.debug(`Extracted archive for Typst version ${version}.`); } found = path.join(found, folder); @@ -131,7 +152,7 @@ const TYPST_PACKAGES_DIR = { path.join( process.env.XDG_CACHE_HOME || (os.homedir() ? path.join(os.homedir(), ".cache") : undefined)!, - "typst/packages" + "typst/packages", ), darwin: () => path.join(process.env.HOME!, "Library/Caches", "typst/packages"), @@ -141,7 +162,7 @@ const TYPST_PACKAGES_DIR = { async function cachePackages(cachePackage: string) { if (!fs.existsSync(cachePackage)) { core.warning( - `Dependency path '${cachePackage}' not found. Skipping caching.` + `Dependency path '${cachePackage}' not found. Skipping caching.`, ); return; } @@ -174,7 +195,7 @@ function getPackageVersion(toml: string): string { 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'.` + `Failed to read TOML file '${toml}': ${(error as Error).message}. Defaulting to version '0.0.0'.`, ); return "0.0.0"; } @@ -192,21 +213,26 @@ function getPackageVersion(toml: string): string { } } core.warning( - `Failed to find version in local package TOML file ${toml}. Package version will be 0.0.0.` + `Failed to find version in local package TOML file ${toml}. Package version will be 0.0.0.`, ); return "0.0.0"; } -const packagesDir = path.join(TYPST_PACKAGES_DIR, "/local"); +const packagesLocalDir = path.join(TYPST_PACKAGES_DIR, "/local"); +const packagesPreviewDir = path.join(TYPST_PACKAGES_DIR, "/preview"); -async function downloadLocalPackage(name: string, url: string) { +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.` + `Directory '${packageDir}' already exists. Check for duplicate package names.`, ); } core.info(`Downloading package ${name} from ${url}.`); @@ -217,12 +243,12 @@ async function downloadLocalPackage(name: string, url: string) { packageResponse, path.join( path.dirname(packageResponse), - `${path.basename(packageResponse)}.zip` - ) + `${path.basename(packageResponse)}.zip`, + ), ); packageResponse = path.join( path.dirname(packageResponse), - `${path.basename(packageResponse)}.zip` + `${path.basename(packageResponse)}.zip`, ); } } @@ -239,26 +265,26 @@ async function downloadLocalPackage(name: string, url: string) { const stats = fs.statSync(innerPath); if (stats.isDirectory()) { const packageVersion = getPackageVersion( - path.join(innerPath, "typst.toml") + path.join(innerPath, "typst.toml"), ); move(innerPath, path.join(packageDir, packageVersion)); } } else { const packageVersion = getPackageVersion( - path.join(packageResponse, "typst.toml") + path.join(packageResponse, "typst.toml"), ); move(packageResponse, path.join(packageDir, packageVersion)); } core.info(`✅ Downloaded ${name} to '${packageDir}'`); } -async function downloadAndCacheLocalPackages( +async function downloadZipLocalPackages( localPackage: string, - cacheLocalPackages: boolean + cacheLocalPackages: boolean, ) { if (!fs.existsSync(localPackage)) { core.warning( - `Local packages path '${localPackage}' not found. Skipping downloading.` + `Zip packages path '${localPackage}' not found. Skipping downloading.`, ); return; } @@ -266,7 +292,7 @@ async function downloadAndCacheLocalPackages( const hash = await hashFiles(localPackage); const primaryKey = `typst-local-packages-${hash}`; core.info(`Computed cache key: ${primaryKey}.`); - const cacheKey = await cache.restoreCache([packagesDir], primaryKey); + const cacheKey = await cache.restoreCache([packagesLocalDir], primaryKey); if (cacheKey != undefined) { core.info(`✅ Local packages restored from cache.`); return; @@ -278,30 +304,30 @@ async function downloadAndCacheLocalPackages( packages = JSON.parse(fs.readFileSync(localPackage, "utf8")); } catch (error) { core.warning( - `Failed to parse local-packages json file: ${(error as Error).message}. Skipping downloading.` + `Failed to parse local-packages json file: ${(error as Error).message}. Skipping downloading.`, ); return; } - core.info(`Downloading local packages.`); - if (!fs.existsSync(packagesDir)) { - fs.mkdirSync(packagesDir, { recursive: true }); - core.debug(`Created local packages directory: '${packagesDir}'.`); + 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 downloadLocalPackage(key, value); + 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([packagesDir], primaryKey); + let cacheId = await cache.saveCache([packagesLocalDir], primaryKey); core.info(`✅ Cache saved successfully with key: ${primaryKey}.`); core.debug(`Cache ID: ${cacheId}`); } catch (error) { @@ -311,10 +337,47 @@ async function downloadAndCacheLocalPackages( return; } -const token = core.getInput("token"); +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" }) : null; + const repoSet = { owner: "typst", repo: "typst", @@ -322,7 +385,7 @@ const repoSet = { const releases = await listReleases(octokit, repoSet); const version = core.getInput("typst-version"); const allowPrereleases = core.getBooleanInput("allow-prereleases"); -const versionExact = await getVersionExact(releases, version, allowPrereleases); +const versionExact = await getExactVersion(releases, version, allowPrereleases); let found = tc.find("typst", versionExact); core.setOutput("cache-hit", !!found); if (!found) { @@ -331,12 +394,15 @@ if (!found) { core.addPath(found); core.setOutput("typst-version", versionExact); core.info(`✅ Typst v${versionExact} installed!`); + const cachePackage = core.getInput("cache-dependency-path"); if (cachePackage) { await cachePackages(cachePackage); } -const localPackage = core.getInput("local-packages"); + +const localPackages = getCompatibleInput("zip-packages", ["local-packages"]); const cacheLocalPackages = core.getBooleanInput("cache-local-packages"); -if (localPackage) { - await downloadAndCacheLocalPackages(localPackage, cacheLocalPackages); +if (localPackages) { + await downloadZipLocalPackages(localPackages, cacheLocalPackages); + await downloadZipPreviewPackages(localPackages); } diff --git a/test/basic.typ b/test/basic/main.typ similarity index 100% rename from test/basic.typ rename to test/basic/main.typ diff --git a/test/basic-requirements.typ b/test/basic/requirements.typ similarity index 100% rename from test/basic-requirements.typ rename to test/basic/requirements.typ diff --git a/test/local-packages.json b/test/local-packages.json deleted file mode 100644 index 98078ce..0000000 --- a/test/local-packages.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "local": { - "glossarium": "https://github.com/typst-community/glossarium/archive/refs/tags/v0.5.4.zip", - "touying": "https://github.com/touying-typ/touying/archive/refs/tags/0.6.1.zip" - } -} diff --git a/test/local-packages.typ b/test/local-packages.typ deleted file mode 100644 index 0499da1..0000000 --- a/test/local-packages.typ +++ /dev/null @@ -1,4 +0,0 @@ -#import "@local/glossarium:0.5.4": * -#import "@local/touying:0.6.1": * - -#lorem(50) diff --git a/test/zip-packages/main.typ b/test/zip-packages/main.typ new file mode 100644 index 0000000..9d2598a --- /dev/null +++ b/test/zip-packages/main.typ @@ -0,0 +1,4 @@ +#import "@preview/algorithmic:1.0.0": * +#import "@local/glossarium:0.5.7": * + +#lorem(50) diff --git a/test/zip-packages/requirements.json b/test/zip-packages/requirements.json new file mode 100644 index 0000000..2d376ae --- /dev/null +++ b/test/zip-packages/requirements.json @@ -0,0 +1,8 @@ +{ + "preview": { + "algorithmic": "https://github.com/typst-community/typst-algorithmic/archive/refs/tags/v1.0.0.zip" + }, + "local": { + "glossarium": "https://github.com/typst-community/glossarium/archive/refs/tags/v0.5.7.zip" + } +}