diff --git a/README.md b/README.md
index 8505732..5bfe2b3 100644
--- a/README.md
+++ b/README.md
@@ -288,6 +288,7 @@ All inputs are optional.
| `package-file` | String | [Glob patterns] for specifying files containing the names of TeX packages to be installed. The file format should be the same as the syntax for the `packages` input. The [`DEPENDS.txt`] format is also supported. |
| `packages` | String | Specify the names of TeX packages to install, separated by whitespaces. Schemes and collections are also acceptable. Everything after `#` will be treated as a comment. |
| `prefix` | String |
TeX Live installation prefix. This has the same effect as [`TEXLIVE_INSTALL_PREFIX`][install-tl-env].
**Default:** [$RUNNER_TEMP]/setup-texlive-action
|
+| `repository` | URL | Specify the package repository to be used as the main repository. Currently only http/https repositories are supported. |
| `texdir` | String | TeX Live system installation directory. This has the same effect as the installer's [`-texdir`] option and takes precedence over the `prefix` input and related environment variables. |
| `tlcontrib` | Bool | Set up [TLContrib] as an additional TeX package repository. This input will be ignored for older versions.
**Default:** `false` |
| `update-all-packages` | Bool | Update all TeX packages when cache restored. Defaults to `false`, and the action will update only `tlmgr`.
**Default:** `false` |
diff --git a/action.yml b/action.yml
index 7b55fb5..80a5dad 100644
--- a/action.yml
+++ b/action.yml
@@ -5,7 +5,7 @@ inputs:
cache:
description: >-
Enable caching for `TEXDIR`.
- default: true
+ default: 'true'
required: false
package-file:
description: >-
@@ -28,6 +28,11 @@ inputs:
This has the same effect as `TEXLIVE_INSTALL_PREFIX`.
Defaults to `$RUNNER_TEMP/setup-texlive-action`.
required: false
+ repository:
+ description: >-
+ Specify the package repository to be used as the main repository.
+ Currently only http/https repositories are supported.
+ required: false
texdir:
description: >-
TeX Live system installation directory.
@@ -39,13 +44,13 @@ inputs:
description: >-
Set up TLContrib as an additional TeX package repository.
This input will be ignored for older versions.
- default: false
+ default: 'false'
required: false
update-all-packages:
description: >-
Update all TeX packages when cache restored.
The default is `false` and the action will update only `tlmgr`.
- default: false
+ default: 'false'
required: false
version:
description: >-
diff --git a/package-lock.json b/package-lock.json
index 6072e2c..ebf012b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,6 @@
"": {
"name": "setup-texlive-action",
"version": "3.0.2",
- "hasInstallScript": true,
"license": "MIT",
"workspaces": [
"packages/*"
@@ -8932,6 +8931,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/texlive-json-schemas": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/texlive-json-schemas/-/texlive-json-schemas-0.1.0.tgz",
+ "integrity": "sha512-L0uHRI13nqLFisBCi285xDj8We3XhL2+vKCY/dnqCvPPofjPlYj+3p4YXL33ODZBAvnebj/rycH2a1sHCB6UGA==",
+ "dev": true
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -10431,6 +10436,7 @@
"@types/mock-fs": "^4.13.4",
"mock-fs": "^5.2.0",
"nock": "^13.5.1",
+ "texlive-json-schemas": "^0.1.0",
"ts-dedent": "^2.2.0",
"ts-essentials": "^9.4.1",
"vitest": "^1.2.2"
diff --git a/packages/e2e/Taskfile.yml b/packages/e2e/Taskfile.yml
index 02693a1..3cf9de0 100644
--- a/packages/e2e/Taskfile.yml
+++ b/packages/e2e/Taskfile.yml
@@ -9,7 +9,7 @@ tasks:
dir: '{{ .npm_config_local_prefix }}'
cmd: >-
act
- --container-architecture linux/{{ ARCH }}
+ --container-architecture linux/{{ default ARCH .architecture }}
--workflows {{ .workflows }}
{{ .CLI_ARGS }}
clear-cache:
@@ -24,6 +24,7 @@ tasks:
historic: &run-global-workflow
<<: *act
vars:
+ architecture: amd64
workflows: .github/workflows/e2e-{{ .TASK }}.yml
proxy: *run-global-workflow
test:
@@ -38,3 +39,4 @@ tasks:
basedir: '{{ .TASKFILE_DIR | relPath .npm_config_local_prefix }}'
workflows: '{{ .basedir }}/workflows/{{ .TASK }}.yml'
move-to-historic: *run-local-workflow
+ tlpretest: *run-local-workflow
diff --git a/packages/e2e/workflows/tlpretest.yml b/packages/e2e/workflows/tlpretest.yml
new file mode 100644
index 0000000..dcfe3a8
--- /dev/null
+++ b/packages/e2e/workflows/tlpretest.yml
@@ -0,0 +1,12 @@
+on: workflow_dispatch
+jobs:
+ tlpretest:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup TeX Live
+ uses: ./
+ with:
+ repository: https://ftp.math.utah.edu/pub/tlpretest/
+ version: 2024
+ - run: tlmgr version
diff --git a/packages/main/package.json b/packages/main/package.json
index 3ec9781..3135de8 100644
--- a/packages/main/package.json
+++ b/packages/main/package.json
@@ -34,6 +34,7 @@
"@types/mock-fs": "^4.13.4",
"mock-fs": "^5.2.0",
"nock": "^13.5.1",
+ "texlive-json-schemas": "^0.1.0",
"ts-dedent": "^2.2.0",
"ts-essentials": "^9.4.1",
"vitest": "^1.2.2"
diff --git a/packages/main/src/action/config.ts b/packages/main/src/action/config.ts
index 03a181e..082d64f 100644
--- a/packages/main/src/action/config.ts
+++ b/packages/main/src/action/config.ts
@@ -1,8 +1,9 @@
import { readFile } from 'node:fs/promises';
import { platform } from 'node:os';
+import * as posixPath from 'node:path/posix';
import { create as createGlobber } from '@actions/glob';
-import type { DeepUndefinable } from 'ts-essentials';
+import type { DeepUndefinable, Writable } from 'ts-essentials';
import * as env from '#/action/env';
import { Inputs } from '#/action/inputs';
@@ -10,9 +11,10 @@ import * as log from '#/log';
import { ReleaseData, Version, dependsTxt } from '#/texlive';
export interface Config
- extends Omit
+ extends Omit
{
readonly packages: ReadonlySet;
+ readonly repository?: Readonly;
readonly version: Version;
}
@@ -21,22 +23,50 @@ export namespace Config {
env.init();
const releases = await ReleaseData.setup();
- const { packageFile, packages, version, ...inputs } = Inputs.load();
+ const {
+ packageFile,
+ packages,
+ repository,
+ version,
+ ...inputs
+ } = Inputs.load();
- const config = {
+ const config: Writable = {
...inputs,
version: await resolveVersion({ version }),
packages: await collectPackages({ packageFile, packages }),
};
- if (!releases.isLatest(config.version)) {
+ if (repository !== undefined) {
+ if (version < '2012') {
+ const error = new RangeError(
+ 'Currently `repository` input is only supported with version 2012 or later',
+ );
+ error['version'] = version;
+ throw error;
+ }
+ const url = new URL(repository);
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
+ const error = new TypeError(
+ 'Currently only http/https repositories are supported',
+ );
+ error['repository'] = repository;
+ throw error;
+ }
+ if (!url.pathname.endsWith('/')) {
+ url.pathname = posixPath.join(url.pathname, '/');
+ }
+ config.repository = url;
+ }
+
+ if (config.version < releases.latest.version) {
if (config.tlcontrib) {
- log.warn(`TLContrib cannot be used with an older version of TeX Live`);
+ log.warn('TLContrib cannot be used with an older version of TeX Live');
config.tlcontrib = false;
}
if (
!(
- releases.isOnePrevious(config.version)
+ config.version < releases.previous.version
&& releases.newVersionReleased()
) && config.updateAllPackages
) {
@@ -77,7 +107,7 @@ async function collectPackages(
async function resolveVersion(
inputs: Pick,
): Promise {
- const { latest } = ReleaseData.use();
+ const { latest, next } = ReleaseData.use();
const version = inputs.version === 'latest'
? latest.version
: Version.parse(inputs.version);
@@ -89,7 +119,7 @@ async function resolveVersion(
'Versions prior to 2013 does not work on 64-bit macOS',
);
}
- if (version > latest.version) {
+ if (version > next.version) {
throw new RangeError(`${version} is not a valid version`);
}
return version;
diff --git a/packages/main/src/action/inputs.ts b/packages/main/src/action/inputs.ts
index 819cf6d..bc84ea8 100644
--- a/packages/main/src/action/inputs.ts
+++ b/packages/main/src/action/inputs.ts
@@ -26,6 +26,9 @@ export class Inputs {
@AsPath
readonly prefix!: string;
+ @Input
+ readonly repository: string | undefined;
+
@Input
@AsPath
readonly texdir: string | undefined;
diff --git a/packages/main/src/action/run/main/index.ts b/packages/main/src/action/run/main/index.ts
index d7939cf..1bd6626 100644
--- a/packages/main/src/action/run/main/index.ts
+++ b/packages/main/src/action/run/main/index.ts
@@ -9,7 +9,7 @@ import { Profile, ReleaseData, Tlmgr, tlnet } from '#/texlive';
export async function main(): Promise {
const config = await Config.load();
- const releases = ReleaseData.use();
+ const { latest, previous, newVersionReleased } = ReleaseData.use();
await using profile = new Profile(config.version, config);
using cache = CacheService.setup({
@@ -31,7 +31,7 @@ export async function main(): Promise {
log.info(profile.toString());
});
await log.group('Installing TeX Live', async () => {
- await install(profile);
+ await install({ profile, repository: config.repository });
});
}
@@ -40,18 +40,15 @@ export async function main(): Promise {
if (cache.restored) {
if (
- releases.isLatest(profile.version)
- || (
- releases.isOnePrevious(profile.version)
- && releases.newVersionReleased()
- )
+ profile.version >= latest.version
+ || (profile.version === previous.version && newVersionReleased())
) {
await log.group(
- releases.isLatest(profile.version)
+ profile.version >= latest.version
? 'Updating tlmgr'
: 'Checking the package repository status',
async () => {
- await updateTlmgr(profile.version);
+ await updateTlmgr(config);
},
);
if (config.updateAllPackages) {
@@ -79,8 +76,8 @@ export async function main(): Promise {
await log.group('TeX Live version info', async () => {
await tlmgr.version();
log.info('Package version:');
- for await (const { name, version, revision } of tlmgr.list()) {
- log.info(' %s: %s', name, version ?? `rev${revision}`);
+ for await (const { name, revision, cataloguedata } of tlmgr.list()) {
+ log.info(' %s: %s', name, cataloguedata?.version ?? `rev${revision}`);
}
});
diff --git a/packages/main/src/action/run/main/install.ts b/packages/main/src/action/run/main/install.ts
index c10341e..afa2efc 100644
--- a/packages/main/src/action/run/main/install.ts
+++ b/packages/main/src/action/run/main/install.ts
@@ -1,41 +1,79 @@
+import { P, match } from 'ts-pattern';
+
import * as log from '#/log';
import {
+ type InstallTL,
InstallTLError,
- Profile,
+ type Profile,
ReleaseData,
TlpdbError,
- installTL,
+ acquire,
tlnet,
} from '#/texlive';
-export async function install(profile: Profile): Promise {
- const { isLatest, isOnePrevious } = ReleaseData.use();
- for (const master of [false, true]) {
- const repository = isLatest(profile.version)
- ? await tlnet.ctan({ master })
- : tlnet.historic(profile.version, { master });
- log.info('Main repository: %s', repository);
+export async function install(options: {
+ readonly profile: Profile;
+ readonly repository?: Readonly | undefined;
+}): Promise {
+ const { latest, previous } = ReleaseData.use();
+ const { version } = options.profile;
+
+ let repository = options?.repository;
+ const fallbackToMaster = repository === undefined
+ && version > previous.version;
+
+ let installTL: InstallTL;
+
+ for (const master of fallbackToMaster ? [false, true] : [false]) {
+ if (repository === undefined || master) {
+ repository = version >= latest.version
+ ? await tlnet.ctan({ master })
+ : tlnet.historic(version, { master });
+ }
+ try {
+ installTL ??= await acquire({ repository, version });
+ } catch (error) {
+ if (
+ !master
+ && fallbackToMaster
+ && error instanceof InstallTLError
+ && match(error.code)
+ .with(InstallTLError.Code.FAILED_TO_DOWNLOAD, () => true)
+ .with(InstallTLError.Code.UNEXPECTED_VERSION, () => true)
+ .otherwise(() => false)
+ ) {
+ log.info({ error });
+ continue;
+ }
+ throw error;
+ }
+ log.info('Using repository: %s', repository);
try {
- await installTL({ profile, repository });
+ await installTL.run({
+ profile: options.profile,
+ repository: options.repository ?? repository,
+ });
return;
} catch (error) {
- const recoverable: (InstallTLError.Code | TlpdbError.Code)[] = [
- InstallTLError.Code.FAILED_TO_DOWNLOAD,
- InstallTLError.Code.INCOMPATIBLE_REPOSITORY_VERSION,
- InstallTLError.Code.UNEXPECTED_VERSION,
- TlpdbError.Code.FAILED_TO_INITIALIZE,
- ];
if (
!master
- && (isLatest(profile.version) || isOnePrevious(profile.version))
- && (error instanceof TlpdbError
- || error instanceof InstallTLError)
- && recoverable.includes(error.code!)
+ && fallbackToMaster
+ && match(error)
+ .with(
+ P.instanceOf(TlpdbError),
+ ({ code }) => code === TlpdbError.Code.FAILED_TO_INITIALIZE,
+ )
+ .with(
+ P.instanceOf(InstallTLError),
+ ({ code }) =>
+ code === InstallTLError.Code.INCOMPATIBLE_REPOSITORY_VERSION,
+ )
+ .otherwise(() => false)
) {
log.info({ error });
- } else {
- throw error;
+ continue;
}
+ throw error;
}
}
}
diff --git a/packages/main/src/action/run/main/update.ts b/packages/main/src/action/run/main/update.ts
index 79cea06..2189411 100644
--- a/packages/main/src/action/run/main/update.ts
+++ b/packages/main/src/action/run/main/update.ts
@@ -13,68 +13,90 @@ import {
tlnet,
} from '#/texlive';
-export async function updateTlmgr(version: Version): Promise {
- const tlmgr = Tlmgr.use();
- const { isOnePrevious } = ReleaseData.use();
+export interface UpdateOptions {
+ readonly version: Version;
+ readonly repository?: Readonly | undefined;
+}
+
+export async function updateTlmgr(options: UpdateOptions): Promise {
try {
- await tlmgr.update({ self: true });
- return;
+ await updateRepositories(options);
} catch (error) {
- const tlcontrib = 'tlcontrib';
if (
- isOnePrevious(version)
- && error instanceof TlmgrError
- && (
- error.code === TlmgrError.Code.TL_VERSION_OUTDATED
- || (
- error.code === TlmgrError.Code.TL_VERSION_NOT_SUPPORTED
- && 'repository' in error
- && error.repository.includes(tlcontrib)
- )
- )
+ error instanceof TlmgrError
+ && error.code === TlmgrError.Code.TL_VERSION_OUTDATED
+ && options.repository === undefined
) {
log.info({ error });
- try {
- log.info('Removing `%s`', tlcontrib);
- await tlmgr.repository.remove(tlcontrib);
- await tlmgr.update({ self: true });
- } catch (error) { // eslint-disable-line @typescript-eslint/no-shadow
- log.info(`${error}`);
- log.debug({ error });
+ await moveToHistoric(options.version);
+ } else {
+ throw error;
+ }
+ }
+}
+
+async function updateRepositories(options: UpdateOptions): Promise {
+ const tlmgr = Tlmgr.use();
+ const { latest, previous } = ReleaseData.use();
+ const version = options.version;
+ let repository = options.repository;
+ if (version >= previous.version) {
+ for await (const { path, tag } of tlmgr.repository.list()) {
+ if (
+ tag === 'main'
+ && path.includes('tlpretest')
+ && repository === undefined
+ && version === latest.version
+ ) {
+ repository = await tlnet.ctan();
+ } else if (
+ (tag === 'tlcontrib' || path.includes('tlcontrib'))
+ && version < latest.version
+ ) {
+ log.info(`Removing %s`, tag ?? path);
+ await tlmgr.repository.remove(tag ?? path);
}
}
}
+ if (repository !== undefined) {
+ await changeRepository('main', repository);
+ } else {
+ await tlmgr.update({ self: true });
+ }
+}
+
+async function moveToHistoric(version: Version): Promise {
+ const cache = CacheService.use();
+ const tag = 'main';
try {
- await setupHistoric(version);
+ await changeRepository(tag, tlnet.historic(version));
} catch (error) {
if (
error instanceof TlpdbError
&& error.code === TlpdbError.Code.FAILED_TO_INITIALIZE
) {
log.info({ error });
- await setupHistoric(version, { master: true });
+ await changeRepository(tag, tlnet.historic(version, { master: true }));
} else {
throw error;
}
}
- CacheService.use().update();
+ cache.update();
}
-export async function setupHistoric(
- version: Version,
- options?: { readonly master?: boolean },
+async function changeRepository(
+ tag: string,
+ url: Readonly,
): Promise {
const tlmgr = Tlmgr.use();
- const tag = 'main';
- const historic = tlnet.historic(version, options);
- log.info('Changing the %s repository to %s', tag, historic.href);
- if (historic.protocol === 'ftp:' && getProxyUrl(historic.href) !== '') {
+ log.info('Changing the repository `%s` to %s', tag, url.href);
+ if (url.protocol === 'ftp:' && getProxyUrl(url.href) !== '') {
throw new Error(
'The use of ftp repositories under proxy is currently not supported',
);
}
await tlmgr.repository.remove(tag);
- await tlmgr.repository.add(historic, tag);
+ await tlmgr.repository.add(url, tag);
await tlmgr.update({ self: true });
}
diff --git a/packages/main/src/texlive/install-tl/cli.ts b/packages/main/src/texlive/install-tl/cli.ts
index cde4b51..b82fc2f 100644
--- a/packages/main/src/texlive/install-tl/cli.ts
+++ b/packages/main/src/texlive/install-tl/cli.ts
@@ -17,38 +17,41 @@ export interface InstallTLOptions {
readonly repository: Readonly;
}
-export async function installTL(options: InstallTLOptions): Promise {
- const { isLatest } = ReleaseData.use();
- const { profile, repository } = options;
- const version = profile.version;
-
- const installTLPath = await acquire(options);
- await exec(installTLPath, ['-version'], { stdin: null });
-
- const result = await exec(
- installTLPath,
- await Array.fromAsync(commandArgs(options)),
- { stdin: null, ignoreReturnCode: true },
- );
+export class InstallTL {
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ constructor(readonly path: string, readonly version: Version) {}
- const errorOptions = { version, repository };
- if (isLatest(version)) {
- InstallTLError.checkCompatibility(result, errorOptions);
- } else {
- TlpdbError.checkRepositoryStatus(result, errorOptions);
- TlpdbError.checkRepositoryHealth(result, errorOptions);
- }
- TlpdbError.checkPackageChecksumMismatch(result, errorOptions);
- try {
- result.check();
- } catch (cause) {
- throw new InstallTLError('Failed to install TeX Live', {
- ...errorOptions,
- cause,
- });
- }
+ async run(options: InstallTLOptions): Promise {
+ const { latest } = ReleaseData.use();
+ const { profile, repository } = options;
+
+ await exec(this.path, ['-version'], { stdin: null });
+
+ const result = await exec(
+ this.path,
+ await Array.fromAsync(commandArgs(options)),
+ { stdin: null, ignoreReturnCode: true },
+ );
+
+ const errorOptions = { version: this.version, repository };
+ if (this.version >= latest.version) {
+ InstallTLError.checkCompatibility(result, errorOptions);
+ } else {
+ TlpdbError.checkRepositoryStatus(result, errorOptions);
+ TlpdbError.checkRepositoryHealth(result, errorOptions);
+ }
+ TlpdbError.checkPackageChecksumMismatch(result, errorOptions);
+ try {
+ result.check();
+ } catch (cause) {
+ throw new InstallTLError('Failed to install TeX Live', {
+ ...errorOptions,
+ cause,
+ });
+ }
- await patch(profile);
+ await patch(profile);
+ }
}
const supportVersions = {
@@ -95,19 +98,24 @@ async function* commandArgs(
];
}
-async function acquire(options: InstallTLOptions): Promise {
- return path.format({
+export interface DownloadOptions {
+ readonly version: Version;
+ readonly repository: Readonly;
+}
+
+export async function acquire(options: DownloadOptions): Promise {
+ const installerPath = path.format({
dir: restoreCache(options) ?? await download(options),
- base: executableName(options.profile.version),
+ base: executableName(options.version),
});
+ return new InstallTL(installerPath, options.version);
}
/** @internal */
-export function restoreCache(options: InstallTLOptions): string | undefined {
- const { profile: { version } } = options;
- const executable = executableName(version);
+export function restoreCache(options: DownloadOptions): string | undefined {
+ const executable = executableName(options.version);
try {
- const TEXMFROOT = findTool(executable, version);
+ const TEXMFROOT = findTool(executable, options.version);
if (TEXMFROOT !== '') {
log.info('Found in tool cache: %s', TEXMFROOT);
return TEXMFROOT;
@@ -119,9 +127,9 @@ export function restoreCache(options: InstallTLOptions): string | undefined {
}
/** @internal */
-export async function download(options: InstallTLOptions): Promise {
- const { isLatest } = ReleaseData.use();
- const { profile: { version }, repository } = options;
+export async function download(options: DownloadOptions): Promise {
+ const { latest } = ReleaseData.use();
+ const { version, repository } = options;
const errorOpts = {
repository,
version,
@@ -156,7 +164,7 @@ export async function download(options: InstallTLOptions): Promise {
archivePath,
platform() === 'win32' ? 'zip' : 'tgz',
);
- if (isLatest(version)) {
+ if (version >= latest.version) {
try {
await InstallTLError.checkVersion(texmfroot, { version, repository });
} catch (error) {
diff --git a/packages/main/src/texlive/install-tl/index.ts b/packages/main/src/texlive/install-tl/index.ts
index f6bf28e..3cf0fb2 100644
--- a/packages/main/src/texlive/install-tl/index.ts
+++ b/packages/main/src/texlive/install-tl/index.ts
@@ -1,4 +1,4 @@
-export { type InstallTLOptions, installTL } from '#/texlive/install-tl/cli';
+export * from '#/texlive/install-tl/cli';
export * from '#/texlive/install-tl/env';
export * from '#/texlive/install-tl/errors';
export * from '#/texlive/install-tl/profile';
diff --git a/packages/main/src/texlive/releases.ts b/packages/main/src/texlive/releases.ts
index 558973e..f8156aa 100644
--- a/packages/main/src/texlive/releases.ts
+++ b/packages/main/src/texlive/releases.ts
@@ -14,14 +14,14 @@ type ZonedDateTime = Temporal.ZonedDateTime;
export interface Release {
readonly version: Version;
- readonly releaseDate: ZonedDateTime | undefined;
+ readonly releaseDate?: ZonedDateTime | undefined;
}
export interface ReleaseData {
readonly newVersionReleased: () => boolean;
+ readonly previous: Release;
readonly latest: Release;
- readonly isLatest: (version: Version) => boolean;
- readonly isOnePrevious: (version: Version) => boolean;
+ readonly next: Release;
}
export namespace ReleaseData {
@@ -36,14 +36,13 @@ export namespace ReleaseData {
function newVersionReleased(): boolean {
return data.latest.version === latest.version;
}
- function isLatest(version: Version): boolean {
- return version === latest.version;
- }
- function isOnePrevious(version: Version): boolean {
- return Number.parseInt(version, 10) + 1
- === Number.parseInt(latest.version, 10);
- }
- const releases = { newVersionReleased, latest, isLatest, isOnePrevious };
+ const latestVersionNumber = Number.parseInt(latest.version, 10);
+ const releases = {
+ newVersionReleased,
+ previous: { version: `${latestVersionNumber - 1}` as Version },
+ latest,
+ next: { version: `${latestVersionNumber + 1}` as Version },
+ };
ctx.set(releases);
return releases;
}
diff --git a/packages/main/src/texlive/tlmgr/actions/list.ts b/packages/main/src/texlive/tlmgr/actions/list.ts
index 323bba7..4328e32 100644
--- a/packages/main/src/texlive/tlmgr/actions/list.ts
+++ b/packages/main/src/texlive/tlmgr/actions/list.ts
@@ -1,21 +1,17 @@
import { readFile } from 'node:fs/promises';
import * as path from 'node:path';
+import { P, match } from 'ts-pattern';
+
import * as log from '#/log';
import { use } from '#/texlive/tlmgr/internals';
-import { type Tlpobj, tlpdb } from '#/texlive/tlpkg';
-
-const RE = {
- nonPackage: /(?:^(?:collection|scheme)-|\.)/v,
- version: /^catalogue-version\s+(\S.*)$/mv,
- revision: /^revision\s+(\d+)\s*$/mv,
-} as const;
+import { type TLPObj, tlpdb } from '#/texlive/tlpkg';
/**
* Lists packages by reading `texlive.tlpdb` directly
* instead of running `tlmgr list`.
*/
-export async function* list(): AsyncGenerator {
+export async function* list(): AsyncGenerator {
const tlpdbPath = path.join(use().TEXDIR, 'tlpkg', 'texlive.tlpdb');
let db: string;
try {
@@ -25,11 +21,16 @@ export async function* list(): AsyncGenerator {
return;
}
try {
- for (const [name, data] of tlpdb.parse(db)) {
- if (name === 'texlive.infra' || !RE.nonPackage.test(name)) {
- const version = RE.version.exec(data)?.[1]?.trimEnd();
- const revision = RE.revision.exec(data)?.[1] ?? '';
- yield { name, version, revision };
+ for (const [tag, data] of tlpdb.parse(db)) {
+ if (
+ tag === 'TLPOBJ' && match(data.name)
+ .with('texlive.infra', () => true)
+ .with(P.string.includes('.'), () => false) // platform-specific subpackage
+ .with(P.string.startsWith('scheme-'), () => false)
+ .with(P.string.startsWith('collection-'), () => false)
+ .otherwise(() => true)
+ ) {
+ yield data;
}
}
} catch (error) {
diff --git a/packages/main/src/texlive/tlpkg/index.ts b/packages/main/src/texlive/tlpkg/index.ts
index 7a5f679..6c766b7 100644
--- a/packages/main/src/texlive/tlpkg/index.ts
+++ b/packages/main/src/texlive/tlpkg/index.ts
@@ -1,5 +1,5 @@
export * from '#/texlive/tlpkg/errors';
export * from '#/texlive/tlpkg/patch';
export * as tlpdb from '#/texlive/tlpkg/tlpdb';
-export type { Tlpobj } from '#/texlive/tlpkg/tlpdb';
+export type * from '#/texlive/tlpkg/tlpdb';
export * from '#/texlive/tlpkg/util';
diff --git a/packages/main/src/texlive/tlpkg/tlpdb.ts b/packages/main/src/texlive/tlpkg/tlpdb.ts
index 53b773a..83ba3b1 100644
--- a/packages/main/src/texlive/tlpkg/tlpdb.ts
+++ b/packages/main/src/texlive/tlpkg/tlpdb.ts
@@ -1,10 +1,66 @@
-export interface Tlpobj {
- readonly name: string;
- readonly version: string | undefined;
- readonly revision: string;
+import type { TLPDBSINGLE, TLPOBJ } from 'texlive-json-schemas/types';
+
+export interface TLPObj {
+ name: TLPOBJ['name'];
+ revision: string;
+ cataloguedata?: {
+ version?: NonNullable['version'];
+ };
+}
+
+export interface TLConfig
+ extends Pick, 'release'>
+{}
+
+export interface TLOptions {
+ location?: string;
+}
+
+const TAG = {
+ TLPOBJ: 'TLPOBJ',
+ TLConfig: 'TLConfig',
+ TLOptions: 'TLOptions',
+} as const;
+
+export type Entry =
+ | [typeof TAG.TLPOBJ, TLPObj]
+ | [typeof TAG.TLConfig, TLConfig]
+ | [typeof TAG.TLOptions, TLOptions];
+
+const RE = {
+ version: /^catalogue-version\s+(\S.*)$/mv,
+ revision: /^revision\s+(\d+)\s*$/mv,
+ location: /^depend\s+(?:opt_)?location:(.+)$/mv,
+ release: /^depend\s+release\/(.+)$/mv,
+} as const;
+
+export function* parse(db: string): Generator {
+ for (const [name, data] of entries(db)) {
+ if (
+ name === '00texlive-installation.config'
+ || name === '00texlive.installation'
+ ) {
+ if (name === '00texlive-installation.config') {
+ yield [TAG.TLConfig, { release: '2008' }];
+ }
+ const location = RE.location.exec(data)?.[1];
+ if (location !== undefined) {
+ yield [TAG.TLOptions, { location }];
+ }
+ } else if (name === '00texlive.config') {
+ const release = RE.release.exec(data)?.[1];
+ if (release !== undefined) {
+ yield [TAG.TLConfig, { release }];
+ }
+ } else if (!name.startsWith('00texlive')) {
+ const version = RE.version.exec(data)?.[1]?.trimEnd();
+ const revision = RE.revision.exec(data)?.[1] ?? '';
+ yield [TAG.TLPOBJ, { name, revision, cataloguedata: { version } }];
+ }
+ }
}
-export function* parse(
+function* entries(
db: string,
): Generator<[name: string, data: string], void, void> {
// dprint-ignore
diff --git a/packages/main/src/util/custom-inspect.ts b/packages/main/src/util/custom-inspect.ts
index 97f3c27..e3ea1cb 100644
--- a/packages/main/src/util/custom-inspect.ts
+++ b/packages/main/src/util/custom-inspect.ts
@@ -1,7 +1,11 @@
import { EOL, platform } from 'node:os';
import * as path from 'node:path';
import { env } from 'node:process';
-import type { InspectOptions, InspectOptionsStylized } from 'node:util';
+import {
+ type InspectOptions,
+ type InspectOptionsStylized,
+ inspect as utilInspect,
+} from 'node:util';
import { isDebug } from '@actions/core';
import ansi from 'ansi-styles';
@@ -18,7 +22,7 @@ Reflect.defineProperty(Error.prototype, customInspect, {
this: Readonly,
depth: number,
options: Readonly,
- inspect: Inspect,
+ inspect: Inspect = utilInspect,
): string {
if (depth < 0) {
return `[${getErrorName(this)}]`;
@@ -33,7 +37,7 @@ Reflect.defineProperty(Error.prototype, customInspect, {
function formatError(
error: Readonly,
options: Readonly,
- inspect: Inspect,
+ inspect: Inspect = utilInspect,
): string {
let stylized: string = inspectNoCustom(error, options, inspect);
// Colorize error name in red to improve legibility.
@@ -56,7 +60,7 @@ function getErrorName(error: Readonly): string {
function inspectNoCustom(
target: object,
options: Readonly,
- inspect: Inspect,
+ inspect: Inspect = utilInspect,
): string {
// Temporarily overrides `customInspect` to prevent circular calls.
const success = Reflect.defineProperty(target, customInspect, {
diff --git a/packages/main/tests/__mocks__/setup-texlive-action/texlive/releases.ts b/packages/main/tests/__mocks__/setup-texlive-action/texlive/releases.ts
index 5e603c2..27eb485 100644
--- a/packages/main/tests/__mocks__/setup-texlive-action/texlive/releases.ts
+++ b/packages/main/tests/__mocks__/setup-texlive-action/texlive/releases.ts
@@ -1,16 +1,12 @@
import { vi } from 'vitest';
export namespace ReleaseData {
+ const latestVersionNumber = Number.parseInt(LATEST_VERSION, 10);
const data = {
newVersionReleased: vi.fn().mockReturnValue(false),
+ previous: { version: `${latestVersionNumber - 1}` },
latest: { version: LATEST_VERSION },
- isLatest: vi.fn((version: typeof LATEST_VERSION) => {
- return version === LATEST_VERSION;
- }),
- isOnePrevious: vi.fn((version: typeof LATEST_VERSION) => {
- return Number.parseInt(version, 10) + 1
- === Number.parseInt(LATEST_VERSION, 10);
- }),
+ next: { version: `${latestVersionNumber + 1}` },
};
export const setup = vi.fn().mockResolvedValue(data);
export const use = vi.fn().mockReturnValue(data);
diff --git a/packages/main/tests/__tests__/action/config.test.ts b/packages/main/tests/__tests__/action/config.test.ts
index 0af4dcc..3632a21 100644
--- a/packages/main/tests/__tests__/action/config.test.ts
+++ b/packages/main/tests/__tests__/action/config.test.ts
@@ -183,6 +183,14 @@ describe('version', () => {
await expect(Config.load()).resolves.toHaveProperty('version', '2018');
});
+ it('permits the next version', async () => {
+ vi.mocked(Inputs.load).mockReturnValueOnce({
+ ...defaultInputs,
+ version: `${Number.parseInt(LATEST_VERSION, 10) + 1}`,
+ });
+ await expect(Config.load()).resolves.not.toThrow();
+ });
+
describe.each(['linux', 'win32'] as const)('on %s', (os) => {
beforeEach(() => {
vi.mocked(platform).mockReturnValue(os);
@@ -205,7 +213,7 @@ describe('version', () => {
...defaultInputs,
version: spec,
});
- await expect(Config.load()).toReject();
+ await expect(Config.load()).rejects.toThrow('');
});
});
diff --git a/packages/main/tests/__tests__/action/run/main.test.ts b/packages/main/tests/__tests__/action/run/main/index.test.ts
similarity index 68%
rename from packages/main/tests/__tests__/action/run/main.test.ts
rename to packages/main/tests/__tests__/action/run/main/index.test.ts
index 60840e0..8149407 100644
--- a/packages/main/tests/__tests__/action/run/main.test.ts
+++ b/packages/main/tests/__tests__/action/run/main/index.test.ts
@@ -3,19 +3,23 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { env } from 'node:process';
import { setOutput } from '@actions/core';
-import type { DeepWritable } from 'ts-essentials';
+import type { Writable } from 'ts-essentials';
import { CacheService } from '#/action/cache';
import { Config } from '#/action/config';
import { main } from '#/action/run/main';
-import { ReleaseData, installTL } from '#/texlive';
+import { install } from '#/action/run/main/install';
+import { adjustTexmf, updateTlmgr } from '#/action/run/main/update';
import * as tlmgr from '#/texlive/tlmgr/actions';
vi.unmock('#/action/run/main');
-const config = {} as DeepWritable;
+const config = {} as Writable;
vi.mocked(Config.load).mockResolvedValue(config);
+vi.mock('#/action/run/main/install');
+vi.mock('#/action/run/main/update');
+
beforeEach(() => {
config.cache = true;
config.packages = new Set();
@@ -69,14 +73,14 @@ const setCacheType = ([type]: typeof cacheTypes[number]) => {
};
it('installs TeX Live if cache not found', async () => {
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(MockCacheService.prototype.restore).toHaveBeenCalled();
- expect(installTL).toHaveBeenCalled();
+ expect(install).toHaveBeenCalled();
});
it('does not use cache if input cache is false', async () => {
config.cache = false;
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(MockCacheService.prototype.restore).not.toHaveBeenCalled();
});
@@ -84,8 +88,8 @@ it.each(cacheTypes)(
'does not install TeX Live if cache found (%s)',
async (...kind) => {
setCacheType(kind);
- await expect(main()).toResolve();
- expect(installTL).not.toHaveBeenCalled();
+ await expect(main()).resolves.not.toThrow();
+ expect(install).not.toHaveBeenCalled();
},
);
@@ -93,21 +97,21 @@ it.each([LATEST_VERSION, '2009', '2014'] as const)(
'sets version to %o with input %o',
async (version) => {
config.version = version;
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(setOutput).toHaveBeenCalledWith('version', version);
},
);
it('adds TeX Live to path after installation', async () => {
- await expect(main()).toResolve();
- expect(tlmgr.path.add).toHaveBeenCalledAfter(installTL);
+ await expect(main()).resolves.not.toThrow();
+ expect(tlmgr.path.add).toHaveBeenCalledAfter(install);
});
it.each(cacheTypes)(
'adds TeX Live to path after cache restoration (%s)',
async (...kind) => {
setCacheType(kind);
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.path.add).not.toHaveBeenCalledBefore(
MockCacheService.prototype.restore,
);
@@ -119,18 +123,17 @@ it.each([[true], [false]])(
'does not update any TeX packages for new installation',
async (input) => {
config.updateAllPackages = input;
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.update).not.toHaveBeenCalled();
},
);
it.each(cacheTypes)(
- 'updates `tlmgr` when cache restored (%s)',
+ 'updates TeX Live when cache restored (%s)',
async (...kind) => {
setCacheType(kind);
- await expect(main()).toResolve();
- expect(tlmgr.update).toHaveBeenCalledOnce();
- expect(tlmgr.update).toHaveBeenCalledWith({ self: true });
+ await expect(main()).resolves.not.toThrow();
+ expect(updateTlmgr).toHaveBeenCalledOnce();
},
);
@@ -139,8 +142,7 @@ it.each(cacheTypes)(
async (...kind) => {
setCacheType(kind);
config.updateAllPackages = true;
- await expect(main()).toResolve();
- expect(tlmgr.update).toHaveBeenCalledTimes(2);
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.update).toHaveBeenCalledWith({
all: true,
reinstallForciblyRemoved: true,
@@ -148,33 +150,17 @@ it.each(cacheTypes)(
},
);
-it.each(cacheTypes)(
- 'updates tlmgr for the one previous version',
- async (...kind) => {
- setCacheType(kind);
- config.updateAllPackages = true;
- config.version = `${
- Number.parseInt(LATEST_VERSION, 10) - 1
- }` as typeof config.version;
- vi.mocked(ReleaseData.use().newVersionReleased).mockReturnValue(true);
- await expect(main()).resolves.not.toThrow();
- expect(tlmgr.update).toHaveBeenCalledWith(
- expect.objectContaining({ self: true }),
- );
- },
-);
-
it('does nothing about TEXMF for new installation', async () => {
- await expect(main()).toResolve();
- expect(tlmgr.conf.texmf).not.toHaveBeenCalled();
+ await expect(main()).resolves.not.toThrow();
+ expect(adjustTexmf).not.toHaveBeenCalled();
});
it.each(cacheTypes)(
'may change TEXMF after adding TeX Live to path (%s)',
async (...kind) => {
setCacheType(kind);
- await expect(main()).toResolve();
- expect(tlmgr.conf.texmf).not.toHaveBeenCalledBefore(tlmgr.path.add);
+ await expect(main()).resolves.not.toThrow();
+ expect(adjustTexmf).not.toHaveBeenCalledBefore(tlmgr.path.add);
},
);
@@ -182,41 +168,20 @@ it.each(cacheTypes)(
'change old settings if they are not appropriate (%s)',
async (...kind) => {
setCacheType(kind);
- vi.mocked(tlmgr.conf.texmf).mockResolvedValue('' as unknown as void);
- await expect(main()).toResolve();
- expect(tlmgr.conf.texmf).toHaveBeenCalledWith(
- 'TEXMFHOME',
- expect.anything(),
- );
- vi.mocked(tlmgr.conf.texmf).mockReset();
- },
-);
-
-it.each(cacheTypes)(
- 'does not change old settings if not necessary (case %s)',
- async (...kind) => {
- setCacheType(kind);
- vi.mocked(tlmgr.conf.texmf).mockResolvedValue(
- '/texmf-local' as unknown as void,
- );
- await expect(main()).toResolve();
- expect(tlmgr.conf.texmf).not.toHaveBeenCalledWith(
- 'TEXMFHOME',
- expect.anything(),
- );
- vi.mocked(tlmgr.conf.texmf).mockReset();
+ await expect(main()).resolves.not.toThrow();
+ expect(adjustTexmf).toHaveBeenCalled();
},
);
it('does not setup tlcontrib by default', async () => {
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.repository.add).not.toHaveBeenCalled();
expect(tlmgr.pinning.add).not.toHaveBeenCalled();
});
it('sets up tlcontrib if input tlcontrib is true', async () => {
config.tlcontrib = true;
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.repository.add).not.toHaveBeenCalledBefore(tlmgr.path.add);
expect(tlmgr.repository.add).toHaveBeenCalledWith(
expect.anything(),
@@ -232,7 +197,7 @@ describe.each([[true], [false]])('%j', (force) => {
});
it('does not install any packages by default', async () => {
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.install).not.toHaveBeenCalled();
});
@@ -241,7 +206,7 @@ describe.each([[true], [false]])('%j', (force) => {
async (...kind) => {
setCacheType(kind);
config.packages = new Set(['foo', 'bar', 'baz']);
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.install).not.toHaveBeenCalled();
},
);
@@ -251,7 +216,7 @@ describe.each([[true], [false]])('%j', (force) => {
async (...kind) => {
setCacheType(kind);
config.packages = new Set(['foo', 'bar', 'baz']);
- await expect(main()).toResolve();
+ await expect(main()).resolves.not.toThrow();
expect(tlmgr.install).toHaveBeenCalled();
},
);
diff --git a/packages/main/tests/__tests__/action/run/main/install.test.ts b/packages/main/tests/__tests__/action/run/main/install.test.ts
new file mode 100644
index 0000000..3d85c40
--- /dev/null
+++ b/packages/main/tests/__tests__/action/run/main/install.test.ts
@@ -0,0 +1,55 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import { install } from '#/action/run/main/install';
+import {
+ type InstallTL,
+ InstallTLError,
+ Profile,
+ TlpdbError,
+ acquire,
+} from '#/texlive';
+
+vi.mocked(acquire).mockResolvedValue({ run: vi.fn() } as unknown as InstallTL);
+
+const downloadErrors = [
+ new InstallTLError('', { code: InstallTLError.Code.FAILED_TO_DOWNLOAD }),
+ new InstallTLError('', { code: InstallTLError.Code.UNEXPECTED_VERSION }),
+] as const;
+
+const installErrors = [
+ new InstallTLError('', {
+ code: InstallTLError.Code.INCOMPATIBLE_REPOSITORY_VERSION,
+ }),
+ new TlpdbError('', { code: TlpdbError.Code.FAILED_TO_INITIALIZE }),
+] as const;
+
+const errors = [...downloadErrors, ...installErrors] as const;
+
+describe('fallback to master', () => {
+ it.each(downloadErrors)('if failed to download', async (error) => {
+ vi.mocked(acquire).mockRejectedValueOnce(error);
+ const profile = new Profile(LATEST_VERSION, { prefix: '' });
+ await expect(install({ profile })).resolves.not.toThrow();
+ });
+
+ it.each(installErrors)('if failed to install', async (error) => {
+ vi.mocked(acquire).mockResolvedValueOnce({
+ run: vi.fn().mockRejectedValueOnce(error),
+ } as unknown as InstallTL);
+ const profile = new Profile(LATEST_VERSION, { prefix: '' });
+ await expect(install({ profile })).resolves.not.toThrow();
+ });
+});
+
+it.each(errors)('does not fallback for older versions', async (error) => {
+ vi.mocked(acquire).mockRejectedValueOnce(error);
+ const profile = new Profile('2021', { prefix: '' });
+ await expect(install({ profile })).rejects.toThrow(error);
+});
+
+it.each(errors)('does not fallback if repository set', async (error) => {
+ vi.mocked(acquire).mockRejectedValueOnce(error);
+ const profile = new Profile(LATEST_VERSION, { prefix: '' });
+ const repository = new URL(MOCK_URL);
+ await expect(install({ profile, repository })).rejects.toThrow(error);
+});
diff --git a/packages/main/tests/__tests__/action/run/main/update.test.ts b/packages/main/tests/__tests__/action/run/main/update.test.ts
new file mode 100644
index 0000000..82b9fcb
--- /dev/null
+++ b/packages/main/tests/__tests__/action/run/main/update.test.ts
@@ -0,0 +1,70 @@
+import { beforeEach, expect, it, vi } from 'vitest';
+
+import { CacheService } from '#/action/cache';
+import { updateTlmgr } from '#/action/run/main/update';
+import { Tlmgr, TlmgrError, TlpdbError } from '#/texlive';
+import { list, remove } from '#/texlive/tlmgr/actions/repository';
+import { update } from '#/texlive/tlmgr/actions/update';
+
+const updateCache = vi.fn();
+
+vi.mocked(list).mockImplementation(async function*() {});
+vi.mocked(CacheService.use).mockReturnValue(
+ { update: updateCache } as unknown as CacheService,
+);
+
+beforeEach(() => {
+ Tlmgr.setup({ version: LATEST_VERSION, TEXDIR: '' });
+});
+
+const versionOutdated = new TlmgrError('', {
+ action: 'update',
+ code: TlmgrError.Code.TL_VERSION_OUTDATED,
+});
+
+const failedToInitialize = new TlpdbError('', {
+ code: TlpdbError.Code.FAILED_TO_INITIALIZE,
+});
+
+it('move to historic', async () => {
+ vi.mocked(update).mockRejectedValueOnce(versionOutdated);
+ const opts = { version: LATEST_VERSION };
+ await expect(updateTlmgr(opts)).resolves.not.toThrow();
+ expect(updateCache).toHaveBeenCalled();
+});
+
+it('move to historic master', async () => {
+ vi
+ .mocked(update)
+ .mockRejectedValueOnce(versionOutdated)
+ .mockRejectedValueOnce(failedToInitialize);
+ const opts = { version: LATEST_VERSION };
+ await expect(updateTlmgr(opts)).resolves.not.toThrow();
+ expect(updateCache).toHaveBeenCalled();
+});
+
+it('does not move to historic if repository set', async () => {
+ vi.mocked(update).mockRejectedValueOnce(versionOutdated);
+ const opts = { version: LATEST_VERSION, repository: new URL(MOCK_URL) };
+ await expect(updateTlmgr(opts)).rejects.toThrow(versionOutdated);
+});
+
+it('removes tlcontrib', async () => {
+ vi.mocked(list).mockImplementationOnce(async function*() {
+ yield { tag: 'main', path: MOCK_URL };
+ yield { tag: 'tlcontrib', path: MOCK_URL };
+ });
+ const opts = { version: '2022' } as const;
+ await expect(updateTlmgr(opts)).resolves.not.toThrow();
+ expect(remove).toHaveBeenCalledWith('tlcontrib');
+});
+
+it('removes tlpretest', async () => {
+ vi.mocked(list).mockImplementationOnce(async function*() {
+ yield { tag: 'main', path: 'https://example.com/path/to/tlpretest/' };
+ yield { tag: 'tlcontrib', path: MOCK_URL };
+ });
+ const opts = { version: LATEST_VERSION };
+ await expect(updateTlmgr(opts)).resolves.not.toThrow();
+ expect(remove).toHaveBeenCalledWith('main');
+});
diff --git a/packages/main/tests/__tests__/texlive/tlmgr/list.test.ts b/packages/main/tests/__tests__/texlive/tlmgr/list.test.ts
index d77c9c6..d640189 100644
--- a/packages/main/tests/__tests__/texlive/tlmgr/list.test.ts
+++ b/packages/main/tests/__tests__/texlive/tlmgr/list.test.ts
@@ -33,15 +33,15 @@ it('lists texlive.infra', () => {
expect(tlpdb['2008']).toContainEqual(
expect.objectContaining({
name: 'texlive.infra',
- version: undefined,
revision: '12186',
+ cataloguedata: { version: undefined },
}),
);
expect(tlpdb['2023']).toContainEqual(
expect.objectContaining({
name: 'texlive.infra',
- version: undefined,
revision: '66822',
+ cataloguedata: { version: undefined },
}),
);
});
@@ -80,15 +80,15 @@ it('lists normal packages', () => {
expect(tlpdb['2008']).toContainEqual(
expect.objectContaining({
name: 'pdftex',
- version: '1.40.9',
revision: '12898',
+ cataloguedata: { version: '1.40.9' },
}),
);
expect(tlpdb['2023']).toContainEqual(
expect.objectContaining({
name: 'hyphen-base',
- version: undefined,
revision: '66413',
+ cataloguedata: { version: undefined },
}),
);
});
diff --git a/packages/main/tests/__tests__/texlive/tlpdb.test.ts b/packages/main/tests/__tests__/texlive/tlpdb.test.ts
new file mode 100644
index 0000000..c721010
--- /dev/null
+++ b/packages/main/tests/__tests__/texlive/tlpdb.test.ts
@@ -0,0 +1,48 @@
+import { describe, expect, test } from 'vitest';
+
+import tlpdb2008 from '@setup-texlive-action/fixtures/texlive.2008.tlpdb';
+import tlpdb2023 from '@setup-texlive-action/fixtures/texlive.2023.tlpdb';
+
+import { parse } from '#/texlive/tlpkg/tlpdb';
+
+const getLocation = (db: string): string | undefined => {
+ for (const [tag, options] of parse(db)) {
+ if (tag === 'TLOptions') {
+ return options.location;
+ }
+ }
+ return undefined;
+};
+
+const getVersion = (db: string): string | undefined => {
+ for (const [tag, config] of parse(db)) {
+ if (tag === 'TLConfig') {
+ return config.release;
+ }
+ }
+ return undefined;
+};
+
+describe('2008', () => {
+ test('location', () => {
+ expect(getLocation(tlpdb2008)).toMatchInlineSnapshot(
+ `"http://ftp.math.utah.edu/pub/tex/historic/systems/texlive/2008/tlnet"`,
+ );
+ });
+
+ test('version', () => {
+ expect(getVersion(tlpdb2008)).toBe('2008');
+ });
+});
+
+describe('2023', () => {
+ test('location', () => {
+ expect(getLocation(tlpdb2023)).toMatchInlineSnapshot(
+ `"http://ftp.dante.de/tex-archive/systems/texlive/tlnet"`,
+ );
+ });
+
+ test('version', () => {
+ expect(getVersion(tlpdb2023)).toBe('2023');
+ });
+});