This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new a9ed80e  release: generate + commit binary LICENSE/NOTICE, drift-check 
in CI
a9ed80e is described below

commit a9ed80ea437dcc48d82cacf44ab7f550b03cfcf1
Author: Wu Sheng <[email protected]>
AuthorDate: Thu May 21 08:32:00 2026 +0800

    release: generate + commit binary LICENSE/NOTICE, drift-check in CI
    
    The binary tarball's LICENSE/NOTICE were only ever generated into dist/
    (gitignored) at package time, and the collector silently produced ZERO
    packages — `pnpm list --prod` in dist/ returns nothing without a lockfile,
    so the binary LICENSE listed no bundled dependencies at all. It also never
    covered the UI deps Vite compiles into the static bundle.
    
    - Rework collect-dist-licenses.mjs to enumerate the full production tree of
      the bundled workspace packages (bff runtime + ui) straight from the pnpm
      store — 231 third-party packages incl. monaco/echarts/vue/dompurify/d3.
      Deterministic output (NOTICE year from the repo-root NOTICE; report has no
      timestamp). Adds --update-reference and --check modes.
    - Commit the generated binary LICENSE + NOTICE as the reviewed reference
      under dist-material/release-docs/ (next to the .tpl sources).
    - Add a human-reviewed license-overrides.json ([email protected]
      ships an MIT LICENSE file but omits the package.json field).
    - CI `dependency-license` now runs `pnpm licenses:bin:check` (regenerate +
      diff against the committed reference + ASF allow/deny) instead of a full
      `pnpm package`; release.sh verifies the same via --check before tarring.
    - New scripts: `pnpm licenses:bin:update` / `licenses:bin:check`.
---
 .github/workflows/ci.yaml                         |  37 +-
 dist-material/release-docs/LICENSE                | 474 ++++++++++++++++++++++
 dist-material/release-docs/NOTICE                 |  18 +
 dist-material/release-docs/license-overrides.json |   4 +
 package.json                                      |   2 +
 scripts/collect-dist-licenses.mjs                 | 192 ++++++---
 scripts/release.sh                                |   7 +-
 7 files changed, 655 insertions(+), 79 deletions(-)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 78a7659..d7f22a4 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -39,15 +39,18 @@ jobs:
         uses: apache/skywalking-eyes@5b7ee1731d036b5aac68f8bd3fc9e6f98ada082e
 
   dependency-license:
-    # Separate from `license-header` on purpose: header check is a quick
-    # static scan; dep-license collection requires a full production build
-    # (pnpm package → dist/node_modules) and then a walk of that tree to
-    # produce dist/LICENSE + dist/NOTICE + dist/licenses/. We also verify
-    # source vs. binary LICENSE/NOTICE differ in the expected way
-    # (only the binary carries the bundled-dep summary).
+    # The binary tarball bundles the full production dependency tree — the
+    # BFF runtime deps (dist/node_modules) AND the UI deps Vite compiles
+    # into the static bundle — so ASF policy requires every one of their
+    # licenses reproduced in the binary LICENSE/NOTICE.
+    # collect-dist-licenses.mjs enumerates that tree straight from the pnpm
+    # store (no `pnpm package` needed), and this job fails if either:
+    #   1. a dep carries a Category-X / unknown license (check-dist-licenses), 
or
+    #   2. the committed binary LICENSE/NOTICE reference is stale (--check) —
+    #      i.e. prod deps changed without `pnpm licenses:bin:update`.
     name: Dependency license
     runs-on: ubuntu-latest
-    timeout-minutes: 20
+    timeout-minutes: 15
     steps:
       - uses: actions/checkout@v6
         with:
@@ -58,30 +61,26 @@ jobs:
           node-version: '20'
           cache: 'pnpm'
       - run: pnpm install --frozen-lockfile
-      - run: pnpm package
-      - name: Collect bundled-dep LICENSE/NOTICE
-        run: node scripts/collect-dist-licenses.mjs
-      - name: Check bundled-dep licenses against ASF allow/deny
-        run: node scripts/check-dist-licenses.mjs
+      - name: Verify binary LICENSE/NOTICE (drift + ASF allow/deny)
+        run: pnpm licenses:bin:check
       - name: Compare source vs. binary LICENSE/NOTICE shape
         run: |
           # Source-flavored LICENSE = repo root; must NOT contain the
           # bundled-dep summary section.
           if grep -q 'Horizon UI Subcomponents' LICENSE; then
             echo "ERROR: repo-root LICENSE contains the bundled-dep summary 
section."
-            echo "       That section belongs only in dist/LICENSE (binary 
tarball)."
+            echo "       That section belongs only in the binary LICENSE."
             exit 1
           fi
-          # Binary-flavored LICENSE = generated; must contain the summary.
-          if ! grep -q 'Horizon UI Subcomponents' dist/LICENSE; then
-            echo "ERROR: dist/LICENSE missing 'Horizon UI Subcomponents' 
section."
-            echo "       collect-dist-licenses.mjs did not run or template 
drift."
+          # Committed binary-flavored LICENSE must contain the summary.
+          if ! grep -q 'Horizon UI Subcomponents' 
dist-material/release-docs/LICENSE; then
+            echo "ERROR: committed binary LICENSE missing 'Horizon UI 
Subcomponents'."
             exit 1
           fi
           # NOTICE: both must exist and start with the same ASF copyright line.
-          test -f NOTICE && test -f dist/NOTICE
+          test -f NOTICE && test -f dist-material/release-docs/NOTICE
           head -1 NOTICE | grep -q 'Apache SkyWalking Horizon UI'
-          head -1 dist/NOTICE | grep -q 'Apache SkyWalking Horizon UI'
+          head -1 dist-material/release-docs/NOTICE | grep -q 'Apache 
SkyWalking Horizon UI'
           echo "src/bin LICENSE+NOTICE shape OK."
       - name: Upload dependency report
         uses: actions/upload-artifact@v4
diff --git a/dist-material/release-docs/LICENSE 
b/dist-material/release-docs/LICENSE
new file mode 100644
index 0000000..2876052
--- /dev/null
+++ b/dist-material/release-docs/LICENSE
@@ -0,0 +1,474 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for describing the origin of the Work and
+      reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Support. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or support.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
+
+
+========================================================================
+   Apache SkyWalking Horizon UI Subcomponents:
+
+   The Apache SkyWalking Horizon UI binary distribution includes a number
+   of subcomponents with separate copyright notices and license terms.
+   Your use of the source code for these subcomponents is subject to the
+   terms and conditions of the licenses listed below. The full text of
+   each license is reproduced in the licenses/ directory of this
+   distribution.
+
+   By license family, the bundled third-party software is:
+========================================================================
+
+
+--- (MPL-2.0 OR Apache-2.0) ---
+
+  * [email protected] (licenses/dompurify-3.4.5/LICENSE)
+
+--- 0BSD ---
+
+  * [email protected] (licenses/tslib-2.3.0/LICENSE.txt)
+
+--- Apache-2.0 ---
+
+  * [email protected] (licenses/d3-flame-graph-4.1.3/LICENSE)
+  * [email protected] (licenses/echarts-6.0.0/LICENSE)
+  * [email protected] (licenses/typescript-5.6.3/LICENSE.txt)
+
+--- BSD-2-Clause ---
+
+  * [email protected] (licenses/entities-7.0.1/LICENSE)
+
+--- BSD-3-Clause ---
+
+  * [email protected] (licenses/d3-ease-3.0.1/LICENSE)
+  * [email protected] (licenses/fast-uri-3.1.2/LICENSE)
+  * [email protected] (licenses/ieee754-1.2.1/LICENSE)
+  * [email protected] (licenses/light-my-request-6.6.0/LICENSE)
+  * [email protected] (licenses/normalize-wheel-es-1.2.0/LICENSE)
+  * [email protected] (licenses/rw-1.3.3/LICENSE)
+  * [email protected] (licenses/secure-json-parse-2.7.0/LICENSE.md)
+  * [email protected] (licenses/secure-json-parse-4.1.0/LICENSE)
+  * [email protected] (licenses/source-map-js-1.2.1/LICENSE)
+  * [email protected] (licenses/zrender-6.0.0/LICENSE)
+
+--- BlueOak-1.0.0 ---
+
+  * [email protected] (licenses/glob-13.0.6/LICENSE.md)
+  * [email protected] (licenses/lru-cache-11.3.6/LICENSE.md)
+  * [email protected] (licenses/minimatch-10.2.5/LICENSE.md)
+  * [email protected] (licenses/minipass-7.1.3/LICENSE.md)
+  * [email protected] (licenses/path-scurry-2.0.2/LICENSE.md)
+
+--- ISC ---
+
+  * [email protected] (licenses/d3-7.9.0/LICENSE)
+  * [email protected] (licenses/d3-array-3.2.4/LICENSE)
+  * [email protected] (licenses/d3-axis-3.0.0/LICENSE)
+  * [email protected] (licenses/d3-brush-3.0.0/LICENSE)
+  * [email protected] (licenses/d3-chord-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-color-3.1.0/LICENSE)
+  * [email protected] (licenses/d3-contour-4.0.2/LICENSE)
+  * [email protected] (licenses/d3-delaunay-6.0.4/LICENSE)
+  * [email protected] (licenses/d3-dispatch-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-drag-3.0.0/LICENSE)
+  * [email protected] (licenses/d3-dsv-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-fetch-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-force-3.0.0/LICENSE)
+  * [email protected] (licenses/d3-format-3.1.2/LICENSE)
+  * [email protected] (licenses/d3-geo-3.1.1/LICENSE)
+  * [email protected] (licenses/d3-hierarchy-3.1.2/LICENSE)
+  * [email protected] (licenses/d3-interpolate-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-path-3.1.0/LICENSE)
+  * [email protected] (licenses/d3-polygon-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-quadtree-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-random-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-scale-4.0.2/LICENSE)
+  * [email protected] (licenses/d3-scale-chromatic-3.1.0/LICENSE)
+  * [email protected] (licenses/d3-selection-3.0.0/LICENSE)
+  * [email protected] (licenses/d3-shape-3.2.0/LICENSE)
+  * [email protected] (licenses/d3-time-3.1.0/LICENSE)
+  * [email protected] (licenses/d3-time-format-4.1.0/LICENSE)
+  * [email protected] (licenses/d3-timer-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-transition-3.0.1/LICENSE)
+  * [email protected] (licenses/d3-zoom-3.0.0/LICENSE)
+  * [email protected] (licenses/delaunator-5.1.0/LICENSE)
+  * [email protected] (licenses/fastq-1.20.1/LICENSE)
+  * [email protected] (licenses/inherits-2.0.4/LICENSE)
+  * [email protected] (licenses/internmap-2.0.3/LICENSE)
+  * [email protected] (licenses/once-1.4.0/LICENSE)
+  * [email protected] (licenses/picocolors-1.1.1/LICENSE)
+  * [email protected] (licenses/semver-7.8.0/LICENSE)
+  * [email protected] (licenses/setprototypeof-1.2.0/LICENSE)
+  * [email protected] (licenses/split2-4.2.0/LICENSE)
+  * [email protected]
+  * [email protected] (licenses/wrappy-1.0.2/LICENSE)
+  * [email protected] (licenses/yaml-2.9.0/LICENSE)
+
+--- MIT ---
+
+  * @babel/[email protected] 
(licenses/@babel__helper-string-parser-7.27.1/LICENSE)
+  * @babel/[email protected] 
(licenses/@babel__helper-validator-identifier-7.28.5/LICENSE)
+  * @babel/[email protected] (licenses/@babel__parser-7.29.3/LICENSE)
+  * @babel/[email protected] (licenses/@babel__types-7.29.0/LICENSE)
+  * @ctrl/[email protected] (licenses/@ctrl__tinycolor-4.2.0/LICENSE)
+  * @element-plus/[email protected] 
(licenses/@element-plus__icons-vue-2.3.2/LICENSE)
+  * @fastify/[email protected] 
(licenses/@fastify__accept-negotiator-2.0.1/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__ajv-compiler-4.0.5/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__cookie-11.0.2/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__error-4.2.0/LICENSE)
+  * @fastify/[email protected] 
(licenses/@fastify__fast-json-stringify-compiler-5.0.3/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__forwarded-3.0.1/LICENSE)
+  * @fastify/[email protected] 
(licenses/@fastify__merge-json-schemas-0.2.1/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__proxy-addr-5.1.0/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__send-4.1.0/LICENSE)
+  * @fastify/[email protected] (licenses/@fastify__static-9.1.3/LICENSE)
+  * @floating-ui/[email protected] (licenses/@floating-ui__core-1.7.5/LICENSE)
+  * @floating-ui/[email protected] (licenses/@floating-ui__dom-1.7.6/LICENSE)
+  * @floating-ui/[email protected] (licenses/@floating-ui__utils-0.2.11/LICENSE)
+  * @graphql-typed-document-node/[email protected] 
(licenses/@graphql-typed-document-node__core-3.2.0/LICENSE)
+  * @interactjs/[email protected] (licenses/@interactjs__actions-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__auto-scroll-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__auto-start-1.10.27/LICENSE)
+  * @interactjs/[email protected] (licenses/@interactjs__core-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__dev-tools-1.10.27/LICENSE)
+  * @interactjs/[email protected] (licenses/@interactjs__inertia-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__interact-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__interactjs-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__modifiers-1.10.27/LICENSE)
+  * @interactjs/[email protected] (licenses/@interactjs__offset-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__pointer-events-1.10.27/LICENSE)
+  * @interactjs/[email protected] (licenses/@interactjs__reflow-1.10.27/LICENSE)
+  * @interactjs/[email protected] 
(licenses/@interactjs__snappers-1.10.27/LICENSE)
+  * @interactjs/[email protected] (licenses/@interactjs__utils-1.10.27/LICENSE)
+  * @intlify/[email protected] (licenses/@intlify__core-base-10.0.8/LICENSE)
+  * @intlify/[email protected] 
(licenses/@intlify__message-compiler-10.0.8/LICENSE)
+  * @intlify/[email protected] (licenses/@intlify__shared-10.0.8/LICENSE)
+  * @jridgewell/[email protected] 
(licenses/@jridgewell__sourcemap-codec-1.5.5/LICENSE)
+  * @lukeed/[email protected] (licenses/@lukeed__ms-2.0.2/license)
+  * @phc/[email protected] (licenses/@phc__format-1.0.0/license)
+  * @pinojs/[email protected] (licenses/@pinojs__redact-0.4.0/LICENSE)
+  * @popperjs/[email protected] (licenses/@popperjs__core-2.11.8/LICENSE.md)
+  * @tanstack/[email protected] 
(licenses/@tanstack__match-sorter-utils-8.19.4/LICENSE)
+  * @tanstack/[email protected] 
(licenses/@tanstack__query-core-5.100.10/LICENSE)
+  * @tanstack/[email protected] 
(licenses/@tanstack__vue-query-5.100.10/LICENSE)
+  * @types/[email protected] (licenses/@types__lodash-4.17.24/LICENSE)
+  * @types/[email protected] (licenses/@types__lodash-es-4.17.12/LICENSE)
+  * @types/[email protected] (licenses/@types__trusted-types-2.0.7/LICENSE)
+  * @types/[email protected] (licenses/@types__web-bluetooth-0.0.20/LICENSE)
+  * @types/[email protected] (licenses/@types__web-bluetooth-0.0.21/LICENSE)
+  * @vue/[email protected] (licenses/@vue__compiler-core-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__compiler-dom-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__compiler-sfc-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__compiler-ssr-3.5.34/LICENSE)
+  * @vue/[email protected]
+  * @vue/[email protected] (licenses/@vue__reactivity-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__runtime-core-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__runtime-dom-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__server-renderer-3.5.34/LICENSE)
+  * @vue/[email protected] (licenses/@vue__shared-3.5.34/LICENSE)
+  * @vueuse/[email protected] (licenses/@vueuse__core-11.3.0/LICENSE)
+  * @vueuse/[email protected] (licenses/@vueuse__core-14.3.0/LICENSE)
+  * @vueuse/[email protected] (licenses/@vueuse__metadata-11.3.0/LICENSE)
+  * @vueuse/[email protected] (licenses/@vueuse__metadata-14.3.0/LICENSE)
+  * @vueuse/[email protected] (licenses/@vueuse__shared-11.3.0/LICENSE)
+  * @vueuse/[email protected] (licenses/@vueuse__shared-14.3.0/LICENSE)
+  * [email protected] (licenses/abort-controller-3.0.0/LICENSE)
+  * [email protected]
+  * [email protected] (licenses/ajv-8.20.0/LICENSE)
+  * [email protected] (licenses/ajv-formats-3.0.1/LICENSE)
+  * [email protected] (licenses/argon2-0.41.1/LICENSE)
+  * [email protected] (licenses/async-validator-4.2.5/LICENSE.md)
+  * [email protected] (licenses/atomic-sleep-1.0.0/LICENSE)
+  * [email protected] (licenses/avvio-9.2.0/LICENSE)
+  * [email protected] (licenses/balanced-match-4.0.4/LICENSE.md)
+  * [email protected] (licenses/base64-js-1.5.1/LICENSE)
+  * [email protected] (licenses/batch-processor-1.0.0/LICENSE)
+  * [email protected] (licenses/brace-expansion-5.0.6/LICENSE)
+  * [email protected] (licenses/buffer-6.0.3/LICENSE)
+  * [email protected] (licenses/chokidar-4.0.3/LICENSE)
+  * [email protected] (licenses/colorette-2.0.20/LICENSE.md)
+  * [email protected] (licenses/commander-7.2.0/LICENSE)
+  * [email protected] (licenses/content-disposition-1.1.0/LICENSE)
+  * [email protected] (licenses/cookie-1.1.1/LICENSE)
+  * [email protected] (licenses/csstype-3.2.3/LICENSE)
+  * [email protected] (licenses/dateformat-4.6.3/LICENSE)
+  * [email protected] (licenses/dayjs-1.11.20/LICENSE)
+  * [email protected] (licenses/depd-2.0.0/LICENSE)
+  * [email protected] (licenses/dequal-2.0.3/license)
+  * [email protected] (licenses/element-plus-2.14.0/LICENSE)
+  * [email protected] 
(licenses/element-resize-detector-1.2.4/LICENSE)
+  * [email protected] (licenses/end-of-stream-1.4.5/LICENSE)
+  * [email protected] (licenses/escape-html-1.0.3/LICENSE)
+  * [email protected] (licenses/estree-walker-2.0.2/LICENSE)
+  * [email protected] (licenses/event-target-shim-5.0.1/LICENSE)
+  * [email protected] (licenses/events-3.3.0/LICENSE)
+  * [email protected] (licenses/fast-copy-3.0.2/LICENSE)
+  * [email protected] 
(licenses/fast-decode-uri-component-1.0.1/LICENSE)
+  * [email protected] (licenses/fast-deep-equal-3.1.3/LICENSE)
+  * [email protected] (licenses/fast-json-stringify-6.4.0/LICENSE)
+  * [email protected] (licenses/fast-querystring-1.1.2/LICENSE)
+  * [email protected] (licenses/fast-safe-stringify-2.1.1/LICENSE)
+  * [email protected] (licenses/fastify-5.8.5/LICENSE)
+  * [email protected] (licenses/fastify-plugin-5.1.0/LICENSE)
+  * [email protected] (licenses/find-my-way-9.6.0/LICENSE)
+  * [email protected] (licenses/graphql-16.14.0/LICENSE)
+  * [email protected] (licenses/graphql-request-7.4.0/LICENSE)
+  * [email protected] (licenses/help-me-5.0.0/LICENSE)
+  * [email protected] (licenses/http-errors-2.0.1/LICENSE)
+  * [email protected] (licenses/iconv-lite-0.6.3/LICENSE)
+  * [email protected] (licenses/ipaddr.js-2.4.0/LICENSE)
+  * [email protected] (licenses/joycon-3.1.1/LICENSE)
+  * [email protected] 
(licenses/json-schema-ref-resolver-3.0.0/LICENSE)
+  * [email protected] (licenses/json-schema-traverse-1.0.0/LICENSE)
+  * [email protected] (licenses/ldapts-8.1.7/LICENSE)
+  * [email protected] (licenses/lodash-4.18.1/LICENSE)
+  * [email protected] (licenses/lodash-es-4.18.1/LICENSE)
+  * [email protected]
+  * [email protected] (licenses/magic-string-0.30.21/LICENSE)
+  * [email protected] (licenses/marked-14.0.0/LICENSE.md)
+  * [email protected] (licenses/memoize-one-6.0.0/LICENSE)
+  * [email protected] (licenses/mime-3.0.0/LICENSE)
+  * [email protected] (licenses/minimist-1.2.8/LICENSE)
+  * [email protected]
+  * [email protected] (licenses/monaco-editor-0.55.1/LICENSE)
+  * [email protected] (licenses/nanoid-3.3.12/LICENSE)
+  * [email protected] (licenses/node-addon-api-8.7.0/LICENSE.md)
+  * [email protected] (licenses/node-gyp-build-4.8.4/LICENSE)
+  * [email protected] (licenses/on-exit-leak-free-2.1.2/LICENSE)
+  * [email protected] (licenses/pinia-2.3.1/LICENSE)
+  * [email protected] (licenses/pino-9.14.0/LICENSE)
+  * [email protected] 
(licenses/pino-abstract-transport-2.0.0/LICENSE)
+  * [email protected] (licenses/pino-pretty-11.3.0/LICENSE)
+  * [email protected] (licenses/pino-std-serializers-7.1.0/LICENSE)
+  * [email protected] (licenses/postcss-8.5.14/LICENSE)
+  * [email protected] (licenses/process-0.11.10/LICENSE)
+  * [email protected] (licenses/process-warning-4.0.1/LICENSE)
+  * [email protected] (licenses/process-warning-5.0.0/LICENSE)
+  * [email protected] (licenses/pump-3.0.4/LICENSE)
+  * [email protected] 
(licenses/quick-format-unescaped-4.0.4/LICENSE)
+  * [email protected] (licenses/readable-stream-4.7.0/LICENSE)
+  * [email protected] (licenses/readdirp-4.1.2/LICENSE)
+  * [email protected] (licenses/real-require-0.2.0/LICENSE.md)
+  * [email protected] (licenses/remove-accents-0.5.0/LICENSE)
+  * [email protected] (licenses/require-from-string-2.0.2/license)
+  * [email protected] (licenses/ret-0.5.0/LICENSE)
+  * [email protected] (licenses/reusify-1.1.0/LICENSE)
+  * [email protected] (licenses/rfdc-1.4.1/LICENSE)
+  * [email protected] (licenses/safe-buffer-5.2.1/LICENSE)
+  * [email protected] (licenses/safe-regex2-5.1.1/LICENSE)
+  * [email protected] (licenses/safe-stable-stringify-2.5.0/LICENSE)
+  * [email protected] (licenses/safer-buffer-2.1.2/LICENSE)
+  * [email protected] (licenses/set-cookie-parser-2.7.2/LICENSE)
+  * [email protected] (licenses/sonic-boom-4.2.1/LICENSE)
+  * [email protected] (licenses/statuses-2.0.2/LICENSE)
+  * [email protected] (licenses/string_decoder-1.3.0/LICENSE)
+  * [email protected] (licenses/strip-json-comments-3.1.1/license)
+  * [email protected] (licenses/thread-stream-3.1.0/LICENSE)
+  * [email protected] (licenses/toad-cache-3.7.0/LICENSE)
+  * [email protected] (licenses/toidentifier-1.0.1/LICENSE)
+  * [email protected] (licenses/vue-3.5.34/LICENSE)
+  * [email protected] 
(licenses/vue-component-type-helpers-3.2.8/LICENSE)
+  * [email protected] (licenses/vue-demi-0.14.10/LICENSE)
+  * [email protected] (licenses/vue-grid-layout-3.0.0-beta1/LICENSE)
+  * [email protected] (licenses/vue-i18n-10.0.8/LICENSE)
+  * [email protected] (licenses/vue-router-4.6.4/LICENSE)
+  * [email protected] (licenses/zod-3.25.76/LICENSE)
+
+--- Unlicense ---
+
+  * [email protected] (licenses/robust-predicates-3.0.3/LICENSE)
diff --git a/dist-material/release-docs/NOTICE 
b/dist-material/release-docs/NOTICE
new file mode 100644
index 0000000..5fe353b
--- /dev/null
+++ b/dist-material/release-docs/NOTICE
@@ -0,0 +1,18 @@
+Apache SkyWalking Horizon UI
+Copyright 2026 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+========================================================================
+This binary distribution bundles third-party software, whose own NOTICE
+files (where present) are reproduced below verbatim.
+========================================================================
+
+------ [email protected] ------
+Apache ECharts
+Copyright 2017-2025 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (https://www.apache.org/).
+
diff --git a/dist-material/release-docs/license-overrides.json 
b/dist-material/release-docs/license-overrides.json
new file mode 100644
index 0000000..a41c71b
--- /dev/null
+++ b/dist-material/release-docs/license-overrides.json
@@ -0,0 +1,4 @@
+{
+  "_comment": "Human-asserted SPDX license for bundled deps whose package.json 
omits or mis-declares the `license` field. Each entry MUST be verified against 
the package's own LICENSE/COPYING file (reproduced under dist/licenses/) before 
being added. Keyed by `name@version`.",
+  "[email protected]": "MIT"
+}
diff --git a/package.json b/package.json
index 52d0b00..e7c233a 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,8 @@
     "test:unit": "pnpm -r run test:unit",
     "license:check": "license-eye -c .licenserc.yaml header check",
     "license:fix": "license-eye -c .licenserc.yaml header fix",
+    "licenses:bin:update": "node scripts/collect-dist-licenses.mjs 
--update-reference",
+    "licenses:bin:check": "node scripts/collect-dist-licenses.mjs --check && 
node scripts/check-dist-licenses.mjs",
     "package": "node scripts/package.mjs",
     "start": "HORIZON_CONFIG=${HORIZON_CONFIG:-./horizon.yaml} node 
dist/server.js"
   },
diff --git a/scripts/collect-dist-licenses.mjs 
b/scripts/collect-dist-licenses.mjs
index ed8700b..9d341de 100644
--- a/scripts/collect-dist-licenses.mjs
+++ b/scripts/collect-dist-licenses.mjs
@@ -16,25 +16,34 @@
  */
 
 /**
- * Walk the production `dist/node_modules/` tree built by
- * `scripts/package.mjs` and emit the binary-tar's LICENSE / NOTICE /
- * licenses/ subtree.
+ * Generate the BINARY distribution's LICENSE / NOTICE (+ per-dependency
+ * license texts) by enumerating the full PRODUCTION dependency tree the
+ * binary bundles:
  *
- * Apache distribution rule: every bundled third-party module's license
- * is reproduced in the binary tarball. The source tarball ships only
- * first-party source (top-level LICENSE + NOTICE are enough), but the
- * binary bundles npm dependencies and therefore needs an expanded LICENSE
- * (license-family summary) and NOTICE (third-party NOTICE pass-throughs)
- * plus per-package license texts under licenses/<name>-<version>/.
+ *   - the BFF runtime deps (shipped verbatim as dist/node_modules), AND
+ *   - the UI's production deps (compiled into dist/static by Vite).
  *
- * Output (relative to repo `dist/`):
- *   LICENSE                 — Apache-2.0 + grouped third-party summary
- *   NOTICE                  — ASF + concatenated third-party NOTICEs
- *   licenses/<pkg>-<ver>/   — verbatim LICENSE-ish files from each dep
- *   .dependency-report.json — { packages: [...] } for check-dist-licenses
+ * Both are redistributed in the binary tarball, so ASF distribution policy
+ * requires every one of their licenses to be reproduced. We read the tree
+ * via `pnpm list --prod` filtered to the bff + ui workspace packages — so
+ * this runs after a plain `pnpm install`, with NO `pnpm package` step. That
+ * lets CI regenerate and diff cheaply.
  *
- * Run after `pnpm package`. Re-runs are idempotent: the script clears
- * dist/licenses/ first.
+ * Output is deterministic: the NOTICE copyright year is read from the
+ * repo-root NOTICE (not the wall clock) and the dependency report carries no
+ * timestamp, so the committed reference is byte-stable across runs/years.
+ *
+ * Modes:
+ *   (default)            Write dist/LICENSE, dist/NOTICE, 
dist/licenses/<pkg>/,
+ *                        and dist/.dependency-report.json (consumed by
+ *                        check-dist-licenses.mjs and the release packager).
+ *   --update-reference   Also copy the generated LICENSE + NOTICE into
+ *                        dist-material/release-docs/ — the committed
+ *                        reference. Run after any production-dependency
+ *                        change, then commit the diff.
+ *   --check              Compare the freshly generated LICENSE + NOTICE
+ *                        against the committed reference and exit non-zero on
+ *                        any drift (the CI guard).
  */
 
 import { execSync } from 'node:child_process';
@@ -45,7 +54,6 @@ import {
   readFileSync,
   readdirSync,
   rmSync,
-  statSync,
   writeFileSync,
 } from 'node:fs';
 import { dirname, join, relative, resolve } from 'node:path';
@@ -54,16 +62,21 @@ import { fileURLToPath } from 'node:url';
 const __dirname = dirname(fileURLToPath(import.meta.url));
 const repoRoot = resolve(__dirname, '..');
 const distDir = resolve(repoRoot, 'dist');
-const nmDir = resolve(distDir, 'node_modules');
 const licensesOutDir = resolve(distDir, 'licenses');
 const templatesDir = resolve(repoRoot, 'dist-material/release-docs');
+// The committed binary LICENSE / NOTICE live next to their .tpl sources.
+const referenceDir = templatesDir;
 
-if (!existsSync(nmDir)) {
-  console.error(
-    `FATAL: ${nmDir} does not exist. Run \`pnpm package\` first.`,
-  );
-  process.exit(1);
-}
+const args = new Set(process.argv.slice(2));
+const CHECK = args.has('--check');
+const UPDATE_REFERENCE = args.has('--update-reference');
+
+// Workspace packages whose PRODUCTION dependencies end up in the binary
+// (bff → node_modules at runtime; ui → compiled into the static bundle).
+const BUNDLED_WORKSPACE_PACKAGES = [
+  '@skywalking-horizon-ui/bff',
+  '@skywalking-horizon-ui/ui',
+];
 
 const LICENSE_FILE_PATTERNS = [
   /^LICENSE$/i,
@@ -85,54 +98,68 @@ function pickFile(dir, patterns) {
   return null;
 }
 
-// Find every realpath-distinct package directory under dist/node_modules.
-// pnpm's layout puts true packages under 
`.pnpm/<pkg>@<ver>(_<peers>)/node_modules/<pkg>/`,
-// with top-level entries being symlinks into that store. We use `pnpm list`
-// to get the canonical production dep graph and resolve each entry's
-// realpath to get the package.json we should be reading.
+// Enumerate every third-party production package reachable from the bundled
+// workspace packages. `pnpm list --prod --depth Infinity` resolves each
+// entry to its real path in the pnpm store, so license files are readable
+// straight from there — no dist/node_modules required.
 function collectPackages() {
-  // `pnpm list --prod --depth Infinity --json` returns the realized
-  // production dep tree. We flatten it ourselves so first-party workspace
-  // packages can be filtered out by name prefix.
+  const filters = BUNDLED_WORKSPACE_PACKAGES.flatMap((p) => ['--filter', p]);
   const raw = execSync(
-    'pnpm list --prod --depth Infinity --json',
+    ['pnpm', 'list', '--prod', '--depth', 'Infinity', '--json', 
...filters].join(' '),
     {
-      cwd: distDir,
-      maxBuffer: 64 * 1024 * 1024,
+      cwd: repoRoot,
+      maxBuffer: 128 * 1024 * 1024,
       stdio: ['ignore', 'pipe', 'inherit'],
     },
   ).toString();
   const json = JSON.parse(raw);
-  // pnpm list returns an array of root packages. dist/ has exactly one.
-  const root = Array.isArray(json) ? json[0] : json;
+  const roots = Array.isArray(json) ? json : [json];
 
-  const seen = new Map(); // key: name@version → { path, name, version }
+  const seen = new Map(); // key: name@version → { name, version, path }
   function walk(deps) {
     if (!deps) return;
     for (const [name, info] of Object.entries(deps)) {
-      // Skip first-party workspace packages — they're our own code.
+      // First-party workspace packages are our own code — recurse past
+      // them so their third-party deps are still captured.
       if (name.startsWith('@skywalking-horizon-ui/')) {
         walk(info.dependencies);
         continue;
       }
       const version = info.version;
       const key = `${name}@${version}`;
-      if (seen.has(key)) continue;
-      const pkgPath = info.path;
-      if (!pkgPath || !existsSync(pkgPath)) {
-        console.warn(`WARN: package path missing for ${key}: ${pkgPath}`);
-        continue;
+      if (!seen.has(key)) {
+        const pkgPath = info.path;
+        if (!pkgPath || !existsSync(pkgPath)) {
+          console.warn(`WARN: package path missing for ${key}: ${pkgPath}`);
+        } else {
+          seen.set(key, { name, version, path: pkgPath });
+        }
       }
-      seen.set(key, { name, version, path: pkgPath });
       walk(info.dependencies);
     }
   }
-  walk(root.dependencies);
+  for (const root of roots) walk(root.dependencies);
+
   return Array.from(seen.values()).sort((a, b) =>
     a.name === b.name ? a.version.localeCompare(b.version) : 
a.name.localeCompare(b.name),
   );
 }
 
+// Human-asserted SPDX licenses for deps whose package.json omits or
+// mis-declares `license`. Verified against each package's own LICENSE file.
+const licenseOverrides = (() => {
+  const p = join(templatesDir, 'license-overrides.json');
+  if (!existsSync(p)) return {};
+  try {
+    const { _comment, ...rest } = JSON.parse(readFileSync(p, 'utf8'));
+    void _comment;
+    return rest;
+  } catch (e) {
+    console.warn(`WARN: cannot parse ${p}: ${e.message}`);
+    return {};
+  }
+})();
+
 function normalizeLicense(pkgJson) {
   const lic = pkgJson.license;
   if (typeof lic === 'string') return lic;
@@ -140,7 +167,6 @@ function normalizeLicense(pkgJson) {
     return lic.type;
   }
   if (Array.isArray(pkgJson.licenses)) {
-    // Deprecated form. Join SPDX-style.
     return pkgJson.licenses.map((l) => l.type || l).filter(Boolean).join(' OR 
');
   }
   return 'UNKNOWN';
@@ -157,9 +183,23 @@ function readPkgJson(pkgPath) {
   }
 }
 
+// Read the copyright year from the committed root NOTICE so the generated
+// binary NOTICE is reproducible (a wall-clock year would drift every Jan 1
+// and break the CI diff).
+function noticeYear() {
+  try {
+    const txt = readFileSync(resolve(repoRoot, 'NOTICE'), 'utf8');
+    const m = txt.match(/Copyright\s+(\d{4})/);
+    if (m) return m[1];
+  } catch {
+    /* fall through */
+  }
+  return String(new Date().getUTCFullYear());
+}
+
 const packages = collectPackages();
 
-// Reset output directory
+mkdirSync(distDir, { recursive: true });
 rmSync(licensesOutDir, { recursive: true, force: true });
 mkdirSync(licensesOutDir, { recursive: true });
 
@@ -170,7 +210,7 @@ const noticePieces = [];
 for (const pkg of packages) {
   const pj = readPkgJson(pkg.path);
   if (!pj) continue;
-  const license = normalizeLicense(pj);
+  const license = licenseOverrides[`${pkg.name}@${pkg.version}`] ?? 
normalizeLicense(pj);
   const homepage = pj.homepage || pj.repository?.url || pj.repository || '';
   const entry = {
     name: pkg.name,
@@ -200,10 +240,7 @@ for (const pkg of packages) {
     cpSync(noticeFile, dest);
     entry.noticeFile = relative(distDir, dest);
     noticePieces.push(
-      `------ ${pkg.name}@${pkg.version} ------\n${readFileSync(
-        noticeFile,
-        'utf8',
-      ).trim()}\n`,
+      `------ ${pkg.name}@${pkg.version} ------\n${readFileSync(noticeFile, 
'utf8').trim()}\n`,
     );
   }
 
@@ -229,9 +266,8 @@ writeFileSync(join(distDir, 'LICENSE'), licenseOut);
 
 // Render NOTICE.tpl → dist/NOTICE
 const noticeTpl = readFileSync(join(templatesDir, 'NOTICE.tpl'), 'utf8');
-const year = new Date().getUTCFullYear();
 const noticeOut = noticeTpl
-  .replace('{{ .Year }}', String(year))
+  .replace('{{ .Year }}', noticeYear())
   .replace(
     '{{ .Notices }}',
     noticePieces.length > 0
@@ -240,12 +276,12 @@ const noticeOut = noticeTpl
   );
 writeFileSync(join(distDir, 'NOTICE'), noticeOut);
 
-// Machine-readable report for the check step.
+// Machine-readable report for check-dist-licenses.mjs. No timestamp — the
+// file is an input to a deterministic diff, not an audit log.
 writeFileSync(
   join(distDir, '.dependency-report.json'),
   JSON.stringify(
     {
-      generatedAt: new Date().toISOString(),
       packageCount: report.length,
       packages: report,
       byLicense: Object.fromEntries(
@@ -254,10 +290,50 @@ writeFileSync(
     },
     null,
     2,
-  ),
+  ) + '\n',
 );
 
 console.log(
   `Wrote dist/LICENSE, dist/NOTICE, dist/licenses/ (${report.length} packages, 
` +
     `${sortedLicenses.length} license families).`,
 );
+
+// ── Reference sync / drift check ────────────────────────────────────────
+const refLicense = join(referenceDir, 'LICENSE');
+const refNotice = join(referenceDir, 'NOTICE');
+
+if (UPDATE_REFERENCE) {
+  cpSync(join(distDir, 'LICENSE'), refLicense);
+  cpSync(join(distDir, 'NOTICE'), refNotice);
+  console.log(
+    `Updated committed reference: ${relative(repoRoot, refLicense)}, 
${relative(repoRoot, refNotice)}.`,
+  );
+}
+
+if (CHECK) {
+  const drift = [];
+  for (const [label, generated, reference] of [
+    ['LICENSE', join(distDir, 'LICENSE'), refLicense],
+    ['NOTICE', join(distDir, 'NOTICE'), refNotice],
+  ]) {
+    if (!existsSync(reference)) {
+      drift.push(`${label}: committed reference ${relative(repoRoot, 
reference)} is missing.`);
+      continue;
+    }
+    if (readFileSync(generated, 'utf8') !== readFileSync(reference, 'utf8')) {
+      drift.push(`${label}: differs from ${relative(repoRoot, reference)}.`);
+    }
+  }
+  if (drift.length > 0) {
+    console.error('');
+    console.error('Binary LICENSE / NOTICE drift detected:');
+    for (const d of drift) console.error(`  - ${d}`);
+    console.error('');
+    console.error(
+      'Production dependencies changed without regenerating the committed 
reference.',
+    );
+    console.error('Run:  pnpm licenses:bin:update   then commit 
dist-material/release-docs/.');
+    process.exit(1);
+  }
+  console.log('Binary LICENSE / NOTICE match the committed reference.');
+}
diff --git a/scripts/release.sh b/scripts/release.sh
index d21f5a6..51b3b57 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -310,8 +310,11 @@ cd "${CLONE_DIR}"
 pnpm install --frozen-lockfile
 pnpm package
 # The packager left dist/server.js + dist/node_modules + dist/static + …
-# Now layer in LICENSE/NOTICE + per-dep license texts.
-node "${CLONE_DIR}/scripts/collect-dist-licenses.mjs"
+# Now layer in LICENSE/NOTICE + per-dep license texts. `--check` regenerates
+# dist/LICENSE + dist/NOTICE AND aborts if they drift from the committed
+# reference (dist-material/release-docs/) — so the binary ships exactly the
+# reviewed bytes. check-dist-licenses then enforces the ASF allow/deny list.
+node "${CLONE_DIR}/scripts/collect-dist-licenses.mjs" --check
 node "${CLONE_DIR}/scripts/check-dist-licenses.mjs"
 
 # Stage the binary contents under a clean folder name so the tar root


Reply via email to