From 96ba7ea760bf59239fda7bd4dedde39e84e3e4f7 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Mon, 1 Jun 2026 12:17:47 -0300 Subject: [PATCH 01/12] [DRAFT] chore(install-dynamic-plugins): consume installer from npm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the COPY of scripts/install-dynamic-plugins/{install-dynamic-plugins.cjs, install-dynamic-plugins.sh} with an `npm install` of @red-hat-developer-hub/cli-module-install-dynamic-plugins (built and published out of redhat-developer/rhdh-plugins). This unblocks the cli-module structure on the rhdh-plugins side — it lets that package use the standard `backstage-cli package build` (unbundled, multi-file dist) instead of a custom esbuild bundle with a keytar stub. See the conversation context: https://github.com/redhat-developer/rhdh-plugins/pull/3254 Backward compatibility is preserved by writing a tiny `/opt/app-root/src/install-dynamic-plugins.sh` shim that delegates to the npm-installed bin, so the Helm chart and Operator init-container spec continue to invoke `./install-dynamic-plugins.sh /dynamic-plugins-root` unchanged. DRAFT — DO NOT MERGE: blocked on redhat-developer/rhdh-plugins#3254 (or the unbundled successor) being merged and published to npm. Opened for review of the consumption pattern and to back the cold-start benchmark posted in Slack. Trade-off summary (cold-start benchmark on empty config): - Current (bundled .cjs, 231 KB single file): ~89 ms warm cache (median) - Proposed (npm install, 25 MB node_modules): ~180 ms warm cache (median) The ~90 ms gap is the module-resolution overhead of unbundled Node — paid once per pod start. Image build time also gets +`npm install` of ~25 MB (one extra layer), offset by deleting ~7000 lines of vendored installer script from this repo in a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index af6f40c8b8..5445b421cf 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -275,10 +275,17 @@ ENV PATH="/opt/techdocs-venv/bin:${PATH}" # RHIDP-4220 - make Konflux preflight and EC checks happy - [check-container] Create a directory named /licenses and include all relevant licensing COPY $EXTERNAL_SOURCE_NESTED/LICENSE /licenses/ -# Copy script to gather dynamic plugins; copy embedded dynamic plugins to root folder; fix permissions -# Bundled TypeScript .cjs (built from scripts/install-dynamic-plugins/src) replaces the previous Python implementation. -COPY $EXTERNAL_SOURCE_NESTED/scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs $EXTERNAL_SOURCE_NESTED/scripts/install-dynamic-plugins/install-dynamic-plugins.sh ./ -RUN chmod -R a+rx ./install-dynamic-plugins.* +# Install the dynamic-plugins installer from npm +# (@red-hat-developer-hub/cli-module-install-dynamic-plugins, formerly scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs) +# and expose it through the same `install-dynamic-plugins.sh` wrapper path so the Helm chart and +# Operator init-container spec keep working unchanged. +RUN npm install --no-fund --no-audit --prefix /opt/app-root/src \ + @red-hat-developer-hub/cli-module-install-dynamic-plugins@^0.1.0 \ + && printf '%s\n' \ + '#!/bin/sh' \ + 'exec node /opt/app-root/src/node_modules/@red-hat-developer-hub/cli-module-install-dynamic-plugins/bin/install-dynamic-plugins install "$1"' \ + > /opt/app-root/src/install-dynamic-plugins.sh \ + && chmod a+rx /opt/app-root/src/install-dynamic-plugins.sh # Fix for https://issues.redhat.com/browse/RHIDP-728 RUN mkdir -p /opt/app-root/src/.npm; chown -R 1001:1001 /opt/app-root/src/.npm From 82f7c0c5b59e2704c90534cf86b052cbc32b27e8 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Mon, 1 Jun 2026 12:27:59 -0300 Subject: [PATCH 02/12] fixup: address PR review on the install-dynamic-plugins Containerfile step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Install into /opt/dynamic-plugins-installer (its own dir, no package.json) instead of /opt/app-root/src so npm cannot honor the yarn workspace and perturb the production tree that `yarn workspaces focus` built at line 208. - Delegate the shim to `node_modules/.bin/install-dynamic-plugins` (the symlink npm creates from the package's bin field) instead of reaching into the package's internal layout. - Add `--no-save --omit=dev` so npm doesn't write a package-lock.json into the installer dir and doesn't fetch devDependencies. - Pin the installer to an exact version (0.1.0) so image builds are reproducible. - Add a build-time smoke check (`install-dynamic-plugins --help`) so a missing or renamed CLI entrypoint fails the image build instead of the init container at pod start. The hermetic-build concern (npm reaching the public registry when this Containerfile runs under Konflux with networking disabled) is acknowledged separately in the PR description — it's the real gating work and is not addressed by this commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index 5445b421cf..7f965e2050 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -276,15 +276,29 @@ ENV PATH="/opt/techdocs-venv/bin:${PATH}" COPY $EXTERNAL_SOURCE_NESTED/LICENSE /licenses/ # Install the dynamic-plugins installer from npm -# (@red-hat-developer-hub/cli-module-install-dynamic-plugins, formerly scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs) -# and expose it through the same `install-dynamic-plugins.sh` wrapper path so the Helm chart and -# Operator init-container spec keep working unchanged. -RUN npm install --no-fund --no-audit --prefix /opt/app-root/src \ - @red-hat-developer-hub/cli-module-install-dynamic-plugins@^0.1.0 \ +# (@red-hat-developer-hub/cli-module-install-dynamic-plugins, formerly +# scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs) into an +# isolated directory so npm cannot reach into /opt/app-root/src — that path +# is the yarn workspace root, and `npm install` there would honor `workspaces` +# and reshuffle the production tree built by `yarn workspaces focus`. +# An exact version is pinned so image builds stay reproducible; bumping it is +# a deliberate change tracked in this Containerfile. +# A smoke `--help` invocation runs immediately so a missing or moved CLI +# entrypoint fails the build instead of the pod at runtime. The shim +# delegates to npm's `.bin` symlink (not to the package's internal layout) +# and lives at the original `/opt/app-root/src/install-dynamic-plugins.sh` +# path so the Helm chart and Operator init-container spec keep working +# unchanged. +RUN INSTALLER_DIR=/opt/dynamic-plugins-installer \ + && mkdir -p "$INSTALLER_DIR" \ + && npm install --no-fund --no-audit --no-save --omit=dev \ + --prefix "$INSTALLER_DIR" \ + @red-hat-developer-hub/cli-module-install-dynamic-plugins@0.1.0 \ + && "$INSTALLER_DIR/node_modules/.bin/install-dynamic-plugins" --help >/dev/null \ && printf '%s\n' \ - '#!/bin/sh' \ - 'exec node /opt/app-root/src/node_modules/@red-hat-developer-hub/cli-module-install-dynamic-plugins/bin/install-dynamic-plugins install "$1"' \ - > /opt/app-root/src/install-dynamic-plugins.sh \ + '#!/bin/sh' \ + 'exec /opt/dynamic-plugins-installer/node_modules/.bin/install-dynamic-plugins install "$1"' \ + > /opt/app-root/src/install-dynamic-plugins.sh \ && chmod a+rx /opt/app-root/src/install-dynamic-plugins.sh # Fix for https://issues.redhat.com/browse/RHIDP-728 From b36a8ca1b9f77fd1ceee37a5559752c5a7995e33 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Mon, 1 Jun 2026 12:35:01 -0300 Subject: [PATCH 03/12] fixup: drop synthetic `install` subcommand from the shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unbundled cli-module variant ships a fast-path bin that calls the installer directly (bypassing @backstage/cli-node's runCliModule dispatch), so the published binary takes the dynamic-plugins-root as a positional without a subcommand prefix — matching the original CLI surface. Verified locally: $ /opt/dynamic-plugins-installer/node_modules/.bin/install-dynamic-plugins /dynamic-plugins-root exits 0 on an empty config. Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index 7f965e2050..d3f255696e 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -297,7 +297,7 @@ RUN INSTALLER_DIR=/opt/dynamic-plugins-installer \ && "$INSTALLER_DIR/node_modules/.bin/install-dynamic-plugins" --help >/dev/null \ && printf '%s\n' \ '#!/bin/sh' \ - 'exec /opt/dynamic-plugins-installer/node_modules/.bin/install-dynamic-plugins install "$1"' \ + 'exec /opt/dynamic-plugins-installer/node_modules/.bin/install-dynamic-plugins "$1"' \ > /opt/app-root/src/install-dynamic-plugins.sh \ && chmod a+rx /opt/app-root/src/install-dynamic-plugins.sh From a79119561942003e47ab297b24213fb436a1a942 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Wed, 3 Jun 2026 16:10:22 -0300 Subject: [PATCH 04/12] chore(install-dynamic-plugins): consume installer through dynamic-plugins yarn workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pivots #4908 from `RUN npm install …` to a plain yarn dependency in dynamic-plugins/package.json. The existing `yarn install --immutable` at line 151 of this Containerfile pulls the installer as part of the same yarn run that already brings in the rest of the dynamic-plugins deps, so the bin lands at /opt/app-root/src/dynamic-plugins/node_modules/.bin/install-dynamic-plugins and the final stage just writes a shim pointing there. Why yarn and not npm: scripts/local-hermeto-build.sh:213 already prefetches yarn deps for ./dynamic-plugins via cachi2/hermeto. No infra change is needed in this repo or in the midstream Konflux pipeline — the hermetic build "just works". The earlier npm-install approach required wiring npm into hermeto's fetch-deps, which is real work spanning two repos. Validated locally: yarn install of the unbundled cli-module variant (via a tarball of the fast-path build) produces .bin/install-dynamic-plugins and the smoke invocation on an empty dynamic-plugins.yaml exits 0. dynamic-plugins/yarn.lock is intentionally NOT regenerated in this commit — that has to happen against the published @red-hat-developer-hub/cli-module-install-dynamic-plugins@0.1.0 once redhat-developer/rhdh-plugins ships it. Marking the PR as Draft until then. Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 32 ++++++++++-------------------- dynamic-plugins/package.json | 3 +++ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index d3f255696e..656df00963 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -275,29 +275,19 @@ ENV PATH="/opt/techdocs-venv/bin:${PATH}" # RHIDP-4220 - make Konflux preflight and EC checks happy - [check-container] Create a directory named /licenses and include all relevant licensing COPY $EXTERNAL_SOURCE_NESTED/LICENSE /licenses/ -# Install the dynamic-plugins installer from npm -# (@red-hat-developer-hub/cli-module-install-dynamic-plugins, formerly -# scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs) into an -# isolated directory so npm cannot reach into /opt/app-root/src — that path -# is the yarn workspace root, and `npm install` there would honor `workspaces` -# and reshuffle the production tree built by `yarn workspaces focus`. -# An exact version is pinned so image builds stay reproducible; bumping it is -# a deliberate change tracked in this Containerfile. -# A smoke `--help` invocation runs immediately so a missing or moved CLI -# entrypoint fails the build instead of the pod at runtime. The shim -# delegates to npm's `.bin` symlink (not to the package's internal layout) -# and lives at the original `/opt/app-root/src/install-dynamic-plugins.sh` -# path so the Helm chart and Operator init-container spec keep working -# unchanged. -RUN INSTALLER_DIR=/opt/dynamic-plugins-installer \ - && mkdir -p "$INSTALLER_DIR" \ - && npm install --no-fund --no-audit --no-save --omit=dev \ - --prefix "$INSTALLER_DIR" \ - @red-hat-developer-hub/cli-module-install-dynamic-plugins@0.1.0 \ - && "$INSTALLER_DIR/node_modules/.bin/install-dynamic-plugins" --help >/dev/null \ +# Install the dynamic-plugins installer through yarn instead of fetching the +# vendored .cjs from this repo. The `@red-hat-developer-hub/cli-module-install-dynamic-plugins` +# dependency is declared in `dynamic-plugins/package.json` and pulled in by the +# existing `yarn install --immutable` at line 151. Hermeto already prefetches +# yarn deps for `./dynamic-plugins` (see scripts/local-hermeto-build.sh:213), +# so this works in the hermetic Konflux build with no infra change. +# A build-time `--help` invocation surfaces a missing or renamed CLI entrypoint +# as a build failure instead of a pod-start failure. +RUN INSTALLER_BIN=/opt/app-root/src/dynamic-plugins/node_modules/.bin/install-dynamic-plugins \ + && "$INSTALLER_BIN" --help >/dev/null \ && printf '%s\n' \ '#!/bin/sh' \ - 'exec /opt/dynamic-plugins-installer/node_modules/.bin/install-dynamic-plugins "$1"' \ + "exec $INSTALLER_BIN \"\$1\"" \ > /opt/app-root/src/install-dynamic-plugins.sh \ && chmod a+rx /opt/app-root/src/install-dynamic-plugins.sh diff --git a/dynamic-plugins/package.json b/dynamic-plugins/package.json index fb5d306d12..b3bb91c3df 100644 --- a/dynamic-plugins/package.json +++ b/dynamic-plugins/package.json @@ -22,6 +22,9 @@ "wrappers/*" ] }, + "dependencies": { + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.1.0" + }, "devDependencies": { "@backstage/cli": "0.36.0", "@backstage/cli-defaults": "0.1.0", From 78952d9431eea3549492f48d21dac49022ef66a9 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Fri, 5 Jun 2026 16:04:31 -0300 Subject: [PATCH 05/12] fixup: shim uses install subcommand + forwards all positional args The rhdh-plugins side reverted the bin shim to the standard `runCliModule` dispatch (redhat-developer/rhdh-plugins#3246 review), so the installed bin now expects an `install` subcommand. Updating the wrapper to match and to forward `"$@"` instead of `"$1"` so any extra positional argument the Helm chart or Operator passes is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index 656df00963..671ffd3dc1 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -287,7 +287,7 @@ RUN INSTALLER_BIN=/opt/app-root/src/dynamic-plugins/node_modules/.bin/install-dy && "$INSTALLER_BIN" --help >/dev/null \ && printf '%s\n' \ '#!/bin/sh' \ - "exec $INSTALLER_BIN \"\$1\"" \ + "exec $INSTALLER_BIN install \"\$@\"" \ > /opt/app-root/src/install-dynamic-plugins.sh \ && chmod a+rx /opt/app-root/src/install-dynamic-plugins.sh From f8ce097189e94d3b245dece024c378608960d81a Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Wed, 10 Jun 2026 19:28:43 -0300 Subject: [PATCH 06/12] chore: pin cli-module-install-dynamic-plugins to 0.2.0 and regen yarn.lock redhat-developer/rhdh-plugins#3246 merged and the changesets release published @red-hat-developer-hub/cli-module-install-dynamic-plugins as 0.2.0 (the changesets minor bump rolled past 0.1.0 because of the existing `0.0.0` workspace version). Updates the dynamic-plugins/package.json pin to 0.2.0 and regenerates dynamic-plugins/yarn.lock so `yarn install --immutable` (line 151 of the Containerfile) and the hermeto yarn prefetch at scripts/local-hermeto-build.sh:213 both resolve the package. The Build Image GitHub Actions job should now go green. Verified locally: yarn install resolves the package, the bin symlink lands at dynamic-plugins/node_modules/.bin/install-dynamic-plugins, and invoking it with `install ` exits 0 on an empty config (going through the standard @backstage/cli-node runCliModule dispatch like the final Containerfile shim does). Co-Authored-By: Claude Opus 4.7 (1M context) --- dynamic-plugins/package.json | 2 +- dynamic-plugins/yarn.lock | 100 +++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/dynamic-plugins/package.json b/dynamic-plugins/package.json index b3bb91c3df..55a63cd3b1 100644 --- a/dynamic-plugins/package.json +++ b/dynamic-plugins/package.json @@ -23,7 +23,7 @@ ] }, "dependencies": { - "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.1.0" + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.2.0" }, "devDependencies": { "@backstage/cli": "0.36.0", diff --git a/dynamic-plugins/yarn.lock b/dynamic-plugins/yarn.lock index 2fdc89d0fd..43764816af 100644 --- a/dynamic-plugins/yarn.lock +++ b/dynamic-plugins/yarn.lock @@ -3680,6 +3680,18 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-common@npm:^0.2.2": + version: 0.2.2 + resolution: "@backstage/cli-common@npm:0.2.2" + dependencies: + "@backstage/errors": "npm:^1.3.1" + cross-spawn: "npm:^7.0.3" + global-agent: "npm:^3.0.0" + undici: "npm:^7.24.5" + checksum: 10c0/b83af32489662c1af7009d4a4965e5251cbe7e6bd12e5e14f2951e25d953872cba5802d9e6b780d0c93be95cdd9712e3ababeb2e97e34632b5cda6729f92a46d + languageName: node + linkType: hard + "@backstage/cli-defaults@npm:0.1.0, @backstage/cli-defaults@npm:^0.1.0": version: 0.1.0 resolution: "@backstage/cli-defaults@npm:0.1.0" @@ -4070,6 +4082,37 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-node@npm:^0.3.2": + version: 0.3.2 + resolution: "@backstage/cli-node@npm:0.3.2" + dependencies: + "@backstage/cli-common": "npm:^0.2.2" + "@backstage/errors": "npm:^1.3.1" + "@backstage/types": "npm:^1.2.2" + "@manypkg/get-packages": "npm:^1.1.3" + "@yarnpkg/lockfile": "npm:^1.1.0" + "@yarnpkg/parsers": "npm:^3.0.0" + chalk: "npm:^4.0.0" + commander: "npm:^12.0.0" + fs-extra: "npm:^11.2.0" + keytar: "npm:^7.9.0" + pirates: "npm:^4.0.6" + proper-lockfile: "npm:^4.1.2" + semver: "npm:^7.5.3" + yaml: "npm:^2.0.0" + zod: "npm:^3.25.76 || ^4.0.0" + peerDependencies: + "@swc/core": ^1.15.6 + dependenciesMeta: + keytar: + optional: true + peerDependenciesMeta: + "@swc/core": + optional: true + checksum: 10c0/bcbf7097edeaf7ab4415b499126ab2592b889f89e13d65ff6b53f790dd024a33f1b386a08cd13346e766660c3e9486cb65ff96a2ec8300059c3837fa972b7ae5 + languageName: node + linkType: hard + "@backstage/cli@npm:0.36.0": version: 0.36.0 resolution: "@backstage/cli@npm:0.36.0" @@ -4437,6 +4480,16 @@ __metadata: languageName: node linkType: hard +"@backstage/errors@npm:^1.3.1": + version: 1.3.1 + resolution: "@backstage/errors@npm:1.3.1" + dependencies: + "@backstage/types": "npm:^1.2.2" + serialize-error: "npm:^8.0.1" + checksum: 10c0/b342551f5337f17bcc6d412868f6274509d657cc6037fbb5af96d42156c24c2419e72fd711136e42d05245bd4e5c5d8fe9cd54f89f260d9285a073c28cca0261 + languageName: node + linkType: hard + "@backstage/eslint-plugin@npm:^0.2.2": version: 0.2.2 resolution: "@backstage/eslint-plugin@npm:0.2.2" @@ -11448,6 +11501,20 @@ __metadata: languageName: node linkType: hard +"@red-hat-developer-hub/cli-module-install-dynamic-plugins@npm:0.2.0": + version: 0.2.0 + resolution: "@red-hat-developer-hub/cli-module-install-dynamic-plugins@npm:0.2.0" + dependencies: + "@backstage/cli-node": "npm:^0.3.2" + cleye: "npm:^2.6.0" + tar: "npm:^7.5.13" + yaml: "npm:^2.8.2" + bin: + cli-module-install-dynamic-plugins: bin/install-dynamic-plugins + checksum: 10c0/4a935c1a6b0c2df7a49bfc68891117f0c0b0b321622b332bf892fc57d524565f797ebf702fc5ce2403beb5babfb11f4417874cd046e27b6cff57e48603ffe748 + languageName: node + linkType: hard + "@redis/client@npm:^1.6.0": version: 1.6.1 resolution: "@redis/client@npm:1.6.1" @@ -17359,6 +17426,16 @@ __metadata: languageName: node linkType: hard +"cleye@npm:^2.6.0": + version: 2.6.0 + resolution: "cleye@npm:2.6.0" + dependencies: + terminal-columns: "npm:^2.0.0" + type-flag: "npm:^4.1.0" + checksum: 10c0/30bf1f11dec3ef191c79fecb94f4edf53507d60afb5aeb1736608c820315ef9b68451ac822475d313e17d8ab9ee79b614eb09a2fd7c5c3e7d8f4477a186793d7 + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -19420,6 +19497,7 @@ __metadata: dependencies: "@backstage/cli": "npm:0.36.0" "@backstage/cli-defaults": "npm:0.1.0" + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "npm:0.2.0" turbo: "npm:2.9.6" dependenciesMeta: keytar: @@ -31387,6 +31465,19 @@ __metadata: languageName: node linkType: hard +"tar@npm:^7.5.13": + version: 7.5.16 + resolution: "tar@npm:7.5.16" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c + languageName: node + linkType: hard + "tar@npm:^7.5.4, tar@npm:^7.5.6": version: 7.5.9 resolution: "tar@npm:7.5.9" @@ -33791,6 +33882,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.8.2": + version: 2.9.0 + resolution: "yaml@npm:2.9.0" + bin: + yaml: bin.mjs + checksum: 10c0/f340718df45e97a9551b9bf9dac61c80050bc464513b710debfb5067c380c8472e3b67809cffacb4ab5ffb5e66ef9310816c88b05f371cec60abfedd8c88e0a2 + languageName: node + linkType: hard + "yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" From bfdfe8fcd477d1dcebe29085e5d46ba7d72252f5 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Wed, 10 Jun 2026 19:34:22 -0300 Subject: [PATCH 07/12] chore: drop vendored scripts/install-dynamic-plugins/ now that it's on npm The dynamic-plugins installer is consumed from @red-hat-developer-hub/cli-module-install-dynamic-plugins via the yarn dependency declared in dynamic-plugins/package.json, so the vendored copy under scripts/install-dynamic-plugins/ is no longer used by the Containerfile and can be removed. Deletes: - scripts/install-dynamic-plugins/ (46 files, ~7000 lines) Cleans up the references that pointed at it: - .gitattributes: drop linguist-generated marker for the bundled .cjs - .dockerignore: drop the dist/ exceptions that kept the bundle in the build context - codecov.yml: drop the install-dynamic-plugins flag (used to track Python coverage from the pre-TS era) - .github/workflows/pr.yaml: drop the vitest + bundle-up-to-date checks - .github/workflows/coverage-baseline.yml: drop the pytest baseline - docs/dynamic-plugins/installing-plugins.md: link to the rhdh-plugins source instead of the vendored path - docs/coverage/e2e-rhdh.md: drop the vitest row from the coverage table - .rulesync/rules/ci-e2e-testing.md + the derived files under .claude/, .cursor/, .opencode/: update the installer link Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/memories/ci-e2e-testing.md | 2 +- .claude/rules/ci-e2e-testing.md | 2 +- .cursor/rules/ci-e2e-testing.mdc | 2 +- .dockerignore | 2 - .gitattributes | 1 - .github/workflows/coverage-baseline.yml | 14 - .github/workflows/pr.yaml | 36 - .opencode/memories/ci-e2e-testing.md | 2 +- .rulesync/rules/ci-e2e-testing.md | 2 +- codecov.yml | 4 - docs/coverage/e2e-rhdh.md | 1 - docs/dynamic-plugins/installing-plugins.md | 2 +- scripts/install-dynamic-plugins/.gitignore | 11 - .../install-dynamic-plugins/.prettierignore | 5 - .../install-dynamic-plugins/.prettierrc.js | 13 - scripts/install-dynamic-plugins/README.md | 111 - .../__tests__/concurrency.test.ts | 106 - .../__tests__/extra-catalog-index.test.ts | 267 -- .../__tests__/finalize-install.test.ts | 84 - .../__tests__/image-resolver.test.ts | 36 - .../__tests__/integrity.test.ts | 63 - .../__tests__/lock-file.test.ts | 46 - .../__tests__/merger-pre-merge.test.ts | 223 -- .../__tests__/merger.test.ts | 92 - .../__tests__/npm-key.test.ts | 48 - .../__tests__/oci-key.test.ts | 197 -- .../__tests__/plugin-hash.test.ts | 56 - .../__tests__/skopeo.test.ts | 73 - .../__tests__/tar-extract.test.ts | 173 -- .../__tests__/types.test.ts | 126 - .../dist/install-dynamic-plugins.cjs | 178 -- .../esbuild.config.mjs | 19 - .../install-dynamic-plugins.sh | 18 - .../install-dynamic-plugins/package-lock.json | 2664 ----------------- scripts/install-dynamic-plugins/package.json | 34 - .../src/catalog-index.ts | 282 -- .../src/concurrency.ts | 100 - scripts/install-dynamic-plugins/src/errors.ts | 10 - .../src/image-cache.ts | 105 - .../src/image-resolver.ts | 27 - scripts/install-dynamic-plugins/src/index.ts | 540 ---- .../src/installer-npm.ts | 128 - .../src/installer-oci.ts | 120 - .../install-dynamic-plugins/src/integrity.ts | 103 - .../install-dynamic-plugins/src/lock-file.ts | 87 - scripts/install-dynamic-plugins/src/log.ts | 3 - scripts/install-dynamic-plugins/src/merger.ts | 509 ---- .../install-dynamic-plugins/src/npm-key.ts | 67 - .../install-dynamic-plugins/src/oci-key.ts | 126 - .../src/plugin-hash.ts | 110 - scripts/install-dynamic-plugins/src/run.ts | 39 - scripts/install-dynamic-plugins/src/skopeo.ts | 90 - .../src/tar-extract.ts | 172 -- scripts/install-dynamic-plugins/src/types.ts | 143 - scripts/install-dynamic-plugins/src/util.ts | 50 - scripts/install-dynamic-plugins/src/which.ts | 26 - scripts/install-dynamic-plugins/tsconfig.json | 16 - .../install-dynamic-plugins/vitest.config.ts | 16 - 58 files changed, 6 insertions(+), 7576 deletions(-) delete mode 100644 scripts/install-dynamic-plugins/.gitignore delete mode 100644 scripts/install-dynamic-plugins/.prettierignore delete mode 100644 scripts/install-dynamic-plugins/.prettierrc.js delete mode 100644 scripts/install-dynamic-plugins/README.md delete mode 100644 scripts/install-dynamic-plugins/__tests__/concurrency.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/extra-catalog-index.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/finalize-install.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/image-resolver.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/integrity.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/lock-file.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/merger-pre-merge.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/merger.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/npm-key.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/oci-key.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/plugin-hash.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/skopeo.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/tar-extract.test.ts delete mode 100644 scripts/install-dynamic-plugins/__tests__/types.test.ts delete mode 100644 scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs delete mode 100644 scripts/install-dynamic-plugins/esbuild.config.mjs delete mode 100755 scripts/install-dynamic-plugins/install-dynamic-plugins.sh delete mode 100644 scripts/install-dynamic-plugins/package-lock.json delete mode 100644 scripts/install-dynamic-plugins/package.json delete mode 100644 scripts/install-dynamic-plugins/src/catalog-index.ts delete mode 100644 scripts/install-dynamic-plugins/src/concurrency.ts delete mode 100644 scripts/install-dynamic-plugins/src/errors.ts delete mode 100644 scripts/install-dynamic-plugins/src/image-cache.ts delete mode 100644 scripts/install-dynamic-plugins/src/image-resolver.ts delete mode 100644 scripts/install-dynamic-plugins/src/index.ts delete mode 100644 scripts/install-dynamic-plugins/src/installer-npm.ts delete mode 100644 scripts/install-dynamic-plugins/src/installer-oci.ts delete mode 100644 scripts/install-dynamic-plugins/src/integrity.ts delete mode 100644 scripts/install-dynamic-plugins/src/lock-file.ts delete mode 100644 scripts/install-dynamic-plugins/src/log.ts delete mode 100644 scripts/install-dynamic-plugins/src/merger.ts delete mode 100644 scripts/install-dynamic-plugins/src/npm-key.ts delete mode 100644 scripts/install-dynamic-plugins/src/oci-key.ts delete mode 100644 scripts/install-dynamic-plugins/src/plugin-hash.ts delete mode 100644 scripts/install-dynamic-plugins/src/run.ts delete mode 100644 scripts/install-dynamic-plugins/src/skopeo.ts delete mode 100644 scripts/install-dynamic-plugins/src/tar-extract.ts delete mode 100644 scripts/install-dynamic-plugins/src/types.ts delete mode 100644 scripts/install-dynamic-plugins/src/util.ts delete mode 100644 scripts/install-dynamic-plugins/src/which.ts delete mode 100644 scripts/install-dynamic-plugins/tsconfig.json delete mode 100644 scripts/install-dynamic-plugins/vitest.config.ts diff --git a/.claude/memories/ci-e2e-testing.md b/.claude/memories/ci-e2e-testing.md index f93be9bd26..d6bc6e4413 100644 --- a/.claude/memories/ci-e2e-testing.md +++ b/.claude/memories/ci-e2e-testing.md @@ -490,7 +490,7 @@ brew install gnu-sed - [Cluster Login Script](.ci/pipelines/ocp-cluster-claim-login.sh) - [Test Reporting Script](.ci/pipelines/reporting.sh) - [Environment Variables](.ci/pipelines/env_variables.sh) -- [Dynamic Plugin Installer](scripts/install-dynamic-plugins) +- [Dynamic Plugin Installer](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/install-dynamic-plugins) ## Test Configuration Files (Config Maps) diff --git a/.claude/rules/ci-e2e-testing.md b/.claude/rules/ci-e2e-testing.md index b83edead86..2c54766ef6 100644 --- a/.claude/rules/ci-e2e-testing.md +++ b/.claude/rules/ci-e2e-testing.md @@ -475,7 +475,7 @@ brew install gnu-sed - [Cluster Login Script](.ci/pipelines/ocp-cluster-claim-login.sh) - [Test Reporting Script](.ci/pipelines/reporting.sh) - [Environment Variables](.ci/pipelines/env_variables.sh) -- [Dynamic Plugin Installer](scripts/install-dynamic-plugins) +- [Dynamic Plugin Installer](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/install-dynamic-plugins) ## Test Configuration Files (Config Maps) diff --git a/.cursor/rules/ci-e2e-testing.mdc b/.cursor/rules/ci-e2e-testing.mdc index 8f382de47f..f25c163513 100644 --- a/.cursor/rules/ci-e2e-testing.mdc +++ b/.cursor/rules/ci-e2e-testing.mdc @@ -478,7 +478,7 @@ brew install gnu-sed - [Cluster Login Script](.ci/pipelines/ocp-cluster-claim-login.sh) - [Test Reporting Script](.ci/pipelines/reporting.sh) - [Environment Variables](.ci/pipelines/env_variables.sh) -- [Dynamic Plugin Installer](scripts/install-dynamic-plugins) +- [Dynamic Plugin Installer](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/install-dynamic-plugins) ## Test Configuration Files (Config Maps) diff --git a/.dockerignore b/.dockerignore index bca2893005..98d2228c93 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,8 +3,6 @@ **/dist # Re-include the bundled install-dynamic-plugins entry point — copied by the # runtime stage of build/containerfiles/Containerfile. -!scripts/install-dynamic-plugins/dist -!scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs **/node_modules plugins !plugins/auth-backend-module-oidc-provider diff --git a/.gitattributes b/.gitattributes index 684041449d..c74f76eb0d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -74,7 +74,6 @@ AUTHORS eol=lf # # Generated bundles — collapsed in GitHub diffs, excluded from language stats -scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs linguist-generated=true # (binary is a macro for -eol=lf -diff) *.png binary diff --git a/.github/workflows/coverage-baseline.yml b/.github/workflows/coverage-baseline.yml index 40cad18702..3ebb7251cf 100644 --- a/.github/workflows/coverage-baseline.yml +++ b/.github/workflows/coverage-baseline.yml @@ -99,17 +99,3 @@ jobs: directory: ${{ runner.temp }}/test-results fail_ci_if_error: false - - name: Install Python dependencies - run: pip install -r python/requirements-dev.txt -r python/requirements.txt - - - name: Run Python tests with coverage - run: pytest scripts/install-dynamic-plugins/test_install-dynamic-plugins.py -v --cov=scripts/install-dynamic-plugins --cov-report=lcov:coverage-install-dynamic-plugins.lcov - - - name: Upload install-dynamic-plugins coverage to Codecov - if: ${{ !cancelled() }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: coverage-install-dynamic-plugins.lcov - flags: install-dynamic-plugins - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 1c920f572b..06081a401d 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -145,23 +145,6 @@ jobs: ln -s ${{ runner.temp }}/jest-junit/node_modules/jest-junit node_modules/jest-junit mkdir -p ${{ runner.temp }}/test-results - # Runs before the monorepo test step so the JUnit report it writes - # ends up in $RUNNER_TEMP/test-results/ in time for the Codecov - # 'Upload test results' step below to pick it up alongside any - # jest-junit reports produced by 'yarn run test --affected'. When the - # PR only touches non-workspace files, 'yarn ... --affected' emits no - # reports and this one is what the upload finds. - - name: Test install-dynamic-plugins - if: ${{ steps.check-image.outputs.is_skipped != 'true' }} - working-directory: scripts/install-dynamic-plugins - run: | - npm ci --no-audit --no-fund - npm run tsc - npx vitest run --coverage \ - --reporter=default \ - --reporter=junit \ - --outputFile.junit="$RUNNER_TEMP/test-results/install-dynamic-plugins.junit.xml" - - name: Run tests if: ${{ steps.check-image.outputs.is_skipped != 'true' }} id: tests @@ -188,25 +171,6 @@ jobs: directory: ${{ runner.temp }}/test-results fail_ci_if_error: false - - name: Upload install-dynamic-plugins coverage to Codecov - if: ${{ steps.check-image.outputs.is_skipped != 'true' && !cancelled() }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - directory: scripts/install-dynamic-plugins/coverage - flags: install-dynamic-plugins - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false - - - name: Verify install-dynamic-plugins bundle is up to date - if: ${{ steps.check-image.outputs.is_skipped != 'true' }} - working-directory: scripts/install-dynamic-plugins - run: | - npm run build - if ! git diff --quiet -- dist/install-dynamic-plugins.cjs; then - echo "ERROR: dist/install-dynamic-plugins.cjs is out of date — run 'npm run build' and commit the result." >&2 - exit 1 - fi - - name: Change directory to dynamic-plugins if: ${{ steps.check-image.outputs.is_skipped != 'true' }} run: cd ./dynamic-plugins diff --git a/.opencode/memories/ci-e2e-testing.md b/.opencode/memories/ci-e2e-testing.md index b83edead86..2c54766ef6 100644 --- a/.opencode/memories/ci-e2e-testing.md +++ b/.opencode/memories/ci-e2e-testing.md @@ -475,7 +475,7 @@ brew install gnu-sed - [Cluster Login Script](.ci/pipelines/ocp-cluster-claim-login.sh) - [Test Reporting Script](.ci/pipelines/reporting.sh) - [Environment Variables](.ci/pipelines/env_variables.sh) -- [Dynamic Plugin Installer](scripts/install-dynamic-plugins) +- [Dynamic Plugin Installer](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/install-dynamic-plugins) ## Test Configuration Files (Config Maps) diff --git a/.rulesync/rules/ci-e2e-testing.md b/.rulesync/rules/ci-e2e-testing.md index 99ab1bc605..c5ddfa6c19 100644 --- a/.rulesync/rules/ci-e2e-testing.md +++ b/.rulesync/rules/ci-e2e-testing.md @@ -481,7 +481,7 @@ brew install gnu-sed - [Cluster Login Script](.ci/pipelines/ocp-cluster-claim-login.sh) - [Test Reporting Script](.ci/pipelines/reporting.sh) - [Environment Variables](.ci/pipelines/env_variables.sh) -- [Dynamic Plugin Installer](scripts/install-dynamic-plugins) +- [Dynamic Plugin Installer](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/install-dynamic-plugins) ## Test Configuration Files (Config Maps) diff --git a/codecov.yml b/codecov.yml index d01e52090a..4bf93a58d3 100644 --- a/codecov.yml +++ b/codecov.yml @@ -93,10 +93,6 @@ flag_management: - packages/plugin-utils/src/** - dynamic-plugins/_utils/src/** carryforward: true - - name: install-dynamic-plugins - paths: - - scripts/install-dynamic-plugins/*.py - carryforward: true # Paths intentionally mirror the rhdh flag — both measure the same source # from different test types. Isolation relies on --flag at upload time, # not on paths. If an upload omits --flag, data lands under "default". diff --git a/docs/coverage/e2e-rhdh.md b/docs/coverage/e2e-rhdh.md index aafebdcbf0..6ecf0f764c 100644 --- a/docs/coverage/e2e-rhdh.md +++ b/docs/coverage/e2e-rhdh.md @@ -160,7 +160,6 @@ parallel workers and retries never overwrite each other's output. | Flag | Source | Scope | |---|---|---| | `rhdh` | Jest (`packages/*`, `plugins/*`) | Unit / integration | -| `install-dynamic-plugins` | Vitest (`scripts/install-dynamic-plugins`) | Install script unit tests | | `rhdh-e2e-frontend` (new) | Playwright `page.coverage` | E2E frontend — this doc | | `rhdh-e2e-full` (future) | Instrumented showcase image variant | E2E frontend, higher fidelity (RHIDP-13244) | | `overlays-e2e-` (future) | CoverPort Tekton | Upstream plugins via OCI (RHIDP-11866) | diff --git a/docs/dynamic-plugins/installing-plugins.md b/docs/dynamic-plugins/installing-plugins.md index 5fd026902b..bbaea0d48b 100644 --- a/docs/dynamic-plugins/installing-plugins.md +++ b/docs/dynamic-plugins/installing-plugins.md @@ -76,7 +76,7 @@ plugins: ### Catalog Entities Extraction -When the `CATALOG_INDEX_IMAGE` is set and the index image contains a `catalog-entities/marketplace` directory, the [`install-dynamic-plugins`](../../scripts/install-dynamic-plugins) installer will automatically extract these catalog entities to a configurable location. +When the `CATALOG_INDEX_IMAGE` is set and the index image contains a `catalog-entities/marketplace` directory, the [`install-dynamic-plugins`](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/install-dynamic-plugins) installer will automatically extract these catalog entities to a configurable location. The extraction destination is governed by the `CATALOG_ENTITIES_EXTRACT_DIR` environment variable: diff --git a/scripts/install-dynamic-plugins/.gitignore b/scripts/install-dynamic-plugins/.gitignore deleted file mode 100644 index 6ca5143a1a..0000000000 --- a/scripts/install-dynamic-plugins/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules/ -build/ -coverage/ -*.log -.yarn/ - -# Re-include the dist/ directory (root .gitignore excludes 'dist'). Only the -# committed bundle should be tracked; anything else under dist/ stays ignored. -!dist/ -dist/* -!dist/install-dynamic-plugins.cjs diff --git a/scripts/install-dynamic-plugins/.prettierignore b/scripts/install-dynamic-plugins/.prettierignore deleted file mode 100644 index 233e7c547e..0000000000 --- a/scripts/install-dynamic-plugins/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -coverage/ -build/ -dist/ -package-lock.json diff --git a/scripts/install-dynamic-plugins/.prettierrc.js b/scripts/install-dynamic-plugins/.prettierrc.js deleted file mode 100644 index f998333800..0000000000 --- a/scripts/install-dynamic-plugins/.prettierrc.js +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-check - -/** - * Only the options that diverge from Prettier 3 defaults are listed here — - * matches the Backstage / RHDH conventions used elsewhere in this repo. - * - * @type {import("prettier").Config} - */ -module.exports = { - printWidth: 100, - singleQuote: true, - arrowParens: 'avoid', -}; diff --git a/scripts/install-dynamic-plugins/README.md b/scripts/install-dynamic-plugins/README.md deleted file mode 100644 index 7db90023c0..0000000000 --- a/scripts/install-dynamic-plugins/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# install-dynamic-plugins - -Init-container utility that downloads, extracts, and configures RHDH dynamic plugins listed in a `dynamic-plugins.yaml` file. - -This package replaces the previous Python implementation (`install-dynamic-plugins.py`) with a TypeScript/Node.js implementation. The runtime contract — input config, output `app-config.dynamic-plugins.yaml`, on-disk layout, hash-based change detection, lock file — is **unchanged**. - -## How it runs in the RHDH container - -The container's init container invokes the wrapper: - -```sh -./install-dynamic-plugins.sh /dynamic-plugins-root -``` - -The wrapper executes the bundled CommonJS entry point with Node.js: - -```sh -exec node install-dynamic-plugins.cjs "$1" -``` - -Both files live at `/opt/app-root/src/` inside the runtime image. Node.js 22 is already present (it runs the Backstage backend), and `skopeo` is installed for OCI inspection — no new system packages are required. - -## Architecture - -``` -src/ -├── index.ts # main() — argv + orchestration of the full install flow -├── log.ts # uniform stdout logger -├── errors.ts # InstallException -├── types.ts # PluginSpec / Plugin / PluginMap / PullPolicy + constants -├── util.ts # shared helpers (fileExists, isInside, isPlainObject, tar filters) -├── run.ts # subprocess wrapper with structured errors -├── concurrency.ts # Semaphore + mapConcurrent + getWorkers() -├── which.ts # PATH lookup (no `which` dep) -├── skopeo.ts # Skopeo wrapper with promise-based inspect cache -├── image-resolver.ts # registry.access.redhat.com → quay.io fallback -├── image-cache.ts # OciImageCache — share OCI tarballs across plugins -├── tar-extract.ts # streaming OCI / NPM extraction with security checks -├── npm-key.ts # NPM package-spec parsing -├── oci-key.ts # OCI package-spec parsing + {{inherit}} + auto-path -├── integrity.ts # streaming SRI integrity verification -├── merger.ts # plugin merging + deep-merge with conflict detection -├── plugin-hash.ts # hash for change-detection ("already installed?") -├── installer-oci.ts # install one OCI plugin -├── installer-npm.ts # install one NPM (or local) plugin -├── catalog-index.ts # CATALOG_INDEX_IMAGE extraction -└── lock-file.ts # exclusive lock + SIGTERM cleanup -``` - -### Concurrency strategy (resource-conscious) - -OCI plugin downloads are parallelized via `mapConcurrent`. NPM `npm pack` calls stay sequential because the upstream npm registry throttles parallel fetches. - -The default worker count comes from `getWorkers()`: - -``` -Math.max(1, Math.min(Math.floor(availableParallelism() / 2), 6)) -``` - -`availableParallelism()` honours cgroup CPU limits, so init containers in OpenShift won't try to use 16 workers on a 0.5 CPU pod. Override with `DYNAMIC_PLUGINS_WORKERS=`. - -### Memory budget - -All tar extraction is streaming via `node-tar` — large layers never load into RAM. SHA verification streams chunks through `node:crypto`. A typical 10-plugin run sits around 20–80 MB peak RSS, comfortably below an init-container memory limit of 512 Mi. - -### Security checks (parity with the previous Python script) - -| Check | Source | -| --------------------------------------------------------------------- | ------------------------------------ | -| Path-traversal in plugin path (`..`, absolute paths) | `tar-extract.ts` | -| Per-entry size cap (zip bomb) — `MAX_ENTRY_SIZE`, default 20 MB | `tar-extract.ts`, `catalog-index.ts` | -| Symlink / hardlink target must stay inside destination | `tar-extract.ts` | -| Reject device files / FIFOs / unknown entry types | `tar-extract.ts` | -| `package/` prefix enforced for NPM tarballs | `tar-extract.ts` | -| SRI integrity verification (`sha256` / `sha384` / `sha512`) | `integrity.ts` | -| Registry fallback: `registry.access.redhat.com/rhdh` → `quay.io/rhdh` | `image-resolver.ts` | - -## Environment variables - -| Variable | Default | Purpose | -| --------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------ | -| `MAX_ENTRY_SIZE` | `20000000` | Per-entry byte limit when extracting tarballs | -| `SKIP_INTEGRITY_CHECK` | `false` | When `true`, skip the SRI integrity check for remote NPM packages | -| `CATALOG_INDEX_IMAGE` | _(unset)_ | OCI image to extract `dynamic-plugins.default.yaml` and catalog entities from | -| `CATALOG_ENTITIES_EXTRACT_DIR` | `$TMPDIR/extensions` | Where to extract `catalog-entities/` from the catalog-index image | -| `DYNAMIC_PLUGINS_WORKERS` | `auto` | Worker count override for parallel OCI downloads (`auto` uses `availableParallelism()/2`, capped at 6) | -| `DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS` | `600000` (10 min) | Max time to wait for the lock file before aborting with an error | - -## Development - -```sh -npm install -npm run tsc # type-check -npm test # Jest unit tests (105 tests) -npm run build # produce dist/install-dynamic-plugins.cjs -``` - -`dist/install-dynamic-plugins.cjs` **is** committed to the repo (consumed directly by the Containerfile, similar to `.yarn/releases/yarn-*.cjs`). The PR check verifies the bundle is up to date relative to the source. - -## Testing in CI - -The CI workflow (`.github/workflows/pr.yaml`) runs: - -1. `npm install && npm run tsc && npm test` — type check + Jest unit tests -2. `npm run build` and a `git diff` check on the committed `dist/install-dynamic-plugins.cjs` - -## Compatibility notes - -- The **input contract** matches the previous Python script exactly: same `dynamic-plugins.yaml` schema (`includes`, `plugins`, `package`, `pluginConfig`, `disabled`, `pullPolicy`, `forceDownload`, `integrity`). -- The **output contract** matches: same `app-config.dynamic-plugins.yaml`, same plugin directory layout, same `dynamic-plugin-config.hash` / `dynamic-plugin-image.hash` files. -- `{{inherit}}` semantics, OCI path auto-detection, registry fallback, integrity algorithms, lock-file behaviour are preserved. diff --git a/scripts/install-dynamic-plugins/__tests__/concurrency.test.ts b/scripts/install-dynamic-plugins/__tests__/concurrency.test.ts deleted file mode 100644 index 48acfd9e7b..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/concurrency.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { getNpmWorkers, getWorkers, mapConcurrent, Semaphore } from '../src/concurrency'; - -describe('Semaphore', () => { - it('bounds the number of concurrent holders', async () => { - const sem = new Semaphore(2); - let inFlight = 0; - let peak = 0; - const work = async () => { - await sem.acquire(); - inFlight++; - peak = Math.max(peak, inFlight); - await new Promise(r => setTimeout(r, 5)); - inFlight--; - sem.release(); - }; - await Promise.all(Array.from({ length: 8 }, work)); - expect(peak).toBeLessThanOrEqual(2); - }); - - it('rejects a max < 1', () => { - expect(() => new Semaphore(0)).toThrow(RangeError); - }); -}); - -describe('mapConcurrent', () => { - it('captures both successes and failures without cancelling peers', async () => { - const results = await mapConcurrent([1, 2, 3, 4], 2, async n => { - if (n === 2) throw new Error('boom'); - return n * 2; - }); - expect(results).toHaveLength(4); - expect(results.filter(r => r.ok).map(r => r.ok && r.value)).toEqual([2, 6, 8]); - expect(results.filter(r => !r.ok).map(r => !r.ok && r.error.message)).toEqual(['boom']); - }); - - it('respects the concurrency limit', async () => { - let inFlight = 0; - let peak = 0; - await mapConcurrent( - Array.from({ length: 20 }, (_, i) => i), - 4, - async () => { - inFlight++; - peak = Math.max(peak, inFlight); - await new Promise(r => setTimeout(r, 2)); - inFlight--; - }, - ); - expect(peak).toBeLessThanOrEqual(4); - }); -}); - -describe('getWorkers', () => { - const originalEnv = process.env.DYNAMIC_PLUGINS_WORKERS; - afterEach(() => { - if (originalEnv === undefined) delete process.env.DYNAMIC_PLUGINS_WORKERS; - else process.env.DYNAMIC_PLUGINS_WORKERS = originalEnv; - }); - - it('honours an explicit worker count', () => { - process.env.DYNAMIC_PLUGINS_WORKERS = '3'; - expect(getWorkers()).toBe(3); - }); - - it('clamps non-numeric values to 1', () => { - process.env.DYNAMIC_PLUGINS_WORKERS = 'banana'; - expect(getWorkers()).toBe(1); - }); - - it('auto-picks a value between 1 and 6', () => { - process.env.DYNAMIC_PLUGINS_WORKERS = 'auto'; - const w = getWorkers(); - expect(w).toBeGreaterThanOrEqual(1); - expect(w).toBeLessThanOrEqual(6); - }); -}); - -describe('getNpmWorkers', () => { - const originalEnv = process.env.DYNAMIC_PLUGINS_NPM_WORKERS; - afterEach(() => { - if (originalEnv === undefined) delete process.env.DYNAMIC_PLUGINS_NPM_WORKERS; - else process.env.DYNAMIC_PLUGINS_NPM_WORKERS = originalEnv; - }); - - it('honours an explicit NPM worker count', () => { - process.env.DYNAMIC_PLUGINS_NPM_WORKERS = '2'; - expect(getNpmWorkers()).toBe(2); - }); - - it('falls back to 1 for non-numeric values', () => { - process.env.DYNAMIC_PLUGINS_NPM_WORKERS = 'banana'; - expect(getNpmWorkers()).toBe(1); - }); - - it('auto-picks a value between 1 and 3 (lower cap than OCI)', () => { - process.env.DYNAMIC_PLUGINS_NPM_WORKERS = 'auto'; - const w = getNpmWorkers(); - expect(w).toBeGreaterThanOrEqual(1); - expect(w).toBeLessThanOrEqual(3); - }); - - it('explicit NPM worker count is independent of the OCI cap', () => { - process.env.DYNAMIC_PLUGINS_NPM_WORKERS = '8'; - expect(getNpmWorkers()).toBe(8); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/extra-catalog-index.test.ts b/scripts/install-dynamic-plugins/__tests__/extra-catalog-index.test.ts deleted file mode 100644 index 644b5fb07f..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/extra-catalog-index.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; -import * as fs from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import * as tar from 'tar'; -import { - extractExtraCatalogIndex, - imageRefToSubdirectory, - parseExtraCatalogIndexImages, -} from '../src/catalog-index'; -import { Skopeo } from '../src/skopeo'; - -describe('imageRefToSubdirectory', () => { - it('replaces /, :, and @ with _', () => { - expect(imageRefToSubdirectory('quay.io/rhdh/plugin-catalog-index:1.10')).toBe( - 'quay.io_rhdh_plugin-catalog-index_1.10', - ); - expect(imageRefToSubdirectory('quay.io/rhdh/index@sha256:abc123')).toBe( - 'quay.io_rhdh_index_sha256_abc123', - ); - }); - - it('returns the input unchanged when no special characters are present', () => { - expect(imageRefToSubdirectory('plain-name')).toBe('plain-name'); - }); -}); - -describe('parseExtraCatalogIndexImages', () => { - it('parses a single plain image ref with auto-derived subdirectory', () => { - expect(parseExtraCatalogIndexImages('quay.io/rhdh/index:1.0')).toEqual([ - ['quay.io_rhdh_index_1.0', 'quay.io/rhdh/index:1.0'], - ]); - }); - - it('parses explicit name=ref entries', () => { - expect(parseExtraCatalogIndexImages('community=quay.io/rhdh-community/index:1.10')).toEqual([ - ['community', 'quay.io/rhdh-community/index:1.10'], - ]); - }); - - it('parses mixed explicit + auto-derived entries in order', () => { - expect( - parseExtraCatalogIndexImages( - 'community=quay.io/rhdh-community/index:1.10,quay.io/partner/index:latest', - ), - ).toEqual([ - ['community', 'quay.io/rhdh-community/index:1.10'], - ['quay.io_partner_index_latest', 'quay.io/partner/index:latest'], - ]); - }); - - it('trims whitespace around each entry and around name=ref', () => { - expect(parseExtraCatalogIndexImages(' community = quay.io/x:1.0 , quay.io/y:2.0 ')).toEqual([ - ['community', 'quay.io/x:1.0'], - ['quay.io_y_2.0', 'quay.io/y:2.0'], - ]); - }); - - it('skips empty entries silently', () => { - expect(parseExtraCatalogIndexImages(',,quay.io/x:1.0,,')).toEqual([ - ['quay.io_x_1.0', 'quay.io/x:1.0'], - ]); - }); - - it('warns and skips entries with an empty image reference', () => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - expect(parseExtraCatalogIndexImages('community=,quay.io/x:1.0')).toEqual([ - ['quay.io_x_1.0', 'quay.io/x:1.0'], - ]); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/WARNING: Skipping EXTRA_CATALOG_INDEX_IMAGES entry with empty image reference/); - } finally { - warn.mockRestore(); - } - }); - - it('warns and skips entries whose explicit name is empty after trimming', () => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - expect(parseExtraCatalogIndexImages('=quay.io/x:1,quay.io/y:2')).toEqual([ - ['quay.io_y_2', 'quay.io/y:2'], - ]); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/unsafe subdirectory name ''/); - } finally { - warn.mockRestore(); - } - }); - - it.each([ - ['..', '..=quay.io/x:1'], - ['.', '.=quay.io/x:1'], - ['foo/bar', 'foo/bar=quay.io/x:1'], - ['..\\evil', '..\\evil=quay.io/x:1'], - ])('rejects path-traversing or separator-bearing subdirectory name %p', (_badName, entry) => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - const result = parseExtraCatalogIndexImages(`${entry},quay.io/safe:1`); - expect(result).toEqual([['quay.io_safe_1', 'quay.io/safe:1']]); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/unsafe subdirectory name/); - } finally { - warn.mockRestore(); - } - }); - - it('accepts URL-encoded separators without URL-decoding (character-based check)', () => { - // %2F is the URL encoding of '/'. We intentionally do NOT decode it, so - // a name like '..%2Fetc' is accepted as a literal directory name. - expect(parseExtraCatalogIndexImages('..%2Fetc=quay.io/x:1')).toEqual([ - ['..%2Fetc', 'quay.io/x:1'], - ]); - }); -}); - -describe('extractExtraCatalogIndex', () => { - let workRoot: string; - let fakeSkopeoDir: string; - - /** - * Build a fake `skopeo` binary that, on `copy dir:`, materialises - * a manifest.json + a single layer tarball at . The layer contents are - * packed at fixture-build time from `layerStageDir`. - */ - async function makeFakeSkopeo(layerStageDir: string): Promise { - const layerTarPath = join(fakeSkopeoDir, 'layer.tar'); - await tar.c({ gzip: false, file: layerTarPath, cwd: layerStageDir }, ['.']); - const binPath = join(fakeSkopeoDir, 'skopeo'); - const digest = 'sha256:fakefakefakefakefakefakefakefake'; - const digestFile = digest.split(':')[1]; - writeFileSync( - binPath, - `#!/bin/sh -DST="" -for arg in "$@"; do - case "$arg" in - dir:*) DST="\${arg#dir:}" ;; - esac -done -mkdir -p "$DST" -cp "${layerTarPath}" "$DST/${digestFile}" -cat > "$DST/manifest.json" < { - workRoot = mkdtempSync(join(tmpdir(), 'extra-cidx-')); - fakeSkopeoDir = mkdtempSync(join(tmpdir(), 'fake-skopeo-extra-')); - }); - - afterEach(() => { - rmSync(workRoot, { recursive: true, force: true }); - rmSync(fakeSkopeoDir, { recursive: true, force: true }); - }); - - /** Stage a `catalog-entities//` tree for the fake skopeo to pack. */ - async function stageLayer(sub: 'extensions' | 'marketplace', file: string, body: string): Promise { - const stage = mkdtempSync(join(tmpdir(), 'extra-layer-stage-')); - const dir = join(stage, 'catalog-entities', sub); - mkdirSync(dir, { recursive: true }); - writeFileSync(join(dir, file), body); - return stage; - } - - it("writes catalog entities to //catalog-entities/ from 'extensions/'", async () => { - const stage = await stageLayer('extensions', 'plugin.yaml', 'kind: Plugin\n'); - const binPath = await makeFakeSkopeo(stage); - rmSync(stage, { recursive: true, force: true }); - const skopeo = new Skopeo(binPath); - const parent = join(workRoot, 'extra'); - await extractExtraCatalogIndex(skopeo, 'quay.io/rhdh-community/index:1.10', 'community', parent, null); - const dst = join(parent, 'community', 'catalog-entities'); - await expect(fs.readFile(join(dst, 'plugin.yaml'), 'utf8')).resolves.toBe('kind: Plugin\n'); - }); - - it('falls back to marketplace/ when extensions/ is missing', async () => { - const stage = await stageLayer('marketplace', 'mp.yaml', 'kind: Marketplace\n'); - const binPath = await makeFakeSkopeo(stage); - rmSync(stage, { recursive: true, force: true }); - const skopeo = new Skopeo(binPath); - const parent = join(workRoot, 'extra'); - await extractExtraCatalogIndex(skopeo, 'quay.io/x/index:1', 'partner', parent, null); - await expect( - fs.readFile(join(parent, 'partner', 'catalog-entities', 'mp.yaml'), 'utf8'), - ).resolves.toBe('kind: Marketplace\n'); - }); - - it('logs a warning and does not throw when neither extensions/ nor marketplace/ is present', async () => { - // Stage an unrelated path so the layer extracts cleanly without writing - // either of the two recognised directories. - const stage = mkdtempSync(join(tmpdir(), 'extra-layer-empty-')); - mkdirSync(join(stage, 'other'), { recursive: true }); - writeFileSync(join(stage, 'other', 'README'), 'noop'); - const binPath = await makeFakeSkopeo(stage); - rmSync(stage, { recursive: true, force: true }); - const skopeo = new Skopeo(binPath); - const writes: string[] = []; - const warn = vi - .spyOn(process.stdout, 'write') - .mockImplementation((chunk: unknown) => { - writes.push(String(chunk)); - return true; - }); - try { - const parent = join(workRoot, 'extra'); - await expect( - extractExtraCatalogIndex(skopeo, 'quay.io/x/empty:1', 'empty', parent, null), - ).resolves.toBeUndefined(); - const out = writes.join(''); - expect(out).toMatch( - /WARNING: Extra catalog index image quay\.io\/x\/empty:1 does not have neither 'catalog-entities\/extensions\/' nor 'catalog-entities\/marketplace\/' directory/, - ); - } finally { - warn.mockRestore(); - } - }); - - it("logs the duplicate-subdir warning AFTER the extraction header when previouslyUsedBy is set", async () => { - const stage = await stageLayer('extensions', 'plugin.yaml', 'kind: Plugin\n'); - const binPath = await makeFakeSkopeo(stage); - rmSync(stage, { recursive: true, force: true }); - const skopeo = new Skopeo(binPath); - const writes: string[] = []; - const warn = vi - .spyOn(process.stdout, 'write') - .mockImplementation((chunk: unknown) => { - writes.push(String(chunk)); - return true; - }); - try { - const parent = join(workRoot, 'extra'); - await extractExtraCatalogIndex( - skopeo, - 'quay.io/second/index:1', - 'community', - parent, - 'quay.io/first/index:1', - ); - const out = writes.join(''); - const headerIdx = out.indexOf("Extracting extra catalog index 'community'"); - const warningIdx = out.indexOf("WARNING: Subdirectory 'community' was already used by 'quay.io/first/index:1'"); - expect(headerIdx).toBeGreaterThanOrEqual(0); - expect(warningIdx).toBeGreaterThan(headerIdx); - } finally { - warn.mockRestore(); - } - }); - - it.each(['', '.', '..', 'foo/bar', 'foo\\bar'])( - 'refuses to extract into unsafe subdirectory %p (defense in depth)', - async badName => { - const stage = await stageLayer('extensions', 'plugin.yaml', 'kind: Plugin\n'); - const binPath = await makeFakeSkopeo(stage); - rmSync(stage, { recursive: true, force: true }); - const skopeo = new Skopeo(binPath); - await expect( - extractExtraCatalogIndex(skopeo, 'quay.io/x:1', badName, join(workRoot, 'extra'), null), - ).rejects.toThrow(/unsafe subdirectory/); - }, - ); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/finalize-install.test.ts b/scripts/install-dynamic-plugins/__tests__/finalize-install.test.ts deleted file mode 100644 index 68b5ea44d6..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/finalize-install.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import type { MockInstance } from 'vitest'; -import { finalizeInstall } from '../src/index'; -import { GLOBAL_CONFIG_FILENAME } from '../src/types'; - -describe('finalizeInstall', () => { - let root: string; - let globalConfigFile: string; - let stdoutSpy: MockInstance; - - beforeEach(() => { - root = mkdtempSync(join(tmpdir(), 'finalize-')); - globalConfigFile = join(root, GLOBAL_CONFIG_FILENAME); - stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - }); - - afterEach(() => { - stdoutSpy.mockRestore(); - rmSync(root, { recursive: true, force: true }); - }); - - it('writes the global config and removes obsolete plugin dirs on success', async () => { - const obsoleteDir = join(root, 'obsolete-plugin'); - mkdirSync(obsoleteDir); - writeFileSync(join(obsoleteDir, 'dynamic-plugin-config.hash'), 'stale-hash'); - const installed = new Map([['stale-hash', 'obsolete-plugin']]); - - const code = await finalizeInstall( - [], - globalConfigFile, - { dynamicPlugins: { rootDirectory: 'dynamic-plugins-root' } }, - root, - installed, - ); - - expect(code).toBe(0); - expect(existsSync(globalConfigFile)).toBe(true); - expect(readFileSync(globalConfigFile, 'utf8')).toContain('rootDirectory: dynamic-plugins-root'); - expect(existsSync(obsoleteDir)).toBe(false); - }); - - it('skips the config write and cleanup when errors were collected', async () => { - const existingDir = join(root, 'keep-me'); - mkdirSync(existingDir); - writeFileSync(join(existingDir, 'dynamic-plugin-config.hash'), 'prior-hash'); - const sentinel = 'dynamicPlugins:\n rootDirectory: dynamic-plugins-root\n# previous run\n'; - writeFileSync(globalConfigFile, sentinel); - const installed = new Map([['prior-hash', 'keep-me']]); - - const code = await finalizeInstall( - ['oci://bogus/image:tag: connection refused'], - globalConfigFile, - { dynamicPlugins: { rootDirectory: 'dynamic-plugins-root' } }, - root, - installed, - ); - - expect(code).toBe(1); - // Previous config on disk must be preserved untouched. - expect(readFileSync(globalConfigFile, 'utf8')).toBe(sentinel); - // Previously-installed plugin dir must not be cleaned up. - expect(existsSync(existingDir)).toBe(true); - - const logged = stdoutSpy.mock.calls.map(c => String(c[0])).join(''); - expect(logged).toContain('1 plugin(s) failed'); - expect(logged).toContain('oci://bogus/image:tag: connection refused'); - expect(logged).toContain(`Skipping ${GLOBAL_CONFIG_FILENAME} write and cleanup`); - }); - - it('does not create the config file on error when no previous run exists', async () => { - const code = await finalizeInstall( - ['npm:broken-plugin: integrity mismatch'], - globalConfigFile, - { dynamicPlugins: { rootDirectory: 'dynamic-plugins-root' } }, - root, - new Map(), - ); - - expect(code).toBe(1); - expect(existsSync(globalConfigFile)).toBe(false); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/image-resolver.test.ts b/scripts/install-dynamic-plugins/__tests__/image-resolver.test.ts deleted file mode 100644 index 3e2bbce309..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/image-resolver.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { resolveImage } from '../src/image-resolver'; -import type { Skopeo } from '../src/skopeo'; - -function fakeSkopeo(exists: (url: string) => boolean): Skopeo { - return { exists: async (url: string) => exists(url) } as unknown as Skopeo; -} - -describe('resolveImage', () => { - it('returns non-RHDH images unchanged', async () => { - const sk = fakeSkopeo(() => true); - await expect(resolveImage(sk, 'oci://quay.io/other/plugin:1.0')).resolves.toBe( - 'oci://quay.io/other/plugin:1.0', - ); - }); - - it('returns the RHDH image unchanged when it exists', async () => { - const sk = fakeSkopeo(() => true); - await expect( - resolveImage(sk, 'oci://registry.access.redhat.com/rhdh/plugin:1.0'), - ).resolves.toBe('oci://registry.access.redhat.com/rhdh/plugin:1.0'); - }); - - it('falls back to quay.io/rhdh when the RHDH image is missing', async () => { - const sk = fakeSkopeo(() => false); - await expect( - resolveImage(sk, 'oci://registry.access.redhat.com/rhdh/plugin:1.0'), - ).resolves.toBe('oci://quay.io/rhdh/plugin:1.0'); - }); - - it('preserves the docker:// protocol on fallback', async () => { - const sk = fakeSkopeo(() => false); - await expect( - resolveImage(sk, 'docker://registry.access.redhat.com/rhdh/plugin:1.0'), - ).resolves.toBe('docker://quay.io/rhdh/plugin:1.0'); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/integrity.test.ts b/scripts/install-dynamic-plugins/__tests__/integrity.test.ts deleted file mode 100644 index 38307a7db7..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/integrity.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { createHash } from 'node:crypto'; -import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { InstallException } from '../src/errors'; -import { verifyIntegrity } from '../src/integrity'; - -describe('verifyIntegrity', () => { - let workDir: string; - let archive: string; - const payload = Buffer.from('hello world'); - - beforeEach(() => { - workDir = mkdtempSync(join(tmpdir(), 'integrity-test-')); - archive = join(workDir, 'pkg.tgz'); - writeFileSync(archive, payload); - }); - - afterEach(() => rmSync(workDir, { recursive: true, force: true })); - - const hashB64 = (algo: string) => createHash(algo).update(payload).digest('base64'); - - it('accepts a matching sha256 hash', async () => { - await expect( - verifyIntegrity('pkg', archive, `sha256-${hashB64('sha256')}`), - ).resolves.toBeUndefined(); - }); - - it('accepts a matching sha512 hash', async () => { - await expect( - verifyIntegrity('pkg', archive, `sha512-${hashB64('sha512')}`), - ).resolves.toBeUndefined(); - }); - - it('accepts a matching sha384 hash', async () => { - await expect( - verifyIntegrity('pkg', archive, `sha384-${hashB64('sha384')}`), - ).resolves.toBeUndefined(); - }); - - it('rejects missing dash delimiter', async () => { - await expect(verifyIntegrity('pkg', archive, 'sha256')).rejects.toThrow(InstallException); - }); - - it('rejects an unsupported algorithm', async () => { - await expect(verifyIntegrity('pkg', archive, `md5-${hashB64('md5')}`)).rejects.toThrow( - /algorithm md5 is not supported/, - ); - }); - - it('rejects invalid base64', async () => { - await expect(verifyIntegrity('pkg', archive, 'sha256-not!!base64')).rejects.toThrow( - /not a valid base64 encoding/, - ); - }); - - it('rejects a hash mismatch', async () => { - const wrong = createHash('sha256').update('tampered').digest('base64'); - await expect(verifyIntegrity('pkg', archive, `sha256-${wrong}`)).rejects.toThrow( - /integrity check failed/, - ); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/lock-file.test.ts b/scripts/install-dynamic-plugins/__tests__/lock-file.test.ts deleted file mode 100644 index 443a85454e..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/lock-file.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { mkdtempSync, rmSync, writeFileSync, existsSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { createLock, removeLock } from '../src/lock-file'; - -describe('lock-file', () => { - let workDir: string; - beforeEach(() => (workDir = mkdtempSync(join(tmpdir(), 'lock-')))); - afterEach(() => rmSync(workDir, { recursive: true, force: true })); - - it('creates the lock file atomically', async () => { - const lockPath = join(workDir, 'test.lock'); - await createLock(lockPath); - expect(existsSync(lockPath)).toBe(true); - await removeLock(lockPath); - expect(existsSync(lockPath)).toBe(false); - }); - - it('removeLock is a no-op when the file is absent', async () => { - await expect(removeLock(join(workDir, 'missing.lock'))).resolves.toBeUndefined(); - }); - - it('waits until an existing lock is released, then acquires it', async () => { - const lockPath = join(workDir, 'wait.lock'); - writeFileSync(lockPath, 'other-pid'); - - const acquired = createLock(lockPath); - // Simulate the other process releasing after a short delay. - setTimeout(() => removeLock(lockPath), 50); - await expect(acquired).resolves.toBeUndefined(); - expect(existsSync(lockPath)).toBe(true); - }); - - it('times out when the existing lock is never released', async () => { - const lockPath = join(workDir, 'stale.lock'); - writeFileSync(lockPath, 'other-pid'); - const prev = process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS; - process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS = '50'; - try { - await expect(createLock(lockPath)).rejects.toThrow(/Timed out.*waiting for lock/); - } finally { - if (prev === undefined) delete process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS; - else process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS = prev; - } - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/merger-pre-merge.test.ts b/scripts/install-dynamic-plugins/__tests__/merger-pre-merge.test.ts deleted file mode 100644 index fd8ed98a38..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/merger-pre-merge.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { InstallException } from '../src/errors'; -import { filterDisabledOciPlugins, preMergeOciDisabledState } from '../src/merger'; -import type { PluginSpec } from '../src/types'; - -describe('preMergeOciDisabledState — level overrides', () => { - const cases: Array<{ - name: string; - include: PluginSpec[]; - main: PluginSpec[]; - expectDisabled: boolean; - }> = [ - { - name: 'include enabled, main disabled (path-less inherit) → effectively disabled', - include: [{ package: 'oci://registry.example.com/plugin:1.0', disabled: false }], - main: [{ package: 'oci://registry.example.com/plugin:{{inherit}}', disabled: true }], - expectDisabled: true, - }, - { - name: 'include disabled, main re-enables → not disabled', - include: [{ package: 'oci://registry.example.com/plugin:1.0', disabled: true }], - main: [{ package: 'oci://registry.example.com/plugin:{{inherit}}', disabled: false }], - expectDisabled: false, - }, - { - name: 'include disabled, no main entry → disabled', - include: [{ package: 'oci://registry.example.com/plugin:1.0', disabled: true }], - main: [], - expectDisabled: true, - }, - { - name: 'cross-form: path-less include enabled, explicit-path main disabled → disabled', - include: [{ package: 'oci://registry.example.com/plugin:1.0', disabled: false }], - main: [{ package: 'oci://registry.example.com/plugin:1.0!my-plugin', disabled: true }], - expectDisabled: true, - }, - { - name: 'cross-form: explicit-path include enabled, path-less main disabled → disabled', - include: [{ package: 'oci://registry.example.com/plugin:1.0!my-plugin', disabled: false }], - main: [{ package: 'oci://registry.example.com/plugin:{{inherit}}', disabled: true }], - expectDisabled: true, - }, - { - name: 'cross-form: explicit-path include disabled, path-less main enables → not disabled', - include: [{ package: 'oci://registry.example.com/plugin:1.0!my-plugin', disabled: true }], - main: [{ package: 'oci://registry.example.com/plugin:{{inherit}}', disabled: false }], - expectDisabled: false, - }, - ]; - - for (const { name, include, main, expectDisabled } of cases) { - it(name, () => { - const result = preMergeOciDisabledState([['include.yaml', include]], main, 'main.yaml'); - const registry = 'oci://registry.example.com/plugin'; - if (expectDisabled) { - expect(result.has(registry)).toBe(true); - } else { - expect(result.has(registry)).toBe(false); - } - }); - } -}); - -describe('preMergeOciDisabledState — path-less + multiple explicit paths', () => { - const include: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0!pluginA' }, - { package: 'oci://registry.example.com/plugin:1.0!pluginB' }, - ]; - - it('warns and skips when the path-less reference is disabled', () => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - const main: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:{{inherit}}', disabled: true }, - ]; - const result = preMergeOciDisabledState([['include.yaml', include]], main, 'main.yaml'); - expect(result.has('oci://registry.example.com/plugin')).toBe(true); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/WARNING: Skipping disabled ambiguous path-less OCI reference/); - expect(out).toMatch(/multiple path-specific entries exist/); - expect(out).toMatch(/Cannot use path-less syntax for multi-plugin images/); - } finally { - warn.mockRestore(); - } - }); - - it('throws when the path-less reference is enabled', () => { - const main: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:{{inherit}}', disabled: false }, - ]; - expect(() => preMergeOciDisabledState([['include.yaml', include]], main, 'main.yaml')).toThrow( - /Ambiguous path-less OCI reference/, - ); - }); -}); - -describe('preMergeOciDisabledState — same-level duplicates', () => { - it('warns and ignores duplicate disabled entries at the same level', () => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - const include: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0!a', disabled: true }, - { package: 'oci://registry.example.com/plugin:1.0!a', disabled: true }, - ]; - const result = preMergeOciDisabledState([['include.yaml', include]], [], 'main.yaml'); - expect(result.has('oci://registry.example.com/plugin')).toBe(false); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/WARNING: Skipping duplicate disabled OCI plugin configuration/); - } finally { - warn.mockRestore(); - } - }); - - it('throws on duplicate enabled entries at the same level', () => { - const include: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0!a' }, - { package: 'oci://registry.example.com/plugin:1.0!a' }, - ]; - expect(() => preMergeOciDisabledState([['include.yaml', include]], [], 'main.yaml')).toThrow( - /Duplicate OCI plugin configuration/, - ); - }); -}); - -describe('preMergeOciDisabledState — invalid OCI strings', () => { - it('warns and skips when an invalid OCI string is disabled', () => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - const main: PluginSpec[] = [{ package: 'oci://not a valid spec', disabled: true }]; - const result = preMergeOciDisabledState([], main, 'main.yaml'); - expect(result.size).toBe(0); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/WARNING: Skipping disabled OCI plugin with invalid format/); - } finally { - warn.mockRestore(); - } - }); - - it('throws when an invalid OCI string is enabled', () => { - const main: PluginSpec[] = [{ package: 'oci://not a valid spec' }]; - expect(() => preMergeOciDisabledState([], main, 'main.yaml')).toThrow( - InstallException, - ); - }); -}); - -describe('filterDisabledOciPlugins', () => { - it('removes plugins whose registry is in the disabled set', () => { - const plugins: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0!a' }, - { package: 'oci://other.example.com/plugin:2.0!b' }, - ]; - const disabled = new Set(['oci://registry.example.com/plugin']); - const out = filterDisabledOciPlugins(plugins, disabled); - expect(out.map(p => p.package)).toEqual(['oci://other.example.com/plugin:2.0!b']); - }); - - it('removes invalid OCI entries that are marked disabled, keeps invalid-enabled ones (caller will surface them later)', () => { - const plugins: PluginSpec[] = [ - { package: 'oci://bad spec', disabled: true }, - { package: 'oci://also bad' }, - ]; - const out = filterDisabledOciPlugins(plugins, new Set()); - expect(out.map(p => p.package)).toEqual(['oci://also bad']); - }); - - it('passes non-OCI entries through unchanged', () => { - const plugins: PluginSpec[] = [ - { package: '@scope/pkg@1.0.0' }, - { package: './local-plugin' }, - ]; - const out = filterDisabledOciPlugins(plugins, new Set(['oci://something/plugin'])); - expect(out).toHaveLength(2); - }); - - it('removes invalid OCI entries that are marked enabled: false', () => { - const plugins: PluginSpec[] = [ - { package: 'oci://bad spec', enabled: false }, - { package: 'oci://also bad' }, - ]; - const out = filterDisabledOciPlugins(plugins, new Set()); - expect(out.map(p => p.package)).toEqual(['oci://also bad']); - }); -}); - -describe('preMergeOciDisabledState — enabled field', () => { - it('enabled: false in main disables the registry', () => { - const include: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0', enabled: true }, - ]; - const main: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:{{inherit}}', enabled: false }, - ]; - const result = preMergeOciDisabledState([['include.yaml', include]], main, 'main.yaml'); - expect(result.has('oci://registry.example.com/plugin')).toBe(true); - }); - - it('enabled: true in main re-enables a disabled include', () => { - const include: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0', enabled: false }, - ]; - const main: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:{{inherit}}', enabled: true }, - ]; - const result = preMergeOciDisabledState([['include.yaml', include]], main, 'main.yaml'); - expect(result.has('oci://registry.example.com/plugin')).toBe(false); - }); - - it('enabled takes precedence when both enabled and disabled are set', () => { - const warn = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); - try { - const main: PluginSpec[] = [ - { package: 'oci://registry.example.com/plugin:1.0', enabled: true, disabled: true }, - ]; - const result = preMergeOciDisabledState([], main, 'main.yaml'); - // enabled: true should win even though disabled: true is also set - expect(result.has('oci://registry.example.com/plugin')).toBe(false); - const out = warn.mock.calls.map(args => String(args[0])).join('\n'); - expect(out).toMatch(/both 'enabled' and 'disabled'/); - } finally { - warn.mockRestore(); - } - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/merger.test.ts b/scripts/install-dynamic-plugins/__tests__/merger.test.ts deleted file mode 100644 index 60236413bb..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/merger.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { InstallException } from '../src/errors'; -import { deepMerge, mergePlugin } from '../src/merger'; -import type { PluginMap } from '../src/types'; - -describe('deepMerge', () => { - it('merges disjoint keys into the destination', () => { - const dst: Record = { a: 1 }; - deepMerge({ b: 2, c: { d: 3 } }, dst); - expect(dst).toEqual({ a: 1, b: 2, c: { d: 3 } }); - }); - - it('deep-merges nested objects', () => { - const dst: Record = { a: { x: 1 }, b: 2 }; - deepMerge({ a: { y: 2 }, c: 3 }, dst); - expect(dst).toEqual({ a: { x: 1, y: 2 }, b: 2, c: 3 }); - }); - - it('is idempotent when the same scalar is merged', () => { - const dst: Record = { a: 1 }; - expect(() => deepMerge({ a: 1 }, dst)).not.toThrow(); - }); - - it('throws on conflicting scalars', () => { - const dst: Record = { a: { x: 1 } }; - expect(() => deepMerge({ a: { x: 2 } }, dst)).toThrow(/Config key 'a.x' defined differently/); - }); - - it('ignores prototype-polluting keys (__proto__, constructor, prototype)', () => { - const dst: Record = {}; - const malicious = JSON.parse( - '{"__proto__":{"polluted":true},"constructor":{"x":1},"prototype":{"y":2}}', - ) as Record; - deepMerge(malicious, dst); - expect(({} as Record).polluted).toBeUndefined(); - expect(Object.prototype.hasOwnProperty.call(dst, '__proto__')).toBe(false); - expect(Object.prototype.hasOwnProperty.call(dst, 'constructor')).toBe(false); - expect(Object.prototype.hasOwnProperty.call(dst, 'prototype')).toBe(false); - }); -}); - -describe('mergePlugin — NPM', () => { - it('adds a new plugin on first merge', async () => { - const all: PluginMap = {}; - await mergePlugin({ package: 'pkg@1.0.0' }, all, 'cfg.yaml', 0); - expect(all['pkg']).toBeDefined(); - expect(all['pkg']?.package).toBe('pkg@1.0.0'); - expect(all['pkg']?.last_modified_level).toBe(0); - }); - - it('overrides a lower-level plugin from a higher level', async () => { - const all: PluginMap = {}; - await mergePlugin({ package: 'pkg@1.0.0', disabled: false }, all, 'inc.yaml', 0); - await mergePlugin({ package: 'pkg@2.0.0', disabled: true }, all, 'cfg.yaml', 1); - expect(all['pkg']?.package).toBe('pkg@2.0.0'); - expect(all['pkg']?.disabled).toBe(true); - expect(all['pkg']?.last_modified_level).toBe(1); - }); - - it('overrides using the enabled field', async () => { - const all: PluginMap = {}; - await mergePlugin({ package: 'pkg@1.0.0', enabled: true }, all, 'inc.yaml', 0); - await mergePlugin({ package: 'pkg@2.0.0', enabled: false }, all, 'cfg.yaml', 1); - expect(all['pkg']?.package).toBe('pkg@2.0.0'); - expect(all['pkg']?.enabled).toBe(false); - expect(all['pkg']?.last_modified_level).toBe(1); - }); - - it('handles enabled overriding disabled from a lower level', async () => { - const all: PluginMap = {}; - await mergePlugin({ package: 'pkg@1.0.0', disabled: true }, all, 'inc.yaml', 0); - await mergePlugin({ package: 'pkg@2.0.0', enabled: true }, all, 'cfg.yaml', 1); - expect(all['pkg']?.package).toBe('pkg@2.0.0'); - expect(all['pkg']?.enabled).toBe(true); - expect(all['pkg']?.last_modified_level).toBe(1); - }); - - it('raises on duplicates within the same level', async () => { - const all: PluginMap = {}; - await mergePlugin({ package: 'pkg@1.0.0' }, all, 'cfg.yaml', 0); - await expect(mergePlugin({ package: 'pkg@2.0.0' }, all, 'cfg.yaml', 0)).rejects.toBeInstanceOf( - InstallException, - ); - }); - - it('rejects non-string package values', async () => { - const all: PluginMap = {}; - await expect( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mergePlugin({ package: 123 as any }, all, 'cfg.yaml', 0), - ).rejects.toThrow(/must be a string/); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/npm-key.test.ts b/scripts/install-dynamic-plugins/__tests__/npm-key.test.ts deleted file mode 100644 index deff92faef..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/npm-key.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { npmPluginKey } from '../src/npm-key'; - -describe('npmPluginKey', () => { - const cases: Array<[string, string]> = [ - // Standard NPM packages with version stripping - ['@npmcli/arborist@latest', '@npmcli/arborist'], - ['@backstage/plugin-catalog@1.0.0', '@backstage/plugin-catalog'], - ['semver@7.2.2', 'semver'], - ['package-name@^1.0.0', 'package-name'], - ['package-name@~2.1.0', 'package-name'], - ['package-name@1.x', 'package-name'], - - // Packages without version - ['package-name', 'package-name'], - ['@scope/package', '@scope/package'], - - // NPM aliases - ['semver:@npm:semver@7.2.2', 'semver:@npm:semver'], - ['my-alias@npm:@npmcli/semver-with-patch', 'my-alias@npm:@npmcli/semver-with-patch'], - ['semver:@npm:@npmcli/semver-with-patch@1.0.0', 'semver:@npm:@npmcli/semver-with-patch'], - ['alias@npm:package@1.0.0', 'alias@npm:package'], - ['alias@npm:@scope/package@2.0.0', 'alias@npm:@scope/package'], - - // Git URLs with ref stripping - ['npm/cli#c12ea07', 'npm/cli'], - ['user/repo#main', 'user/repo'], - ['github:user/repo#ref', 'github:user/repo'], - ['git+https://github.com/user/repo.git#branch', 'git+https://github.com/user/repo.git'], - ['git+https://github.com/user/repo#branch', 'git+https://github.com/user/repo'], - ['git@github.com:user/repo.git#ref', 'git@github.com:user/repo.git'], - ['git+ssh://git@github.com/user/repo.git#tag', 'git+ssh://git@github.com/user/repo.git'], - ['git://github.com/user/repo#commit', 'git://github.com/user/repo'], - ['https://github.com/user/repo.git#v1.0.0', 'https://github.com/user/repo.git'], - - // Local paths (unchanged) - ['./my-local-plugin', './my-local-plugin'], - ['./path/to/plugin', './path/to/plugin'], - - // Tarballs (unchanged) - ['package.tgz', 'package.tgz'], - ['my-package-1.0.0.tgz', 'my-package-1.0.0.tgz'], - ['https://example.com/package.tgz', 'https://example.com/package.tgz'], - ]; - - it.each(cases)('parses %s -> %s', (input, expected) => { - expect(npmPluginKey(input)).toBe(expected); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/oci-key.test.ts b/scripts/install-dynamic-plugins/__tests__/oci-key.test.ts deleted file mode 100644 index ba0b58bd4d..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/oci-key.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import type { OciImageCache } from '../src/image-cache'; -import { ociPluginKey } from '../src/oci-key'; - -type SuccessCase = { - pkg: string; - key: string; - version: string; - inherit: boolean; - path: string | null; -}; - -const successCases: SuccessCase[] = [ - // Tag-based packages with explicit path - { - pkg: 'oci://quay.io/user/plugin:v1.0!plugin-name', - key: 'oci://quay.io/user/plugin:!plugin-name', - version: 'v1.0', - inherit: false, - path: 'plugin-name', - }, - { - pkg: 'oci://registry.io/plugin:latest!path/to/plugin', - key: 'oci://registry.io/plugin:!path/to/plugin', - version: 'latest', - inherit: false, - path: 'path/to/plugin', - }, - { - pkg: 'oci://ghcr.io/org/plugin:1.2.3!my-plugin', - key: 'oci://ghcr.io/org/plugin:!my-plugin', - version: '1.2.3', - inherit: false, - path: 'my-plugin', - }, - { - pkg: 'oci://docker.io/library/plugin:v2.0.0!plugin', - key: 'oci://docker.io/library/plugin:!plugin', - version: 'v2.0.0', - inherit: false, - path: 'plugin', - }, - - // Digests with supported algorithms - { - pkg: 'oci://quay.io/user/plugin@sha256:abc123def456!plugin', - key: 'oci://quay.io/user/plugin:!plugin', - version: 'sha256:abc123def456', - inherit: false, - path: 'plugin', - }, - { - pkg: 'oci://registry.io/plugin@sha512:fedcba987654!plugin', - key: 'oci://registry.io/plugin:!plugin', - version: 'sha512:fedcba987654', - inherit: false, - path: 'plugin', - }, - { - pkg: 'oci://example.com/plugin@blake3:1234567890abcdef!my-plugin', - key: 'oci://example.com/plugin:!my-plugin', - version: 'blake3:1234567890abcdef', - inherit: false, - path: 'my-plugin', - }, - - // Inherit - { - pkg: 'oci://quay.io/user/plugin:{{inherit}}!plugin', - key: 'oci://quay.io/user/plugin:!plugin', - version: '{{inherit}}', - inherit: true, - path: 'plugin', - }, - { - pkg: 'oci://registry.io/plugin:{{inherit}}!path/to/plugin', - key: 'oci://registry.io/plugin:!path/to/plugin', - version: '{{inherit}}', - inherit: true, - path: 'path/to/plugin', - }, - - // Host:port registries - { - pkg: 'oci://registry.localhost:5000/rhdh-plugins/plugin:v1.0!plugin-name', - key: 'oci://registry.localhost:5000/rhdh-plugins/plugin:!plugin-name', - version: 'v1.0', - inherit: false, - path: 'plugin-name', - }, - { - pkg: 'oci://registry.localhost:5000/path@sha256:abc123!plugin', - key: 'oci://registry.localhost:5000/path:!plugin', - version: 'sha256:abc123', - inherit: false, - path: 'plugin', - }, - { - pkg: 'oci://registry.localhost:5000/path:{{inherit}}!plugin', - key: 'oci://registry.localhost:5000/path:!plugin', - version: '{{inherit}}', - inherit: true, - path: 'plugin', - }, - { - pkg: 'oci://10.0.0.1:5000/repo/plugin:tag!plugin', - key: 'oci://10.0.0.1:5000/repo/plugin:!plugin', - version: 'tag', - inherit: false, - path: 'plugin', - }, -]; - -const invalidCases: string[] = [ - // Missing tag/digest - 'oci://registry.io/plugin!path', - 'oci://registry.io/plugin', - 'oci://host:1000/path', - // No tag/digest before ! - 'oci://registry.io!path', - 'oci://host:1000!path', - // Unsupported digest algorithm - 'oci://registry.io/plugin@md5:abc123!plugin', - 'oci://host:1000/path@md5:abc123!plugin', - // Multiple @ - 'oci://registry.io/plugin@@sha256:abc!plugin', - 'oci://host:1000/path@@sha256:abc!plugin', - // Multiple : in tag - 'oci://registry.io/plugin:v1:v2!plugin', - 'oci://host:1000/path:v1:v2!plugin', - // Empty tag - 'oci://registry.io/plugin:!plugin', - 'oci://registry.io/plugin:', - 'oci://host:1000/path:!plugin', - 'oci://host:1000/path:', - // Empty path after ! - 'oci://registry.io/plugin:v1.0!', - 'oci://host:1000/path:v1.0!', - // No oci:// prefix - 'registry.io/plugin:v1.0!plugin', - 'registry.io/plugin:v1.0', - 'host:1000/path:v1.0!plugin', - 'host:1000/path:v1.0', -]; - -describe('ociPluginKey — success cases', () => { - it.each(successCases)('parses $pkg', async ({ pkg, key, version, inherit, path }) => { - const parsed = await ociPluginKey(pkg); - expect(parsed.pluginKey).toBe(key); - expect(parsed.version).toBe(version); - expect(parsed.inherit).toBe(inherit); - expect(parsed.resolvedPath).toBe(path); - }); -}); - -describe('ociPluginKey — invalid cases', () => { - it.each(invalidCases)('rejects %s', async pkg => { - await expect(ociPluginKey(pkg)).rejects.toThrow(/not in the expected format/); - }); -}); - -describe('ociPluginKey — {{inherit}} without path', () => { - it('returns registry-only key and null path', async () => { - const parsed = await ociPluginKey('oci://registry.io/plugin:{{inherit}}'); - expect(parsed.pluginKey).toBe('oci://registry.io/plugin'); - expect(parsed.version).toBe('{{inherit}}'); - expect(parsed.inherit).toBe(true); - expect(parsed.resolvedPath).toBeNull(); - }); -}); - -describe('ociPluginKey — auto-detect from image cache', () => { - function fakeImageCache(paths: string[]): OciImageCache { - return { getPluginPaths: async () => paths } as unknown as OciImageCache; - } - - it('resolves a single plugin path', async () => { - const parsed = await ociPluginKey( - 'oci://registry.io/plugin:v1.0', - fakeImageCache(['auto-detected-plugin']), - ); - expect(parsed.pluginKey).toBe('oci://registry.io/plugin:!auto-detected-plugin'); - expect(parsed.version).toBe('v1.0'); - expect(parsed.resolvedPath).toBe('auto-detected-plugin'); - }); - - it('errors when no plugins are declared', async () => { - await expect(ociPluginKey('oci://registry.io/plugin:v1.0', fakeImageCache([]))).rejects.toThrow( - /No plugins found/, - ); - }); - - it('errors with list when multiple plugins are declared', async () => { - await expect( - ociPluginKey('oci://registry.io/plugin:v1.0', fakeImageCache(['p1', 'p2', 'p3'])), - ).rejects.toThrow(/Multiple plugins found[\s\S]+p1[\s\S]+p2[\s\S]+p3/); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/plugin-hash.test.ts b/scripts/install-dynamic-plugins/__tests__/plugin-hash.test.ts deleted file mode 100644 index cef6bcdeed..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/plugin-hash.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { computePluginHash } from '../src/plugin-hash'; -import type { Plugin } from '../src/types'; - -describe('computePluginHash', () => { - it('produces a deterministic hash for the same plugin', () => { - const plugin: Plugin = { - package: 'oci://host/img:v1.0!pkg', - disabled: false, - pluginConfig: { a: 1 }, - version: 'v1.0', - }; - const h1 = computePluginHash({ ...plugin }); - const h2 = computePluginHash({ ...plugin, pluginConfig: { a: 2 } }); - // pluginConfig and version do not participate in the hash. - expect(h1).toBe(h2); - }); - - it('changes the hash when package changes', () => { - const a = computePluginHash({ package: 'a@1' }); - const b = computePluginHash({ package: 'b@1' }); - expect(a).not.toBe(b); - }); - - it('changes the hash when pullPolicy changes', () => { - const a = computePluginHash({ package: 'x@1' }); - const b = computePluginHash({ package: 'x@1', pullPolicy: 'Always' }); - expect(a).not.toBe(b); - }); - - // The reference hashes below were produced by the Python `install-dynamic-plugins.py` - // script via `hashlib.sha256(json.dumps(hash_dict, sort_keys=True).encode()).hexdigest()`. - // If these break, the install hash is no longer cross-compatible with the Python - // implementation — every existing dynamic-plugins-root will be reinstalled on upgrade. - describe('Python compatibility', () => { - it('matches Python hash for a simple package', () => { - // python3 -c "import hashlib,json; - // print(hashlib.sha256(json.dumps({'package':'a@1'},sort_keys=True).encode()).hexdigest())" - expect(computePluginHash({ package: 'a@1' })).toBe( - 'd2cd9f6a6952d7df2f1760af29dcf232d441be5509048d5dec5093a5bd840b5a', - ); - }); - - it('matches Python hash for an OCI plugin with last_modified_level + pullPolicy', () => { - // python3 -c "import hashlib,json; - // d={'package':'oci://x/y:1!p','last_modified_level':1,'pullPolicy':'Always'}; - // print(hashlib.sha256(json.dumps(d,sort_keys=True).encode()).hexdigest())" - expect( - computePluginHash({ - package: 'oci://x/y:1!p', - pullPolicy: 'Always', - last_modified_level: 1, - }), - ).toBe('0191b0eeccc1307af7f9c5d8d7e249690152b6fd2a492aa30de8f7ec2fda02c9'); - }); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/skopeo.test.ts b/scripts/install-dynamic-plugins/__tests__/skopeo.test.ts deleted file mode 100644 index ba4900a936..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/skopeo.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { mkdirSync, writeFileSync, mkdtempSync, rmSync, chmodSync, readFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { Skopeo } from '../src/skopeo'; - -/** - * Build a fake `skopeo` binary that records every invocation. Used to verify - * the in-memory caches in `Skopeo.exists/inspect/inspectRaw` actually dedupe - * forks and the wrapper survives realistic exit codes. - */ -function makeFakeSkopeo(opts: { inspectExitCode?: number; inspectStdout?: string }): { - binPath: string; - logPath: string; - cleanup: () => void; -} { - const dir = mkdtempSync(join(tmpdir(), 'fake-skopeo-')); - const binPath = join(dir, 'skopeo'); - const logPath = join(dir, 'invocations.log'); - const exitCode = opts.inspectExitCode ?? 0; - const stdout = opts.inspectStdout ?? '{"Digest":"sha256:abc"}'; - writeFileSync( - binPath, - `#!/bin/sh -echo "$@" >> "${logPath}" -echo '${stdout.replace(/'/g, `'\\''`)}' -exit ${exitCode} -`, - ); - chmodSync(binPath, 0o755); - mkdirSync(dir, { recursive: true }); - return { binPath, logPath, cleanup: () => rmSync(dir, { recursive: true, force: true }) }; -} - -describe('Skopeo cache behaviour', () => { - it('exists() memoizes the result and only forks skopeo once per URL', async () => { - const { binPath, logPath, cleanup } = makeFakeSkopeo({ inspectExitCode: 0 }); - try { - const skopeo = new Skopeo(binPath); - const url = 'docker://example.com/image:1.0'; - - const results = await Promise.all([ - skopeo.exists(url), - skopeo.exists(url), - skopeo.exists(url), - ]); - expect(results).toEqual([true, true, true]); - - // One more sequential call after the cache is populated. - expect(await skopeo.exists(url)).toBe(true); - - const log = readFileSync(logPath, 'utf8'); - const invocations = log.split('\n').filter(Boolean); - expect(invocations).toHaveLength(1); - expect(invocations[0]).toContain('inspect --no-tags docker://example.com/image:1.0'); - } finally { - cleanup(); - } - }); - - it('exists() returns false for non-existent images and caches the negative result', async () => { - const { binPath, logPath, cleanup } = makeFakeSkopeo({ inspectExitCode: 1 }); - try { - const skopeo = new Skopeo(binPath); - expect(await skopeo.exists('docker://example.com/missing')).toBe(false); - expect(await skopeo.exists('docker://example.com/missing')).toBe(false); - - const log = readFileSync(logPath, 'utf8'); - expect(log.split('\n').filter(Boolean)).toHaveLength(1); - } finally { - cleanup(); - } - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/tar-extract.test.ts b/scripts/install-dynamic-plugins/__tests__/tar-extract.test.ts deleted file mode 100644 index ee6e93598f..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/tar-extract.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { mkdtempSync, readFileSync, rmSync, writeFileSync, symlinkSync, existsSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import * as tar from 'tar'; -import { InstallException } from '../src/errors'; -import { extractNpmPackage, extractOciPlugin } from '../src/tar-extract'; - -const PAYLOAD_LIMIT = 40_000_000; - -async function makeTarball( - archivePath: string, - entries: string[], - prep: (root: string) => Promise | void, -): Promise { - const stageDir = mkdtempSync(join(tmpdir(), 'tar-stage-')); - try { - await prep(stageDir); - await tar.c({ file: archivePath, cwd: stageDir, portable: true }, entries); - } finally { - rmSync(stageDir, { recursive: true, force: true }); - } -} - -describe('extractOciPlugin', () => { - let workDir: string; - let tarball: string; - - beforeEach(() => { - workDir = mkdtempSync(join(tmpdir(), 'oci-extract-')); - tarball = join(workDir, 'layer.tar'); - }); - afterEach(() => rmSync(workDir, { recursive: true, force: true })); - - it('extracts just the requested plugin subdirectory', async () => { - await makeTarball(tarball, ['plugin-one', 'plugin-two'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - require('node:fs').mkdirSync(join(stage, 'plugin-two')); - writeFileSync(join(stage, 'plugin-one/index.js'), 'module.exports = 1'); - writeFileSync(join(stage, 'plugin-two/index.js'), 'module.exports = 2'); - }); - - const dest = join(workDir, 'out'); - await extractOciPlugin(tarball, 'plugin-one', dest); - - expect(existsSync(join(dest, 'plugin-one/index.js'))).toBe(true); - expect(existsSync(join(dest, 'plugin-two'))).toBe(false); - }); - - it("rejects plugin paths with a '..' segment", async () => { - await makeTarball(tarball, ['plugin-one'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - writeFileSync(join(stage, 'plugin-one/index.js'), 'x'); - }); - await expect(extractOciPlugin(tarball, '../etc/passwd', workDir)).rejects.toBeInstanceOf( - InstallException, - ); - await expect(extractOciPlugin(tarball, 'foo/../bar', workDir)).rejects.toBeInstanceOf( - InstallException, - ); - }); - - it("rejects plugin paths with a '.' segment or empty segments", async () => { - await makeTarball(tarball, ['plugin-one'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - writeFileSync(join(stage, 'plugin-one/index.js'), 'x'); - }); - await expect(extractOciPlugin(tarball, './plugin-one', workDir)).rejects.toBeInstanceOf( - InstallException, - ); - await expect(extractOciPlugin(tarball, 'foo//bar', workDir)).rejects.toBeInstanceOf( - InstallException, - ); - }); - - it("accepts a plugin name that contains '..' inside a segment (not as a segment)", async () => { - await makeTarball(tarball, ['my..plugin'], stage => { - require('node:fs').mkdirSync(join(stage, 'my..plugin')); - writeFileSync(join(stage, 'my..plugin/index.js'), 'ok'); - }); - const dest = join(workDir, 'out'); - await extractOciPlugin(tarball, 'my..plugin', dest); - expect(existsSync(join(dest, 'my..plugin/index.js'))).toBe(true); - }); - - it('rejects absolute plugin paths', async () => { - await makeTarball(tarball, ['plugin-one'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - writeFileSync(join(stage, 'plugin-one/index.js'), 'x'); - }); - await expect(extractOciPlugin(tarball, '/etc/passwd', workDir)).rejects.toBeInstanceOf( - InstallException, - ); - }); - - it('raises when any entry exceeds MAX_ENTRY_SIZE', async () => { - await makeTarball(tarball, ['plugin-one'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - const bigPath = join(stage, 'plugin-one/big.bin'); - const fd = require('node:fs').openSync(bigPath, 'w'); - require('node:fs').ftruncateSync(fd, PAYLOAD_LIMIT + 1); - require('node:fs').closeSync(fd); - }); - await expect(extractOciPlugin(tarball, 'plugin-one', join(workDir, 'out'))).rejects.toThrow( - /Zip bomb/, - ); - }); - - it('skips symlinks whose target escapes the destination', async () => { - await makeTarball(tarball, ['plugin-one'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - writeFileSync(join(stage, 'plugin-one/ok.txt'), 'ok'); - symlinkSync('/etc/passwd', join(stage, 'plugin-one/bad-link')); - }); - const dest = join(workDir, 'out'); - await extractOciPlugin(tarball, 'plugin-one', dest); - expect(existsSync(join(dest, 'plugin-one/ok.txt'))).toBe(true); - expect(existsSync(join(dest, 'plugin-one/bad-link'))).toBe(false); - }); - - it('does not extract sibling directories with the same name prefix', async () => { - await makeTarball(tarball, ['plugin-one', 'plugin-one-evil'], stage => { - require('node:fs').mkdirSync(join(stage, 'plugin-one')); - require('node:fs').mkdirSync(join(stage, 'plugin-one-evil')); - writeFileSync(join(stage, 'plugin-one/index.js'), 'module.exports = 1'); - writeFileSync(join(stage, 'plugin-one-evil/index.js'), 'module.exports = 2'); - }); - const dest = join(workDir, 'out'); - await extractOciPlugin(tarball, 'plugin-one', dest); - expect(existsSync(join(dest, 'plugin-one/index.js'))).toBe(true); - expect(existsSync(join(dest, 'plugin-one-evil'))).toBe(false); - }); -}); - -describe('extractNpmPackage', () => { - let workDir: string; - beforeEach(() => (workDir = mkdtempSync(join(tmpdir(), 'npm-extract-')))); - afterEach(() => rmSync(workDir, { recursive: true, force: true })); - - it("strips the 'package/' prefix and returns the pkg directory name", async () => { - const archive = join(workDir, 'pkg.tgz'); - await makeTarball(archive, ['package'], stage => { - require('node:fs').mkdirSync(join(stage, 'package')); - writeFileSync(join(stage, 'package/package.json'), '{"name":"x"}'); - writeFileSync(join(stage, 'package/index.js'), 'module.exports={};'); - }); - - const dir = await extractNpmPackage(archive); - expect(dir).toBe('pkg'); - expect(readFileSync(join(workDir, 'pkg', 'package.json'), 'utf8')).toBe('{"name":"x"}'); - }); - - it("rejects archives with entries outside 'package/'", async () => { - const archive = join(workDir, 'pkg.tgz'); - await makeTarball(archive, ['package', 'evil.txt'], stage => { - require('node:fs').mkdirSync(join(stage, 'package')); - writeFileSync(join(stage, 'package/index.js'), 'x'); - writeFileSync(join(stage, 'evil.txt'), 'x'); - }); - await expect(extractNpmPackage(archive)).rejects.toThrow(/package\//); - }); - - it('rejects zip-bomb entries', async () => { - const archive = join(workDir, 'pkg.tgz'); - await makeTarball(archive, ['package'], stage => { - require('node:fs').mkdirSync(join(stage, 'package')); - const bigPath = join(stage, 'package/big.bin'); - const fd = require('node:fs').openSync(bigPath, 'w'); - require('node:fs').ftruncateSync(fd, PAYLOAD_LIMIT + 1); - require('node:fs').closeSync(fd); - }); - await expect(extractNpmPackage(archive)).rejects.toThrow(/Zip bomb/); - }); -}); diff --git a/scripts/install-dynamic-plugins/__tests__/types.test.ts b/scripts/install-dynamic-plugins/__tests__/types.test.ts deleted file mode 100644 index b643a263b3..0000000000 --- a/scripts/install-dynamic-plugins/__tests__/types.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { isPluginDisabled, parseMaxEntrySize } from '../src/types'; - -describe('parseMaxEntrySize', () => { - const DEFAULT = 40_000_000; - - it('returns the default when unset', () => { - expect(parseMaxEntrySize(undefined)).toBe(DEFAULT); - }); - - it('returns the default when empty', () => { - expect(parseMaxEntrySize('')).toBe(DEFAULT); - }); - - it('returns the parsed value for a positive integer', () => { - expect(parseMaxEntrySize('1000')).toBe(1000); - }); - - it('falls back to the default for a non-numeric value (no silent NaN)', () => { - expect(parseMaxEntrySize('abc')).toBe(DEFAULT); - }); - - it('falls back to the default for zero', () => { - expect(parseMaxEntrySize('0')).toBe(DEFAULT); - }); - - it('falls back to the default for a negative value', () => { - expect(parseMaxEntrySize('-100')).toBe(DEFAULT); - }); - - it('falls back to the default for NaN', () => { - expect(parseMaxEntrySize('NaN')).toBe(DEFAULT); - }); -}); - -describe('isPluginDisabled', () => { - it('returns false when neither enabled nor disabled is set', () => { - expect(isPluginDisabled({ package: 'pkg@1.0' })).toBe(false); - }); - - it('returns false when enabled: true', () => { - expect(isPluginDisabled({ package: 'pkg@1.0', enabled: true })).toBe(false); - }); - - it('returns true when enabled: false', () => { - expect(isPluginDisabled({ package: 'pkg@1.0', enabled: false })).toBe(true); - }); - - it('returns true when disabled: true (backward compat)', () => { - expect(isPluginDisabled({ package: 'pkg@1.0', disabled: true })).toBe(true); - }); - - it('returns false when disabled: false (backward compat)', () => { - expect(isPluginDisabled({ package: 'pkg@1.0', disabled: false })).toBe(false); - }); - - it('enabled takes precedence over disabled when both set (enabled: true, disabled: true)', () => { - const warnings: string[] = []; - const result = isPluginDisabled( - { package: 'pkg@1.0', enabled: true, disabled: true }, - msg => warnings.push(msg), - ); - expect(result).toBe(false); - expect(warnings).toHaveLength(1); - expect(warnings[0]).toMatch(/both 'enabled' and 'disabled'/); - }); - - it('enabled takes precedence over disabled when both set (enabled: false, disabled: false)', () => { - const warnings: string[] = []; - const result = isPluginDisabled( - { package: 'pkg@1.0', enabled: false, disabled: false }, - msg => warnings.push(msg), - ); - expect(result).toBe(true); - expect(warnings).toHaveLength(1); - }); - - it('does not warn when no callback provided', () => { - // Should not throw even when both fields are set - expect(isPluginDisabled({ package: 'pkg@1.0', enabled: true, disabled: true })).toBe(false); - }); - - it('treats non-boolean enabled as unset', () => { - const warnings: string[] = []; - const result = isPluginDisabled( - { package: 'pkg@1.0', enabled: 'false' as unknown as boolean }, - msg => warnings.push(msg), - ); - expect(result).toBe(false); - expect(warnings).toHaveLength(1); - expect(warnings[0]).toMatch(/non-boolean 'enabled: false'/); - }); - - it('treats null enabled as unset', () => { - const warnings: string[] = []; - const result = isPluginDisabled( - { package: 'pkg@1.0', enabled: null as unknown as boolean }, - msg => warnings.push(msg), - ); - expect(result).toBe(false); - expect(warnings).toHaveLength(1); - expect(warnings[0]).toMatch(/non-boolean 'enabled: null'/); - }); - - it('treats non-boolean disabled as unset', () => { - const warnings: string[] = []; - const result = isPluginDisabled( - { package: 'pkg@1.0', disabled: 'true' as unknown as boolean }, - msg => warnings.push(msg), - ); - expect(result).toBe(false); - expect(warnings).toHaveLength(1); - expect(warnings[0]).toMatch(/non-boolean 'disabled: true'/); - }); - - it('falls back to valid disabled when enabled is non-boolean', () => { - const warnings: string[] = []; - const result = isPluginDisabled( - { package: 'pkg@1.0', enabled: 'yes' as unknown as boolean, disabled: true }, - msg => warnings.push(msg), - ); - // enabled is non-boolean so ignored; disabled: true is valid - expect(result).toBe(true); - expect(warnings).toHaveLength(1); - expect(warnings[0]).toMatch(/non-boolean 'enabled/); - }); -}); diff --git a/scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs b/scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs deleted file mode 100644 index 1ae1386578..0000000000 --- a/scripts/install-dynamic-plugins/dist/install-dynamic-plugins.cjs +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env node -"use strict";var bd=Object.create;var ys=Object.defineProperty;var wd=Object.getOwnPropertyDescriptor;var Sd=Object.getOwnPropertyNames;var vd=Object.getPrototypeOf,Ed=Object.prototype.hasOwnProperty;var y=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),kd=(e,t)=>{for(var i in t)ys(e,i,{get:t[i],enumerable:!0})},Sl=(e,t,i,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Sd(t))!Ed.call(e,n)&&n!==i&&ys(e,n,{get:()=>t[n],enumerable:!(s=wd(t,n))||s.enumerable});return e};var O=(e,t,i)=>(i=e!=null?bd(vd(e)):{},Sl(t||!e||!e.__esModule?ys(i,"default",{value:e,enumerable:!0}):i,e)),Od=e=>Sl(ys({},"__esModule",{value:!0}),e);var I=y(J=>{"use strict";var ur=Symbol.for("yaml.alias"),vl=Symbol.for("yaml.document"),bs=Symbol.for("yaml.map"),El=Symbol.for("yaml.pair"),fr=Symbol.for("yaml.scalar"),ws=Symbol.for("yaml.seq"),Ne=Symbol.for("yaml.node.type"),_d=e=>!!e&&typeof e=="object"&&e[Ne]===ur,Ad=e=>!!e&&typeof e=="object"&&e[Ne]===vl,Nd=e=>!!e&&typeof e=="object"&&e[Ne]===bs,Rd=e=>!!e&&typeof e=="object"&&e[Ne]===El,kl=e=>!!e&&typeof e=="object"&&e[Ne]===fr,Pd=e=>!!e&&typeof e=="object"&&e[Ne]===ws;function Ol(e){if(e&&typeof e=="object")switch(e[Ne]){case bs:case ws:return!0}return!1}function Id(e){if(e&&typeof e=="object")switch(e[Ne]){case ur:case bs:case fr:case ws:return!0}return!1}var Td=e=>(kl(e)||Ol(e))&&!!e.anchor;J.ALIAS=ur;J.DOC=vl;J.MAP=bs;J.NODE_TYPE=Ne;J.PAIR=El;J.SCALAR=fr;J.SEQ=ws;J.hasAnchor=Td;J.isAlias=_d;J.isCollection=Ol;J.isDocument=Ad;J.isMap=Nd;J.isNode=Id;J.isPair=Rd;J.isScalar=kl;J.isSeq=Pd});var oi=y(dr=>{"use strict";var Y=I(),te=Symbol("break visit"),_l=Symbol("skip children"),Se=Symbol("remove node");function Ss(e,t){let i=Al(t);Y.isDocument(e)?Ct(null,e.contents,i,Object.freeze([e]))===Se&&(e.contents=null):Ct(null,e,i,Object.freeze([]))}Ss.BREAK=te;Ss.SKIP=_l;Ss.REMOVE=Se;function Ct(e,t,i,s){let n=Nl(e,t,i,s);if(Y.isNode(n)||Y.isPair(n))return Rl(e,s,n),Ct(e,n,i,s);if(typeof n!="symbol"){if(Y.isCollection(t)){s=Object.freeze(s.concat(t));for(let r=0;r{"use strict";var Pl=I(),Ld=oi(),Cd={"!":"%21",",":"%2C","[":"%5B","]":"%5D","{":"%7B","}":"%7D"},Md=e=>e.replace(/[!,[\]{}]/g,t=>Cd[t]),ai=class e{constructor(t,i){this.docStart=null,this.docEnd=!1,this.yaml=Object.assign({},e.defaultYaml,t),this.tags=Object.assign({},e.defaultTags,i)}clone(){let t=new e(this.yaml,this.tags);return t.docStart=this.docStart,t}atDocument(){let t=new e(this.yaml,this.tags);switch(this.yaml.version){case"1.1":this.atNextDocument=!0;break;case"1.2":this.atNextDocument=!1,this.yaml={explicit:e.defaultYaml.explicit,version:"1.2"},this.tags=Object.assign({},e.defaultTags);break}return t}add(t,i){this.atNextDocument&&(this.yaml={explicit:e.defaultYaml.explicit,version:"1.1"},this.tags=Object.assign({},e.defaultTags),this.atNextDocument=!1);let s=t.trim().split(/[ \t]+/),n=s.shift();switch(n){case"%TAG":{if(s.length!==2&&(i(0,"%TAG directive should contain exactly two parts"),s.length<2))return!1;let[r,o]=s;return this.tags[r]=o,!0}case"%YAML":{if(this.yaml.explicit=!0,s.length!==1)return i(0,"%YAML directive should contain exactly one part"),!1;let[r]=s;if(r==="1.1"||r==="1.2")return this.yaml.version=r,!0;{let o=/^\d+\.\d+$/.test(r);return i(6,`Unsupported YAML version ${r}`,o),!1}}default:return i(0,`Unknown directive ${n}`,!0),!1}}tagName(t,i){if(t==="!")return"!";if(t[0]!=="!")return i(`Not a valid tag: ${t}`),null;if(t[1]==="<"){let o=t.slice(2,-1);return o==="!"||o==="!!"?(i(`Verbatim tags aren't resolved, so ${t} is invalid.`),null):(t[t.length-1]!==">"&&i("Verbatim tags must end with a >"),o)}let[,s,n]=t.match(/^(.*!)([^!]*)$/s);n||i(`The ${t} tag has no suffix`);let r=this.tags[s];if(r)try{return r+decodeURIComponent(n)}catch(o){return i(String(o)),null}return s==="!"?t:(i(`Could not resolve tag: ${t}`),null)}tagString(t){for(let[i,s]of Object.entries(this.tags))if(t.startsWith(s))return i+Md(t.substring(s.length));return t[0]==="!"?t:`!<${t}>`}toString(t){let i=this.yaml.explicit?[`%YAML ${this.yaml.version||"1.2"}`]:[],s=Object.entries(this.tags),n;if(t&&s.length>0&&Pl.isNode(t.contents)){let r={};Ld.visit(t.contents,(o,a)=>{Pl.isNode(a)&&a.tag&&(r[a.tag]=!0)}),n=Object.keys(r)}else n=[];for(let[r,o]of s)r==="!!"&&o==="tag:yaml.org,2002:"||(!t||n.some(a=>a.startsWith(o)))&&i.push(`%TAG ${r} ${o}`);return i.join(` -`)}};ai.defaultYaml={explicit:!1,version:"1.2"};ai.defaultTags={"!!":"tag:yaml.org,2002:"};Il.Directives=ai});var Es=y(li=>{"use strict";var Tl=I(),Dd=oi();function $d(e){if(/[\x00-\x19\s,[\]{}]/.test(e)){let i=`Anchor must not contain whitespace or control characters: ${JSON.stringify(e)}`;throw new Error(i)}return!0}function Ll(e){let t=new Set;return Dd.visit(e,{Value(i,s){s.anchor&&t.add(s.anchor)}}),t}function Cl(e,t){for(let i=1;;++i){let s=`${e}${i}`;if(!t.has(s))return s}}function xd(e,t){let i=[],s=new Map,n=null;return{onAnchor:r=>{i.push(r),n??(n=Ll(e));let o=Cl(t,n);return n.add(o),o},setAnchors:()=>{for(let r of i){let o=s.get(r);if(typeof o=="object"&&o.anchor&&(Tl.isScalar(o.node)||Tl.isCollection(o.node)))o.node.anchor=o.anchor;else{let a=new Error("Failed to resolve repeated object (this should not happen)");throw a.source=r,a}}},sourceObjects:s}}li.anchorIsValid=$d;li.anchorNames=Ll;li.createNodeAnchors=xd;li.findNewAnchor=Cl});var mr=y(Ml=>{"use strict";function ci(e,t,i,s){if(s&&typeof s=="object")if(Array.isArray(s))for(let n=0,r=s.length;n{"use strict";var qd=I();function Dl(e,t,i){if(Array.isArray(e))return e.map((s,n)=>Dl(s,String(n),i));if(e&&typeof e.toJSON=="function"){if(!i||!qd.hasAnchor(e))return e.toJSON(t,i);let s={aliasCount:0,count:1,res:void 0};i.anchors.set(e,s),i.onCreate=r=>{s.res=r,delete i.onCreate};let n=e.toJSON(t,i);return i.onCreate&&i.onCreate(n),n}return typeof e=="bigint"&&!i?.keep?Number(e):e}$l.toJS=Dl});var ks=y(ql=>{"use strict";var Bd=mr(),xl=I(),Fd=Be(),gr=class{constructor(t){Object.defineProperty(this,xl.NODE_TYPE,{value:t})}clone(){let t=Object.create(Object.getPrototypeOf(this),Object.getOwnPropertyDescriptors(this));return this.range&&(t.range=this.range.slice()),t}toJS(t,{mapAsMap:i,maxAliasCount:s,onAnchor:n,reviver:r}={}){if(!xl.isDocument(t))throw new TypeError("A document argument is required");let o={anchors:new Map,doc:t,keep:!0,mapAsMap:i===!0,mapKeyWarned:!1,maxAliasCount:typeof s=="number"?s:100},a=Fd.toJS(this,"",o);if(typeof n=="function")for(let{count:l,res:c}of o.anchors.values())n(c,l);return typeof r=="function"?Bd.applyReviver(r,{"":a},"",a):a}};ql.NodeBase=gr});var hi=y(Bl=>{"use strict";var jd=Es(),Kd=oi(),Dt=I(),Ud=ks(),zd=Be(),yr=class extends Ud.NodeBase{constructor(t){super(Dt.ALIAS),this.source=t,Object.defineProperty(this,"tag",{set(){throw new Error("Alias nodes cannot have tags")}})}resolve(t,i){let s;i?.aliasResolveCache?s=i.aliasResolveCache:(s=[],Kd.visit(t,{Node:(r,o)=>{(Dt.isAlias(o)||Dt.hasAnchor(o))&&s.push(o)}}),i&&(i.aliasResolveCache=s));let n;for(let r of s){if(r===this)break;r.anchor===this.source&&(n=r)}return n}toJSON(t,i){if(!i)return{source:this.source};let{anchors:s,doc:n,maxAliasCount:r}=i,o=this.resolve(n,i);if(!o){let l=`Unresolved alias (the anchor must be set before the alias): ${this.source}`;throw new ReferenceError(l)}let a=s.get(o);if(a||(zd.toJS(o,null,i),a=s.get(o)),a?.res===void 0){let l="This should not happen: Alias anchor was not resolved?";throw new ReferenceError(l)}if(r>=0&&(a.count+=1,a.aliasCount===0&&(a.aliasCount=Os(n,o,s)),a.count*a.aliasCount>r)){let l="Excessive alias count indicates a resource exhaustion attack";throw new ReferenceError(l)}return a.res}toString(t,i,s){let n=`*${this.source}`;if(t){if(jd.anchorIsValid(this.source),t.options.verifyAliasOrder&&!t.anchors.has(this.source)){let r=`Unresolved alias (the anchor must be set before the alias): ${this.source}`;throw new Error(r)}if(t.implicitKey)return`${n} `}return n}};function Os(e,t,i){if(Dt.isAlias(t)){let s=t.resolve(e),n=i&&s&&i.get(s);return n?n.count*n.aliasCount:0}else if(Dt.isCollection(t)){let s=0;for(let n of t.items){let r=Os(e,n,i);r>s&&(s=r)}return s}else if(Dt.isPair(t)){let s=Os(e,t.key,i),n=Os(e,t.value,i);return Math.max(s,n)}return 1}Bl.Alias=yr});var B=y(br=>{"use strict";var Yd=I(),Gd=ks(),Wd=Be(),Hd=e=>!e||typeof e!="function"&&typeof e!="object",Fe=class extends Gd.NodeBase{constructor(t){super(Yd.SCALAR),this.value=t}toJSON(t,i){return i?.keep?this.value:Wd.toJS(this.value,t,i)}toString(){return String(this.value)}};Fe.BLOCK_FOLDED="BLOCK_FOLDED";Fe.BLOCK_LITERAL="BLOCK_LITERAL";Fe.PLAIN="PLAIN";Fe.QUOTE_DOUBLE="QUOTE_DOUBLE";Fe.QUOTE_SINGLE="QUOTE_SINGLE";br.Scalar=Fe;br.isScalarValue=Hd});var ui=y(jl=>{"use strict";var Vd=hi(),lt=I(),Fl=B(),Jd="tag:yaml.org,2002:";function Zd(e,t,i){if(t){let s=i.filter(r=>r.tag===t),n=s.find(r=>!r.format)??s[0];if(!n)throw new Error(`Tag ${t} not found`);return n}return i.find(s=>s.identify?.(e)&&!s.format)}function Xd(e,t,i){if(lt.isDocument(e)&&(e=e.contents),lt.isNode(e))return e;if(lt.isPair(e)){let u=i.schema[lt.MAP].createNode?.(i.schema,null,i);return u.items.push(e),u}(e instanceof String||e instanceof Number||e instanceof Boolean||typeof BigInt<"u"&&e instanceof BigInt)&&(e=e.valueOf());let{aliasDuplicateObjects:s,onAnchor:n,onTagObj:r,schema:o,sourceObjects:a}=i,l;if(s&&e&&typeof e=="object"){if(l=a.get(e),l)return l.anchor??(l.anchor=n(e)),new Vd.Alias(l.anchor);l={anchor:null,node:null},a.set(e,l)}t?.startsWith("!!")&&(t=Jd+t.slice(2));let c=Zd(e,t,o.tags);if(!c){if(e&&typeof e.toJSON=="function"&&(e=e.toJSON()),!e||typeof e!="object"){let u=new Fl.Scalar(e);return l&&(l.node=u),u}c=e instanceof Map?o[lt.MAP]:Symbol.iterator in Object(e)?o[lt.SEQ]:o[lt.MAP]}r&&(r(c),delete i.onTagObj);let h=c?.createNode?c.createNode(i.schema,e,i):typeof c?.nodeClass?.from=="function"?c.nodeClass.from(i.schema,e,i):new Fl.Scalar(e);return t?h.tag=t:c.default||(h.tag=c.tag),l&&(l.node=h),h}jl.createNode=Xd});var As=y(_s=>{"use strict";var Qd=ui(),ve=I(),ep=ks();function wr(e,t,i){let s=i;for(let n=t.length-1;n>=0;--n){let r=t[n];if(typeof r=="number"&&Number.isInteger(r)&&r>=0){let o=[];o[r]=s,s=o}else s=new Map([[r,s]])}return Qd.createNode(s,void 0,{aliasDuplicateObjects:!1,keepUndefined:!1,onAnchor:()=>{throw new Error("This should not happen, please report a bug.")},schema:e,sourceObjects:new Map})}var Kl=e=>e==null||typeof e=="object"&&!!e[Symbol.iterator]().next().done,Sr=class extends ep.NodeBase{constructor(t,i){super(t),Object.defineProperty(this,"schema",{value:i,configurable:!0,enumerable:!1,writable:!0})}clone(t){let i=Object.create(Object.getPrototypeOf(this),Object.getOwnPropertyDescriptors(this));return t&&(i.schema=t),i.items=i.items.map(s=>ve.isNode(s)||ve.isPair(s)?s.clone(t):s),this.range&&(i.range=this.range.slice()),i}addIn(t,i){if(Kl(t))this.add(i);else{let[s,...n]=t,r=this.get(s,!0);if(ve.isCollection(r))r.addIn(n,i);else if(r===void 0&&this.schema)this.set(s,wr(this.schema,n,i));else throw new Error(`Expected YAML collection at ${s}. Remaining path: ${n}`)}}deleteIn(t){let[i,...s]=t;if(s.length===0)return this.delete(i);let n=this.get(i,!0);if(ve.isCollection(n))return n.deleteIn(s);throw new Error(`Expected YAML collection at ${i}. Remaining path: ${s}`)}getIn(t,i){let[s,...n]=t,r=this.get(s,!0);return n.length===0?!i&&ve.isScalar(r)?r.value:r:ve.isCollection(r)?r.getIn(n,i):void 0}hasAllNullValues(t){return this.items.every(i=>{if(!ve.isPair(i))return!1;let s=i.value;return s==null||t&&ve.isScalar(s)&&s.value==null&&!s.commentBefore&&!s.comment&&!s.tag})}hasIn(t){let[i,...s]=t;if(s.length===0)return this.has(i);let n=this.get(i,!0);return ve.isCollection(n)?n.hasIn(s):!1}setIn(t,i){let[s,...n]=t;if(n.length===0)this.set(s,i);else{let r=this.get(s,!0);if(ve.isCollection(r))r.setIn(n,i);else if(r===void 0&&this.schema)this.set(s,wr(this.schema,n,i));else throw new Error(`Expected YAML collection at ${s}. Remaining path: ${n}`)}}};_s.Collection=Sr;_s.collectionFromPath=wr;_s.isEmptyPath=Kl});var fi=y(Ns=>{"use strict";var tp=e=>e.replace(/^(?!$)(?: $)?/gm,"#");function vr(e,t){return/^\n+$/.test(e)?e.substring(1):t?e.replace(/^(?! *$)/gm,t):e}var ip=(e,t,i)=>e.endsWith(` -`)?vr(i,t):i.includes(` -`)?` -`+vr(i,t):(e.endsWith(" ")?"":" ")+i;Ns.indentComment=vr;Ns.lineComment=ip;Ns.stringifyComment=tp});var zl=y(di=>{"use strict";var sp="flow",Er="block",Rs="quoted";function np(e,t,i="flow",{indentAtStart:s,lineWidth:n=80,minContentWidth:r=20,onFold:o,onOverflow:a}={}){if(!n||n<0)return e;nn-Math.max(2,r)?c.push(0):u=n-s);let f,p,g=!1,d=-1,m=-1,w=-1;i===Er&&(d=Ul(e,d,t.length),d!==-1&&(u=d+l));for(let k;k=e[d+=1];){if(i===Rs&&k==="\\"){switch(m=d,e[d+1]){case"x":d+=3;break;case"u":d+=5;break;case"U":d+=9;break;default:d+=1}w=d}if(k===` -`)i===Er&&(d=Ul(e,d,t.length)),u=d+t.length+l,f=void 0;else{if(k===" "&&p&&p!==" "&&p!==` -`&&p!==" "){let _=e[d+1];_&&_!==" "&&_!==` -`&&_!==" "&&(f=d)}if(d>=u)if(f)c.push(f),u=f+l,f=void 0;else if(i===Rs){for(;p===" "||p===" ";)p=k,k=e[d+=1],g=!0;let _=d>w+1?d-2:m-1;if(h[_])return e;c.push(_),h[_]=!0,u=_+l,f=void 0}else g=!0}p=k}if(g&&a&&a(),c.length===0)return e;o&&o();let E=e.slice(0,c[0]);for(let k=0;k{"use strict";var he=B(),je=zl(),Is=(e,t)=>({indentAtStart:t?e.indent.length:e.indentAtStart,lineWidth:e.options.lineWidth,minContentWidth:e.options.minContentWidth}),Ts=e=>/^(%|---|\.\.\.)/m.test(e);function rp(e,t,i){if(!t||t<0)return!1;let s=t-i,n=e.length;if(n<=s)return!1;for(let r=0,o=0;rs)return!0;if(o=r+1,n-o<=s)return!1}return!0}function pi(e,t){let i=JSON.stringify(e);if(t.options.doubleQuotedAsJSON)return i;let{implicitKey:s}=t,n=t.options.doubleQuotedMinMultiLineLength,r=t.indent||(Ts(e)?" ":""),o="",a=0;for(let l=0,c=i[l];c;c=i[++l])if(c===" "&&i[l+1]==="\\"&&i[l+2]==="n"&&(o+=i.slice(a,l)+"\\ ",l+=1,a=l,c="\\"),c==="\\")switch(i[l+1]){case"u":{o+=i.slice(a,l);let h=i.substr(l+2,4);switch(h){case"0000":o+="\\0";break;case"0007":o+="\\a";break;case"000b":o+="\\v";break;case"001b":o+="\\e";break;case"0085":o+="\\N";break;case"00a0":o+="\\_";break;case"2028":o+="\\L";break;case"2029":o+="\\P";break;default:h.substr(0,2)==="00"?o+="\\x"+h.substr(2):o+=i.substr(l,6)}l+=5,a=l+1}break;case"n":if(s||i[l+2]==='"'||i.length -`;let u,f;for(f=i.length;f>0;--f){let A=i[f-1];if(A!==` -`&&A!==" "&&A!==" ")break}let p=i.substring(f),g=p.indexOf(` -`);g===-1?u="-":i===p||g!==p.length-1?(u="+",r&&r()):u="",p&&(i=i.slice(0,-p.length),p[p.length-1]===` -`&&(p=p.slice(0,-1)),p=p.replace(Or,`$&${c}`));let d=!1,m,w=-1;for(m=0;m{N=!0});let v=je.foldFlowLines(`${E}${A}${p}`,c,je.FOLD_BLOCK,C);if(!N)return`>${_} -${c}${v}`}return i=i.replace(/\n+/g,`$&${c}`),`|${_} -${c}${E}${i}${p}`}function op(e,t,i,s){let{type:n,value:r}=e,{actualString:o,implicitKey:a,indent:l,indentStep:c,inFlow:h}=t;if(a&&r.includes(` -`)||h&&/[[\]{},]/.test(r))return $t(r,t);if(/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(r))return a||h||!r.includes(` -`)?$t(r,t):Ps(e,t,i,s);if(!a&&!h&&n!==he.Scalar.PLAIN&&r.includes(` -`))return Ps(e,t,i,s);if(Ts(r)){if(l==="")return t.forceBlockIndent=!0,Ps(e,t,i,s);if(a&&l===c)return $t(r,t)}let u=r.replace(/\n+/g,`$& -${l}`);if(o){let f=d=>d.default&&d.tag!=="tag:yaml.org,2002:str"&&d.test?.test(u),{compat:p,tags:g}=t.doc.schema;if(g.some(f)||p?.some(f))return $t(r,t)}return a?u:je.foldFlowLines(u,l,je.FOLD_FLOW,Is(t,!1))}function ap(e,t,i,s){let{implicitKey:n,inFlow:r}=t,o=typeof e.value=="string"?e:Object.assign({},e,{value:String(e.value)}),{type:a}=e;a!==he.Scalar.QUOTE_DOUBLE&&/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(o.value)&&(a=he.Scalar.QUOTE_DOUBLE);let l=h=>{switch(h){case he.Scalar.BLOCK_FOLDED:case he.Scalar.BLOCK_LITERAL:return n||r?$t(o.value,t):Ps(o,t,i,s);case he.Scalar.QUOTE_DOUBLE:return pi(o.value,t);case he.Scalar.QUOTE_SINGLE:return kr(o.value,t);case he.Scalar.PLAIN:return op(o,t,i,s);default:return null}},c=l(a);if(c===null){let{defaultKeyType:h,defaultStringType:u}=t.options,f=n&&h||u;if(c=l(f),c===null)throw new Error(`Unsupported default string type ${f}`)}return c}Yl.stringifyString=ap});var gi=y(_r=>{"use strict";var lp=Es(),Ke=I(),cp=fi(),hp=mi();function up(e,t){let i=Object.assign({blockQuote:!0,commentString:cp.stringifyComment,defaultKeyType:null,defaultStringType:"PLAIN",directives:null,doubleQuotedAsJSON:!1,doubleQuotedMinMultiLineLength:40,falseStr:"false",flowCollectionPadding:!0,indentSeq:!0,lineWidth:80,minContentWidth:20,nullStr:"null",simpleKeys:!1,singleQuote:null,trailingComma:!1,trueStr:"true",verifyAliasOrder:!0},e.schema.toStringOptions,t),s;switch(i.collectionStyle){case"block":s=!1;break;case"flow":s=!0;break;default:s=null}return{anchors:new Set,doc:e,flowCollectionPadding:i.flowCollectionPadding?" ":"",indent:"",indentStep:typeof i.indent=="number"?" ".repeat(i.indent):" ",inFlow:s,options:i}}function fp(e,t){if(t.tag){let n=e.filter(r=>r.tag===t.tag);if(n.length>0)return n.find(r=>r.format===t.format)??n[0]}let i,s;if(Ke.isScalar(t)){s=t.value;let n=e.filter(r=>r.identify?.(s));if(n.length>1){let r=n.filter(o=>o.test);r.length>0&&(n=r)}i=n.find(r=>r.format===t.format)??n.find(r=>!r.format)}else s=t,i=e.find(n=>n.nodeClass&&s instanceof n.nodeClass);if(!i){let n=s?.constructor?.name??(s===null?"null":typeof s);throw new Error(`Tag not resolved for ${n} value`)}return i}function dp(e,t,{anchors:i,doc:s}){if(!s.directives)return"";let n=[],r=(Ke.isScalar(e)||Ke.isCollection(e))&&e.anchor;r&&lp.anchorIsValid(r)&&(i.add(r),n.push(`&${r}`));let o=e.tag??(t.default?null:t.tag);return o&&n.push(s.directives.tagString(o)),n.join(" ")}function pp(e,t,i,s){if(Ke.isPair(e))return e.toString(t,i,s);if(Ke.isAlias(e)){if(t.doc.directives)return e.toString(t);if(t.resolvedAliases?.has(e))throw new TypeError("Cannot stringify circular structure without alias nodes");t.resolvedAliases?t.resolvedAliases.add(e):t.resolvedAliases=new Set([e]),e=e.resolve(t.doc)}let n,r=Ke.isNode(e)?e:t.doc.createNode(e,{onTagObj:l=>n=l});n??(n=fp(t.doc.schema.tags,r));let o=dp(r,n,t);o.length>0&&(t.indentAtStart=(t.indentAtStart??0)+o.length+1);let a=typeof n.stringify=="function"?n.stringify(r,t,i,s):Ke.isScalar(r)?hp.stringifyString(r,t,i,s):r.toString(t,i,s);return o?Ke.isScalar(r)||a[0]==="{"||a[0]==="["?`${o} ${a}`:`${o} -${t.indent}${a}`:a}_r.createStringifyContext=up;_r.stringify=pp});var Vl=y(Hl=>{"use strict";var Re=I(),Gl=B(),Wl=gi(),yi=fi();function mp({key:e,value:t},i,s,n){let{allNullValues:r,doc:o,indent:a,indentStep:l,options:{commentString:c,indentSeq:h,simpleKeys:u}}=i,f=Re.isNode(e)&&e.comment||null;if(u){if(f)throw new Error("With simple keys, key nodes cannot have comments");if(Re.isCollection(e)||!Re.isNode(e)&&typeof e=="object"){let C="With simple keys, collection cannot be used as a key value";throw new Error(C)}}let p=!u&&(!e||f&&t==null&&!i.inFlow||Re.isCollection(e)||(Re.isScalar(e)?e.type===Gl.Scalar.BLOCK_FOLDED||e.type===Gl.Scalar.BLOCK_LITERAL:typeof e=="object"));i=Object.assign({},i,{allNullValues:!1,implicitKey:!p&&(u||!r),indent:a+l});let g=!1,d=!1,m=Wl.stringify(e,i,()=>g=!0,()=>d=!0);if(!p&&!i.inFlow&&m.length>1024){if(u)throw new Error("With simple keys, single line scalar must not span more than 1024 characters");p=!0}if(i.inFlow){if(r||t==null)return g&&s&&s(),m===""?"?":p?`? ${m}`:m}else if(r&&!u||t==null&&p)return m=`? ${m}`,f&&!g?m+=yi.lineComment(m,i.indent,c(f)):d&&n&&n(),m;g&&(f=null),p?(f&&(m+=yi.lineComment(m,i.indent,c(f))),m=`? ${m} -${a}:`):(m=`${m}:`,f&&(m+=yi.lineComment(m,i.indent,c(f))));let w,E,k;Re.isNode(t)?(w=!!t.spaceBefore,E=t.commentBefore,k=t.comment):(w=!1,E=null,k=null,t&&typeof t=="object"&&(t=o.createNode(t))),i.implicitKey=!1,!p&&!f&&Re.isScalar(t)&&(i.indentAtStart=m.length+1),d=!1,!h&&l.length>=2&&!i.inFlow&&!p&&Re.isSeq(t)&&!t.flow&&!t.tag&&!t.anchor&&(i.indent=i.indent.substring(2));let _=!1,A=Wl.stringify(t,i,()=>_=!0,()=>d=!0),N=" ";if(f||w||E){if(N=w?` -`:"",E){let C=c(E);N+=` -${yi.indentComment(C,i.indent)}`}A===""&&!i.inFlow?N===` -`&&k&&(N=` - -`):N+=` -${i.indent}`}else if(!p&&Re.isCollection(t)){let C=A[0],v=A.indexOf(` -`),U=v!==-1,qe=i.inFlow??t.flow??t.items.length===0;if(U||!qe){let Lt=!1;if(U&&(C==="&"||C==="!")){let z=A.indexOf(" ");C==="&"&&z!==-1&&z{"use strict";var Jl=require("process");function gp(e,...t){e==="debug"&&console.log(...t)}function yp(e,t){(e==="debug"||e==="warn")&&(typeof Jl.emitWarning=="function"?Jl.emitWarning(t):console.warn(t))}Ar.debug=gp;Ar.warn=yp});var Ds=y(Ms=>{"use strict";var bi=I(),Zl=B(),Ls="<<",Cs={identify:e=>e===Ls||typeof e=="symbol"&&e.description===Ls,default:"key",tag:"tag:yaml.org,2002:merge",test:/^<<$/,resolve:()=>Object.assign(new Zl.Scalar(Symbol(Ls)),{addToJSMap:Xl}),stringify:()=>Ls},bp=(e,t)=>(Cs.identify(t)||bi.isScalar(t)&&(!t.type||t.type===Zl.Scalar.PLAIN)&&Cs.identify(t.value))&&e?.doc.schema.tags.some(i=>i.tag===Cs.tag&&i.default);function Xl(e,t,i){if(i=e&&bi.isAlias(i)?i.resolve(e.doc):i,bi.isSeq(i))for(let s of i.items)Rr(e,t,s);else if(Array.isArray(i))for(let s of i)Rr(e,t,s);else Rr(e,t,i)}function Rr(e,t,i){let s=e&&bi.isAlias(i)?i.resolve(e.doc):i;if(!bi.isMap(s))throw new Error("Merge sources must be maps or map aliases");let n=s.toJSON(null,e,Map);for(let[r,o]of n)t instanceof Map?t.has(r)||t.set(r,o):t instanceof Set?t.add(r):Object.prototype.hasOwnProperty.call(t,r)||Object.defineProperty(t,r,{value:o,writable:!0,enumerable:!0,configurable:!0});return t}Ms.addMergeToJSMap=Xl;Ms.isMergeKey=bp;Ms.merge=Cs});var Ir=y(tc=>{"use strict";var wp=Nr(),Ql=Ds(),Sp=gi(),ec=I(),Pr=Be();function vp(e,t,{key:i,value:s}){if(ec.isNode(i)&&i.addToJSMap)i.addToJSMap(e,t,s);else if(Ql.isMergeKey(e,i))Ql.addMergeToJSMap(e,t,s);else{let n=Pr.toJS(i,"",e);if(t instanceof Map)t.set(n,Pr.toJS(s,n,e));else if(t instanceof Set)t.add(n);else{let r=Ep(i,n,e),o=Pr.toJS(s,r,e);r in t?Object.defineProperty(t,r,{value:o,writable:!0,enumerable:!0,configurable:!0}):t[r]=o}}return t}function Ep(e,t,i){if(t===null)return"";if(typeof t!="object")return String(t);if(ec.isNode(e)&&i?.doc){let s=Sp.createStringifyContext(i.doc,{});s.anchors=new Set;for(let r of i.anchors.keys())s.anchors.add(r.anchor);s.inFlow=!0,s.inStringifyKey=!0;let n=e.toString(s);if(!i.mapKeyWarned){let r=JSON.stringify(n);r.length>40&&(r=r.substring(0,36)+'..."'),wp.warn(i.doc.options.logLevel,`Keys with collection values will be stringified due to JS Object restrictions: ${r}. Set mapAsMap: true to use object keys.`),i.mapKeyWarned=!0}return n}return JSON.stringify(t)}tc.addPairToJSMap=vp});var Ue=y(Tr=>{"use strict";var ic=ui(),kp=Vl(),Op=Ir(),$s=I();function _p(e,t,i){let s=ic.createNode(e,void 0,i),n=ic.createNode(t,void 0,i);return new xs(s,n)}var xs=class e{constructor(t,i=null){Object.defineProperty(this,$s.NODE_TYPE,{value:$s.PAIR}),this.key=t,this.value=i}clone(t){let{key:i,value:s}=this;return $s.isNode(i)&&(i=i.clone(t)),$s.isNode(s)&&(s=s.clone(t)),new e(i,s)}toJSON(t,i){let s=i?.mapAsMap?new Map:{};return Op.addPairToJSMap(i,s,this)}toString(t,i,s){return t?.doc?kp.stringifyPair(this,t,i,s):JSON.stringify(this)}};Tr.Pair=xs;Tr.createPair=_p});var Lr=y(nc=>{"use strict";var ct=I(),sc=gi(),qs=fi();function Ap(e,t,i){return(t.inFlow??e.flow?Rp:Np)(e,t,i)}function Np({comment:e,items:t},i,{blockItemPrefix:s,flowChars:n,itemIndent:r,onChompKeep:o,onComment:a}){let{indent:l,options:{commentString:c}}=i,h=Object.assign({},i,{indent:r,type:null}),u=!1,f=[];for(let g=0;gm=null,()=>u=!0);m&&(w+=qs.lineComment(w,r,c(m))),u&&m&&(u=!1),f.push(s+w)}let p;if(f.length===0)p=n.start+n.end;else{p=f[0];for(let g=1;gm=null);c||(c=u.length>h||w.includes(` -`)),g0&&(c||(c=u.reduce((E,k)=>E+k.length+2,2)+(w.length+2)>t.options.lineWidth)),c&&(w+=",")),m&&(w+=qs.lineComment(w,s,a(m))),u.push(w),h=u.length}let{start:f,end:p}=i;if(u.length===0)return f+p;if(!c){let g=u.reduce((d,m)=>d+m.length+2,2);c=t.options.lineWidth>0&&g>t.options.lineWidth}if(c){let g=f;for(let d of u)g+=d?` -${r}${n}${d}`:` -`;return`${g} -${n}${p}`}else return`${f}${o}${u.join(" ")}${o}${p}`}function Bs({indent:e,options:{commentString:t}},i,s,n){if(s&&n&&(s=s.replace(/^\n+/,"")),s){let r=qs.indentComment(t(s),e);i.push(r.trimStart())}}nc.stringifyCollection=Ap});var Ye=y(Mr=>{"use strict";var Pp=Lr(),Ip=Ir(),Tp=As(),ze=I(),Fs=Ue(),Lp=B();function wi(e,t){let i=ze.isScalar(t)?t.value:t;for(let s of e)if(ze.isPair(s)&&(s.key===t||s.key===i||ze.isScalar(s.key)&&s.key.value===i))return s}var Cr=class extends Tp.Collection{static get tagName(){return"tag:yaml.org,2002:map"}constructor(t){super(ze.MAP,t),this.items=[]}static from(t,i,s){let{keepUndefined:n,replacer:r}=s,o=new this(t),a=(l,c)=>{if(typeof r=="function")c=r.call(i,l,c);else if(Array.isArray(r)&&!r.includes(l))return;(c!==void 0||n)&&o.items.push(Fs.createPair(l,c,s))};if(i instanceof Map)for(let[l,c]of i)a(l,c);else if(i&&typeof i=="object")for(let l of Object.keys(i))a(l,i[l]);return typeof t.sortMapEntries=="function"&&o.items.sort(t.sortMapEntries),o}add(t,i){let s;ze.isPair(t)?s=t:!t||typeof t!="object"||!("key"in t)?s=new Fs.Pair(t,t?.value):s=new Fs.Pair(t.key,t.value);let n=wi(this.items,s.key),r=this.schema?.sortMapEntries;if(n){if(!i)throw new Error(`Key ${s.key} already set`);ze.isScalar(n.value)&&Lp.isScalarValue(s.value)?n.value.value=s.value:n.value=s.value}else if(r){let o=this.items.findIndex(a=>r(s,a)<0);o===-1?this.items.push(s):this.items.splice(o,0,s)}else this.items.push(s)}delete(t){let i=wi(this.items,t);return i?this.items.splice(this.items.indexOf(i),1).length>0:!1}get(t,i){let n=wi(this.items,t)?.value;return(!i&&ze.isScalar(n)?n.value:n)??void 0}has(t){return!!wi(this.items,t)}set(t,i){this.add(new Fs.Pair(t,i),!0)}toJSON(t,i,s){let n=s?new s:i?.mapAsMap?new Map:{};i?.onCreate&&i.onCreate(n);for(let r of this.items)Ip.addPairToJSMap(i,n,r);return n}toString(t,i,s){if(!t)return JSON.stringify(this);for(let n of this.items)if(!ze.isPair(n))throw new Error(`Map items must all be pairs; found ${JSON.stringify(n)} instead`);return!t.allNullValues&&this.hasAllNullValues(!1)&&(t=Object.assign({},t,{allNullValues:!0})),Pp.stringifyCollection(this,t,{blockItemPrefix:"",flowChars:{start:"{",end:"}"},itemIndent:t.indent||"",onChompKeep:s,onComment:i})}};Mr.YAMLMap=Cr;Mr.findPair=wi});var xt=y(oc=>{"use strict";var Cp=I(),rc=Ye(),Mp={collection:"map",default:!0,nodeClass:rc.YAMLMap,tag:"tag:yaml.org,2002:map",resolve(e,t){return Cp.isMap(e)||t("Expected a mapping for this tag"),e},createNode:(e,t,i)=>rc.YAMLMap.from(e,t,i)};oc.map=Mp});var Ge=y(ac=>{"use strict";var Dp=ui(),$p=Lr(),xp=As(),Ks=I(),qp=B(),Bp=Be(),Dr=class extends xp.Collection{static get tagName(){return"tag:yaml.org,2002:seq"}constructor(t){super(Ks.SEQ,t),this.items=[]}add(t){this.items.push(t)}delete(t){let i=js(t);return typeof i!="number"?!1:this.items.splice(i,1).length>0}get(t,i){let s=js(t);if(typeof s!="number")return;let n=this.items[s];return!i&&Ks.isScalar(n)?n.value:n}has(t){let i=js(t);return typeof i=="number"&&i=0?t:null}ac.YAMLSeq=Dr});var qt=y(cc=>{"use strict";var Fp=I(),lc=Ge(),jp={collection:"seq",default:!0,nodeClass:lc.YAMLSeq,tag:"tag:yaml.org,2002:seq",resolve(e,t){return Fp.isSeq(e)||t("Expected a sequence for this tag"),e},createNode:(e,t,i)=>lc.YAMLSeq.from(e,t,i)};cc.seq=jp});var Si=y(hc=>{"use strict";var Kp=mi(),Up={identify:e=>typeof e=="string",default:!0,tag:"tag:yaml.org,2002:str",resolve:e=>e,stringify(e,t,i,s){return t=Object.assign({actualString:!0},t),Kp.stringifyString(e,t,i,s)}};hc.string=Up});var Us=y(dc=>{"use strict";var uc=B(),fc={identify:e=>e==null,createNode:()=>new uc.Scalar(null),default:!0,tag:"tag:yaml.org,2002:null",test:/^(?:~|[Nn]ull|NULL)?$/,resolve:()=>new uc.Scalar(null),stringify:({source:e},t)=>typeof e=="string"&&fc.test.test(e)?e:t.options.nullStr};dc.nullTag=fc});var $r=y(mc=>{"use strict";var zp=B(),pc={identify:e=>typeof e=="boolean",default:!0,tag:"tag:yaml.org,2002:bool",test:/^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,resolve:e=>new zp.Scalar(e[0]==="t"||e[0]==="T"),stringify({source:e,value:t},i){if(e&&pc.test.test(e)){let s=e[0]==="t"||e[0]==="T";if(t===s)return e}return t?i.options.trueStr:i.options.falseStr}};mc.boolTag=pc});var Bt=y(gc=>{"use strict";function Yp({format:e,minFractionDigits:t,tag:i,value:s}){if(typeof s=="bigint")return String(s);let n=typeof s=="number"?s:Number(s);if(!isFinite(n))return isNaN(n)?".nan":n<0?"-.inf":".inf";let r=Object.is(s,-0)?"-0":JSON.stringify(s);if(!e&&t&&(!i||i==="tag:yaml.org,2002:float")&&/^\d/.test(r)){let o=r.indexOf(".");o<0&&(o=r.length,r+=".");let a=t-(r.length-o-1);for(;a-- >0;)r+="0"}return r}gc.stringifyNumber=Yp});var qr=y(zs=>{"use strict";var Gp=B(),xr=Bt(),Wp={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",test:/^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,resolve:e=>e.slice(-3).toLowerCase()==="nan"?NaN:e[0]==="-"?Number.NEGATIVE_INFINITY:Number.POSITIVE_INFINITY,stringify:xr.stringifyNumber},Hp={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",format:"EXP",test:/^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,resolve:e=>parseFloat(e),stringify(e){let t=Number(e.value);return isFinite(t)?t.toExponential():xr.stringifyNumber(e)}},Vp={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",test:/^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/,resolve(e){let t=new Gp.Scalar(parseFloat(e)),i=e.indexOf(".");return i!==-1&&e[e.length-1]==="0"&&(t.minFractionDigits=e.length-i-1),t},stringify:xr.stringifyNumber};zs.float=Vp;zs.floatExp=Hp;zs.floatNaN=Wp});var Fr=y(Gs=>{"use strict";var yc=Bt(),Ys=e=>typeof e=="bigint"||Number.isInteger(e),Br=(e,t,i,{intAsBigInt:s})=>s?BigInt(e):parseInt(e.substring(t),i);function bc(e,t,i){let{value:s}=e;return Ys(s)&&s>=0?i+s.toString(t):yc.stringifyNumber(e)}var Jp={identify:e=>Ys(e)&&e>=0,default:!0,tag:"tag:yaml.org,2002:int",format:"OCT",test:/^0o[0-7]+$/,resolve:(e,t,i)=>Br(e,2,8,i),stringify:e=>bc(e,8,"0o")},Zp={identify:Ys,default:!0,tag:"tag:yaml.org,2002:int",test:/^[-+]?[0-9]+$/,resolve:(e,t,i)=>Br(e,0,10,i),stringify:yc.stringifyNumber},Xp={identify:e=>Ys(e)&&e>=0,default:!0,tag:"tag:yaml.org,2002:int",format:"HEX",test:/^0x[0-9a-fA-F]+$/,resolve:(e,t,i)=>Br(e,2,16,i),stringify:e=>bc(e,16,"0x")};Gs.int=Zp;Gs.intHex=Xp;Gs.intOct=Jp});var Sc=y(wc=>{"use strict";var Qp=xt(),em=Us(),tm=qt(),im=Si(),sm=$r(),jr=qr(),Kr=Fr(),nm=[Qp.map,tm.seq,im.string,em.nullTag,sm.boolTag,Kr.intOct,Kr.int,Kr.intHex,jr.floatNaN,jr.floatExp,jr.float];wc.schema=nm});var kc=y(Ec=>{"use strict";var rm=B(),om=xt(),am=qt();function vc(e){return typeof e=="bigint"||Number.isInteger(e)}var Ws=({value:e})=>JSON.stringify(e),lm=[{identify:e=>typeof e=="string",default:!0,tag:"tag:yaml.org,2002:str",resolve:e=>e,stringify:Ws},{identify:e=>e==null,createNode:()=>new rm.Scalar(null),default:!0,tag:"tag:yaml.org,2002:null",test:/^null$/,resolve:()=>null,stringify:Ws},{identify:e=>typeof e=="boolean",default:!0,tag:"tag:yaml.org,2002:bool",test:/^true$|^false$/,resolve:e=>e==="true",stringify:Ws},{identify:vc,default:!0,tag:"tag:yaml.org,2002:int",test:/^-?(?:0|[1-9][0-9]*)$/,resolve:(e,t,{intAsBigInt:i})=>i?BigInt(e):parseInt(e,10),stringify:({value:e})=>vc(e)?e.toString():JSON.stringify(e)},{identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",test:/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,resolve:e=>parseFloat(e),stringify:Ws}],cm={default:!0,tag:"",test:/^/,resolve(e,t){return t(`Unresolved plain scalar ${JSON.stringify(e)}`),e}},hm=[om.map,am.seq].concat(lm,cm);Ec.schema=hm});var zr=y(Oc=>{"use strict";var vi=require("buffer"),Ur=B(),um=mi(),fm={identify:e=>e instanceof Uint8Array,default:!1,tag:"tag:yaml.org,2002:binary",resolve(e,t){if(typeof vi.Buffer=="function")return vi.Buffer.from(e,"base64");if(typeof atob=="function"){let i=atob(e.replace(/[\n\r]/g,"")),s=new Uint8Array(i.length);for(let n=0;n{"use strict";var Hs=I(),Yr=Ue(),dm=B(),pm=Ge();function _c(e,t){if(Hs.isSeq(e))for(let i=0;i1&&t("Each pair must have its own sequence indicator");let n=s.items[0]||new Yr.Pair(new dm.Scalar(null));if(s.commentBefore&&(n.key.commentBefore=n.key.commentBefore?`${s.commentBefore} -${n.key.commentBefore}`:s.commentBefore),s.comment){let r=n.value??n.key;r.comment=r.comment?`${s.comment} -${r.comment}`:s.comment}s=n}e.items[i]=Hs.isPair(s)?s:new Yr.Pair(s)}}else t("Expected a sequence for this tag");return e}function Ac(e,t,i){let{replacer:s}=i,n=new pm.YAMLSeq(e);n.tag="tag:yaml.org,2002:pairs";let r=0;if(t&&Symbol.iterator in Object(t))for(let o of t){typeof s=="function"&&(o=s.call(t,String(r++),o));let a,l;if(Array.isArray(o))if(o.length===2)a=o[0],l=o[1];else throw new TypeError(`Expected [key, value] tuple: ${o}`);else if(o&&o instanceof Object){let c=Object.keys(o);if(c.length===1)a=c[0],l=o[a];else throw new TypeError(`Expected tuple with one key, not ${c.length} keys`)}else a=o;n.items.push(Yr.createPair(a,l,i))}return n}var mm={collection:"seq",default:!1,tag:"tag:yaml.org,2002:pairs",resolve:_c,createNode:Ac};Vs.createPairs=Ac;Vs.pairs=mm;Vs.resolvePairs=_c});var Hr=y(Wr=>{"use strict";var Nc=I(),Gr=Be(),Ei=Ye(),gm=Ge(),Rc=Js(),ht=class e extends gm.YAMLSeq{constructor(){super(),this.add=Ei.YAMLMap.prototype.add.bind(this),this.delete=Ei.YAMLMap.prototype.delete.bind(this),this.get=Ei.YAMLMap.prototype.get.bind(this),this.has=Ei.YAMLMap.prototype.has.bind(this),this.set=Ei.YAMLMap.prototype.set.bind(this),this.tag=e.tag}toJSON(t,i){if(!i)return super.toJSON(t);let s=new Map;i?.onCreate&&i.onCreate(s);for(let n of this.items){let r,o;if(Nc.isPair(n)?(r=Gr.toJS(n.key,"",i),o=Gr.toJS(n.value,r,i)):r=Gr.toJS(n,"",i),s.has(r))throw new Error("Ordered maps must not include duplicate keys");s.set(r,o)}return s}static from(t,i,s){let n=Rc.createPairs(t,i,s),r=new this;return r.items=n.items,r}};ht.tag="tag:yaml.org,2002:omap";var ym={collection:"seq",identify:e=>e instanceof Map,nodeClass:ht,default:!1,tag:"tag:yaml.org,2002:omap",resolve(e,t){let i=Rc.resolvePairs(e,t),s=[];for(let{key:n}of i.items)Nc.isScalar(n)&&(s.includes(n.value)?t(`Ordered maps must not include duplicate keys: ${n.value}`):s.push(n.value));return Object.assign(new ht,i)},createNode:(e,t,i)=>ht.from(e,t,i)};Wr.YAMLOMap=ht;Wr.omap=ym});var Cc=y(Vr=>{"use strict";var Pc=B();function Ic({value:e,source:t},i){return t&&(e?Tc:Lc).test.test(t)?t:e?i.options.trueStr:i.options.falseStr}var Tc={identify:e=>e===!0,default:!0,tag:"tag:yaml.org,2002:bool",test:/^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/,resolve:()=>new Pc.Scalar(!0),stringify:Ic},Lc={identify:e=>e===!1,default:!0,tag:"tag:yaml.org,2002:bool",test:/^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/,resolve:()=>new Pc.Scalar(!1),stringify:Ic};Vr.falseTag=Lc;Vr.trueTag=Tc});var Mc=y(Zs=>{"use strict";var bm=B(),Jr=Bt(),wm={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",test:/^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,resolve:e=>e.slice(-3).toLowerCase()==="nan"?NaN:e[0]==="-"?Number.NEGATIVE_INFINITY:Number.POSITIVE_INFINITY,stringify:Jr.stringifyNumber},Sm={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",format:"EXP",test:/^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,resolve:e=>parseFloat(e.replace(/_/g,"")),stringify(e){let t=Number(e.value);return isFinite(t)?t.toExponential():Jr.stringifyNumber(e)}},vm={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",test:/^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/,resolve(e){let t=new bm.Scalar(parseFloat(e.replace(/_/g,""))),i=e.indexOf(".");if(i!==-1){let s=e.substring(i+1).replace(/_/g,"");s[s.length-1]==="0"&&(t.minFractionDigits=s.length)}return t},stringify:Jr.stringifyNumber};Zs.float=vm;Zs.floatExp=Sm;Zs.floatNaN=wm});var $c=y(Oi=>{"use strict";var Dc=Bt(),ki=e=>typeof e=="bigint"||Number.isInteger(e);function Xs(e,t,i,{intAsBigInt:s}){let n=e[0];if((n==="-"||n==="+")&&(t+=1),e=e.substring(t).replace(/_/g,""),s){switch(i){case 2:e=`0b${e}`;break;case 8:e=`0o${e}`;break;case 16:e=`0x${e}`;break}let o=BigInt(e);return n==="-"?BigInt(-1)*o:o}let r=parseInt(e,i);return n==="-"?-1*r:r}function Zr(e,t,i){let{value:s}=e;if(ki(s)){let n=s.toString(t);return s<0?"-"+i+n.substr(1):i+n}return Dc.stringifyNumber(e)}var Em={identify:ki,default:!0,tag:"tag:yaml.org,2002:int",format:"BIN",test:/^[-+]?0b[0-1_]+$/,resolve:(e,t,i)=>Xs(e,2,2,i),stringify:e=>Zr(e,2,"0b")},km={identify:ki,default:!0,tag:"tag:yaml.org,2002:int",format:"OCT",test:/^[-+]?0[0-7_]+$/,resolve:(e,t,i)=>Xs(e,1,8,i),stringify:e=>Zr(e,8,"0")},Om={identify:ki,default:!0,tag:"tag:yaml.org,2002:int",test:/^[-+]?[0-9][0-9_]*$/,resolve:(e,t,i)=>Xs(e,0,10,i),stringify:Dc.stringifyNumber},_m={identify:ki,default:!0,tag:"tag:yaml.org,2002:int",format:"HEX",test:/^[-+]?0x[0-9a-fA-F_]+$/,resolve:(e,t,i)=>Xs(e,2,16,i),stringify:e=>Zr(e,16,"0x")};Oi.int=Om;Oi.intBin=Em;Oi.intHex=_m;Oi.intOct=km});var Qr=y(Xr=>{"use strict";var tn=I(),Qs=Ue(),en=Ye(),ut=class e extends en.YAMLMap{constructor(t){super(t),this.tag=e.tag}add(t){let i;tn.isPair(t)?i=t:t&&typeof t=="object"&&"key"in t&&"value"in t&&t.value===null?i=new Qs.Pair(t.key,null):i=new Qs.Pair(t,null),en.findPair(this.items,i.key)||this.items.push(i)}get(t,i){let s=en.findPair(this.items,t);return!i&&tn.isPair(s)?tn.isScalar(s.key)?s.key.value:s.key:s}set(t,i){if(typeof i!="boolean")throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof i}`);let s=en.findPair(this.items,t);s&&!i?this.items.splice(this.items.indexOf(s),1):!s&&i&&this.items.push(new Qs.Pair(t))}toJSON(t,i){return super.toJSON(t,i,Set)}toString(t,i,s){if(!t)return JSON.stringify(this);if(this.hasAllNullValues(!0))return super.toString(Object.assign({},t,{allNullValues:!0}),i,s);throw new Error("Set items must all have null values")}static from(t,i,s){let{replacer:n}=s,r=new this(t);if(i&&Symbol.iterator in Object(i))for(let o of i)typeof n=="function"&&(o=n.call(i,o,o)),r.items.push(Qs.createPair(o,null,s));return r}};ut.tag="tag:yaml.org,2002:set";var Am={collection:"map",identify:e=>e instanceof Set,nodeClass:ut,default:!1,tag:"tag:yaml.org,2002:set",createNode:(e,t,i)=>ut.from(e,t,i),resolve(e,t){if(tn.isMap(e)){if(e.hasAllNullValues(!0))return Object.assign(new ut,e);t("Set items must all have null values")}else t("Expected a mapping for this tag");return e}};Xr.YAMLSet=ut;Xr.set=Am});var to=y(sn=>{"use strict";var Nm=Bt();function eo(e,t){let i=e[0],s=i==="-"||i==="+"?e.substring(1):e,n=o=>t?BigInt(o):Number(o),r=s.replace(/_/g,"").split(":").reduce((o,a)=>o*n(60)+n(a),n(0));return i==="-"?n(-1)*r:r}function xc(e){let{value:t}=e,i=o=>o;if(typeof t=="bigint")i=o=>BigInt(o);else if(isNaN(t)||!isFinite(t))return Nm.stringifyNumber(e);let s="";t<0&&(s="-",t*=i(-1));let n=i(60),r=[t%n];return t<60?r.unshift(0):(t=(t-r[0])/n,r.unshift(t%n),t>=60&&(t=(t-r[0])/n,r.unshift(t))),s+r.map(o=>String(o).padStart(2,"0")).join(":").replace(/000000\d*$/,"")}var Rm={identify:e=>typeof e=="bigint"||Number.isInteger(e),default:!0,tag:"tag:yaml.org,2002:int",format:"TIME",test:/^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/,resolve:(e,t,{intAsBigInt:i})=>eo(e,i),stringify:xc},Pm={identify:e=>typeof e=="number",default:!0,tag:"tag:yaml.org,2002:float",format:"TIME",test:/^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/,resolve:e=>eo(e,!1),stringify:xc},qc={identify:e=>e instanceof Date,default:!0,tag:"tag:yaml.org,2002:timestamp",test:RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?$"),resolve(e){let t=e.match(qc.test);if(!t)throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");let[,i,s,n,r,o,a]=t.map(Number),l=t[7]?Number((t[7]+"00").substr(1,3)):0,c=Date.UTC(i,s-1,n,r||0,o||0,a||0,l),h=t[8];if(h&&h!=="Z"){let u=eo(h,!1);Math.abs(u)<30&&(u*=60),c-=6e4*u}return new Date(c)},stringify:({value:e})=>e?.toISOString().replace(/(T00:00:00)?\.000Z$/,"")??""};sn.floatTime=Pm;sn.intTime=Rm;sn.timestamp=qc});var jc=y(Fc=>{"use strict";var Im=xt(),Tm=Us(),Lm=qt(),Cm=Si(),Mm=zr(),Bc=Cc(),io=Mc(),nn=$c(),Dm=Ds(),$m=Hr(),xm=Js(),qm=Qr(),so=to(),Bm=[Im.map,Lm.seq,Cm.string,Tm.nullTag,Bc.trueTag,Bc.falseTag,nn.intBin,nn.intOct,nn.int,nn.intHex,io.floatNaN,io.floatExp,io.float,Mm.binary,Dm.merge,$m.omap,xm.pairs,qm.set,so.intTime,so.floatTime,so.timestamp];Fc.schema=Bm});var Zc=y(oo=>{"use strict";var Yc=xt(),Fm=Us(),Gc=qt(),jm=Si(),Km=$r(),no=qr(),ro=Fr(),Um=Sc(),zm=kc(),Wc=zr(),_i=Ds(),Hc=Hr(),Vc=Js(),Kc=jc(),Jc=Qr(),rn=to(),Uc=new Map([["core",Um.schema],["failsafe",[Yc.map,Gc.seq,jm.string]],["json",zm.schema],["yaml11",Kc.schema],["yaml-1.1",Kc.schema]]),zc={binary:Wc.binary,bool:Km.boolTag,float:no.float,floatExp:no.floatExp,floatNaN:no.floatNaN,floatTime:rn.floatTime,int:ro.int,intHex:ro.intHex,intOct:ro.intOct,intTime:rn.intTime,map:Yc.map,merge:_i.merge,null:Fm.nullTag,omap:Hc.omap,pairs:Vc.pairs,seq:Gc.seq,set:Jc.set,timestamp:rn.timestamp},Ym={"tag:yaml.org,2002:binary":Wc.binary,"tag:yaml.org,2002:merge":_i.merge,"tag:yaml.org,2002:omap":Hc.omap,"tag:yaml.org,2002:pairs":Vc.pairs,"tag:yaml.org,2002:set":Jc.set,"tag:yaml.org,2002:timestamp":rn.timestamp};function Gm(e,t,i){let s=Uc.get(t);if(s&&!e)return i&&!s.includes(_i.merge)?s.concat(_i.merge):s.slice();let n=s;if(!n)if(Array.isArray(e))n=[];else{let r=Array.from(Uc.keys()).filter(o=>o!=="yaml11").map(o=>JSON.stringify(o)).join(", ");throw new Error(`Unknown schema "${t}"; use one of ${r} or define customTags array`)}if(Array.isArray(e))for(let r of e)n=n.concat(r);else typeof e=="function"&&(n=e(n.slice()));return i&&(n=n.concat(_i.merge)),n.reduce((r,o)=>{let a=typeof o=="string"?zc[o]:o;if(!a){let l=JSON.stringify(o),c=Object.keys(zc).map(h=>JSON.stringify(h)).join(", ");throw new Error(`Unknown custom tag ${l}; use one of ${c}`)}return r.includes(a)||r.push(a),r},[])}oo.coreKnownTags=Ym;oo.getTags=Gm});var co=y(Xc=>{"use strict";var ao=I(),Wm=xt(),Hm=qt(),Vm=Si(),on=Zc(),Jm=(e,t)=>e.keyt.key?1:0,lo=class e{constructor({compat:t,customTags:i,merge:s,resolveKnownTags:n,schema:r,sortMapEntries:o,toStringDefaults:a}){this.compat=Array.isArray(t)?on.getTags(t,"compat"):t?on.getTags(null,t):null,this.name=typeof r=="string"&&r||"core",this.knownTags=n?on.coreKnownTags:{},this.tags=on.getTags(i,this.name,s),this.toStringOptions=a??null,Object.defineProperty(this,ao.MAP,{value:Wm.map}),Object.defineProperty(this,ao.SCALAR,{value:Vm.string}),Object.defineProperty(this,ao.SEQ,{value:Hm.seq}),this.sortMapEntries=typeof o=="function"?o:o===!0?Jm:null}clone(){let t=Object.create(e.prototype,Object.getOwnPropertyDescriptors(this));return t.tags=this.tags.slice(),t}};Xc.Schema=lo});var eh=y(Qc=>{"use strict";var Zm=I(),ho=gi(),Ai=fi();function Xm(e,t){let i=[],s=t.directives===!0;if(t.directives!==!1&&e.directives){let l=e.directives.toString(e);l?(i.push(l),s=!0):e.directives.docStart&&(s=!0)}s&&i.push("---");let n=ho.createStringifyContext(e,t),{commentString:r}=n.options;if(e.commentBefore){i.length!==1&&i.unshift("");let l=r(e.commentBefore);i.unshift(Ai.indentComment(l,""))}let o=!1,a=null;if(e.contents){if(Zm.isNode(e.contents)){if(e.contents.spaceBefore&&s&&i.push(""),e.contents.commentBefore){let h=r(e.contents.commentBefore);i.push(Ai.indentComment(h,""))}n.forceBlockIndent=!!e.comment,a=e.contents.comment}let l=a?void 0:()=>o=!0,c=ho.stringify(e.contents,n,()=>a=null,l);a&&(c+=Ai.lineComment(c,"",r(a))),(c[0]==="|"||c[0]===">")&&i[i.length-1]==="---"?i[i.length-1]=`--- ${c}`:i.push(c)}else i.push(ho.stringify(e.contents,n));if(e.directives?.docEnd)if(e.comment){let l=r(e.comment);l.includes(` -`)?(i.push("..."),i.push(Ai.indentComment(l,""))):i.push(`... ${l}`)}else i.push("...");else{let l=e.comment;l&&o&&(l=l.replace(/^\n+/,"")),l&&((!o||a)&&i[i.length-1]!==""&&i.push(""),i.push(Ai.indentComment(r(l),"")))}return i.join(` -`)+` -`}Qc.stringifyDocument=Xm});var Ni=y(th=>{"use strict";var Qm=hi(),Ft=As(),oe=I(),eg=Ue(),tg=Be(),ig=co(),sg=eh(),uo=Es(),ng=mr(),rg=ui(),fo=pr(),po=class e{constructor(t,i,s){this.commentBefore=null,this.comment=null,this.errors=[],this.warnings=[],Object.defineProperty(this,oe.NODE_TYPE,{value:oe.DOC});let n=null;typeof i=="function"||Array.isArray(i)?n=i:s===void 0&&i&&(s=i,i=void 0);let r=Object.assign({intAsBigInt:!1,keepSourceTokens:!1,logLevel:"warn",prettyErrors:!0,strict:!0,stringKeys:!1,uniqueKeys:!0,version:"1.2"},s);this.options=r;let{version:o}=r;s?._directives?(this.directives=s._directives.atDocument(),this.directives.yaml.explicit&&(o=this.directives.yaml.version)):this.directives=new fo.Directives({version:o}),this.setSchema(o,s),this.contents=t===void 0?null:this.createNode(t,n,s)}clone(){let t=Object.create(e.prototype,{[oe.NODE_TYPE]:{value:oe.DOC}});return t.commentBefore=this.commentBefore,t.comment=this.comment,t.errors=this.errors.slice(),t.warnings=this.warnings.slice(),t.options=Object.assign({},this.options),this.directives&&(t.directives=this.directives.clone()),t.schema=this.schema.clone(),t.contents=oe.isNode(this.contents)?this.contents.clone(t.schema):this.contents,this.range&&(t.range=this.range.slice()),t}add(t){jt(this.contents)&&this.contents.add(t)}addIn(t,i){jt(this.contents)&&this.contents.addIn(t,i)}createAlias(t,i){if(!t.anchor){let s=uo.anchorNames(this);t.anchor=!i||s.has(i)?uo.findNewAnchor(i||"a",s):i}return new Qm.Alias(t.anchor)}createNode(t,i,s){let n;if(typeof i=="function")t=i.call({"":t},"",t),n=i;else if(Array.isArray(i)){let m=E=>typeof E=="number"||E instanceof String||E instanceof Number,w=i.filter(m).map(String);w.length>0&&(i=i.concat(w)),n=i}else s===void 0&&i&&(s=i,i=void 0);let{aliasDuplicateObjects:r,anchorPrefix:o,flow:a,keepUndefined:l,onTagObj:c,tag:h}=s??{},{onAnchor:u,setAnchors:f,sourceObjects:p}=uo.createNodeAnchors(this,o||"a"),g={aliasDuplicateObjects:r??!0,keepUndefined:l??!1,onAnchor:u,onTagObj:c,replacer:n,schema:this.schema,sourceObjects:p},d=rg.createNode(t,h,g);return a&&oe.isCollection(d)&&(d.flow=!0),f(),d}createPair(t,i,s={}){let n=this.createNode(t,null,s),r=this.createNode(i,null,s);return new eg.Pair(n,r)}delete(t){return jt(this.contents)?this.contents.delete(t):!1}deleteIn(t){return Ft.isEmptyPath(t)?this.contents==null?!1:(this.contents=null,!0):jt(this.contents)?this.contents.deleteIn(t):!1}get(t,i){return oe.isCollection(this.contents)?this.contents.get(t,i):void 0}getIn(t,i){return Ft.isEmptyPath(t)?!i&&oe.isScalar(this.contents)?this.contents.value:this.contents:oe.isCollection(this.contents)?this.contents.getIn(t,i):void 0}has(t){return oe.isCollection(this.contents)?this.contents.has(t):!1}hasIn(t){return Ft.isEmptyPath(t)?this.contents!==void 0:oe.isCollection(this.contents)?this.contents.hasIn(t):!1}set(t,i){this.contents==null?this.contents=Ft.collectionFromPath(this.schema,[t],i):jt(this.contents)&&this.contents.set(t,i)}setIn(t,i){Ft.isEmptyPath(t)?this.contents=i:this.contents==null?this.contents=Ft.collectionFromPath(this.schema,Array.from(t),i):jt(this.contents)&&this.contents.setIn(t,i)}setSchema(t,i={}){typeof t=="number"&&(t=String(t));let s;switch(t){case"1.1":this.directives?this.directives.yaml.version="1.1":this.directives=new fo.Directives({version:"1.1"}),s={resolveKnownTags:!1,schema:"yaml-1.1"};break;case"1.2":case"next":this.directives?this.directives.yaml.version=t:this.directives=new fo.Directives({version:t}),s={resolveKnownTags:!0,schema:"core"};break;case null:this.directives&&delete this.directives,s=null;break;default:{let n=JSON.stringify(t);throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${n}`)}}if(i.schema instanceof Object)this.schema=i.schema;else if(s)this.schema=new ig.Schema(Object.assign(s,i));else throw new Error("With a null YAML version, the { schema: Schema } option is required")}toJS({json:t,jsonArg:i,mapAsMap:s,maxAliasCount:n,onAnchor:r,reviver:o}={}){let a={anchors:new Map,doc:this,keep:!t,mapAsMap:s===!0,mapKeyWarned:!1,maxAliasCount:typeof n=="number"?n:100},l=tg.toJS(this.contents,i??"",a);if(typeof r=="function")for(let{count:c,res:h}of a.anchors.values())r(h,c);return typeof o=="function"?ng.applyReviver(o,{"":l},"",l):l}toJSON(t,i){return this.toJS({json:!0,jsonArg:t,mapAsMap:!1,onAnchor:i})}toString(t={}){if(this.errors.length>0)throw new Error("Document with errors cannot be stringified");if("indent"in t&&(!Number.isInteger(t.indent)||Number(t.indent)<=0)){let i=JSON.stringify(t.indent);throw new Error(`"indent" option must be a positive integer, not ${i}`)}return sg.stringifyDocument(this,t)}};function jt(e){if(oe.isCollection(e))return!0;throw new Error("Expected a YAML collection as document contents")}th.Document=po});var Ii=y(Pi=>{"use strict";var Ri=class extends Error{constructor(t,i,s,n){super(),this.name=t,this.code=s,this.message=n,this.pos=i}},mo=class extends Ri{constructor(t,i,s){super("YAMLParseError",t,i,s)}},go=class extends Ri{constructor(t,i,s){super("YAMLWarning",t,i,s)}},og=(e,t)=>i=>{if(i.pos[0]===-1)return;i.linePos=i.pos.map(a=>t.linePos(a));let{line:s,col:n}=i.linePos[0];i.message+=` at line ${s}, column ${n}`;let r=n-1,o=e.substring(t.lineStarts[s-1],t.lineStarts[s]).replace(/[\n\r]+$/,"");if(r>=60&&o.length>80){let a=Math.min(r-39,o.length-79);o="\u2026"+o.substring(a),r-=a-1}if(o.length>80&&(o=o.substring(0,79)+"\u2026"),s>1&&/^ *$/.test(o.substring(0,r))){let a=e.substring(t.lineStarts[s-2],t.lineStarts[s-1]);a.length>80&&(a=a.substring(0,79)+`\u2026 -`),o=a+o}if(/[^ ]/.test(o)){let a=1,l=i.linePos[1];l?.line===s&&l.col>n&&(a=Math.max(1,Math.min(l.col-n,80-r)));let c=" ".repeat(r)+"^".repeat(a);i.message+=`: - -${o} -${c} -`}};Pi.YAMLError=Ri;Pi.YAMLParseError=mo;Pi.YAMLWarning=go;Pi.prettifyError=og});var Ti=y(ih=>{"use strict";function ag(e,{flow:t,indicator:i,next:s,offset:n,onError:r,parentIndent:o,startOnNewline:a}){let l=!1,c=a,h=a,u="",f="",p=!1,g=!1,d=null,m=null,w=null,E=null,k=null,_=null,A=null;for(let v of e)switch(g&&(v.type!=="space"&&v.type!=="newline"&&v.type!=="comma"&&r(v.offset,"MISSING_CHAR","Tags and anchors must be separated from the next token by white space"),g=!1),d&&(c&&v.type!=="comment"&&v.type!=="newline"&&r(d,"TAB_AS_INDENT","Tabs are not allowed as indentation"),d=null),v.type){case"space":!t&&(i!=="doc-start"||s?.type!=="flow-collection")&&v.source.includes(" ")&&(d=v),h=!0;break;case"comment":{h||r(v,"MISSING_CHAR","Comments must be separated from other tokens by white space characters");let U=v.source.substring(1)||" ";u?u+=f+U:u=U,f="",c=!1;break}case"newline":c?u?u+=v.source:(!_||i!=="seq-item-ind")&&(l=!0):f+=v.source,c=!0,p=!0,(m||w)&&(E=v),h=!0;break;case"anchor":m&&r(v,"MULTIPLE_ANCHORS","A node can have at most one anchor"),v.source.endsWith(":")&&r(v.offset+v.source.length-1,"BAD_ALIAS","Anchor ending in : is ambiguous",!0),m=v,A??(A=v.offset),c=!1,h=!1,g=!0;break;case"tag":{w&&r(v,"MULTIPLE_TAGS","A node can have at most one tag"),w=v,A??(A=v.offset),c=!1,h=!1,g=!0;break}case i:(m||w)&&r(v,"BAD_PROP_ORDER",`Anchors and tags must be after the ${v.source} indicator`),_&&r(v,"UNEXPECTED_TOKEN",`Unexpected ${v.source} in ${t??"collection"}`),_=v,c=i==="seq-item-ind"||i==="explicit-key-ind",h=!1;break;case"comma":if(t){k&&r(v,"UNEXPECTED_TOKEN",`Unexpected , in ${t}`),k=v,c=!1,h=!1;break}default:r(v,"UNEXPECTED_TOKEN",`Unexpected ${v.type} token`),c=!1,h=!1}let N=e[e.length-1],C=N?N.offset+N.source.length:n;return g&&s&&s.type!=="space"&&s.type!=="newline"&&s.type!=="comma"&&(s.type!=="scalar"||s.source!=="")&&r(s.offset,"MISSING_CHAR","Tags and anchors must be separated from the next token by white space"),d&&(c&&d.indent<=o||s?.type==="block-map"||s?.type==="block-seq")&&r(d,"TAB_AS_INDENT","Tabs are not allowed as indentation"),{comma:k,found:_,spaceBefore:l,comment:u,hasNewline:p,anchor:m,tag:w,newlineAfterProp:E,end:C,start:A??C}}ih.resolveProps=ag});var an=y(sh=>{"use strict";function yo(e){if(!e)return null;switch(e.type){case"alias":case"scalar":case"double-quoted-scalar":case"single-quoted-scalar":if(e.source.includes(` -`))return!0;if(e.end){for(let t of e.end)if(t.type==="newline")return!0}return!1;case"flow-collection":for(let t of e.items){for(let i of t.start)if(i.type==="newline")return!0;if(t.sep){for(let i of t.sep)if(i.type==="newline")return!0}if(yo(t.key)||yo(t.value))return!0}return!1;default:return!0}}sh.containsNewline=yo});var bo=y(nh=>{"use strict";var lg=an();function cg(e,t,i){if(t?.type==="flow-collection"){let s=t.end[0];s.indent===e&&(s.source==="]"||s.source==="}")&&lg.containsNewline(t)&&i(s,"BAD_INDENT","Flow end indicator should be more indented than parent",!0)}}nh.flowIndentCheck=cg});var wo=y(oh=>{"use strict";var rh=I();function hg(e,t,i){let{uniqueKeys:s}=e.options;if(s===!1)return!1;let n=typeof s=="function"?s:(r,o)=>r===o||rh.isScalar(r)&&rh.isScalar(o)&&r.value===o.value;return t.some(r=>n(r.key,i))}oh.mapIncludes=hg});var fh=y(uh=>{"use strict";var ah=Ue(),ug=Ye(),lh=Ti(),fg=an(),ch=bo(),dg=wo(),hh="All mapping items must start at the same column";function pg({composeNode:e,composeEmptyNode:t},i,s,n,r){let o=r?.nodeClass??ug.YAMLMap,a=new o(i.schema);i.atRoot&&(i.atRoot=!1);let l=s.offset,c=null;for(let h of s.items){let{start:u,key:f,sep:p,value:g}=h,d=lh.resolveProps(u,{indicator:"explicit-key-ind",next:f??p?.[0],offset:l,onError:n,parentIndent:s.indent,startOnNewline:!0}),m=!d.found;if(m){if(f&&(f.type==="block-seq"?n(l,"BLOCK_AS_IMPLICIT_KEY","A block sequence may not be used as an implicit map key"):"indent"in f&&f.indent!==s.indent&&n(l,"BAD_INDENT",hh)),!d.anchor&&!d.tag&&!p){c=d.end,d.comment&&(a.comment?a.comment+=` -`+d.comment:a.comment=d.comment);continue}(d.newlineAfterProp||fg.containsNewline(f))&&n(f??u[u.length-1],"MULTILINE_IMPLICIT_KEY","Implicit keys need to be on a single line")}else d.found?.indent!==s.indent&&n(l,"BAD_INDENT",hh);i.atKey=!0;let w=d.end,E=f?e(i,f,d,n):t(i,w,u,null,d,n);i.schema.compat&&ch.flowIndentCheck(s.indent,f,n),i.atKey=!1,dg.mapIncludes(i,a.items,E)&&n(w,"DUPLICATE_KEY","Map keys must be unique");let k=lh.resolveProps(p??[],{indicator:"map-value-ind",next:g,offset:E.range[2],onError:n,parentIndent:s.indent,startOnNewline:!f||f.type==="block-scalar"});if(l=k.end,k.found){m&&(g?.type==="block-map"&&!k.hasNewline&&n(l,"BLOCK_AS_IMPLICIT_KEY","Nested mappings are not allowed in compact mappings"),i.options.strict&&d.start{"use strict";var mg=Ge(),gg=Ti(),yg=bo();function bg({composeNode:e,composeEmptyNode:t},i,s,n,r){let o=r?.nodeClass??mg.YAMLSeq,a=new o(i.schema);i.atRoot&&(i.atRoot=!1),i.atKey&&(i.atKey=!1);let l=s.offset,c=null;for(let{start:h,value:u}of s.items){let f=gg.resolveProps(h,{indicator:"seq-item-ind",next:u,offset:l,onError:n,parentIndent:s.indent,startOnNewline:!0});if(!f.found)if(f.anchor||f.tag||u)u?.type==="block-seq"?n(f.end,"BAD_INDENT","All sequence items must start at the same column"):n(l,"MISSING_CHAR","Sequence item without - indicator");else{c=f.end,f.comment&&(a.comment=f.comment);continue}let p=u?e(i,u,f,n):t(i,f.end,h,null,f,n);i.schema.compat&&yg.flowIndentCheck(s.indent,u,n),l=p.range[2],a.items.push(p)}return a.range=[s.offset,l,c??l],a}dh.resolveBlockSeq=bg});var Kt=y(mh=>{"use strict";function wg(e,t,i,s){let n="";if(e){let r=!1,o="";for(let a of e){let{source:l,type:c}=a;switch(c){case"space":r=!0;break;case"comment":{i&&!r&&s(a,"MISSING_CHAR","Comments must be separated from other tokens by white space characters");let h=l.substring(1)||" ";n?n+=o+h:n=h,o="";break}case"newline":n&&(o+=l),r=!0;break;default:s(a,"UNEXPECTED_TOKEN",`Unexpected ${c} at node end`)}t+=l.length}}return{comment:n,offset:t}}mh.resolveEnd=wg});var wh=y(bh=>{"use strict";var Sg=I(),vg=Ue(),gh=Ye(),Eg=Ge(),kg=Kt(),yh=Ti(),Og=an(),_g=wo(),So="Block collections are not allowed within flow collections",vo=e=>e&&(e.type==="block-map"||e.type==="block-seq");function Ag({composeNode:e,composeEmptyNode:t},i,s,n,r){let o=s.start.source==="{",a=o?"flow map":"flow sequence",l=r?.nodeClass??(o?gh.YAMLMap:Eg.YAMLSeq),c=new l(i.schema);c.flow=!0;let h=i.atRoot;h&&(i.atRoot=!1),i.atKey&&(i.atKey=!1);let u=s.offset+s.start.source.length;for(let m=0;m0){let m=kg.resolveEnd(g,d,i.options.strict,n);m.comment&&(c.comment?c.comment+=` -`+m.comment:c.comment=m.comment),c.range=[s.offset,d,m.offset]}else c.range=[s.offset,d,d];return c}bh.resolveFlowCollection=Ag});var vh=y(Sh=>{"use strict";var Ng=I(),Rg=B(),Pg=Ye(),Ig=Ge(),Tg=fh(),Lg=ph(),Cg=wh();function Eo(e,t,i,s,n,r){let o=i.type==="block-map"?Tg.resolveBlockMap(e,t,i,s,r):i.type==="block-seq"?Lg.resolveBlockSeq(e,t,i,s,r):Cg.resolveFlowCollection(e,t,i,s,r),a=o.constructor;return n==="!"||n===a.tagName?(o.tag=a.tagName,o):(n&&(o.tag=n),o)}function Mg(e,t,i,s,n){let r=s.tag,o=r?t.directives.tagName(r.source,f=>n(r,"TAG_RESOLVE_FAILED",f)):null;if(i.type==="block-seq"){let{anchor:f,newlineAfterProp:p}=s,g=f&&r?f.offset>r.offset?f:r:f??r;g&&(!p||p.offsetf.tag===o&&f.collection===a);if(!l){let f=t.schema.knownTags[o];if(f?.collection===a)t.schema.tags.push(Object.assign({},f,{default:!1})),l=f;else return f?n(r,"BAD_COLLECTION_TYPE",`${f.tag} used for ${a} collection, but expects ${f.collection??"scalar"}`,!0):n(r,"TAG_RESOLVE_FAILED",`Unresolved tag: ${o}`,!0),Eo(e,t,i,n,o)}let c=Eo(e,t,i,n,o,l),h=l.resolve?.(c,f=>n(r,"TAG_RESOLVE_FAILED",f),t.options)??c,u=Ng.isNode(h)?h:new Rg.Scalar(h);return u.range=c.range,u.tag=o,l?.format&&(u.format=l.format),u}Sh.composeCollection=Mg});var Oo=y(Eh=>{"use strict";var ko=B();function Dg(e,t,i){let s=t.offset,n=$g(t,e.options.strict,i);if(!n)return{value:"",type:null,comment:"",range:[s,s,s]};let r=n.mode===">"?ko.Scalar.BLOCK_FOLDED:ko.Scalar.BLOCK_LITERAL,o=t.source?xg(t.source):[],a=o.length;for(let d=o.length-1;d>=0;--d){let m=o[d][1];if(m===""||m==="\r")a=d;else break}if(a===0){let d=n.chomp==="+"&&o.length>0?` -`.repeat(Math.max(1,o.length-1)):"",m=s+n.length;return t.source&&(m+=t.source.length),{value:d,type:r,comment:n.comment,range:[s,m,m]}}let l=t.indent+n.indent,c=t.offset+n.length,h=0;for(let d=0;dl&&(l=m.length);else{m.length=a;--d)o[d][0].length>l&&(a=d+1);let u="",f="",p=!1;for(let d=0;dl||w[0]===" "?(f===" "?f=` -`:!p&&f===` -`&&(f=` - -`),u+=f+m.slice(l)+w,f=` -`,p=!0):w===""?f===` -`?u+=` -`:f=` -`:(u+=f+w,f=" ",p=!1)}switch(n.chomp){case"-":break;case"+":for(let d=a;d{"use strict";var _o=B(),qg=Kt();function Bg(e,t,i){let{offset:s,type:n,source:r,end:o}=e,a,l,c=(f,p,g)=>i(s+f,p,g);switch(n){case"scalar":a=_o.Scalar.PLAIN,l=Fg(r,c);break;case"single-quoted-scalar":a=_o.Scalar.QUOTE_SINGLE,l=jg(r,c);break;case"double-quoted-scalar":a=_o.Scalar.QUOTE_DOUBLE,l=Kg(r,c);break;default:return i(e,"UNEXPECTED_TOKEN",`Expected a flow scalar value, but found: ${n}`),{value:"",type:null,comment:"",range:[s,s+r.length,s+r.length]}}let h=s+r.length,u=qg.resolveEnd(o,h,t,i);return{value:l,type:a,comment:u.comment,range:[s,h,u.offset]}}function Fg(e,t){let i="";switch(e[0]){case" ":i="a tab character";break;case",":i="flow indicator character ,";break;case"%":i="directive indicator character %";break;case"|":case">":{i=`block scalar indicator ${e[0]}`;break}case"@":case"`":{i=`reserved character ${e[0]}`;break}}return i&&t(0,"BAD_SCALAR_START",`Plain value cannot start with ${i}`),kh(e)}function jg(e,t){return(e[e.length-1]!=="'"||e.length===1)&&t(e.length,"MISSING_CHAR","Missing closing 'quote"),kh(e.slice(1,-1)).replace(/''/g,"'")}function kh(e){let t,i;try{t=new RegExp(`(.*?)(?r?e.slice(r,s+1):n)}else i+=n}return(e[e.length-1]!=='"'||e.length===1)&&t(e.length,"MISSING_CHAR",'Missing closing "quote'),i}function Ug(e,t){let i="",s=e[t+1];for(;(s===" "||s===" "||s===` -`||s==="\r")&&!(s==="\r"&&e[t+2]!==` -`);)s===` -`&&(i+=` -`),t+=1,s=e[t+1];return i||(i=" "),{fold:i,offset:t}}var zg={0:"\0",a:"\x07",b:"\b",e:"\x1B",f:"\f",n:` -`,r:"\r",t:" ",v:"\v",N:"\x85",_:"\xA0",L:"\u2028",P:"\u2029"," ":" ",'"':'"',"/":"/","\\":"\\"," ":" "};function Yg(e,t,i,s){let n=e.substr(t,i),o=n.length===i&&/^[0-9a-fA-F]+$/.test(n)?parseInt(n,16):NaN;if(isNaN(o)){let a=e.substr(t-2,i+2);return s(t-2,"BAD_DQ_ESCAPE",`Invalid escape sequence ${a}`),a}return String.fromCodePoint(o)}Oh.resolveFlowScalar=Bg});var Nh=y(Ah=>{"use strict";var ft=I(),_h=B(),Gg=Oo(),Wg=Ao();function Hg(e,t,i,s){let{value:n,type:r,comment:o,range:a}=t.type==="block-scalar"?Gg.resolveBlockScalar(e,t,s):Wg.resolveFlowScalar(t,e.options.strict,s),l=i?e.directives.tagName(i.source,u=>s(i,"TAG_RESOLVE_FAILED",u)):null,c;e.options.stringKeys&&e.atKey?c=e.schema[ft.SCALAR]:l?c=Vg(e.schema,n,l,i,s):t.type==="scalar"?c=Jg(e,n,t,s):c=e.schema[ft.SCALAR];let h;try{let u=c.resolve(n,f=>s(i??t,"TAG_RESOLVE_FAILED",f),e.options);h=ft.isScalar(u)?u:new _h.Scalar(u)}catch(u){let f=u instanceof Error?u.message:String(u);s(i??t,"TAG_RESOLVE_FAILED",f),h=new _h.Scalar(n)}return h.range=a,h.source=n,r&&(h.type=r),l&&(h.tag=l),c.format&&(h.format=c.format),o&&(h.comment=o),h}function Vg(e,t,i,s,n){if(i==="!")return e[ft.SCALAR];let r=[];for(let a of e.tags)if(!a.collection&&a.tag===i)if(a.default&&a.test)r.push(a);else return a;for(let a of r)if(a.test?.test(t))return a;let o=e.knownTags[i];return o&&!o.collection?(e.tags.push(Object.assign({},o,{default:!1,test:void 0})),o):(n(s,"TAG_RESOLVE_FAILED",`Unresolved tag: ${i}`,i!=="tag:yaml.org,2002:str"),e[ft.SCALAR])}function Jg({atKey:e,directives:t,schema:i},s,n,r){let o=i.tags.find(a=>(a.default===!0||e&&a.default==="key")&&a.test?.test(s))||i[ft.SCALAR];if(i.compat){let a=i.compat.find(l=>l.default&&l.test?.test(s))??i[ft.SCALAR];if(o.tag!==a.tag){let l=t.tagString(o.tag),c=t.tagString(a.tag),h=`Value may be parsed as either ${l} or ${c}`;r(n,"TAG_RESOLVE_FAILED",h,!0)}}return o}Ah.composeScalar=Hg});var Ph=y(Rh=>{"use strict";function Zg(e,t,i){if(t){i??(i=t.length);for(let s=i-1;s>=0;--s){let n=t[s];switch(n.type){case"space":case"comment":case"newline":e-=n.source.length;continue}for(n=t[++s];n?.type==="space";)e+=n.source.length,n=t[++s];break}}return e}Rh.emptyScalarPosition=Zg});var Lh=y(Ro=>{"use strict";var Xg=hi(),Qg=I(),ey=vh(),Ih=Nh(),ty=Kt(),iy=Ph(),sy={composeNode:Th,composeEmptyNode:No};function Th(e,t,i,s){let n=e.atKey,{spaceBefore:r,comment:o,anchor:a,tag:l}=i,c,h=!0;switch(t.type){case"alias":c=ny(e,t,s),(a||l)&&s(t,"ALIAS_PROPS","An alias node must not specify any properties");break;case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":case"block-scalar":c=Ih.composeScalar(e,t,l,s),a&&(c.anchor=a.source.substring(1));break;case"block-map":case"block-seq":case"flow-collection":try{c=ey.composeCollection(sy,e,t,i,s),a&&(c.anchor=a.source.substring(1))}catch(u){let f=u instanceof Error?u.message:String(u);s(t,"RESOURCE_EXHAUSTION",f)}break;default:{let u=t.type==="error"?t.message:`Unsupported token (type: ${t.type})`;s(t,"UNEXPECTED_TOKEN",u),h=!1}}return c??(c=No(e,t.offset,void 0,null,i,s)),a&&c.anchor===""&&s(a,"BAD_ALIAS","Anchor cannot be an empty string"),n&&e.options.stringKeys&&(!Qg.isScalar(c)||typeof c.value!="string"||c.tag&&c.tag!=="tag:yaml.org,2002:str")&&s(l??t,"NON_STRING_KEY","With stringKeys, all keys must be strings"),r&&(c.spaceBefore=!0),o&&(t.type==="scalar"&&t.source===""?c.comment=o:c.commentBefore=o),e.options.keepSourceTokens&&h&&(c.srcToken=t),c}function No(e,t,i,s,{spaceBefore:n,comment:r,anchor:o,tag:a,end:l},c){let h={type:"scalar",offset:iy.emptyScalarPosition(t,i,s),indent:-1,source:""},u=Ih.composeScalar(e,h,a,c);return o&&(u.anchor=o.source.substring(1),u.anchor===""&&c(o,"BAD_ALIAS","Anchor cannot be an empty string")),n&&(u.spaceBefore=!0),r&&(u.comment=r,u.range[2]=l),u}function ny({options:e},{offset:t,source:i,end:s},n){let r=new Xg.Alias(i.substring(1));r.source===""&&n(t,"BAD_ALIAS","Alias cannot be an empty string"),r.source.endsWith(":")&&n(t+i.length-1,"BAD_ALIAS","Alias ending in : is ambiguous",!0);let o=t+i.length,a=ty.resolveEnd(s,o,e.strict,n);return r.range=[t,o,a.offset],a.comment&&(r.comment=a.comment),r}Ro.composeEmptyNode=No;Ro.composeNode=Th});var Dh=y(Mh=>{"use strict";var ry=Ni(),Ch=Lh(),oy=Kt(),ay=Ti();function ly(e,t,{offset:i,start:s,value:n,end:r},o){let a=Object.assign({_directives:t},e),l=new ry.Document(void 0,a),c={atKey:!1,atRoot:!0,directives:l.directives,options:l.options,schema:l.schema},h=ay.resolveProps(s,{indicator:"doc-start",next:n??r?.[0],offset:i,onError:o,parentIndent:0,startOnNewline:!0});h.found&&(l.directives.docStart=!0,n&&(n.type==="block-map"||n.type==="block-seq")&&!h.hasNewline&&o(h.end,"MISSING_CHAR","Block collection cannot start on same line with directives-end marker")),l.contents=n?Ch.composeNode(c,n,h,o):Ch.composeEmptyNode(c,h.end,s,null,h,o);let u=l.contents.range[2],f=oy.resolveEnd(r,u,!1,o);return f.comment&&(l.comment=f.comment),l.range=[i,u,f.offset],l}Mh.composeDoc=ly});var Io=y(qh=>{"use strict";var cy=require("process"),hy=pr(),uy=Ni(),Li=Ii(),$h=I(),fy=Dh(),dy=Kt();function Ci(e){if(typeof e=="number")return[e,e+1];if(Array.isArray(e))return e.length===2?e:[e[0],e[1]];let{offset:t,source:i}=e;return[t,t+(typeof i=="string"?i.length:1)]}function xh(e){let t="",i=!1,s=!1;for(let n=0;n{let o=Ci(i);r?this.warnings.push(new Li.YAMLWarning(o,s,n)):this.errors.push(new Li.YAMLParseError(o,s,n))},this.directives=new hy.Directives({version:t.version||"1.2"}),this.options=t}decorate(t,i){let{comment:s,afterEmptyLine:n}=xh(this.prelude);if(s){let r=t.contents;if(i)t.comment=t.comment?`${t.comment} -${s}`:s;else if(n||t.directives.docStart||!r)t.commentBefore=s;else if($h.isCollection(r)&&!r.flow&&r.items.length>0){let o=r.items[0];$h.isPair(o)&&(o=o.key);let a=o.commentBefore;o.commentBefore=a?`${s} -${a}`:s}else{let o=r.commentBefore;r.commentBefore=o?`${s} -${o}`:s}}i?(Array.prototype.push.apply(t.errors,this.errors),Array.prototype.push.apply(t.warnings,this.warnings)):(t.errors=this.errors,t.warnings=this.warnings),this.prelude=[],this.errors=[],this.warnings=[]}streamInfo(){return{comment:xh(this.prelude).comment,directives:this.directives,errors:this.errors,warnings:this.warnings}}*compose(t,i=!1,s=-1){for(let n of t)yield*this.next(n);yield*this.end(i,s)}*next(t){switch(cy.env.LOG_STREAM&&console.dir(t,{depth:null}),t.type){case"directive":this.directives.add(t.source,(i,s,n)=>{let r=Ci(t);r[0]+=i,this.onError(r,"BAD_DIRECTIVE",s,n)}),this.prelude.push(t.source),this.atDirectives=!0;break;case"document":{let i=fy.composeDoc(this.options,this.directives,t,this.onError);this.atDirectives&&!i.directives.docStart&&this.onError(t,"MISSING_CHAR","Missing directives-end/doc-start indicator line"),this.decorate(i,!1),this.doc&&(yield this.doc),this.doc=i,this.atDirectives=!1;break}case"byte-order-mark":case"space":break;case"comment":case"newline":this.prelude.push(t.source);break;case"error":{let i=t.source?`${t.message}: ${JSON.stringify(t.source)}`:t.message,s=new Li.YAMLParseError(Ci(t),"UNEXPECTED_TOKEN",i);this.atDirectives||!this.doc?this.errors.push(s):this.doc.errors.push(s);break}case"doc-end":{if(!this.doc){let s="Unexpected doc-end without preceding document";this.errors.push(new Li.YAMLParseError(Ci(t),"UNEXPECTED_TOKEN",s));break}this.doc.directives.docEnd=!0;let i=dy.resolveEnd(t.end,t.offset+t.source.length,this.doc.options.strict,this.onError);if(this.decorate(this.doc,!0),i.comment){let s=this.doc.comment;this.doc.comment=s?`${s} -${i.comment}`:i.comment}this.doc.range[2]=i.offset;break}default:this.errors.push(new Li.YAMLParseError(Ci(t),"UNEXPECTED_TOKEN",`Unsupported token ${t.type}`))}}*end(t=!1,i=-1){if(this.doc)this.decorate(this.doc,!0),yield this.doc,this.doc=null;else if(t){let s=Object.assign({_directives:this.directives},this.options),n=new uy.Document(void 0,s);this.atDirectives&&this.onError(i,"MISSING_CHAR","Missing directives-end indicator line"),n.range=[0,i,i],this.decorate(n,!1),yield n}}};qh.Composer=Po});var jh=y(ln=>{"use strict";var py=Oo(),my=Ao(),gy=Ii(),Bh=mi();function yy(e,t=!0,i){if(e){let s=(n,r,o)=>{let a=typeof n=="number"?n:Array.isArray(n)?n[0]:n.offset;if(i)i(a,r,o);else throw new gy.YAMLParseError([a,a+1],r,o)};switch(e.type){case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":return my.resolveFlowScalar(e,t,s);case"block-scalar":return py.resolveBlockScalar({options:{strict:t}},e,s)}}return null}function by(e,t){let{implicitKey:i=!1,indent:s,inFlow:n=!1,offset:r=-1,type:o="PLAIN"}=t,a=Bh.stringifyString({type:o,value:e},{implicitKey:i,indent:s>0?" ".repeat(s):"",inFlow:n,options:{blockQuote:!0,lineWidth:-1}}),l=t.end??[{type:"newline",offset:-1,indent:s,source:` -`}];switch(a[0]){case"|":case">":{let c=a.indexOf(` -`),h=a.substring(0,c),u=a.substring(c+1)+` -`,f=[{type:"block-scalar-header",offset:r,indent:s,source:h}];return Fh(f,l)||f.push({type:"newline",offset:-1,indent:s,source:` -`}),{type:"block-scalar",offset:r,indent:s,props:f,source:u}}case'"':return{type:"double-quoted-scalar",offset:r,indent:s,source:a,end:l};case"'":return{type:"single-quoted-scalar",offset:r,indent:s,source:a,end:l};default:return{type:"scalar",offset:r,indent:s,source:a,end:l}}}function wy(e,t,i={}){let{afterKey:s=!1,implicitKey:n=!1,inFlow:r=!1,type:o}=i,a="indent"in e?e.indent:null;if(s&&typeof a=="number"&&(a+=2),!o)switch(e.type){case"single-quoted-scalar":o="QUOTE_SINGLE";break;case"double-quoted-scalar":o="QUOTE_DOUBLE";break;case"block-scalar":{let c=e.props[0];if(c.type!=="block-scalar-header")throw new Error("Invalid block scalar header");o=c.source[0]===">"?"BLOCK_FOLDED":"BLOCK_LITERAL";break}default:o="PLAIN"}let l=Bh.stringifyString({type:o,value:t},{implicitKey:n||a===null,indent:a!==null&&a>0?" ".repeat(a):"",inFlow:r,options:{blockQuote:!0,lineWidth:-1}});switch(l[0]){case"|":case">":Sy(e,l);break;case'"':To(e,l,"double-quoted-scalar");break;case"'":To(e,l,"single-quoted-scalar");break;default:To(e,l,"scalar")}}function Sy(e,t){let i=t.indexOf(` -`),s=t.substring(0,i),n=t.substring(i+1)+` -`;if(e.type==="block-scalar"){let r=e.props[0];if(r.type!=="block-scalar-header")throw new Error("Invalid block scalar header");r.source=s,e.source=n}else{let{offset:r}=e,o="indent"in e?e.indent:-1,a=[{type:"block-scalar-header",offset:r,indent:o,source:s}];Fh(a,"end"in e?e.end:void 0)||a.push({type:"newline",offset:-1,indent:o,source:` -`});for(let l of Object.keys(e))l!=="type"&&l!=="offset"&&delete e[l];Object.assign(e,{type:"block-scalar",indent:o,props:a,source:n})}}function Fh(e,t){if(t)for(let i of t)switch(i.type){case"space":case"comment":e.push(i);break;case"newline":return e.push(i),!0}return!1}function To(e,t,i){switch(e.type){case"scalar":case"double-quoted-scalar":case"single-quoted-scalar":e.type=i,e.source=t;break;case"block-scalar":{let s=e.props.slice(1),n=t.length;e.props[0].type==="block-scalar-header"&&(n-=e.props[0].source.length);for(let r of s)r.offset+=n;delete e.props,Object.assign(e,{type:i,source:t,end:s});break}case"block-map":case"block-seq":{let n={type:"newline",offset:e.offset+t.length,indent:e.indent,source:` -`};delete e.items,Object.assign(e,{type:i,source:t,end:[n]});break}default:{let s="indent"in e?e.indent:-1,n="end"in e&&Array.isArray(e.end)?e.end.filter(r=>r.type==="space"||r.type==="comment"||r.type==="newline"):[];for(let r of Object.keys(e))r!=="type"&&r!=="offset"&&delete e[r];Object.assign(e,{type:i,indent:s,source:t,end:n})}}}ln.createScalarToken=by;ln.resolveAsScalar=yy;ln.setScalarValue=wy});var Uh=y(Kh=>{"use strict";var vy=e=>"type"in e?hn(e):cn(e);function hn(e){switch(e.type){case"block-scalar":{let t="";for(let i of e.props)t+=hn(i);return t+e.source}case"block-map":case"block-seq":{let t="";for(let i of e.items)t+=cn(i);return t}case"flow-collection":{let t=e.start.source;for(let i of e.items)t+=cn(i);for(let i of e.end)t+=i.source;return t}case"document":{let t=cn(e);if(e.end)for(let i of e.end)t+=i.source;return t}default:{let t=e.source;if("end"in e&&e.end)for(let i of e.end)t+=i.source;return t}}}function cn({start:e,key:t,sep:i,value:s}){let n="";for(let r of e)n+=r.source;if(t&&(n+=hn(t)),i)for(let r of i)n+=r.source;return s&&(n+=hn(s)),n}Kh.stringify=vy});var Wh=y(Gh=>{"use strict";var Lo=Symbol("break visit"),Ey=Symbol("skip children"),zh=Symbol("remove item");function dt(e,t){"type"in e&&e.type==="document"&&(e={start:e.start,value:e.value}),Yh(Object.freeze([]),e,t)}dt.BREAK=Lo;dt.SKIP=Ey;dt.REMOVE=zh;dt.itemAtPath=(e,t)=>{let i=e;for(let[s,n]of t){let r=i?.[s];if(r&&"items"in r)i=r.items[n];else return}return i};dt.parentCollection=(e,t)=>{let i=dt.itemAtPath(e,t.slice(0,-1)),s=t[t.length-1][0],n=i?.[s];if(n&&"items"in n)return n;throw new Error("Parent collection not found")};function Yh(e,t,i){let s=i(t,e);if(typeof s=="symbol")return s;for(let n of["key","value"]){let r=t[n];if(r&&"items"in r){for(let o=0;o{"use strict";var Co=jh(),ky=Uh(),Oy=Wh(),Mo="\uFEFF",Do="",$o="",xo="",_y=e=>!!e&&"items"in e,Ay=e=>!!e&&(e.type==="scalar"||e.type==="single-quoted-scalar"||e.type==="double-quoted-scalar"||e.type==="block-scalar");function Ny(e){switch(e){case Mo:return"";case Do:return"";case $o:return"";case xo:return"";default:return JSON.stringify(e)}}function Ry(e){switch(e){case Mo:return"byte-order-mark";case Do:return"doc-mode";case $o:return"flow-error-end";case xo:return"scalar";case"---":return"doc-start";case"...":return"doc-end";case"":case` -`:case`\r -`:return"newline";case"-":return"seq-item-ind";case"?":return"explicit-key-ind";case":":return"map-value-ind";case"{":return"flow-map-start";case"}":return"flow-map-end";case"[":return"flow-seq-start";case"]":return"flow-seq-end";case",":return"comma"}switch(e[0]){case" ":case" ":return"space";case"#":return"comment";case"%":return"directive-line";case"*":return"alias";case"&":return"anchor";case"!":return"tag";case"'":return"single-quoted-scalar";case'"':return"double-quoted-scalar";case"|":case">":return"block-scalar-header"}return null}ie.createScalarToken=Co.createScalarToken;ie.resolveAsScalar=Co.resolveAsScalar;ie.setScalarValue=Co.setScalarValue;ie.stringify=ky.stringify;ie.visit=Oy.visit;ie.BOM=Mo;ie.DOCUMENT=Do;ie.FLOW_END=$o;ie.SCALAR=xo;ie.isCollection=_y;ie.isScalar=Ay;ie.prettyToken=Ny;ie.tokenType=Ry});var Fo=y(Vh=>{"use strict";var Mi=un();function ue(e){switch(e){case void 0:case" ":case` -`:case"\r":case" ":return!0;default:return!1}}var Hh=new Set("0123456789ABCDEFabcdef"),Py=new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"),fn=new Set(",[]{}"),Iy=new Set(` ,[]{} -\r `),qo=e=>!e||Iy.has(e),Bo=class{constructor(){this.atEnd=!1,this.blockScalarIndent=-1,this.blockScalarKeep=!1,this.buffer="",this.flowKey=!1,this.flowLevel=0,this.indentNext=0,this.indentValue=0,this.lineEndPos=null,this.next=null,this.pos=0}*lex(t,i=!1){if(t){if(typeof t!="string")throw TypeError("source is not a string");this.buffer=this.buffer?this.buffer+t:t,this.lineEndPos=null}this.atEnd=!i;let s=this.next??"stream";for(;s&&(i||this.hasChars(1));)s=yield*this.parseNext(s)}atLineEnd(){let t=this.pos,i=this.buffer[t];for(;i===" "||i===" ";)i=this.buffer[++t];return!i||i==="#"||i===` -`?!0:i==="\r"?this.buffer[t+1]===` -`:!1}charAt(t){return this.buffer[this.pos+t]}continueScalar(t){let i=this.buffer[t];if(this.indentNext>0){let s=0;for(;i===" ";)i=this.buffer[++s+t];if(i==="\r"){let n=this.buffer[s+t+1];if(n===` -`||!n&&!this.atEnd)return t+s+1}return i===` -`||s>=this.indentNext||!i&&!this.atEnd?t+s:-1}if(i==="-"||i==="."){let s=this.buffer.substr(t,3);if((s==="---"||s==="...")&&ue(this.buffer[t+3]))return-1}return t}getLine(){let t=this.lineEndPos;return(typeof t!="number"||t!==-1&&tthis.indentValue&&!ue(this.charAt(1))&&(this.indentNext=this.indentValue),yield*this.parseBlockStart()}*parseBlockStart(){let[t,i]=this.peek(2);if(!i&&!this.atEnd)return this.setNext("block-start");if((t==="-"||t==="?"||t===":")&&ue(i)){let s=(yield*this.pushCount(1))+(yield*this.pushSpaces(!0));return this.indentNext=this.indentValue+1,this.indentValue+=s,yield*this.parseBlockStart()}return"doc"}*parseDocument(){yield*this.pushSpaces(!0);let t=this.getLine();if(t===null)return this.setNext("doc");let i=yield*this.pushIndicators();switch(t[i]){case"#":yield*this.pushCount(t.length-i);case void 0:return yield*this.pushNewline(),yield*this.parseLineStart();case"{":case"[":return yield*this.pushCount(1),this.flowKey=!1,this.flowLevel=1,"flow";case"}":case"]":return yield*this.pushCount(1),"doc";case"*":return yield*this.pushUntil(qo),"doc";case'"':case"'":return yield*this.parseQuotedScalar();case"|":case">":return i+=yield*this.parseBlockScalarHeader(),i+=yield*this.pushSpaces(!0),yield*this.pushCount(t.length-i),yield*this.pushNewline(),yield*this.parseBlockScalar();default:return yield*this.parsePlainScalar()}}*parseFlowCollection(){let t,i,s=-1;do t=yield*this.pushNewline(),t>0?(i=yield*this.pushSpaces(!1),this.indentValue=s=i):i=0,i+=yield*this.pushSpaces(!0);while(t+i>0);let n=this.getLine();if(n===null)return this.setNext("flow");if((s!==-1&&s"0"&&i<="9")this.blockScalarIndent=Number(i)-1;else if(i!=="-")break}return yield*this.pushUntil(i=>ue(i)||i==="#")}*parseBlockScalar(){let t=this.pos-1,i=0,s;e:for(let r=this.pos;s=this.buffer[r];++r)switch(s){case" ":i+=1;break;case` -`:t=r,i=0;break;case"\r":{let o=this.buffer[r+1];if(!o&&!this.atEnd)return this.setNext("block-scalar");if(o===` -`)break}default:break e}if(!s&&!this.atEnd)return this.setNext("block-scalar");if(i>=this.indentNext){this.blockScalarIndent===-1?this.indentNext=i:this.indentNext=this.blockScalarIndent+(this.indentNext===0?1:this.indentNext);do{let r=this.continueScalar(t+1);if(r===-1)break;t=this.buffer.indexOf(` -`,r)}while(t!==-1);if(t===-1){if(!this.atEnd)return this.setNext("block-scalar");t=this.buffer.length}}let n=t+1;for(s=this.buffer[n];s===" ";)s=this.buffer[++n];if(s===" "){for(;s===" "||s===" "||s==="\r"||s===` -`;)s=this.buffer[++n];t=n-1}else if(!this.blockScalarKeep)do{let r=t-1,o=this.buffer[r];o==="\r"&&(o=this.buffer[--r]);let a=r;for(;o===" ";)o=this.buffer[--r];if(o===` -`&&r>=this.pos&&r+1+i>a)t=r;else break}while(!0);return yield Mi.SCALAR,yield*this.pushToIndex(t+1,!0),yield*this.parseLineStart()}*parsePlainScalar(){let t=this.flowLevel>0,i=this.pos-1,s=this.pos-1,n;for(;n=this.buffer[++s];)if(n===":"){let r=this.buffer[s+1];if(ue(r)||t&&fn.has(r))break;i=s}else if(ue(n)){let r=this.buffer[s+1];if(n==="\r"&&(r===` -`?(s+=1,n=` -`,r=this.buffer[s+1]):i=s),r==="#"||t&&fn.has(r))break;if(n===` -`){let o=this.continueScalar(s+1);if(o===-1)break;s=Math.max(s,o-2)}}else{if(t&&fn.has(n))break;i=s}return!n&&!this.atEnd?this.setNext("plain-scalar"):(yield Mi.SCALAR,yield*this.pushToIndex(i+1,!0),t?"flow":"doc")}*pushCount(t){return t>0?(yield this.buffer.substr(this.pos,t),this.pos+=t,t):0}*pushToIndex(t,i){let s=this.buffer.slice(this.pos,t);return s?(yield s,this.pos+=s.length,s.length):(i&&(yield""),0)}*pushIndicators(){switch(this.charAt(0)){case"!":return(yield*this.pushTag())+(yield*this.pushSpaces(!0))+(yield*this.pushIndicators());case"&":return(yield*this.pushUntil(qo))+(yield*this.pushSpaces(!0))+(yield*this.pushIndicators());case"-":case"?":case":":{let t=this.flowLevel>0,i=this.charAt(1);if(ue(i)||t&&fn.has(i))return t?this.flowKey&&(this.flowKey=!1):this.indentNext=this.indentValue+1,(yield*this.pushCount(1))+(yield*this.pushSpaces(!0))+(yield*this.pushIndicators())}}return 0}*pushTag(){if(this.charAt(1)==="<"){let t=this.pos+2,i=this.buffer[t];for(;!ue(i)&&i!==">";)i=this.buffer[++t];return yield*this.pushToIndex(i===">"?t+1:t,!1)}else{let t=this.pos+1,i=this.buffer[t];for(;i;)if(Py.has(i))i=this.buffer[++t];else if(i==="%"&&Hh.has(this.buffer[t+1])&&Hh.has(this.buffer[t+2]))i=this.buffer[t+=3];else break;return yield*this.pushToIndex(t,!1)}}*pushNewline(){let t=this.buffer[this.pos];return t===` -`?yield*this.pushCount(1):t==="\r"&&this.charAt(1)===` -`?yield*this.pushCount(2):0}*pushSpaces(t){let i=this.pos-1,s;do s=this.buffer[++i];while(s===" "||t&&s===" ");let n=i-this.pos;return n>0&&(yield this.buffer.substr(this.pos,n),this.pos=i),n}*pushUntil(t){let i=this.pos,s=this.buffer[i];for(;!t(s);)s=this.buffer[++i];return yield*this.pushToIndex(i,!1)}};Vh.Lexer=Bo});var Ko=y(Jh=>{"use strict";var jo=class{constructor(){this.lineStarts=[],this.addNewLine=t=>this.lineStarts.push(t),this.linePos=t=>{let i=0,s=this.lineStarts.length;for(;i>1;this.lineStarts[r]{"use strict";var Ty=require("process"),Zh=un(),Ly=Fo();function We(e,t){for(let i=0;i=0;)switch(e[t].type){case"doc-start":case"explicit-key-ind":case"map-value-ind":case"seq-item-ind":case"newline":break e}for(;e[++t]?.type==="space";);return e.splice(t,e.length)}function Qh(e){if(e.start.type==="flow-seq-start")for(let t of e.items)t.sep&&!t.value&&!We(t.start,"explicit-key-ind")&&!We(t.sep,"map-value-ind")&&(t.key&&(t.value=t.key),delete t.key,eu(t.value)?t.value.end?Array.prototype.push.apply(t.value.end,t.sep):t.value.end=t.sep:Array.prototype.push.apply(t.start,t.sep),delete t.sep)}var Uo=class{constructor(t){this.atNewLine=!0,this.atScalar=!1,this.indent=0,this.offset=0,this.onKeyLine=!1,this.stack=[],this.source="",this.type="",this.lexer=new Ly.Lexer,this.onNewLine=t}*parse(t,i=!1){this.onNewLine&&this.offset===0&&this.onNewLine(0);for(let s of this.lexer.lex(t,i))yield*this.next(s);i||(yield*this.end())}*next(t){if(this.source=t,Ty.env.LOG_TOKENS&&console.log("|",Zh.prettyToken(t)),this.atScalar){this.atScalar=!1,yield*this.step(),this.offset+=t.length;return}let i=Zh.tokenType(t);if(i)if(i==="scalar")this.atNewLine=!1,this.atScalar=!0,this.type="scalar";else{switch(this.type=i,yield*this.step(),i){case"newline":this.atNewLine=!0,this.indent=0,this.onNewLine&&this.onNewLine(this.offset+t.length);break;case"space":this.atNewLine&&t[0]===" "&&(this.indent+=t.length);break;case"explicit-key-ind":case"map-value-ind":case"seq-item-ind":this.atNewLine&&(this.indent+=t.length);break;case"doc-mode":case"flow-error-end":return;default:this.atNewLine=!1}this.offset+=t.length}else{let s=`Not a YAML token: ${t}`;yield*this.pop({type:"error",offset:this.offset,message:s,source:t}),this.offset+=t.length}}*end(){for(;this.stack.length>0;)yield*this.pop()}get sourceToken(){return{type:this.type,offset:this.offset,indent:this.indent,source:this.source}}*step(){let t=this.peek(1);if(this.type==="doc-end"&&t?.type!=="doc-end"){for(;this.stack.length>0;)yield*this.pop();this.stack.push({type:"doc-end",offset:this.offset,source:this.source});return}if(!t)return yield*this.stream();switch(t.type){case"document":return yield*this.document(t);case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":return yield*this.scalar(t);case"block-scalar":return yield*this.blockScalar(t);case"block-map":return yield*this.blockMap(t);case"block-seq":return yield*this.blockSequence(t);case"flow-collection":return yield*this.flowCollection(t);case"doc-end":return yield*this.documentEnd(t)}yield*this.pop()}peek(t){return this.stack[this.stack.length-t]}*pop(t){let i=t??this.stack.pop();if(!i)yield{type:"error",offset:this.offset,source:"",message:"Tried to pop an empty stack"};else if(this.stack.length===0)yield i;else{let s=this.peek(1);switch(i.type==="block-scalar"?i.indent="indent"in s?s.indent:0:i.type==="flow-collection"&&s.type==="document"&&(i.indent=0),i.type==="flow-collection"&&Qh(i),s.type){case"document":s.value=i;break;case"block-scalar":s.props.push(i);break;case"block-map":{let n=s.items[s.items.length-1];if(n.value){s.items.push({start:[],key:i,sep:[]}),this.onKeyLine=!0;return}else if(n.sep)n.value=i;else{Object.assign(n,{key:i,sep:[]}),this.onKeyLine=!n.explicitKey;return}break}case"block-seq":{let n=s.items[s.items.length-1];n.value?s.items.push({start:[],value:i}):n.value=i;break}case"flow-collection":{let n=s.items[s.items.length-1];!n||n.value?s.items.push({start:[],key:i,sep:[]}):n.sep?n.value=i:Object.assign(n,{key:i,sep:[]});return}default:yield*this.pop(),yield*this.pop(i)}if((s.type==="document"||s.type==="block-map"||s.type==="block-seq")&&(i.type==="block-map"||i.type==="block-seq")){let n=i.items[i.items.length-1];n&&!n.sep&&!n.value&&n.start.length>0&&Xh(n.start)===-1&&(i.indent===0||n.start.every(r=>r.type!=="comment"||r.indent=t.indent){let s=!this.onKeyLine&&this.indent===t.indent,n=s&&(i.sep||i.explicitKey)&&this.type!=="seq-item-ind",r=[];if(n&&i.sep&&!i.value){let o=[];for(let a=0;at.indent&&(o.length=0);break;default:o.length=0}}o.length>=2&&(r=i.sep.splice(o[1]))}switch(this.type){case"anchor":case"tag":n||i.value?(r.push(this.sourceToken),t.items.push({start:r}),this.onKeyLine=!0):i.sep?i.sep.push(this.sourceToken):i.start.push(this.sourceToken);return;case"explicit-key-ind":!i.sep&&!i.explicitKey?(i.start.push(this.sourceToken),i.explicitKey=!0):n||i.value?(r.push(this.sourceToken),t.items.push({start:r,explicitKey:!0})):this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:[this.sourceToken],explicitKey:!0}]}),this.onKeyLine=!0;return;case"map-value-ind":if(i.explicitKey)if(i.sep)if(i.value)t.items.push({start:[],key:null,sep:[this.sourceToken]});else if(We(i.sep,"map-value-ind"))this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:r,key:null,sep:[this.sourceToken]}]});else if(eu(i.key)&&!We(i.sep,"newline")){let o=Ut(i.start),a=i.key,l=i.sep;l.push(this.sourceToken),delete i.key,delete i.sep,this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:o,key:a,sep:l}]})}else r.length>0?i.sep=i.sep.concat(r,this.sourceToken):i.sep.push(this.sourceToken);else if(We(i.start,"newline"))Object.assign(i,{key:null,sep:[this.sourceToken]});else{let o=Ut(i.start);this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:o,key:null,sep:[this.sourceToken]}]})}else i.sep?i.value||n?t.items.push({start:r,key:null,sep:[this.sourceToken]}):We(i.sep,"map-value-ind")?this.stack.push({type:"block-map",offset:this.offset,indent:this.indent,items:[{start:[],key:null,sep:[this.sourceToken]}]}):i.sep.push(this.sourceToken):Object.assign(i,{key:null,sep:[this.sourceToken]});this.onKeyLine=!0;return;case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":{let o=this.flowScalar(this.type);n||i.value?(t.items.push({start:r,key:o,sep:[]}),this.onKeyLine=!0):i.sep?this.stack.push(o):(Object.assign(i,{key:o,sep:[]}),this.onKeyLine=!0);return}default:{let o=this.startBlockValue(t);if(o){if(o.type==="block-seq"){if(!i.explicitKey&&i.sep&&!We(i.sep,"newline")){yield*this.pop({type:"error",offset:this.offset,message:"Unexpected block-seq-ind on same line with key",source:this.source});return}}else s&&t.items.push({start:r});this.stack.push(o);return}}}}yield*this.pop(),yield*this.step()}*blockSequence(t){let i=t.items[t.items.length-1];switch(this.type){case"newline":if(i.value){let s="end"in i.value?i.value.end:void 0;(Array.isArray(s)?s[s.length-1]:void 0)?.type==="comment"?s?.push(this.sourceToken):t.items.push({start:[this.sourceToken]})}else i.start.push(this.sourceToken);return;case"space":case"comment":if(i.value)t.items.push({start:[this.sourceToken]});else{if(this.atIndentedComment(i.start,t.indent)){let n=t.items[t.items.length-2]?.value?.end;if(Array.isArray(n)){Array.prototype.push.apply(n,i.start),n.push(this.sourceToken),t.items.pop();return}}i.start.push(this.sourceToken)}return;case"anchor":case"tag":if(i.value||this.indent<=t.indent)break;i.start.push(this.sourceToken);return;case"seq-item-ind":if(this.indent!==t.indent)break;i.value||We(i.start,"seq-item-ind")?t.items.push({start:[this.sourceToken]}):i.start.push(this.sourceToken);return}if(this.indent>t.indent){let s=this.startBlockValue(t);if(s){this.stack.push(s);return}}yield*this.pop(),yield*this.step()}*flowCollection(t){let i=t.items[t.items.length-1];if(this.type==="flow-error-end"){let s;do yield*this.pop(),s=this.peek(1);while(s?.type==="flow-collection")}else if(t.end.length===0){switch(this.type){case"comma":case"explicit-key-ind":!i||i.sep?t.items.push({start:[this.sourceToken]}):i.start.push(this.sourceToken);return;case"map-value-ind":!i||i.value?t.items.push({start:[],key:null,sep:[this.sourceToken]}):i.sep?i.sep.push(this.sourceToken):Object.assign(i,{key:null,sep:[this.sourceToken]});return;case"space":case"comment":case"newline":case"anchor":case"tag":!i||i.value?t.items.push({start:[this.sourceToken]}):i.sep?i.sep.push(this.sourceToken):i.start.push(this.sourceToken);return;case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":{let n=this.flowScalar(this.type);!i||i.value?t.items.push({start:[],key:n,sep:[]}):i.sep?this.stack.push(n):Object.assign(i,{key:n,sep:[]});return}case"flow-map-end":case"flow-seq-end":t.end.push(this.sourceToken);return}let s=this.startBlockValue(t);s?this.stack.push(s):(yield*this.pop(),yield*this.step())}else{let s=this.peek(2);if(s.type==="block-map"&&(this.type==="map-value-ind"&&s.indent===t.indent||this.type==="newline"&&!s.items[s.items.length-1].sep))yield*this.pop(),yield*this.step();else if(this.type==="map-value-ind"&&s.type!=="flow-collection"){let n=dn(s),r=Ut(n);Qh(t);let o=t.end.splice(1,t.end.length);o.push(this.sourceToken);let a={type:"block-map",offset:t.offset,indent:t.indent,items:[{start:r,key:t,sep:o}]};this.onKeyLine=!0,this.stack[this.stack.length-1]=a}else yield*this.lineEnd(t)}}flowScalar(t){if(this.onNewLine){let i=this.source.indexOf(` -`)+1;for(;i!==0;)this.onNewLine(this.offset+i),i=this.source.indexOf(` -`,i)+1}return{type:t,offset:this.offset,indent:this.indent,source:this.source}}startBlockValue(t){switch(this.type){case"alias":case"scalar":case"single-quoted-scalar":case"double-quoted-scalar":return this.flowScalar(this.type);case"block-scalar-header":return{type:"block-scalar",offset:this.offset,indent:this.indent,props:[this.sourceToken],source:""};case"flow-map-start":case"flow-seq-start":return{type:"flow-collection",offset:this.offset,indent:this.indent,start:this.sourceToken,items:[],end:[]};case"seq-item-ind":return{type:"block-seq",offset:this.offset,indent:this.indent,items:[{start:[this.sourceToken]}]};case"explicit-key-ind":{this.onKeyLine=!0;let i=dn(t),s=Ut(i);return s.push(this.sourceToken),{type:"block-map",offset:this.offset,indent:this.indent,items:[{start:s,explicitKey:!0}]}}case"map-value-ind":{this.onKeyLine=!0;let i=dn(t),s=Ut(i);return{type:"block-map",offset:this.offset,indent:this.indent,items:[{start:s,key:null,sep:[this.sourceToken]}]}}}return null}atIndentedComment(t,i){return this.type!=="comment"||this.indent<=i?!1:t.every(s=>s.type==="newline"||s.type==="space")}*documentEnd(t){this.type!=="doc-mode"&&(t.end?t.end.push(this.sourceToken):t.end=[this.sourceToken],this.type==="newline"&&(yield*this.pop()))}*lineEnd(t){switch(this.type){case"comma":case"doc-start":case"doc-end":case"flow-seq-end":case"flow-map-end":case"map-value-ind":yield*this.pop(),yield*this.step();break;case"newline":this.onKeyLine=!1;case"space":case"comment":default:t.end?t.end.push(this.sourceToken):t.end=[this.sourceToken],this.type==="newline"&&(yield*this.pop())}}};tu.Parser=Uo});var ou=y($i=>{"use strict";var iu=Io(),Cy=Ni(),Di=Ii(),My=Nr(),Dy=I(),$y=Ko(),su=zo();function nu(e){let t=e.prettyErrors!==!1;return{lineCounter:e.lineCounter||t&&new $y.LineCounter||null,prettyErrors:t}}function xy(e,t={}){let{lineCounter:i,prettyErrors:s}=nu(t),n=new su.Parser(i?.addNewLine),r=new iu.Composer(t),o=Array.from(r.compose(n.parse(e)));if(s&&i)for(let a of o)a.errors.forEach(Di.prettifyError(e,i)),a.warnings.forEach(Di.prettifyError(e,i));return o.length>0?o:Object.assign([],{empty:!0},r.streamInfo())}function ru(e,t={}){let{lineCounter:i,prettyErrors:s}=nu(t),n=new su.Parser(i?.addNewLine),r=new iu.Composer(t),o=null;for(let a of r.compose(n.parse(e),!0,e.length))if(!o)o=a;else if(o.options.logLevel!=="silent"){o.errors.push(new Di.YAMLParseError(a.range.slice(0,2),"MULTIPLE_DOCS","Source contains multiple documents; please use YAML.parseAllDocuments()"));break}return s&&i&&(o.errors.forEach(Di.prettifyError(e,i)),o.warnings.forEach(Di.prettifyError(e,i))),o}function qy(e,t,i){let s;typeof t=="function"?s=t:i===void 0&&t&&typeof t=="object"&&(i=t);let n=ru(e,i);if(!n)return null;if(n.warnings.forEach(r=>My.warn(n.options.logLevel,r)),n.errors.length>0){if(n.options.logLevel!=="silent")throw n.errors[0];n.errors=[]}return n.toJS(Object.assign({reviver:s},i))}function By(e,t,i){let s=null;if(typeof t=="function"||Array.isArray(t)?s=t:i===void 0&&t&&(i=t),typeof i=="string"&&(i=i.length),typeof i=="number"){let n=Math.round(i);i=n<1?void 0:n>8?{indent:8}:{indent:n}}if(e===void 0){let{keepUndefined:n}=i??t??{};if(!n)return}return Dy.isDocument(e)&&!s?e.toString(i):new Cy.Document(e,s,i).toString(i)}$i.parse=qy;$i.parseAllDocuments=xy;$i.parseDocument=ru;$i.stringify=By});var Go=y(T=>{"use strict";var Fy=Io(),jy=Ni(),Ky=co(),Yo=Ii(),Uy=hi(),He=I(),zy=Ue(),Yy=B(),Gy=Ye(),Wy=Ge(),Hy=un(),Vy=Fo(),Jy=Ko(),Zy=zo(),pn=ou(),au=oi();T.Composer=Fy.Composer;T.Document=jy.Document;T.Schema=Ky.Schema;T.YAMLError=Yo.YAMLError;T.YAMLParseError=Yo.YAMLParseError;T.YAMLWarning=Yo.YAMLWarning;T.Alias=Uy.Alias;T.isAlias=He.isAlias;T.isCollection=He.isCollection;T.isDocument=He.isDocument;T.isMap=He.isMap;T.isNode=He.isNode;T.isPair=He.isPair;T.isScalar=He.isScalar;T.isSeq=He.isSeq;T.Pair=zy.Pair;T.Scalar=Yy.Scalar;T.YAMLMap=Gy.YAMLMap;T.YAMLSeq=Wy.YAMLSeq;T.CST=Hy;T.Lexer=Vy.Lexer;T.LineCounter=Jy.LineCounter;T.Parser=Zy.Parser;T.parse=pn.parse;T.parseAllDocuments=pn.parseAllDocuments;T.parseDocument=pn.parseDocument;T.stringify=pn.stringify;T.visit=au.visit;T.visitAsync=au.visitAsync});var XS={};kd(XS,{finalizeInstall:()=>md});module.exports=Od(XS);var dd=require("node:fs"),X=O(require("node:fs/promises")),ms=O(require("node:os")),K=O(require("node:path")),gs=O(Go());var q=O(require("node:fs/promises")),cl=O(require("node:os")),x=O(require("node:path"));var Fu=O(require("events"),1),ee=O(require("fs"),1),zn=require("node:events"),Ka=O(require("node:stream"),1),ju=require("node:string_decoder"),za=O(require("node:path"),1),Ot=O(require("node:fs"),1),Gn=require("path"),zu=require("events"),Fn=O(require("assert"),1),st=require("buffer"),fu=O(require("zlib"),1),Yu=O(require("zlib"),1),kt=require("node:path"),Zu=require("node:path"),rs=O(require("fs"),1),me=O(require("fs"),1),Oa=O(require("path"),1),sf=require("node:path"),La=O(require("path"),1),Xa=O(require("node:fs"),1),cf=O(require("node:assert"),1),Qa=require("node:crypto"),P=O(require("node:fs"),1),j=O(require("node:path"),1),el=O(require("fs"),1),ls=O(require("node:fs"),1),Qt=O(require("node:path"),1),ne=O(require("node:fs"),1),bf=O(require("node:fs/promises"),1),os=O(require("node:path"),1),tl=require("node:path"),se=O(require("node:fs"),1),sl=O(require("node:path"),1),Xy=Object.defineProperty,Qy=(e,t)=>{for(var i in t)Xy(e,i,{get:t[i],enumerable:!0})},lu=typeof process=="object"&&process?process:{stdout:null,stderr:null},eb=e=>!!e&&typeof e=="object"&&(e instanceof Rt||e instanceof Ka.default||tb(e)||ib(e)),tb=e=>!!e&&typeof e=="object"&&e instanceof zn.EventEmitter&&typeof e.pipe=="function"&&e.pipe!==Ka.default.Writable.prototype.pipe,ib=e=>!!e&&typeof e=="object"&&e instanceof zn.EventEmitter&&typeof e.write=="function"&&typeof e.end=="function",Pe=Symbol("EOF"),Ie=Symbol("maybeEmitEnd"),Ve=Symbol("emittedEnd"),mn=Symbol("emittingEnd"),xi=Symbol("emittedError"),gn=Symbol("closed"),cu=Symbol("read"),yn=Symbol("flush"),hu=Symbol("flushChunk"),fe=Symbol("encoding"),zt=Symbol("decoder"),G=Symbol("flowing"),qi=Symbol("paused"),Ht=Symbol("resume"),W=Symbol("buffer"),Q=Symbol("pipes"),H=Symbol("bufferLength"),Wo=Symbol("bufferPush"),bn=Symbol("bufferShift"),Z=Symbol("objectMode"),$=Symbol("destroyed"),Ho=Symbol("error"),Vo=Symbol("emitData"),uu=Symbol("emitEnd"),Jo=Symbol("emitEnd2"),Ee=Symbol("async"),Zo=Symbol("abort"),wn=Symbol("aborted"),Bi=Symbol("signal"),pt=Symbol("dataListeners"),re=Symbol("discarded"),Fi=e=>Promise.resolve().then(e),sb=e=>e(),nb=e=>e==="end"||e==="finish"||e==="prefinish",rb=e=>e instanceof ArrayBuffer||!!e&&typeof e=="object"&&e.constructor&&e.constructor.name==="ArrayBuffer"&&e.byteLength>=0,ob=e=>!Buffer.isBuffer(e)&&ArrayBuffer.isView(e),Ku=class{src;dest;opts;ondrain;constructor(e,t,i){this.src=e,this.dest=t,this.opts=i,this.ondrain=()=>e[Ht](),this.dest.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(e){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},ab=class extends Ku{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,t,i){super(e,t,i),this.proxyErrors=s=>this.dest.emit("error",s),e.on("error",this.proxyErrors)}},lb=e=>!!e.objectMode,cb=e=>!e.objectMode&&!!e.encoding&&e.encoding!=="buffer",Rt=class extends zn.EventEmitter{[G]=!1;[qi]=!1;[Q]=[];[W]=[];[Z];[fe];[Ee];[zt];[Pe]=!1;[Ve]=!1;[mn]=!1;[gn]=!1;[xi]=null;[H]=0;[$]=!1;[Bi];[wn]=!1;[pt]=0;[re]=!1;writable=!0;readable=!0;constructor(...e){let t=e[0]||{};if(super(),t.objectMode&&typeof t.encoding=="string")throw new TypeError("Encoding and objectMode may not be used together");lb(t)?(this[Z]=!0,this[fe]=null):cb(t)?(this[fe]=t.encoding,this[Z]=!1):(this[Z]=!1,this[fe]=null),this[Ee]=!!t.async,this[zt]=this[fe]?new ju.StringDecoder(this[fe]):null,t&&t.debugExposeBuffer===!0&&Object.defineProperty(this,"buffer",{get:()=>this[W]}),t&&t.debugExposePipes===!0&&Object.defineProperty(this,"pipes",{get:()=>this[Q]});let{signal:i}=t;i&&(this[Bi]=i,i.aborted?this[Zo]():i.addEventListener("abort",()=>this[Zo]()))}get bufferLength(){return this[H]}get encoding(){return this[fe]}set encoding(e){throw new Error("Encoding must be set at instantiation time")}setEncoding(e){throw new Error("Encoding must be set at instantiation time")}get objectMode(){return this[Z]}set objectMode(e){throw new Error("objectMode must be set at instantiation time")}get async(){return this[Ee]}set async(e){this[Ee]=this[Ee]||!!e}[Zo](){this[wn]=!0,this.emit("abort",this[Bi]?.reason),this.destroy(this[Bi]?.reason)}get aborted(){return this[wn]}set aborted(e){}write(e,t,i){if(this[wn])return!1;if(this[Pe])throw new Error("write after end");if(this[$])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof t=="function"&&(i=t,t="utf8"),t||(t="utf8");let s=this[Ee]?Fi:sb;if(!this[Z]&&!Buffer.isBuffer(e)){if(ob(e))e=Buffer.from(e.buffer,e.byteOffset,e.byteLength);else if(rb(e))e=Buffer.from(e);else if(typeof e!="string")throw new Error("Non-contiguous data written to non-objectMode stream")}return this[Z]?(this[G]&&this[H]!==0&&this[yn](!0),this[G]?this.emit("data",e):this[Wo](e),this[H]!==0&&this.emit("readable"),i&&s(i),this[G]):e.length?(typeof e=="string"&&!(t===this[fe]&&!this[zt]?.lastNeed)&&(e=Buffer.from(e,t)),Buffer.isBuffer(e)&&this[fe]&&(e=this[zt].write(e)),this[G]&&this[H]!==0&&this[yn](!0),this[G]?this.emit("data",e):this[Wo](e),this[H]!==0&&this.emit("readable"),i&&s(i),this[G]):(this[H]!==0&&this.emit("readable"),i&&s(i),this[G])}read(e){if(this[$])return null;if(this[re]=!1,this[H]===0||e===0||e&&e>this[H])return this[Ie](),null;this[Z]&&(e=null),this[W].length>1&&!this[Z]&&(this[W]=[this[fe]?this[W].join(""):Buffer.concat(this[W],this[H])]);let t=this[cu](e||null,this[W][0]);return this[Ie](),t}[cu](e,t){if(this[Z])this[bn]();else{let i=t;e===i.length||e===null?this[bn]():typeof i=="string"?(this[W][0]=i.slice(e),t=i.slice(0,e),this[H]-=e):(this[W][0]=i.subarray(e),t=i.subarray(0,e),this[H]-=e)}return this.emit("data",t),!this[W].length&&!this[Pe]&&this.emit("drain"),t}end(e,t,i){return typeof e=="function"&&(i=e,e=void 0),typeof t=="function"&&(i=t,t="utf8"),e!==void 0&&this.write(e,t),i&&this.once("end",i),this[Pe]=!0,this.writable=!1,(this[G]||!this[qi])&&this[Ie](),this}[Ht](){this[$]||(!this[pt]&&!this[Q].length&&(this[re]=!0),this[qi]=!1,this[G]=!0,this.emit("resume"),this[W].length?this[yn]():this[Pe]?this[Ie]():this.emit("drain"))}resume(){return this[Ht]()}pause(){this[G]=!1,this[qi]=!0,this[re]=!1}get destroyed(){return this[$]}get flowing(){return this[G]}get paused(){return this[qi]}[Wo](e){this[Z]?this[H]+=1:this[H]+=e.length,this[W].push(e)}[bn](){return this[Z]?this[H]-=1:this[H]-=this[W][0].length,this[W].shift()}[yn](e=!1){do;while(this[hu](this[bn]())&&this[W].length);!e&&!this[W].length&&!this[Pe]&&this.emit("drain")}[hu](e){return this.emit("data",e),this[G]}pipe(e,t){if(this[$])return e;this[re]=!1;let i=this[Ve];return t=t||{},e===lu.stdout||e===lu.stderr?t.end=!1:t.end=t.end!==!1,t.proxyErrors=!!t.proxyErrors,i?t.end&&e.end():(this[Q].push(t.proxyErrors?new ab(this,e,t):new Ku(this,e,t)),this[Ee]?Fi(()=>this[Ht]()):this[Ht]()),e}unpipe(e){let t=this[Q].find(i=>i.dest===e);t&&(this[Q].length===1?(this[G]&&this[pt]===0&&(this[G]=!1),this[Q]=[]):this[Q].splice(this[Q].indexOf(t),1),t.unpipe())}addListener(e,t){return this.on(e,t)}on(e,t){let i=super.on(e,t);if(e==="data")this[re]=!1,this[pt]++,!this[Q].length&&!this[G]&&this[Ht]();else if(e==="readable"&&this[H]!==0)super.emit("readable");else if(nb(e)&&this[Ve])super.emit(e),this.removeAllListeners(e);else if(e==="error"&&this[xi]){let s=t;this[Ee]?Fi(()=>s.call(this,this[xi])):s.call(this,this[xi])}return i}removeListener(e,t){return this.off(e,t)}off(e,t){let i=super.off(e,t);return e==="data"&&(this[pt]=this.listeners("data").length,this[pt]===0&&!this[re]&&!this[Q].length&&(this[G]=!1)),i}removeAllListeners(e){let t=super.removeAllListeners(e);return(e==="data"||e===void 0)&&(this[pt]=0,!this[re]&&!this[Q].length&&(this[G]=!1)),t}get emittedEnd(){return this[Ve]}[Ie](){!this[mn]&&!this[Ve]&&!this[$]&&this[W].length===0&&this[Pe]&&(this[mn]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[gn]&&this.emit("close"),this[mn]=!1)}emit(e,...t){let i=t[0];if(e!=="error"&&e!=="close"&&e!==$&&this[$])return!1;if(e==="data")return!this[Z]&&!i?!1:this[Ee]?(Fi(()=>this[Vo](i)),!0):this[Vo](i);if(e==="end")return this[uu]();if(e==="close"){if(this[gn]=!0,!this[Ve]&&!this[$])return!1;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[xi]=i,super.emit(Ho,i);let n=!this[Bi]||this.listeners("error").length?super.emit("error",i):!1;return this[Ie](),n}else if(e==="resume"){let n=super.emit("resume");return this[Ie](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let s=super.emit(e,...t);return this[Ie](),s}[Vo](e){for(let i of this[Q])i.dest.write(e)===!1&&this.pause();let t=this[re]?!1:super.emit("data",e);return this[Ie](),t}[uu](){return this[Ve]?!1:(this[Ve]=!0,this.readable=!1,this[Ee]?(Fi(()=>this[Jo]()),!0):this[Jo]())}[Jo](){if(this[zt]){let t=this[zt].end();if(t){for(let i of this[Q])i.dest.write(t);this[re]||super.emit("data",t)}}for(let t of this[Q])t.end();let e=super.emit("end");return this.removeAllListeners("end"),e}async collect(){let e=Object.assign([],{dataLength:0});this[Z]||(e.dataLength=0);let t=this.promise();return this.on("data",i=>{e.push(i),this[Z]||(e.dataLength+=i.length)}),await t,e}async concat(){if(this[Z])throw new Error("cannot concat in objectMode");let e=await this.collect();return this[fe]?e.join(""):Buffer.concat(e,e.dataLength)}async promise(){return new Promise((e,t)=>{this.on($,()=>t(new Error("stream destroyed"))),this.on("error",i=>t(i)),this.on("end",()=>e())})}[Symbol.asyncIterator](){this[re]=!1;let e=!1,t=async()=>(this.pause(),e=!0,{value:void 0,done:!0});return{next:()=>{if(e)return t();let i=this.read();if(i!==null)return Promise.resolve({done:!1,value:i});if(this[Pe])return t();let s,n,r=c=>{this.off("data",o),this.off("end",a),this.off($,l),t(),n(c)},o=c=>{this.off("error",r),this.off("end",a),this.off($,l),this.pause(),s({value:c,done:!!this[Pe]})},a=()=>{this.off("error",r),this.off("data",o),this.off($,l),t(),s({done:!0,value:void 0})},l=()=>r(new Error("stream destroyed"));return new Promise((c,h)=>{n=h,s=c,this.once($,l),this.once("error",r),this.once("end",a),this.once("data",o)})},throw:t,return:t,[Symbol.asyncIterator](){return this},[Symbol.asyncDispose]:async()=>{}}}[Symbol.iterator](){this[re]=!1;let e=!1,t=()=>(this.pause(),this.off(Ho,t),this.off($,t),this.off("end",t),e=!0,{done:!0,value:void 0}),i=()=>{if(e)return t();let s=this.read();return s===null?t():{done:!1,value:s}};return this.once("end",t),this.once(Ho,t),this.once($,t),{next:i,throw:t,return:t,[Symbol.iterator](){return this},[Symbol.dispose]:()=>{}}}destroy(e){if(this[$])return e?this.emit("error",e):this.emit($),this;this[$]=!0,this[re]=!0,this[W].length=0,this[H]=0;let t=this;return typeof t.close=="function"&&!this[gn]&&t.close(),e?this.emit("error",e):this.emit($),this}static get isStream(){return eb}},hb=ee.default.writev,rt=Symbol("_autoClose"),ye=Symbol("_close"),ji=Symbol("_ended"),L=Symbol("_fd"),Xo=Symbol("_finished"),Me=Symbol("_flags"),Qo=Symbol("_flush"),Sa=Symbol("_handleChunk"),va=Symbol("_makeBuf"),Zi=Symbol("_mode"),Sn=Symbol("_needDrain"),Xt=Symbol("_onerror"),ei=Symbol("_onopen"),ea=Symbol("_onread"),Vt=Symbol("_onwrite"),ot=Symbol("_open"),ge=Symbol("_path"),Qe=Symbol("_pos"),ke=Symbol("_queue"),Jt=Symbol("_read"),ta=Symbol("_readSize"),Ce=Symbol("_reading"),Ki=Symbol("_remain"),ia=Symbol("_size"),Pn=Symbol("_write"),mt=Symbol("_writing"),In=Symbol("_defaultFlag"),_t=Symbol("_errored"),Ua=class extends Rt{[_t]=!1;[L];[ge];[ta];[Ce]=!1;[ia];[Ki];[rt];constructor(e,t){if(t=t||{},super(t),this.readable=!0,this.writable=!1,typeof e!="string")throw new TypeError("path must be a string");this[_t]=!1,this[L]=typeof t.fd=="number"?t.fd:void 0,this[ge]=e,this[ta]=t.readSize||16*1024*1024,this[Ce]=!1,this[ia]=typeof t.size=="number"?t.size:1/0,this[Ki]=this[ia],this[rt]=typeof t.autoClose=="boolean"?t.autoClose:!0,typeof this[L]=="number"?this[Jt]():this[ot]()}get fd(){return this[L]}get path(){return this[ge]}write(){throw new TypeError("this is a readable stream")}end(){throw new TypeError("this is a readable stream")}[ot](){ee.default.open(this[ge],"r",(e,t)=>this[ei](e,t))}[ei](e,t){e?this[Xt](e):(this[L]=t,this.emit("open",t),this[Jt]())}[va](){return Buffer.allocUnsafe(Math.min(this[ta],this[Ki]))}[Jt](){if(!this[Ce]){this[Ce]=!0;let e=this[va]();if(e.length===0)return process.nextTick(()=>this[ea](null,0,e));ee.default.read(this[L],e,0,e.length,null,(t,i,s)=>this[ea](t,i,s))}}[ea](e,t,i){this[Ce]=!1,e?this[Xt](e):this[Sa](t,i)&&this[Jt]()}[ye](){if(this[rt]&&typeof this[L]=="number"){let e=this[L];this[L]=void 0,ee.default.close(e,t=>t?this.emit("error",t):this.emit("close"))}}[Xt](e){this[Ce]=!0,this[ye](),this.emit("error",e)}[Sa](e,t){let i=!1;return this[Ki]-=e,e>0&&(i=super.write(ethis[ei](e,t))}[ei](e,t){this[In]&&this[Me]==="r+"&&e&&e.code==="ENOENT"?(this[Me]="w",this[ot]()):e?this[Xt](e):(this[L]=t,this.emit("open",t),this[mt]||this[Qo]())}end(e,t){return e&&this.write(e,t),this[ji]=!0,!this[mt]&&!this[ke].length&&typeof this[L]=="number"&&this[Vt](null,0),this}write(e,t){return typeof e=="string"&&(e=Buffer.from(e,t)),this[ji]?(this.emit("error",new Error("write() after end()")),!1):this[L]===void 0||this[mt]||this[ke].length?(this[ke].push(e),this[Sn]=!0,!1):(this[mt]=!0,this[Pn](e),!0)}[Pn](e){ee.default.write(this[L],e,0,e.length,this[Qe],(t,i)=>this[Vt](t,i))}[Vt](e,t){e?this[Xt](e):(this[Qe]!==void 0&&typeof t=="number"&&(this[Qe]+=t),this[ke].length?this[Qo]():(this[mt]=!1,this[ji]&&!this[Xo]?(this[Xo]=!0,this[ye](),this.emit("finish")):this[Sn]&&(this[Sn]=!1,this.emit("drain"))))}[Qo](){if(this[ke].length===0)this[ji]&&this[Vt](null,0);else if(this[ke].length===1)this[Pn](this[ke].pop());else{let e=this[ke];this[ke]=[],hb(this[L],e,this[Qe],(t,i)=>this[Vt](t,i))}}[ye](){if(this[rt]&&typeof this[L]=="number"){let e=this[L];this[L]=void 0,ee.default.close(e,t=>t?this.emit("error",t):this.emit("close"))}}},Uu=class extends Yn{[ot](){let e;if(this[In]&&this[Me]==="r+")try{e=ee.default.openSync(this[ge],this[Me],this[Zi])}catch(t){if(t?.code==="ENOENT")return this[Me]="w",this[ot]();throw t}else e=ee.default.openSync(this[ge],this[Me],this[Zi]);this[ei](null,e)}[ye](){if(this[rt]&&typeof this[L]=="number"){let e=this[L];this[L]=void 0,ee.default.closeSync(e),this.emit("close")}}[Pn](e){let t=!0;try{this[Vt](null,ee.default.writeSync(this[L],e,0,e.length,this[Qe])),t=!1}finally{if(t)try{this[ye]()}catch{}}}},fb=new Map([["C","cwd"],["f","file"],["z","gzip"],["P","preservePaths"],["U","unlink"],["strip-components","strip"],["stripComponents","strip"],["keep-newer","newer"],["keepNewer","newer"],["keep-newer-files","newer"],["keepNewerFiles","newer"],["k","keep"],["keep-existing","keep"],["keepExisting","keep"],["m","noMtime"],["no-mtime","noMtime"],["p","preserveOwner"],["L","follow"],["h","follow"],["onentry","onReadEntry"]]),db=e=>!!e.sync&&!!e.file,pb=e=>!e.sync&&!!e.file,mb=e=>!!e.sync&&!e.file,gb=e=>!e.sync&&!e.file,yb=e=>!!e.file,bb=e=>fb.get(e)||e,Ya=(e={})=>{if(!e)return{};let t={};for(let[i,s]of Object.entries(e)){let n=bb(i);t[n]=s}return t.chmod===void 0&&t.noChmod===!1&&(t.chmod=!0),delete t.noChmod,t},as=(e,t,i,s,n)=>Object.assign((r=[],o,a)=>{Array.isArray(r)&&(o=r,r={}),typeof o=="function"&&(a=o,o=void 0),o=o?Array.from(o):[];let l=Ya(r);if(n?.(l,o),db(l)){if(typeof a=="function")throw new TypeError("callback not supported for sync tar functions");return e(l,o)}else if(pb(l)){let c=t(l,o);return a?c.then(()=>a(),a):c}else if(mb(l)){if(typeof a=="function")throw new TypeError("callback not supported for sync tar functions");return i(l,o)}else if(gb(l)){if(typeof a=="function")throw new TypeError("callback only supported with file option");return s(l,o)}throw new Error("impossible options??")},{syncFile:e,asyncFile:t,syncNoFile:i,asyncNoFile:s,validate:n}),wb=Yu.default.constants||{ZLIB_VERNUM:4736},Ae=Object.freeze(Object.assign(Object.create(null),{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_VERSION_ERROR:-6,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,DEFLATE:1,INFLATE:2,GZIP:3,GUNZIP:4,DEFLATERAW:5,INFLATERAW:6,UNZIP:7,BROTLI_DECODE:8,BROTLI_ENCODE:9,Z_MIN_WINDOWBITS:8,Z_MAX_WINDOWBITS:15,Z_DEFAULT_WINDOWBITS:15,Z_MIN_CHUNK:64,Z_MAX_CHUNK:1/0,Z_DEFAULT_CHUNK:16384,Z_MIN_MEMLEVEL:1,Z_MAX_MEMLEVEL:9,Z_DEFAULT_MEMLEVEL:8,Z_MIN_LEVEL:-1,Z_MAX_LEVEL:9,Z_DEFAULT_LEVEL:-1,BROTLI_OPERATION_PROCESS:0,BROTLI_OPERATION_FLUSH:1,BROTLI_OPERATION_FINISH:2,BROTLI_OPERATION_EMIT_METADATA:3,BROTLI_MODE_GENERIC:0,BROTLI_MODE_TEXT:1,BROTLI_MODE_FONT:2,BROTLI_DEFAULT_MODE:0,BROTLI_MIN_QUALITY:0,BROTLI_MAX_QUALITY:11,BROTLI_DEFAULT_QUALITY:11,BROTLI_MIN_WINDOW_BITS:10,BROTLI_MAX_WINDOW_BITS:24,BROTLI_LARGE_MAX_WINDOW_BITS:30,BROTLI_DEFAULT_WINDOW:22,BROTLI_MIN_INPUT_BLOCK_BITS:16,BROTLI_MAX_INPUT_BLOCK_BITS:24,BROTLI_PARAM_MODE:0,BROTLI_PARAM_QUALITY:1,BROTLI_PARAM_LGWIN:2,BROTLI_PARAM_LGBLOCK:3,BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:4,BROTLI_PARAM_SIZE_HINT:5,BROTLI_PARAM_LARGE_WINDOW:6,BROTLI_PARAM_NPOSTFIX:7,BROTLI_PARAM_NDIRECT:8,BROTLI_DECODER_RESULT_ERROR:0,BROTLI_DECODER_RESULT_SUCCESS:1,BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:2,BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION:0,BROTLI_DECODER_PARAM_LARGE_WINDOW:1,BROTLI_DECODER_NO_ERROR:0,BROTLI_DECODER_SUCCESS:1,BROTLI_DECODER_NEEDS_MORE_INPUT:2,BROTLI_DECODER_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:-1,BROTLI_DECODER_ERROR_FORMAT_RESERVED:-2,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:-3,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:-4,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:-5,BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:-6,BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:-7,BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:-8,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:-9,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:-10,BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:-11,BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:-12,BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:-13,BROTLI_DECODER_ERROR_FORMAT_PADDING_1:-14,BROTLI_DECODER_ERROR_FORMAT_PADDING_2:-15,BROTLI_DECODER_ERROR_FORMAT_DISTANCE:-16,BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:-19,BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:-20,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:-21,BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:-22,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:-25,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:-26,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:-27,BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:-30,BROTLI_DECODER_ERROR_UNREACHABLE:-31},wb)),Sb=st.Buffer.concat,du=Object.getOwnPropertyDescriptor(st.Buffer,"concat"),vb=e=>e,sa=du?.writable===!0||du?.set!==void 0?e=>{st.Buffer.concat=e?vb:Sb}:e=>{},At=Symbol("_superWrite"),vn=class extends Error{code;errno;constructor(e,t){super("zlib: "+e.message,{cause:e}),this.code=e.code,this.errno=e.errno,this.code||(this.code="ZLIB_ERROR"),this.message="zlib: "+e.message,Error.captureStackTrace(this,t??this.constructor)}get name(){return"ZlibError"}},na=Symbol("flushFlag"),Ga=class extends Rt{#e=!1;#i=!1;#s;#r;#n;#t;#o;get sawError(){return this.#e}get handle(){return this.#t}get flushFlag(){return this.#s}constructor(e,t){if(!e||typeof e!="object")throw new TypeError("invalid options for ZlibBase constructor");if(super(e),this.#s=e.flush??0,this.#r=e.finishFlush??0,this.#n=e.fullFlushFlag??0,typeof fu[t]!="function")throw new TypeError("Compression method not supported: "+t);try{this.#t=new fu[t](e)}catch(i){throw new vn(i,this.constructor)}this.#o=i=>{this.#e||(this.#e=!0,this.close(),this.emit("error",i))},this.#t?.on("error",i=>this.#o(new vn(i))),this.once("end",()=>this.close)}close(){this.#t&&(this.#t.close(),this.#t=void 0,this.emit("close"))}reset(){if(!this.#e)return(0,Fn.default)(this.#t,"zlib binding closed"),this.#t.reset?.()}flush(e){this.ended||(typeof e!="number"&&(e=this.#n),this.write(Object.assign(st.Buffer.alloc(0),{[na]:e})))}end(e,t,i){return typeof e=="function"&&(i=e,t=void 0,e=void 0),typeof t=="function"&&(i=t,t=void 0),e&&(t?this.write(e,t):this.write(e)),this.flush(this.#r),this.#i=!0,super.end(i)}get ended(){return this.#i}[At](e){return super.write(e)}write(e,t,i){if(typeof t=="function"&&(i=t,t="utf8"),typeof e=="string"&&(e=st.Buffer.from(e,t)),this.#e)return;(0,Fn.default)(this.#t,"zlib binding closed");let s=this.#t._handle,n=s.close;s.close=()=>{};let r=this.#t.close;this.#t.close=()=>{},sa(!0);let o;try{let l=typeof e[na]=="number"?e[na]:this.#s;o=this.#t._processChunk(e,l),sa(!1)}catch(l){sa(!1),this.#o(new vn(l,this.write))}finally{this.#t&&(this.#t._handle=s,s.close=n,this.#t.close=r,this.#t.removeAllListeners("error"))}this.#t&&this.#t.on("error",l=>this.#o(new vn(l,this.write)));let a;if(o)if(Array.isArray(o)&&o.length>0){let l=o[0];a=this[At](st.Buffer.from(l));for(let c=1;c{typeof s=="function"&&(n=s,s=this.flushFlag),this.flush(s),n?.()};try{this.handle.params(e,t)}finally{this.handle.flush=i}this.handle&&(this.#e=e,this.#i=t)}}}},Eb=class extends Gu{#e;constructor(e){super(e,"Gzip"),this.#e=e&&!!e.portable}[At](e){return this.#e?(this.#e=!1,e[9]=255,super[At](e)):super[At](e)}},kb=class extends Gu{constructor(e){super(e,"Unzip")}},Wu=class extends Ga{constructor(e,t){e=e||{},e.flush=e.flush||Ae.BROTLI_OPERATION_PROCESS,e.finishFlush=e.finishFlush||Ae.BROTLI_OPERATION_FINISH,e.fullFlushFlag=Ae.BROTLI_OPERATION_FLUSH,super(e,t)}},Ob=class extends Wu{constructor(e){super(e,"BrotliCompress")}},_b=class extends Wu{constructor(e){super(e,"BrotliDecompress")}},Hu=class extends Ga{constructor(e,t){e=e||{},e.flush=e.flush||Ae.ZSTD_e_continue,e.finishFlush=e.finishFlush||Ae.ZSTD_e_end,e.fullFlushFlag=Ae.ZSTD_e_flush,super(e,t)}},Ab=class extends Hu{constructor(e){super(e,"ZstdCompress")}},Nb=class extends Hu{constructor(e){super(e,"ZstdDecompress")}},Rb=(e,t)=>{if(Number.isSafeInteger(e))e<0?Ib(e,t):Pb(e,t);else throw Error("cannot encode number outside of javascript safe integer range");return t},Pb=(e,t)=>{t[0]=128;for(var i=t.length;i>1;i--)t[i-1]=e&255,e=Math.floor(e/256)},Ib=(e,t)=>{t[0]=255;var i=!1;e=e*-1;for(var s=t.length;s>1;s--){var n=e&255;e=Math.floor(e/256),i?t[s-1]=Vu(n):n===0?t[s-1]=0:(i=!0,t[s-1]=Ju(n))}},Tb=e=>{let t=e[0],i=t===128?Cb(e.subarray(1,e.length)):t===255?Lb(e):null;if(i===null)throw Error("invalid base256 encoding");if(!Number.isSafeInteger(i))throw Error("parsed number outside of javascript safe integer range");return i},Lb=e=>{for(var t=e.length,i=0,s=!1,n=t-1;n>-1;n--){var r=Number(e[n]),o;s?o=Vu(r):r===0?o=r:(s=!0,o=Ju(r)),o!==0&&(i-=o*Math.pow(256,t-n-1))}return i},Cb=e=>{for(var t=e.length,i=0,s=t-1;s>-1;s--){var n=Number(e[s]);n!==0&&(i+=n*Math.pow(256,t-s-1))}return i},Vu=e=>(255^e)&255,Ju=e=>(255^e)+1&255,Mb={};Qy(Mb,{code:()=>Wa,isCode:()=>Tn,isName:()=>Db,name:()=>Wn});var Tn=e=>Wn.has(e),Db=e=>Wa.has(e),Wn=new Map([["0","File"],["","OldFile"],["1","Link"],["2","SymbolicLink"],["3","CharacterDevice"],["4","BlockDevice"],["5","Directory"],["6","FIFO"],["7","ContiguousFile"],["g","GlobalExtendedHeader"],["x","ExtendedHeader"],["A","SolarisACL"],["D","GNUDumpDir"],["I","Inode"],["K","NextFileHasLongLinkpath"],["L","NextFileHasLongPath"],["M","ContinuationFile"],["N","OldGnuLongPath"],["S","SparseFile"],["V","TapeVolumeHeader"],["X","OldExtendedHeader"]]),Wa=new Map(Array.from(Wn).map(e=>[e[1],e[0]])),Nt=class{cksumValid=!1;needPax=!1;nullBlock=!1;block;path;mode;uid;gid;size;cksum;#e="Unsupported";linkpath;uname;gname;devmaj=0;devmin=0;atime;ctime;mtime;charset;comment;constructor(e,t=0,i,s){Buffer.isBuffer(e)?this.decode(e,t||0,i,s):e&&this.#i(e)}decode(e,t,i,s){if(t||(t=0),!e||!(e.length>=t+512))throw new Error("need 512 bytes for header");this.path=i?.path??gt(e,t,100),this.mode=i?.mode??s?.mode??et(e,t+100,8),this.uid=i?.uid??s?.uid??et(e,t+108,8),this.gid=i?.gid??s?.gid??et(e,t+116,8),this.size=i?.size??s?.size??et(e,t+124,12),this.mtime=i?.mtime??s?.mtime??ra(e,t+136,12),this.cksum=et(e,t+148,12),s&&this.#i(s,!0),i&&this.#i(i);let n=gt(e,t+156,1);if(Tn(n)&&(this.#e=n||"0"),this.#e==="0"&&this.path.slice(-1)==="/"&&(this.#e="5"),this.#e==="5"&&(this.size=0),this.linkpath=gt(e,t+157,100),e.subarray(t+257,t+265).toString()==="ustar\x0000")if(this.uname=i?.uname??s?.uname??gt(e,t+265,32),this.gname=i?.gname??s?.gname??gt(e,t+297,32),this.devmaj=i?.devmaj??s?.devmaj??et(e,t+329,8)??0,this.devmin=i?.devmin??s?.devmin??et(e,t+337,8)??0,e[t+475]!==0){let o=gt(e,t+345,155);this.path=o+"/"+this.path}else{let o=gt(e,t+345,130);o&&(this.path=o+"/"+this.path),this.atime=i?.atime??s?.atime??ra(e,t+476,12),this.ctime=i?.ctime??s?.ctime??ra(e,t+488,12)}let r=256;for(let o=t;o!(s==null||i==="path"&&t||i==="linkpath"&&t||i==="global"))))}encode(e,t=0){if(e||(e=this.block=Buffer.alloc(512)),this.#e==="Unsupported"&&(this.#e="0"),!(e.length>=t+512))throw new Error("need 512 bytes for header");let i=this.ctime||this.atime?130:155,s=$b(this.path||"",i),n=s[0],r=s[1];this.needPax=!!s[2],this.needPax=yt(e,t,100,n)||this.needPax,this.needPax=tt(e,t+100,8,this.mode)||this.needPax,this.needPax=tt(e,t+108,8,this.uid)||this.needPax,this.needPax=tt(e,t+116,8,this.gid)||this.needPax,this.needPax=tt(e,t+124,12,this.size)||this.needPax,this.needPax=oa(e,t+136,12,this.mtime)||this.needPax,e[t+156]=Number(this.#e.codePointAt(0)),this.needPax=yt(e,t+157,100,this.linkpath)||this.needPax,e.write("ustar\x0000",t+257,8),this.needPax=yt(e,t+265,32,this.uname)||this.needPax,this.needPax=yt(e,t+297,32,this.gname)||this.needPax,this.needPax=tt(e,t+329,8,this.devmaj)||this.needPax,this.needPax=tt(e,t+337,8,this.devmin)||this.needPax,this.needPax=yt(e,t+345,i,r)||this.needPax,e[t+475]!==0?this.needPax=yt(e,t+345,155,r)||this.needPax:(this.needPax=yt(e,t+345,130,r)||this.needPax,this.needPax=oa(e,t+476,12,this.atime)||this.needPax,this.needPax=oa(e,t+488,12,this.ctime)||this.needPax);let o=256;for(let a=t;a{let i=e,s="",n,r=kt.posix.parse(e).root||".";if(Buffer.byteLength(i)<100)n=[i,s,!1];else{s=kt.posix.dirname(i),i=kt.posix.basename(i);do Buffer.byteLength(i)<=100&&Buffer.byteLength(s)<=t?n=[i,s,!1]:Buffer.byteLength(i)>100&&Buffer.byteLength(s)<=t?n=[i.slice(0,99),s,!0]:(i=kt.posix.join(kt.posix.basename(s),i),s=kt.posix.dirname(s));while(s!==r&&n===void 0);n||(n=[e.slice(0,99),"",!0])}return n},gt=(e,t,i)=>e.subarray(t,t+i).toString("utf8").replace(/\0.*/,""),ra=(e,t,i)=>xb(et(e,t,i)),xb=e=>e===void 0?void 0:new Date(e*1e3),et=(e,t,i)=>Number(e[t])&128?Tb(e.subarray(t,t+i)):Bb(e,t,i),qb=e=>isNaN(e)?void 0:e,Bb=(e,t,i)=>qb(parseInt(e.subarray(t,t+i).toString("utf8").replace(/\0.*$/,"").trim(),8)),Fb={12:8589934591,8:2097151},tt=(e,t,i,s)=>s===void 0?!1:s>Fb[i]||s<0?(Rb(s,e.subarray(t,t+i)),!0):(jb(e,t,i,s),!1),jb=(e,t,i,s)=>e.write(Kb(s,i),t,i,"ascii"),Kb=(e,t)=>Ub(Math.floor(e).toString(8),t),Ub=(e,t)=>(e.length===t-1?e:new Array(t-e.length-1).join("0")+e+" ")+"\0",oa=(e,t,i,s)=>s===void 0?!1:tt(e,t,i,s.getTime()/1e3),zb=new Array(156).join("\0"),yt=(e,t,i,s)=>s===void 0?!1:(e.write(s+zb,t,i,"utf8"),s.length!==Buffer.byteLength(s)||s.length>i),jn=class Xu{atime;mtime;ctime;charset;comment;gid;uid;gname;uname;linkpath;dev;ino;nlink;path;size;mode;global;constructor(t,i=!1){this.atime=t.atime,this.charset=t.charset,this.comment=t.comment,this.ctime=t.ctime,this.dev=t.dev,this.gid=t.gid,this.global=i,this.gname=t.gname,this.ino=t.ino,this.linkpath=t.linkpath,this.mtime=t.mtime,this.nlink=t.nlink,this.path=t.path,this.size=t.size,this.uid=t.uid,this.uname=t.uname}encode(){let t=this.encodeBody();if(t==="")return Buffer.allocUnsafe(0);let i=Buffer.byteLength(t),s=512*Math.ceil(1+i/512),n=Buffer.allocUnsafe(s);for(let r=0;r<512;r++)n[r]=0;new Nt({path:("PaxHeader/"+(0,Zu.basename)(this.path??"")).slice(0,99),mode:this.mode||420,uid:this.uid,gid:this.gid,size:i,mtime:this.mtime,type:this.global?"GlobalExtendedHeader":"ExtendedHeader",linkpath:"",uname:this.uname||"",gname:this.gname||"",devmaj:0,devmin:0,atime:this.atime,ctime:this.ctime}).encode(n),n.write(t,512,i,"utf8");for(let r=i+512;r=Math.pow(10,o)&&(o+=1),o+r+n}static parse(t,i,s=!1){return new Xu(Yb(Gb(t),i),s)}},Yb=(e,t)=>t?Object.assign({},t,e):e,Gb=e=>e.replace(/\n$/,"").split(` -`).reduce(Wb,Object.create(null)),Wb=(e,t)=>{let i=parseInt(t,10);if(i!==Buffer.byteLength(t)+1)return e;t=t.slice((i+" ").length);let s=t.split("="),n=s.shift();if(!n)return e;let r=n.replace(/^SCHILY\.(dev|ino|nlink)/,"$1"),o=s.join("=");return e[r]=/^([A-Z]+\.)?([mac]|birth|creation)time$/.test(r)?new Date(Number(o)*1e3):/^[0-9]+$/.test(o)?+o:o,e},Hb=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,R=Hb!=="win32"?e=>e:e=>e&&e.replaceAll(/\\/g,"/"),Qu=class extends Rt{extended;globalExtended;header;startBlockSize;blockRemain;remain;type;meta=!1;ignore=!1;path;mode;uid;gid;uname;gname;size=0;mtime;atime;ctime;linkpath;dev;ino;nlink;invalid=!1;absolute;unsupported=!1;constructor(e,t,i){switch(super({}),this.pause(),this.extended=t,this.globalExtended=i,this.header=e,this.remain=e.size??0,this.startBlockSize=512*Math.ceil(this.remain/512),this.blockRemain=this.startBlockSize,this.type=e.type,this.type){case"File":case"OldFile":case"Link":case"SymbolicLink":case"CharacterDevice":case"BlockDevice":case"Directory":case"FIFO":case"ContiguousFile":case"GNUDumpDir":break;case"NextFileHasLongLinkpath":case"NextFileHasLongPath":case"OldGnuLongPath":case"GlobalExtendedHeader":case"ExtendedHeader":case"OldExtendedHeader":this.meta=!0;break;default:this.ignore=!0}if(!e.path)throw new Error("no path provided for tar.ReadEntry");this.path=R(e.path),this.mode=e.mode,this.mode&&(this.mode=this.mode&4095),this.uid=e.uid,this.gid=e.gid,this.uname=e.uname,this.gname=e.gname,this.size=this.remain,this.mtime=e.mtime,this.atime=e.atime,this.ctime=e.ctime,this.linkpath=e.linkpath?R(e.linkpath):void 0,this.uname=e.uname,this.gname=e.gname,t&&this.#e(t),i&&this.#e(i,!0)}write(e){let t=e.length;if(t>this.blockRemain)throw new Error("writing more to entry than is appropriate");let i=this.remain,s=this.blockRemain;return this.remain=Math.max(0,i-t),this.blockRemain=Math.max(0,s-t),this.ignore?!0:i>=t?super.write(e):super.write(e.subarray(0,i))}#e(e,t=!1){e.path&&(e.path=R(e.path)),e.linkpath&&(e.linkpath=R(e.linkpath)),Object.assign(this,Object.fromEntries(Object.entries(e).filter(([i,s])=>!(s==null||i==="path"&&t))))}},Hn=(e,t,i,s={})=>{e.file&&(s.file=e.file),e.cwd&&(s.cwd=e.cwd),s.code=i instanceof Error&&i.code||t,s.tarCode=t,!e.strict&&s.recoverable!==!1?(i instanceof Error&&(s=Object.assign(i,s),i=i.message),e.emit("warn",t,i,s)):i instanceof Error?e.emit("error",Object.assign(i,s)):e.emit("error",Object.assign(new Error(`${t}: ${i}`),s))},Vb=1024*1024,Ea=Buffer.from([31,139]),ka=Buffer.from([40,181,47,253]),Jb=Math.max(Ea.length,ka.length),ae=Symbol("state"),bt=Symbol("writeEntry"),Te=Symbol("readEntry"),aa=Symbol("nextEntry"),pu=Symbol("processEntry"),Oe=Symbol("extendedHeader"),Ui=Symbol("globalExtendedHeader"),Je=Symbol("meta"),mu=Symbol("emitMeta"),M=Symbol("buffer"),Le=Symbol("queue"),Ze=Symbol("ended"),la=Symbol("emittedEnd"),wt=Symbol("emit"),F=Symbol("unzip"),En=Symbol("consumeChunk"),kn=Symbol("consumeChunkSub"),ca=Symbol("consumeBody"),gu=Symbol("consumeMeta"),yu=Symbol("consumeHeader"),zi=Symbol("consuming"),ha=Symbol("bufferConcat"),On=Symbol("maybeEnd"),Yt=Symbol("writing"),Xe=Symbol("aborted"),_n=Symbol("onDone"),St=Symbol("sawValidEntry"),An=Symbol("sawNullBlock"),Nn=Symbol("sawEOF"),bu=Symbol("closeStream"),Zb=()=>!0,ns=class extends zu.EventEmitter{file;strict;maxMetaEntrySize;filter;brotli;zstd;writable=!0;readable=!1;[Le]=[];[M];[Te];[bt];[ae]="begin";[Je]="";[Oe];[Ui];[Ze]=!1;[F];[Xe]=!1;[St];[An]=!1;[Nn]=!1;[Yt]=!1;[zi]=!1;[la]=!1;constructor(e={}){super(),this.file=e.file||"",this.on(_n,()=>{(this[ae]==="begin"||this[St]===!1)&&this.warn("TAR_BAD_ARCHIVE","Unrecognized archive format")}),e.ondone?this.on(_n,e.ondone):this.on(_n,()=>{this.emit("prefinish"),this.emit("finish"),this.emit("end")}),this.strict=!!e.strict,this.maxMetaEntrySize=e.maxMetaEntrySize||Vb,this.filter=typeof e.filter=="function"?e.filter:Zb;let t=e.file&&(e.file.endsWith(".tar.br")||e.file.endsWith(".tbr"));this.brotli=!(e.gzip||e.zstd)&&e.brotli!==void 0?e.brotli:t?void 0:!1;let i=e.file&&(e.file.endsWith(".tar.zst")||e.file.endsWith(".tzst"));this.zstd=!(e.gzip||e.brotli)&&e.zstd!==void 0?e.zstd:i?!0:void 0,this.on("end",()=>this[bu]()),typeof e.onwarn=="function"&&this.on("warn",e.onwarn),typeof e.onReadEntry=="function"&&this.on("entry",e.onReadEntry)}warn(e,t,i={}){Hn(this,e,t,i)}[yu](e,t){this[St]===void 0&&(this[St]=!1);let i;try{i=new Nt(e,t,this[Oe],this[Ui])}catch(s){return this.warn("TAR_ENTRY_INVALID",s)}if(i.nullBlock)this[An]?(this[Nn]=!0,this[ae]==="begin"&&(this[ae]="header"),this[wt]("eof")):(this[An]=!0,this[wt]("nullBlock"));else if(this[An]=!1,!i.cksumValid)this.warn("TAR_ENTRY_INVALID","checksum failure",{header:i});else if(!i.path)this.warn("TAR_ENTRY_INVALID","path is required",{header:i});else{let s=i.type;if(/^(Symbolic)?Link$/.test(s)&&!i.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath required",{header:i});else if(!/^(Symbolic)?Link$/.test(s)&&!/^(Global)?ExtendedHeader$/.test(s)&&i.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath forbidden",{header:i});else{let n=this[bt]=new Qu(i,this[Oe],this[Ui]);if(!this[St])if(n.remain){let r=()=>{n.invalid||(this[St]=!0)};n.on("end",r)}else this[St]=!0;n.meta?n.size>this.maxMetaEntrySize?(n.ignore=!0,this[wt]("ignoredEntry",n),this[ae]="ignore",n.resume()):n.size>0&&(this[Je]="",n.on("data",r=>this[Je]+=r),this[ae]="meta"):(this[Oe]=void 0,n.ignore=n.ignore||!this.filter(n.path,n),n.ignore?(this[wt]("ignoredEntry",n),this[ae]=n.remain?"ignore":"header",n.resume()):(n.remain?this[ae]="body":(this[ae]="header",n.end()),this[Te]?this[Le].push(n):(this[Le].push(n),this[aa]())))}}}[bu](){queueMicrotask(()=>this.emit("close"))}[pu](e){let t=!0;if(!e)this[Te]=void 0,t=!1;else if(Array.isArray(e)){let[i,...s]=e;this.emit(i,...s)}else this[Te]=e,this.emit("entry",e),e.emittedEnd||(e.on("end",()=>this[aa]()),t=!1);return t}[aa](){do;while(this[pu](this[Le].shift()));if(this[Le].length===0){let e=this[Te];!e||e.flowing||e.size===e.remain?this[Yt]||this.emit("drain"):e.once("drain",()=>this.emit("drain"))}}[ca](e,t){let i=this[bt];if(!i)throw new Error("attempt to consume body without entry??");let s=i.blockRemain??0,n=s>=e.length&&t===0?e:e.subarray(t,t+s);return i.write(n),i.blockRemain||(this[ae]="header",this[bt]=void 0,i.end()),n.length}[gu](e,t){let i=this[bt],s=this[ca](e,t);return!this[bt]&&i&&this[mu](i),s}[wt](e,t,i){this[Le].length===0&&!this[Te]?this.emit(e,t,i):this[Le].push([e,t,i])}[mu](e){switch(this[wt]("meta",this[Je]),e.type){case"ExtendedHeader":case"OldExtendedHeader":this[Oe]=jn.parse(this[Je],this[Oe],!1);break;case"GlobalExtendedHeader":this[Ui]=jn.parse(this[Je],this[Ui],!0);break;case"NextFileHasLongPath":case"OldGnuLongPath":{let t=this[Oe]??Object.create(null);this[Oe]=t,t.path=this[Je].replace(/\0.*/,"");break}case"NextFileHasLongLinkpath":{let t=this[Oe]||Object.create(null);this[Oe]=t,t.linkpath=this[Je].replace(/\0.*/,"");break}default:throw new Error("unknown meta: "+e.type)}}abort(e){this[Xe]=!0,this.emit("abort",e),this.warn("TAR_ABORT",e,{recoverable:!1})}write(e,t,i){if(typeof t=="function"&&(i=t,t=void 0),typeof e=="string"&&(e=Buffer.from(e,typeof t=="string"?t:"utf8")),this[Xe])return i?.(),!1;if((this[F]===void 0||this.brotli===void 0&&this[F]===!1)&&e){if(this[M]&&(e=Buffer.concat([this[M],e]),this[M]=void 0),e.lengththis[En](l)),this[F].on("error",l=>this.abort(l)),this[F].on("end",()=>{this[Ze]=!0,this[En]()}),this[Yt]=!0;let a=!!this[F][o?"end":"write"](e);return this[Yt]=!1,i?.(),a}}this[Yt]=!0,this[F]?this[F].write(e):this[En](e),this[Yt]=!1;let s=this[Le].length>0?!1:this[Te]?this[Te].flowing:!0;return!s&&this[Le].length===0&&this[Te]?.once("drain",()=>this.emit("drain")),i?.(),s}[ha](e){e&&!this[Xe]&&(this[M]=this[M]?Buffer.concat([this[M],e]):e)}[On](){if(this[Ze]&&!this[la]&&!this[Xe]&&!this[zi]){this[la]=!0;let e=this[bt];if(e&&e.blockRemain){let t=this[M]?this[M].length:0;this.warn("TAR_BAD_ARCHIVE",`Truncated input (needed ${e.blockRemain} more bytes, only ${t} available)`,{entry:e}),this[M]&&e.write(this[M]),e.end()}this[wt](_n)}}[En](e){if(this[zi]&&e)this[ha](e);else if(!e&&!this[M])this[On]();else if(e){if(this[zi]=!0,this[M]){this[ha](e);let t=this[M];this[M]=void 0,this[kn](t)}else this[kn](e);for(;this[M]&&this[M]?.length>=512&&!this[Xe]&&!this[Nn];){let t=this[M];this[M]=void 0,this[kn](t)}this[zi]=!1}(!this[M]||this[Ze])&&this[On]()}[kn](e){let t=0,i=e.length;for(;t+512<=i&&!this[Xe]&&!this[Nn];)switch(this[ae]){case"begin":case"header":this[yu](e,t),t+=512;break;case"ignore":case"body":t+=this[ca](e,t);break;case"meta":t+=this[gu](e,t);break;default:throw new Error("invalid state: "+this[ae])}t{let t=e.length-1,i=-1;for(;t>-1&&e.charAt(t)==="/";)i=t,t--;return i===-1?e:e.slice(0,i)},Xb=e=>{let t=e.onReadEntry;e.onReadEntry=t?i=>{t(i),i.resume()}:i=>i.resume()},ef=(e,t)=>{let i=new Map(t.map(r=>[Xi(r),!0])),s=e.filter,n=(r,o="")=>{let a=o||(0,Gn.parse)(r).root||".",l;if(r===a)l=!1;else{let c=i.get(r);l=c!==void 0?c:n((0,Gn.dirname)(r),a)}return i.set(r,l),l};e.filter=s?(r,o)=>s(r,o)&&n(Xi(r)):r=>n(Xi(r))},Qb=e=>{let t=new ns(e),i=e.file,s;try{s=Ot.default.openSync(i,"r");let n=Ot.default.fstatSync(s),r=e.maxReadSize||16*1024*1024;if(n.size{let i=new ns(e),s=e.maxReadSize||16*1024*1024,n=e.file;return new Promise((r,o)=>{i.on("error",o),i.on("end",r),Ot.default.stat(n,(a,l)=>{if(a)o(a);else{let c=new Ua(n,{readSize:s,size:l.size});c.on("error",o),c.pipe(i)}})})},Vn=as(Qb,ew,e=>new ns(e),e=>new ns(e),(e,t)=>{t?.length&&ef(e,t),e.noResume||Xb(e)}),tf=(e,t,i)=>(e&=4095,i&&(e=(e|384)&-19),t&&(e&256&&(e|=64),e&32&&(e|=8),e&4&&(e|=1)),e),{isAbsolute:tw,parse:wu}=sf.win32,Ha=e=>{let t="",i=wu(e);for(;tw(e)||i.root;){let s=e.charAt(0)==="/"&&e.slice(0,4)!=="//?/"?"/":i.root;e=e.slice(s.length),t+=s,i=wu(e)}return[t,e]},Jn=["|","<",">","?",":"],Va=Jn.map(e=>String.fromCodePoint(61440+Number(e.codePointAt(0)))),iw=new Map(Jn.map((e,t)=>[e,Va[t]])),sw=new Map(Va.map((e,t)=>[e,Jn[t]])),Su=e=>Jn.reduce((t,i)=>t.split(i).join(iw.get(i)),e),nw=e=>Va.reduce((t,i)=>t.split(i).join(sw.get(i)),e),nf=(e,t)=>t?(e=R(e).replace(/^\.(\/|$)/,""),Xi(t)+"/"+e):R(e),rw=16*1024*1024,vu=Symbol("process"),Eu=Symbol("file"),ku=Symbol("directory"),_a=Symbol("symlink"),Ou=Symbol("hardlink"),Yi=Symbol("header"),Ln=Symbol("read"),Aa=Symbol("lstat"),Cn=Symbol("onlstat"),Na=Symbol("onread"),Ra=Symbol("onreadlink"),Pa=Symbol("openfile"),Ia=Symbol("onopenfile"),it=Symbol("close"),Kn=Symbol("mode"),Ta=Symbol("awaitDrain"),ua=Symbol("ondrain"),_e=Symbol("prefix"),rf=class extends Rt{path;portable;myuid=process.getuid&&process.getuid()||0;myuser=process.env.USER||"";maxReadSize;linkCache;statCache;preservePaths;cwd;strict;mtime;noPax;noMtime;prefix;fd;blockLen=0;blockRemain=0;buf;pos=0;remain=0;length=0;offset=0;win32;absolute;header;type;linkpath;stat;onWriteEntry;#e=!1;constructor(e,t={}){let i=Ya(t);super(),this.path=R(e),this.portable=!!i.portable,this.maxReadSize=i.maxReadSize||rw,this.linkCache=i.linkCache||new Map,this.statCache=i.statCache||new Map,this.preservePaths=!!i.preservePaths,this.cwd=R(i.cwd||process.cwd()),this.strict=!!i.strict,this.noPax=!!i.noPax,this.noMtime=!!i.noMtime,this.mtime=i.mtime,this.prefix=i.prefix?R(i.prefix):void 0,this.onWriteEntry=i.onWriteEntry,typeof i.onwarn=="function"&&this.on("warn",i.onwarn);let s=!1;if(!this.preservePaths){let[r,o]=Ha(this.path);r&&typeof o=="string"&&(this.path=o,s=r)}this.win32=!!i.win32||process.platform==="win32",this.win32&&(this.path=nw(this.path.replaceAll(/\\/g,"/")),e=e.replaceAll(/\\/g,"/")),this.absolute=R(i.absolute||Oa.default.resolve(this.cwd,e)),this.path===""&&(this.path="./"),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path});let n=this.statCache.get(this.absolute);n?this[Cn](n):this[Aa]()}warn(e,t,i={}){return Hn(this,e,t,i)}emit(e,...t){return e==="error"&&(this.#e=!0),super.emit(e,...t)}[Aa](){me.default.lstat(this.absolute,(e,t)=>{if(e)return this.emit("error",e);this[Cn](t)})}[Cn](e){this.statCache.set(this.absolute,e),this.stat=e,e.isFile()||(e.size=0),this.type=lw(e),this.emit("stat",e),this[vu]()}[vu](){switch(this.type){case"File":return this[Eu]();case"Directory":return this[ku]();case"SymbolicLink":return this[_a]();default:return this.end()}}[Kn](e){return tf(e,this.type==="Directory",this.portable)}[_e](e){return nf(e,this.prefix)}[Yi](){if(!this.stat)throw new Error("cannot write header before stat");this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.onWriteEntry?.(this),this.header=new Nt({path:this[_e](this.path),linkpath:this.type==="Link"&&this.linkpath!==void 0?this[_e](this.linkpath):this.linkpath,mode:this[Kn](this.stat.mode),uid:this.portable?void 0:this.stat.uid,gid:this.portable?void 0:this.stat.gid,size:this.stat.size,mtime:this.noMtime?void 0:this.mtime||this.stat.mtime,type:this.type==="Unsupported"?void 0:this.type,uname:this.portable?void 0:this.stat.uid===this.myuid?this.myuser:"",atime:this.portable?void 0:this.stat.atime,ctime:this.portable?void 0:this.stat.ctime}),this.header.encode()&&!this.noPax&&super.write(new jn({atime:this.portable?void 0:this.header.atime,ctime:this.portable?void 0:this.header.ctime,gid:this.portable?void 0:this.header.gid,mtime:this.noMtime?void 0:this.mtime||this.header.mtime,path:this[_e](this.path),linkpath:this.type==="Link"&&this.linkpath!==void 0?this[_e](this.linkpath):this.linkpath,size:this.header.size,uid:this.portable?void 0:this.header.uid,uname:this.portable?void 0:this.header.uname,dev:this.portable?void 0:this.stat.dev,ino:this.portable?void 0:this.stat.ino,nlink:this.portable?void 0:this.stat.nlink}).encode());let e=this.header?.block;if(!e)throw new Error("failed to encode header");super.write(e)}[ku](){if(!this.stat)throw new Error("cannot create directory entry without stat");this.path.slice(-1)!=="/"&&(this.path+="/"),this.stat.size=0,this[Yi](),this.end()}[_a](){me.default.readlink(this.absolute,(e,t)=>{if(e)return this.emit("error",e);this[Ra](t)})}[Ra](e){this.linkpath=R(e),this[Yi](),this.end()}[Ou](e){if(!this.stat)throw new Error("cannot create link entry without stat");this.type="Link",this.linkpath=R(Oa.default.relative(this.cwd,e)),this.stat.size=0,this[Yi](),this.end()}[Eu](){if(!this.stat)throw new Error("cannot create file entry without stat");if(this.stat.nlink>1){let e=`${this.stat.dev}:${this.stat.ino}`,t=this.linkCache.get(e);if(t?.indexOf(this.cwd)===0)return this[Ou](t);this.linkCache.set(e,this.absolute)}if(this[Yi](),this.stat.size===0)return this.end();this[Pa]()}[Pa](){me.default.open(this.absolute,"r",(e,t)=>{if(e)return this.emit("error",e);this[Ia](t)})}[Ia](e){if(this.fd=e,this.#e)return this[it]();if(!this.stat)throw new Error("should stat before calling onopenfile");this.blockLen=512*Math.ceil(this.stat.size/512),this.blockRemain=this.blockLen;let t=Math.min(this.blockLen,this.maxReadSize);this.buf=Buffer.allocUnsafe(t),this.offset=0,this.pos=0,this.remain=this.stat.size,this.length=this.buf.length,this[Ln]()}[Ln](){let{fd:e,buf:t,offset:i,length:s,pos:n}=this;if(e===void 0||t===void 0)throw new Error("cannot read file without first opening");me.default.read(e,t,i,s,n,(r,o)=>{if(r)return this[it](()=>this.emit("error",r));this[Na](o)})}[it](e=()=>{}){this.fd!==void 0&&me.default.close(this.fd,e)}[Na](e){if(e<=0&&this.remain>0){let i=Object.assign(new Error("encountered unexpected EOF"),{path:this.absolute,syscall:"read",code:"EOF"});return this[it](()=>this.emit("error",i))}if(e>this.remain){let i=Object.assign(new Error("did not encounter expected EOF"),{path:this.absolute,syscall:"read",code:"EOF"});return this[it](()=>this.emit("error",i))}if(!this.buf)throw new Error("should have created buffer prior to reading");if(e===this.remain)for(let i=e;ithis[ua]())}[Ta](e){this.once("drain",e)}write(e,t,i){if(typeof t=="function"&&(i=t,t=void 0),typeof e=="string"&&(e=Buffer.from(e,typeof t=="string"?t:"utf8")),this.blockRemaine?this.emit("error",e):this.end());if(!this.buf)throw new Error("buffer lost somehow in ONDRAIN");this.offset>=this.length&&(this.buf=Buffer.allocUnsafe(Math.min(this.blockRemain,this.buf.length)),this.offset=0),this.length=this.buf.length-this.offset,this[Ln]()}},ow=class extends rf{sync=!0;[Aa](){this[Cn](me.default.lstatSync(this.absolute))}[_a](){this[Ra](me.default.readlinkSync(this.absolute))}[Pa](){this[Ia](me.default.openSync(this.absolute,"r"))}[Ln](){let e=!0;try{let{fd:t,buf:i,offset:s,length:n,pos:r}=this;if(t===void 0||i===void 0)throw new Error("fd and buf must be set in READ method");let o=me.default.readSync(t,i,s,n,r);this[Na](o),e=!1}finally{if(e)try{this[it](()=>{})}catch{}}}[Ta](e){e()}[it](e=()=>{}){this.fd!==void 0&&me.default.closeSync(this.fd),e()}},aw=class extends Rt{blockLen=0;blockRemain=0;buf=0;pos=0;remain=0;length=0;preservePaths;portable;strict;noPax;noMtime;readEntry;type;prefix;path;mode;uid;gid;uname;gname;header;mtime;atime;ctime;linkpath;size;onWriteEntry;warn(e,t,i={}){return Hn(this,e,t,i)}constructor(e,t={}){let i=Ya(t);super(),this.preservePaths=!!i.preservePaths,this.portable=!!i.portable,this.strict=!!i.strict,this.noPax=!!i.noPax,this.noMtime=!!i.noMtime,this.onWriteEntry=i.onWriteEntry,this.readEntry=e;let{type:s}=e;if(s==="Unsupported")throw new Error("writing entry that should be ignored");this.type=s,this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.prefix=i.prefix,this.path=R(e.path),this.mode=e.mode!==void 0?this[Kn](e.mode):void 0,this.uid=this.portable?void 0:e.uid,this.gid=this.portable?void 0:e.gid,this.uname=this.portable?void 0:e.uname,this.gname=this.portable?void 0:e.gname,this.size=e.size,this.mtime=this.noMtime?void 0:i.mtime||e.mtime,this.atime=this.portable?void 0:e.atime,this.ctime=this.portable?void 0:e.ctime,this.linkpath=e.linkpath!==void 0?R(e.linkpath):void 0,typeof i.onwarn=="function"&&this.on("warn",i.onwarn);let n=!1;if(!this.preservePaths){let[o,a]=Ha(this.path);o&&typeof a=="string"&&(this.path=a,n=o)}this.remain=e.size,this.blockRemain=e.startBlockSize,this.onWriteEntry?.(this),this.header=new Nt({path:this[_e](this.path),linkpath:this.type==="Link"&&this.linkpath!==void 0?this[_e](this.linkpath):this.linkpath,mode:this.mode,uid:this.portable?void 0:this.uid,gid:this.portable?void 0:this.gid,size:this.size,mtime:this.noMtime?void 0:this.mtime,type:this.type,uname:this.portable?void 0:this.uname,atime:this.portable?void 0:this.atime,ctime:this.portable?void 0:this.ctime}),n&&this.warn("TAR_ENTRY_INFO",`stripping ${n} from absolute path`,{entry:this,path:n+this.path}),this.header.encode()&&!this.noPax&&super.write(new jn({atime:this.portable?void 0:this.atime,ctime:this.portable?void 0:this.ctime,gid:this.portable?void 0:this.gid,mtime:this.noMtime?void 0:this.mtime,path:this[_e](this.path),linkpath:this.type==="Link"&&this.linkpath!==void 0?this[_e](this.linkpath):this.linkpath,size:this.size,uid:this.portable?void 0:this.uid,uname:this.portable?void 0:this.uname,dev:this.portable?void 0:this.readEntry.dev,ino:this.portable?void 0:this.readEntry.ino,nlink:this.portable?void 0:this.readEntry.nlink}).encode());let r=this.header?.block;if(!r)throw new Error("failed to encode header");super.write(r),e.pipe(this)}[_e](e){return nf(e,this.prefix)}[Kn](e){return tf(e,this.type==="Directory",this.portable)}write(e,t,i){typeof t=="function"&&(i=t,t=void 0),typeof e=="string"&&(e=Buffer.from(e,typeof t=="string"?t:"utf8"));let s=e.length;if(s>this.blockRemain)throw new Error("writing more to entry than is appropriate");return this.blockRemain-=s,super.write(e,i)}end(e,t,i){return this.blockRemain&&super.write(Buffer.alloc(this.blockRemain)),typeof e=="function"&&(i=e,t=void 0,e=void 0),typeof t=="function"&&(i=t,t=void 0),typeof e=="string"&&(e=Buffer.from(e,t??"utf8")),i&&this.once("finish",i),e?super.end(e,i):super.end(i),this}},lw=e=>e.isFile()?"File":e.isDirectory()?"Directory":e.isSymbolicLink()?"SymbolicLink":"Unsupported",cw=class Zt{tail;head;length=0;static create(t=[]){return new Zt(t)}constructor(t=[]){for(let i of t)this.push(i)}*[Symbol.iterator](){for(let t=this.head;t;t=t.next)yield t.value}removeNode(t){if(t.list!==this)throw new Error("removing node which does not belong to this list");let i=t.next,s=t.prev;return i&&(i.prev=s),s&&(s.next=i),t===this.head&&(this.head=i),t===this.tail&&(this.tail=s),this.length--,t.next=void 0,t.prev=void 0,t.list=void 0,i}unshiftNode(t){if(t===this.head)return;t.list&&t.list.removeNode(t);let i=this.head;t.list=this,t.next=i,i&&(i.prev=t),this.head=t,this.tail||(this.tail=t),this.length++}pushNode(t){if(t===this.tail)return;t.list&&t.list.removeNode(t);let i=this.tail;t.list=this,t.prev=i,i&&(i.next=t),this.tail=t,this.head||(this.head=t),this.length++}push(...t){for(let i=0,s=t.length;i1)s=i;else if(this.head)n=this.head.next,s=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var r=0;n;r++)s=t(s,n.value,r),n=n.next;return s}reduceReverse(t,i){let s,n=this.tail;if(arguments.length>1)s=i;else if(this.tail)n=this.tail.prev,s=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(let r=this.length-1;n;r--)s=t(s,n.value,r),n=n.prev;return s}toArray(){let t=new Array(this.length);for(let i=0,s=this.head;s;i++)t[i]=s.value,s=s.next;return t}toArrayReverse(){let t=new Array(this.length);for(let i=0,s=this.tail;s;i++)t[i]=s.value,s=s.prev;return t}slice(t=0,i=this.length){i<0&&(i+=this.length),t<0&&(t+=this.length);let s=new Zt;if(ithis.length&&(i=this.length);let n=this.head,r=0;for(r=0;n&&rthis.length&&(i=this.length);let n=this.length,r=this.tail;for(;r&&n>i;n--)r=r.prev;for(;r&&n>t;n--,r=r.prev)s.push(r.value);return s}splice(t,i=0,...s){t>this.length&&(t=this.length-1),t<0&&(t=this.length+t);let n=this.head;for(let o=0;n&&o1)throw new TypeError("gzip, brotli, zstd are mutually exclusive");if(e.gzip&&(typeof e.gzip!="object"&&(e.gzip={}),this.portable&&(e.gzip.portable=!0),this.zip=new Eb(e.gzip)),e.brotli&&(typeof e.brotli!="object"&&(e.brotli={}),this.zip=new Ob(e.brotli)),e.zstd&&(typeof e.zstd!="object"&&(e.zstd={}),this.zip=new Ab(e.zstd)),!this.zip)throw new Error("impossible");let t=this.zip;t.on("data",i=>super.write(i)),t.on("end",()=>super.end()),t.on("drain",()=>this[ma]()),this.on("resume",()=>t.resume())}else this.on("drain",this[ma]);this.noDirRecurse=!!e.noDirRecurse,this.follow=!!e.follow,this.noMtime=!!e.noMtime,e.mtime&&(this.mtime=e.mtime),this.filter=typeof e.filter=="function"?e.filter:()=>!0,this[de]=new cw,this[pe]=0,this.jobs=Number(e.jobs)||4,this[Wi]=!1,this[Gi]=!1}[of](e){return super.write(e)}add(e){return this.write(e),this}end(e,t,i){return typeof e=="function"&&(i=e,e=void 0),typeof t=="function"&&(i=t,t=void 0),e&&this.add(e),this[Gi]=!0,this[Et](),i&&i(),this}write(e){if(this[Gi])throw new Error("write after end");return e instanceof Qu?this[Nu](e):this[Dn](e),this.flowing}[Nu](e){let t=R(La.default.resolve(this.cwd,e.path));if(!this.filter(e.path,e))e.resume();else{let i=new _u(e.path,t);i.entry=new aw(e,this[pa](i)),i.entry.on("end",()=>this[da](i)),this[pe]+=1,this[de].push(i)}this[Et]()}[Dn](e){let t=R(La.default.resolve(this.cwd,e));this[de].push(new _u(e,t)),this[Et]()}[Ca](e){e.pending=!0,this[pe]+=1;let t=this.follow?"stat":"lstat";rs.default[t](e.absolute,(i,s)=>{e.pending=!1,this[pe]-=1,i?this.emit("error",i):this[Mn](e,s)})}[Mn](e,t){this.statCache.set(e.absolute,t),e.stat=t,this.filter(e.path,t)?t.isFile()&&t.nlink>1&&e===this[vt]&&!this.linkCache.get(`${t.dev}:${t.ino}`)&&!this.sync&&this[fa](e):e.ignore=!0,this[Et]()}[Ma](e){e.pending=!0,this[pe]+=1,rs.default.readdir(e.absolute,(t,i)=>{if(e.pending=!1,this[pe]-=1,t)return this.emit("error",t);this[$n](e,i)})}[$n](e,t){this.readdirCache.set(e.absolute,t),e.readdir=t,this[Et]()}[Et](){if(!this[Wi]){this[Wi]=!0;for(let e=this[de].head;e&&this[pe]this.warn(t,i,s),noPax:this.noPax,cwd:this.cwd,absolute:e.absolute,preservePaths:this.preservePaths,maxReadSize:this.maxReadSize,strict:this.strict,portable:this.portable,linkCache:this.linkCache,statCache:this.statCache,noMtime:this.noMtime,mtime:this.mtime,prefix:this.prefix,onWriteEntry:this.onWriteEntry}}[Ru](e){this[pe]+=1;try{return new this[qn](e.path,this[pa](e)).on("end",()=>this[da](e)).on("error",t=>this.emit("error",t))}catch(t){this.emit("error",t)}}[ma](){this[vt]&&this[vt].entry&&this[vt].entry.resume()}[xn](e){e.piped=!0,e.readdir&&e.readdir.forEach(s=>{let n=e.path,r=n==="./"?"":n.replace(/\/*$/,"/");this[Dn](r+s)});let t=e.entry,i=this.zip;if(!t)throw new Error("cannot pipe without source");i?t.on("data",s=>{i.write(s)||t.pause()}):t.on("data",s=>{super.write(s)||t.pause()})}pause(){return this.zip&&this.zip.pause(),super.pause()}warn(e,t,i={}){Hn(this,e,t,i)}},Za=class extends Zn{sync=!0;constructor(e){super(e),this[qn]=ow}pause(){}resume(){}[Ca](e){let t=this.follow?"statSync":"lstatSync";this[Mn](e,rs.default[t](e.absolute))}[Ma](e){this[$n](e,rs.default.readdirSync(e.absolute))}[xn](e){let t=e.entry,i=this.zip;if(e.readdir&&e.readdir.forEach(s=>{let n=e.path,r=n==="./"?"":n.replace(/\/*$/,"/");this[Dn](r+s)}),!t)throw new Error("Cannot pipe without source");i?t.on("data",s=>{i.write(s)}):t.on("data",s=>{super[of](s)})}},dw=(e,t)=>{let i=new Za(e),s=new Uu(e.file,{mode:e.mode||438});i.pipe(s),af(i,t)},pw=(e,t)=>{let i=new Zn(e),s=new Yn(e.file,{mode:e.mode||438});i.pipe(s);let n=new Promise((r,o)=>{s.on("error",o),s.on("close",r),i.on("error",o)});return lf(i,t).catch(r=>i.emit("error",r)),n},af=(e,t)=>{t.forEach(i=>{i.charAt(0)==="@"?Vn({file:za.default.resolve(e.cwd,i.slice(1)),sync:!0,noResume:!0,onReadEntry:s=>e.add(s)}):e.add(i)}),e.end()},lf=async(e,t)=>{for(let i of t)i.charAt(0)==="@"?await Vn({file:za.default.resolve(String(e.cwd),i.slice(1)),noResume:!0,onReadEntry:s=>{e.add(s)}}):e.add(i);e.end()},mw=(e,t)=>{let i=new Za(e);return af(i,t),i},gw=(e,t)=>{let i=new Zn(e);return lf(i,t).catch(s=>i.emit("error",s)),i},bv=as(dw,pw,mw,gw,(e,t)=>{if(!t?.length)throw new TypeError("no paths specified to add to archive")}),yw=process.env.__FAKE_PLATFORM__||process.platform,hf=yw==="win32",{O_CREAT:uf,O_NOFOLLOW:Pu,O_TRUNC:ff,O_WRONLY:df}=el.default.constants,pf=Number(process.env.__FAKE_FS_O_FILENAME__)||el.default.constants.UV_FS_O_FILEMAP||0,bw=hf&&!!pf,ww=512*1024,Sw=pf|ff|uf|df,Iu=!hf&&typeof Pu=="number"?Pu|ff|uf|df:null,mf=Iu!==null?()=>Iu:bw?e=>e"w",Da=(e,t,i)=>{try{return ls.default.lchownSync(e,t,i)}catch(s){if(s?.code!=="ENOENT")throw s}},Un=(e,t,i,s)=>{ls.default.lchown(e,t,i,n=>{s(n&&n?.code!=="ENOENT"?n:null)})},vw=(e,t,i,s,n)=>{if(t.isDirectory())gf(Qt.default.resolve(e,t.name),i,s,r=>{if(r)return n(r);let o=Qt.default.resolve(e,t.name);Un(o,i,s,n)});else{let r=Qt.default.resolve(e,t.name);Un(r,i,s,n)}},gf=(e,t,i,s)=>{ls.default.readdir(e,{withFileTypes:!0},(n,r)=>{if(n){if(n.code==="ENOENT")return s();if(n.code!=="ENOTDIR"&&n.code!=="ENOTSUP")return s(n)}if(n||!r.length)return Un(e,t,i,s);let o=r.length,a=null,l=c=>{if(!a){if(c)return s(a=c);if(--o===0)return Un(e,t,i,s)}};for(let c of r)vw(e,c,t,i,l)})},Ew=(e,t,i,s)=>{t.isDirectory()&&yf(Qt.default.resolve(e,t.name),i,s),Da(Qt.default.resolve(e,t.name),i,s)},yf=(e,t,i)=>{let s;try{s=ls.default.readdirSync(e,{withFileTypes:!0})}catch(n){let r=n;if(r?.code==="ENOENT")return;if(r?.code==="ENOTDIR"||r?.code==="ENOTSUP")return Da(e,t,i);throw r}for(let n of s)Ew(e,n,t,i);return Da(e,t,i)},wf=class extends Error{path;code;syscall="chdir";constructor(e,t){super(`${t}: Cannot cd into '${e}'`),this.path=e,this.code=t}get name(){return"CwdError"}},Xn=class extends Error{path;symlink;syscall="symlink";code="TAR_SYMLINK_ERROR";constructor(e,t){super("TAR_SYMLINK_ERROR: Cannot extract through symbolic link"),this.symlink=e,this.path=t}get name(){return"SymlinkError"}},kw=(e,t)=>{ne.default.stat(e,(i,s)=>{(i||!s.isDirectory())&&(i=new wf(e,i?.code||"ENOTDIR")),t(i)})},Ow=(e,t,i)=>{e=R(e);let s=t.umask??18,n=t.mode|448,r=(n&s)!==0,o=t.uid,a=t.gid,l=typeof o=="number"&&typeof a=="number"&&(o!==t.processUid||a!==t.processGid),c=t.preserve,h=t.unlink,u=R(t.cwd),f=(g,d)=>{g?i(g):d&&l?gf(d,o,a,m=>f(m)):r?ne.default.chmod(e,n,i):i()};if(e===u)return kw(e,f);if(c)return bf.default.mkdir(e,{mode:n,recursive:!0}).then(g=>f(null,g??void 0),f);let p=R(os.default.relative(u,e)).split("/");$a(u,p,n,h,u,void 0,f)},$a=(e,t,i,s,n,r,o)=>{if(t.length===0)return o(null,r);let a=t.shift(),l=R(os.default.resolve(e+"/"+a));ne.default.mkdir(l,i,Sf(l,t,i,s,n,r,o))},Sf=(e,t,i,s,n,r,o)=>a=>{a?ne.default.lstat(e,(l,c)=>{if(l)l.path=l.path&&R(l.path),o(l);else if(c.isDirectory())$a(e,t,i,s,n,r,o);else if(s)ne.default.unlink(e,h=>{if(h)return o(h);ne.default.mkdir(e,i,Sf(e,t,i,s,n,r,o))});else{if(c.isSymbolicLink())return o(new Xn(e,e+"/"+t.join("/")));o(a)}}):(r=r||e,$a(e,t,i,s,n,r,o))},_w=e=>{let t=!1,i;try{t=ne.default.statSync(e).isDirectory()}catch(s){i=s?.code}finally{if(!t)throw new wf(e,i??"ENOTDIR")}},Aw=(e,t)=>{e=R(e);let i=t.umask??18,s=t.mode|448,n=(s&i)!==0,r=t.uid,o=t.gid,a=typeof r=="number"&&typeof o=="number"&&(r!==t.processUid||o!==t.processGid),l=t.preserve,c=t.unlink,h=R(t.cwd),u=g=>{g&&a&&yf(g,r,o),n&&ne.default.chmodSync(e,s)};if(e===h)return _w(h),u();if(l)return u(ne.default.mkdirSync(e,{mode:s,recursive:!0})??void 0);let f=R(os.default.relative(h,e)).split("/"),p;for(let g=f.shift(),d=h;g&&(d+="/"+g);g=f.shift()){d=R(os.default.resolve(d));try{ne.default.mkdirSync(d,s),p=p||d}catch{let m=ne.default.lstatSync(d);if(m.isDirectory())continue;if(c){ne.default.unlinkSync(d),ne.default.mkdirSync(d,s),p=p||d;continue}else if(m.isSymbolicLink())return new Xn(d,d+"/"+f.join("/"))}}return u(p)},ga=Object.create(null),Tu=1e4,Gt=new Set,Nw=e=>{Gt.has(e)?Gt.delete(e):ga[e]=e.normalize("NFD").toLocaleLowerCase("en").toLocaleUpperCase("en"),Gt.add(e);let t=ga[e],i=Gt.size-Tu;if(i>Tu/10){for(let s of Gt)if(Gt.delete(s),delete ga[s],--i<=0)break}return t},Rw=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,Pw=Rw==="win32",Iw=e=>e.split("/").slice(0,-1).reduce((t,i)=>{let s=t.at(-1);return s!==void 0&&(i=(0,tl.join)(s,i)),t.push(i||"/"),t},[]),Tw=class{#e=new Map;#i=new Map;#s=new Set;reserve(e,t){e=Pw?["win32 parallelization disabled"]:e.map(s=>Xi((0,tl.join)(Nw(s))));let i=new Set(e.map(s=>Iw(s)).reduce((s,n)=>s.concat(n)));this.#i.set(t,{dirs:i,paths:e});for(let s of e){let n=this.#e.get(s);n?n.push(t):this.#e.set(s,[t])}for(let s of i){let n=this.#e.get(s);if(!n)this.#e.set(s,[new Set([t])]);else{let r=n.at(-1);r instanceof Set?r.add(t):n.push(new Set([t]))}}return this.#n(t)}#r(e){let t=this.#i.get(e);if(!t)throw new Error("function does not have any path reservations");return{paths:t.paths.map(i=>this.#e.get(i)),dirs:[...t.dirs].map(i=>this.#e.get(i))}}check(e){let{paths:t,dirs:i}=this.#r(e);return t.every(s=>s&&s[0]===e)&&i.every(s=>s&&s[0]instanceof Set&&s[0].has(e))}#n(e){return this.#s.has(e)||!this.check(e)?!1:(this.#s.add(e),e(()=>this.#t(e)),!0)}#t(e){if(!this.#s.has(e))return!1;let t=this.#i.get(e);if(!t)throw new Error("invalid reservation");let{paths:i,dirs:s}=t,n=new Set;for(let r of i){let o=this.#e.get(r);if(!o||o?.[0]!==e)continue;let a=o[1];if(!a){this.#e.delete(r);continue}if(o.shift(),typeof a=="function")n.add(a);else for(let l of a)n.add(l)}for(let r of s){let o=this.#e.get(r),a=o?.[0];if(!(!o||!(a instanceof Set)))if(a.size===1&&o.length===1){this.#e.delete(r);continue}else if(a.size===1){o.shift();let l=o[0];typeof l=="function"&&n.add(l)}else a.delete(e)}return this.#s.delete(e),n.forEach(r=>this.#n(r)),!0}},Lw=()=>process.umask(),Lu=Symbol("onEntry"),xa=Symbol("checkFs"),Cu=Symbol("checkFs2"),qa=Symbol("isReusable"),le=Symbol("makeFs"),Ba=Symbol("file"),Fa=Symbol("directory"),Bn=Symbol("link"),Mu=Symbol("symlink"),Du=Symbol("hardlink"),Ji=Symbol("ensureNoSymlink"),$u=Symbol("unsupported"),xu=Symbol("checkPath"),ya=Symbol("stripAbsolutePath"),nt=Symbol("mkdir"),V=Symbol("onError"),Rn=Symbol("pending"),qu=Symbol("pend"),Wt=Symbol("unpend"),ba=Symbol("ended"),wa=Symbol("maybeClose"),ja=Symbol("skip"),Qi=Symbol("doChown"),es=Symbol("uid"),ts=Symbol("gid"),is=Symbol("checkedCwd"),Cw=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,ss=Cw==="win32",Mw=1024,Dw=(e,t)=>{if(!ss)return P.default.unlink(e,t);let i=e+".DELETE."+(0,Qa.randomBytes)(16).toString("hex");P.default.rename(e,i,s=>{if(s)return t(s);P.default.unlink(i,t)})},$w=e=>{if(!ss)return P.default.unlinkSync(e);let t=e+".DELETE."+(0,Qa.randomBytes)(16).toString("hex");P.default.renameSync(e,t),P.default.unlinkSync(t)},Bu=(e,t,i)=>e!==void 0&&e===e>>>0?e:t!==void 0&&t===t>>>0?t:i,il=class extends ns{[ba]=!1;[is]=!1;[Rn]=0;reservations=new Tw;transform;writable=!0;readable=!1;uid;gid;setOwner;preserveOwner;processGid;processUid;maxDepth;forceChown;win32;newer;keep;noMtime;preservePaths;unlink;cwd;strip;processUmask;umask;dmode;fmode;chmod;constructor(e={}){if(e.ondone=()=>{this[ba]=!0,this[wa]()},super(e),this.transform=e.transform,this.chmod=!!e.chmod,typeof e.uid=="number"||typeof e.gid=="number"){if(typeof e.uid!="number"||typeof e.gid!="number")throw new TypeError("cannot set owner without number uid and gid");if(e.preserveOwner)throw new TypeError("cannot preserve owner in archive and also set owner explicitly");this.uid=e.uid,this.gid=e.gid,this.setOwner=!0}else this.uid=void 0,this.gid=void 0,this.setOwner=!1;this.preserveOwner=e.preserveOwner===void 0&&typeof e.uid!="number"?!!(process.getuid&&process.getuid()===0):!!e.preserveOwner,this.processUid=(this.preserveOwner||this.setOwner)&&process.getuid?process.getuid():void 0,this.processGid=(this.preserveOwner||this.setOwner)&&process.getgid?process.getgid():void 0,this.maxDepth=typeof e.maxDepth=="number"?e.maxDepth:Mw,this.forceChown=e.forceChown===!0,this.win32=!!e.win32||ss,this.newer=!!e.newer,this.keep=!!e.keep,this.noMtime=!!e.noMtime,this.preservePaths=!!e.preservePaths,this.unlink=!!e.unlink,this.cwd=R(j.default.resolve(e.cwd||process.cwd())),this.strip=Number(e.strip)||0,this.processUmask=this.chmod?typeof e.processUmask=="number"?e.processUmask:Lw():0,this.umask=typeof e.umask=="number"?e.umask:this.processUmask,this.dmode=e.dmode||511&~this.umask,this.fmode=e.fmode||438&~this.umask,this.on("entry",t=>this[Lu](t))}warn(e,t,i={}){return(e==="TAR_BAD_ARCHIVE"||e==="TAR_ABORT")&&(i.recoverable=!1),super.warn(e,t,i)}[wa](){this[ba]&&this[Rn]===0&&(this.emit("prefinish"),this.emit("finish"),this.emit("end"))}[ya](e,t){let i=e[t],{type:s}=e;if(!i||this.preservePaths)return!0;let[n,r]=Ha(i),o=r.replaceAll(/\\/g,"/").split("/");if(o.includes("..")||ss&&/^[a-z]:\.\.$/i.test(o[0]??"")){if(t==="path"||s==="Link")return this.warn("TAR_ENTRY_ERROR",`${t} contains '..'`,{entry:e,[t]:i}),!1;let a=j.default.posix.dirname(e.path),l=j.default.posix.normalize(j.default.posix.join(a,o.join("/")));if(l.startsWith("../")||l==="..")return this.warn("TAR_ENTRY_ERROR",`${t} escapes extraction directory`,{entry:e,[t]:i}),!1}return n&&(e[t]=String(r),this.warn("TAR_ENTRY_INFO",`stripping ${n} from absolute ${t}`,{entry:e,[t]:i})),!0}[xu](e){let t=R(e.path),i=t.split("/");if(this.strip){if(i.length=this.strip)e.linkpath=s.slice(this.strip).join("/");else return!1}i.splice(0,this.strip),e.path=i.join("/")}if(isFinite(this.maxDepth)&&i.length>this.maxDepth)return this.warn("TAR_ENTRY_ERROR","path excessively deep",{entry:e,path:t,depth:i.length,maxDepth:this.maxDepth}),!1;if(!this[ya](e,"path")||!this[ya](e,"linkpath"))return!1;if(e.absolute=j.default.isAbsolute(e.path)?R(j.default.resolve(e.path)):R(j.default.resolve(this.cwd,e.path)),!this.preservePaths&&typeof e.absolute=="string"&&e.absolute.indexOf(this.cwd+"/")!==0&&e.absolute!==this.cwd)return this.warn("TAR_ENTRY_ERROR","path escaped extraction target",{entry:e,path:R(e.path),resolvedPath:e.absolute,cwd:this.cwd}),!1;if(e.absolute===this.cwd&&e.type!=="Directory"&&e.type!=="GNUDumpDir")return!1;if(this.win32){let{root:s}=j.default.win32.parse(String(e.absolute));e.absolute=s+Su(String(e.absolute).slice(s.length));let{root:n}=j.default.win32.parse(e.path);e.path=n+Su(e.path.slice(n.length))}return!0}[Lu](e){if(!this[xu](e))return e.resume();switch(cf.default.equal(typeof e.absolute,"string"),e.type){case"Directory":case"GNUDumpDir":e.mode&&(e.mode=e.mode|448);case"File":case"OldFile":case"ContiguousFile":case"Link":case"SymbolicLink":return this[xa](e);default:return this[$u](e)}}[V](e,t){e.name==="CwdError"?this.emit("error",e):(this.warn("TAR_ENTRY_ERROR",e,{entry:t}),this[Wt](),t.resume())}[nt](e,t,i){Ow(R(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cwd:this.cwd,mode:t},i)}[Qi](e){return this.forceChown||this.preserveOwner&&(typeof e.uid=="number"&&e.uid!==this.processUid||typeof e.gid=="number"&&e.gid!==this.processGid)||typeof this.uid=="number"&&this.uid!==this.processUid||typeof this.gid=="number"&&this.gid!==this.processGid}[es](e){return Bu(this.uid,e.uid,this.processUid)}[ts](e){return Bu(this.gid,e.gid,this.processGid)}[Ba](e,t){let i=typeof e.mode=="number"?e.mode&4095:this.fmode,s=new Yn(String(e.absolute),{flags:mf(e.size),mode:i,autoClose:!1});s.on("error",a=>{s.fd&&P.default.close(s.fd,()=>{}),s.write=()=>!0,this[V](a,e),t()});let n=1,r=a=>{if(a){s.fd&&P.default.close(s.fd,()=>{}),this[V](a,e),t();return}--n===0&&s.fd!==void 0&&P.default.close(s.fd,l=>{l?this[V](l,e):this[Wt](),t()})};s.on("finish",()=>{let a=String(e.absolute),l=s.fd;if(typeof l=="number"&&e.mtime&&!this.noMtime){n++;let c=e.atime||new Date,h=e.mtime;P.default.futimes(l,c,h,u=>u?P.default.utimes(a,c,h,f=>r(f&&u)):r())}if(typeof l=="number"&&this[Qi](e)){n++;let c=this[es](e),h=this[ts](e);typeof c=="number"&&typeof h=="number"&&P.default.fchown(l,c,h,u=>u?P.default.chown(a,c,h,f=>r(f&&u)):r())}r()});let o=this.transform&&this.transform(e)||e;o!==e&&(o.on("error",a=>{this[V](a,e),t()}),e.pipe(o)),o.pipe(s)}[Fa](e,t){let i=typeof e.mode=="number"?e.mode&4095:this.dmode;this[nt](String(e.absolute),i,s=>{if(s){this[V](s,e),t();return}let n=1,r=()=>{--n===0&&(t(),this[Wt](),e.resume())};e.mtime&&!this.noMtime&&(n++,P.default.utimes(String(e.absolute),e.atime||new Date,e.mtime,r)),this[Qi](e)&&(n++,P.default.chown(String(e.absolute),Number(this[es](e)),Number(this[ts](e)),r)),r()})}[$u](e){e.unsupported=!0,this.warn("TAR_ENTRY_UNSUPPORTED",`unsupported entry type: ${e.type}`,{entry:e}),e.resume()}[Mu](e,t){let i=R(j.default.relative(this.cwd,j.default.resolve(j.default.dirname(String(e.absolute)),String(e.linkpath)))).split("/");this[Ji](e,this.cwd,i,()=>this[Bn](e,String(e.linkpath),"symlink",t),s=>{this[V](s,e),t()})}[Du](e,t){let i=R(j.default.resolve(this.cwd,String(e.linkpath))),s=R(String(e.linkpath)).split("/");this[Ji](e,this.cwd,s,()=>this[Bn](e,i,"link",t),n=>{this[V](n,e),t()})}[Ji](e,t,i,s,n){let r=i.shift();if(this.preservePaths||r===void 0)return s();let o=j.default.resolve(t,r);P.default.lstat(o,(a,l)=>{if(a)return s();if(l?.isSymbolicLink())return n(new Xn(o,j.default.resolve(o,i.join("/"))));this[Ji](e,o,i,s,n)})}[qu](){this[Rn]++}[Wt](){this[Rn]--,this[wa]()}[ja](e){this[Wt](),e.resume()}[qa](e,t){return e.type==="File"&&!this.unlink&&t.isFile()&&t.nlink<=1&&!ss}[xa](e){this[qu]();let t=[e.path];e.linkpath&&t.push(e.linkpath),this.reservations.reserve(t,i=>this[Cu](e,i))}[Cu](e,t){let i=o=>{t(o)},s=()=>{this[nt](this.cwd,this.dmode,o=>{if(o){this[V](o,e),i();return}this[is]=!0,n()})},n=()=>{if(e.absolute!==this.cwd){let o=R(j.default.dirname(String(e.absolute)));if(o!==this.cwd)return this[nt](o,this.dmode,a=>{if(a){this[V](a,e),i();return}r()})}r()},r=()=>{P.default.lstat(String(e.absolute),(o,a)=>{if(a&&(this.keep||this.newer&&a.mtime>(e.mtime??a.mtime))){this[ja](e),i();return}if(o||this[qa](e,a))return this[le](null,e,i);if(a.isDirectory()){if(e.type==="Directory"){let l=this.chmod&&e.mode&&(a.mode&4095)!==e.mode,c=h=>this[le](h??null,e,i);return l?P.default.chmod(String(e.absolute),Number(e.mode),c):c()}if(e.absolute!==this.cwd)return P.default.rmdir(String(e.absolute),l=>this[le](l??null,e,i))}if(e.absolute===this.cwd)return this[le](null,e,i);Dw(String(e.absolute),l=>this[le](l??null,e,i))})};this[is]?n():s()}[le](e,t,i){if(e){this[V](e,t),i();return}switch(t.type){case"File":case"OldFile":case"ContiguousFile":return this[Ba](t,i);case"Link":return this[Du](t,i);case"SymbolicLink":return this[Mu](t,i);case"Directory":case"GNUDumpDir":return this[Fa](t,i)}}[Bn](e,t,i,s){P.default[i](t,String(e.absolute),n=>{n?this[V](n,e):(this[Wt](),e.resume()),s()})}},Hi=e=>{try{return[null,e()]}catch(t){return[t,null]}},vf=class extends il{sync=!0;[le](e,t){return super[le](e,t,()=>{})}[xa](e){if(!this[is]){let n=this[nt](this.cwd,this.dmode);if(n)return this[V](n,e);this[is]=!0}if(e.absolute!==this.cwd){let n=R(j.default.dirname(String(e.absolute)));if(n!==this.cwd){let r=this[nt](n,this.dmode);if(r)return this[V](r,e)}}let[t,i]=Hi(()=>P.default.lstatSync(String(e.absolute)));if(i&&(this.keep||this.newer&&i.mtime>(e.mtime??i.mtime)))return this[ja](e);if(t||this[qa](e,i))return this[le](null,e);if(i.isDirectory()){if(e.type==="Directory"){let r=this.chmod&&e.mode&&(i.mode&4095)!==e.mode,[o]=r?Hi(()=>{P.default.chmodSync(String(e.absolute),Number(e.mode))}):[];return this[le](o,e)}let[n]=Hi(()=>P.default.rmdirSync(String(e.absolute)));this[le](n,e)}let[s]=e.absolute===this.cwd?[]:Hi(()=>$w(String(e.absolute)));this[le](s,e)}[Ba](e,t){let i=typeof e.mode=="number"?e.mode&4095:this.fmode,s=o=>{let a;try{P.default.closeSync(n)}catch(l){a=l}(o||a)&&this[V](o||a,e),t()},n;try{n=P.default.openSync(String(e.absolute),mf(e.size),i)}catch(o){return s(o)}let r=this.transform&&this.transform(e)||e;r!==e&&(r.on("error",o=>this[V](o,e)),e.pipe(r)),r.on("data",o=>{try{P.default.writeSync(n,o,0,o.length)}catch(a){s(a)}}),r.on("end",()=>{let o=null;if(e.mtime&&!this.noMtime){let a=e.atime||new Date,l=e.mtime;try{P.default.futimesSync(n,a,l)}catch(c){try{P.default.utimesSync(String(e.absolute),a,l)}catch{o=c}}}if(this[Qi](e)){let a=this[es](e),l=this[ts](e);try{P.default.fchownSync(n,Number(a),Number(l))}catch(c){try{P.default.chownSync(String(e.absolute),Number(a),Number(l))}catch{o=o||c}}}s(o)})}[Fa](e,t){let i=typeof e.mode=="number"?e.mode&4095:this.dmode,s=this[nt](String(e.absolute),i);if(s){this[V](s,e),t();return}if(e.mtime&&!this.noMtime)try{P.default.utimesSync(String(e.absolute),e.atime||new Date,e.mtime)}catch{}if(this[Qi](e))try{P.default.chownSync(String(e.absolute),Number(this[es](e)),Number(this[ts](e)))}catch{}t(),e.resume()}[nt](e,t){try{return Aw(R(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cwd:this.cwd,mode:t})}catch(i){return i}}[Ji](e,t,i,s,n){if(this.preservePaths||i.length===0)return s();let r=t;for(let o of i){r=j.default.resolve(r,o);let[a,l]=Hi(()=>P.default.lstatSync(r));if(a)return s();if(l.isSymbolicLink())return n(new Xn(r,j.default.resolve(t,i.join("/"))))}s()}[Bn](e,t,i,s){let n=`${i}Sync`;try{P.default[n](t,String(e.absolute)),s(),e.resume()}catch(r){return this[V](r,e)}}},xw=e=>{let t=new vf(e),i=e.file,s=Xa.default.statSync(i),n=e.maxReadSize||16*1024*1024;new ub(i,{readSize:n,size:s.size}).pipe(t)},qw=(e,t)=>{let i=new il(e),s=e.maxReadSize||16*1024*1024,n=e.file;return new Promise((r,o)=>{i.on("error",o),i.on("close",r),Xa.default.stat(n,(a,l)=>{if(a)o(a);else{let c=new Ua(n,{readSize:s,size:l.size});c.on("error",o),c.pipe(i)}})})},cs=as(xw,qw,e=>new vf(e),e=>new il(e),(e,t)=>{t?.length&&ef(e,t)}),Bw=(e,t)=>{let i=new Za(e),s=!0,n,r;try{try{n=se.default.openSync(e.file,"r+")}catch(l){if(l?.code==="ENOENT")n=se.default.openSync(e.file,"w+");else throw l}let o=se.default.fstatSync(n),a=Buffer.alloc(512);e:for(r=0;ro.size)break;r+=c,e.mtimeCache&&l.mtime&&e.mtimeCache.set(String(l.path),l.mtime)}s=!1,Fw(e,i,r,n,t)}finally{if(s)try{se.default.closeSync(n)}catch{}}},Fw=(e,t,i,s,n)=>{let r=new Uu(e.file,{fd:s,start:i});t.pipe(r),Kw(t,n)},jw=(e,t)=>{t=Array.from(t);let i=new Zn(e),s=(n,r,o)=>{let a=(f,p)=>{f?se.default.close(n,g=>o(f)):o(null,p)},l=0;if(r===0)return a(null,0);let c=0,h=Buffer.alloc(512),u=(f,p)=>{if(f||p===void 0)return a(f);if(c+=p,c<512&&p)return se.default.read(n,h,c,h.length-c,l+c,u);if(l===0&&h[0]===31&&h[1]===139)return a(new Error("cannot append to compressed archives"));if(c<512)return a(null,l);let g=new Nt(h);if(!g.cksumValid)return a(null,l);let d=512*Math.ceil((g.size??0)/512);if(l+d+512>r||(l+=d+512,l>=r))return a(null,l);e.mtimeCache&&g.mtime&&e.mtimeCache.set(String(g.path),g.mtime),c=0,se.default.read(n,h,0,512,l,u)};se.default.read(n,h,0,512,l,u)};return new Promise((n,r)=>{i.on("error",r);let o="r+",a=(l,c)=>{if(l&&l.code==="ENOENT"&&o==="r+")return o="w+",se.default.open(e.file,o,a);if(l||!c)return r(l);se.default.fstat(c,(h,u)=>{if(h)return se.default.close(c,()=>r(h));s(c,u.size,(f,p)=>{if(f)return r(f);let g=new Yn(e.file,{fd:c,start:p});i.pipe(g),g.on("error",r),g.on("close",n),Uw(i,t)})})};se.default.open(e.file,o,a)})},Kw=(e,t)=>{t.forEach(i=>{i.charAt(0)==="@"?Vn({file:sl.default.resolve(e.cwd,i.slice(1)),sync:!0,noResume:!0,onReadEntry:s=>e.add(s)}):e.add(i)}),e.end()},Uw=async(e,t)=>{for(let i of t)i.charAt(0)==="@"?await Vn({file:sl.default.resolve(String(e.cwd),i.slice(1)),noResume:!0,onReadEntry:s=>e.add(s)}):e.add(i);e.end()},Vi=as(Bw,jw,()=>{throw new TypeError("file is required")},()=>{throw new TypeError("file is required")},(e,t)=>{if(!yb(e))throw new TypeError("file is required");if(e.gzip||e.brotli||e.zstd||e.file.endsWith(".br")||e.file.endsWith(".tbr"))throw new TypeError("cannot append to compressed archives");if(!t?.length)throw new TypeError("no paths specified to add/replace")}),wv=as(Vi.syncFile,Vi.asyncFile,Vi.syncNoFile,Vi.asyncNoFile,(e,t=[])=>{Vi.validate?.(e,t),zw(e)}),zw=e=>{let t=e.filter;e.mtimeCache||(e.mtimeCache=new Map),e.filter=t?(i,s)=>t(i,s)&&!((e.mtimeCache?.get(i)??s.mtime??0)>(s.mtime??0)):(i,s)=>!((e.mtimeCache?.get(i)??s.mtime??0)>(s.mtime??0))};var b=class extends Error{constructor(t){super(t),this.name="InstallException"}};function S(e){process.stdout.write(`${e} -`)}var Pt={IF_NOT_PRESENT:"IfNotPresent",ALWAYS:"Always"},ce="docker://",D="oci://",Yw=":latest!",nl="registry.access.redhat.com/rhdh/",rl="quay.io/rhdh/",ti="dynamic-plugin-config.hash",ol="dynamic-plugin-image.hash",Qn="dynamic-plugins.default.yaml",Of="install-dynamic-plugins.lock",al="app-config.dynamic-plugins.yaml",kf=4e7;function Gw(e=process.env.MAX_ENTRY_SIZE){if(!e)return kf;let t=Number.parseInt(e,10);return Number.isFinite(t)&&t>=1?t:kf}var hs=Gw(),at=["sha512","sha384","sha256"];function er(e){return e.pullPolicy?e.pullPolicy:e.package.includes(Yw)?Pt.ALWAYS:Pt.IF_NOT_PRESENT}function De(e,t){let i=typeof e.enabled=="boolean",s=typeof e.disabled=="boolean";return e.enabled!==void 0&&!i&&t?.(`WARNING: Plugin ${e.package} has non-boolean 'enabled: ${String(e.enabled)}'. Expected true or false; ignoring the field.`),e.disabled!==void 0&&!s&&t?.(`WARNING: Plugin ${e.package} has non-boolean 'disabled: ${String(e.disabled)}'. Expected true or false; ignoring the field.`),i&&s?(t?.(`WARNING: Plugin ${e.package} specifies both 'enabled' and 'disabled'. The 'enabled' field takes precedence; please use only 'enabled'.`),!e.enabled):i?!e.enabled:s?e.disabled===!0:!1}async function ii(e,t){let{proto:i,raw:s}=Ww(t);if(!s.startsWith(nl))return t;let n=`${ce}${s}`;if(await e.exists(n))return t;let r=s.replace(nl,rl);return S(` ==> Falling back to ${rl} for ${s}`),`${i}${r}`}function Ww(e){return e.startsWith(D)?{proto:D,raw:e.slice(D.length)}:e.startsWith(ce)?{proto:ce,raw:e.slice(ce.length)}:{proto:"",raw:e}}var _f=O(require("node:fs/promises")),ll=O(require("node:path"));async function be(e){try{return await _f.access(e),!0}catch{return!1}}function si(e,t){let i=t.endsWith(ll.sep)?t:t+ll.sep;return e===t||e.startsWith(i)}function $e(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function tr(e){return e==="File"||e==="Directory"||e==="SymbolicLink"||e==="Link"||e==="OldFile"||e==="ContiguousFile"}function ir(e,t){for(let[i,s]of e)s===t&&e.delete(i)}async function Af(e,t,i,s){S(` -======= Extracting catalog index from ${t}`);let n=x.join(i,".catalog-index-temp");await q.mkdir(n,{recursive:!0});let r=x.resolve(n);await Nf(e,t,r);let o=x.join(n,Qn);if(!await be(o))throw new b(`dynamic-plugins.default.yaml not found in ${t}`);S(" ==> Extracted dynamic-plugins.default.yaml");for(let a of["catalog-entities/extensions","catalog-entities/marketplace"]){let l=x.join(n,a);if(await be(l)){await q.mkdir(s,{recursive:!0});let c=x.join(s,"catalog-entities");await q.rm(c,{recursive:!0,force:!0}),await hl(l,c),S(` ==> Extracted catalog entities from ${a}`);break}}return o}async function Nf(e,t,i){let s=await ii(e,t),n=await q.mkdtemp(x.join(cl.tmpdir(),"rhdh-catalog-index-"));try{let r=s.startsWith(ce)?s:`${ce}${s.replace(D,"")}`,o=x.join(n,"idx");S(" ==> Downloading catalog index image"),await e.copy(r,`dir:${o}`);let a=x.join(o,"manifest.json");if(!await be(a))throw new b(`manifest.json not found in catalog index image ${t}`);let c=JSON.parse(await q.readFile(a,"utf8")).layers??[],h=null;for(let u of c){if(h)break;let f=u.digest;if(!f)continue;let[,p]=f.split(":");if(!p)continue;let g=x.join(o,p);await be(g)&&await cs({file:g,cwd:i,preservePaths:!1,filter:(d,m)=>{if(h)return!1;let w=m;if(w.size>hs)return h=new b(`Zip bomb detected in ${d}`),!1;if(w.type==="SymbolicLink"||w.type==="Link"){let k=x.resolve(i,w.linkpath??"");if(!si(k,i))return!1}let E=x.resolve(i,d);return si(E,i)?tr(w.type):!1}})}if(h)throw h}finally{await q.rm(n,{recursive:!0,force:!0})}}async function Rf(e,t,i,s,n){if(!If(i))throw new b(`Refusing to extract extra catalog index into unsafe subdirectory '${i}'`);S(` -======= Extracting extra catalog index '${i}' from ${t}`),n&&S(` ==> WARNING: Subdirectory '${i}' was already used by '${n}'. The previous extraction will be overwritten.`);let r=await q.mkdtemp(x.join(cl.tmpdir(),"rhdh-extra-catalog-index-"));try{let o=x.join(r,"extracted");await q.mkdir(o,{recursive:!0}),await Nf(e,t,o);let a=x.join(s,i);S(` ==> Extracting extensions catalog entities to ${a}`);let l=null;for(let h of["catalog-entities/extensions","catalog-entities/marketplace"]){let u=x.join(o,h);if(await be(u)){l=u;break}}if(!l){S(` ==> WARNING: Extra catalog index image ${t} does not have neither 'catalog-entities/extensions/' nor 'catalog-entities/marketplace/' directory`);return}await q.mkdir(a,{recursive:!0});let c=x.join(a,"catalog-entities");await q.rm(c,{recursive:!0,force:!0}),await hl(l,c),S(` ==> Successfully extracted extensions catalog entities from extra index image to ${a}`)}finally{await q.rm(r,{recursive:!0,force:!0})}}function Hw(e){return e.replaceAll(/[/:@]/g,"_")}function Pf(e){let t=[];for(let i of e.split(",")){let s=i.trim();if(!s)continue;let n,r,o=s.indexOf("=");if(o===-1?(r=s,n=Hw(r)):(n=s.slice(0,o).trim(),r=s.slice(o+1).trim()),!r){S(`WARNING: Skipping EXTRA_CATALOG_INDEX_IMAGES entry with empty image reference: '${s}'`);continue}if(!If(n)){S(`WARNING: Skipping EXTRA_CATALOG_INDEX_IMAGES entry with unsafe subdirectory name '${n}' in '${s}'. Names must be non-empty and must not contain '/', '\\\\', or '..'.`);continue}t.push([n,r])}return t}function If(e){return!e||e==="."||e===".."?!1:!/[/\\]/.test(e)}async function Tf(e){await q.rm(x.join(e,".catalog-index-temp"),{recursive:!0,force:!0})}async function hl(e,t){await q.mkdir(t,{recursive:!0});let i=await q.readdir(e,{withFileTypes:!0});for(let s of i){let n=x.join(e,s.name),r=x.join(t,s.name);s.isDirectory()?await hl(n,r):s.isFile()&&await q.copyFile(n,r)}}var us=O(require("node:os")),ul=class{available;queue=[];constructor(t){if(t<1)throw new RangeError(`Semaphore max must be >= 1, got ${t}`);this.available=t}async acquire(){if(this.available>0){this.available--;return}return new Promise(t=>this.queue.push(t))}release(){let t=this.queue.shift();t?t():this.available++}};async function Lf(e,t,i){let s=new ul(Math.max(1,t));return Promise.all(e.map(async n=>{await s.acquire();try{return{ok:!0,value:await i(n),item:n}}catch(r){return{ok:!1,error:r,item:n}}finally{s.release()}}))}var Vw=6,Jw=3;function Cf(){return Df(process.env.DYNAMIC_PLUGINS_WORKERS,Vw)}function Mf(){return Df(process.env.DYNAMIC_PLUGINS_NPM_WORKERS,Jw)}function Df(e,t){let i=e??"auto";if(i!=="auto"){let n=Number.parseInt(i,10);return!Number.isFinite(n)||n<1?1:n}let s=typeof us.availableParallelism=="function"?us.availableParallelism():us.cpus().length;return Math.max(1,Math.min(Math.floor(s/2),t))}var $f=require("node:crypto"),rr=O(require("node:fs/promises")),sr=O(require("node:path"));var nr=class{constructor(t,i){this.skopeo=t;this.tmpDir=i}tarballs=new Map;async getTarball(t){let i=await ii(this.skopeo,t),s=this.tarballs.get(i);return s||(s=this.downloadAndLocateTarball(i),this.tarballs.set(i,s),s.catch(()=>this.tarballs.delete(i))),s}async getDigest(t){let s=(await ii(this.skopeo,t)).replace(D,ce),r=(await this.skopeo.inspect(s)).Digest;if(!r)throw new b(`No digest returned for ${t}`);let[,o]=r.split(":");if(!o)throw new b(`Malformed digest ${r} for ${t}`);return o}async getPluginPaths(t){let s=(await ii(this.skopeo,t)).replace(D,ce),r=(await this.skopeo.inspectRaw(s)).annotations?.["io.backstage.dynamic-packages"];if(!r)return[];let o;try{let l=Buffer.from(r,"base64").toString("utf8");o=JSON.parse(l)}catch(l){throw new b(`Could not decode 'io.backstage.dynamic-packages' annotation on ${t}: ${l.message}`)}if(!Array.isArray(o))return[];let a=[];for(let l of o)l&&typeof l=="object"&&a.push(...Object.keys(l));return a}async downloadAndLocateTarball(t){let i=(0,$f.createHash)("sha256").update(t).digest("hex"),s=sr.join(this.tmpDir,i);await rr.mkdir(s,{recursive:!0});let n=t.replace(D,ce);S(` ==> Downloading ${t}`),await this.skopeo.copy(n,`dir:${s}`);let r=sr.join(s,"manifest.json"),a=JSON.parse(await rr.readFile(r,"utf8")).layers?.[0]?.digest;if(!a)throw new b(`OCI manifest for ${t} has no layers`);let[,l]=a.split(":");if(!l)throw new b(`Malformed layer digest ${a} in ${t}`);return sr.join(s,l)}};var Gf=O(require("node:fs/promises")),or=O(require("node:path"));var qf=require("node:crypto"),Bf=require("node:fs"),Ff=require("node:stream/promises");async function jf(e,t,i){let s=i.indexOf("-");if(s===-1)throw new b(`Package integrity for ${e} must be a string of the form -`);let n=i.slice(0,s),r=i.slice(s+1);if(!Zw(n))throw new b(`${e}: Provided Package integrity algorithm ${n} is not supported, please use one of following algorithms ${at.join(", ")} instead`);if(!Xw(r))throw new b(`${e}: Provided Package integrity hash ${r} is not a valid base64 encoding`);let o=(0,qf.createHash)(n);await(0,Ff.pipeline)((0,Bf.createReadStream)(t),o);let a=o.digest("base64");if(a!==r)throw new b(`${e}: integrity check failed \u2014 got ${n}-${a}, expected ${i}`)}function Zw(e){return at.includes(e)}function Xw(e){if(e.length===0||!Qw(e))return!1;try{let t=Buffer.from(e,"base64");return xf(t.toString("base64"))===xf(e)}catch{return!1}}var Kf=61;function Qw(e){let t=0;for(let i=0;i2)return!1;continue}if(t>0||!eS(s))return!1}return!0}function eS(e){return e>=65&&e<=90||e>=97&&e<=122||e>=48&&e<=57||e===43||e===47}function xf(e){let t=e.length;for(;t>0&&e.codePointAt(t-1)===Kf;)t--;return e.slice(0,t)}var Uf=require("node:child_process");async function fs(e,t,i={}){if(e.length===0)throw new b(`${t}: empty command`);let[s,...n]=e;return new Promise((r,o)=>{let a=(0,Uf.spawn)(s,n,{...i,stdio:["ignore","pipe","pipe"]}),l="",c="";a.stdout?.on("data",h=>l+=h.toString()),a.stderr?.on("data",h=>c+=h.toString()),a.on("error",h=>o(new b(`${t}: ${h.message}`))),a.on("close",h=>{if(h===0)r({stdout:l,stderr:c});else{let u=[`${t}: exit code ${h}`,`cmd: ${e.join(" ")}`];c.trim()&&u.push(`stderr: ${c.trim()}`),o(new b(u.join(` -`)))}})})}var It=O(require("node:fs/promises")),we=O(require("node:path"));var ds="package/";async function zf(e,t,i){tS(t);let s=we.resolve(i),n=we.join(s,t);await It.rm(n,{recursive:!0,force:!0}),await It.mkdir(s,{recursive:!0});let r=t.endsWith("/")?t:`${t}/`,o=null;if(await cs({file:e,cwd:s,preservePaths:!1,filter:(a,l)=>{if(o)return!1;let c=l;if(a!==t&&!a.startsWith(r))return!1;if(c.size>hs)return o=new b(`Zip bomb detected in ${a}`),!1;if(c.type==="SymbolicLink"||c.type==="Link"){let h=c.linkpath??"",u=we.resolve(s,h);if(!si(u,s))return S(` ==> WARNING: skipping file containing link outside of the archive: ${a} -> ${h}`),!1}return tr(c.type)?!0:(o=new b(`Disallowed tar entry type ${c.type} for ${a}`),!1)}}),o)throw o}async function Yf(e){if(!e.endsWith(".tgz"))throw new b(`Expected .tgz archive, got ${e}`);let t=e.slice(0,-4),i=we.resolve(t);await It.rm(t,{recursive:!0,force:!0}),await It.mkdir(t,{recursive:!0});let s=null;if(await cs({file:e,cwd:t,preservePaths:!1,filter:(n,r)=>{if(s)return!1;let o=r;if(o.type==="Directory")return!1;if(o.type==="File")return n.startsWith(ds)?o.size>hs?(s=new b(`Zip bomb detected in ${n}`),!1):(o.path=n.slice(ds.length),!0):(s=new b(`NPM package archive does not start with 'package/' as it should: ${n}`),!1);if(o.type==="SymbolicLink"||o.type==="Link"){let a=o.linkpath??"";if(!a.startsWith(ds))return s=new b(`NPM package archive contains a link outside of the archive: ${n} -> ${a}`),!1;o.path=n.slice(ds.length),o.linkpath=a.slice(ds.length);let l=we.resolve(t,o.linkpath);return si(l,i)?!0:(s=new b(`NPM package archive contains a link outside of the archive: ${o.path} -> ${o.linkpath}`),!1)}return s=new b(`NPM package archive contains a non-regular file: ${n}`),!1}}),s)throw s;return await It.rm(e,{force:!0}),we.basename(i)}function tS(e){if(we.isAbsolute(e))throw new b(`Invalid plugin path (absolute): ${e}`);if(e.length===0)throw new b("Invalid plugin path (empty)");let t=e.split(/[/\\]/);for(let i of t)if(i===""||i==="."||i==="..")throw new b(`Invalid plugin path (path traversal detected): ${e}`)}async function Wf(e,t,i,s){if(De(e,S))return{pluginPath:null,pluginConfig:{}};let n=e.plugin_hash;if(!n)throw new b(`Internal error: plugin ${e.package} missing plugin_hash`);let r=e.package,o=e.pluginConfig??{},a=r.startsWith("./"),l=a?or.join(process.cwd(),r.slice(2)):r,c=!a&&!i;if(c&&!e.integrity)throw new b(`No integrity hash provided for Package ${r}. This is an insecure installation. To ignore this error, set the SKIP_INTEGRITY_CHECK environment variable to 'true'.`);S(" ==> Running npm pack");let h=await iS(l,t);if(!nS(h))throw new b(`npm pack returned an unsafe filename for ${r}: '${h}'`);let u=or.join(t,h);c&&(S(" ==> Verifying package integrity"),await jf(r,u,e.integrity));let f=await Yf(u);return await Gf.writeFile(or.join(t,f,ti),n),ir(s,f),{pluginPath:f,pluginConfig:o}}async function iS(e,t){let{stdout:i}=await fs(["npm","pack","--json","--ignore-scripts",e],`npm pack failed for ${e}`,{cwd:t}),s;try{s=JSON.parse(i)}catch(r){throw new b(`npm pack produced invalid JSON for ${e}: ${r.message}`)}if(!Array.isArray(s)||s.length===0)throw new b(`npm pack produced no archives for ${e}`);let n=s[0];if(!sS(n))throw new b(`npm pack output missing 'filename' for ${e}`);return n.filename}function sS(e){return!!e&&typeof e=="object"&&typeof e.filename=="string"}function nS(e){return!e||e==="."||e===".."||e.startsWith("..")?!1:!/[/\\]/.test(e)}var Tt=O(require("node:fs/promises")),ps=O(require("node:path"));function Hf(e){let t=e.indexOf("!");if(t===-1)return null;let i=e.slice(0,t),s=e.slice(t+1);return!i||!s?null:{imagePart:i,pluginPath:s}}async function Vf(e,t,i,s){if(De(e,S))return{pluginPath:null,pluginConfig:{}};let n=e.plugin_hash;if(!n)throw new b(`Internal error: plugin ${e.package} missing plugin_hash`);let r=e.package,o=e.pluginConfig??{},a=er(e);if(await rS(r,n,a,t,i,s))return s.delete(n),{pluginPath:null,pluginConfig:o};if(!e.version)throw new b(`No version for ${r}`);let l=Hf(r);if(!l)throw new b(`OCI package ${r} missing !plugin-path suffix`);let{imagePart:c,pluginPath:h}=l,u=await i.getTarball(c);await zf(u,h,t);let f=ps.join(t,h);return await Tt.mkdir(f,{recursive:!0}),await Tt.writeFile(ps.join(f,ol),await i.getDigest(c)),await Tt.writeFile(ps.join(f,ti),n),ir(s,h),{pluginPath:h,pluginConfig:o}}async function rS(e,t,i,s,n,r){let o=r.get(t);if(o===void 0)return!1;if(i===Pt.IF_NOT_PRESENT)return S(` ==> ${e}: already installed, skipping`),!0;if(i!==Pt.ALWAYS)return!1;let a=ps.join(s,o,ol);if(!await be(a))return!1;let l=(await Tt.readFile(a,"utf8")).trim(),c=Hf(e);if(!c)return!1;let h=await n.getDigest(c.imagePart);return l!==h?!1:(S(` ==> ${e}: digest unchanged, skipping`),!0)}var Zf=require("node:fs"),ni=O(require("node:fs/promises"));var oS=1e3,Jf=600*1e3;async function Xf(e){let t=aS(process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS),i=Date.now()+t;for(;;){try{await ni.writeFile(e,String(process.pid),{flag:"wx"}),S(`======= Created lock file: ${e}`);return}catch(s){if(s.code!=="EEXIST")throw s}if(Date.now()>=i)throw new b(`Timed out after ${t}ms waiting for lock file ${e}. Another install may be stuck \u2014 remove the file manually to proceed.`);S(`======= Waiting for lock to be released: ${e}`),await lS(e,i)}}function aS(e){if(!e)return Jf;let t=Number.parseInt(e,10);return Number.isFinite(t)&&t>=1?t:Jf}async function Qf(e){try{await ni.unlink(e),S(`======= Removed lock file: ${e}`)}catch(t){if(t.code!=="ENOENT")throw t}}function ed(e){let t=()=>{try{(0,Zf.unlinkSync)(e)}catch{}};process.on("exit",t),process.on("SIGTERM",()=>{t(),process.exit(0)}),process.on("SIGINT",()=>{t(),process.exit(130)})}async function lS(e,t){for(;;){try{await ni.access(e)}catch{return}if(Date.now()>=t)return;await cS(oS)}}function cS(e){return new Promise(t=>setTimeout(t,e))}var SS=O(Go());var hS=/^(@[^/]+\/)?([^@]+)(?:@(.+))?$/,uS=/^([^@]+)@npm:(@[^/]+\/)?([^@]+)(?:@(.+))?$/,fS=/^([^/@]+)\/([^/#]+)(?:#(.+))?$/,dS=[/^git\+https?:\/\/[^#]+(?:#(.+))?$/,/^git\+ssh:\/\/[^#]+(?:#(.+))?$/,/^git:\/\/[^#]+(?:#(.+))?$/,/^https:\/\/github\.com\/[^/]+\/[^/#]+(?:\.git)?(?:#(.+))?$/,/^git@github\.com:[^/]+\/[^/#]+(?:\.git)?(?:#(.+))?$/,/^github:([^/@]+)\/([^/#]+)(?:#(.+))?$/];function td(e){if(e.startsWith("./")||e.endsWith(".tgz"))return e;let t=pS(e);return t||(mS(e)?gS(e):yS(e))}function pS(e){let t=uS.exec(e);if(!t)return null;let[,i,s,n]=t;return`${i}@npm:${s??""}${n}`}function mS(e){return dS.some(t=>t.test(e))?!0:e.includes("://")||e.startsWith("@")?!1:fS.test(e)}function gS(e){let t=e.indexOf("#");return t>=0?e.slice(0,t):e}function yS(e){let t=hS.exec(e);if(!t)return e;let[,i,s]=t;return`${i??""}${s}`}var id=new RegExp("^("+wS(D)+String.raw`[^\s/:@]+`+String.raw`(?::\d+)?`+String.raw`(?:/[^\s:@]+)+`+")"+String.raw`(?::([^\s!@:]+)`+"|"+String.raw`@((?:sha256|sha512|blake3):[^\s!@:]+))`+String.raw`(?:!([^\s]+))?$`);async function sd(e,t){let i=id.exec(e);if(!i)throw new b(`oci package '${e}' is not in the expected format '${D}:' or '${D}@:' (optionally followed by '!') where may include a port (e.g. host:5000/path) and is one of ${at.join(", ")}`);let s=i[1],n=i[2],r=i[3],o=i[4]??null,a=n??r,l=n==="{{inherit}}"&&r===void 0;return l&&!o?{pluginKey:s,version:a,inherit:l,resolvedPath:null}:(o||(o=await bS(e,s,a,n!==void 0,t)),{pluginKey:`${s}:!${o}`,version:a,inherit:l,resolvedPath:o})}async function bS(e,t,i,s,n){if(!n)throw new b(`Cannot auto-detect plugin path for ${e}: no image cache provided`);let r=s?`${t}:${i}`:`${t}@${i}`;S(` -======= No plugin path specified for ${r}, auto-detecting from OCI manifest`);let o=await n.getPluginPaths(r);if(o.length===0)throw new b(`No plugins found in OCI image ${r}. The image might not contain the 'io.backstage.dynamic-packages' annotation. Please ensure it was packaged using the @red-hat-developer-hub/cli plugin package command.`);if(o.length>1){let l=o.map(c=>` - ${c}`).join(` -`);throw new b(`Multiple plugins found in OCI image ${r}: -${l} -Please specify which plugin to install using the syntax: ${r}!`)}let a=o[0];return S(` -======= Auto-resolving OCI package ${r} to use plugin path: ${a}`),a}function fl(e){let t=id.exec(e);return t?{registry:t[1],path:t[4]??null}:null}function wS(e){return e.replaceAll(/[.*+?^${}()|[\]\\/]/g,String.raw`\$&`)}function rd(e){return e==="__proto__"||e==="constructor"||e==="prototype"}function dl(e,t,i){t==="__proto__"||t==="constructor"||t==="prototype"||Object.defineProperty(e,t,{value:i,writable:!0,enumerable:!0,configurable:!0})}function lr(e,t,i=""){for(let[s,n]of Object.entries(e)){if(rd(s))continue;let r=t;if($e(n)){let o=r[s],a=$e(o)?o:{};dl(r,s,a),lr(n,a,`${i}${s}.`)}else{if(s in t&&!ml(r[s],n))throw new b(`Config key '${i}${s}' defined differently for 2 dynamic plugins`);dl(r,s,n)}}return t}async function pl(e,t,i,s,n){if(typeof e.package!="string")throw new b(`content of the 'plugins.package' field must be a string in ${i}`);e.package.startsWith(D)?await ES(e,t,i,s,n):vS(e,t,i,s)}function vS(e,t,i,s){let n=td(e.package);OS(n,e,t,i,s)}async function ES(e,t,i,s,n){let r=await sd(e.package,n);r.inherit&&r.resolvedPath===null?r=kS(e,t,r):!e.package.includes("!")&&r.resolvedPath&&(e.package=`${e.package}!${r.resolvedPath}`),e.version=r.version;let o=t[r.pluginKey];if(!o){if(r.inherit)throw new b(`ERROR: {{inherit}} tag is set and there is currently no resolved tag or digest for ${e.package} in ${i}.`);S(` -======= Adding new dynamic plugin configuration for version \`${r.version}\` of ${r.pluginKey}`),e.last_modified_level=s,t[r.pluginKey]=e;return}if(S(` -======= Overriding dynamic plugin configuration ${r.pluginKey}`),o.last_modified_level===s)throw new b(`Duplicate plugin configuration for ${e.package} found in ${i}.`);r.inherit||(o.package=e.package,o.version!==r.version&&S(`INFO: Overriding version for ${r.pluginKey} from \`${o.version??""}\` to \`${r.version}\``),o.version=r.version),od(e,o,["package","version","last_modified_level"]),o.last_modified_level=s}function kS(e,t,i){let s=`${i.pluginKey}:!`,n=Object.keys(t).filter(h=>h.startsWith(s));if(n.length===0)throw new b(`Cannot use {{inherit}} for ${i.pluginKey}: no existing plugin configuration found. Ensure a plugin from this image is defined in an included file with an explicit version.`);if(n.length>1){let h=n.map(u=>{let f=t[u]?.version??"",p=u.split(":!")[0]??"",g=u.split(":!").at(-1)??"";return` - ${p}:${f}!${g}`}).join(` -`);throw new b(`Cannot use {{inherit}} for ${i.pluginKey}: multiple plugins from this image are defined in the included files: -${h} -Please specify which plugin configuration to inherit from using: ${i.pluginKey}:{{inherit}}!`)}let r=n[0],o=t[r];if(!o?.version)throw new b(`Internal: inherited plugin ${r} has no version`);let a=o.version,l=r.split(":!").at(-1)??"",c=r.split(":!")[0]??"";return e.package=`${c}:${a}!${l}`,S(` -======= Inheriting version \`${a}\` and plugin path \`${l}\` for ${r}`),{pluginKey:r,version:a,inherit:!0,resolvedPath:l}}function OS(e,t,i,s,n){let r=i[e];if(!r){S(` -======= Adding new dynamic plugin configuration for ${e}`),t.last_modified_level=n,i[e]=t;return}if(S(` -======= Overriding dynamic plugin configuration ${e}`),r.last_modified_level===n)throw new b(`Duplicate plugin configuration for ${t.package} found in ${s}.`);od(t,r,["last_modified_level"]),r.last_modified_level=n}function od(e,t,i){let s=new Set(i);for(let[n,r]of Object.entries(e))s.has(n)||rd(n)||dl(t,n,r)}function ml(e,t){return e===t?!0:typeof e!=typeof t?!1:Array.isArray(e)&&Array.isArray(t)?_S(e,t):$e(e)&&$e(t)?AS(e,t):!1}function _S(e,t){return e.length!==t.length?!1:e.every((i,s)=>ml(i,t[s]))}function AS(e,t){let i=Object.keys(e);return i.length!==Object.keys(t).length?!1:i.every(s=>ml(e[s],t[s]))}function ar(e,t){return`${e} ${t??""}`}function NS(e,t,i){if(!i)throw new b(`oci package '${e}' is not in the expected format '${D}:' or '${D}@:' (optionally followed by '!') in ${t} where may include a port (e.g. host:5000/path) and is one of ${at.join(", ")}`);S(`WARNING: Skipping disabled OCI plugin with invalid format: '${e}' in ${t}. Expected format: '${D}:' or '${D}@:' (optionally followed by '!') where may include a port (e.g. host:5000/path) and is one of ${at.join(", ")}`)}function RS(e,t,i,s,n,r,o){let a=ar(t,i),l=e.perEntryState.get(a);if(!l)return e.perEntryState.set(a,{disabled:n,level:s}),!0;if(l.level===s){let c=i?`!${i}`:"";if(!n)throw new b(`Duplicate OCI plugin configuration for ${t}${c} found at the same level in ${o}: ${r}`);return S(`WARNING: Skipping duplicate disabled OCI plugin configuration for ${t}${c} in ${o}`),!1}return s>l.level&&e.perEntryState.set(a,{disabled:n,level:s}),!0}function PS(e,t,i,s){if(!i){e.pathlessRegistries.set(t,s);return}let n=e.definedPaths.get(t);n||(n=new Map,e.definedPaths.set(t,n)),n.set(i,s)}function nd(e,t,i,s){let n=t.package;if(typeof n!="string"||!n.startsWith(D))return;let r=De(t,S),o=fl(n);if(!o){NS(n,s,r);return}let{registry:a,path:l}=o;RS(e,a,l,i,r,n,s)&&PS(e,a,l,s)}function IS(e){return[...e.entries()].sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>`${t} (in ${i})`).join(` - - `)}function TS(e){for(let[t,i]of e.pathlessRegistries){let s=e.definedPaths.get(t);if(!s||s.size<=1)continue;let n=IS(s);if(e.perEntryState.get(ar(t,null))?.disabled){S(`WARNING: Skipping disabled ambiguous path-less OCI reference for ${t} in ${i}: multiple path-specific entries exist: - - ${n} -Cannot use path-less syntax for multi-plugin images. Please specify a ! suffix for the plugin`);continue}throw new b(`Ambiguous path-less OCI reference for ${t} in ${i}: multiple path-specific entries exist: - - ${n} -Cannot use path-less syntax for multi-plugin images. Please specify a ! suffix for the plugin.`)}}function LS(e,t){let i=e.perEntryState.get(ar(t,null));if(!i)return!1;let s=e.definedPaths.get(t);if(s?.size!==1)return i.disabled;let[n]=s.keys();if(n===void 0)return i.disabled;let r=e.perEntryState.get(ar(t,n));return r&&r.level>i.level?r.disabled:i.disabled}function CS(e){let t=new Set;for(let i of e.pathlessRegistries.keys())LS(e,i)&&t.add(i);return t}function ad(e,t,i){let s={perEntryState:new Map,pathlessRegistries:new Map,definedPaths:new Map};for(let[n,r]of e)for(let o of r)nd(s,o,0,n);for(let n of t)nd(s,n,1,i);return TS(s),CS(s)}function gl(e,t){let i=[];for(let s of e){let n=s.package;if(typeof n=="string"&&n.startsWith(D)){let r=fl(n);if(r&&t.has(r.registry)){S(` -======= Disabling OCI plugin ${n}`);continue}if(!r&&De(s)){S(` -======= Disabling OCI plugin ${n}`);continue}}i.push(s)}return i}var ld=require("node:crypto"),xe=require("node:fs"),ri=O(require("node:path"));function cd(e){let t={};for(let[s,n]of Object.entries(e))s==="pluginConfig"||s==="version"||s==="plugin_hash"||(t[s]=n);e.package.startsWith("./")&&(t._local_package_info=MS(e.package));let i=bl(t);return(0,ld.createHash)("sha256").update(i).digest("hex")}function MS(e){let t=ri.isAbsolute(e)?e:ri.join(process.cwd(),e.slice(2)),i=ri.join(t,"package.json");if(!(0,xe.existsSync)(i))try{return{_directory_mtime:yl((0,xe.statSync)(t).mtimeMs)}}catch{return{_not_found:!0}}try{let s={_package_json:JSON.parse((0,xe.readFileSync)(i,"utf8")),_package_json_mtime:yl((0,xe.statSync)(i).mtimeMs)};for(let n of["package-lock.json","yarn.lock"]){let r=ri.join(t,n);(0,xe.existsSync)(r)&&(s[`_${n}_mtime`]=yl((0,xe.statSync)(r).mtimeMs))}return s}catch(s){return{_error:s.message}}}function yl(e){return e/1e3}function DS(e,t){return et?1:0}function bl(e){if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(bl).join(", ")}]`;let t=e;return`{${Object.keys(t).sort(DS).map(s=>`${JSON.stringify(s)}: ${bl(t[s])}`).join(", ")}}`}var fd=require("node:child_process");var cr=require("node:fs"),hd=O(require("node:path"));function ud(e){let t=process.env.PATH??"",i=process.platform==="win32"?";":":",s=process.platform==="win32"?(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";"):[""];for(let n of t.split(i))if(n)for(let r of s){let o=hd.join(n,e+r);try{return(0,cr.accessSync)(o,cr.constants.X_OK),o}catch{}}return null}var hr=class{path;inspectRawCache=new Map;inspectCache=new Map;existsCache=new Map;constructor(t){let i=t??ud("skopeo");if(!i)throw new b("skopeo not found in PATH");this.path=i}async copy(t,i){await fs([this.path,"copy","--override-os=linux","--override-arch=amd64",t,i],`skopeo copy failed: ${t}`)}async inspectRaw(t){let i=this.inspectRawCache.get(t);if(i)return i;let s=this.runInspect(t,!0);this.inspectRawCache.set(t,s);try{return await s}catch(n){throw this.inspectRawCache.delete(t),n}}async inspect(t){let i=this.inspectCache.get(t);if(i)return i;let s=this.runInspect(t,!1);this.inspectCache.set(t,s);try{return await s}catch(n){throw this.inspectCache.delete(t),n}}async exists(t){let i=this.existsCache.get(t);if(i)return i;let s=new Promise(n=>{let r=(0,fd.spawn)(this.path,["inspect","--no-tags",t],{stdio:"ignore"});r.on("error",()=>n(!1)),r.on("close",o=>n(o===0))});return this.existsCache.set(t,s),s}async runInspect(t,i){let s=["inspect","--no-tags",t];i&&s.splice(1,0,"--raw");let{stdout:n}=await fs([this.path,...s],`skopeo inspect failed: ${t}`);return JSON.parse(n)}};var pd="dynamic-plugins.yaml";async function $S(){let[e]=process.argv.slice(2);e||(process.stderr.write(`Usage: install-dynamic-plugins -`),process.exit(1));let t=K.resolve(e),i=K.join(t,Of);ed(i),await X.mkdir(t,{recursive:!0}),await Xf(i);let s=0;try{s=await xS(t)}finally{await Tf(t).catch(()=>{}),await Qf(i).catch(()=>{})}process.exit(s)}async function xS(e){let t=new hr,i=Cf();S(`======= Workers: ${i} (CPUs: ${ms.cpus().length})`);let s=K.resolve(pd),n=K.dirname(s),r=K.join(e,al);S(`======= Config file: ${s}`);let o=process.env.CATALOG_ENTITIES_EXTRACT_DIR??K.join(ms.tmpdir(),"extensions"),a=await qS(t,e,o);await BS(t,o);let l=await FS(s,r);if(!l)return 0;let c=new nr(t,await X.mkdtemp(K.join(ms.tmpdir(),"rhdh-oci-cache-"))),h=await jS(l,s,n,a,c),u=await ZS(e),f={dynamicPlugins:{rootDirectory:"dynamic-plugins-root"}},{oci:p,npm:g,skipped:d}=US(h);zS(d,f);let m=(process.env.SKIP_INTEGRITY_CHECK??"").toLowerCase()==="true",w=[];return await YS(p,e,c,u,i,f,w),await GS(g,e,m,u,f,w),md(w,r,f,e,u)}async function qS(e,t,i){let s=process.env.CATALOG_INDEX_IMAGE??"";return s?Af(e,s,t,i):null}async function BS(e,t){let i=process.env.EXTRA_CATALOG_INDEX_IMAGES??"";if(!i)return;let s=K.join(t,"extra"),n=new Map;for(let[r,o]of Pf(i)){let a=n.get(r)??null;n.set(r,o),await Rf(e,o,r,s,a)}}async function FS(e,t){if(!await be(e))return S(`No ${pd} found at ${e}. Skipping.`),await X.writeFile(t,""),null;let i=await X.readFile(e,"utf8"),s=(0,gs.parse)(i);return s||(S(`${e} is empty. Skipping.`),await X.writeFile(t,""),null)}async function jS(e,t,i,s,n){let r={},o=KS(e.includes??[],i,s),a=[];for(let h of o){if(!await be(h)){S(`WARNING: include file ${h} not found, skipping`);continue}S(` -======= Including plugins from ${h}`);let u=(0,gs.parse)(await X.readFile(h,"utf8"));if(u&&!$e(u))throw new b(`${h} must contain a mapping`);let f=u?.plugins??[];if(!Array.isArray(f))throw new b(`${h} must contain a 'plugins' list (got ${typeof f})`);a.push([h,f])}let l=e.plugins??[],c=ad(a,l,t);for(let[h,u]of a)for(let f of gl(u,c))await pl(f,r,h,0,n);for(let h of gl(l,c))await pl(h,r,t,1,n);for(let h of Object.values(r))h.plugin_hash=cd(h);return r}function KS(e,t,i){let s=e.map(n=>K.isAbsolute(n)?n:K.resolve(t,n));if(i){let n=s.findIndex(r=>K.basename(r)===Qn);n!==-1&&(s[n]=i)}return s}async function md(e,t,i,s,n){if(e.length>0){S(` -======= ${e.length} plugin(s) failed:`);for(let r of e)S(` - ${r}`);return S(` -======= Skipping ${al} write and cleanup because of install failures. Fix the errors above and re-run; the previous successful state is preserved.`),1}return await X.writeFile(t,(0,gs.stringify)(i)),await JS(s,n),S(` -======= All plugins installed successfully`),0}function US(e){let t=[],i=[],s=[];for(let n of Object.values(e)){if(De(n,S)){S(` -======= Skipping disabled plugin ${n.package}`);continue}if(n.package.startsWith(D)){t.push(n);continue}if(n.package.startsWith("./")){let r=K.join(process.cwd(),n.package.slice(2));(0,dd.existsSync)(r)?i.push(n):s.push(n);continue}i.push(n)}return{oci:t,npm:i,skipped:s}}function zS(e,t){if(e.length!==0){S(` -======= Skipping ${e.length} local plugins (directories not found)`);for(let i of e){let s=K.join(process.cwd(),i.package.slice(2));S(` ==> ${i.package} (not found at ${s})`),$e(i.pluginConfig)&&lr(i.pluginConfig,t)}}}async function YS(e,t,i,s,n,r,o){await gd({plugins:e,workers:n,label:"OCI",installFn:a=>Vf(a,t,i,s),installed:s,globalConfig:r,errors:o})}async function GS(e,t,i,s,n,r){await gd({plugins:e,workers:Mf(),label:"NPM",installFn:o=>Wf(o,t,i,s),installed:s,globalConfig:n,errors:r})}async function gd(e){let{plugins:t,workers:i,label:s,installFn:n,installed:r,globalConfig:o,errors:a}=e;if(t.length===0)return;let l=WS(t,r,o,a);if(l.length===0)return;let c=i===1?"":"s";S(` -======= Installing ${l.length} ${s} plugin(s) (${i} worker${c})`);let h=await Lf(l,i,async u=>(S(` -======= Installing ${s} plugin ${u.package}`),n(u)));HS(h,o,a)}function WS(e,t,i,s){let n=[];for(let r of e)VS(r,t)?(S(` ==> ${r.package}: already installed, skipping`),t.delete(r.plugin_hash),yd(r.pluginConfig,i,r.package,s)):n.push(r);return n}function HS(e,t,i){for(let s of e){if(!s.ok){i.push(`${s.item.package}: ${s.error.message}`),S(` ==> ERROR: ${s.item.package}: ${s.error.message}`);continue}let{value:n,item:r}=s;yd(n.pluginConfig,t,r.package,i)&&n.pluginPath&&S(` ==> Installed ${r.package}`)}}function yd(e,t,i,s){if(!$e(e))return!0;try{return lr(e,t),!0}catch(n){return s.push(`${i}: ${n.message}`),!1}}function VS(e,t){return!e.plugin_hash||!t.has(e.plugin_hash)||e.forceDownload?!1:er(e)!==Pt.ALWAYS}async function JS(e,t){for(let[,i]of t){let s=K.join(e,i);S(` -======= Removing obsolete plugin ${i}`),await X.rm(s,{recursive:!0,force:!0})}}async function ZS(e){let t=new Map,i;try{i=await X.readdir(e)}catch{return t}for(let s of i){let n=K.join(e,s,ti);try{let r=(await X.readFile(n,"utf8")).trim();r&&t.set(r,s)}catch{}}return t}require.main===module&&$S().catch(e=>{let t=e instanceof b?e.message:String(e);process.stderr.write(` -install-dynamic-plugins failed: ${t} -`),process.exit(1)});0&&(module.exports={finalizeInstall}); diff --git a/scripts/install-dynamic-plugins/esbuild.config.mjs b/scripts/install-dynamic-plugins/esbuild.config.mjs deleted file mode 100644 index 7ab5e591a8..0000000000 --- a/scripts/install-dynamic-plugins/esbuild.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import { build } from 'esbuild'; - -await build({ - entryPoints: ['src/index.ts'], - bundle: true, - platform: 'node', - target: 'node22', - format: 'cjs', - outfile: 'dist/install-dynamic-plugins.cjs', - // Minify the production bundle to reduce cold-start parse cost in the - // init container. The external sourcemap (committed alongside the .cjs) - // is what `node --enable-source-maps` consumes if a stack trace needs to - // be unminified during debugging. - minify: true, - sourcemap: 'external', - banner: { js: '#!/usr/bin/env node' }, - legalComments: 'external', - logLevel: 'info', -}); diff --git a/scripts/install-dynamic-plugins/install-dynamic-plugins.sh b/scripts/install-dynamic-plugins/install-dynamic-plugins.sh deleted file mode 100755 index 8a80859a00..0000000000 --- a/scripts/install-dynamic-plugins/install-dynamic-plugins.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -# -# Copyright Red Hat, Inc. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -exec node install-dynamic-plugins.cjs "$1" diff --git a/scripts/install-dynamic-plugins/package-lock.json b/scripts/install-dynamic-plugins/package-lock.json deleted file mode 100644 index fea0429c9c..0000000000 --- a/scripts/install-dynamic-plugins/package-lock.json +++ /dev/null @@ -1,2664 +0,0 @@ -{ - "name": "@rhdh/install-dynamic-plugins", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@rhdh/install-dynamic-plugins", - "version": "1.0.0", - "license": "Apache-2.0", - "dependencies": { - "tar": "^7.5.13", - "yaml": "^2.8.2" - }, - "bin": { - "install-dynamic-plugins": "dist/install-dynamic-plugins.cjs" - }, - "devDependencies": { - "@types/node": "^22.10.0", - "@types/tar": "^6.1.13", - "@vitest/coverage-v8": "^4.1.4", - "esbuild": "^0.25.0", - "prettier": "^3.3.3", - "typescript": "^5.9.3", - "vitest": "^4.1.4" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@isaacs/fs-minipass/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.124.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", - "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", - "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", - "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", - "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "minipass": "^4.0.0" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", - "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.4", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.4", - "vitest": "4.1.4" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", - "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", - "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", - "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.4", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", - "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", - "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", - "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", - "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vitest": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", - "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/expect": "4.1.4", - "@vitest/mocker": "4.1.4", - "@vitest/pretty-format": "4.1.4", - "@vitest/runner": "4.1.4", - "@vitest/snapshot": "4.1.4", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.4", - "@vitest/browser-preview": "4.1.4", - "@vitest/browser-webdriverio": "4.1.4", - "@vitest/coverage-istanbul": "4.1.4", - "@vitest/coverage-v8": "4.1.4", - "@vitest/ui": "4.1.4", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, - "node_modules/vitest/node_modules/vite": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", - "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - } - } -} diff --git a/scripts/install-dynamic-plugins/package.json b/scripts/install-dynamic-plugins/package.json deleted file mode 100644 index ec21c8e2b0..0000000000 --- a/scripts/install-dynamic-plugins/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@rhdh/install-dynamic-plugins", - "version": "1.0.0", - "private": true, - "license": "Apache-2.0", - "description": "Install dynamic plugins into a RHDH instance from a dynamic-plugins.yaml config (OCI, NPM, local).", - "engines": { - "node": ">=22" - }, - "bin": { - "install-dynamic-plugins": "dist/install-dynamic-plugins.cjs" - }, - "scripts": { - "build": "node esbuild.config.mjs", - "tsc": "tsc", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "prettier:check": "prettier --check .", - "prettier:fix": "prettier --write ." - }, - "dependencies": { - "tar": "^7.5.13", - "yaml": "^2.8.2" - }, - "devDependencies": { - "@types/node": "^22.10.0", - "@types/tar": "^6.1.13", - "@vitest/coverage-v8": "^4.1.4", - "esbuild": "^0.25.0", - "prettier": "^3.3.3", - "typescript": "^5.9.3", - "vitest": "^4.1.4" - } -} diff --git a/scripts/install-dynamic-plugins/src/catalog-index.ts b/scripts/install-dynamic-plugins/src/catalog-index.ts deleted file mode 100644 index ade2014ba8..0000000000 --- a/scripts/install-dynamic-plugins/src/catalog-index.ts +++ /dev/null @@ -1,282 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import * as tar from 'tar'; -import { InstallException } from './errors.js'; -import { log } from './log.js'; -import { resolveImage } from './image-resolver.js'; -import { type Skopeo } from './skopeo.js'; -import { DOCKER_PROTO, DPDY_FILENAME, MAX_ENTRY_SIZE, OCI_PROTO } from './types.js'; -import { fileExists, isAllowedEntryType, isInside } from './util.js'; - -type OciManifest = { - layers?: Array<{ digest: string }>; -}; - -/** - * Extract the plugin catalog index OCI image (when `CATALOG_INDEX_IMAGE` is - * set). Produces: - * - `/.catalog-index-temp/dynamic-plugins.default.yaml` - * - `/catalog-entities/` (if present in the image) - * - * Returns the absolute path to the extracted `dynamic-plugins.default.yaml`, - * which the caller will substitute into `includes[]`. - */ -export async function extractCatalogIndex( - skopeo: Skopeo, - image: string, - mountDir: string, - entitiesDir: string, -): Promise { - log(`\n======= Extracting catalog index from ${image}`); - const tempDir = path.join(mountDir, '.catalog-index-temp'); - await fs.mkdir(tempDir, { recursive: true }); - const tempDirAbs = path.resolve(tempDir); - - await extractCatalogIndexLayers(skopeo, image, tempDirAbs); - - const dpdy = path.join(tempDir, DPDY_FILENAME); - if (!(await fileExists(dpdy))) { - throw new InstallException(`dynamic-plugins.default.yaml not found in ${image}`); - } - log('\t==> Extracted dynamic-plugins.default.yaml'); - - // Also surface catalog entities if present. - for (const sub of ['catalog-entities/extensions', 'catalog-entities/marketplace']) { - const src = path.join(tempDir, sub); - if (await fileExists(src)) { - await fs.mkdir(entitiesDir, { recursive: true }); - const dst = path.join(entitiesDir, 'catalog-entities'); - await fs.rm(dst, { recursive: true, force: true }); - await copyDir(src, dst); - log(`\t==> Extracted catalog entities from ${sub}`); - break; - } - } - return dpdy; -} - -/** - * Pull an OCI image with `skopeo copy` and untar every layer into `destDirAbs`. - * Shared by the primary `extractCatalogIndex` and the per-image - * `extractExtraCatalogIndex` flows. Applies the same security filter as - * `extractCatalogIndex` (per-entry size cap, path-traversal rejection, - * link-target containment, allowed-type whitelist). - */ -export async function extractCatalogIndexLayers( - skopeo: Skopeo, - image: string, - destDirAbs: string, -): Promise { - const resolved = await resolveImage(skopeo, image); - const workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rhdh-catalog-index-')); - try { - const url = resolved.startsWith(DOCKER_PROTO) - ? resolved - : `${DOCKER_PROTO}${resolved.replace(OCI_PROTO, '')}`; - const localDir = path.join(workDir, 'idx'); - log('\t==> Downloading catalog index image'); - await skopeo.copy(url, `dir:${localDir}`); - - const manifestPath = path.join(localDir, 'manifest.json'); - if (!(await fileExists(manifestPath))) { - throw new InstallException(`manifest.json not found in catalog index image ${image}`); - } - - const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8')) as OciManifest; - const layers = manifest.layers ?? []; - - let pending: InstallException | null = null; - for (const layer of layers) { - if (pending) break; - const digest = layer.digest; - if (!digest) continue; - const [, fname] = digest.split(':'); - if (!fname) continue; - const layerPath = path.join(localDir, fname); - if (!(await fileExists(layerPath))) continue; - - await tar.x({ - file: layerPath, - cwd: destDirAbs, - preservePaths: false, - filter: (filePath, entry) => { - if (pending) return false; - const stat = entry as tar.ReadEntry; - - if (stat.size > MAX_ENTRY_SIZE) { - pending = new InstallException(`Zip bomb detected in ${filePath}`); - return false; - } - - if (stat.type === 'SymbolicLink' || stat.type === 'Link') { - const linkTarget = path.resolve(destDirAbs, stat.linkpath ?? ''); - if (!isInside(linkTarget, destDirAbs)) return false; - } - - // Reject any entry that would resolve outside destDirAbs. - const memberPath = path.resolve(destDirAbs, filePath); - if (!isInside(memberPath, destDirAbs)) return false; - - return isAllowedEntryType(stat.type); - }, - }); - } - if (pending) throw pending; - } finally { - await fs.rm(workDir, { recursive: true, force: true }); - } -} - -/** - * Extract an extra catalog index image (driven by `EXTRA_CATALOG_INDEX_IMAGES`). - * Unlike `extractCatalogIndex`, this does NOT require a - * `dynamic-plugins.default.yaml` — extra images contribute catalog entities - * for the Extensions UI only. - * - * Writes catalog entities to `//catalog-entities`, - * overwriting any prior content at that path. When the source image carries - * neither `catalog-entities/extensions/` nor `catalog-entities/marketplace/`, - * a warning is logged and the function returns without throwing. - * - * `previouslyUsedBy` should be the image ref that previously mapped to this - * subdirectory name in the same `EXTRA_CATALOG_INDEX_IMAGES` invocation; - * pass `null` on first use. When non-null, an overwrite warning is logged - * AFTER the extraction header (matches the Python fix-up commit ordering). - */ -export async function extractExtraCatalogIndex( - skopeo: Skopeo, - image: string, - subdirectory: string, - parentDir: string, - previouslyUsedBy: string | null, -): Promise { - if (!isSafeSubdirectoryName(subdirectory)) { - throw new InstallException( - `Refusing to extract extra catalog index into unsafe subdirectory '${subdirectory}'`, - ); - } - log(`\n======= Extracting extra catalog index '${subdirectory}' from ${image}`); - if (previouslyUsedBy) { - log( - `\t==> WARNING: Subdirectory '${subdirectory}' was already used by '${previouslyUsedBy}'. ` + - `The previous extraction will be overwritten.`, - ); - } - - const workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rhdh-extra-catalog-index-')); - try { - const extractedDir = path.join(workDir, 'extracted'); - await fs.mkdir(extractedDir, { recursive: true }); - await extractCatalogIndexLayers(skopeo, image, extractedDir); - - const subdirParent = path.join(parentDir, subdirectory); - log(`\t==> Extracting extensions catalog entities to ${subdirParent}`); - - let sourceDir: string | null = null; - for (const sub of ['catalog-entities/extensions', 'catalog-entities/marketplace']) { - const candidate = path.join(extractedDir, sub); - if (await fileExists(candidate)) { - sourceDir = candidate; - break; - } - } - - if (!sourceDir) { - log( - `\t==> WARNING: Extra catalog index image ${image} does not have neither ` + - `'catalog-entities/extensions/' nor 'catalog-entities/marketplace/' directory`, - ); - return; - } - - await fs.mkdir(subdirParent, { recursive: true }); - const dst = path.join(subdirParent, 'catalog-entities'); - await fs.rm(dst, { recursive: true, force: true }); - await copyDir(sourceDir, dst); - log( - `\t==> Successfully extracted extensions catalog entities from extra index image to ${subdirParent}`, - ); - } finally { - await fs.rm(workDir, { recursive: true, force: true }); - } -} - -/** - * Convert an OCI image reference to a filesystem-safe subdirectory name by - * replacing `/`, `:`, and `@` with `_`. Matches the Python - * `image_ref_to_subdirectory` helper so the on-disk layout is identical - * between the two implementations. - */ -export function imageRefToSubdirectory(imageRef: string): string { - return imageRef.replaceAll(/[/:@]/g, '_'); -} - -/** - * Parse the `EXTRA_CATALOG_INDEX_IMAGES` env var. Each comma-separated entry - * is either a plain image reference (subdirectory auto-derived via - * `imageRefToSubdirectory`) or `=` (explicit subdirectory - * name). Empty entries and empty image_refs are skipped with a warning — - * the caller still consumes the rest of the list. - */ -export function parseExtraCatalogIndexImages(raw: string): Array<[name: string, imageRef: string]> { - const out: Array<[string, string]> = []; - for (const rawEntry of raw.split(',')) { - const entry = rawEntry.trim(); - if (!entry) continue; - let name: string; - let imageRef: string; - const eq = entry.indexOf('='); - if (eq === -1) { - imageRef = entry; - name = imageRefToSubdirectory(imageRef); - } else { - name = entry.slice(0, eq).trim(); - imageRef = entry.slice(eq + 1).trim(); - } - if (!imageRef) { - log(`WARNING: Skipping EXTRA_CATALOG_INDEX_IMAGES entry with empty image reference: '${entry}'`); - continue; - } - if (!isSafeSubdirectoryName(name)) { - log( - `WARNING: Skipping EXTRA_CATALOG_INDEX_IMAGES entry with unsafe subdirectory name '${name}' in '${entry}'. ` + - `Names must be non-empty and must not contain '/', '\\\\', or '..'.`, - ); - continue; - } - out.push([name, imageRef]); - } - return out; -} - -/** - * Reject subdirectory names that are empty or could escape `` once - * passed to `path.join` (path separators or `..` segments). Mirrors the - * defensive check applied to plugin paths during tar extraction. - */ -function isSafeSubdirectoryName(name: string): boolean { - if (!name || name === '.' || name === '..') return false; - return !/[/\\]/.test(name); -} - -export async function cleanupCatalogIndexTemp(mountDir: string): Promise { - await fs.rm(path.join(mountDir, '.catalog-index-temp'), { - recursive: true, - force: true, - }); -} - -async function copyDir(src: string, dst: string): Promise { - await fs.mkdir(dst, { recursive: true }); - const entries = await fs.readdir(src, { withFileTypes: true }); - for (const entry of entries) { - const s = path.join(src, entry.name); - const d = path.join(dst, entry.name); - if (entry.isDirectory()) { - await copyDir(s, d); - } else if (entry.isFile()) { - await fs.copyFile(s, d); - } - } -} diff --git a/scripts/install-dynamic-plugins/src/concurrency.ts b/scripts/install-dynamic-plugins/src/concurrency.ts deleted file mode 100644 index 4fb4c41fbe..0000000000 --- a/scripts/install-dynamic-plugins/src/concurrency.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as os from 'node:os'; - -/** - * Minimal semaphore for bounding concurrent async work. - * Matches the Python `ThreadPoolExecutor(max_workers=N)` worker model from - * install-dynamic-plugins-fast.py — single-threaded JS means no lock needed - * on the counter itself. - */ -export class Semaphore { - private available: number; - private readonly queue: Array<() => void> = []; - - constructor(max: number) { - if (max < 1) throw new RangeError(`Semaphore max must be >= 1, got ${max}`); - this.available = max; - } - - async acquire(): Promise { - if (this.available > 0) { - this.available--; - return; - } - return new Promise(resolve => this.queue.push(resolve)); - } - - release(): void { - const next = this.queue.shift(); - if (next) next(); - else this.available++; - } -} - -export type Outcome = - | { ok: true; value: T; item: Item } - | { ok: false; error: Error; item: Item }; - -/** - * Run `fn` over `items` with at most `limit` concurrent executions. - * Returns every outcome — errors are captured, not thrown, so one failure - * does not cancel the others. Mirrors the behaviour of fast.py's parallel install loop. - */ -export async function mapConcurrent( - items: readonly Item[], - limit: number, - fn: (item: Item) => Promise, -): Promise>> { - const sem = new Semaphore(Math.max(1, limit)); - return Promise.all( - items.map(async item => { - await sem.acquire(); - try { - return { ok: true as const, value: await fn(item), item }; - } catch (err) { - return { ok: false as const, error: err as Error, item }; - } finally { - sem.release(); - } - }), - ); -} - -/** Conservative upper bound for parallel `skopeo` calls. */ -const MAX_OCI_WORKERS = 6; - -/** - * Lower cap for NPM installs because `npm pack` hits the public registry - * and shares a single CLI cache (`~/.npm/_cacache`) — excessive concurrency - * triggers throttling and cache contention without a wall-clock benefit. - */ -const MAX_NPM_WORKERS = 3; - -/** - * Worker count selection, honouring `DYNAMIC_PLUGINS_WORKERS` env and cgroup - * CPU limits (via `availableParallelism`). Conservative default for OpenShift - * init containers: half of available CPUs, capped at `MAX_OCI_WORKERS`. - */ -export function getWorkers(): number { - return resolveWorkers(process.env.DYNAMIC_PLUGINS_WORKERS, MAX_OCI_WORKERS); -} - -/** - * Worker count for concurrent NPM installs. Override via - * `DYNAMIC_PLUGINS_NPM_WORKERS` (set to `1` to restore the original - * sequential behaviour). - */ -export function getNpmWorkers(): number { - return resolveWorkers(process.env.DYNAMIC_PLUGINS_NPM_WORKERS, MAX_NPM_WORKERS); -} - -function resolveWorkers(rawEnv: string | undefined, cap: number): number { - const env = rawEnv ?? 'auto'; - if (env !== 'auto') { - const n = Number.parseInt(env, 10); - if (!Number.isFinite(n) || n < 1) return 1; - return n; - } - const cpus = - typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length; - return Math.max(1, Math.min(Math.floor(cpus / 2), cap)); -} diff --git a/scripts/install-dynamic-plugins/src/errors.ts b/scripts/install-dynamic-plugins/src/errors.ts deleted file mode 100644 index 265517bb68..0000000000 --- a/scripts/install-dynamic-plugins/src/errors.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Thrown for any installer-level failure (parsing, download, extraction, integrity). - * Kept as a named class so callers can distinguish expected failures from bugs. - */ -export class InstallException extends Error { - constructor(message: string) { - super(message); - this.name = 'InstallException'; - } -} diff --git a/scripts/install-dynamic-plugins/src/image-cache.ts b/scripts/install-dynamic-plugins/src/image-cache.ts deleted file mode 100644 index 30f3ed16ba..0000000000 --- a/scripts/install-dynamic-plugins/src/image-cache.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { createHash } from 'node:crypto'; -import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; -import { InstallException } from './errors.js'; -import { log } from './log.js'; -import { resolveImage } from './image-resolver.js'; -import { type Skopeo } from './skopeo.js'; -import { DOCKER_PROTO, OCI_PROTO } from './types.js'; - -type OciManifest = { - layers?: Array<{ digest: string }>; - annotations?: Record; -}; - -/** - * Shared cache that keeps each OCI image's single-layer tarball on disk and - * returns the path. If several plugins point at the same image (very common - * for multi-plugin overlays) we download once and extract slices from that - * same tarball. - * - * The cache stores *promises*, so concurrent `getTarball` calls for the same - * image share the in-flight `skopeo copy` rather than racing. This is the - * JS equivalent of fast.py's `threading.Lock` guard around the cache. - */ -export class OciImageCache { - private readonly tarballs = new Map>(); - - constructor( - private readonly skopeo: Skopeo, - private readonly tmpDir: string, - ) {} - - async getTarball(image: string): Promise { - const resolved = await resolveImage(this.skopeo, image); - let pending = this.tarballs.get(resolved); - if (!pending) { - pending = this.downloadAndLocateTarball(resolved); - this.tarballs.set(resolved, pending); - pending.catch(() => this.tarballs.delete(resolved)); - } - return pending; - } - - async getDigest(image: string): Promise { - const resolved = await resolveImage(this.skopeo, image); - const dockerUrl = resolved.replace(OCI_PROTO, DOCKER_PROTO); - const data = await this.skopeo.inspect(dockerUrl); - const digest = data.Digest; - if (!digest) throw new InstallException(`No digest returned for ${image}`); - const [, hash] = digest.split(':'); - if (!hash) throw new InstallException(`Malformed digest ${digest} for ${image}`); - return hash; - } - - /** - * Plugin paths are published via the `io.backstage.dynamic-packages` OCI - * annotation (base64-encoded JSON array of `{path: {...}}` objects). An - * image with no annotation returns an empty list. - */ - async getPluginPaths(image: string): Promise { - const resolved = await resolveImage(this.skopeo, image); - const dockerUrl = resolved.replace(OCI_PROTO, DOCKER_PROTO); - const manifest = (await this.skopeo.inspectRaw(dockerUrl)) as OciManifest; - const annotation = manifest.annotations?.['io.backstage.dynamic-packages']; - if (!annotation) return []; - let entries: unknown; - try { - const decoded = Buffer.from(annotation, 'base64').toString('utf8'); - entries = JSON.parse(decoded); - } catch (err) { - throw new InstallException( - `Could not decode 'io.backstage.dynamic-packages' annotation on ${image}: ${(err as Error).message}`, - ); - } - if (!Array.isArray(entries)) return []; - const paths: string[] = []; - for (const entry of entries) { - if (entry && typeof entry === 'object') { - paths.push(...Object.keys(entry as Record)); - } - } - return paths; - } - - private async downloadAndLocateTarball(resolved: string): Promise { - const digest = createHash('sha256').update(resolved).digest('hex'); - const localDir = path.join(this.tmpDir, digest); - await fs.mkdir(localDir, { recursive: true }); - const dockerUrl = resolved.replace(OCI_PROTO, DOCKER_PROTO); - log(`\t==> Downloading ${resolved}`); - await this.skopeo.copy(dockerUrl, `dir:${localDir}`); - - const manifestPath = path.join(localDir, 'manifest.json'); - const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8')) as OciManifest; - const firstLayer = manifest.layers?.[0]?.digest; - if (!firstLayer) { - throw new InstallException(`OCI manifest for ${resolved} has no layers`); - } - const [, filename] = firstLayer.split(':'); - if (!filename) { - throw new InstallException(`Malformed layer digest ${firstLayer} in ${resolved}`); - } - return path.join(localDir, filename); - } -} diff --git a/scripts/install-dynamic-plugins/src/image-resolver.ts b/scripts/install-dynamic-plugins/src/image-resolver.ts deleted file mode 100644 index b4370c135f..0000000000 --- a/scripts/install-dynamic-plugins/src/image-resolver.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { log } from './log.js'; -import { type Skopeo } from './skopeo.js'; -import { DOCKER_PROTO, OCI_PROTO, RHDH_FALLBACK, RHDH_REGISTRY } from './types.js'; - -/** - * Resolve a (possibly oci:// / docker://) image reference. If it points at - * `registry.access.redhat.com/rhdh/...` and that registry rejects the image, - * fall back to `quay.io/rhdh/...` (same protocol). Mirrors fast.py `resolve_image`. - */ -export async function resolveImage(skopeo: Skopeo, image: string): Promise { - const { proto, raw } = stripProto(image); - if (!raw.startsWith(RHDH_REGISTRY)) return image; - - const dockerUrl = `${DOCKER_PROTO}${raw}`; - if (await skopeo.exists(dockerUrl)) return image; - - const fallback = raw.replace(RHDH_REGISTRY, RHDH_FALLBACK); - log(`\t==> Falling back to ${RHDH_FALLBACK} for ${raw}`); - return `${proto}${fallback}`; -} - -function stripProto(image: string): { proto: string; raw: string } { - if (image.startsWith(OCI_PROTO)) return { proto: OCI_PROTO, raw: image.slice(OCI_PROTO.length) }; - if (image.startsWith(DOCKER_PROTO)) - return { proto: DOCKER_PROTO, raw: image.slice(DOCKER_PROTO.length) }; - return { proto: '', raw: image }; -} diff --git a/scripts/install-dynamic-plugins/src/index.ts b/scripts/install-dynamic-plugins/src/index.ts deleted file mode 100644 index c353675b90..0000000000 --- a/scripts/install-dynamic-plugins/src/index.ts +++ /dev/null @@ -1,540 +0,0 @@ -import { existsSync } from 'node:fs'; -import * as fs from 'node:fs/promises'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; -import { - cleanupCatalogIndexTemp, - extractCatalogIndex, - extractExtraCatalogIndex, - parseExtraCatalogIndexImages, -} from './catalog-index.js'; -import { getNpmWorkers, getWorkers, mapConcurrent, type Outcome } from './concurrency.js'; -import { InstallException } from './errors.js'; -import { OciImageCache } from './image-cache.js'; -import { installNpmPlugin } from './installer-npm.js'; -import { installOciPlugin } from './installer-oci.js'; -import { createLock, registerLockCleanup, removeLock } from './lock-file.js'; -import { log } from './log.js'; -import { - deepMerge, - filterDisabledOciPlugins, - mergePlugin, - preMergeOciDisabledState, -} from './merger.js'; -import { computePluginHash } from './plugin-hash.js'; -import { Skopeo } from './skopeo.js'; -import { - CONFIG_HASH_FILE, - DPDY_FILENAME, - type DynamicPluginsConfig, - effectivePullPolicy, - GLOBAL_CONFIG_FILENAME, - isPluginDisabled, - LOCK_FILENAME, - OCI_PROTO, - type Plugin, - type PluginMap, - type PluginSpec, - PullPolicy, -} from './types.js'; -import { fileExists, isPlainObject } from './util.js'; - -const CONFIG_FILE = 'dynamic-plugins.yaml'; - -async function main(): Promise { - const [rootArg] = process.argv.slice(2); - if (!rootArg) { - process.stderr.write(`Usage: install-dynamic-plugins \n`); - process.exit(1); - } - const root = path.resolve(rootArg); - const lockPath = path.join(root, LOCK_FILENAME); - registerLockCleanup(lockPath); - await fs.mkdir(root, { recursive: true }); - await createLock(lockPath); - - let exitCode = 0; - try { - exitCode = await runInstaller(root); - } finally { - await cleanupCatalogIndexTemp(root).catch(() => undefined); - await removeLock(lockPath).catch(() => undefined); - } - process.exit(exitCode); -} - -async function runInstaller(root: string): Promise { - const skopeo = new Skopeo(); - const workers = getWorkers(); - log(`======= Workers: ${workers} (CPUs: ${os.cpus().length})`); - - // Resolve the config file path against CWD at startup so the dependency on - // CWD is explicit in the operator log; includes are resolved relative to - // the config file's directory (matches the Python installer). - const configFileAbs = path.resolve(CONFIG_FILE); - const configDir = path.dirname(configFileAbs); - const globalConfigFile = path.join(root, GLOBAL_CONFIG_FILENAME); - log(`======= Config file: ${configFileAbs}`); - - const entitiesDir = - process.env.CATALOG_ENTITIES_EXTRACT_DIR ?? path.join(os.tmpdir(), 'extensions'); - const catalogDpdy = await maybeExtractCatalogIndex(skopeo, root, entitiesDir); - await maybeExtractExtraCatalogIndexes(skopeo, entitiesDir); - const content = await loadDynamicPluginsConfig(configFileAbs, globalConfigFile); - if (!content) return 0; - - const imageCache = new OciImageCache( - skopeo, - await fs.mkdtemp(path.join(os.tmpdir(), 'rhdh-oci-cache-')), - ); - - const allPlugins = await loadAllPlugins( - content, - configFileAbs, - configDir, - catalogDpdy, - imageCache, - ); - const installed = await readInstalledPluginHashes(root); - const globalConfig: Record = { - dynamicPlugins: { rootDirectory: 'dynamic-plugins-root' }, - }; - const { oci, npm, skipped } = categorize(allPlugins); - handleSkippedLocals(skipped, globalConfig); - - const skipIntegrity = (process.env.SKIP_INTEGRITY_CHECK ?? '').toLowerCase() === 'true'; - const errors: string[] = []; - await installOci(oci, root, imageCache, installed, workers, globalConfig, errors); - await installNpm(npm, root, skipIntegrity, installed, globalConfig, errors); - - return finalizeInstall(errors, globalConfigFile, globalConfig, root, installed); -} - -/** Optional `CATALOG_INDEX_IMAGE` extraction — returns the path to the - * extracted `dynamic-plugins.default.yaml`, or `null` when the env var is - * unset. */ -async function maybeExtractCatalogIndex( - skopeo: Skopeo, - root: string, - entitiesDir: string, -): Promise { - const catalogImage = process.env.CATALOG_INDEX_IMAGE ?? ''; - if (!catalogImage) return null; - return extractCatalogIndex(skopeo, catalogImage, root, entitiesDir); -} - -/** - * Optional `EXTRA_CATALOG_INDEX_IMAGES` extraction. Each entry is extracted - * into an isolated subdirectory under `/extra/` so multiple - * indexes can coexist without clobbering the primary index's - * `catalog-entities/`. Duplicate subdirectory names within the same env-var - * value emit an overwrite warning (handled by `extractExtraCatalogIndex`). - */ -async function maybeExtractExtraCatalogIndexes(skopeo: Skopeo, entitiesDir: string): Promise { - const raw = process.env.EXTRA_CATALOG_INDEX_IMAGES ?? ''; - if (!raw) return; - const extraParent = path.join(entitiesDir, 'extra'); - const seen = new Map(); - for (const [name, imageRef] of parseExtraCatalogIndexImages(raw)) { - const prev = seen.get(name) ?? null; - seen.set(name, imageRef); - await extractExtraCatalogIndex(skopeo, imageRef, name, extraParent, prev); - } -} - -/** Read and parse `dynamic-plugins.yaml`. Writes an empty global config and - * returns `null` for the two short-circuit cases (file missing, file empty) - * so the caller can early-exit with code 0. */ -async function loadDynamicPluginsConfig( - configFileAbs: string, - globalConfigFile: string, -): Promise { - if (!(await fileExists(configFileAbs))) { - log(`No ${CONFIG_FILE} found at ${configFileAbs}. Skipping.`); - await fs.writeFile(globalConfigFile, ''); - return null; - } - const rawContent = await fs.readFile(configFileAbs, 'utf8'); - const content = parseYaml(rawContent) as DynamicPluginsConfig | null; - if (!content) { - log(`${configFileAbs} is empty. Skipping.`); - await fs.writeFile(globalConfigFile, ''); - return null; - } - return content; -} - -/** Resolve include paths, substitute the catalog-index placeholder, merge - * everything into a single `PluginMap`, and compute change-detection hashes. - * - * Two-phase to match the Python pre-merge OCI-disable pass: load every - * include file's plugin list into memory FIRST, compute the effectively - * disabled OCI registries, then filter those entries out of every list - * before merging. Without this pass an OCI plugin marked `disabled: true` - * at level 1 would still trigger a `skopeo` round-trip during the level-0 - * merge — wasted work and a footgun in restricted-network init containers. - */ -async function loadAllPlugins( - content: DynamicPluginsConfig, - configFileAbs: string, - configDir: string, - catalogDpdy: string | null, - imageCache: OciImageCache, -): Promise { - const allPlugins: PluginMap = {}; - const includes = resolveIncludes(content.includes ?? [], configDir, catalogDpdy); - - const includeLists: Array<[string, PluginSpec[]]> = []; - for (const inc of includes) { - if (!(await fileExists(inc))) { - log(`WARNING: include file ${inc} not found, skipping`); - continue; - } - log(`\n======= Including plugins from ${inc}`); - const parsed = parseYaml(await fs.readFile(inc, 'utf8')) as DynamicPluginsConfig | null; - if (parsed && !isPlainObject(parsed)) { - throw new InstallException(`${inc} must contain a mapping`); - } - const plugins = parsed?.plugins ?? []; - if (!Array.isArray(plugins)) { - throw new InstallException(`${inc} must contain a 'plugins' list (got ${typeof plugins})`); - } - includeLists.push([inc, plugins]); - } - const mainPlugins = content.plugins ?? []; - - const disabledRegistries = preMergeOciDisabledState( - includeLists, - mainPlugins, - configFileAbs, - ); - - for (const [inc, plugins] of includeLists) { - for (const plugin of filterDisabledOciPlugins(plugins, disabledRegistries)) { - await mergePlugin(plugin, allPlugins, inc, /* level */ 0, imageCache); - } - } - for (const plugin of filterDisabledOciPlugins(mainPlugins, disabledRegistries)) { - await mergePlugin(plugin, allPlugins, configFileAbs, /* level */ 1, imageCache); - } - - for (const p of Object.values(allPlugins)) { - p.plugin_hash = computePluginHash(p); - } - return allPlugins; -} - -function resolveIncludes( - rawIncludes: readonly string[], - configDir: string, - catalogDpdy: string | null, -): string[] { - const includes = rawIncludes.map(inc => - path.isAbsolute(inc) ? inc : path.resolve(configDir, inc), - ); - if (catalogDpdy) { - const idx = includes.findIndex(inc => path.basename(inc) === DPDY_FILENAME); - if (idx !== -1) includes[idx] = catalogDpdy; - } - return includes; -} - -export async function finalizeInstall( - errors: string[], - globalConfigFile: string, - globalConfig: Record, - root: string, - installed: Map, -): Promise { - if (errors.length > 0) { - log(`\n======= ${errors.length} plugin(s) failed:`); - for (const err of errors) log(` - ${err}`); - // Skip writing the global config and cleaning up previously-installed - // plugin dirs so the filesystem does not end up in an "almost valid" - // state. Exit 1 is enough for init containers to block startup, but a - // manual restart of the main container (or a deployment that does not - // enforce init-container success) could otherwise pick up a partial - // config — e.g. a frontend plugin without its backend dep, yielding a - // broken UI. Preserving the prior state makes the next run a clean retry. - log( - `\n======= Skipping ${GLOBAL_CONFIG_FILENAME} write and cleanup because of install failures. ` + - `Fix the errors above and re-run; the previous successful state is preserved.`, - ); - return 1; - } - - await fs.writeFile(globalConfigFile, stringifyYaml(globalConfig)); - await cleanupRemoved(root, installed); - - log('\n======= All plugins installed successfully'); - return 0; -} - -type Categorized = { - oci: Plugin[]; - npm: Plugin[]; - skipped: Plugin[]; -}; - -function categorize(allPlugins: PluginMap): Categorized { - const oci: Plugin[] = []; - const npm: Plugin[] = []; - const skipped: Plugin[] = []; - for (const plugin of Object.values(allPlugins)) { - if (isPluginDisabled(plugin, log)) { - log(`\n======= Skipping disabled plugin ${plugin.package}`); - continue; - } - if (plugin.package.startsWith(OCI_PROTO)) { - oci.push(plugin); - continue; - } - if (plugin.package.startsWith('./')) { - const localPath = path.join(process.cwd(), plugin.package.slice(2)); - if (existsSync(localPath)) npm.push(plugin); - else skipped.push(plugin); - continue; - } - npm.push(plugin); - } - return { oci, npm, skipped }; -} - -function handleSkippedLocals(skipped: Plugin[], globalConfig: Record): void { - if (skipped.length === 0) return; - log(`\n======= Skipping ${skipped.length} local plugins (directories not found)`); - for (const plugin of skipped) { - const abs = path.join(process.cwd(), plugin.package.slice(2)); - log(`\t==> ${plugin.package} (not found at ${abs})`); - if (isPlainObject(plugin.pluginConfig)) { - deepMerge(plugin.pluginConfig, globalConfig); - } - } -} - -type InstallOutcome = { - pluginPath: string | null; - pluginConfig: Record; -}; - -async function installOci( - plugins: Plugin[], - root: string, - imageCache: OciImageCache, - installed: Map, - workers: number, - globalConfig: Record, - errors: string[], -): Promise { - await runInstallPipeline({ - plugins, - workers, - label: 'OCI', - installFn: plugin => installOciPlugin(plugin, root, imageCache, installed), - installed, - globalConfig, - errors, - }); -} - -async function installNpm( - plugins: Plugin[], - root: string, - skipIntegrity: boolean, - installed: Map, - globalConfig: Record, - errors: string[], -): Promise { - // `npm pack` writes the tarball to `cwd` with a package-derived filename - // (`-.tgz`), so concurrent invocations against different - // packages don't clash on the filename. The npm CLI cache - // (`~/.npm/_cacache`) handles its own locking. Cap defaults to 3 to keep - // the registry happy — override with `DYNAMIC_PLUGINS_NPM_WORKERS=1` to - // restore the original sequential behaviour. - await runInstallPipeline({ - plugins, - workers: getNpmWorkers(), - label: 'NPM', - installFn: plugin => installNpmPlugin(plugin, root, skipIntegrity, installed), - installed, - globalConfig, - errors, - }); -} - -type RunInstallPipelineArgs = { - plugins: Plugin[]; - workers: number; - label: 'OCI' | 'NPM'; - installFn: (plugin: Plugin) => Promise; - installed: Map; - globalConfig: Record; - errors: string[]; -}; - -/** - * Shared install pipeline used by both `installOci` and `installNpm`: - * 1. Synchronous pre-pass that short-circuits "definitely no-op" plugins - * (hash present, no force, pull policy not Always) without spinning - * up the worker pool — avoids the parallel-skopeo / parallel-npm-pack - * overhead in the steady-state restart case. - * 2. `mapConcurrent` over the plugins that actually need work, capped by - * `workers`. - * 3. Single-pass over the outcomes that records errors and merges plugin - * configs into the global config. - * - * Keeping both categories on this shared body so a behaviour change (a new - * fast-path filter, a different log format, an extra error pathway) does - * not have to be made twice in two slightly-divergent copies. - */ -async function runInstallPipeline(args: RunInstallPipelineArgs): Promise { - const { plugins, workers, label, installFn, installed, globalConfig, errors } = args; - if (plugins.length === 0) return; - - const needsWork = partitionDefinitelyNoOp(plugins, installed, globalConfig, errors); - if (needsWork.length === 0) return; - - const workerSuffix = workers === 1 ? '' : 's'; - log( - `\n======= Installing ${needsWork.length} ${label} plugin(s) (${workers} worker${workerSuffix})`, - ); - - const results = await mapConcurrent(needsWork, workers, async plugin => { - log(`\n======= Installing ${label} plugin ${plugin.package}`); - return installFn(plugin); - }); - - applyInstallOutcomes(results, globalConfig, errors); -} - -/** - * Synchronous pre-pass: drop plugins that are definitely a no-op (hash on - * disk, not forced, not Always-pull) without paying the worker-pool / - * Promise overhead, and return the remaining plugins that actually need - * installation work. Side-effect: removes the no-op plugins from - * `installed` and merges their `pluginConfig` into `globalConfig`. - */ -function partitionDefinitelyNoOp( - plugins: Plugin[], - installed: Map, - globalConfig: Record, - errors: string[], -): Plugin[] { - const needsWork: Plugin[] = []; - for (const plugin of plugins) { - if (definitelyNoOp(plugin, installed)) { - log(`\t==> ${plugin.package}: already installed, skipping`); - installed.delete(plugin.plugin_hash); - mergeConfigSafely(plugin.pluginConfig, globalConfig, plugin.package, errors); - } else { - needsWork.push(plugin); - } - } - return needsWork; -} - -/** - * Drain a `mapConcurrent` outcome list: record errors, merge configs into - * the global config, log success lines. Pulled out of `runInstallPipeline` - * to keep that orchestrator small enough to read top-to-bottom. - */ -function applyInstallOutcomes( - results: ReadonlyArray>, - globalConfig: Record, - errors: string[], -): void { - for (const outcome of results) { - if (!outcome.ok) { - errors.push(`${outcome.item.package}: ${outcome.error.message}`); - log(`\t==> ERROR: ${outcome.item.package}: ${outcome.error.message}`); - continue; - } - const { value, item } = outcome; - const merged = mergeConfigSafely(value.pluginConfig, globalConfig, item.package, errors); - if (merged && value.pluginPath) { - log(`\t==> Installed ${item.package}`); - } - } -} - -/** - * Merge `pluginConfig` into `globalConfig` if it is a plain object. Returns - * `false` and pushes onto `errors` when `deepMerge` raises a key conflict — - * the caller uses this to skip the "Installed" success log so the operator - * sees only the error line for the affected plugin. - */ -function mergeConfigSafely( - pluginConfig: Record | undefined, - globalConfig: Record, - pkg: string, - errors: string[], -): boolean { - if (!isPlainObject(pluginConfig)) return true; - try { - deepMerge(pluginConfig, globalConfig); - return true; - } catch (err) { - errors.push(`${pkg}: ${(err as Error).message}`); - return false; - } -} - -/** - * Cheap synchronous check: a plugin is "definitely" a no-op when its hash - * is already on disk, the user did not force a re-download, and the pull - * policy doesn't demand a remote-digest comparison. ALWAYS-pull plugins - * still go through the regular install path because they need a - * `skopeo inspect` to compare local vs remote digest. - * - * Type guard: narrows `plugin.plugin_hash` to a non-undefined `string` - * inside the `if (definitelyNoOp(...))` branch so the caller does not - * need a `as string` cast on the subsequent `installed.delete` call. - */ -function definitelyNoOp( - plugin: Plugin, - installed: Map, -): plugin is Plugin & { plugin_hash: string } { - if (!plugin.plugin_hash || !installed.has(plugin.plugin_hash)) return false; - if (plugin.forceDownload) return false; - return effectivePullPolicy(plugin) !== PullPolicy.ALWAYS; -} - -async function cleanupRemoved(root: string, installed: Map): Promise { - for (const [, dir] of installed) { - const pluginDir = path.join(root, dir); - log(`\n======= Removing obsolete plugin ${dir}`); - await fs.rm(pluginDir, { recursive: true, force: true }); - } -} - -async function readInstalledPluginHashes(root: string): Promise> { - const installed = new Map(); - let entries: string[]; - try { - entries = await fs.readdir(root); - } catch { - return installed; - } - for (const entry of entries) { - const hashFile = path.join(root, entry, CONFIG_HASH_FILE); - try { - const hash = (await fs.readFile(hashFile, 'utf8')).trim(); - if (hash) installed.set(hash, entry); - } catch { - /* not a plugin dir */ - } - } - return installed; -} - -// Only run main() when invoked directly (CLI or esbuild's CJS bundle entry), -// not when imported for tests. Under ts-jest the source is transpiled to CJS, -// so `require.main === module` is the reliable signal. -if (require.main === module) { - main().catch((err: unknown) => { - const msg = err instanceof InstallException ? err.message : String(err); - process.stderr.write(`\ninstall-dynamic-plugins failed: ${msg}\n`); - process.exit(1); - }); -} diff --git a/scripts/install-dynamic-plugins/src/installer-npm.ts b/scripts/install-dynamic-plugins/src/installer-npm.ts deleted file mode 100644 index 49ecebe333..0000000000 --- a/scripts/install-dynamic-plugins/src/installer-npm.ts +++ /dev/null @@ -1,128 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; -import { InstallException } from './errors.js'; -import { verifyIntegrity } from './integrity.js'; -import { log } from './log.js'; -import { run } from './run.js'; -import { extractNpmPackage } from './tar-extract.js'; -import { CONFIG_HASH_FILE, isPluginDisabled, type Plugin } from './types.js'; -import { markAsFresh } from './util.js'; - -export type NpmInstallResult = { - pluginPath: string | null; - pluginConfig: Record; -}; - -/** - * Install a single NPM-packaged (or local) plugin into `destination`. - * Runs `npm pack` to produce the tarball, verifies integrity for remote - * packages (unless skipped), then extracts. - * - * Concurrency is the caller's responsibility — `installNpm` in `index.ts` - * runs a bounded `mapConcurrent` (default 3 workers via `getNpmWorkers()`) - * over a list of plugins that have already passed the `definitelyNoOp` - * pre-pass, so by the time this function is called the plugin definitely - * needs work. - */ -export async function installNpmPlugin( - plugin: Plugin, - destination: string, - skipIntegrity: boolean, - installed: Map, -): Promise { - if (isPluginDisabled(plugin, log)) { - return { pluginPath: null, pluginConfig: {} }; - } - const hash = plugin.plugin_hash; - if (!hash) { - throw new InstallException(`Internal error: plugin ${plugin.package} missing plugin_hash`); - } - const pkg = plugin.package; - const config: Record = plugin.pluginConfig ?? {}; - - const isLocal = pkg.startsWith('./'); - const actualPkg = isLocal ? path.join(process.cwd(), pkg.slice(2)) : pkg; - - const verifyRemoteIntegrity = !isLocal && !skipIntegrity; - if (verifyRemoteIntegrity && !plugin.integrity) { - throw new InstallException( - `No integrity hash provided for Package ${pkg}. This is an insecure installation. ` + - `To ignore this error, set the SKIP_INTEGRITY_CHECK environment variable to 'true'.`, - ); - } - - log('\t==> Running npm pack'); - const archiveName = await npmPack(actualPkg, destination); - if (!isSafeArchiveName(archiveName)) { - throw new InstallException( - `npm pack returned an unsafe filename for ${pkg}: '${archiveName}'`, - ); - } - const archive = path.join(destination, archiveName); - - if (verifyRemoteIntegrity) { - log('\t==> Verifying package integrity'); - // `plugin.integrity` is guaranteed present — the check above throws otherwise. - await verifyIntegrity(pkg, archive, plugin.integrity as string); - } - - const pluginPath = await extractNpmPackage(archive); - await fs.writeFile(path.join(destination, pluginPath, CONFIG_HASH_FILE), hash); - - markAsFresh(installed, pluginPath); - return { pluginPath, pluginConfig: config }; -} - -/** - * Run `npm pack --json` and extract the archive filename from the structured - * output. The text form of `npm pack` intermixes warnings with the filename - * (last-line parsing is fragile); `--json` gives `[{ filename, ... }]`. - */ -async function npmPack(actualPkg: string, destination: string): Promise { - // `--ignore-scripts` blocks `preinstall` / `prepack` / `prepare` lifecycle - // hooks that NPM packages can declare. Dynamic plugins are not expected - // to ship build steps that need to run at install time, and skipping the - // hooks both removes a code-execution-on-install attack surface and - // shaves a fork+exec per package off the wall clock. - const { stdout } = await run( - ['npm', 'pack', '--json', '--ignore-scripts', actualPkg], - `npm pack failed for ${actualPkg}`, - { cwd: destination }, - ); - let parsed: unknown; - try { - parsed = JSON.parse(stdout); - } catch (err) { - throw new InstallException( - `npm pack produced invalid JSON for ${actualPkg}: ${(err as Error).message}`, - ); - } - if (!Array.isArray(parsed) || parsed.length === 0) { - throw new InstallException(`npm pack produced no archives for ${actualPkg}`); - } - const first = parsed[0]; - if (!isNpmPackJsonEntry(first)) { - throw new InstallException(`npm pack output missing 'filename' for ${actualPkg}`); - } - return first.filename; -} - -function isNpmPackJsonEntry(value: unknown): value is { filename: string } { - return ( - !!value && - typeof value === 'object' && - typeof (value as { filename?: unknown }).filename === 'string' - ); -} - -/** - * Reject any filename that would let `npm pack` escape `destination` once - * passed to `path.join` — directory separators, leading `..`, or empty. - * `npm pack` is expected to emit a flat `-.tgz`, so any - * non-flat name is treated as adversarial. - */ -function isSafeArchiveName(name: string): boolean { - if (!name || name === '.' || name === '..') return false; - if (name.startsWith('..')) return false; - return !/[/\\]/.test(name); -} diff --git a/scripts/install-dynamic-plugins/src/installer-oci.ts b/scripts/install-dynamic-plugins/src/installer-oci.ts deleted file mode 100644 index f8fb65313c..0000000000 --- a/scripts/install-dynamic-plugins/src/installer-oci.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; -import { InstallException } from './errors.js'; -import { type OciImageCache } from './image-cache.js'; -import { log } from './log.js'; -import { extractOciPlugin } from './tar-extract.js'; -import { - CONFIG_HASH_FILE, - effectivePullPolicy, - IMAGE_HASH_FILE, - isPluginDisabled, - type Plugin, - PullPolicy, -} from './types.js'; -import { fileExists, markAsFresh } from './util.js'; - -/** - * Split an OCI package spec into `!`. Uses - * `indexOf` so plugin paths containing `!` (legal per the OCI grammar) are - * preserved on the right side instead of being silently truncated by - * `String#split`. - */ -function splitOciPackage(pkg: string): { imagePart: string; pluginPath: string } | null { - const bang = pkg.indexOf('!'); - if (bang === -1) return null; - const imagePart = pkg.slice(0, bang); - const pluginPath = pkg.slice(bang + 1); - if (!imagePart || !pluginPath) return null; - return { imagePart, pluginPath }; -} - -export type OciInstallResult = { - /** The installed plugin's directory name (relative to destination), or null when skipped. */ - pluginPath: string | null; - pluginConfig: Record; -}; - -/** - * Install a single OCI-packaged plugin into `destination`. Returns the - * on-disk directory name and the plugin's own config (for merging into the - * global app-config). - */ -export async function installOciPlugin( - plugin: Plugin, - destination: string, - imageCache: OciImageCache, - installed: Map, -): Promise { - if (isPluginDisabled(plugin, log)) { - return { pluginPath: null, pluginConfig: {} }; - } - const hash = plugin.plugin_hash; - if (!hash) { - throw new InstallException(`Internal error: plugin ${plugin.package} missing plugin_hash`); - } - const pkg = plugin.package; - const config: Record = plugin.pluginConfig ?? {}; - const pullPolicy = effectivePullPolicy(plugin); - - if (await isAlreadyInstalled(pkg, hash, pullPolicy, destination, imageCache, installed)) { - installed.delete(hash); - return { pluginPath: null, pluginConfig: config }; - } - - if (!plugin.version) { - throw new InstallException(`No version for ${pkg}`); - } - const parts = splitOciPackage(pkg); - if (!parts) { - throw new InstallException(`OCI package ${pkg} missing !plugin-path suffix`); - } - const { imagePart, pluginPath } = parts; - - const tarball = await imageCache.getTarball(imagePart); - await extractOciPlugin(tarball, pluginPath, destination); - - const pluginDir = path.join(destination, pluginPath); - await fs.mkdir(pluginDir, { recursive: true }); - await fs.writeFile(path.join(pluginDir, IMAGE_HASH_FILE), await imageCache.getDigest(imagePart)); - await fs.writeFile(path.join(pluginDir, CONFIG_HASH_FILE), hash); - - markAsFresh(installed, pluginPath); - return { pluginPath, pluginConfig: config }; -} - -/** - * Returns true when the plugin is already installed and can be skipped: - * - IfNotPresent policy → skip unconditionally - * - Always policy → skip only when the remote digest matches what's on disk - */ -async function isAlreadyInstalled( - pkg: string, - hash: string, - pullPolicy: PullPolicy, - destination: string, - imageCache: OciImageCache, - installed: Map, -): Promise { - const pathInstalled = installed.get(hash); - if (pathInstalled === undefined) return false; - - if (pullPolicy === PullPolicy.IF_NOT_PRESENT) { - log(`\t==> ${pkg}: already installed, skipping`); - return true; - } - - if (pullPolicy !== PullPolicy.ALWAYS) return false; - - const digestFile = path.join(destination, pathInstalled, IMAGE_HASH_FILE); - if (!(await fileExists(digestFile))) return false; - - const localDigest = (await fs.readFile(digestFile, 'utf8')).trim(); - const parts = splitOciPackage(pkg); - if (!parts) return false; - const remoteDigest = await imageCache.getDigest(parts.imagePart); - if (localDigest !== remoteDigest) return false; - - log(`\t==> ${pkg}: digest unchanged, skipping`); - return true; -} diff --git a/scripts/install-dynamic-plugins/src/integrity.ts b/scripts/install-dynamic-plugins/src/integrity.ts deleted file mode 100644 index ae6f47aedb..0000000000 --- a/scripts/install-dynamic-plugins/src/integrity.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { createHash } from 'node:crypto'; -import { createReadStream } from 'node:fs'; -import { pipeline } from 'node:stream/promises'; -import { InstallException } from './errors.js'; -import { RECOGNIZED_ALGORITHMS, type Algorithm } from './types.js'; - -/** - * Verify an NPM package archive matches the declared SRI-style integrity string. - * - * Uses streaming `createHash` so large archives never load into memory — safe - * for the tight init-container memory budgets on OpenShift. - */ -export async function verifyIntegrity( - pkg: string, - archive: string, - integrity: string, -): Promise { - const dash = integrity.indexOf('-'); - if (dash === -1) { - throw new InstallException( - `Package integrity for ${pkg} must be a string of the form -`, - ); - } - const algo = integrity.slice(0, dash); - const expected = integrity.slice(dash + 1); - - if (!isRecognizedAlgorithm(algo)) { - throw new InstallException( - `${pkg}: Provided Package integrity algorithm ${algo} is not supported, ` + - `please use one of following algorithms ${RECOGNIZED_ALGORITHMS.join(', ')} instead`, - ); - } - if (!isValidBase64(expected)) { - throw new InstallException( - `${pkg}: Provided Package integrity hash ${expected} is not a valid base64 encoding`, - ); - } - - const hash = createHash(algo); - await pipeline(createReadStream(archive), hash); - const actual = hash.digest('base64'); - - if (actual !== expected) { - throw new InstallException( - `${pkg}: integrity check failed — got ${algo}-${actual}, expected ${integrity}`, - ); - } -} - -function isRecognizedAlgorithm(value: string): value is Algorithm { - return (RECOGNIZED_ALGORITHMS as readonly string[]).includes(value); -} - -/** - * Validate a base64 string without regex (avoids Sonar ReDoS flags and is - * genuinely linear-time). Accepts the standard base64 alphabet plus up to two - * trailing `=` padding characters; requires the round-trip encoding to match - * so malformed padding is rejected. - */ -function isValidBase64(value: string): boolean { - if (value.length === 0) return false; - if (!isBase64Shape(value)) return false; - try { - const buf = Buffer.from(value, 'base64'); - return stripTrailingEquals(buf.toString('base64')) === stripTrailingEquals(value); - } catch { - return false; - } -} - -const EQUALS = 0x3d; - -function isBase64Shape(value: string): boolean { - let paddingCount = 0; - for (let i = 0; i < value.length; i++) { - const c = value.codePointAt(i) ?? 0; - if (c === EQUALS) { - paddingCount++; - if (paddingCount > 2) return false; - continue; - } - // Padding, once started, must run to the end of the string. - if (paddingCount > 0) return false; - if (!isBase64Char(c)) return false; - } - return true; -} - -function isBase64Char(c: number): boolean { - return ( - (c >= 0x41 && c <= 0x5a) || // A-Z - (c >= 0x61 && c <= 0x7a) || // a-z - (c >= 0x30 && c <= 0x39) || // 0-9 - c === 0x2b || // + - c === 0x2f // / - ); -} - -function stripTrailingEquals(s: string): string { - let end = s.length; - while (end > 0 && s.codePointAt(end - 1) === EQUALS) end--; - return s.slice(0, end); -} diff --git a/scripts/install-dynamic-plugins/src/lock-file.ts b/scripts/install-dynamic-plugins/src/lock-file.ts deleted file mode 100644 index 22d47f8178..0000000000 --- a/scripts/install-dynamic-plugins/src/lock-file.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { unlinkSync } from 'node:fs'; -import * as fs from 'node:fs/promises'; -import { InstallException } from './errors.js'; -import { log } from './log.js'; - -const POLL_INTERVAL_MS = 1000; -const DEFAULT_LOCK_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes - -/** - * Acquire an exclusive lock file. If the file exists we wait (polling every - * second) until it disappears, then try to create it atomically with the - * `wx` flag. Bounded by `DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS` (default 10 min) - * so a stale lock from a `kill -9`'d process doesn't wedge the init container - * forever — override via env var. - */ -export async function createLock(lockPath: string): Promise { - const timeoutMs = parseLockTimeout(process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS); - const deadline = Date.now() + timeoutMs; - for (;;) { - try { - await fs.writeFile(lockPath, String(process.pid), { flag: 'wx' }); - log(`======= Created lock file: ${lockPath}`); - return; - } catch (err) { - if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err; - } - if (Date.now() >= deadline) { - throw new InstallException( - `Timed out after ${timeoutMs}ms waiting for lock file ${lockPath}. ` + - `Another install may be stuck — remove the file manually to proceed.`, - ); - } - log(`======= Waiting for lock to be released: ${lockPath}`); - await waitForPath(lockPath, deadline); - } -} - -function parseLockTimeout(raw: string | undefined): number { - if (!raw) return DEFAULT_LOCK_TIMEOUT_MS; - const n = Number.parseInt(raw, 10); - return Number.isFinite(n) && n >= 1 ? n : DEFAULT_LOCK_TIMEOUT_MS; -} - -export async function removeLock(lockPath: string): Promise { - try { - await fs.unlink(lockPath); - log(`======= Removed lock file: ${lockPath}`); - } catch (err) { - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err; - } -} - -/** Register sync best-effort cleanup on process exit / SIGTERM / SIGINT. */ -export function registerLockCleanup(lockPath: string): void { - const cleanup = (): void => { - try { - unlinkSync(lockPath); - } catch { - /* lock already gone */ - } - }; - process.on('exit', cleanup); - process.on('SIGTERM', () => { - cleanup(); - process.exit(0); - }); - process.on('SIGINT', () => { - cleanup(); - process.exit(130); - }); -} - -async function waitForPath(lockPath: string, deadline: number): Promise { - for (;;) { - try { - await fs.access(lockPath); - } catch { - return; // gone - } - if (Date.now() >= deadline) return; - await sleep(POLL_INTERVAL_MS); - } -} - -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/scripts/install-dynamic-plugins/src/log.ts b/scripts/install-dynamic-plugins/src/log.ts deleted file mode 100644 index 250ab73bc1..0000000000 --- a/scripts/install-dynamic-plugins/src/log.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function log(message: string): void { - process.stdout.write(`${message}\n`); -} diff --git a/scripts/install-dynamic-plugins/src/merger.ts b/scripts/install-dynamic-plugins/src/merger.ts deleted file mode 100644 index 9de6aaa626..0000000000 --- a/scripts/install-dynamic-plugins/src/merger.ts +++ /dev/null @@ -1,509 +0,0 @@ -import * as fs from 'node:fs/promises'; -import { parse as parseYaml } from 'yaml'; -import { InstallException } from './errors.js'; -import { log } from './log.js'; -import { type OciImageCache } from './image-cache.js'; -import { npmPluginKey } from './npm-key.js'; -import { ociPluginKey, type ParsedOciKey, tryParseOciRegistryAndPath } from './oci-key.js'; -import { - type DynamicPluginsConfig, - isPluginDisabled, - OCI_PROTO, - type Plugin, - type PluginMap, - type PluginSpec, - RECOGNIZED_ALGORITHMS, -} from './types.js'; -import { isPlainObject } from './util.js'; - -/** - * Reject `__proto__`, `constructor`, and `prototype` keys. - * - * Inlined string-literal comparisons (rather than a shared `Set.has()` call) - * because CodeQL's `js/prototype-polluting-function` analysis only treats - * this specific pattern as an exhaustive prototype-pollution sanitizer when - * looking at the call site — a `Set` lookup gets flagged as "not guarded". - */ -function isForbiddenKey(key: string): boolean { - return key === '__proto__' || key === 'constructor' || key === 'prototype'; -} - -/** - * Safely assign `value` to `dst[key]` without touching the prototype chain. - * - * Two layers of defense: - * 1. Reject the three prototype-pollution keys outright. - * 2. `Object.defineProperty` over `dst[key] = value` so that even if a - * forbidden key somehow slipped through, the prototype chain is still - * not mutated (`defineProperty` bypasses the `__proto__` setter). - */ -function safeSet(dst: T, key: string, value: unknown): void { - if (key === '__proto__' || key === 'constructor' || key === 'prototype') return; - Object.defineProperty(dst, key, { - value, - writable: true, - enumerable: true, - configurable: true, - }); -} - -/** - * Recursively merges `src` into `dst` in place and returns `dst`. Raises on - * conflicting scalar values so duplicate plugin configs never silently - * overwrite each other (matches the Python `merge()` contract). - * - * Skips `__proto__`, `constructor`, and `prototype` keys to prevent prototype - * pollution via user-supplied YAML. - */ -export function deepMerge>( - src: Record, - dst: T, - prefix = '', -): T { - for (const [key, value] of Object.entries(src)) { - if (isForbiddenKey(key)) continue; - const dstRecord = dst as Record; - if (isPlainObject(value)) { - const existing = dstRecord[key]; - const node = isPlainObject(existing) ? existing : {}; - safeSet(dstRecord, key, node); - deepMerge(value, node, `${prefix}${key}.`); - } else { - if (key in dst && !isEqual(dstRecord[key], value)) { - throw new InstallException( - `Config key '${prefix}${key}' defined differently for 2 dynamic plugins`, - ); - } - safeSet(dstRecord, key, value); - } - } - return dst; -} - -/** - * Read a dynamic-plugins config file (main or included), parse its `plugins`, - * and merge each into `allPlugins` using the OCI or NPM merger as appropriate. - */ -export async function mergePluginsFromFile( - configFile: string, - allPlugins: PluginMap, - level: number, - imageCache?: OciImageCache, -): Promise { - const content = parseYaml(await fs.readFile(configFile, 'utf8')); - if (!isPlainObject(content)) { - throw new InstallException(`${configFile} must contain a mapping`); - } - const plugins = (content as DynamicPluginsConfig).plugins; - if (!Array.isArray(plugins)) { - throw new InstallException( - `${configFile} must contain a 'plugins' list (got ${typeof plugins})`, - ); - } - for (const plugin of plugins) { - await mergePlugin(plugin, allPlugins, configFile, level, imageCache); - } -} - -export async function mergePlugin( - plugin: Plugin, - allPlugins: PluginMap, - configFile: string, - level: number, - imageCache?: OciImageCache, -): Promise { - if (typeof plugin.package !== 'string') { - throw new InstallException( - `content of the 'plugins.package' field must be a string in ${configFile}`, - ); - } - if (plugin.package.startsWith(OCI_PROTO)) { - await mergeOciPlugin(plugin, allPlugins, configFile, level, imageCache); - } else { - mergeNpmPlugin(plugin, allPlugins, configFile, level); - } -} - -function mergeNpmPlugin( - plugin: Plugin, - allPlugins: PluginMap, - configFile: string, - level: number, -): void { - const key = npmPluginKey(plugin.package); - doMerge(key, plugin, allPlugins, configFile, level); -} - -async function mergeOciPlugin( - plugin: Plugin, - allPlugins: PluginMap, - configFile: string, - level: number, - imageCache: OciImageCache | undefined, -): Promise { - let parsed = await ociPluginKey(plugin.package, imageCache); - - if (parsed.inherit && parsed.resolvedPath === null) { - parsed = resolveInherit(plugin, allPlugins, parsed); - } else if (!plugin.package.includes('!') && parsed.resolvedPath) { - plugin.package = `${plugin.package}!${parsed.resolvedPath}`; - } - - plugin.version = parsed.version; - - const existing = allPlugins[parsed.pluginKey]; - if (!existing) { - if (parsed.inherit) { - throw new InstallException( - `ERROR: {{inherit}} tag is set and there is currently no resolved tag or digest ` + - `for ${plugin.package} in ${configFile}.`, - ); - } - log( - `\n======= Adding new dynamic plugin configuration for version \`${parsed.version}\` of ${parsed.pluginKey}`, - ); - plugin.last_modified_level = level; - allPlugins[parsed.pluginKey] = plugin; - return; - } - - log(`\n======= Overriding dynamic plugin configuration ${parsed.pluginKey}`); - if (existing.last_modified_level === level) { - throw new InstallException( - `Duplicate plugin configuration for ${plugin.package} found in ${configFile}.`, - ); - } - - if (!parsed.inherit) { - existing.package = plugin.package; - if (existing.version !== parsed.version) { - log( - `INFO: Overriding version for ${parsed.pluginKey} from \`${existing.version ?? ''}\` to \`${parsed.version}\``, - ); - } - existing.version = parsed.version; - } - copyPluginFields(plugin, existing, ['package', 'version', 'last_modified_level']); - existing.last_modified_level = level; -} - -/** - * Resolve `{{inherit}}` without a plugin path — finds a single previously- - * merged plugin from the same image, adopts its version + path, and mutates - * `plugin.package` in place. Throws with a helpful message when zero or - * multiple matches are found. - */ -function resolveInherit(plugin: Plugin, allPlugins: PluginMap, parsed: ParsedOciKey): ParsedOciKey { - const prefix = `${parsed.pluginKey}:!`; - const matches = Object.keys(allPlugins).filter(k => k.startsWith(prefix)); - if (matches.length === 0) { - throw new InstallException( - `Cannot use {{inherit}} for ${parsed.pluginKey}: no existing plugin ` + - `configuration found. Ensure a plugin from this image is defined in an ` + - `included file with an explicit version.`, - ); - } - if (matches.length > 1) { - const formatted = matches - .map(m => { - const baseVersion = allPlugins[m]?.version ?? ''; - const registryPart = m.split(':!')[0] ?? ''; - const pathPart = m.split(':!').at(-1) ?? ''; - return ` - ${registryPart}:${baseVersion}!${pathPart}`; - }) - .join('\n'); - throw new InstallException( - `Cannot use {{inherit}} for ${parsed.pluginKey}: multiple plugins from ` + - `this image are defined in the included files:\n${formatted}\n` + - `Please specify which plugin configuration to inherit from using: ` + - `${parsed.pluginKey}:{{inherit}}!`, - ); - } - const matchedKey = matches[0] as string; - const basePlugin = allPlugins[matchedKey]; - if (!basePlugin?.version) { - throw new InstallException(`Internal: inherited plugin ${matchedKey} has no version`); - } - const version = basePlugin.version; - const resolvedPath = matchedKey.split(':!').at(-1) ?? ''; - const registryPart = matchedKey.split(':!')[0] ?? ''; - plugin.package = `${registryPart}:${version}!${resolvedPath}`; - log( - `\n======= Inheriting version \`${version}\` and plugin path \`${resolvedPath}\` for ${matchedKey}`, - ); - return { pluginKey: matchedKey, version, inherit: true, resolvedPath }; -} - -function doMerge( - key: string, - plugin: Plugin, - allPlugins: PluginMap, - configFile: string, - level: number, -): void { - const existing = allPlugins[key]; - if (!existing) { - log(`\n======= Adding new dynamic plugin configuration for ${key}`); - plugin.last_modified_level = level; - allPlugins[key] = plugin; - return; - } - log(`\n======= Overriding dynamic plugin configuration ${key}`); - if (existing.last_modified_level === level) { - throw new InstallException( - `Duplicate plugin configuration for ${plugin.package} found in ${configFile}.`, - ); - } - copyPluginFields(plugin, existing, ['last_modified_level']); - existing.last_modified_level = level; -} - -function copyPluginFields(src: Plugin, dst: Plugin, skip: ReadonlyArray): void { - const skipSet = new Set(skip); - for (const [k, v] of Object.entries(src)) { - if (skipSet.has(k) || isForbiddenKey(k)) continue; - safeSet(dst, k, v); - } -} - -function isEqual(a: unknown, b: unknown): boolean { - if (a === b) return true; - if (typeof a !== typeof b) return false; - if (Array.isArray(a) && Array.isArray(b)) return isArrayEqual(a, b); - if (isPlainObject(a) && isPlainObject(b)) return isObjectEqual(a, b); - return false; -} - -function isArrayEqual(a: readonly unknown[], b: readonly unknown[]): boolean { - if (a.length !== b.length) return false; - return a.every((v, i) => isEqual(v, b[i])); -} - -function isObjectEqual(a: Record, b: Record): boolean { - const keysA = Object.keys(a); - if (keysA.length !== Object.keys(b).length) return false; - return keysA.every(k => isEqual(a[k], b[k])); -} - -type IncludePluginList = readonly [file: string, plugins: readonly PluginSpec[]]; - -type EntryState = { disabled: boolean; level: number }; - -type PreMergeState = { - perEntryState: Map; - pathlessRegistries: Map; - definedPaths: Map>; -}; - -function entryKeyOf(registry: string, path: string | null): string { - return `${registry} ${path ?? ''}`; -} - -function logInvalidOciFormat(pkg: string, sourceFile: string, disabled: boolean): void { - if (!disabled) { - throw new InstallException( - `oci package '${pkg}' is not in the expected format '${OCI_PROTO}:' ` + - `or '${OCI_PROTO}@:' (optionally followed by '!') in ${sourceFile} ` + - `where may include a port (e.g. host:5000/path) ` + - `and is one of ${RECOGNIZED_ALGORITHMS.join(', ')}`, - ); - } - log( - `WARNING: Skipping disabled OCI plugin with invalid format: '${pkg}' in ${sourceFile}. ` + - `Expected format: '${OCI_PROTO}:' or '${OCI_PROTO}@:' ` + - `(optionally followed by '!') where may include a port (e.g. host:5000/path) ` + - `and is one of ${RECOGNIZED_ALGORITHMS.join(', ')}`, - ); -} - -/** - * Record the entry's disabled state at its level. Returns `false` when the - * entry is a duplicate at the same level (warning logged for disabled-dups, - * throws for enabled-dups) so the caller can skip recording its path/source. - */ -function recordEntryState( - state: PreMergeState, - registry: string, - path: string | null, - level: number, - disabled: boolean, - pkg: string, - sourceFile: string, -): boolean { - const key = entryKeyOf(registry, path); - const existing = state.perEntryState.get(key); - if (!existing) { - state.perEntryState.set(key, { disabled, level }); - return true; - } - if (existing.level === level) { - const pathSuffix = path ? `!${path}` : ''; - if (!disabled) { - throw new InstallException( - `Duplicate OCI plugin configuration for ${registry}${pathSuffix} ` + - `found at the same level in ${sourceFile}: ${pkg}`, - ); - } - log( - `WARNING: Skipping duplicate disabled OCI plugin configuration for ${registry}${pathSuffix} in ${sourceFile}`, - ); - return false; - } - if (level > existing.level) state.perEntryState.set(key, { disabled, level }); - return true; -} - -function recordRegistryPath( - state: PreMergeState, - registry: string, - path: string | null, - sourceFile: string, -): void { - if (!path) { - state.pathlessRegistries.set(registry, sourceFile); - return; - } - let bucket = state.definedPaths.get(registry); - if (!bucket) { - bucket = new Map(); - state.definedPaths.set(registry, bucket); - } - bucket.set(path, sourceFile); -} - -function processOciEntry( - state: PreMergeState, - plugin: PluginSpec, - level: number, - sourceFile: string, -): void { - const pkg = plugin.package; - if (typeof pkg !== 'string' || !pkg.startsWith(OCI_PROTO)) return; - const disabled = isPluginDisabled(plugin, log); - const parsed = tryParseOciRegistryAndPath(pkg); - if (!parsed) { - logInvalidOciFormat(pkg, sourceFile, disabled); - return; - } - const { registry, path } = parsed; - if (!recordEntryState(state, registry, path, level, disabled, pkg, sourceFile)) return; - recordRegistryPath(state, registry, path, sourceFile); -} - -function formatExplicitPaths(bucket: Map): string { - return [...bucket.entries()] - .sort(([a], [b]) => a.localeCompare(b)) - .map(([p, src]) => `${p} (in ${src})`) - .join('\n - '); -} - -function validateAmbiguousPathless(state: PreMergeState): void { - for (const [registry, pathlessSource] of state.pathlessRegistries) { - const bucket = state.definedPaths.get(registry); - if (!bucket || bucket.size <= 1) continue; - const formatted = formatExplicitPaths(bucket); - const pathlessState = state.perEntryState.get(entryKeyOf(registry, null)); - if (pathlessState?.disabled) { - log( - `WARNING: Skipping disabled ambiguous path-less OCI reference for ${registry} in ${pathlessSource}: ` + - `multiple path-specific entries exist:\n - ${formatted}\n` + - `Cannot use path-less syntax for multi-plugin images. ` + - `Please specify a ! suffix for the plugin`, - ); - continue; - } - throw new InstallException( - `Ambiguous path-less OCI reference for ${registry} in ${pathlessSource}: ` + - `multiple path-specific entries exist:\n - ${formatted}\n` + - `Cannot use path-less syntax for multi-plugin images. ` + - `Please specify a ! suffix for the plugin.`, - ); - } -} - -function effectiveRegistryDisabled(state: PreMergeState, registry: string): boolean { - const pathlessState = state.perEntryState.get(entryKeyOf(registry, null)); - if (!pathlessState) return false; - const bucket = state.definedPaths.get(registry); - if (bucket?.size !== 1) return pathlessState.disabled; - const [singlePath] = bucket.keys(); - if (singlePath === undefined) return pathlessState.disabled; - const definedState = state.perEntryState.get(entryKeyOf(registry, singlePath)); - if (definedState && definedState.level > pathlessState.level) return definedState.disabled; - return pathlessState.disabled; -} - -function computeDisabledRegistries(state: PreMergeState): Set { - const out = new Set(); - for (const registry of state.pathlessRegistries.keys()) { - if (effectiveRegistryDisabled(state, registry)) out.add(registry); - } - return out; -} - -/** - * Pre-merge pass that walks every OCI plugin entry from the included files - * (level 0) and the main config (level 1) and returns the set of OCI - * registries that will be effectively disabled after the merge. Computed - * BEFORE any skopeo work so disabled plugins never trigger a remote fetch. - * - * Ports `pre_merge_oci_disabled_state` from the Python installer - * (`install-dynamic-plugins.py`). Only inspects `package` and `disabled` — - * does NOT merge `pluginConfig`. - * - * Throws an `InstallException` for: - * - invalid OCI package strings on enabled entries, - * - duplicate enabled OCI entries declared at the same level, - * - path-less enabled references that collide with multiple explicit-path - * entries from the same image (ambiguous). - * - * Logs a warning (and skips the offending entry) for the equivalent - * `disabled: true` scenarios — operators can still ship a disabled - * descriptor without aborting the install. - */ -export function preMergeOciDisabledState( - includePluginLists: ReadonlyArray, - mainPlugins: ReadonlyArray, - mainConfigFile: string, -): Set { - const state: PreMergeState = { - perEntryState: new Map(), - pathlessRegistries: new Map(), - definedPaths: new Map(), - }; - for (const [file, plugins] of includePluginLists) { - for (const plugin of plugins) processOciEntry(state, plugin, 0, file); - } - for (const plugin of mainPlugins) processOciEntry(state, plugin, 1, mainConfigFile); - - validateAmbiguousPathless(state); - return computeDisabledRegistries(state); -} - -/** - * Drop every OCI plugin whose registry is in the disabled set, plus invalid - * OCI entries flagged `disabled: true` (a no-op the operator clearly intends - * to remove). Non-OCI entries pass through unchanged. - */ -export function filterDisabledOciPlugins( - plugins: ReadonlyArray, - disabledRegistries: ReadonlySet, -): PluginSpec[] { - const out: PluginSpec[] = []; - for (const plugin of plugins) { - const pkg = plugin.package; - if (typeof pkg === 'string' && pkg.startsWith(OCI_PROTO)) { - const parsed = tryParseOciRegistryAndPath(pkg); - if (parsed && disabledRegistries.has(parsed.registry)) { - log(`\n======= Disabling OCI plugin ${pkg}`); - continue; - } - if (!parsed && isPluginDisabled(plugin)) { - log(`\n======= Disabling OCI plugin ${pkg}`); - continue; - } - } - out.push(plugin); - } - return out; -} diff --git a/scripts/install-dynamic-plugins/src/npm-key.ts b/scripts/install-dynamic-plugins/src/npm-key.ts deleted file mode 100644 index c947e60f99..0000000000 --- a/scripts/install-dynamic-plugins/src/npm-key.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * NPM package-spec parsing, matching install-dynamic-plugins.py - * (`NPMPackageMerger.parse_plugin_key`). - * - * A "plugin key" is the package identifier with version/ref stripped, used - * as the dedup key when merging plugins from multiple config files. Local - * paths (`./...`) and tarball files (`*.tgz`) are returned unchanged — - * there's no canonical version to strip. - * - * Spec reference: https://docs.npmjs.com/cli/v11/using-npm/package-spec - */ - -// [@scope/]name[@version] -const NPM_PACKAGE_PATTERN = /^(@[^/]+\/)?([^@]+)(?:@(.+))?$/; -// alias@npm:[@scope/]name[@version] -const NPM_ALIAS_PATTERN = /^([^@]+)@npm:(@[^/]+\/)?([^@]+)(?:@(.+))?$/; -// user/repo -const GITHUB_SHORTHAND_PATTERN = /^([^/@]+)\/([^/#]+)(?:#(.+))?$/; - -const GIT_URL_PATTERNS: RegExp[] = [ - /^git\+https?:\/\/[^#]+(?:#(.+))?$/, - /^git\+ssh:\/\/[^#]+(?:#(.+))?$/, - /^git:\/\/[^#]+(?:#(.+))?$/, - /^https:\/\/github\.com\/[^/]+\/[^/#]+(?:\.git)?(?:#(.+))?$/, - /^git@github\.com:[^/]+\/[^/#]+(?:\.git)?(?:#(.+))?$/, - /^github:([^/@]+)\/([^/#]+)(?:#(.+))?$/, -]; - -export function npmPluginKey(pkg: string): string { - // Local packages and tarballs have no version to strip. - if (pkg.startsWith('./') || pkg.endsWith('.tgz')) return pkg; - - // Aliases: "my-alias@npm:real-pkg@1.2.3" -> "my-alias@npm:real-pkg" - const aliasKey = tryParseAlias(pkg); - if (aliasKey) return aliasKey; - - // Git URLs / GitHub shorthand: strip `#ref` suffix. - if (isGitLikeSpec(pkg)) return stripRefSuffix(pkg); - - return stripStandardNpmVersion(pkg); -} - -function tryParseAlias(pkg: string): string | null { - const m = NPM_ALIAS_PATTERN.exec(pkg); - if (!m) return null; - const [, aliasName, scope, name] = m; - return `${aliasName}@npm:${scope ?? ''}${name}`; -} - -function isGitLikeSpec(pkg: string): boolean { - if (GIT_URL_PATTERNS.some(re => re.test(pkg))) return true; - // GitHub shorthand `user/repo#ref` — but not scoped packages or full URLs. - if (pkg.includes('://') || pkg.startsWith('@')) return false; - return GITHUB_SHORTHAND_PATTERN.test(pkg); -} - -function stripRefSuffix(pkg: string): string { - const hash = pkg.indexOf('#'); - return hash >= 0 ? pkg.slice(0, hash) : pkg; -} - -function stripStandardNpmVersion(pkg: string): string { - const m = NPM_PACKAGE_PATTERN.exec(pkg); - if (!m) return pkg; - const [, scope, name] = m; - return `${scope ?? ''}${name}`; -} diff --git a/scripts/install-dynamic-plugins/src/oci-key.ts b/scripts/install-dynamic-plugins/src/oci-key.ts deleted file mode 100644 index addb2c4d8a..0000000000 --- a/scripts/install-dynamic-plugins/src/oci-key.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { InstallException } from './errors.js'; -import { log } from './log.js'; -import { type OciImageCache } from './image-cache.js'; -import { OCI_PROTO, RECOGNIZED_ALGORITHMS } from './types.js'; - -export const OCI_REGEX = new RegExp( - '^(' + - escape(OCI_PROTO) + - String.raw`[^\s/:@]+` + // registry host - String.raw`(?::\d+)?` + // optional port - String.raw`(?:/[^\s:@]+)+` + // at least one path segment - ')' + - String.raw`(?::([^\s!@:]+)` + // tag - '|' + - String.raw`@((?:sha256|sha512|blake3):[^\s!@:]+))` + // or digest - String.raw`(?:!([^\s]+))?$`, // optional ! -); - -export type ParsedOciKey = { - /** `oci://registry/image:!plugin_path` — version-stripped identifier. */ - pluginKey: string; - /** Tag (e.g. `1.2.3`) or digest (`sha256:...`). */ - version: string; - /** True when tag was `{{inherit}}` (version to be resolved from an included config). */ - inherit: boolean; - /** - * Resolved plugin path — explicit `!`, auto-detected from the image's - * `io.backstage.dynamic-packages` annotation, or `null` when `{{inherit}}` - * is used without a path (the merger resolves it later). - */ - resolvedPath: string | null; -}; - -/** - * Parse an `oci://...` package spec. Matches fast.py and the original - * `OciPackageMerger.parse_plugin_key`. Calls into `imageCache.getPluginPaths` - * to auto-detect single-plugin images when the `!path` suffix is omitted. - */ -export async function ociPluginKey(pkg: string, imageCache?: OciImageCache): Promise { - const m = OCI_REGEX.exec(pkg); - if (!m) { - throw new InstallException( - `oci package '${pkg}' is not in the expected format '${OCI_PROTO}:' ` + - `or '${OCI_PROTO}@:' (optionally followed by '!') ` + - `where may include a port (e.g. host:5000/path) ` + - `and is one of ${RECOGNIZED_ALGORITHMS.join(', ')}`, - ); - } - - const registry = m[1] as string; - const tag = m[2]; - const digest = m[3]; - let path = m[4] ?? null; - - const version = (tag ?? digest) as string; - const inherit = tag === '{{inherit}}' && digest === undefined; - - if (inherit && !path) { - // The merger will match against an earlier included plugin from the same image. - return { pluginKey: registry, version, inherit, resolvedPath: null }; - } - - if (!path) { - path = await autoDetectPluginPath(pkg, registry, version, tag !== undefined, imageCache); - } - - return { - pluginKey: `${registry}:!${path}`, - version, - inherit, - resolvedPath: path, - }; -} - -async function autoDetectPluginPath( - pkg: string, - registry: string, - version: string, - isTag: boolean, - imageCache: OciImageCache | undefined, -): Promise { - if (!imageCache) { - throw new InstallException( - `Cannot auto-detect plugin path for ${pkg}: no image cache provided`, - ); - } - const fullImage = isTag ? `${registry}:${version}` : `${registry}@${version}`; - log(`\n======= No plugin path specified for ${fullImage}, auto-detecting from OCI manifest`); - const paths = await imageCache.getPluginPaths(fullImage); - if (paths.length === 0) { - throw new InstallException( - `No plugins found in OCI image ${fullImage}. ` + - `The image might not contain the 'io.backstage.dynamic-packages' annotation. ` + - `Please ensure it was packaged using the @red-hat-developer-hub/cli plugin package command.`, - ); - } - if (paths.length > 1) { - const formatted = paths.map(p => ` - ${p}`).join('\n'); - throw new InstallException( - `Multiple plugins found in OCI image ${fullImage}:\n${formatted}\n` + - `Please specify which plugin to install using the syntax: ${fullImage}!`, - ); - } - const resolved = paths[0] as string; - log(`\n======= Auto-resolving OCI package ${fullImage} to use plugin path: ${resolved}`); - return resolved; -} - -/** - * Synchronous parse for the disable-pre-merge pass. Returns `null` when the - * package does not match the expected OCI grammar — callers decide how to - * react (warn-and-skip when the entry is disabled, throw when it is enabled). - * Mirrors the `match.group(1)` / `match.group(4)` access pattern used by - * `pre_merge_oci_disabled_state` in the Python implementation. - */ -export function tryParseOciRegistryAndPath( - pkg: string, -): { registry: string; path: string | null } | null { - const m = OCI_REGEX.exec(pkg); - if (!m) return null; - return { registry: m[1] as string, path: m[4] ?? null }; -} - -function escape(s: string): string { - return s.replaceAll(/[.*+?^${}()|[\]\\/]/g, String.raw`\$&`); -} diff --git a/scripts/install-dynamic-plugins/src/plugin-hash.ts b/scripts/install-dynamic-plugins/src/plugin-hash.ts deleted file mode 100644 index 2291c63908..0000000000 --- a/scripts/install-dynamic-plugins/src/plugin-hash.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { createHash } from 'node:crypto'; -import { statSync, existsSync, readFileSync } from 'node:fs'; -import * as path from 'node:path'; -import { type Plugin } from './types.js'; - -/** - * Compute the config-hash for a plugin, used to detect "already installed". - * - * The hash is byte-compatible with the Python implementation - * (`install-dynamic-plugins.py`): both versions - * - strip `pluginConfig` and `version` before hashing, - * - keep `last_modified_level` (the include-file precedence) inside the hash, - * - serialize via stable, sort-keyed JSON, - * - and use the same field names for local-package metadata - * (`_local_package_info`, `_package_json`, `_package_json_mtime`, - * `_directory_mtime`, `_not_found`, `_error`, `__mtime`). - * - * Cross-compat matters because an in-place upgrade from the Python script - * to this TS port should not trigger a full reinstall of every plugin on - * the first run. - */ -export function computePluginHash(plugin: Plugin): string { - const copy: Record = {}; - for (const [k, v] of Object.entries(plugin)) { - if (k === 'pluginConfig' || k === 'version' || k === 'plugin_hash') continue; - copy[k] = v; - } - if (plugin.package.startsWith('./')) { - copy['_local_package_info'] = localPackageInfo(plugin.package); - } - const serialized = stableStringify(copy); - return createHash('sha256').update(serialized).digest('hex'); -} - -type LocalPackageInfo = { - _package_json?: unknown; - _package_json_mtime?: number; - _directory_mtime?: number; - _not_found?: boolean; - _error?: string; - [key: string]: unknown; -}; - -/** - * Inspect a local package path and return the metadata included in the - * install hash. Field names and value formats match the Python helper - * `get_local_package_info` so the resulting hash is identical. - * - * Mtime is stored in seconds-since-epoch as a float — Python's - * `os.path.getmtime` returns float seconds, so we divide Node's - * millisecond value by 1000. - */ -function localPackageInfo(pkgPath: string): LocalPackageInfo { - const absPath = path.isAbsolute(pkgPath) ? pkgPath : path.join(process.cwd(), pkgPath.slice(2)); - const pj = path.join(absPath, 'package.json'); - if (!existsSync(pj)) { - try { - return { _directory_mtime: toSeconds(statSync(absPath).mtimeMs) }; - } catch { - return { _not_found: true }; - } - } - try { - const info: LocalPackageInfo = { - _package_json: JSON.parse(readFileSync(pj, 'utf8')), - _package_json_mtime: toSeconds(statSync(pj).mtimeMs), - }; - for (const lockFile of ['package-lock.json', 'yarn.lock']) { - const lockPath = path.join(absPath, lockFile); - if (existsSync(lockPath)) { - info[`_${lockFile}_mtime`] = toSeconds(statSync(lockPath).mtimeMs); - } - } - return info; - } catch (err) { - return { _error: (err as Error).message }; - } -} - -function toSeconds(mtimeMs: number): number { - return mtimeMs / 1000; -} - -/** - * Deterministic JSON stringification — sorts keys at every level and emits - * Python-style separators (`, ` between elements, `: ` between key/value) - * so the resulting string is byte-identical to Python's - * `json.dumps(..., sort_keys=True)`. Required for hash compatibility with - * the previous Python implementation. - * - * Uses an explicit code-point comparator (locale-independent, matches - * Python's default `sorted()` ordering on str keys). - */ -function compareCodePoint(a: string, b: string): number { - if (a < b) return -1; - if (a > b) return 1; - return 0; -} - -function stableStringify(value: unknown): string { - if (value === null || typeof value !== 'object') return JSON.stringify(value); - if (Array.isArray(value)) { - return `[${value.map(stableStringify).join(', ')}]`; - } - const obj = value as Record; - const entries = Object.keys(obj) - .sort(compareCodePoint) - .map(k => `${JSON.stringify(k)}: ${stableStringify(obj[k])}`); - return `{${entries.join(', ')}}`; -} diff --git a/scripts/install-dynamic-plugins/src/run.ts b/scripts/install-dynamic-plugins/src/run.ts deleted file mode 100644 index 40e94e750f..0000000000 --- a/scripts/install-dynamic-plugins/src/run.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { spawn, type SpawnOptions } from 'node:child_process'; -import { InstallException } from './errors.js'; - -export type RunResult = { - stdout: string; - stderr: string; -}; - -/** - * Execute a command, capturing stdout/stderr. Throws InstallException with full - * context (exit code, stderr) on non-zero exit. Matches the Python `run()` contract. - */ -export async function run( - cmd: string[], - errMsg: string, - options: SpawnOptions = {}, -): Promise { - if (cmd.length === 0) { - throw new InstallException(`${errMsg}: empty command`); - } - const [bin, ...args] = cmd as [string, ...string[]]; - return new Promise((resolve, reject) => { - const child = spawn(bin, args, { ...options, stdio: ['ignore', 'pipe', 'pipe'] }); - let stdout = ''; - let stderr = ''; - child.stdout?.on('data', (chunk: Buffer) => (stdout += chunk.toString())); - child.stderr?.on('data', (chunk: Buffer) => (stderr += chunk.toString())); - child.on('error', err => reject(new InstallException(`${errMsg}: ${err.message}`))); - child.on('close', code => { - if (code === 0) { - resolve({ stdout, stderr }); - } else { - const parts = [`${errMsg}: exit code ${code}`, `cmd: ${cmd.join(' ')}`]; - if (stderr.trim()) parts.push(`stderr: ${stderr.trim()}`); - reject(new InstallException(parts.join('\n'))); - } - }); - }); -} diff --git a/scripts/install-dynamic-plugins/src/skopeo.ts b/scripts/install-dynamic-plugins/src/skopeo.ts deleted file mode 100644 index 61b1d51be7..0000000000 --- a/scripts/install-dynamic-plugins/src/skopeo.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { spawn } from 'node:child_process'; -import { InstallException } from './errors.js'; -import { run } from './run.js'; -import { which } from './which.js'; - -/** - * Wrapper around the `skopeo` CLI with in-memory caching for `inspect` results. - * - * JS is single-threaded so the caches don't need locks, unlike the Python - * version. Multiple concurrent callers inspecting the same image still share - * the one in-flight request because we cache the promise, not just the value. - */ -export class Skopeo { - private readonly path: string; - private readonly inspectRawCache = new Map>(); - private readonly inspectCache = new Map>(); - private readonly existsCache = new Map>(); - - constructor(skopeoPath?: string) { - const resolved = skopeoPath ?? which('skopeo'); - if (!resolved) throw new InstallException('skopeo not found in PATH'); - this.path = resolved; - } - - async copy(src: string, dst: string): Promise { - await run( - [this.path, 'copy', '--override-os=linux', '--override-arch=amd64', src, dst], - `skopeo copy failed: ${src}`, - ); - } - - async inspectRaw(url: string): Promise { - const cached = this.inspectRawCache.get(url); - if (cached) return cached; - const pending = this.runInspect(url, true); - this.inspectRawCache.set(url, pending); - try { - return await pending; - } catch (err) { - this.inspectRawCache.delete(url); - throw err; - } - } - - async inspect(url: string): Promise { - const cached = this.inspectCache.get(url); - if (cached) return cached; - const pending = this.runInspect(url, false) as Promise; - this.inspectCache.set(url, pending); - try { - return await pending; - } catch (err) { - this.inspectCache.delete(url); - throw err; - } - } - - /** - * Returns true iff `skopeo inspect` succeeds; never throws. Result is - * memoized — subsequent calls for the same URL reuse the in-flight or - * resolved promise. This dedups the `resolveImage` registry probe across - * the many plugins that share the same OCI image (common for the RHDH - * plugin catalog). - */ - async exists(url: string): Promise { - const cached = this.existsCache.get(url); - if (cached) return cached; - const pending = new Promise(resolve => { - const child = spawn(this.path, ['inspect', '--no-tags', url], { stdio: 'ignore' }); - child.on('error', () => resolve(false)); - child.on('close', code => resolve(code === 0)); - }); - this.existsCache.set(url, pending); - return pending; - } - - private async runInspect(url: string, raw: boolean): Promise { - const args = ['inspect', '--no-tags', url]; - if (raw) args.splice(1, 0, '--raw'); // inspect --raw --no-tags - const { stdout } = await run([this.path, ...args], `skopeo inspect failed: ${url}`); - return JSON.parse(stdout); - } -} - -export type SkopeoInspect = { - Name?: string; - Digest?: string; - Labels?: Record; - [key: string]: unknown; -}; diff --git a/scripts/install-dynamic-plugins/src/tar-extract.ts b/scripts/install-dynamic-plugins/src/tar-extract.ts deleted file mode 100644 index 589fdd0c78..0000000000 --- a/scripts/install-dynamic-plugins/src/tar-extract.ts +++ /dev/null @@ -1,172 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; -import * as tar from 'tar'; -import { InstallException } from './errors.js'; -import { log } from './log.js'; -import { MAX_ENTRY_SIZE } from './types.js'; -import { isAllowedEntryType, isInside } from './util.js'; - -const PACKAGE_PREFIX = 'package/'; - -/** - * Extract a slice of an OCI layer tarball into `destination/pluginPath`. - * - * Mirrors the Python `extract_oci_plugin` with the same security guards: - * - reject absolute or `..`-containing plugin paths - * - enforce per-entry size limit (MAX_ENTRY_SIZE) against zip bombs - * - skip sym/hard links whose targets would escape `destination` - * - reject device files / FIFOs (`tar` `filter` only emits regular types) - * - * Uses streaming via `node-tar` — no full-archive read into memory. - */ -export async function extractOciPlugin( - tarball: string, - pluginPath: string, - destination: string, -): Promise { - assertSafePluginPath(pluginPath); - - const destAbs = path.resolve(destination); - const pluginDir = path.join(destAbs, pluginPath); - await fs.rm(pluginDir, { recursive: true, force: true }); - await fs.mkdir(destAbs, { recursive: true }); - - // Boundary-safe path prefix — prevents `plugin-one` from matching sibling - // directories with the same prefix (e.g., `plugin-one-evil/`). Uses POSIX - // semantics because `node-tar` always emits forward-slash entry paths - // regardless of host OS. - const pluginPathBoundary = pluginPath.endsWith('/') ? pluginPath : `${pluginPath}/`; - - // Errors thrown inside `tar` filter callbacks are sometimes swallowed by the - // parser; capture them in a closure and re-throw after extraction completes. - let pending: InstallException | null = null; - - await tar.x({ - file: tarball, - cwd: destAbs, - preservePaths: false, - filter: (filePath, entry) => { - if (pending) return false; - const stat = entry as tar.ReadEntry; - if (filePath !== pluginPath && !filePath.startsWith(pluginPathBoundary)) return false; - - if (stat.size > MAX_ENTRY_SIZE) { - pending = new InstallException(`Zip bomb detected in ${filePath}`); - return false; - } - if (stat.type === 'SymbolicLink' || stat.type === 'Link') { - const linkName = stat.linkpath ?? ''; - const linkTarget = path.resolve(destAbs, linkName); - if (!isInside(linkTarget, destAbs)) { - log( - `\t==> WARNING: skipping file containing link outside of the archive: ${filePath} -> ${linkName}`, - ); - return false; - } - } - if (!isAllowedEntryType(stat.type)) { - pending = new InstallException(`Disallowed tar entry type ${stat.type} for ${filePath}`); - return false; - } - return true; - }, - }); - - if (pending) throw pending; -} - -/** - * Extract an NPM tarball (`npm pack` output). Entries all start with `package/` - * which is stripped. Matches `extract_npm_package` in fast.py, including the - * realpath-based escape check for symlinks inside the archive. - * - * Returns the directory name (basename) the package was extracted into. - */ -export async function extractNpmPackage(archive: string): Promise { - if (!archive.endsWith('.tgz')) { - throw new InstallException(`Expected .tgz archive, got ${archive}`); - } - const pkgDir = archive.slice(0, -'.tgz'.length); - const pkgDirReal = path.resolve(pkgDir); - await fs.rm(pkgDir, { recursive: true, force: true }); - await fs.mkdir(pkgDir, { recursive: true }); - - let pending: InstallException | null = null; - - await tar.x({ - file: archive, - cwd: pkgDir, - preservePaths: false, - filter: (filePath, entry) => { - if (pending) return false; - const stat = entry as tar.ReadEntry; - if (stat.type === 'Directory') return false; - - if (stat.type === 'File') { - if (!filePath.startsWith(PACKAGE_PREFIX)) { - pending = new InstallException( - `NPM package archive does not start with 'package/' as it should: ${filePath}`, - ); - return false; - } - if (stat.size > MAX_ENTRY_SIZE) { - pending = new InstallException(`Zip bomb detected in ${filePath}`); - return false; - } - stat.path = filePath.slice(PACKAGE_PREFIX.length); - return true; - } - - if (stat.type === 'SymbolicLink' || stat.type === 'Link') { - const linkPath = stat.linkpath ?? ''; - if (!linkPath.startsWith(PACKAGE_PREFIX)) { - pending = new InstallException( - `NPM package archive contains a link outside of the archive: ${filePath} -> ${linkPath}`, - ); - return false; - } - stat.path = filePath.slice(PACKAGE_PREFIX.length); - stat.linkpath = linkPath.slice(PACKAGE_PREFIX.length); - const linkTarget = path.resolve(pkgDir, stat.linkpath); - if (!isInside(linkTarget, pkgDirReal)) { - pending = new InstallException( - `NPM package archive contains a link outside of the archive: ${stat.path} -> ${stat.linkpath}`, - ); - return false; - } - return true; - } - - pending = new InstallException( - `NPM package archive contains a non-regular file: ${filePath}`, - ); - return false; - }, - }); - - if (pending) throw pending; - - await fs.rm(archive, { force: true }); - return path.basename(pkgDirReal); -} - -/** - * Validate a plugin path against traversal attempts. Segment-based — a bare - * `..` substring in a filename (`my..plugin`) is allowed; a `..` path segment - * (`foo/../bar`) is not. Absolute paths, empty segments, and `.` segments are - * also rejected. - */ -function assertSafePluginPath(pluginPath: string): void { - if (path.isAbsolute(pluginPath)) { - throw new InstallException(`Invalid plugin path (absolute): ${pluginPath}`); - } - if (pluginPath.length === 0) { - throw new InstallException('Invalid plugin path (empty)'); - } - const segments = pluginPath.split(/[/\\]/); - for (const segment of segments) { - if (segment === '' || segment === '.' || segment === '..') { - throw new InstallException(`Invalid plugin path (path traversal detected): ${pluginPath}`); - } - } -} diff --git a/scripts/install-dynamic-plugins/src/types.ts b/scripts/install-dynamic-plugins/src/types.ts deleted file mode 100644 index f1b3dd7fa0..0000000000 --- a/scripts/install-dynamic-plugins/src/types.ts +++ /dev/null @@ -1,143 +0,0 @@ -export const PullPolicy = { - IF_NOT_PRESENT: 'IfNotPresent', - ALWAYS: 'Always', -} as const; - -export type PullPolicy = (typeof PullPolicy)[keyof typeof PullPolicy]; - -/** - * External schema — the fields a user may declare in `dynamic-plugins.yaml`. - * Keep this in sync with RHDH documentation. - */ -export type PluginSpec = { - package: string; - /** - * Recommended: Use `enabled` instead. - * When both `enabled` and `disabled` are present, `enabled` takes precedence. - */ - disabled?: boolean; - /** - * Whether the plugin is active. Preferred over `disabled` (positive logic). - * When both `enabled` and `disabled` are present, `enabled` takes precedence - * and a warning is logged. - */ - enabled?: boolean; - pullPolicy?: PullPolicy; - forceDownload?: boolean; - integrity?: string; - pluginConfig?: Record; -}; - -/** - * Internal plugin record. Extends the YAML schema with fields populated at - * runtime (`version` from the package string, `plugin_hash` for change - * detection, `last_modified_level` to track include-file precedence). - * - * The field name `last_modified_level` matches the Python implementation so - * the install hashes computed by `plugin-hash.ts` stay byte-compatible - * across the Python ↔ TS migration. Renaming it would force every existing - * dynamic-plugins-root to be re-installed on the first TS run. - */ -export type Plugin = PluginSpec & { - version?: string; - plugin_hash?: string; - last_modified_level?: number; -}; - -export type PluginMap = Record; - -export type DynamicPluginsConfig = { - includes?: string[]; - plugins?: PluginSpec[]; -}; - -export const DOCKER_PROTO = 'docker://'; -export const OCI_PROTO = 'oci://'; -/** - * Tag suffix that, by convention, opts an OCI plugin into `pullPolicy: Always` - * when no explicit policy is set — mirrors the Python script's behaviour and - * keeps the two implementations swappable. Always parsed in combination with - * the `!plugin-path` separator so a plugin tagged `:latest` (no plugin path) - * does not accidentally trigger. - */ -export const LATEST_TAG_MARKER = ':latest!'; -export const RHDH_REGISTRY = 'registry.access.redhat.com/rhdh/'; -export const RHDH_FALLBACK = 'quay.io/rhdh/'; -export const CONFIG_HASH_FILE = 'dynamic-plugin-config.hash'; -export const IMAGE_HASH_FILE = 'dynamic-plugin-image.hash'; -export const DPDY_FILENAME = 'dynamic-plugins.default.yaml'; -export const LOCK_FILENAME = 'install-dynamic-plugins.lock'; -export const GLOBAL_CONFIG_FILENAME = 'app-config.dynamic-plugins.yaml'; - -const DEFAULT_MAX_ENTRY_SIZE = 40_000_000; - -/** - * Parse the MAX_ENTRY_SIZE env var, falling back to the default when unset, - * non-numeric, or < 1. Exported for unit tests — the `MAX_ENTRY_SIZE` constant - * below is the module-level value used by the extractors. - */ -export function parseMaxEntrySize(raw: string | undefined = process.env.MAX_ENTRY_SIZE): number { - if (!raw) return DEFAULT_MAX_ENTRY_SIZE; - const n = Number.parseInt(raw, 10); - return Number.isFinite(n) && n >= 1 ? n : DEFAULT_MAX_ENTRY_SIZE; -} - -export const MAX_ENTRY_SIZE = parseMaxEntrySize(); -export const RECOGNIZED_ALGORITHMS = ['sha512', 'sha384', 'sha256'] as const; -export type Algorithm = (typeof RECOGNIZED_ALGORITHMS)[number]; - -/** - * Resolve the effective `pullPolicy` for an OCI plugin: an explicit policy - * wins, otherwise the convention is `Always` for `:latest!` images and - * `IfNotPresent` for everything else. Shared by the install pipeline and the - * "definitely no-op" pre-pass so the `:latest!` semantics live in one place. - */ -export function effectivePullPolicy(plugin: { pullPolicy?: PullPolicy; package: string }): PullPolicy { - if (plugin.pullPolicy) return plugin.pullPolicy; - return plugin.package.includes(LATEST_TAG_MARKER) ? PullPolicy.ALWAYS : PullPolicy.IF_NOT_PRESENT; -} - -/** - * Resolve the effective disabled state from the `enabled` and `disabled` - * fields on a plugin spec. Precedence rules (per RHIDP-11983): - * - * 1. When only `enabled` is set → `disabled = !enabled`. - * 2. When only `disabled` is set → use it directly (backward compat). - * 3. When both are set → `enabled` wins and a warning is emitted - * via the optional `warn` callback. - * 4. When neither is set → default to `false` (not disabled). - * - * The `warn` callback receives the warning message string. Pass `log` or - * leave it out for silent resolution (unit tests, hashing). - */ -export function isPluginDisabled( - plugin: { package: string; disabled?: boolean; enabled?: boolean }, - warn?: (msg: string) => void, -): boolean { - const hasEnabled = typeof plugin.enabled === 'boolean'; - const hasDisabled = typeof plugin.disabled === 'boolean'; - - if (plugin.enabled !== undefined && !hasEnabled) { - warn?.( - `WARNING: Plugin ${plugin.package} has non-boolean 'enabled: ${String(plugin.enabled)}'. ` + - `Expected true or false; ignoring the field.`, - ); - } - if (plugin.disabled !== undefined && !hasDisabled) { - warn?.( - `WARNING: Plugin ${plugin.package} has non-boolean 'disabled: ${String(plugin.disabled)}'. ` + - `Expected true or false; ignoring the field.`, - ); - } - - if (hasEnabled && hasDisabled) { - warn?.( - `WARNING: Plugin ${plugin.package} specifies both 'enabled' and 'disabled'. ` + - `The 'enabled' field takes precedence; please use only 'enabled'.`, - ); - return !plugin.enabled; - } - if (hasEnabled) return !plugin.enabled; - if (hasDisabled) return plugin.disabled === true; - return false; -} diff --git a/scripts/install-dynamic-plugins/src/util.ts b/scripts/install-dynamic-plugins/src/util.ts deleted file mode 100644 index a07b1fba40..0000000000 --- a/scripts/install-dynamic-plugins/src/util.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; -import type * as tar from 'tar'; - -/** Returns true when the file/directory exists; swallows all other errors. */ -export async function fileExists(filePath: string): Promise { - try { - await fs.access(filePath); - return true; - } catch { - return false; - } -} - -/** True when `childAbs` resolves to `parentAbs` or a path under it. */ -export function isInside(childAbs: string, parentAbs: string): boolean { - const normalized = parentAbs.endsWith(path.sep) ? parentAbs : parentAbs + path.sep; - return childAbs === parentAbs || childAbs.startsWith(normalized); -} - -/** Plain JS object test — excludes arrays, null, class instances with custom prototype. */ -export function isPlainObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -/** - * Allowed `tar` entry types: regular file kinds + directory + links. Anything - * else (character/block devices, FIFOs, unknown) is rejected by the tar filters. - */ -export function isAllowedEntryType(type: tar.ReadEntry['type']): boolean { - return ( - type === 'File' || - type === 'Directory' || - type === 'SymbolicLink' || - type === 'Link' || - type === 'OldFile' || - type === 'ContiguousFile' - ); -} - -/** - * Drop any entries from `installed` whose value (on-disk directory) matches - * `pluginPath`. Called after a successful install so stale hash entries for - * the same directory are not mistakenly removed by the cleanup phase. - */ -export function markAsFresh(installed: Map, pluginPath: string): void { - for (const [k, v] of installed) { - if (v === pluginPath) installed.delete(k); - } -} diff --git a/scripts/install-dynamic-plugins/src/which.ts b/scripts/install-dynamic-plugins/src/which.ts deleted file mode 100644 index 2333104c85..0000000000 --- a/scripts/install-dynamic-plugins/src/which.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { accessSync, constants } from 'node:fs'; -import * as path from 'node:path'; - -/** - * Minimal `which(1)` — returns the absolute path of `bin` if found on PATH - * and executable, otherwise `null`. Avoids a dependency on the `which` npm package. - */ -export function which(bin: string): string | null { - const pathEnv = process.env.PATH ?? ''; - const sep = process.platform === 'win32' ? ';' : ':'; - const exts = - process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';') : ['']; - for (const dir of pathEnv.split(sep)) { - if (!dir) continue; - for (const ext of exts) { - const full = path.join(dir, bin + ext); - try { - accessSync(full, constants.X_OK); - return full; - } catch { - /* next */ - } - } - } - return null; -} diff --git a/scripts/install-dynamic-plugins/tsconfig.json b/scripts/install-dynamic-plugins/tsconfig.json deleted file mode 100644 index 7170b35746..0000000000 --- a/scripts/install-dynamic-plugins/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "noUncheckedIndexedAccess": true, - "isolatedModules": true, - "esModuleInterop": true, - "skipLibCheck": true, - "types": ["node", "vitest/globals"], - "noEmit": true - }, - "include": ["src/**/*.ts", "__tests__/**/*.ts", "esbuild.config.mjs"], - "exclude": ["node_modules", "dist"] -} diff --git a/scripts/install-dynamic-plugins/vitest.config.ts b/scripts/install-dynamic-plugins/vitest.config.ts deleted file mode 100644 index 0eb6d24022..0000000000 --- a/scripts/install-dynamic-plugins/vitest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['__tests__/**/*.test.ts'], - testTimeout: 20000, - coverage: { - provider: 'v8', - reporter: ['text', 'lcov', 'html'], - include: ['src/**/*.ts'], - exclude: ['src/index.ts'], - }, - }, -}); From dd7b4b143d734d6a207e3f0cff9d0edace8800a7 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Thu, 11 Jun 2026 09:52:59 -0300 Subject: [PATCH 08/12] fix: move installer dep to root package.json so the bin survives stage 3 The Containerfile's build stage at line 175 wipes everything under dynamic-plugins/ except dist/, which deleted dynamic-plugins/node_modules/.bin/ along with everything else. The final stage then failed at runtime with exit code 127 because the bin wasn't there: /bin/sh: line 1: /opt/app-root/src/dynamic-plugins/node_modules/.bin/install-dynamic-plugins: No such file or directory Moves @red-hat-developer-hub/cli-module-install-dynamic-plugins from dynamic-plugins/package.json to the repo root package.json. The bin then survives the `yarn workspaces focus --all --production` at line 208 and lands at /opt/app-root/src/node_modules/.bin/ where the final stage's shim can reach it. Hermeto already prefetches yarn deps for the repo root (scripts/local-hermeto-build.sh:213, `{"type": "yarn", "path": "."}`), so no infra change. Also corrects the shim path: the published package uses the string form of `bin` ("bin/install-dynamic-plugins"), and yarn names the symlink after the package's base name (after the scope), so the actual bin lands at node_modules/.bin/cli-module-install-dynamic-plugins, not node_modules/.bin/install-dynamic-plugins. Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 16 +++-- dynamic-plugins/package.json | 3 - dynamic-plugins/yarn.lock | 100 --------------------------- package.json | 3 + yarn.lock | 107 +++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 110 deletions(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index 671ffd3dc1..924077294e 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -277,13 +277,15 @@ COPY $EXTERNAL_SOURCE_NESTED/LICENSE /licenses/ # Install the dynamic-plugins installer through yarn instead of fetching the # vendored .cjs from this repo. The `@red-hat-developer-hub/cli-module-install-dynamic-plugins` -# dependency is declared in `dynamic-plugins/package.json` and pulled in by the -# existing `yarn install --immutable` at line 151. Hermeto already prefetches -# yarn deps for `./dynamic-plugins` (see scripts/local-hermeto-build.sh:213), -# so this works in the hermetic Konflux build with no infra change. -# A build-time `--help` invocation surfaces a missing or renamed CLI entrypoint -# as a build failure instead of a pod-start failure. -RUN INSTALLER_BIN=/opt/app-root/src/dynamic-plugins/node_modules/.bin/install-dynamic-plugins \ +# dependency is declared in the root `package.json` so the bin survives the +# `yarn workspaces focus --all --production` step at line 208 (the earlier +# `dynamic-plugins/node_modules/` is wiped at line 175 to keep only `dist/`). +# Hermeto already prefetches yarn deps for the repo root (see +# scripts/local-hermeto-build.sh:213), so this works in the hermetic Konflux +# build with no infra change. A build-time `--help` invocation surfaces a +# missing or renamed CLI entrypoint as a build failure instead of a pod-start +# failure. +RUN INSTALLER_BIN=/opt/app-root/src/node_modules/.bin/cli-module-install-dynamic-plugins \ && "$INSTALLER_BIN" --help >/dev/null \ && printf '%s\n' \ '#!/bin/sh' \ diff --git a/dynamic-plugins/package.json b/dynamic-plugins/package.json index 55a63cd3b1..fb5d306d12 100644 --- a/dynamic-plugins/package.json +++ b/dynamic-plugins/package.json @@ -22,9 +22,6 @@ "wrappers/*" ] }, - "dependencies": { - "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.2.0" - }, "devDependencies": { "@backstage/cli": "0.36.0", "@backstage/cli-defaults": "0.1.0", diff --git a/dynamic-plugins/yarn.lock b/dynamic-plugins/yarn.lock index 43764816af..2fdc89d0fd 100644 --- a/dynamic-plugins/yarn.lock +++ b/dynamic-plugins/yarn.lock @@ -3680,18 +3680,6 @@ __metadata: languageName: node linkType: hard -"@backstage/cli-common@npm:^0.2.2": - version: 0.2.2 - resolution: "@backstage/cli-common@npm:0.2.2" - dependencies: - "@backstage/errors": "npm:^1.3.1" - cross-spawn: "npm:^7.0.3" - global-agent: "npm:^3.0.0" - undici: "npm:^7.24.5" - checksum: 10c0/b83af32489662c1af7009d4a4965e5251cbe7e6bd12e5e14f2951e25d953872cba5802d9e6b780d0c93be95cdd9712e3ababeb2e97e34632b5cda6729f92a46d - languageName: node - linkType: hard - "@backstage/cli-defaults@npm:0.1.0, @backstage/cli-defaults@npm:^0.1.0": version: 0.1.0 resolution: "@backstage/cli-defaults@npm:0.1.0" @@ -4082,37 +4070,6 @@ __metadata: languageName: node linkType: hard -"@backstage/cli-node@npm:^0.3.2": - version: 0.3.2 - resolution: "@backstage/cli-node@npm:0.3.2" - dependencies: - "@backstage/cli-common": "npm:^0.2.2" - "@backstage/errors": "npm:^1.3.1" - "@backstage/types": "npm:^1.2.2" - "@manypkg/get-packages": "npm:^1.1.3" - "@yarnpkg/lockfile": "npm:^1.1.0" - "@yarnpkg/parsers": "npm:^3.0.0" - chalk: "npm:^4.0.0" - commander: "npm:^12.0.0" - fs-extra: "npm:^11.2.0" - keytar: "npm:^7.9.0" - pirates: "npm:^4.0.6" - proper-lockfile: "npm:^4.1.2" - semver: "npm:^7.5.3" - yaml: "npm:^2.0.0" - zod: "npm:^3.25.76 || ^4.0.0" - peerDependencies: - "@swc/core": ^1.15.6 - dependenciesMeta: - keytar: - optional: true - peerDependenciesMeta: - "@swc/core": - optional: true - checksum: 10c0/bcbf7097edeaf7ab4415b499126ab2592b889f89e13d65ff6b53f790dd024a33f1b386a08cd13346e766660c3e9486cb65ff96a2ec8300059c3837fa972b7ae5 - languageName: node - linkType: hard - "@backstage/cli@npm:0.36.0": version: 0.36.0 resolution: "@backstage/cli@npm:0.36.0" @@ -4480,16 +4437,6 @@ __metadata: languageName: node linkType: hard -"@backstage/errors@npm:^1.3.1": - version: 1.3.1 - resolution: "@backstage/errors@npm:1.3.1" - dependencies: - "@backstage/types": "npm:^1.2.2" - serialize-error: "npm:^8.0.1" - checksum: 10c0/b342551f5337f17bcc6d412868f6274509d657cc6037fbb5af96d42156c24c2419e72fd711136e42d05245bd4e5c5d8fe9cd54f89f260d9285a073c28cca0261 - languageName: node - linkType: hard - "@backstage/eslint-plugin@npm:^0.2.2": version: 0.2.2 resolution: "@backstage/eslint-plugin@npm:0.2.2" @@ -11501,20 +11448,6 @@ __metadata: languageName: node linkType: hard -"@red-hat-developer-hub/cli-module-install-dynamic-plugins@npm:0.2.0": - version: 0.2.0 - resolution: "@red-hat-developer-hub/cli-module-install-dynamic-plugins@npm:0.2.0" - dependencies: - "@backstage/cli-node": "npm:^0.3.2" - cleye: "npm:^2.6.0" - tar: "npm:^7.5.13" - yaml: "npm:^2.8.2" - bin: - cli-module-install-dynamic-plugins: bin/install-dynamic-plugins - checksum: 10c0/4a935c1a6b0c2df7a49bfc68891117f0c0b0b321622b332bf892fc57d524565f797ebf702fc5ce2403beb5babfb11f4417874cd046e27b6cff57e48603ffe748 - languageName: node - linkType: hard - "@redis/client@npm:^1.6.0": version: 1.6.1 resolution: "@redis/client@npm:1.6.1" @@ -17426,16 +17359,6 @@ __metadata: languageName: node linkType: hard -"cleye@npm:^2.6.0": - version: 2.6.0 - resolution: "cleye@npm:2.6.0" - dependencies: - terminal-columns: "npm:^2.0.0" - type-flag: "npm:^4.1.0" - checksum: 10c0/30bf1f11dec3ef191c79fecb94f4edf53507d60afb5aeb1736608c820315ef9b68451ac822475d313e17d8ab9ee79b614eb09a2fd7c5c3e7d8f4477a186793d7 - languageName: node - linkType: hard - "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -19497,7 +19420,6 @@ __metadata: dependencies: "@backstage/cli": "npm:0.36.0" "@backstage/cli-defaults": "npm:0.1.0" - "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "npm:0.2.0" turbo: "npm:2.9.6" dependenciesMeta: keytar: @@ -31465,19 +31387,6 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.5.13": - version: 7.5.16 - resolution: "tar@npm:7.5.16" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c - languageName: node - linkType: hard - "tar@npm:^7.5.4, tar@npm:^7.5.6": version: 7.5.9 resolution: "tar@npm:7.5.9" @@ -33882,15 +33791,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.8.2": - version: 2.9.0 - resolution: "yaml@npm:2.9.0" - bin: - yaml: bin.mjs - checksum: 10c0/f340718df45e97a9551b9bf9dac61c80050bc464513b710debfb5067c380c8472e3b67809cffacb4ab5ffb5e66ef9310816c88b05f371cec60abfedd8c88e0a2 - languageName: node - linkType: hard - "yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" diff --git a/package.json b/package.json index d0bf447023..c3615e445f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,9 @@ "plugins/*" ] }, + "dependencies": { + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.2.0" + }, "devDependencies": { "@backstage/cli": "0.36.0", "@backstage/cli-defaults": "0.1.0", diff --git a/yarn.lock b/yarn.lock index af6b16e7cb..ec071c12b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3063,6 +3063,18 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-common@npm:^0.2.2": + version: 0.2.2 + resolution: "@backstage/cli-common@npm:0.2.2" + dependencies: + "@backstage/errors": "npm:^1.3.1" + cross-spawn: "npm:^7.0.3" + global-agent: "npm:^3.0.0" + undici: "npm:^7.24.5" + checksum: 10c0/b83af32489662c1af7009d4a4965e5251cbe7e6bd12e5e14f2951e25d953872cba5802d9e6b780d0c93be95cdd9712e3ababeb2e97e34632b5cda6729f92a46d + languageName: node + linkType: hard + "@backstage/cli-defaults@npm:0.1.0, @backstage/cli-defaults@npm:^0.1.0": version: 0.1.0 resolution: "@backstage/cli-defaults@npm:0.1.0" @@ -3422,6 +3434,37 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-node@npm:^0.3.2": + version: 0.3.2 + resolution: "@backstage/cli-node@npm:0.3.2" + dependencies: + "@backstage/cli-common": "npm:^0.2.2" + "@backstage/errors": "npm:^1.3.1" + "@backstage/types": "npm:^1.2.2" + "@manypkg/get-packages": "npm:^1.1.3" + "@yarnpkg/lockfile": "npm:^1.1.0" + "@yarnpkg/parsers": "npm:^3.0.0" + chalk: "npm:^4.0.0" + commander: "npm:^12.0.0" + fs-extra: "npm:^11.2.0" + keytar: "npm:^7.9.0" + pirates: "npm:^4.0.6" + proper-lockfile: "npm:^4.1.2" + semver: "npm:^7.5.3" + yaml: "npm:^2.0.0" + zod: "npm:^3.25.76 || ^4.0.0" + peerDependencies: + "@swc/core": ^1.15.6 + dependenciesMeta: + keytar: + optional: true + peerDependenciesMeta: + "@swc/core": + optional: true + checksum: 10c0/bcbf7097edeaf7ab4415b499126ab2592b889f89e13d65ff6b53f790dd024a33f1b386a08cd13346e766660c3e9486cb65ff96a2ec8300059c3837fa972b7ae5 + languageName: node + linkType: hard + "@backstage/cli@npm:0.36.0": version: 0.36.0 resolution: "@backstage/cli@npm:0.36.0" @@ -3816,6 +3859,16 @@ __metadata: languageName: node linkType: hard +"@backstage/errors@npm:^1.3.1": + version: 1.3.1 + resolution: "@backstage/errors@npm:1.3.1" + dependencies: + "@backstage/types": "npm:^1.2.2" + serialize-error: "npm:^8.0.1" + checksum: 10c0/b342551f5337f17bcc6d412868f6274509d657cc6037fbb5af96d42156c24c2419e72fd711136e42d05245bd4e5c5d8fe9cd54f89f260d9285a073c28cca0261 + languageName: node + linkType: hard + "@backstage/eslint-plugin@npm:^0.2.2": version: 0.2.2 resolution: "@backstage/eslint-plugin@npm:0.2.2" @@ -13690,6 +13743,20 @@ __metadata: languageName: node linkType: hard +"@red-hat-developer-hub/cli-module-install-dynamic-plugins@npm:0.2.0": + version: 0.2.0 + resolution: "@red-hat-developer-hub/cli-module-install-dynamic-plugins@npm:0.2.0" + dependencies: + "@backstage/cli-node": "npm:^0.3.2" + cleye: "npm:^2.6.0" + tar: "npm:^7.5.13" + yaml: "npm:^2.8.2" + bin: + cli-module-install-dynamic-plugins: bin/install-dynamic-plugins + checksum: 10c0/4a935c1a6b0c2df7a49bfc68891117f0c0b0b321622b332bf892fc57d524565f797ebf702fc5ce2403beb5babfb11f4417874cd046e27b6cff57e48603ffe748 + languageName: node + linkType: hard + "@red-hat-developer-hub/plugin-utils@npm:1.0.0, @red-hat-developer-hub/plugin-utils@workspace:packages/plugin-utils": version: 0.0.0-use.local resolution: "@red-hat-developer-hub/plugin-utils@workspace:packages/plugin-utils" @@ -20443,6 +20510,16 @@ __metadata: languageName: node linkType: hard +"cleye@npm:^2.6.0": + version: 2.6.0 + resolution: "cleye@npm:2.6.0" + dependencies: + terminal-columns: "npm:^2.0.0" + type-flag: "npm:^4.1.0" + checksum: 10c0/30bf1f11dec3ef191c79fecb94f4edf53507d60afb5aeb1736608c820315ef9b68451ac822475d313e17d8ab9ee79b614eb09a2fd7c5c3e7d8f4477a186793d7 + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -35236,6 +35313,7 @@ __metadata: "@ianvs/prettier-plugin-sort-imports": "npm:4.7.1" "@jest/environment-jsdom-abstract": "npm:30.3.0" "@manypkg/cli": "npm:0.25.1" + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "npm:0.2.0" "@types/jest": "npm:30.0.0" glob: "npm:11.1.0" husky: "npm:8.0.3" @@ -37155,6 +37233,19 @@ __metadata: languageName: node linkType: hard +"tar@npm:^7.5.13": + version: 7.5.16 + resolution: "tar@npm:7.5.16" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c + languageName: node + linkType: hard + "tar@npm:^7.5.4, tar@npm:^7.5.6": version: 7.5.9 resolution: "tar@npm:7.5.9" @@ -38334,6 +38425,13 @@ __metadata: languageName: node linkType: hard +"undici@npm:^7.24.5": + version: 7.27.2 + resolution: "undici@npm:7.27.2" + checksum: 10c0/714632147c80eb8eda8a52df51b481d346df5e035ccc1d87eb3bbcb8f92ec25d7cbbe81abdeae5db4e37a93e490c8d2fa2359ecdca4b2c5c6c513dcd2626ad47 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -39777,6 +39875,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.8.2": + version: 2.9.0 + resolution: "yaml@npm:2.9.0" + bin: + yaml: bin.mjs + checksum: 10c0/f340718df45e97a9551b9bf9dac61c80050bc464513b710debfb5067c380c8472e3b67809cffacb4ab5ffb5e66ef9310816c88b05f371cec60abfedd8c88e0a2 + languageName: node + linkType: hard + "yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" From 48157ecaf6e871509776fc5f6cc6b03c0c83a162 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Thu, 11 Jun 2026 09:58:23 -0300 Subject: [PATCH 09/12] chore: drop pytest residue left by the Python install-dynamic-plugins era MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dynamic-plugins installer was migrated to TypeScript in #4574 and the vendored TypeScript copy was removed earlier in this PR, so the pytest test infrastructure no longer has anything live to test. - Deletes python/requirements-dev.in and python/requirements-dev.txt (pytest, pytest-cov, pytest-mock — and their transitive deps). - Drops the .github/workflows/pr.yaml "Install Python dependencies" step that installed all three requirements files; no subsequent step in the PR workflow uses Python. - Cleans the stale comment in codecov.yml referencing the removed install-dynamic-plugins pytest flag. python/requirements.txt and python/requirements-build.txt stay — they are consumed by the TechDocs venv build in the Containerfile (line 237) for mkdocs / mkdocs-techdocs-core / plantuml-markdown, which is unrelated. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/scheduled_tasks.lock | 1 + .github/workflows/pr.yaml | 7 ------- codecov.yml | 1 - python/requirements-dev.in | 11 ----------- python/requirements-dev.txt | 27 --------------------------- 5 files changed, 1 insertion(+), 46 deletions(-) create mode 100644 .claude/scheduled_tasks.lock delete mode 100644 python/requirements-dev.in delete mode 100644 python/requirements-dev.txt diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000000..05ca91083a --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"c39504b3-6fe0-497e-bfbf-1844023e4f0d","pid":70478,"procStart":"Fri May 29 12:25:37 2026","acquiredAt":1780082651914} \ No newline at end of file diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 06081a401d..954a02d0d6 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -112,13 +112,6 @@ jobs: with: cache-prefix: ${{ runner.os }}-${{ hashFiles('.nvmrc') }} - - name: Install Python dependencies - if: ${{ steps.check-image.outputs.is_skipped != 'true' }} - run: | - pip install -r python/requirements-dev.txt - pip install -r python/requirements-build.txt - pip install -r python/requirements.txt - - name: Run prettier if: ${{ steps.check-image.outputs.is_skipped != 'true' }} run: yarn prettier:check --continue --affected diff --git a/codecov.yml b/codecov.yml index 4bf93a58d3..f4507dae62 100644 --- a/codecov.yml +++ b/codecov.yml @@ -70,7 +70,6 @@ ignore: # Flags let us view coverage per area in the Codecov dashboard. # - rhdh: the main monorepo Jest/Backstage CLI run -# - install-dynamic-plugins: the pytest-based install script coverage # Additional flags (overlays-e2e-*, community-*) will be introduced by # the respective Stories under RHIDP-11866 and RHIDP-11865. flag_management: diff --git a/python/requirements-dev.in b/python/requirements-dev.in deleted file mode 100644 index dcea92451e..0000000000 --- a/python/requirements-dev.in +++ /dev/null @@ -1,11 +0,0 @@ -# Development dependencies for testing -# This file is not included in the production Docker image -# -# to use this file, `pip install pip-tools -U` first (need >= 7.3), then -# pip-compile --allow-unsafe --output-file=requirements-dev.txt --strip-extras requirements-dev.in - -# Testing framework -pytest>=8.0.0 -pytest-cov>=4.1.0 -pytest-mock>=3.12.0 - diff --git a/python/requirements-dev.txt b/python/requirements-dev.txt deleted file mode 100644 index 472c947334..0000000000 --- a/python/requirements-dev.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --output-file=requirements-dev.txt --strip-extras requirements-dev.in -# -coverage==7.13.2 - # via pytest-cov -iniconfig==2.3.0 - # via pytest -packaging==26.0 - # via pytest -pluggy==1.6.0 - # via - # pytest - # pytest-cov -pygments==2.19.2 - # via pytest -pytest==9.0.2 - # via - # -r requirements-dev.in - # pytest-cov - # pytest-mock -pytest-cov==7.0.0 - # via -r requirements-dev.in -pytest-mock==3.15.1 - # via -r requirements-dev.in From e936008f38bb13b6b3232640783e600bc840ce73 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Thu, 11 Jun 2026 09:58:38 -0300 Subject: [PATCH 10/12] chore: stop tracking and ignore the Claude Code session lock file `.claude/scheduled_tasks.lock` is a Claude Code runtime artifact (per-session pid lock); it got accidentally included by `git add -A` in the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/scheduled_tasks.lock | 1 - .gitignore | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index 05ca91083a..0000000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"c39504b3-6fe0-497e-bfbf-1844023e4f0d","pid":70478,"procStart":"Fri May 29 12:25:37 2026","acquiredAt":1780082651914} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1cfb300b24..6cc08142cb 100644 --- a/.gitignore +++ b/.gitignore @@ -100,4 +100,7 @@ e2e-tests/test-results/ # Hermeto Cache hermeto-cache/ -build/containerfiles/Containerfile.hermeto \ No newline at end of file +build/containerfiles/Containerfile.hermeto + +# Claude Code session lock +.claude/scheduled_tasks.lock From 2e704a2c70e4b97912927aed3b14fdfab1060568 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Thu, 11 Jun 2026 10:01:20 -0300 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94?= =?UTF-8?q?=20smoke=20check=20`install`,=20stop=20pointing=20at=20line=20n?= =?UTF-8?q?umbers,=20drop=20orphaned=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Build-time smoke check now invokes `install --help`, not just `--help`. A top-level `--help` only proves the bin resolves; the shim hardcodes the `install` subcommand, so if a future release of the package renames/removes it, the failure should land at image build time, not at pod start. cleye prints the subcommand help and exits 0 before validating the required `` positional, so this is a strict upgrade with no behavioural change for the current published package. - Replace hardcoded line-number references in the Containerfile comment (already drifted: line 175 → actual 173) with the step banners `=== YARN WORKSPACES FOCUS ===` and `=== DELETE DYNAMIC PLUGINS/* ===`, which survive edits to the surrounding RUN steps. Same treatment for scripts/local-hermeto-build.sh: point at the `{"type": "yarn", "path": "."}` fetch-deps entry instead of a line number. - Drop the orphaned `.dockerignore` comment that explained the now-removed `!scripts/install-dynamic-plugins/dist` re-include lines and was reading as if it described `**/node_modules`. - Drop the orphaned `.gitattributes` section header `# Generated bundles — collapsed in GitHub diffs, …` left behind when the `dist/install-dynamic-plugins.cjs linguist-generated=true` entry was removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .dockerignore | 2 -- .gitattributes | 2 -- build/containerfiles/Containerfile | 18 ++++++++++-------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.dockerignore b/.dockerignore index 98d2228c93..86ddcb34e6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,6 @@ .yarn/cache .yarn/install-state.gz **/dist -# Re-include the bundled install-dynamic-plugins entry point — copied by the -# runtime stage of build/containerfiles/Containerfile. **/node_modules plugins !plugins/auth-backend-module-oidc-provider diff --git a/.gitattributes b/.gitattributes index c74f76eb0d..a677605459 100644 --- a/.gitattributes +++ b/.gitattributes @@ -73,8 +73,6 @@ AUTHORS eol=lf ## These files are binary and should be left untouched # -# Generated bundles — collapsed in GitHub diffs, excluded from language stats - # (binary is a macro for -eol=lf -diff) *.png binary *.jpg binary diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index 924077294e..420b420894 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -278,15 +278,17 @@ COPY $EXTERNAL_SOURCE_NESTED/LICENSE /licenses/ # Install the dynamic-plugins installer through yarn instead of fetching the # vendored .cjs from this repo. The `@red-hat-developer-hub/cli-module-install-dynamic-plugins` # dependency is declared in the root `package.json` so the bin survives the -# `yarn workspaces focus --all --production` step at line 208 (the earlier -# `dynamic-plugins/node_modules/` is wiped at line 175 to keep only `dist/`). -# Hermeto already prefetches yarn deps for the repo root (see -# scripts/local-hermeto-build.sh:213), so this works in the hermetic Konflux -# build with no infra change. A build-time `--help` invocation surfaces a -# missing or renamed CLI entrypoint as a build failure instead of a pod-start -# failure. +# `=== YARN WORKSPACES FOCUS ===` production install (the earlier +# `=== DELETE DYNAMIC PLUGINS/* ===` step wipes `dynamic-plugins/node_modules/` +# to keep only `dist/`). Hermeto already prefetches yarn deps for the repo +# root (the `{"type": "yarn", "path": "."}` entry in the fetch-deps call in +# scripts/local-hermeto-build.sh), so this works in the hermetic Konflux +# build with no infra change. The build-time `install --help` invocation +# fails the build if either the bin disappears or the `install` subcommand +# the shim hardcodes is renamed/removed, instead of letting that surface as +# a pod-start failure. RUN INSTALLER_BIN=/opt/app-root/src/node_modules/.bin/cli-module-install-dynamic-plugins \ - && "$INSTALLER_BIN" --help >/dev/null \ + && "$INSTALLER_BIN" install --help >/dev/null \ && printf '%s\n' \ '#!/bin/sh' \ "exec $INSTALLER_BIN install \"\$@\"" \ From dc18ca3da08150cfaf4a22b59afa4489cc41c129 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Thu, 11 Jun 2026 10:08:13 -0300 Subject: [PATCH 12/12] fix: move installer dep to packages/backend to satisfy monorepo:check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sherif rule in `yarn run monorepo:check` rejects `dependencies` on the private root package.json — the rationale being that dependencies vs devDependencies is a no-op for a private package and creates confusion. CI fails on that check today. Moves `@red-hat-developer-hub/cli-module-install-dynamic-plugins` from the root package.json to `packages/backend/package.json`, keeping the load-bearing behaviour: backend is `private: true` and gets included by `yarn workspaces focus --all --production`, so the bin still hoists to `/opt/app-root/src/node_modules/.bin/cli-module-install-dynamic-plugins` where the Containerfile shim picks it up. Semantically this is the right home anyway — the backend is the runtime that the init-container runs alongside. Containerfile comment updated to point at the new declaration site. Verified locally: - yarn run monorepo:check → No issues found - node_modules/.bin/cli-module-install-dynamic-plugins install --help exits 0 Co-Authored-By: Claude Opus 4.7 (1M context) --- build/containerfiles/Containerfile | 20 ++++++++++---------- package.json | 3 --- packages/backend/package.json | 1 + yarn.lock | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index 420b420894..f0ff3ff504 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -277,16 +277,16 @@ COPY $EXTERNAL_SOURCE_NESTED/LICENSE /licenses/ # Install the dynamic-plugins installer through yarn instead of fetching the # vendored .cjs from this repo. The `@red-hat-developer-hub/cli-module-install-dynamic-plugins` -# dependency is declared in the root `package.json` so the bin survives the -# `=== YARN WORKSPACES FOCUS ===` production install (the earlier -# `=== DELETE DYNAMIC PLUGINS/* ===` step wipes `dynamic-plugins/node_modules/` -# to keep only `dist/`). Hermeto already prefetches yarn deps for the repo -# root (the `{"type": "yarn", "path": "."}` entry in the fetch-deps call in -# scripts/local-hermeto-build.sh), so this works in the hermetic Konflux -# build with no infra change. The build-time `install --help` invocation -# fails the build if either the bin disappears or the `install` subcommand -# the shim hardcodes is renamed/removed, instead of letting that surface as -# a pod-start failure. +# dependency is declared in `packages/backend/package.json` so the bin +# survives the `=== YARN WORKSPACES FOCUS ===` production install (the +# earlier `=== DELETE DYNAMIC PLUGINS/* ===` step wipes +# `dynamic-plugins/node_modules/` to keep only `dist/`). Hermeto already +# prefetches yarn deps for the repo root (the `{"type": "yarn", "path": "."}` +# entry in the fetch-deps call in scripts/local-hermeto-build.sh), so this +# works in the hermetic Konflux build with no infra change. The build-time +# `install --help` invocation fails the build if either the bin disappears +# or the `install` subcommand the shim hardcodes is renamed/removed, +# instead of letting that surface as a pod-start failure. RUN INSTALLER_BIN=/opt/app-root/src/node_modules/.bin/cli-module-install-dynamic-plugins \ && "$INSTALLER_BIN" install --help >/dev/null \ && printf '%s\n' \ diff --git a/package.json b/package.json index c3615e445f..d0bf447023 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,6 @@ "plugins/*" ] }, - "dependencies": { - "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.2.0" - }, "devDependencies": { "@backstage/cli": "0.36.0", "@backstage/cli-defaults": "0.1.0", diff --git a/packages/backend/package.json b/packages/backend/package.json index 49eded1253..97847eade2 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -75,6 +75,7 @@ "@opentelemetry/instrumentation-runtime-node": "0.30.0", "@opentelemetry/sdk-node": "0.218.0", "@red-hat-developer-hub/backstage-plugin-translations-backend": "0.3.1", + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.2.0", "app": "workspace:*", "app-next": "workspace:*", "better-sqlite3": "12.6.2", diff --git a/yarn.lock b/yarn.lock index ec071c12b4..fc6e49d67b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19424,6 +19424,7 @@ __metadata: "@opentelemetry/instrumentation-runtime-node": "npm:0.30.0" "@opentelemetry/sdk-node": "npm:0.218.0" "@red-hat-developer-hub/backstage-plugin-translations-backend": "npm:0.3.1" + "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "npm:0.2.0" "@types/express": "npm:4.17.25" "@types/global-agent": "npm:2.1.3" app: "workspace:*" @@ -35313,7 +35314,6 @@ __metadata: "@ianvs/prettier-plugin-sort-imports": "npm:4.7.1" "@jest/environment-jsdom-abstract": "npm:30.3.0" "@manypkg/cli": "npm:0.25.1" - "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "npm:0.2.0" "@types/jest": "npm:30.0.0" glob: "npm:11.1.0" husky: "npm:8.0.3"