diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a6d6fc3..7725caf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,14 +18,13 @@ concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-# The mesh avatar engine is bundled from this mesh-avatar-studio commit at build time. Pinned so
-# an upstream change cannot silently change what CI builds and ships; move it on purpose, in a
-# commit of its own, after rendering the examples with the new engine.
+# The mesh avatar engine is bundled at build time from the mesh-avatar development dependency,
+# pinned to an exact version in package.json so an upstream release cannot silently change what CI
+# builds and ships; move it on purpose, in a commit of its own, after rendering the examples with it.
env:
# onnxruntime-node's install script fetches its CUDA binaries from NuGet on Linux x64 (hundreds of
# MB, per install). Alignment runs on the CPU binaries the package already contains.
ONNXRUNTIME_NODE_INSTALL: skip
- MESH_AVATAR_STUDIO_REF: 8713e85a00e50f5cdf8fe33294b961d1d6506fc9
# Timeouts are generous: here they also count time spent queued for a runner, and jobs that take
# a minute were cancelled after 15 minutes of waiting when runners were busy.
@@ -36,28 +35,15 @@ jobs:
name: Format, lint, typecheck, build
runs-on: ubuntu-latest
timeout-minutes: 30
- defaults:
- run:
- working-directory: avatarscript
steps:
- # Side by side, as in development: the build bundles the engine from ../mesh-avatar-studio.
- # Not nested inside this checkout, where vitest and tsc would pick up its files.
- uses: actions/checkout@v6
with:
- path: avatarscript
- persist-credentials: false
- - uses: actions/checkout@v6
- with:
- repository: shinshin86/mesh-avatar-studio
- ref: ${{ env.MESH_AVATAR_STUDIO_REF }}
- path: mesh-avatar-studio
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- cache-dependency-path: avatarscript/package-lock.json
# PUPPETEER_SKIP_DOWNLOAD: nothing here starts a browser, and Chrome is most of the install.
- run: npm ci
@@ -80,15 +66,14 @@ jobs:
- name: Build
run: npm run build
- # The build must have bundled the pinned engine, not whatever happened to be checked out.
+ # The build must have bundled the version package.json pins, not whatever was installed.
- name: Check the bundled engine
run: |
node -e "
const s = JSON.parse(require('fs').readFileSync('dist/engine/source.json', 'utf8'));
- if (s.commit !== process.env.MESH_AVATAR_STUDIO_REF || s.modified) {
- console.error('bundled engine', s); process.exit(1);
- }
- console.log('bundled engine', s.commit);"
+ const pinned = JSON.parse(require('fs').readFileSync('package.json', 'utf8')).devDependencies['mesh-avatar'];
+ if (s.version !== pinned) { console.error('bundled engine', s, 'pinned', pinned); process.exit(1); }
+ console.log('bundled engine', s.package + '@' + s.version);"
# ubuntu + macOS: the unit tests need no browser and no API key (CLAUDE.md), but kuromoji's
# dictionary loading and path handling are worth running on both platforms the CLI is used on.
@@ -123,34 +108,22 @@ jobs:
# Verifies the published package actually works: scripts/smoke.sh packs the exact tarball
# (prepack builds dist/ and bundles the engine), installs it into an empty folder with no
- # mesh-avatar-studio and no esbuild beside it, and renders videos through the CLI and the
+ # mesh-avatar package and no esbuild beside it, and renders videos through the CLI and the
# library with the offline mock voice, checking codec, length and that the frames are not
# blank. Run the same script locally before changing packaging or rendering.
package-smoke:
name: Package smoke (pack, install, render)
runs-on: ubuntu-latest
timeout-minutes: 45
- defaults:
- run:
- working-directory: avatarscript
steps:
- uses: actions/checkout@v6
with:
- path: avatarscript
- persist-credentials: false
- # Also the source of the sample avatar the smoke renders.
- - uses: actions/checkout@v6
- with:
- repository: shinshin86/mesh-avatar-studio
- ref: ${{ env.MESH_AVATAR_STUDIO_REF }}
- path: mesh-avatar-studio
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- cache-dependency-path: avatarscript/package-lock.json
- name: Install ffmpeg
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ffmpeg
diff --git a/README.md b/README.md
index f824246..52fcd2e 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ avatar package + text ──► AvatarScript (.avs) ──► compiler + TTS ─
AvatarScript defines a standard avatar package, a script language for directing an avatar's
speech, emotion and gestures, and a compiler and renderer that produce the video. The first
-runtime is the mesh avatar engine from
+runtime is the mesh avatar engine, [mesh-avatar](https://www.npmjs.com/package/mesh-avatar) from
[mesh-avatar-studio](https://github.com/shinshin86/mesh-avatar-studio).
See [PLAN.md](PLAN.md) for the design and milestones.
@@ -238,9 +238,11 @@ a font file always works.
## Develop
-Clone [mesh-avatar-studio](https://github.com/shinshin86/mesh-avatar-studio) next to this
-repository: running from source (`node src/cli.ts …`) bundles the engine from that checkout
-(or from `--engine
` / `AVATARSCRIPT_MESH_ENGINE`).
+Running from source (`node src/cli.ts …`) bundles the engine from the
+[mesh-avatar](https://www.npmjs.com/package/mesh-avatar) development dependency, pinned to an
+exact version in package.json. To try engine changes before they are published, point
+`--engine ` / `AVATARSCRIPT_MESH_ENGINE` at a
+[mesh-avatar-studio](https://github.com/shinshin86/mesh-avatar-studio) checkout.
```sh
npm install
@@ -248,19 +250,20 @@ npm run format # Prettier
npm run lint # ESLint, including type-aware rules
npm test
npm run typecheck
-npm run build # dist/, including the engine bundled from ../mesh-avatar-studio
+npm run build # dist/, including the engine bundled from mesh-avatar
scripts/smoke.sh avatars/ani # pack, install, render (as CI does)
```
CI (`.github/workflows/ci.yml`) runs Prettier, ESLint, the typecheck and build, the tests on Ubuntu and macOS,
-and the package smoke above, against a pinned mesh-avatar-studio commit.
+and the package smoke above.
-`npm run build` records the engine's source commit in `dist/engine/source.json`.
+`npm run build` records the engine's package version in `dist/engine/source.json`.
## License
-[MIT](LICENSE). The bundled mesh avatar engine (`dist/engine/`) is from
+[MIT](LICENSE). The bundled mesh avatar engine (`dist/engine/`) is
+[mesh-avatar](https://www.npmjs.com/package/mesh-avatar) from
[mesh-avatar-studio](https://github.com/shinshin86/mesh-avatar-studio), MIT License,
-© Yuki Shindo; its license is included as `dist/engine/LICENSE.mesh-avatar-studio`. The
+© Yuki Shindo; its license is included as `dist/engine/LICENSE.mesh-avatar`. The
sample avatar `avatars/ani` (illustration and built layers) is © 2026 receptron, under the same
MIT License.
diff --git a/package-lock.json b/package-lock.json
index 2b554a1..decb9d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
"eslint-plugin-security": "^4.2.0",
"eslint-plugin-sonarjs": "^4.2.2",
"globals": "^17.13.0",
+ "mesh-avatar": "0.1.1",
"onnxruntime-node": "^1.30.0",
"prettier": "^3.9.9",
"typescript": "~6.0.2",
@@ -2171,6 +2172,13 @@
}
}
},
+ "node_modules/fflate": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/file-entry-cache": {
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz",
@@ -2827,6 +2835,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/mesh-avatar": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/mesh-avatar/-/mesh-avatar-0.1.1.tgz",
+ "integrity": "sha512-RVSizpqYjVX6rbGfdSgwS0/bDcADn5tcDDSyctDi9cPeSdb5cCep8Y2AAzj3WRw258PHDsJ8nTmkREyDLZOF/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fflate": "^0.8.3"
+ }
+ },
"node_modules/minimatch": {
"version": "10.2.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
diff --git a/package.json b/package.json
index 29449b5..b99fefe 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"eslint-plugin-security": "^4.2.0",
"eslint-plugin-sonarjs": "^4.2.2",
"globals": "^17.13.0",
+ "mesh-avatar": "0.1.1",
"onnxruntime-node": "^1.30.0",
"prettier": "^3.9.9",
"typescript": "~6.0.2",
diff --git a/scripts/build.ts b/scripts/build.ts
index cc11f0f..9393004 100644
--- a/scripts/build.ts
+++ b/scripts/build.ts
@@ -1,24 +1,28 @@
-// npm run build: compiles src/ to dist/ and bundles the mesh avatar engine into
-// dist/engine/ with its license, so the package runs without a mesh-avatar-studio checkout.
+// npm run build: compiles src/ to dist/ and bundles the mesh avatar engine (the mesh-avatar
+// package) into dist/engine/ with its license, so the package runs without it or esbuild.
import { execFileSync } from "node:child_process";
-import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
+import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
-import { bundleEngine, findEngineRoot } from "../src/engine.ts";
+import { z } from "zod";
+import { parseJson } from "../src/json.ts";
+import { bundleEngine, packageEngine } from "../src/engine.ts";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const dist = join(root, "dist");
await rm(dist, { recursive: true, force: true });
execFileSync("npx", ["tsc", "-p", "tsconfig.build.json"], { cwd: root, stdio: "inherit" });
-const engineRoot = await findEngineRoot(process.argv[2]);
-const git = (...args: string[]) => execFileSync("git", ["-C", engineRoot, ...args], { encoding: "utf8" }).trim();
-const commit = git("rev-parse", "HEAD");
-const dirty = git("status", "--porcelain", "--", "src/engine", "src/rig") !== "";
-const source = { repository: "https://github.com/shinshin86/mesh-avatar-studio", commit, modified: dirty };
+const engine = packageEngine();
+const { name, version } = parseJson(
+ z.looseObject({ name: z.string(), version: z.string() }),
+ await readFile(join(engine.root, "package.json"), "utf8"),
+ "mesh-avatar package.json",
+);
+const source = { package: name, version, repository: "https://github.com/shinshin86/mesh-avatar-studio" };
await mkdir(join(dist, "engine"), { recursive: true });
-const banner = `/*! Mesh avatar engine from mesh-avatar-studio (${source.repository}, ${commit.slice(0, 7)}${dirty ? ", modified" : ""}).\n * MIT License, see LICENSE.mesh-avatar-studio in this folder. */\n`;
-await writeFile(join(dist, "engine/mesh-engine.js"), banner + (await bundleEngine(engineRoot)));
-await copyFile(join(engineRoot, "LICENSE"), join(dist, "engine/LICENSE.mesh-avatar-studio"));
+const banner = `/*! Mesh avatar engine: ${name}@${version} (${source.repository}).\n * MIT License, see LICENSE.mesh-avatar in this folder. */\n`;
+await writeFile(join(dist, "engine/mesh-engine.js"), banner + (await bundleEngine(engine)));
+await copyFile(join(engine.root, "LICENSE"), join(dist, "engine/LICENSE.mesh-avatar"));
await writeFile(join(dist, "engine/source.json"), JSON.stringify(source, null, 2) + "\n");
-console.log(`built dist/ with the mesh engine from ${engineRoot} @ ${commit.slice(0, 7)}${dirty ? " (modified)" : ""}`);
+console.log(`built dist/ with the mesh engine ${name}@${version}`);
diff --git a/scripts/smoke.sh b/scripts/smoke.sh
index 476ed23..62176b8 100755
--- a/scripts/smoke.sh
+++ b/scripts/smoke.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Package smoke test: pack the exact tarball (prepack builds dist/ and bundles the engine),
-# install it into an empty folder with no mesh-avatar-studio checkout and no esbuild beside it,
+# install it into an empty folder with no mesh-avatar package and no esbuild beside it,
# and make a video through both the CLI and the library with the offline mock voice.
# Then check the video is real: an H.264 + AAC MP4 of the expected length whose frames show
# the avatar, not a blank canvas.
@@ -20,8 +20,8 @@ npm init -y >/dev/null
npm pkg set type=module
npm install --no-audit --no-fund "$WORK/$TGZ" >/dev/null
-if [ -e node_modules/esbuild ]; then
- echo "esbuild was installed with the package; the engine must come prebuilt"; exit 1
+if [ -e node_modules/esbuild ] || [ -e node_modules/mesh-avatar ]; then
+ echo "esbuild or mesh-avatar was installed with the package; the engine must come prebuilt"; exit 1
fi
printf '[emotion:happy] こんにちは! Hello from the package.\n' > story.avs
diff --git a/src/cli.ts b/src/cli.ts
index a744cff..5a908fd 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -37,7 +37,7 @@ Video (make, render):
--seed randomness of blinks and idle motion (default 1)
--subtitles draw subtitles (style from the script's subtitle-* front matter)
--no-subtitles do not draw subtitles, even if the script turns them on
- --engine mesh-avatar-studio checkout (default ../mesh-avatar-studio)
+ --engine bundle the engine from a mesh-avatar-studio checkout instead
API keys are read from the environment or a .env file.`;
diff --git a/src/engine.ts b/src/engine.ts
index dc951fb..0f07b89 100644
--- a/src/engine.ts
+++ b/src/engine.ts
@@ -1,6 +1,6 @@
-// The mesh avatar engine (from mesh-avatar-studio) as one script for the render page.
-// Packages ship it prebuilt in dist/engine/; a source checkout bundles it from a
-// mesh-avatar-studio checkout next to this repository.
+// The mesh avatar engine (the mesh-avatar package, from mesh-avatar-studio) as one script for the
+// render page. Packages ship it prebuilt in dist/engine/; a source checkout bundles it from the
+// mesh-avatar development dependency, or from a mesh-avatar-studio checkout when one is named.
import { readFile, stat } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
@@ -8,6 +8,14 @@ import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url));
/** Where `npm run build` writes the bundled engine (dist/engine/ next to dist/engine.js). */
export const BUNDLED_ENGINE = resolve(here, "engine/mesh-engine.js");
+/**
+ * The engine in a mesh-avatar-studio checkout: the runtime package, or the src/engine/ it lived in
+ * before mesh-avatar was published (as at the commit avatarscript 0.4 bundled). `root` holds its LICENSE.
+ */
+const CHECKOUT_LAYOUTS = [
+ { entry: "packages/runtime/src/index.ts", root: "packages/runtime" },
+ { entry: "src/engine/index.ts", root: "." },
+];
const exists = (p: string) =>
stat(p).then(
@@ -15,20 +23,35 @@ const exists = (p: string) =>
() => false,
);
-export async function findEngineRoot(explicit?: string): Promise {
- const root = resolve(explicit ?? process.env.AVATARSCRIPT_MESH_ENGINE ?? resolve(here, "../../mesh-avatar-studio"));
- const entry = resolve(root, "src/engine/index.ts");
- if (!(await exists(entry))) {
- throw new Error(`mesh engine not found at ${entry}. Clone mesh-avatar-studio next to this repository, or pass --engine / set AVATARSCRIPT_MESH_ENGINE.`);
+/** Where the engine is bundled from: the installed mesh-avatar package, or a checkout's runtime source. */
+export interface EngineSource {
+ entry: string;
+ /** the folder holding the engine's LICENSE */
+ root: string;
+}
+
+/** The installed mesh-avatar package (a development dependency: packages ship the engine bundled). */
+export function packageEngine(): EngineSource {
+ // import.meta.resolve: the package exports only an `import` condition
+ const entry = fileURLToPath(import.meta.resolve("mesh-avatar"));
+ return { entry, root: resolve(dirname(entry), "..") };
+}
+
+/** A mesh-avatar-studio checkout, for trying engine changes before they are published. */
+export async function checkoutEngine(root: string): Promise {
+ for (const layout of CHECKOUT_LAYOUTS) {
+ const entry = resolve(root, layout.entry);
+ if (await exists(entry)) return { entry, root: resolve(root, layout.root) };
}
- return root;
+ const tried = CHECKOUT_LAYOUTS.map((l) => resolve(root, l.entry)).join(" or ");
+ throw new Error(`mesh engine not found at ${tried}: --engine / AVATARSCRIPT_MESH_ENGINE must name a mesh-avatar-studio checkout.`);
}
-/** Bundles the engine from a mesh-avatar-studio checkout (esbuild is a development dependency). */
-export async function bundleEngine(root: string): Promise {
+/** Bundles the engine into one script that sets `window.MeshAvatarEngine` (esbuild is a development dependency). */
+export async function bundleEngine(source: EngineSource): Promise {
const { build } = await import("esbuild");
const result = await build({
- entryPoints: [resolve(root, "src/engine/index.ts")],
+ entryPoints: [source.entry],
bundle: true,
write: false,
format: "iife",
@@ -40,12 +63,12 @@ export async function bundleEngine(root: string): Promise {
}
/**
- * The engine script. An explicit checkout (option or AVATARSCRIPT_MESH_ENGINE) wins; otherwise
- * the bundled copy, otherwise the checkout next to this repository.
+ * The engine script. A named checkout (option or AVATARSCRIPT_MESH_ENGINE) wins; otherwise the
+ * bundled copy, otherwise the installed mesh-avatar package.
*/
export async function loadEngine(explicitRoot?: string): Promise {
- if (!explicitRoot && !process.env.AVATARSCRIPT_MESH_ENGINE && (await exists(BUNDLED_ENGINE))) {
- return readFile(BUNDLED_ENGINE, "utf8");
- }
- return bundleEngine(await findEngineRoot(explicitRoot));
+ const root = explicitRoot ?? process.env.AVATARSCRIPT_MESH_ENGINE;
+ if (root) return bundleEngine(await checkoutEngine(root));
+ if (await exists(BUNDLED_ENGINE)) return readFile(BUNDLED_ENGINE, "utf8");
+ return bundleEngine(packageEngine());
}
diff --git a/src/render-page.ts b/src/render-page.ts
index a6311fc..4aad6b4 100644
--- a/src/render-page.ts
+++ b/src/render-page.ts
@@ -19,7 +19,7 @@ export interface FramePlan {
caption: string | null;
}
-/** The part of the mesh engine's avatar API the renderer drives (mesh-avatar-studio src/engine/index.ts). */
+/** The part of the mesh engine's avatar API the renderer drives (the mesh-avatar package's MeshAvatar). */
interface MeshAvatarApi {
readonly motions: { id: string }[];
setEmotion(tag: string, options: { playMotion: boolean }): void;
diff --git a/src/render.ts b/src/render.ts
index 690a254..b5343c9 100644
--- a/src/render.ts
+++ b/src/render.ts
@@ -26,7 +26,7 @@ export interface RenderOptions {
height?: number;
/** background and avatar placement over the score's */
view?: ViewInput;
- /** mesh-avatar-studio checkout to bundle the engine from, instead of the bundled copy */
+ /** mesh-avatar-studio checkout to bundle the engine from, instead of the bundled mesh-avatar package */
engineRoot?: string;
seed?: number;
/** mouth shapes lead the sound slightly so the smoothed mouth arrives on time, seconds */
diff --git a/tests/engine.test.ts b/tests/engine.test.ts
new file mode 100644
index 0000000..086e115
--- /dev/null
+++ b/tests/engine.test.ts
@@ -0,0 +1,38 @@
+import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, describe, expect, it } from "vitest";
+import { checkoutEngine } from "../src/engine.ts";
+
+const dirs: string[] = [];
+afterEach(async () => {
+ await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
+});
+
+/** A fake mesh-avatar-studio checkout with the given files. */
+async function checkout(...files: string[]): Promise {
+ const root = await mkdtemp(join(tmpdir(), "avatarscript-engine-"));
+ dirs.push(root);
+ for (const f of files) {
+ await mkdir(join(root, f, ".."), { recursive: true });
+ await writeFile(join(root, f), "");
+ }
+ return root;
+}
+
+describe("checkoutEngine", () => {
+ it("bundles the runtime package of a current checkout", async () => {
+ const root = await checkout("packages/runtime/src/index.ts", "src/engine/index.ts");
+ expect(await checkoutEngine(root)).toEqual({ entry: join(root, "packages/runtime/src/index.ts"), root: join(root, "packages/runtime") });
+ });
+
+ it("accepts a checkout from before the runtime package (src/engine/)", async () => {
+ const root = await checkout("src/engine/index.ts");
+ expect(await checkoutEngine(root)).toEqual({ entry: join(root, "src/engine/index.ts"), root });
+ });
+
+ it("names both layouts when neither is there", async () => {
+ const root = await checkout("README.md");
+ await expect(checkoutEngine(root)).rejects.toThrow(/packages\/runtime\/src\/index\.ts or .*src\/engine\/index\.ts/);
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 7dcca0f..07764be 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -820,6 +820,11 @@ fdir@^6.5.0:
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+fflate@^0.8.3:
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.3.tgz#bc27d8eb30343d4d512abb03480202ce65d825fc"
+ integrity sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==
+
"file-entry-cache@11.1.5 || >11.1.6 <12":
version "11.1.5"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-11.1.5.tgz#c8210eb055de63e68685ccfb6a017e386d4577d0"
@@ -1030,6 +1035,13 @@ matcher@^4.0.0:
dependencies:
escape-string-regexp "^4.0.0"
+mesh-avatar@0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/mesh-avatar/-/mesh-avatar-0.1.1.tgz#2cf8a3500c61fa2df9484ab8d00b9fb2c875bd71"
+ integrity sha512-RVSizpqYjVX6rbGfdSgwS0/bDcADn5tcDDSyctDi9cPeSdb5cCep8Y2AAzj3WRw258PHDsJ8nTmkREyDLZOF/Q==
+ dependencies:
+ fflate "^0.8.3"
+
minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5, minimatch@^10.2.6:
version "10.2.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef"