astro-lilypond 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +3 -7
  2. package/dist/binary/autoInstall.d.ts +13 -0
  3. package/dist/binary/autoInstall.js +6 -0
  4. package/dist/binary/cacheDir.d.ts +1 -0
  5. package/dist/binary/cacheDir.js +15 -0
  6. package/dist/binary/downloadLilypond.d.ts +15 -0
  7. package/dist/binary/downloadLilypond.js +129 -0
  8. package/dist/binary/index.d.ts +5 -0
  9. package/dist/binary/index.js +5 -0
  10. package/dist/binary/platformTarget.d.ts +14 -0
  11. package/dist/binary/platformTarget.js +35 -0
  12. package/dist/binary/resolveLilypondBinary.d.ts +9 -0
  13. package/dist/binary/resolveLilypondBinary.js +41 -0
  14. package/dist/index.d.ts +9 -10
  15. package/dist/index.js +20 -60
  16. package/dist/loader.d.ts +7 -7
  17. package/dist/loader.js +17 -38
  18. package/dist/plugins/index.d.ts +1 -1
  19. package/dist/plugins/rehype.d.ts +2 -3
  20. package/dist/plugins/rehype.js +10 -16
  21. package/dist/plugins/remark.d.ts +2 -3
  22. package/dist/plugins/remark.js +10 -16
  23. package/dist/plugins/satteri.d.ts +2 -3
  24. package/dist/plugins/satteri.js +9 -15
  25. package/dist/plugins/types.d.ts +1 -14
  26. package/dist/utils/altTextFor.d.ts +1 -3
  27. package/dist/utils/altTextFor.js +1 -3
  28. package/dist/utils/altTextForBlock.d.ts +0 -1
  29. package/dist/utils/altTextForBlock.js +0 -1
  30. package/dist/utils/emitLilypondAsset.d.ts +13 -0
  31. package/dist/utils/emitLilypondAsset.fake.d.ts +6 -0
  32. package/dist/utils/emitLilypondAsset.fake.js +14 -0
  33. package/dist/utils/emitLilypondAsset.js +26 -0
  34. package/dist/utils/index.d.ts +1 -4
  35. package/dist/utils/index.js +1 -3
  36. package/dist/utils/renderedHtml.d.ts +2 -6
  37. package/dist/utils/renderedHtml.js +1 -1
  38. package/package.json +2 -1
  39. package/dist/deleteAssets.d.ts +0 -26
  40. package/dist/deleteAssets.js +0 -51
  41. package/dist/utils/assetsUrlBaseFor.d.ts +0 -5
  42. package/dist/utils/assetsUrlBaseFor.js +0 -8
  43. package/dist/utils/contentHashFor.d.ts +0 -19
  44. package/dist/utils/contentHashFor.js +0 -20
  45. package/dist/utils/ownAssetFileName.d.ts +0 -11
  46. package/dist/utils/ownAssetFileName.js +0 -24
  47. package/dist/writeAsset.d.ts +0 -51
  48. package/dist/writeAsset.js +0 -80
package/README.md CHANGED
@@ -13,17 +13,13 @@ Read the docs: https://lilypond.ky.fyi
13
13
 
14
14
  ## Quick Start
15
15
 
16
- ### 1. Install LilyPond
17
-
18
- Install the LilyPond binary directly from [lilypond.org](https://lilypond.org/download.html) or with `brew install lilypond` (Mac) or `apt install lilypond` (Linux).
19
-
20
- ### 2. Install `astro-lilypond`
16
+ ### 1. Install `astro-lilypond`
21
17
 
22
18
  ```sh
23
19
  pnpm add astro-lilypond
24
20
  ```
25
21
 
26
- ### 3. Add the integration to your Astro config
22
+ ### 2. Add the integration to your Astro config
27
23
 
28
24
  ```js
29
25
  // astro.config.mjs
@@ -35,7 +31,7 @@ export default defineConfig({
35
31
  });
36
32
  ```
37
33
 
38
- ### 4. Write your music
34
+ ### 3. Write your music
39
35
 
40
36
  Write LilyPond code within fenced code blocks with the `lilypond`, `ly`, or `ily` language tag.
41
37
 
@@ -0,0 +1,13 @@
1
+ import type { LilypondVersion } from "../types/lilypondVersion.js";
2
+ export interface AutoInstallOptions {
3
+ /**
4
+ * LilyPond version to download when none is found on `PATH`. Independent
5
+ * of `defaults.version`, which scores are compiled against.
6
+ * @default "2.26.0"
7
+ */
8
+ version?: LilypondVersion;
9
+ }
10
+ export declare function resolveAutoInstallOption(autoInstall: boolean | AutoInstallOptions | undefined): {
11
+ version?: LilypondVersion;
12
+ autoInstall: boolean;
13
+ };
@@ -0,0 +1,6 @@
1
+ export function resolveAutoInstallOption(autoInstall) {
2
+ return {
3
+ version: typeof autoInstall === "object" ? autoInstall.version : undefined,
4
+ autoInstall: autoInstall !== false,
5
+ };
6
+ }
@@ -0,0 +1 @@
1
+ export declare function lilypondCacheDir(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
@@ -0,0 +1,15 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ export function lilypondCacheDir(env = process.env, platform = process.platform) {
4
+ if (env.ASTRO_LILYPOND_CACHE_DIR)
5
+ return env.ASTRO_LILYPOND_CACHE_DIR;
6
+ if (platform === "darwin") {
7
+ return join(homedir(), "Library", "Caches", "astro-lilypond");
8
+ }
9
+ if (platform === "win32") {
10
+ const base = env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local");
11
+ return join(base, "astro-lilypond", "Cache");
12
+ }
13
+ const base = env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
14
+ return join(base, "astro-lilypond");
15
+ }
@@ -0,0 +1,15 @@
1
+ import type { LilypondVersion } from "../types/lilypondVersion.js";
2
+ export interface DownloadLilypondOptions {
3
+ version: LilypondVersion;
4
+ cacheDir?: string;
5
+ fetchImpl?: typeof fetch;
6
+ log?: (message: string) => void;
7
+ }
8
+ /**
9
+ * Downloads, verifies, and extracts a prebuilt LilyPond release into the
10
+ * cache dir, then returns the path to its `bin/lilypond[.exe]`. Returns
11
+ * `undefined` when LilyPond publishes no prebuilt for the current
12
+ * platform/arch. A previously-completed install for the same version is
13
+ * reused as-is.
14
+ */
15
+ export declare function downloadLilypond({ version, cacheDir, fetchImpl, log, }: DownloadLilypondOptions): Promise<string | undefined>;
@@ -0,0 +1,129 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { access, mkdir, mkdtemp, readdir, rename, rm, writeFile, } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { lilypondCacheDir } from "./cacheDir.js";
7
+ import { resolvePlatformTarget } from "./platformTarget.js";
8
+ const execFileAsync = promisify(execFile);
9
+ function downloadUrlFor(version, platform, arch, archiveExt) {
10
+ const filename = `lilypond-${version}-${platform}-${arch}.${archiveExt}`;
11
+ return `https://gitlab.com/api/v4/projects/lilypond%2Flilypond/packages/generic/lilypond/${version}/${filename}`;
12
+ }
13
+ async function pathExists(path) {
14
+ try {
15
+ await access(path);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * `.zip` archives (Windows/mingw) use PowerShell's `Expand-Archive` rather
24
+ * than `tar`, since `tar -xf`'s zip support depends on a libarchive/bsdtar
25
+ * build resolving first on `PATH`, which isn't guaranteed on Windows.
26
+ */
27
+ async function extractArchive(archivePath, extractDir, archiveExt) {
28
+ if (archiveExt === "zip") {
29
+ await execFileAsync("powershell.exe", [
30
+ "-NoProfile",
31
+ "-NonInteractive",
32
+ "-Command",
33
+ "Expand-Archive -LiteralPath $env:ASTRO_LILYPOND_ARCHIVE -DestinationPath $env:ASTRO_LILYPOND_EXTRACT_DIR -Force",
34
+ ], {
35
+ env: {
36
+ ...process.env,
37
+ ASTRO_LILYPOND_ARCHIVE: archivePath,
38
+ ASTRO_LILYPOND_EXTRACT_DIR: extractDir,
39
+ },
40
+ });
41
+ return;
42
+ }
43
+ await execFileAsync("tar", ["-xf", archivePath, "-C", extractDir]);
44
+ }
45
+ /**
46
+ * Downloads, verifies, and extracts a prebuilt LilyPond release into the
47
+ * cache dir, then returns the path to its `bin/lilypond[.exe]`. Returns
48
+ * `undefined` when LilyPond publishes no prebuilt for the current
49
+ * platform/arch. A previously-completed install for the same version is
50
+ * reused as-is.
51
+ */
52
+ export async function downloadLilypond({ version, cacheDir = lilypondCacheDir(), fetchImpl = fetch, log = () => { }, }) {
53
+ const target = resolvePlatformTarget();
54
+ if (!target)
55
+ return undefined;
56
+ const { platform, arch, archiveExt, binaryName } = target;
57
+ const installDir = join(cacheDir, `lilypond-${version}-${platform}-${arch}`);
58
+ const markerPath = join(installDir, ".complete");
59
+ const binaryPath = join(installDir, "bin", binaryName);
60
+ if (await pathExists(markerPath))
61
+ return binaryPath;
62
+ const url = downloadUrlFor(version, platform, arch, archiveExt);
63
+ log(`astro-lilypond: downloading LilyPond ${version} (${platform}-${arch})…`);
64
+ const response = await fetchImpl(url);
65
+ if (!response.ok || !response.body) {
66
+ throw new Error(`failed to download LilyPond ${version} from ${url} (HTTP ${response.status})`);
67
+ }
68
+ const expectedSha256 = response.headers.get("x-checksum-sha256");
69
+ await mkdir(cacheDir, { recursive: true });
70
+ const workDir = await mkdtemp(join(cacheDir, ".tmp-"));
71
+ try {
72
+ const archivePath = join(workDir, `archive.${archiveExt}`);
73
+ const hash = createHash("sha256");
74
+ const chunks = [];
75
+ for await (const chunk of response.body) {
76
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
77
+ hash.update(buf);
78
+ chunks.push(buf);
79
+ }
80
+ const data = Buffer.concat(chunks);
81
+ if (expectedSha256) {
82
+ const actualSha256 = hash.digest("hex");
83
+ if (actualSha256 !== expectedSha256) {
84
+ throw new Error(`checksum mismatch downloading LilyPond ${version} from ${url}\n` +
85
+ ` expected ${expectedSha256}\n got ${actualSha256}`);
86
+ }
87
+ }
88
+ else {
89
+ log("astro-lilypond: download response had no checksum header — skipping integrity verification");
90
+ }
91
+ await writeFile(archivePath, data);
92
+ const extractDir = join(workDir, "extract");
93
+ await mkdir(extractDir, { recursive: true });
94
+ await extractArchive(archivePath, extractDir, archiveExt);
95
+ // The archive contains a single top-level `lilypond-<version>/` dir.
96
+ const [extractedRoot] = await readdir(extractDir);
97
+ if (!extractedRoot) {
98
+ throw new Error(`downloaded archive for LilyPond ${version} was empty`);
99
+ }
100
+ const extractedPath = join(extractDir, extractedRoot);
101
+ // Writing the marker inside the extracted tree makes the rename below
102
+ // move the binary and its "installed" marker into place atomically
103
+ // together, so a process killed mid-install can't leave a good install
104
+ // looking uninstalled.
105
+ await writeFile(join(extractedPath, ".complete"), "");
106
+ const reuseConcurrentInstall = async () => {
107
+ if (!(await pathExists(markerPath)))
108
+ return false;
109
+ log(`astro-lilypond: LilyPond ${version} was installed concurrently by another process; reusing it`);
110
+ return true;
111
+ };
112
+ if (await reuseConcurrentInstall())
113
+ return binaryPath;
114
+ try {
115
+ await rm(installDir, { recursive: true, force: true });
116
+ await rename(extractedPath, installDir);
117
+ }
118
+ catch (err) {
119
+ if (await reuseConcurrentInstall())
120
+ return binaryPath;
121
+ throw err;
122
+ }
123
+ log(`astro-lilypond: installed LilyPond ${version} to ${installDir}`);
124
+ return binaryPath;
125
+ }
126
+ finally {
127
+ await rm(workDir, { recursive: true, force: true });
128
+ }
129
+ }
@@ -0,0 +1,5 @@
1
+ export { type AutoInstallOptions, resolveAutoInstallOption, } from "./autoInstall.js";
2
+ export { lilypondCacheDir } from "./cacheDir.js";
3
+ export { downloadLilypond } from "./downloadLilypond.js";
4
+ export { resolvePlatformTarget } from "./platformTarget.js";
5
+ export { DEFAULT_LILYPOND_VERSION, resolveLilypondBinary, } from "./resolveLilypondBinary.js";
@@ -0,0 +1,5 @@
1
+ export { resolveAutoInstallOption, } from "./autoInstall.js";
2
+ export { lilypondCacheDir } from "./cacheDir.js";
3
+ export { downloadLilypond } from "./downloadLilypond.js";
4
+ export { resolvePlatformTarget } from "./platformTarget.js";
5
+ export { DEFAULT_LILYPOND_VERSION, resolveLilypondBinary, } from "./resolveLilypondBinary.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A platform/arch combination LilyPond publishes a prebuilt archive for,
3
+ * per https://gitlab.com/lilypond/lilypond/-/releases.
4
+ */
5
+ export interface PlatformTarget {
6
+ /** LilyPond's own platform token, as used in its release filenames. */
7
+ platform: "linux" | "darwin" | "mingw";
8
+ /** LilyPond's own arch token, as used in its release filenames. */
9
+ arch: "x86_64" | "arm64";
10
+ archiveExt: "tar.gz" | "zip";
11
+ /** Name of the `lilypond` executable inside the archive's `bin/` dir. */
12
+ binaryName: "lilypond" | "lilypond.exe";
13
+ }
14
+ export declare function resolvePlatformTarget(platform?: NodeJS.Platform, arch?: string): PlatformTarget | undefined;
@@ -0,0 +1,35 @@
1
+ export function resolvePlatformTarget(platform = process.platform, arch = process.arch) {
2
+ if (platform === "linux" && arch === "x64") {
3
+ return {
4
+ platform: "linux",
5
+ arch: "x86_64",
6
+ archiveExt: "tar.gz",
7
+ binaryName: "lilypond",
8
+ };
9
+ }
10
+ if (platform === "darwin" && arch === "arm64") {
11
+ return {
12
+ platform: "darwin",
13
+ arch: "arm64",
14
+ archiveExt: "tar.gz",
15
+ binaryName: "lilypond",
16
+ };
17
+ }
18
+ if (platform === "darwin" && arch === "x64") {
19
+ return {
20
+ platform: "darwin",
21
+ arch: "x86_64",
22
+ archiveExt: "tar.gz",
23
+ binaryName: "lilypond",
24
+ };
25
+ }
26
+ if (platform === "win32" && arch === "x64") {
27
+ return {
28
+ platform: "mingw",
29
+ arch: "x86_64",
30
+ archiveExt: "zip",
31
+ binaryName: "lilypond.exe",
32
+ };
33
+ }
34
+ return undefined;
35
+ }
@@ -0,0 +1,9 @@
1
+ import type { LilypondVersion } from "../types/lilypondVersion.js";
2
+ export declare const DEFAULT_LILYPOND_VERSION: LilypondVersion;
3
+ export interface ResolveLilypondBinaryOptions {
4
+ version?: LilypondVersion;
5
+ autoInstall: boolean;
6
+ log?: (message: string) => void;
7
+ warn?: (message: string) => void;
8
+ }
9
+ export declare function resolveLilypondBinary({ version, autoInstall, log, warn, }: ResolveLilypondBinaryOptions): Promise<string>;
@@ -0,0 +1,41 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { downloadLilypond } from "./downloadLilypond.js";
4
+ import { resolvePlatformTarget } from "./platformTarget.js";
5
+ const execFileAsync = promisify(execFile);
6
+ export const DEFAULT_LILYPOND_VERSION = "2.26.0";
7
+ const IS_ON_PATH_TIMEOUT_MS = 10_000;
8
+ async function isOnPath() {
9
+ try {
10
+ await execFileAsync("lilypond", ["--version"], {
11
+ signal: AbortSignal.timeout(IS_ON_PATH_TIMEOUT_MS),
12
+ });
13
+ return true;
14
+ }
15
+ catch (err) {
16
+ const code = err?.code;
17
+ return code !== "ENOENT";
18
+ }
19
+ }
20
+ export async function resolveLilypondBinary({ version = DEFAULT_LILYPOND_VERSION, autoInstall, log = () => { }, warn = () => { }, }) {
21
+ if (await isOnPath())
22
+ return "lilypond";
23
+ if (!autoInstall) {
24
+ warn("astro-lilypond: `lilypond` binary not found on PATH — LilyPond blocks will render as errors. " +
25
+ "Install LilyPond and ensure it is on PATH, or enable the `autoInstall` option (on by default).");
26
+ return "lilypond";
27
+ }
28
+ if (!resolvePlatformTarget()) {
29
+ warn(`astro-lilypond: \`lilypond\` binary not found on PATH, and no prebuilt LilyPond ${version} ` +
30
+ `is available for ${process.platform}/${process.arch}. Install LilyPond manually and ensure it is on PATH.`);
31
+ return "lilypond";
32
+ }
33
+ try {
34
+ const binaryPath = await downloadLilypond({ version, log });
35
+ return binaryPath ?? "lilypond";
36
+ }
37
+ catch (err) {
38
+ throw new Error(`astro-lilypond: automatic LilyPond install failed — ${err instanceof Error ? err.message : String(err)}. ` +
39
+ "Install LilyPond manually and ensure it is on PATH, or set `autoInstall: false` to require a PATH install without attempting a download.", { cause: err });
40
+ }
41
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { AstroIntegration } from "astro";
2
+ import { type AutoInstallOptions } from "./binary/index.js";
2
3
  import { type PluginOptions } from "./plugins/index.js";
3
4
  import { type LilypondDefaults } from "./render.js";
4
5
  export declare const LY_EXTENSIONS: readonly [".ly", ".lilypond", ".ily"];
5
- export type { LilypondDefaults, PluginOptions as LilypondPluginOptions };
6
+ export type { AutoInstallOptions, LilypondDefaults, PluginOptions as LilypondPluginOptions, };
6
7
  export interface LilypondPage {
7
8
  src: string;
8
9
  width?: number;
@@ -14,7 +15,7 @@ export interface LilypondContent {
14
15
  }
15
16
  export interface LilypondOptions extends PluginOptions {
16
17
  /**
17
- * Output format to be written to a file under `outputDir`.
18
+ * Output format.
18
19
  * @default "svg"
19
20
  */
20
21
  format?: "svg" | "png";
@@ -30,14 +31,12 @@ export interface LilypondOptions extends PluginOptions {
30
31
  */
31
32
  timeout?: number;
32
33
  /**
33
- * Directory name, relative to Astro's `publicDir`, that rendered assets
34
- * are written into by both `astro dev` and `astro build`. Filenames are
35
- * content-addressed, so unchanged scores are reused instead of being
36
- * re-rendered, and it's safe to commit this directory if you'd like
37
- * faster rebuilds.
38
- *
39
- * @default "_lilypond"
34
+ * When no `lilypond` binary is found on `PATH`, download a matching
35
+ * prebuilt release into a local cache and use that instead. Set to
36
+ * `false` to only ever use a `PATH` install, or pass an object to pick
37
+ * which version gets downloaded.
38
+ * @default true
40
39
  */
41
- outputDir?: string;
40
+ autoInstall?: boolean | AutoInstallOptions;
42
41
  }
43
42
  export default function lilypond(options?: LilypondOptions): AstroIntegration;
package/dist/index.js CHANGED
@@ -1,13 +1,8 @@
1
- import { execFile } from "node:child_process";
2
- import { join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { promisify } from "node:util";
5
- import { pruneOrphanedAssets, pruneStaleAssets } from "./deleteAssets.js";
1
+ import emitAssetIntegration from "astro-emit-asset";
2
+ import { resolveAutoInstallOption, resolveLilypondBinary, } from "./binary/index.js";
6
3
  import { rehypePlugin, remarkPlugin, satteriPlugin, } from "./plugins/index.js";
7
4
  import { defaultOptions, render, resolveCrop, } from "./render.js";
8
- import { altTextFor, assetsUrlBaseFor, contentHashFor, includePathsFor, lyTypeDeclarationsFor, parseLyHeader, parseLyImportQuery, prependVersion, RECOGNIZED_QUERY_PARAMS, resolveDefaults, sourceNameFor, titleFor, } from "./utils/index.js";
9
- import { writeAssets } from "./writeAsset.js";
10
- const execFileAsync = promisify(execFile);
5
+ import { altTextFor, emitLilypondAsset, includePathsFor, lyTypeDeclarationsFor, parseLyHeader, parseLyImportQuery, prependVersion, RECOGNIZED_QUERY_PARAMS, resolveDefaults, sourceNameFor, titleFor, } from "./utils/index.js";
11
6
  export const LY_EXTENSIONS = [".ly", ".lilypond", ".ily"];
12
7
  function lyFilePlugin(options) {
13
8
  return {
@@ -27,34 +22,27 @@ function lyFilePlugin(options) {
27
22
  const includePaths = includePathsFor(pathname);
28
23
  const sourceName = sourceNameFor(pathname);
29
24
  const title = titleFor(sourceName);
30
- const hash = contentHashFor({ source: src, format, resolution, crop });
31
25
  const alt = altTextFor(parseLyHeader(source));
32
- const assets = await writeAssets({
33
- hash,
26
+ const pages = await emitLilypondAsset({
34
27
  title,
35
28
  format,
36
- outputDir: options.assetsDir,
37
- urlBase: options.assetsUrlBase,
29
+ source: src,
30
+ resolution,
31
+ crop,
38
32
  sizeScale: crop ? cropScale : 1,
39
- trackAsset: options.trackAsset,
40
- getBuffers: () => render(src, {
33
+ binaryPath: options.binaryPath,
34
+ render: () => render(src, {
41
35
  format,
42
36
  crop,
43
37
  defaults: options.defaults,
44
38
  timeout: options.timeout,
39
+ binaryPath: options.binaryPath,
45
40
  includePaths,
46
41
  sourceName,
47
42
  }),
48
43
  });
49
- // Keyed by the full id (including query) so `./score.ly` and
50
- // `./score.ly?crop` are tracked as independent sources.
51
- await options.pruneStaleAssets(id, assets.map((asset) => asset.fileName));
52
44
  const content = {
53
- pages: assets.map((asset) => ({
54
- src: asset.url,
55
- width: asset.width,
56
- height: asset.height,
57
- })),
45
+ pages,
58
46
  alt,
59
47
  };
60
48
  return {
@@ -64,37 +52,18 @@ function lyFilePlugin(options) {
64
52
  };
65
53
  }
66
54
  export default function lilypond(options = {}) {
67
- const referencedAssets = new Set();
68
- const assetsBySource = new Map();
69
- let assetsDir;
70
55
  return {
71
56
  name: "astro-lilypond",
72
57
  hooks: {
73
58
  "astro:config:setup": async ({ config, updateConfig, logger }) => {
74
- await execFileAsync("lilypond", ["--version"]).catch((err) => {
75
- if (err.code === "ENOENT") {
76
- logger?.warn("astro-lilypond: `lilypond` binary not found — LilyPond blocks will render as errors. Install LilyPond and ensure it is on PATH.");
77
- }
59
+ options.binaryPath = await resolveLilypondBinary({
60
+ ...resolveAutoInstallOption(options.autoInstall),
61
+ log: (message) => logger?.info(message),
62
+ warn: (message) => logger?.warn(message),
78
63
  });
79
- const outputDirName = options.outputDir ?? "_lilypond";
80
- const assetsUrlBase = assetsUrlBaseFor(config.base, outputDirName);
81
- const resolvedAssetsDir = join(fileURLToPath(config.publicDir), outputDirName);
82
- assetsDir = resolvedAssetsDir;
83
- const resolvedOptions = {
84
- ...options,
85
- assetsDir,
86
- assetsUrlBase,
87
- trackAsset: (fileName) => referencedAssets.add(fileName),
88
- pruneStaleAssets: (sourceKey, fileNames) => pruneStaleAssets({
89
- assetsBySource,
90
- sourceKey,
91
- fileNames,
92
- outputDir: resolvedAssetsDir,
93
- logger,
94
- }),
95
- };
96
64
  updateConfig({
97
- vite: { plugins: [lyFilePlugin(resolvedOptions)] },
65
+ integrations: [emitAssetIntegration()],
66
+ vite: { plugins: [lyFilePlugin(options)] },
98
67
  });
99
68
  const existingProcessor = config.markdown?.processor;
100
69
  if (existingProcessor?.name === "satteri") {
@@ -110,7 +79,7 @@ export default function lilypond(options = {}) {
110
79
  ...existingOptions,
111
80
  mdastPlugins: [
112
81
  ...(existingOptions.mdastPlugins ?? []),
113
- satteriPlugin(resolvedOptions),
82
+ satteriPlugin(options),
114
83
  ],
115
84
  }),
116
85
  },
@@ -131,11 +100,11 @@ export default function lilypond(options = {}) {
131
100
  ...existingOptions,
132
101
  remarkPlugins: [
133
102
  ...(existingOptions.remarkPlugins ?? []),
134
- [remarkPlugin, resolvedOptions],
103
+ [remarkPlugin, options],
135
104
  ],
136
105
  rehypePlugins: [
137
106
  ...(existingOptions.rehypePlugins ?? []),
138
- [rehypePlugin, resolvedOptions],
107
+ [rehypePlugin, options],
139
108
  ],
140
109
  }),
141
110
  },
@@ -154,15 +123,6 @@ export default function lilypond(options = {}) {
154
123
  content: lyTypeDeclarationsFor(LY_EXTENSIONS, RECOGNIZED_QUERY_PARAMS),
155
124
  });
156
125
  },
157
- "astro:build:done": async ({ logger }) => {
158
- if (!assetsDir)
159
- return;
160
- await pruneOrphanedAssets({
161
- dir: assetsDir,
162
- referenced: referencedAssets,
163
- logger,
164
- });
165
- },
166
126
  },
167
127
  };
168
128
  }
package/dist/loader.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Loader } from "astro/loaders";
2
2
  import { z } from "astro/zod";
3
+ import { type AutoInstallOptions } from "./binary/index.js";
3
4
  import { type LilypondContent } from "./index.js";
4
5
  import type { PluginOptions } from "./plugins/index.js";
5
6
  import { type KnownLyHeaderFields } from "./utils/index.js";
@@ -28,14 +29,13 @@ export interface LilypondLoaderOptions extends PluginOptions {
28
29
  */
29
30
  generateId?: (options: GenerateIdOptions) => string;
30
31
  /**
31
- * Directory name, relative to Astro's `publicDir`, that rendered assets
32
- * are written into. Exclusive to this collection — the loader prunes
33
- * anything under it that isn't currently referenced by one of its own
34
- * entries, so don't point two different collections (or the `lilypond()`
35
- * integration's own `outputDir`) at the same directory.
36
- * @default `"_lilypond/<collection name>"`
32
+ * When no `lilypond` binary is found on `PATH`, download a matching
33
+ * prebuilt release into a local cache and use that instead. Set to
34
+ * `false` to only ever use a `PATH` install, or pass an object to pick
35
+ * which version gets downloaded.
36
+ * @default true
37
37
  */
38
- outputDir?: string;
38
+ autoInstall?: boolean | AutoInstallOptions;
39
39
  }
40
40
  export interface LilypondHeaderData extends KnownLyHeaderFields {
41
41
  /** Header fields outside LilyPond's standard set (e.g. `mutopiacomposer`). */