mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-02 10:03:43 +00:00
Add license checks
This commit is contained in:
+25
-2
@@ -127,13 +127,36 @@ markup for it. The one that has already bitten us:
|
||||
left-aligned. **Add `text-start`.** `TestFullWidthFlexButtonsAreTextStart`, in
|
||||
the same file, enforces it.
|
||||
|
||||
### 5. CI is the gate, not the first line
|
||||
### 5. Third-party licensing is a gate — permissive only
|
||||
We ship a binary and an image, so every dependency has to be one whose license
|
||||
we can actually comply with. Dependencies stay permissive — copyleft terms
|
||||
(GPL, LGPL, AGPL, MPL, SSPL) would reach back and constrain how MeshTender
|
||||
itself may be licensed and distributed. The allowed set is `AllowedSPDX` in
|
||||
`internal/licenses/manifest.go`; adding to it is a legal decision, not a build
|
||||
fix. This applies to the test tree too.
|
||||
|
||||
- `mise run licenses` scans the Go module graph (binary + test + `browser` tag)
|
||||
with `google/licensecheck` **and** the non-Go manifest, then verifies
|
||||
`THIRD-PARTY-NOTICES.md` is current. `--update` regenerates it. CI gates on it
|
||||
(`.woodpecker/licenses.yaml`).
|
||||
- Anything third-party that Go tooling can't see — vendored front-end files,
|
||||
bundled code, icon artwork, the base image, external services — lives in
|
||||
`internal/licenses/manifest.go` with its version, SHA-256, upstream source, and
|
||||
committed license text. The tests there verify the declared SPDX ID against the
|
||||
actual text, pin file hashes, require attribution banners, and **fail if a file
|
||||
in `internal/web/static/` is neither declared nor listed as first-party**. Don't
|
||||
work around that last one — it's what stops a new library escaping the audit.
|
||||
- **Minifiers strip copyright banners; MIT and BSD require they stay.** When you
|
||||
update a vendored asset, keep or restore the banner (with the copyright line,
|
||||
not just the license name) and refresh the manifest hash.
|
||||
|
||||
### 6. CI is the gate, not the first line
|
||||
Woodpecker (`.woodpecker/`): `test` + `lint` + `vuln` (govulncheck) must pass
|
||||
before `build`; `deploy` follows `build` on `main`. A reachable CVE blocks the
|
||||
build. Catch problems locally first; if CI catches something you didn't, close the
|
||||
local-testing gap.
|
||||
|
||||
### 6. Git hygiene — stage one batch at a time, don't commit
|
||||
### 7. Git hygiene — stage one batch at a time, don't commit
|
||||
This is a single-developer project with no PR workflow. **Stage your changes but do
|
||||
NOT commit or push** — the developer commits from a git GUI. Stage exactly the
|
||||
files for one logical change with explicit `git add <file> …` (never `-A`/`.`).
|
||||
|
||||
@@ -15,6 +15,17 @@ run = "go run ./cmd/meshtender"
|
||||
[tasks.lint]
|
||||
run = "golangci-lint run"
|
||||
|
||||
# Audits every third-party dependency: scans the Go module graph (including
|
||||
# test-only and browser-tagged deps) plus the non-Go manifest in
|
||||
# internal/licenses, and fails if anything is not permissively licensed or if
|
||||
# THIRD-PARTY-NOTICES.md has drifted. Copyleft terms would constrain how
|
||||
# MeshTender itself may be licensed, so this is a licensing gate, not a lint.
|
||||
[tasks.licenses]
|
||||
usage = '''
|
||||
flag "-u --update" help="Rewrite THIRD-PARTY-NOTICES.md instead of only checking it"
|
||||
'''
|
||||
run = 'go run ./cmd/licenses ${usage_update:+--update}'
|
||||
|
||||
[tasks.seed]
|
||||
run = "go run ./cmd/meshtender --seed"
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Audits third-party dependency licensing on every push and pull request.
|
||||
#
|
||||
# Copyleft terms would reach back and constrain how MeshTender itself may be
|
||||
# licensed and distributed — so a GPL/LGPL/AGPL/MPL dependency is a licensing
|
||||
# conflict, not a style problem. This step scans the
|
||||
# whole Go module graph (binary, test-only, and browser-tagged) plus the non-Go
|
||||
# manifest in internal/licenses, and also fails when THIRD-PARTY-NOTICES.md has
|
||||
# drifted from what the dependencies actually are.
|
||||
#
|
||||
# The offline half of these checks also runs inside `go test ./...`; this step
|
||||
# adds the module-graph scan, which needs a module cache.
|
||||
when:
|
||||
- event: [push, pull_request]
|
||||
|
||||
steps:
|
||||
licenses:
|
||||
image: golang:1.26.5
|
||||
commands:
|
||||
- go run ./cmd/licenses
|
||||
|
||||
depends_on: []
|
||||
@@ -0,0 +1,581 @@
|
||||
# Third-Party Notices
|
||||
|
||||
This file covers the third-party software MeshTender depends on, all of
|
||||
which is permissively licensed. It makes no statement about MeshTender's
|
||||
own license.
|
||||
|
||||
**This file is generated. Do not edit it by hand** — run `mise run licenses --update`.
|
||||
Front-end and artwork entries come from `internal/licenses/manifest.go`; the Go
|
||||
module list is scanned from the module graph.
|
||||
|
||||
<!-- BEGIN GENERATED: assets — edit internal/licenses/manifest.go, then run `mise run licenses --update` -->
|
||||
|
||||
## Vendored front-end assets and artwork
|
||||
|
||||
The following third-party code and artwork is redistributed as part of
|
||||
MeshTender — compiled into the binary via `go:embed` and served to browsers.
|
||||
|
||||
### htmx 2.0.10 — 0BSD
|
||||
|
||||
- Homepage: <https://htmx.org>
|
||||
- Source: https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js
|
||||
- File: `internal/web/static/htmx.min.js` (sha256 `71ea67185bfa8c98c39d31717c6fce5d852370fcdfd129db4543774d3145c0de`)
|
||||
|
||||
```
|
||||
Zero-Clause BSD
|
||||
=============
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
||||
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
||||
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
```
|
||||
|
||||
### Leaflet 1.9.4 — BSD-2-Clause
|
||||
|
||||
- Homepage: <https://leafletjs.com>
|
||||
- Source: https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/
|
||||
- File: `internal/web/static/leaflet.js` (sha256 `db49d009c841f5ca34a888c96511ae936fd9f5533e90d8b2c4d57596f4e5641a`)
|
||||
- File: `internal/web/static/leaflet.css` (sha256 `498bd934faeb2cb455d6db2d9304d18d5aea69afe43fd2ac933c3f3753724617`)
|
||||
- Modified: leaflet.css carries a hand-restored @preserve banner; upstream ships the stylesheet without one. Body is byte-identical to upstream.
|
||||
|
||||
```
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2010-2023, Volodymyr Agafonkin
|
||||
Copyright (c) 2010-2011, CloudMade
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
### Leaflet-Geoman 2.20.0 — MIT
|
||||
|
||||
- Homepage: <https://geoman.io>
|
||||
- Source: https://cdn.jsdelivr.net/npm/@geoman-io/leaflet-geoman-free@2.20.0/dist/
|
||||
- File: `internal/web/static/leaflet-geoman.js` (sha256 `50bce5ec0c880d7edc912254f645aa77364fd6c29d66ef92296f855b8b615498`)
|
||||
- File: `internal/web/static/leaflet-geoman.css` (sha256 `51e45cbdf47dccb437bb34c9aa96b2017957a2471e17f41a72f4ec15a3b8c3f2`)
|
||||
- Modified: Both files carry hand-restored banners: the upstream esbuild bundle strips its own. Bodies are byte-identical to leaflet-geoman.min.js and leaflet-geoman.css upstream.
|
||||
- Note: This is the free MIT package (@geoman-io/leaflet-geoman-free). Geoman also sells a commercially licensed product — do not upgrade into it.
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Sumit Kumar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
### Leaflet.markercluster 1.5.3 — MIT
|
||||
|
||||
- Homepage: <https://github.com/Leaflet/Leaflet.markercluster>
|
||||
- Source: https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.3/dist/
|
||||
- File: `internal/web/static/leaflet.markercluster.js` (sha256 `b687c3bd8b9239b1dbe4bc4241c2940426cf15ca8543c73e5d4e31e3346fab25`)
|
||||
- File: `internal/web/static/leaflet.markercluster.css` (sha256 `882ea5266422a7ff57e5641f78a7e8464f81b575f0665634808d60ae6f5ed41d`)
|
||||
- Modified: The stylesheet is upstream MarkerCluster.css + MarkerCluster.Default.css concatenated, plus a banner; the script is upstream plus a banner. Both bodies are byte-identical to upstream.
|
||||
|
||||
```
|
||||
Copyright 2012 David Leaver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
### Tabler 1.4.0 — MIT
|
||||
|
||||
- Homepage: <https://tabler.io>
|
||||
- Source: https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/
|
||||
- File: `internal/web/static/tabler.min.js` (sha256 `b60c76160e97624574dbb8cf10abe6aee9a6493b60096fdfc15dd1dd2bd99eb9`)
|
||||
- File: `internal/web/static/tabler.min.css` (sha256 `7ef750bd10546a695d0b12767ad8048bd8f3ec5de7daefb1067f9d0daa3d1c9a`)
|
||||
- Note: These two files are byte-identical to the public MIT @tabler/core@1.4.0 npm artifacts, verified by SHA-256. Tabler's paid add-ons (Illustrations, Emails, Avatars) are a Personal License that forbids open-source redistribution — nothing from them may enter this repository. The license text here is from the tabler/tabler dev branch: upstream publishes no v1.4.0 git tag and the npm package ships no LICENSE file.
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2026 The Tabler Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
```
|
||||
|
||||
### Bootstrap 5.3.7 — MIT
|
||||
|
||||
- Homepage: <https://getbootstrap.com>
|
||||
- Source: bundled inside @tabler/core@1.4.0 dist/js/tabler.min.js
|
||||
- Note: Not vendored directly: Tabler's bundle embeds Bootstrap, which its own banner declares partway through tabler.min.js. It ships to every user, so it is attributed here.
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2025 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
```
|
||||
|
||||
### Tabler Icons — MIT
|
||||
|
||||
- Homepage: <https://tabler.io/icons>
|
||||
- Source: https://github.com/tabler/tabler-icons (icon path data, various versions)
|
||||
- File: `internal/web/templates/icons.html` (sha256 `cb067527ea3b67de4525ff2d44dc7591a19424fb4ae347f8dfcfe0b66bbdedaf`)
|
||||
- Modified: Icon path data copied into Go template definitions rather than vendored as SVG files; the transparent 24x24 guard path upstream emits is dropped.
|
||||
- Note: 45 of the 46 icons are Tabler Icons; several are renamed locally (antenna<-antenna-bars-5, copy<-squares, list<-list-details, plug<-plug-connected, terminal<-terminal-2, alert<-alert-triangle, brand-signal<-message-circle-2). Version is unpinned because the set was collected across releases. icon-logo is first-party MeshTender artwork, not Tabler's.
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2026 Paweł Kuna
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
## Base image and external services
|
||||
|
||||
These are not compiled into the binary. The base image is redistributed as
|
||||
part of the published container; the service is called by the browser at runtime.
|
||||
|
||||
### distroless static-debian12 — Apache-2.0
|
||||
|
||||
- Homepage: <https://github.com/GoogleContainerTools/distroless>
|
||||
- Source: gcr.io/distroless/static-debian12:nonroot (Dockerfile runtime stage)
|
||||
- Note: Runtime base image, redistributed as part of the published container. The distroless project is Apache-2.0; the image layer also carries Debian-packaged CA certificates and tzdata under their own upstream licenses (Mozilla's CA bundle is MPL-2.0, applying to the certificate data we redistribute unmodified, not to MeshTender).
|
||||
|
||||
### CARTO basemaps
|
||||
|
||||
- Homepage: <https://carto.com>
|
||||
- Source: https://{s}.basemaps.cartocdn.com (allowlisted in the CSP img-src)
|
||||
- Note: Raster map tiles fetched by the browser at runtime; no code is redistributed, so no license applies. Attribution ("(c) OpenStreetMap (c) CARTO") is rendered by meshmap.js and regionmap.js. Terms of use are CARTO's and are not verified by any test here — re-read them before relying on unauthenticated basemap access, especially for a commercially licensed deployment.
|
||||
|
||||
<!-- END GENERATED: assets -->
|
||||
|
||||
## Go modules
|
||||
|
||||
<!-- BEGIN GENERATED: go-modules — run `mise run licenses --update` -->
|
||||
|
||||
Go module dependencies, scanned from the module graph with
|
||||
[licensecheck](https://github.com/google/licensecheck). Each module's own
|
||||
license file is the authoritative text; the copyright lines below are
|
||||
reproduced from it to satisfy the attribution clauses.
|
||||
|
||||
### Linked into the MeshTender binary
|
||||
|
||||
Redistributed in compiled form. Their notices are reproduced here.
|
||||
|
||||
- **filippo.io/edwards25519** v1.2.0 — BSD-3-Clause
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/alexedwards/scs/pgxstore** v0.0.0-20251002162104-209de6e426de — MIT
|
||||
Copyright (c) 2016 Alex Edwards
|
||||
- **github.com/alexedwards/scs/v2** v2.9.0 — MIT
|
||||
Copyright (c) 2016 Alex Edwards
|
||||
- **github.com/andybalholm/brotli** v1.2.2 — MIT
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/aymerick/douceur** v0.2.0 — MIT
|
||||
Copyright (c) 2015 Aymerick JEHANNE
|
||||
- **github.com/brianvoe/gofakeit/v7** v7.15.0 — MIT
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
Copyright (c) [year] [fullname]
|
||||
- **github.com/coder/websocket** v1.8.15 — ISC
|
||||
Copyright (c) 2025 Coder
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
- **github.com/fxamacker/cbor/v2** v2.9.2 — MIT
|
||||
Copyright (c) 2019-present Faye Amacker
|
||||
- **github.com/go-chi/chi/v5** v5.3.1 — MIT
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc.
|
||||
- **github.com/go-viper/mapstructure/v2** v2.5.0 — MIT
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
- **github.com/go-webauthn/webauthn** v0.17.4 — BSD-3-Clause
|
||||
Copyright (c) 2025 github.com/go-webauthn/webauthn authors.
|
||||
- **github.com/go-webauthn/x** v0.2.6 — BSD-3-Clause
|
||||
Copyright (c) 2014 CloudFlare Inc.
|
||||
Copyright (c) 2021-2023 github.com/go-webauthn authors.
|
||||
- **github.com/golang-jwt/jwt/v5** v5.3.1 — MIT
|
||||
Copyright (c) 2012 Dave Grijalva
|
||||
Copyright (c) 2021 golang-jwt maintainers
|
||||
- **github.com/google/go-tpm** v0.9.8 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/google/uuid** v1.6.0 — BSD-3-Clause
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/gorilla/css** v1.0.1 — BSD-3-Clause
|
||||
Copyright (c) 2023 The Gorilla Authors. All rights reserved.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/jackc/pgpassfile** v1.0.0 — MIT
|
||||
Copyright (c) 2019 Jack Christensen
|
||||
- **github.com/jackc/pgservicefile** v0.0.0-20240606120523-5a60cdf6a761 — MIT
|
||||
Copyright (c) 2020 Jack Christensen
|
||||
- **github.com/jackc/pgx/v5** v5.10.0 — MIT
|
||||
Copyright (c) 2013-2021 Jack Christensen
|
||||
- **github.com/jackc/puddle/v2** v2.2.2 — MIT
|
||||
Copyright (c) 2018 Jack Christensen
|
||||
- **github.com/meshcore-go/meshcore-go** v1.0.9 — MIT
|
||||
Copyright (c) 2026 meshcore-go
|
||||
- **github.com/mfridman/interpolate** v0.0.2 — MIT
|
||||
Copyright (c) 2014-2017 Buildkite Pty Ltd
|
||||
Copyright (c) 2023 Michael Fridman
|
||||
- **github.com/microcosm-cc/bluemonday** v1.0.27 — BSD-3-Clause
|
||||
Copyright (c) 2014, David Kitchen <david@buro9.com>
|
||||
- **github.com/peterstace/simplefeatures** v0.59.0 — MIT
|
||||
Copyright (c) 2019 the contributors.
|
||||
- **github.com/philhofer/fwd** v1.2.0 — MIT
|
||||
Copyright (c) 2014-2015, Philip Hofer
|
||||
- **github.com/pressly/goose/v3** v3.27.2 — MIT
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
- **github.com/resend/resend-go/v3** v3.12.0 — MIT
|
||||
Copyright (c) 2023 Derich Pacheco
|
||||
- **github.com/sethvargo/go-retry** v0.3.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/skip2/go-qrcode** v0.0.0-20200617195104-da1b6568686e — MIT
|
||||
Copyright (c) 2014 Tom Harwood
|
||||
- **github.com/tinylib/msgp** v1.6.4 — MIT
|
||||
Copyright (c) 2014 Philip Hofer
|
||||
- **github.com/x448/float16** v0.8.4 — MIT
|
||||
Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker
|
||||
- **github.com/yuin/goldmark** v1.8.2 — MIT
|
||||
Copyright (c) 2019 Yusuke Inuzuka
|
||||
- **go.uber.org/multierr** v1.11.0 — MIT
|
||||
Copyright (c) 2017-2021 Uber Technologies, Inc.
|
||||
- **golang.org/x/crypto** v0.54.0 — BSD-3-Clause
|
||||
Copyright 2009 The Go Authors.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **golang.org/x/net** v0.57.0 — BSD-3-Clause
|
||||
Copyright 2009 The Go Authors.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **golang.org/x/sync** v0.22.0 — BSD-3-Clause
|
||||
Copyright 2009 The Go Authors.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **golang.org/x/sys** v0.47.0 — BSD-3-Clause
|
||||
Copyright 2009 The Go Authors.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **golang.org/x/text** v0.40.0 — BSD-3-Clause
|
||||
Copyright 2009 The Go Authors.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
|
||||
### Build, test, and tooling only
|
||||
|
||||
Not present in the shipped binary or container. Listed for completeness.
|
||||
|
||||
- **dario.cat/mergo** v1.0.2 — BSD-3-Clause
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2013 Dario Castañé. All rights reserved.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/cenkalti/backoff/v4** v4.3.0 — MIT
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
- **github.com/cespare/xxhash/v2** v2.3.0 — MIT
|
||||
Copyright (c) 2016 Caleb Spare
|
||||
- **github.com/chromedp/cdproto** v0.0.0-20260704091341-6ca7914c3938 — MIT
|
||||
Copyright (c) 2016-2025 Kenneth Shaw
|
||||
- **github.com/chromedp/chromedp** v0.15.1 — MIT
|
||||
Copyright (c) 2016-2025 Kenneth Shaw
|
||||
- **github.com/chromedp/sysutil** v1.1.0 — MIT
|
||||
Copyright (c) 2016-2017 Kenneth Shaw
|
||||
- **github.com/containerd/errdefs** v1.0.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright The containerd Authors
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/containerd/errdefs/pkg** v0.3.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright The containerd Authors
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/containerd/log** v0.1.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright The containerd Authors
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/containerd/platforms** v0.2.1 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright The containerd Authors
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/cpuguy83/dockercfg** v0.3.2 — MIT
|
||||
Copyright (c) 2020 Brian Goff
|
||||
- **github.com/davecgh/go-spew** v1.1.1 — ISC
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
- **github.com/distribution/reference** v0.6.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/docker/go-connections** v0.7.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2015 Docker, Inc.
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/docker/go-units** v0.5.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2015 Docker, Inc.
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/ebitengine/purego** v0.10.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/felixge/httpsnoop** v1.0.4 — MIT
|
||||
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
|
||||
- **github.com/go-json-experiment/json** v0.0.0-20260214004413-d219187c3433 — BSD-3-Clause
|
||||
Copyright (c) 2020 The Go Authors. All rights reserved.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/go-logr/logr** v1.4.3 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/go-logr/stdr** v1.2.2 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/gobwas/httphead** v0.1.0 — MIT
|
||||
Copyright (c) 2017 Sergey Kamardin
|
||||
- **github.com/gobwas/pool** v0.2.1 — MIT
|
||||
Copyright (c) 2017-2019 Sergey Kamardin <gobwas@gmail.com>
|
||||
- **github.com/gobwas/ws** v1.4.0 — MIT
|
||||
Copyright (c) 2017-2021 Sergey Kamardin <gobwas@gmail.com>
|
||||
- **github.com/google/licensecheck** v0.3.1 — BSD-3-Clause
|
||||
Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/klauspost/compress** v1.18.5 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2015 Klaus Post
|
||||
Copyright (c) 2015, Pierre Curto
|
||||
Copyright (c) 2016 Caleb Spare
|
||||
Copyright (c) 2016 Evan Huus
|
||||
Copyright (c) 2019 Klaus Post. All rights reserved.
|
||||
Copyright (c) 2023 Klaus Post
|
||||
Copyright 2016 The filepathx Authors
|
||||
Copyright 2016-2017 The New York Times Company
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/magiconair/properties** v1.8.10 — BSD-2-Clause
|
||||
Copyright (c) 2013-2020, Frank Schroeder
|
||||
- **github.com/moby/docker-image-spec** v1.3.1 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/go-archive** v0.2.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/moby/api** v1.55.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/moby/client** v0.5.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/patternmatcher** v0.6.1 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2012-2017 Docker, Inc.
|
||||
Copyright 2013-2018 Docker, Inc.
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/sys/sequential** v0.6.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/sys/user** v0.4.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/moby/term** v0.5.2 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2013-2018 Docker, Inc.
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/opencontainers/go-digest** v1.0.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2016 Docker, Inc.
|
||||
Copyright 2019, 2020 OCI Contributors
|
||||
copyright and certain other rights. Our licenses are
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
- **github.com/opencontainers/image-spec** v1.1.1 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2016 The Linux Foundation.
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **github.com/pmezard/go-difflib** v1.0.0 — BSD-3-Clause
|
||||
Copyright (c) 2013, Patrick Mezard
|
||||
- **github.com/shirou/gopsutil/v4** v4.26.5 — BSD-3-Clause
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2014, WAKAYAMA Shirou
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **github.com/sirupsen/logrus** v1.9.4 — MIT
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
- **github.com/stretchr/testify** v1.11.1 — MIT
|
||||
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
|
||||
- **github.com/testcontainers/testcontainers-go** v0.43.0 — MIT
|
||||
Copyright (c) 2017-2019 Gianluca Arbezzano
|
||||
- **github.com/testcontainers/testcontainers-go/modules/postgres** v0.43.0 — MIT
|
||||
Copyright (c) 2017-2019 Gianluca Arbezzano
|
||||
- **github.com/tklauser/go-sysconf** v0.3.16 — BSD-3-Clause
|
||||
Copyright (c) 2018-2022, Tobias Klauser
|
||||
- **go.opentelemetry.io/auto/sdk** v1.2.1 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
- **go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp** v0.68.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2009 The Go Authors.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **go.opentelemetry.io/otel** v1.43.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2009 The Go Authors.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **go.opentelemetry.io/otel/metric** v1.43.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2009 The Go Authors.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **go.opentelemetry.io/otel/trace** v1.43.0 — Apache-2.0
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
Copyright 2009 The Go Authors.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
copyright notice that is included in or attached to the work
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
- **gopkg.in/yaml.v3** v3.0.1 — Apache-2.0
|
||||
Copyright (c) 2006-2010 Kirill Simonov
|
||||
Copyright (c) 2006-2011 Kirill Simonov
|
||||
Copyright (c) 2011-2019 Canonical Ltd
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
copyright staring in 2011 when the project was ported over:
|
||||
|
||||
<!-- END GENERATED: go-modules -->
|
||||
@@ -0,0 +1,337 @@
|
||||
// Command licenses audits every third-party dependency and keeps
|
||||
// THIRD-PARTY-NOTICES.md current.
|
||||
//
|
||||
// It scans the Go module graph — resolving each module to its license file and
|
||||
// identifying that file with github.com/google/licensecheck — and combines the
|
||||
// result with the non-Go manifest in internal/licenses. Anything whose license
|
||||
// is not on the permissive allowlist fails the run: copyleft terms would reach
|
||||
// back and constrain how MeshTender itself may be licensed and distributed, so
|
||||
// they are excluded as a matter of policy rather than preference.
|
||||
//
|
||||
// Run it through mise:
|
||||
//
|
||||
// mise run licenses # check; non-zero exit on a problem or on drift
|
||||
// mise run licenses --update # rewrite THIRD-PARTY-NOTICES.md
|
||||
//
|
||||
// The Go-module half of the notices file can only be regenerated where a module
|
||||
// cache exists, which is why the offline test in internal/licenses checks the
|
||||
// manifest-derived half and CI runs this command for the rest.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"github.com/jleight/meshtender/internal/licenses"
|
||||
)
|
||||
|
||||
// minCoverage mirrors the threshold the package test uses.
|
||||
const minCoverage = 90.0
|
||||
|
||||
func main() {
|
||||
update := flag.Bool("update", false, "rewrite THIRD-PARTY-NOTICES.md instead of only checking it")
|
||||
flag.Parse()
|
||||
|
||||
if err := run(*update); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "licenses: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(update bool) error {
|
||||
root, err := repoRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mods, problems, err := scanModules(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inBinary := 0
|
||||
for _, m := range mods {
|
||||
if m.InBinary {
|
||||
inBinary++
|
||||
}
|
||||
}
|
||||
fmt.Printf("Go modules scanned: %d (%d linked into the binary, %d build/test only)\n",
|
||||
len(mods), inBinary, len(mods)-inBinary)
|
||||
fmt.Printf("Manifest entries (non-Go): %d\n", len(licenses.Deps))
|
||||
|
||||
for _, d := range licenses.Deps {
|
||||
if d.SPDX != "" && !licenses.AllowedSPDX[d.SPDX] {
|
||||
problems = append(problems, fmt.Sprintf("%s declares non-permissive %s", d.Label(), d.SPDX))
|
||||
}
|
||||
}
|
||||
|
||||
byLicense := map[string]int{}
|
||||
for _, m := range mods {
|
||||
byLicense[m.SPDX]++
|
||||
}
|
||||
var ids []string
|
||||
for id := range byLicense {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
fmt.Println("\nGo module licenses:")
|
||||
for _, id := range ids {
|
||||
fmt.Printf(" %-32s %d\n", id, byLicense[id])
|
||||
}
|
||||
|
||||
if len(problems) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "\nProblems:")
|
||||
for _, p := range problems {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", p)
|
||||
}
|
||||
return fmt.Errorf("%d dependency problem(s); every dependency must be "+
|
||||
"permissively licensed", len(problems))
|
||||
}
|
||||
|
||||
doc, err := licenses.Notices(licenses.GoSection(mods))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(root, licenses.NoticesPath)
|
||||
if update {
|
||||
if err := os.WriteFile(path, []byte(doc), 0o644); err != nil { //nolint:gosec // G306: THIRD-PARTY-NOTICES.md is a committed, world-readable document
|
||||
return fmt.Errorf("writing %s: %w", licenses.NoticesPath, err)
|
||||
}
|
||||
fmt.Printf("\nWrote %s\n", licenses.NoticesPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
existing, err := os.ReadFile(path) //nolint:gosec // G304: path is repoRoot() + a constant filename, not user input
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading %s: %w (run `mise run licenses --update`)", licenses.NoticesPath, err)
|
||||
}
|
||||
if string(existing) != doc {
|
||||
return fmt.Errorf("%s is out of date — run `mise run licenses --update` and commit the result",
|
||||
licenses.NoticesPath)
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s is current. All dependencies are permissively licensed.\n", licenses.NoticesPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func repoRoot() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||||
return dir, nil
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return "", errors.New("no go.mod found above the working directory")
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
// scanModules resolves every module in the build to its license. It asks the go
|
||||
// tool three questions: which modules the shipped binary links, which the tests
|
||||
// add, and which the browser-tagged e2e suite adds on top of that — so a
|
||||
// dependency cannot hide behind a build tag.
|
||||
func scanModules(root string) ([]licenses.GoModule, []string, error) {
|
||||
binary, err := listModules(root, []string{"list", "-deps", "-json", "./cmd/meshtender"})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("listing binary dependencies: %w", err)
|
||||
}
|
||||
all, err := listModules(root, []string{"list", "-deps", "-test", "-json", "./..."})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("listing all dependencies: %w", err)
|
||||
}
|
||||
browser, err := listModules(root, []string{"list", "-deps", "-test", "-tags", "browser", "-json", "./..."})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("listing browser-tagged dependencies: %w", err)
|
||||
}
|
||||
for path, dir := range browser {
|
||||
if _, ok := all[path]; !ok {
|
||||
all[path] = dir
|
||||
}
|
||||
}
|
||||
|
||||
var mods []licenses.GoModule
|
||||
var problems []string
|
||||
|
||||
paths := make([]string, 0, len(all))
|
||||
for path := range all {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
|
||||
for _, path := range paths {
|
||||
info := all[path]
|
||||
spdx, copyrights, err := identify(info.Dir)
|
||||
if err != nil {
|
||||
problems = append(problems, fmt.Sprintf("%s: %v", path, err))
|
||||
spdx = "UNKNOWN"
|
||||
}
|
||||
if spdx != "UNKNOWN" && !licenses.AllowedSPDX[spdx] {
|
||||
problems = append(problems, fmt.Sprintf("%s %s is %s, which is not permissive", path, info.Version, spdx))
|
||||
}
|
||||
_, shipped := binary[path]
|
||||
mods = append(mods, licenses.GoModule{
|
||||
Path: path,
|
||||
Version: info.Version,
|
||||
SPDX: spdx,
|
||||
Copyrights: copyrights,
|
||||
InBinary: shipped,
|
||||
})
|
||||
}
|
||||
|
||||
return mods, problems, nil
|
||||
}
|
||||
|
||||
type moduleInfo struct {
|
||||
Version string
|
||||
Dir string
|
||||
}
|
||||
|
||||
// listModules runs a `go list -json` invocation and collects the modules behind
|
||||
// the packages it reports, skipping the standard library and this module itself.
|
||||
func listModules(root string, args []string) (map[string]moduleInfo, error) {
|
||||
cmd := exec.Command("go", args...) //nolint:gosec // G204: args are the literal `go list` invocations in scanModules, never external input
|
||||
cmd.Dir = root
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
Module *struct {
|
||||
Path string
|
||||
Version string
|
||||
Dir string
|
||||
Main bool
|
||||
}
|
||||
}
|
||||
|
||||
mods := map[string]moduleInfo{}
|
||||
dec := json.NewDecoder(strings.NewReader(string(out)))
|
||||
for {
|
||||
var p pkg
|
||||
if err := dec.Decode(&p); err != nil {
|
||||
break
|
||||
}
|
||||
if p.Module == nil || p.Module.Main || p.Module.Dir == "" {
|
||||
continue
|
||||
}
|
||||
mods[p.Module.Path] = moduleInfo{Version: p.Module.Version, Dir: p.Module.Dir}
|
||||
}
|
||||
return mods, nil
|
||||
}
|
||||
|
||||
// identify finds a module's license file and reads its SPDX ID and copyright
|
||||
// lines out of it.
|
||||
func identify(dir string) (string, []string, error) {
|
||||
var best licensecheck.Coverage
|
||||
var bestIDs []string
|
||||
var copyrights []string
|
||||
found := false
|
||||
|
||||
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil //nolint:nilerr // an unreadable entry is not fatal to the scan
|
||||
}
|
||||
if d.IsDir() {
|
||||
switch d.Name() {
|
||||
case "testdata", "vendor", ".git":
|
||||
return fs.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !isLicenseFile(d.Name()) {
|
||||
return nil
|
||||
}
|
||||
b, readErr := os.ReadFile(p) //nolint:gosec // G304: p comes from WalkDir over the module cache directory
|
||||
if readErr != nil {
|
||||
return nil
|
||||
}
|
||||
found = true
|
||||
cov := licensecheck.Scan(b)
|
||||
if cov.Percent > best.Percent {
|
||||
best = cov
|
||||
bestIDs = nil
|
||||
for _, m := range cov.Match {
|
||||
bestIDs = append(bestIDs, m.ID)
|
||||
}
|
||||
}
|
||||
copyrights = append(copyrights, copyrightLines(string(b))...)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if !found {
|
||||
return "", nil, errors.New("no license file found in the module")
|
||||
}
|
||||
if best.Percent < minCoverage || len(bestIDs) == 0 {
|
||||
return "", dedupe(copyrights), fmt.Errorf("license not recognized (best coverage %.0f%%)", best.Percent)
|
||||
}
|
||||
|
||||
// Prefer a permissive match when a module offers a dual license.
|
||||
sort.Strings(bestIDs)
|
||||
for _, id := range bestIDs {
|
||||
if licenses.AllowedSPDX[id] {
|
||||
return id, dedupe(copyrights), nil
|
||||
}
|
||||
}
|
||||
return bestIDs[0], dedupe(copyrights), nil
|
||||
}
|
||||
|
||||
func isLicenseFile(name string) bool {
|
||||
u := strings.ToUpper(name)
|
||||
for _, suffix := range []string{".MD", ".TXT", ".CODE"} {
|
||||
u = strings.TrimSuffix(u, suffix)
|
||||
}
|
||||
return strings.HasPrefix(u, "LICENSE") || strings.HasPrefix(u, "LICENCE") ||
|
||||
strings.HasPrefix(u, "COPYING") || u == "NOTICE"
|
||||
}
|
||||
|
||||
func copyrightLines(text string) []string {
|
||||
var out []string
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
lower := strings.ToLower(line)
|
||||
if !strings.HasPrefix(lower, "copyright") && !strings.HasPrefix(lower, "(c)") {
|
||||
continue
|
||||
}
|
||||
// Skip the boilerplate sentence from MIT/BSD bodies, which is not a notice.
|
||||
if strings.Contains(lower, "above copyright notice") || strings.Contains(lower, "shall be included") {
|
||||
continue
|
||||
}
|
||||
if line != "" {
|
||||
out = append(out, line)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func dedupe(in []string) []string {
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, s := range in {
|
||||
if seen[s] {
|
||||
continue
|
||||
}
|
||||
seen[s] = true
|
||||
out = append(out, s)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
github.com/coder/websocket v1.8.15
|
||||
github.com/go-chi/chi/v5 v5.3.1
|
||||
github.com/go-webauthn/webauthn v0.17.4
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/meshcore-go/meshcore-go v1.0.9
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
|
||||
@@ -92,6 +92,8 @@ github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc=
|
||||
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc=
|
||||
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
||||
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
)
|
||||
|
||||
// minCoverage is how much of a license text licensecheck must recognize before
|
||||
// we trust the declared SPDX ID. Real license files score 97-100%; a truncated
|
||||
// or hand-mangled one scores far lower, which is exactly what we want to catch.
|
||||
const minCoverage = 90.0
|
||||
|
||||
// repoRoot walks up from the test's working directory to the module root.
|
||||
func repoRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd: %v", err)
|
||||
}
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||||
return dir
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
t.Fatal("could not find go.mod above the test directory")
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
// TestManifestEntriesAreWellFormed checks the shape of each entry, so the later
|
||||
// tests can assume the fields they need are populated.
|
||||
func TestManifestEntriesAreWellFormed(t *testing.T) {
|
||||
seen := map[string]bool{}
|
||||
for _, d := range Deps {
|
||||
if d.Name == "" {
|
||||
t.Fatal("a manifest entry has no Name")
|
||||
}
|
||||
if seen[d.Label()] {
|
||||
t.Errorf("%s: duplicate manifest entry", d.Label())
|
||||
}
|
||||
seen[d.Label()] = true
|
||||
|
||||
if d.Kind == "" {
|
||||
t.Errorf("%s: no Kind", d.Label())
|
||||
}
|
||||
if d.Source == "" {
|
||||
t.Errorf("%s: no Source — provenance must be recorded", d.Label())
|
||||
}
|
||||
|
||||
if d.ShipsCode() {
|
||||
if d.LicenseText == "" {
|
||||
t.Errorf("%s: ships code or artwork but declares no LicenseText", d.Label())
|
||||
}
|
||||
if d.SPDX == "" {
|
||||
t.Errorf("%s: ships code or artwork but declares no SPDX ID", d.Label())
|
||||
}
|
||||
} else {
|
||||
if d.LicenseText != "" {
|
||||
t.Errorf("%s: kind %q should not carry a license text", d.Label(), d.Kind)
|
||||
}
|
||||
if len(d.Files) != 0 {
|
||||
t.Errorf("%s: kind %q should not claim vendored files", d.Label(), d.Kind)
|
||||
}
|
||||
if d.Note == "" {
|
||||
t.Errorf("%s: kind %q must explain itself in Note", d.Label(), d.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
if d.Kind == KindAsset && len(d.Files) == 0 {
|
||||
t.Errorf("%s: declared as a vendored asset but claims no files", d.Label())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeclaredSPDXMatchesLicenseText is the core check: it reads the committed
|
||||
// license text and asserts licensecheck agrees with the SPDX ID we claim. This
|
||||
// is what makes the manifest evidence rather than an assertion — a wrong,
|
||||
// swapped, or truncated license text fails here.
|
||||
func TestDeclaredSPDXMatchesLicenseText(t *testing.T) {
|
||||
for _, d := range Deps {
|
||||
if !d.ShipsCode() {
|
||||
continue
|
||||
}
|
||||
t.Run(d.Label(), func(t *testing.T) {
|
||||
text, err := d.Text()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cov := licensecheck.Scan([]byte(text))
|
||||
if cov.Percent < minCoverage {
|
||||
t.Fatalf("license text recognized at only %.0f%% (want >= %.0f%%); is texts/%s truncated or modified?",
|
||||
cov.Percent, minCoverage, d.LicenseText)
|
||||
}
|
||||
var got []string
|
||||
for _, m := range cov.Match {
|
||||
if m.ID == d.SPDX {
|
||||
return // declared ID confirmed by the text
|
||||
}
|
||||
got = append(got, m.ID)
|
||||
}
|
||||
t.Fatalf("declares %s but texts/%s reads as %v (coverage %.0f%%)",
|
||||
d.SPDX, d.LicenseText, got, cov.Percent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAllDependenciesArePermissive guards the licensing model: copyleft terms
|
||||
// would constrain how MeshTender itself may be licensed and distributed, so no
|
||||
// dependency may carry them.
|
||||
func TestAllDependenciesArePermissive(t *testing.T) {
|
||||
for _, d := range Deps {
|
||||
if d.SPDX == "" {
|
||||
continue // services carry no license
|
||||
}
|
||||
if !AllowedSPDX[d.SPDX] {
|
||||
t.Errorf("%s declares %s, which is not on the permissive allowlist. "+
|
||||
"A copyleft dependency is a licensing conflict, not a build failure "+
|
||||
"to wave through.", d.Label(), d.SPDX)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVendoredFilesMatchAuditedHashes pins each file to the content that was
|
||||
// actually reviewed, so an upgrade cannot land without updating the manifest
|
||||
// (and therefore without re-checking the license and version).
|
||||
func TestVendoredFilesMatchAuditedHashes(t *testing.T) {
|
||||
root := repoRoot(t)
|
||||
for _, d := range Deps {
|
||||
for _, f := range d.Files {
|
||||
t.Run(f.Path, func(t *testing.T) {
|
||||
b, err := os.ReadFile(filepath.Join(root, f.Path))
|
||||
if err != nil {
|
||||
t.Fatalf("%s declares %s: %v", d.Label(), f.Path, err)
|
||||
}
|
||||
sum := sha256.Sum256(b)
|
||||
if got := hex.EncodeToString(sum[:]); got != f.SHA256 {
|
||||
t.Errorf("%s changed since it was audited for %s\n want %s\n got %s\n"+
|
||||
"If this was an intentional upgrade, re-check the upstream license and "+
|
||||
"version, then run `mise run licenses --update`.", f.Path, d.Label(), f.SHA256, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNoticeBearingFilesCarryAttribution enforces the actual legal obligation:
|
||||
// MIT and the BSD licenses require the copyright notice travel with copies, and
|
||||
// minifiers strip banners. Requiring the version string in the banner also
|
||||
// catches a file swapped for a different release.
|
||||
func TestNoticeBearingFilesCarryAttribution(t *testing.T) {
|
||||
const bannerWindow = 4096
|
||||
|
||||
root := repoRoot(t)
|
||||
for _, d := range Deps {
|
||||
for _, f := range d.Files {
|
||||
if !f.Notice {
|
||||
continue
|
||||
}
|
||||
t.Run(f.Path, func(t *testing.T) {
|
||||
b, err := os.ReadFile(filepath.Join(root, f.Path))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
head := string(b)
|
||||
if len(head) > bannerWindow {
|
||||
head = head[:bannerWindow]
|
||||
}
|
||||
lower := strings.ToLower(head)
|
||||
|
||||
if !strings.Contains(lower, "copyright") && !strings.Contains(lower, "(c)") {
|
||||
t.Errorf("%s carries no copyright notice in its first %d bytes; %s is %s and "+
|
||||
"requires the notice be retained in redistributed copies",
|
||||
f.Path, bannerWindow, d.Label(), d.SPDX)
|
||||
}
|
||||
if !strings.Contains(lower, strings.ToLower(d.Name)) {
|
||||
t.Errorf("%s has a banner that does not name %q", f.Path, d.Name)
|
||||
}
|
||||
if d.Version != "" && !strings.Contains(head, d.Version) {
|
||||
t.Errorf("%s has a banner that does not state version %s; either the banner is "+
|
||||
"stale or the file was upgraded without updating the manifest", f.Path, d.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEveryStaticFileIsAccountedFor is the check that keeps this manifest
|
||||
// honest over time. Without it, the next vendored library is simply absent from
|
||||
// the audit and nothing notices.
|
||||
func TestEveryStaticFileIsAccountedFor(t *testing.T) {
|
||||
root := repoRoot(t)
|
||||
staticDir := filepath.Join(root, "internal", "web", "static")
|
||||
|
||||
claimed := map[string]string{} // base name -> owning dependency
|
||||
for _, d := range Deps {
|
||||
for _, f := range d.Files {
|
||||
claimed[filepath.Base(f.Path)] = d.Label()
|
||||
}
|
||||
}
|
||||
firstParty := map[string]bool{}
|
||||
for _, name := range FirstPartyStatic {
|
||||
firstParty[name] = true
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(staticDir)
|
||||
if err != nil {
|
||||
t.Fatalf("reading %s: %v", staticDir, err)
|
||||
}
|
||||
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := e.Name()
|
||||
switch {
|
||||
case claimed[name] != "":
|
||||
// vendored and declared
|
||||
case firstParty[name]:
|
||||
// ours
|
||||
default:
|
||||
t.Errorf("internal/web/static/%s is neither declared in internal/licenses/manifest.go "+
|
||||
"nor listed in FirstPartyStatic. If it is third-party, add a manifest entry with its "+
|
||||
"license; if we wrote it, add it to FirstPartyStatic.", name)
|
||||
}
|
||||
}
|
||||
|
||||
// A stale FirstPartyStatic entry is worth knowing about too: it means a file
|
||||
// was deleted or renamed and the list drifted.
|
||||
present := map[string]bool{}
|
||||
for _, e := range entries {
|
||||
present[e.Name()] = true
|
||||
}
|
||||
for _, name := range FirstPartyStatic {
|
||||
if !present[name] {
|
||||
t.Errorf("FirstPartyStatic lists %q, which no longer exists in internal/web/static", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNoticesFileIsCurrent proves the published notices file still matches the
|
||||
// manifest. Only the assets half is checked here: the Go-module half needs the
|
||||
// module cache, so `mise run licenses` (and the CI step running it) owns that.
|
||||
func TestNoticesFileIsCurrent(t *testing.T) {
|
||||
root := repoRoot(t)
|
||||
path := filepath.Join(root, NoticesPath)
|
||||
|
||||
doc, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("reading %s: %v (run `mise run licenses --update`)", NoticesPath, err)
|
||||
}
|
||||
|
||||
got, err := Section(string(doc), AssetsBegin, AssetsEnd)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", NoticesPath, err)
|
||||
}
|
||||
want, err := AssetsSection()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != strings.TrimSpace(want) {
|
||||
t.Errorf("%s is out of date with internal/licenses/manifest.go — run `mise run licenses --update`", NoticesPath)
|
||||
}
|
||||
|
||||
goSection, err := Section(string(doc), GoBegin, GoEnd)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", NoticesPath, err)
|
||||
}
|
||||
if goSection == "" {
|
||||
t.Errorf("%s has an empty Go module section — run `mise run licenses --update`", NoticesPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNoticesUsesUnixLineEndings is a regression test. Leaflet's license file
|
||||
// ships with CRLF, and the generator used to copy those bytes straight into the
|
||||
// Markdown. The committed file then got normalized to LF by an editor, so it no
|
||||
// longer matched the generator's output and TestNoticesFileIsCurrent failed on
|
||||
// every run with no way to fix it by regenerating. A mixed-line-ending
|
||||
// generated file is a trap; assert it never comes back.
|
||||
func TestNoticesUsesUnixLineEndings(t *testing.T) {
|
||||
assets, err := AssetsSection()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(assets, "\r") {
|
||||
t.Error("AssetsSection emits a carriage return; embedded license text must be normalized to LF before it reaches the Markdown")
|
||||
}
|
||||
|
||||
doc, err := os.ReadFile(filepath.Join(repoRoot(t), NoticesPath))
|
||||
if err != nil {
|
||||
t.Fatalf("reading %s: %v", NoticesPath, err)
|
||||
}
|
||||
if bytes.Contains(doc, []byte("\r")) {
|
||||
t.Errorf("%s contains a carriage return — run `mise run licenses --update`", NoticesPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadmeVendoredVersionsMatchManifest keeps the README's version table from
|
||||
// drifting, since it is the second place a version is written down.
|
||||
func TestReadmeVendoredVersionsMatchManifest(t *testing.T) {
|
||||
root := repoRoot(t)
|
||||
b, err := os.ReadFile(filepath.Join(root, "README.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("reading README.md: %v", err)
|
||||
}
|
||||
readme := string(b)
|
||||
|
||||
for _, d := range Deps {
|
||||
if d.Kind != KindAsset || d.Version == "" {
|
||||
continue
|
||||
}
|
||||
// The table renders as: | [Name](url) | version | files |
|
||||
row := fmt.Sprintf("| %s |", d.Version)
|
||||
if !strings.Contains(readme, row) {
|
||||
t.Errorf("README.md has no vendored-assets table row stating version %s for %s; "+
|
||||
"the table and the manifest disagree", d.Version, d.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Package licenses is the manifest of every third-party dependency that Go
|
||||
// tooling cannot see: the front-end libraries vendored into
|
||||
// internal/web/static, third-party code bundled inside those libraries, icon
|
||||
// artwork copied into our templates, the container base image, and the one
|
||||
// external service the app talks to at runtime.
|
||||
//
|
||||
// Go modules are deliberately NOT listed here — the module graph already names
|
||||
// them and every module ships its own license file. `mise run licenses` scans
|
||||
// them and writes their section of the generated THIRD-PARTY-NOTICES.md.
|
||||
//
|
||||
// The tests in this package are what make the manifest load-bearing rather than
|
||||
// documentation: they verify each declared SPDX ID against the committed
|
||||
// license text (via github.com/google/licensecheck), that every declared file
|
||||
// still hashes to what was audited, that files needing an attribution notice
|
||||
// carry one, and — most importantly — that nothing third-party in
|
||||
// internal/web/static escapes the manifest entirely.
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// texts holds the verbatim upstream license text for each dependency. Fetch
|
||||
// these with `mise run licenses --refresh`; never hand-edit them, because the
|
||||
// tests scan them to confirm the declared SPDX ID is really what the text says.
|
||||
//
|
||||
//go:embed texts/*.txt
|
||||
var texts embed.FS
|
||||
|
||||
// Kind describes what sort of dependency an entry is, which decides which
|
||||
// checks apply to it.
|
||||
type Kind string
|
||||
|
||||
const (
|
||||
// KindAsset is a third-party file served to the browser out of
|
||||
// internal/web/static.
|
||||
KindAsset Kind = "asset"
|
||||
|
||||
// KindBundled is third-party code embedded inside another vendored asset.
|
||||
// It has no file of its own, so only its license text is checked — but it
|
||||
// still ships to users and still needs attribution.
|
||||
KindBundled Kind = "bundled"
|
||||
|
||||
// KindArtwork is third-party art (icon paths) copied into our templates
|
||||
// rather than vendored as a whole file.
|
||||
KindArtwork Kind = "artwork"
|
||||
|
||||
// KindImage is a container base image referenced by the Dockerfile. We
|
||||
// redistribute it as part of the published image.
|
||||
KindImage Kind = "image"
|
||||
|
||||
// KindService is an external service the app calls at runtime. It ships no
|
||||
// code, so there is no license to scan — only terms to point at.
|
||||
KindService Kind = "service"
|
||||
)
|
||||
|
||||
// File is one vendored file, pinned to the content that was audited.
|
||||
type File struct {
|
||||
// Path is relative to the repository root.
|
||||
Path string
|
||||
|
||||
// SHA256 is the hash of the file as committed. For most files that is
|
||||
// byte-identical to the upstream artifact; where our vendoring process
|
||||
// modifies it (a restored banner, a stripped sourceMappingURL comment, or
|
||||
// two upstream stylesheets concatenated), Dep.Modified says so.
|
||||
SHA256 string
|
||||
|
||||
// Notice marks a file that must carry a copyright banner naming the
|
||||
// dependency, because its license requires the notice travel with copies.
|
||||
Notice bool
|
||||
}
|
||||
|
||||
// Dep is one third-party dependency outside the Go module graph.
|
||||
type Dep struct {
|
||||
Name string
|
||||
Version string // empty when upstream publishes no version we can pin
|
||||
SPDX string // must agree with what licensecheck reads from LicenseText
|
||||
Homepage string
|
||||
Kind Kind
|
||||
|
||||
// Source is where the artifact came from, so provenance is a fact in the
|
||||
// repo rather than an investigation later.
|
||||
Source string
|
||||
|
||||
// LicenseText names a file in texts/. Required for everything that ships
|
||||
// code or art; empty for images and services.
|
||||
LicenseText string
|
||||
|
||||
// Files are the vendored files this dependency accounts for. Empty for
|
||||
// bundled code, images, and services.
|
||||
Files []File
|
||||
|
||||
// Modified explains how the committed files differ from upstream, or is
|
||||
// empty when they are byte-identical.
|
||||
Modified string
|
||||
|
||||
// Note carries anything a future reader needs: why an entry exists, what
|
||||
// terms apply, what to watch out for.
|
||||
Note string
|
||||
}
|
||||
|
||||
// Deps is the manifest. Adding a vendored library means adding it here — the
|
||||
// tests fail otherwise.
|
||||
var Deps = []Dep{
|
||||
{
|
||||
Name: "htmx",
|
||||
Version: "2.0.10",
|
||||
SPDX: "0BSD",
|
||||
Homepage: "https://htmx.org",
|
||||
Kind: KindAsset,
|
||||
Source: "https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js",
|
||||
LicenseText: "htmx-2.0.10.txt",
|
||||
// 0BSD imposes no attribution requirement at all, so Notice is false:
|
||||
// the upstream build ships no banner and none is owed.
|
||||
Files: []File{
|
||||
{Path: "internal/web/static/htmx.min.js", SHA256: "71ea67185bfa8c98c39d31717c6fce5d852370fcdfd129db4543774d3145c0de"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Leaflet",
|
||||
Version: "1.9.4",
|
||||
SPDX: "BSD-2-Clause",
|
||||
Homepage: "https://leafletjs.com",
|
||||
Kind: KindAsset,
|
||||
Source: "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/",
|
||||
LicenseText: "leaflet-1.9.4.txt",
|
||||
Files: []File{
|
||||
{Path: "internal/web/static/leaflet.js", SHA256: "db49d009c841f5ca34a888c96511ae936fd9f5533e90d8b2c4d57596f4e5641a", Notice: true},
|
||||
{Path: "internal/web/static/leaflet.css", SHA256: "498bd934faeb2cb455d6db2d9304d18d5aea69afe43fd2ac933c3f3753724617", Notice: true},
|
||||
},
|
||||
Modified: "leaflet.css carries a hand-restored @preserve banner; upstream ships the stylesheet without one. Body is byte-identical to upstream.",
|
||||
},
|
||||
{
|
||||
Name: "Leaflet-Geoman",
|
||||
Version: "2.20.0",
|
||||
SPDX: "MIT",
|
||||
Homepage: "https://geoman.io",
|
||||
Kind: KindAsset,
|
||||
Source: "https://cdn.jsdelivr.net/npm/@geoman-io/leaflet-geoman-free@2.20.0/dist/",
|
||||
LicenseText: "leaflet-geoman-free-2.20.0.txt",
|
||||
Files: []File{
|
||||
{Path: "internal/web/static/leaflet-geoman.js", SHA256: "50bce5ec0c880d7edc912254f645aa77364fd6c29d66ef92296f855b8b615498", Notice: true},
|
||||
{Path: "internal/web/static/leaflet-geoman.css", SHA256: "51e45cbdf47dccb437bb34c9aa96b2017957a2471e17f41a72f4ec15a3b8c3f2", Notice: true},
|
||||
},
|
||||
Modified: "Both files carry hand-restored banners: the upstream esbuild bundle strips its own. Bodies are byte-identical to leaflet-geoman.min.js and leaflet-geoman.css upstream.",
|
||||
Note: "This is the free MIT package (@geoman-io/leaflet-geoman-free). Geoman also sells a commercially licensed product — do not upgrade into it.",
|
||||
},
|
||||
{
|
||||
Name: "Leaflet.markercluster",
|
||||
Version: "1.5.3",
|
||||
SPDX: "MIT",
|
||||
Homepage: "https://github.com/Leaflet/Leaflet.markercluster",
|
||||
Kind: KindAsset,
|
||||
Source: "https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.3/dist/",
|
||||
LicenseText: "leaflet.markercluster-1.5.3.txt",
|
||||
Files: []File{
|
||||
{Path: "internal/web/static/leaflet.markercluster.js", SHA256: "b687c3bd8b9239b1dbe4bc4241c2940426cf15ca8543c73e5d4e31e3346fab25", Notice: true},
|
||||
{Path: "internal/web/static/leaflet.markercluster.css", SHA256: "882ea5266422a7ff57e5641f78a7e8464f81b575f0665634808d60ae6f5ed41d", Notice: true},
|
||||
},
|
||||
Modified: "The stylesheet is upstream MarkerCluster.css + MarkerCluster.Default.css concatenated, plus a banner; the script is upstream plus a banner. Both bodies are byte-identical to upstream.",
|
||||
},
|
||||
{
|
||||
Name: "Tabler",
|
||||
Version: "1.4.0",
|
||||
SPDX: "MIT",
|
||||
Homepage: "https://tabler.io",
|
||||
Kind: KindAsset,
|
||||
Source: "https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/",
|
||||
LicenseText: "tabler-1.4.0.txt",
|
||||
Files: []File{
|
||||
{Path: "internal/web/static/tabler.min.js", SHA256: "b60c76160e97624574dbb8cf10abe6aee9a6493b60096fdfc15dd1dd2bd99eb9", Notice: true},
|
||||
{Path: "internal/web/static/tabler.min.css", SHA256: "7ef750bd10546a695d0b12767ad8048bd8f3ec5de7daefb1067f9d0daa3d1c9a", Notice: true},
|
||||
},
|
||||
Note: "These two files are byte-identical to the public MIT @tabler/core@1.4.0 npm artifacts, verified by SHA-256. Tabler's paid add-ons (Illustrations, Emails, Avatars) are a Personal License that forbids open-source redistribution — nothing from them may enter this repository. The license text here is from the tabler/tabler dev branch: upstream publishes no v1.4.0 git tag and the npm package ships no LICENSE file.",
|
||||
},
|
||||
{
|
||||
Name: "Bootstrap",
|
||||
Version: "5.3.7",
|
||||
SPDX: "MIT",
|
||||
Homepage: "https://getbootstrap.com",
|
||||
Kind: KindBundled,
|
||||
Source: "bundled inside @tabler/core@1.4.0 dist/js/tabler.min.js",
|
||||
LicenseText: "bootstrap-5.3.7.txt",
|
||||
Note: "Not vendored directly: Tabler's bundle embeds Bootstrap, which its own banner declares partway through tabler.min.js. It ships to every user, so it is attributed here.",
|
||||
},
|
||||
{
|
||||
Name: "Tabler Icons",
|
||||
SPDX: "MIT",
|
||||
Homepage: "https://tabler.io/icons",
|
||||
Kind: KindArtwork,
|
||||
Source: "https://github.com/tabler/tabler-icons (icon path data, various versions)",
|
||||
LicenseText: "tabler-icons.txt",
|
||||
Files: []File{
|
||||
{Path: "internal/web/templates/icons.html", SHA256: "cb067527ea3b67de4525ff2d44dc7591a19424fb4ae347f8dfcfe0b66bbdedaf", Notice: true},
|
||||
},
|
||||
Modified: "Icon path data copied into Go template definitions rather than vendored as SVG files; the transparent 24x24 guard path upstream emits is dropped.",
|
||||
Note: "45 of the 46 icons are Tabler Icons; several are renamed locally (antenna<-antenna-bars-5, copy<-squares, list<-list-details, plug<-plug-connected, terminal<-terminal-2, alert<-alert-triangle, brand-signal<-message-circle-2). Version is unpinned because the set was collected across releases. icon-logo is first-party MeshTender artwork, not Tabler's.",
|
||||
},
|
||||
{
|
||||
Name: "distroless static-debian12",
|
||||
SPDX: "Apache-2.0",
|
||||
Homepage: "https://github.com/GoogleContainerTools/distroless",
|
||||
Kind: KindImage,
|
||||
Source: "gcr.io/distroless/static-debian12:nonroot (Dockerfile runtime stage)",
|
||||
Note: "Runtime base image, redistributed as part of the published container. The distroless project is Apache-2.0; the image layer also carries Debian-packaged CA certificates and tzdata under their own upstream licenses (Mozilla's CA bundle is MPL-2.0, applying to the certificate data we redistribute unmodified, not to MeshTender).",
|
||||
},
|
||||
{
|
||||
Name: "CARTO basemaps",
|
||||
Homepage: "https://carto.com",
|
||||
Kind: KindService,
|
||||
Source: "https://{s}.basemaps.cartocdn.com (allowlisted in the CSP img-src)",
|
||||
Note: "Raster map tiles fetched by the browser at runtime; no code is redistributed, so no license applies. Attribution (\"(c) OpenStreetMap (c) CARTO\") is rendered by meshmap.js and regionmap.js. Terms of use are CARTO's and are not verified by any test here — re-read them before relying on unauthenticated basemap access, especially for a commercially licensed deployment.",
|
||||
},
|
||||
}
|
||||
|
||||
// FirstPartyStatic lists the files in internal/web/static that we wrote
|
||||
// ourselves. Anything in that directory which is neither listed here nor
|
||||
// claimed by a Deps entry fails TestEveryStaticFileIsAccountedFor — that check
|
||||
// is what stops the next vendored library from slipping in unaudited.
|
||||
var FirstPartyStatic = []string{
|
||||
"app.css",
|
||||
"console-config.js",
|
||||
"console.js",
|
||||
"favicon.svg",
|
||||
"link-editor.js",
|
||||
"listfilter.js",
|
||||
"meshmap.js",
|
||||
"regionmap.js",
|
||||
"serial-setup.js",
|
||||
"timezone-picker.js",
|
||||
"ui.js",
|
||||
"webauthn.js",
|
||||
}
|
||||
|
||||
// AllowedSPDX is the set of licenses a dependency may carry. Everything here is
|
||||
// permissive: copyleft terms (GPL, LGPL, AGPL, MPL, SSPL) would reach back and
|
||||
// constrain how MeshTender itself may be licensed and distributed, so a
|
||||
// copyleft dependency is a licensing conflict rather than a preference. Adding
|
||||
// to this list is a legal decision, not a build fix.
|
||||
var AllowedSPDX = map[string]bool{
|
||||
"0BSD": true,
|
||||
"Apache-2.0": true,
|
||||
"BSD-2-Clause": true,
|
||||
"BSD-3-Clause": true,
|
||||
"ISC": true,
|
||||
"MIT": true,
|
||||
"Unlicense": true,
|
||||
}
|
||||
|
||||
// LicenseText returns the verbatim upstream license text for a dependency.
|
||||
func (d Dep) Text() (string, error) {
|
||||
if d.LicenseText == "" {
|
||||
return "", fmt.Errorf("licenses: %s declares no license text", d.Name)
|
||||
}
|
||||
b, err := texts.ReadFile("texts/" + d.LicenseText)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("licenses: reading text for %s: %w", d.Name, err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// Label names a dependency for humans, with its version when we have one.
|
||||
func (d Dep) Label() string {
|
||||
if d.Version == "" {
|
||||
return d.Name
|
||||
}
|
||||
return d.Name + " " + d.Version
|
||||
}
|
||||
|
||||
// ShipsCode reports whether an entry carries code or art we redistribute, and
|
||||
// therefore must have a scannable license text.
|
||||
func (d Dep) ShipsCode() bool {
|
||||
switch d.Kind {
|
||||
case KindAsset, KindBundled, KindArtwork:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NoticesPath is where the generated notices file lives, relative to the
|
||||
// repository root.
|
||||
const NoticesPath = "THIRD-PARTY-NOTICES.md"
|
||||
|
||||
// The generated file is split into two marked sections so each has an owner.
|
||||
// The assets section derives entirely from this package's manifest, so the
|
||||
// tests can regenerate and compare it with no network and no module cache. The
|
||||
// Go-module section needs `go list` plus the module cache, so cmd/licenses owns
|
||||
// it and CI is what proves it current.
|
||||
const (
|
||||
AssetsBegin = "<!-- BEGIN GENERATED: assets — edit internal/licenses/manifest.go, then run `mise run licenses --update` -->"
|
||||
AssetsEnd = "<!-- END GENERATED: assets -->"
|
||||
GoBegin = "<!-- BEGIN GENERATED: go-modules — run `mise run licenses --update` -->"
|
||||
GoEnd = "<!-- END GENERATED: go-modules -->"
|
||||
)
|
||||
|
||||
// Section extracts the text between two markers, exclusive of the markers.
|
||||
func Section(doc, begin, end string) (string, error) {
|
||||
i := strings.Index(doc, begin)
|
||||
if i < 0 {
|
||||
return "", fmt.Errorf("licenses: marker %q not found", begin)
|
||||
}
|
||||
i += len(begin)
|
||||
j := strings.Index(doc[i:], end)
|
||||
if j < 0 {
|
||||
return "", fmt.Errorf("licenses: marker %q not found after %q", end, begin)
|
||||
}
|
||||
return strings.TrimSpace(doc[i : i+j]), nil
|
||||
}
|
||||
|
||||
// AssetsSection renders the manifest as Markdown. It is a pure function of
|
||||
// Deps, which is what lets the test assert the committed file matches.
|
||||
func AssetsSection() (string, error) {
|
||||
var b strings.Builder
|
||||
|
||||
shipping, referenced := splitByKind(Deps)
|
||||
|
||||
b.WriteString("## Vendored front-end assets and artwork\n\n")
|
||||
b.WriteString("The following third-party code and artwork is redistributed as part of\n")
|
||||
b.WriteString("MeshTender — compiled into the binary via `go:embed` and served to browsers.\n")
|
||||
|
||||
for _, d := range shipping {
|
||||
if err := writeDep(&b, d, true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("\n## Base image and external services\n\n")
|
||||
b.WriteString("These are not compiled into the binary. The base image is redistributed as\n")
|
||||
b.WriteString("part of the published container; the service is called by the browser at runtime.\n")
|
||||
|
||||
for _, d := range referenced {
|
||||
if err := writeDep(&b, d, false); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(b.String()), nil
|
||||
}
|
||||
|
||||
func splitByKind(deps []Dep) (shipping, referenced []Dep) {
|
||||
for _, d := range deps {
|
||||
if d.ShipsCode() {
|
||||
shipping = append(shipping, d)
|
||||
} else {
|
||||
referenced = append(referenced, d)
|
||||
}
|
||||
}
|
||||
return shipping, referenced
|
||||
}
|
||||
|
||||
func writeDep(b *strings.Builder, d Dep, withText bool) error {
|
||||
fmt.Fprintf(b, "\n### %s", d.Label())
|
||||
if d.SPDX != "" {
|
||||
fmt.Fprintf(b, " — %s", d.SPDX)
|
||||
}
|
||||
b.WriteString("\n\n")
|
||||
|
||||
if d.Homepage != "" {
|
||||
fmt.Fprintf(b, "- Homepage: <%s>\n", d.Homepage)
|
||||
}
|
||||
if d.Source != "" {
|
||||
fmt.Fprintf(b, "- Source: %s\n", d.Source)
|
||||
}
|
||||
for _, f := range d.Files {
|
||||
fmt.Fprintf(b, "- File: `%s` (sha256 `%s`)\n", f.Path, f.SHA256)
|
||||
}
|
||||
if d.Modified != "" {
|
||||
fmt.Fprintf(b, "- Modified: %s\n", d.Modified)
|
||||
}
|
||||
if d.Note != "" {
|
||||
fmt.Fprintf(b, "- Note: %s\n", d.Note)
|
||||
}
|
||||
|
||||
if !withText {
|
||||
return nil
|
||||
}
|
||||
text, err := d.Text()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.WriteString("\n```\n")
|
||||
b.WriteString(normalizeEOL(strings.TrimSpace(text)))
|
||||
b.WriteString("\n```\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeEOL rewrites CRLF to LF. Some upstream license files ship with
|
||||
// Windows line endings (Leaflet's does), and Dep.Text deliberately returns them
|
||||
// verbatim so the SPDX check reads exactly what upstream published. Writing
|
||||
// those bytes straight into the Markdown, though, leaves the generated file
|
||||
// with mixed line endings — which any editor or `git add` with autocrlf will
|
||||
// silently normalize, permanently desynchronizing the committed file from what
|
||||
// the generator produces and failing the drift check forever. That already
|
||||
// happened once. Normalizing here keeps the notices file pure LF, while the
|
||||
// embedded texts/ files stay byte-faithful to upstream.
|
||||
func normalizeEOL(s string) string {
|
||||
return strings.ReplaceAll(s, "\r\n", "\n")
|
||||
}
|
||||
|
||||
// GoModule is one module from the Go dependency graph, as scanned by
|
||||
// cmd/licenses.
|
||||
type GoModule struct {
|
||||
Path string
|
||||
Version string
|
||||
SPDX string
|
||||
Copyrights []string
|
||||
// InBinary distinguishes modules linked into the shipped binary from those
|
||||
// only used by tests and tooling. Both are listed; only the first group
|
||||
// carries redistribution obligations.
|
||||
InBinary bool
|
||||
}
|
||||
|
||||
// GoSection renders scanned modules as Markdown, grouped by whether they ship.
|
||||
func GoSection(mods []GoModule) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("Go module dependencies, scanned from the module graph with\n")
|
||||
b.WriteString("[licensecheck](https://github.com/google/licensecheck). Each module's own\n")
|
||||
b.WriteString("license file is the authoritative text; the copyright lines below are\n")
|
||||
b.WriteString("reproduced from it to satisfy the attribution clauses.\n")
|
||||
|
||||
groups := []struct {
|
||||
title string
|
||||
blurb string
|
||||
inBinary bool
|
||||
}{
|
||||
{
|
||||
"Linked into the MeshTender binary",
|
||||
"Redistributed in compiled form. Their notices are reproduced here.",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Build, test, and tooling only",
|
||||
"Not present in the shipped binary or container. Listed for completeness.",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range groups {
|
||||
var rows []GoModule
|
||||
for _, m := range mods {
|
||||
if m.InBinary == g.inBinary {
|
||||
rows = append(rows, m)
|
||||
}
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, "\n### %s\n\n%s\n\n", g.title, g.blurb)
|
||||
for _, m := range rows {
|
||||
fmt.Fprintf(&b, "- **%s** %s — %s", m.Path, m.Version, m.SPDX)
|
||||
if len(m.Copyrights) > 0 {
|
||||
fmt.Fprintf(&b, " \n %s", strings.Join(m.Copyrights, " \n "))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
|
||||
// Notices assembles the whole file from the manifest and a scanned Go section.
|
||||
// Passing an empty goSection preserves nothing — callers that only want to
|
||||
// refresh the assets half should read the existing file and pass its Go section
|
||||
// back in.
|
||||
func Notices(goSection string) (string, error) {
|
||||
assets, err := AssetsSection()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("# Third-Party Notices\n\n")
|
||||
b.WriteString("This file covers the third-party software MeshTender depends on, all of\n")
|
||||
b.WriteString("which is permissively licensed. It makes no statement about MeshTender's\n")
|
||||
b.WriteString("own license.\n\n")
|
||||
b.WriteString("**This file is generated. Do not edit it by hand** — run `mise run licenses --update`.\n")
|
||||
b.WriteString("Front-end and artwork entries come from `internal/licenses/manifest.go`; the Go\n")
|
||||
b.WriteString("module list is scanned from the module graph.\n\n")
|
||||
|
||||
b.WriteString(AssetsBegin + "\n\n")
|
||||
b.WriteString(assets + "\n\n")
|
||||
b.WriteString(AssetsEnd + "\n\n")
|
||||
|
||||
b.WriteString("## Go modules\n\n")
|
||||
b.WriteString(GoBegin + "\n\n")
|
||||
b.WriteString(strings.TrimSpace(goSection) + "\n\n")
|
||||
b.WriteString(GoEnd + "\n")
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2025 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,13 @@
|
||||
Zero-Clause BSD
|
||||
=============
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
||||
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
||||
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,26 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2010-2023, Volodymyr Agafonkin
|
||||
Copyright (c) 2010-2011, CloudMade
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Sumit Kumar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright 2012 David Leaver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2026 The Tabler Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2026 Paweł Kuna
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,3 +1,9 @@
|
||||
/* @preserve
|
||||
* Leaflet-Geoman (leaflet-geoman-free) 2.20.0 stylesheet, https://geoman.io
|
||||
* (c) 2017 Sumit Kumar, MIT License.
|
||||
* Banner restored by hand: the upstream esbuild bundle ships none.
|
||||
* Vendored (self-hosted per CSP); see THIRD-PARTY-NOTICES.md.
|
||||
*/
|
||||
/* src/css/layers.css */
|
||||
.marker-icon {
|
||||
background-color: #ffffff;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,8 @@
|
||||
/* @preserve
|
||||
* Leaflet 1.9.4 stylesheet (leaflet.css), https://leafletjs.com
|
||||
* (c) 2010-2023 Volodymyr Agafonkin, (c) 2010-2011 CloudMade
|
||||
* BSD-2-Clause. Vendored (self-hosted per CSP); see THIRD-PARTY-NOTICES.md.
|
||||
*/
|
||||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/* @preserve Leaflet.markercluster 1.5.3 styles (MarkerCluster.css + MarkerCluster.Default.css), MIT License. Vendored. */
|
||||
/* @preserve
|
||||
* Leaflet.markercluster 1.5.3 styles, https://github.com/Leaflet/Leaflet.markercluster
|
||||
* Copyright 2012 David Leaver, MIT License.
|
||||
* Upstream MarkerCluster.css + MarkerCluster.Default.css concatenated.
|
||||
* Vendored (self-hosted per CSP); see THIRD-PARTY-NOTICES.md.
|
||||
*/
|
||||
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
||||
-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||
-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
{{/* Tabler inline SVG icons. Render with {{template "icon-NAME" .}}; the dot is
|
||||
an optional extra class string appended to the default "icon" class. */}}
|
||||
{{/* Third-party notice — every icon below except icon-logo is Tabler Icons:
|
||||
Tabler Icons, Copyright (c) 2020-2026 Pawel Kuna, MIT License.
|
||||
https://tabler.io/icons — see THIRD-PARTY-NOTICES.md for the full text.
|
||||
The path data is copied verbatim from upstream (minus the transparent 24x24
|
||||
guard path), collected across releases, so a few are renamed locally:
|
||||
antenna<-antenna-bars-5, copy<-squares, list<-list-details,
|
||||
plug<-plug-connected, terminal<-terminal-2, alert<-alert-triangle, and
|
||||
brand-signal<-message-circle-2 (Tabler has no Signal mark).
|
||||
Adding an icon? Take it from Tabler Icons and keep this notice accurate;
|
||||
internal/licenses enforces that this file carries it. */}}
|
||||
{{/* icon-logo is the MeshTender brand mark (traced from the source artwork). It is
|
||||
a fill-based icon rather than a stroke icon, so it sets fill="currentColor". */}}
|
||||
{{define "icon-logo"}}<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 837.000000 477.000000" fill="currentColor" aria-hidden="true" focusable="false" style="height:1.6em;width:auto" class="{{.}}"><g transform="translate(0.000000,477.000000) scale(0.100000,-0.100000)" > <path d="M7140 4596 c-36 -20 -200 -205 -217 -244 -24 -58 -9 -91 95 -214 366 -430 561 -839 649 -1363 26 -156 26 -580 0 -745 -81 -515 -321 -1019 -659 -1385 -80 -86 -88 -101 -88 -162 0 -60 13 -82 129 -210 149 -167 161 -163 396 107 631 729 889 1696 695 2610 -119 563 -368 1039 -778 1487 -121 132 -159 153 -222 119z"/> <path d="M1041 4528 c-254 -265 -483 -611 -634 -958 -237 -545 -302 -1138 -191 -1746 101 -557 402 -1139 803 -1555 89 -92 99 -99 134 -99 53 0 64 7 149 107 152 178 154 211 22 354 -185 202 -381 511 -488 771 -245 596 -258 1286 -34 1888 120 323 283 595 531 883 120 140 117 168 -36 330 -91 96 -93 97 -139 97 -45 0 -50 -3 -117 -72z"/> <path d="M4105 4561 c-195 -90 -463 -377 -548 -589 l-33 -81 -42 19 c-129 58 -310 101 -502 120 -126 13 -162 6 -184 -39 -25 -48 -49 -215 -55 -376 l-6 -160 -55 -6 c-209 -23 -474 -160 -538 -279 -32 -61 -26 -170 17 -299 65 -194 159 -336 303 -456 l55 -47 -80 -77 c-181 -173 -300 -419 -301 -623 -1 -93 0 -97 30 -135 89 -111 331 -227 519 -249 l52 -6 6 -157 c9 -217 37 -368 73 -400 27 -22 33 -23 119 -17 174 12 342 50 498 112 49 20 90 34 91 32 2 -1 22 -43 44 -91 101 -217 287 -420 506 -551 76 -46 120 -47 194 -6 222 122 457 376 537 578 16 40 29 72 31 72 1 0 35 -13 76 -29 160 -64 344 -106 515 -118 88 -6 92 -5 116 19 39 39 76 257 77 451 0 95 2 107 18 107 55 0 238 56 327 100 227 111 278 189 240 367 -51 244 -213 492 -393 603 -15 9 -14 13 12 33 163 124 237 210 311 362 88 180 116 333 76 421 -50 110 -302 249 -518 285 l-70 11 -6 152 c-10 263 -41 393 -99 417 -68 28 -399 -25 -577 -92 -103 -38 -121 -40 -121 -14 0 26 -100 211 -149 275 -62 82 -206 221 -291 282 -140 99 -196 115 -275 79z m141 -182 c240 -168 390 -366 446 -589 25 -103 21 -220 -12 -320 -46 -138 -240 -474 -268 -466 -145 45 -287 51 -416 17 -63 -17 -82 -18 -88 -9 -45 68 -218 437 -233 496 -59 243 32 500 254 722 74 74 227 190 250 190 4 0 34 -19 67 -41z m-1182 -515 c116 -18 306 -73 375 -109 l48 -24 7 -94 c12 -174 54 -293 195 -558 45 -83 81 -155 81 -159 0 -4 -16 -20 -36 -35 -31 -24 -37 -25 -52 -12 -481 414 -595 500 -722 542 l-49 16 -7 56 c-7 61 0 204 18 320 12 84 1 79 142 57z m2368 -18 c14 -55 30 -270 23 -326 l-6 -56 -88 -21 c-195 -46 -400 -175 -696 -438 -47 -42 -85 -74 -85 -72 0 3 29 51 66 108 151 240 212 401 221 586 l6 112 67 30 c89 39 223 78 335 95 127 20 147 18 157 -18z m303 -566 c92 -23 223 -86 291 -137 l46 -35 -7 -52 c-33 -244 -193 -478 -397 -582 l-48 -24 -392 2 -391 3 -13 60 c-17 83 -40 138 -91 219 -52 80 -60 63 87 191 399 348 618 432 915 355z m-2895 9 c122 -27 234 -104 535 -364 109 -93 204 -175 213 -182 13 -11 11 -20 -15 -80 -17 -37 -38 -101 -48 -140 l-18 -73 -414 0 -414 0 -58 39 c-169 115 -312 354 -337 562 l-6 56 48 35 c159 116 376 179 514 147z m1433 -414 c332 -70 504 -439 344 -738 -196 -369 -712 -359 -893 16 -180 376 147 807 549 722z m-750 -648 c17 -67 52 -148 90 -212 l24 -40 -171 -162 c-273 -260 -385 -336 -546 -369 -171 -36 -425 27 -587 145 -49 36 -53 57 -28 156 51 199 147 355 295 481 l75 64 416 0 417 0 15 -63z m2142 34 c195 -114 356 -358 390 -592 l7 -47 -62 -42 c-272 -185 -582 -191 -843 -16 -70 46 -405 313 -460 365 -17 16 -16 19 16 62 47 61 83 136 103 211 9 34 18 69 20 76 3 9 89 12 391 12 l388 0 50 -29z m-1896 -420 c11 -7 5 -25 -33 -90 -170 -295 -246 -504 -246 -677 l0 -63 -47 -21 c-124 -55 -345 -114 -463 -122 l-45 -3 -12 70 c-7 39 -14 130 -15 204 l-3 134 70 19 c201 55 348 157 661 460 104 100 109 104 133 89z m1176 -322 c174 -131 307 -198 459 -229 l59 -12 -6 -126 c-3 -70 -11 -165 -18 -210 l-12 -84 -46 7 c-164 22 -393 85 -483 131 -26 13 -28 19 -28 73 0 201 -68 377 -280 726 -21 33 -2 20 115 -81 77 -66 185 -154 240 -195z m-940 211 c50 -13 111 -20 170 -20 59 0 120 7 170 20 43 11 79 20 81 20 5 0 161 -274 197 -345 122 -244 112 -441 -34 -688 -90 -152 -379 -403 -433 -376 -81 42 -234 176 -313 274 -157 197 -217 420 -167 621 19 75 231 514 248 514 2 0 38 -9 81 -20z"/> <path d="M1715 3826 c-80 -35 -270 -302 -391 -549 -294 -605 -268 -1247 75 -1815 121 -202 258 -373 317 -398 123 -52 274 54 274 193 0 38 -19 74 -82 152 -527 652 -525 1389 6 2085 75 98 85 133 62 204 -36 109 -163 171 -261 128z"/> <path d="M6483 3815 c-74 -37 -113 -98 -113 -176 0 -54 3 -62 49 -121 206 -262 331 -515 393 -795 30 -133 32 -425 5 -553 -55 -258 -205 -570 -357 -742 -70 -80 -90 -117 -90 -167 1 -164 192 -269 306 -169 150 132 382 518 468 779 180 546 85 1145 -263 1670 -184 277 -272 338 -398 274z"/> </g></svg>{{end}}
|
||||
|
||||
Reference in New Issue
Block a user