From 95a5756f93542aac3635a700262c41e932079a80 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 31 Aug 2026 13:33:06 -0700 Subject: [PATCH 01/14] feat(settings): add open source license notices --- .gitignore | 1 + apps/mobile/licenses/badgin.txt | 23 + apps/mobile/licenses/boolbase.txt | 13 + apps/mobile/licenses/bplist-parser.txt | 22 + apps/mobile/licenses/expo-devcert.txt | 21 + apps/mobile/licenses/expo-xcpretty.txt | 29 + apps/mobile/licenses/fb-dotslash.txt | 21 + apps/mobile/licenses/ghostty-kit.txt | 32 + apps/mobile/licenses/jimp-compact.txt | 21 + apps/mobile/licenses/meslo-lgs-nf.txt | 13 + apps/mobile/licenses/metro.txt | 21 + apps/mobile/licenses/react-native-ai.txt | 22 + .../licenses/react-native-nitro-modules.txt | 21 + .../licenses/react-remove-scroll-bar.txt | 21 + apps/mobile/licenses/standard-navigation.txt | 21 + apps/mobile/licenses/stream-buffers.txt | 24 + apps/mobile/licenses/structured-headers.txt | 21 + apps/mobile/metro.config.js | 66 +- apps/mobile/src/Stack.tsx | 18 + .../SettingsOpenSourceLicensesRouteScreen.tsx | 212 ++++++ .../features/settings/SettingsRouteScreen.tsx | 5 + .../components/settings-sheet-targets.ts | 1 + .../settings/mobileThirdPartyLicenses.ts | 4 + .../types/mobile-third-party-licenses.d.ts | 4 + apps/web/THIRD_PARTY_NOTICES.md | 27 +- .../electron-internal-extract-zip.txt | 25 + apps/web/licenses/glob-to-regexp.txt | 24 + apps/web/licenses/isarray.txt | 21 + apps/web/licenses/keyv.txt | 22 + apps/web/licenses/kubernetes-types.txt | 176 +++++ apps/web/licenses/lazy-val.txt | 21 + apps/web/licenses/lru-map.txt | 19 + .../licenses/msgpackr-extract-linux-x64.txt | 21 + apps/web/licenses/pierre-theming.txt | 189 +++++ apps/web/licenses/react-grab-cli.txt | 21 + apps/web/licenses/type-fest.txt | 9 + .../settings/OpenSourceLicenses.tsx | 205 +++++ .../settings/SettingsBreadcrumb.tsx | 1 + .../components/settings/SettingsPanels.tsx | 13 + .../settings/SettingsSidebarNav.tsx | 9 +- .../src/components/settings/settingsSearch.ts | 5 + apps/web/src/routeTree.gen.ts | 22 + .../routes/settings.open-source-licenses.tsx | 7 + apps/web/tsconfig.json | 3 +- apps/web/vite.config.ts | 10 + docs/internals/open-source-licenses.md | 84 +++ docs/user/open-source-licenses.md | 12 + packages/shared/package.json | 4 + .../shared/src/thirdPartyLicenses.test.ts | 63 ++ packages/shared/src/thirdPartyLicenses.ts | 103 +++ scripts/lib/third-party-licenses.test.ts | 245 ++++++ scripts/lib/third-party-licenses.ts | 710 ++++++++++++++++++ third-party-licenses.config.json | 181 +++++ 53 files changed, 2895 insertions(+), 14 deletions(-) create mode 100644 apps/mobile/licenses/badgin.txt create mode 100644 apps/mobile/licenses/boolbase.txt create mode 100644 apps/mobile/licenses/bplist-parser.txt create mode 100644 apps/mobile/licenses/expo-devcert.txt create mode 100644 apps/mobile/licenses/expo-xcpretty.txt create mode 100644 apps/mobile/licenses/fb-dotslash.txt create mode 100644 apps/mobile/licenses/ghostty-kit.txt create mode 100644 apps/mobile/licenses/jimp-compact.txt create mode 100644 apps/mobile/licenses/meslo-lgs-nf.txt create mode 100644 apps/mobile/licenses/metro.txt create mode 100644 apps/mobile/licenses/react-native-ai.txt create mode 100644 apps/mobile/licenses/react-native-nitro-modules.txt create mode 100644 apps/mobile/licenses/react-remove-scroll-bar.txt create mode 100644 apps/mobile/licenses/standard-navigation.txt create mode 100644 apps/mobile/licenses/stream-buffers.txt create mode 100644 apps/mobile/licenses/structured-headers.txt create mode 100644 apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx create mode 100644 apps/mobile/src/features/settings/mobileThirdPartyLicenses.ts create mode 100644 apps/mobile/src/types/mobile-third-party-licenses.d.ts create mode 100644 apps/web/licenses/electron-internal-extract-zip.txt create mode 100644 apps/web/licenses/glob-to-regexp.txt create mode 100644 apps/web/licenses/isarray.txt create mode 100644 apps/web/licenses/keyv.txt create mode 100644 apps/web/licenses/kubernetes-types.txt create mode 100644 apps/web/licenses/lazy-val.txt create mode 100644 apps/web/licenses/lru-map.txt create mode 100644 apps/web/licenses/msgpackr-extract-linux-x64.txt create mode 100644 apps/web/licenses/pierre-theming.txt create mode 100644 apps/web/licenses/react-grab-cli.txt create mode 100644 apps/web/licenses/type-fest.txt create mode 100644 apps/web/src/components/settings/OpenSourceLicenses.tsx create mode 100644 apps/web/src/routes/settings.open-source-licenses.tsx create mode 100644 docs/internals/open-source-licenses.md create mode 100644 docs/user/open-source-licenses.md create mode 100644 packages/shared/src/thirdPartyLicenses.test.ts create mode 100644 packages/shared/src/thirdPartyLicenses.ts create mode 100644 scripts/lib/third-party-licenses.test.ts create mode 100644 scripts/lib/third-party-licenses.ts create mode 100644 third-party-licenses.config.json diff --git a/.gitignore b/.gitignore index 57262578a786..0bca4d9ba452 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ dist-electron/ .electron-runtime/ .showcase/ apps/mobile/.showcase/ +apps/mobile/.generated/ artifacts/app-store/screenshots/ .github/pr-assets/ native/**/target/ diff --git a/apps/mobile/licenses/badgin.txt b/apps/mobile/licenses/badgin.txt new file mode 100644 index 000000000000..36fb0f2975ba --- /dev/null +++ b/apps/mobile/licenses/badgin.txt @@ -0,0 +1,23 @@ +badgin is a refactored fork of Tinycon. + +MIT License + +Copyright (c) Julian Hundeloh and Tinycon contributors + +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. diff --git a/apps/mobile/licenses/boolbase.txt b/apps/mobile/licenses/boolbase.txt new file mode 100644 index 000000000000..a689aae96e89 --- /dev/null +++ b/apps/mobile/licenses/boolbase.txt @@ -0,0 +1,13 @@ +Copyright (c) 2014-2015, Felix Boehm + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +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. diff --git a/apps/mobile/licenses/bplist-parser.txt b/apps/mobile/licenses/bplist-parser.txt new file mode 100644 index 000000000000..43e0a819e8eb --- /dev/null +++ b/apps/mobile/licenses/bplist-parser.txt @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2012 Near Infinity Corporation + +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. diff --git a/apps/mobile/licenses/expo-devcert.txt b/apps/mobile/licenses/expo-devcert.txt new file mode 100644 index 000000000000..1b2a58dc871f --- /dev/null +++ b/apps/mobile/licenses/expo-devcert.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Dave Wasmer + +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. diff --git a/apps/mobile/licenses/expo-xcpretty.txt b/apps/mobile/licenses/expo-xcpretty.txt new file mode 100644 index 000000000000..4a70306986eb --- /dev/null +++ b/apps/mobile/licenses/expo-xcpretty.txt @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016-present, 650 Industries, Inc. (Expo) +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. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +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. diff --git a/apps/mobile/licenses/fb-dotslash.txt b/apps/mobile/licenses/fb-dotslash.txt new file mode 100644 index 000000000000..b93be90515cc --- /dev/null +++ b/apps/mobile/licenses/fb-dotslash.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +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. diff --git a/apps/mobile/licenses/ghostty-kit.txt b/apps/mobile/licenses/ghostty-kit.txt new file mode 100644 index 000000000000..d9f72b0a65ab --- /dev/null +++ b/apps/mobile/licenses/ghostty-kit.txt @@ -0,0 +1,32 @@ +GhosttyKit + +The iOS terminal renderer vendors GhosttyKit.xcframework, a libghostty build produced from T3's +iOS 16 support fork. That fork was created from VVTerm's custom-I/O Ghostty fork. + +Upstream project: https://github.com/ghostty-org/ghostty +Custom-I/O base fork: https://github.com/wiedymi/ghostty/tree/custom-io +Vendored source fork: https://github.com/Yash-Singh1/ghostty/tree/custom-io +Vendored revision: d36c3b8dffd0d756dd5e5f4933962f774a0e6753 +Reference integration: https://github.com/vivy-company/vvterm + +MIT License + +Copyright (c) 2024 Mitchell Hashimoto, Ghostty contributors + +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. diff --git a/apps/mobile/licenses/jimp-compact.txt b/apps/mobile/licenses/jimp-compact.txt new file mode 100644 index 000000000000..baa5d48509a1 --- /dev/null +++ b/apps/mobile/licenses/jimp-compact.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Oliver Moran + +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. diff --git a/apps/mobile/licenses/meslo-lgs-nf.txt b/apps/mobile/licenses/meslo-lgs-nf.txt new file mode 100644 index 000000000000..e07201aa0327 --- /dev/null +++ b/apps/mobile/licenses/meslo-lgs-nf.txt @@ -0,0 +1,13 @@ +Copyright 2009, 2010, 2013 André Berg + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/apps/mobile/licenses/metro.txt b/apps/mobile/licenses/metro.txt new file mode 100644 index 000000000000..b93be90515cc --- /dev/null +++ b/apps/mobile/licenses/metro.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +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. diff --git a/apps/mobile/licenses/react-native-ai.txt b/apps/mobile/licenses/react-native-ai.txt new file mode 100644 index 000000000000..b78d113be4e3 --- /dev/null +++ b/apps/mobile/licenses/react-native-ai.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2024-2025 Szymon Rybczak +Copyright (c) 2025-present Callstack + +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. diff --git a/apps/mobile/licenses/react-native-nitro-modules.txt b/apps/mobile/licenses/react-native-nitro-modules.txt new file mode 100644 index 000000000000..c9efc1be9e23 --- /dev/null +++ b/apps/mobile/licenses/react-native-nitro-modules.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Marc Rousavy + +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. diff --git a/apps/mobile/licenses/react-remove-scroll-bar.txt b/apps/mobile/licenses/react-remove-scroll-bar.txt new file mode 100644 index 000000000000..669467a25577 --- /dev/null +++ b/apps/mobile/licenses/react-remove-scroll-bar.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Anton Korzunov + +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. diff --git a/apps/mobile/licenses/standard-navigation.txt b/apps/mobile/licenses/standard-navigation.txt new file mode 100644 index 000000000000..887f80b0b9ca --- /dev/null +++ b/apps/mobile/licenses/standard-navigation.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 React Navigation Contributors + +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. diff --git a/apps/mobile/licenses/stream-buffers.txt b/apps/mobile/licenses/stream-buffers.txt new file mode 100644 index 000000000000..68a49daad8ff --- /dev/null +++ b/apps/mobile/licenses/stream-buffers.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to diff --git a/apps/mobile/licenses/structured-headers.txt b/apps/mobile/licenses/structured-headers.txt new file mode 100644 index 000000000000..faa9c812abfc --- /dev/null +++ b/apps/mobile/licenses/structured-headers.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Evert Pot + +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. diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index f8eda69b6fb7..fb53ff888cbd 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -1,5 +1,7 @@ const fs = require("node:fs"); const path = require("node:path"); +const { pathToFileURL } = require("node:url"); +const TypeScript = require("typescript"); const { getDefaultConfig } = require("expo/metro-config"); const { withUniwindConfig } = require("uniwind/metro"); const extraThemes = require("./generated-uniwind-theme-names.json"); @@ -7,6 +9,13 @@ const extraThemes = require("./generated-uniwind-theme-names.json"); /** @type {import("expo/metro-config").MetroConfig} */ const config = getDefaultConfig(__dirname); const workspaceRoot = path.resolve(__dirname, "../.."); +const generatedLicenseModuleRoot = path.join(__dirname, ".generated", "third-party-licenses"); +const licenseGeneratorSource = path.join( + workspaceRoot, + "scripts", + "lib", + "third-party-licenses.ts", +); const escapedWorkspaceRoot = workspaceRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const mobileShikiRoot = path.dirname(require.resolve("shiki/package.json", { paths: [__dirname] })); const resolveShikiDependencyRoot = (packageName) => { @@ -37,6 +46,7 @@ config.resolver = { ], extraNodeModules: { ...config.resolver?.extraNodeModules, + "@t3tools/mobile-third-party-licenses": generatedLicenseModuleRoot, shiki: mobileShikiRoot, "@shikijs/core": resolveShikiDependencyRoot("@shikijs/core"), "@shikijs/engine-javascript": resolveShikiDependencyRoot("@shikijs/engine-javascript"), @@ -48,8 +58,54 @@ config.resolver = { }, }; -module.exports = withUniwindConfig(config, { - cssEntryFile: "./global.css", - extraThemes, - polyfills: { rem: 14 }, -}); +async function writeFileIfChanged(filePath, contents) { + try { + if ((await fs.promises.readFile(filePath, "utf8")) === contents) return; + } catch (error) { + if (error?.code !== "ENOENT") throw error; + } + await fs.promises.writeFile(filePath, contents, "utf8"); +} + +async function generateMobileThirdPartyLicenses() { + await fs.promises.mkdir(generatedLicenseModuleRoot, { recursive: true }); + const generatorSource = await fs.promises.readFile(licenseGeneratorSource, "utf8"); + const compiledGenerator = TypeScript.transpileModule(generatorSource, { + compilerOptions: { + module: TypeScript.ModuleKind.ESNext, + target: TypeScript.ScriptTarget.ES2022, + verbatimModuleSyntax: true, + }, + fileName: licenseGeneratorSource, + }).outputText; + const compiledGeneratorPath = path.join(generatedLicenseModuleRoot, "generator.mjs"); + await writeFileIfChanged(compiledGeneratorPath, compiledGenerator); + + const generatorVersion = (await fs.promises.stat(compiledGeneratorPath)).mtimeMs; + const { generateThirdPartyLicenseManifest } = await import( + `${pathToFileURL(compiledGeneratorPath).href}?version=${String(generatorVersion)}` + ); + const manifest = await generateThirdPartyLicenseManifest({ + configFile: path.join(workspaceRoot, "third-party-licenses.config.json"), + packageManifests: [{ bundle: "mobile", path: path.join(__dirname, "package.json") }], + }); + + await Promise.all([ + writeFileIfChanged( + path.join(generatedLicenseModuleRoot, "index.js"), + `module.exports = ${JSON.stringify(manifest)};\n`, + ), + writeFileIfChanged( + path.join(generatedLicenseModuleRoot, "package.json"), + '{"main":"index.js"}\n', + ), + ]); +} + +module.exports = generateMobileThirdPartyLicenses().then(() => + withUniwindConfig(config, { + cssEntryFile: "./global.css", + extraThemes, + polyfills: { rem: 14 }, + }), +); diff --git a/apps/mobile/src/Stack.tsx b/apps/mobile/src/Stack.tsx index dd0b48700ef7..914b7c4f73a4 100644 --- a/apps/mobile/src/Stack.tsx +++ b/apps/mobile/src/Stack.tsx @@ -55,6 +55,10 @@ import { SettingsClientStorageRouteScreen } from "./features/settings/SettingsCl import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteScreen"; import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen"; import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen"; +import { + SettingsOpenSourceLicenseRouteScreen, + SettingsOpenSourceLicensesRouteScreen, +} from "./features/settings/SettingsOpenSourceLicensesRouteScreen"; import { SettingsProjectGroupingRouteScreen } from "./features/settings/SettingsProjectGroupingRouteScreen"; import { UsageLimitAccountScreen } from "./features/usage/UsageLimitsPooled"; import { UsageRouteScreen } from "./features/usage/UsageRouteScreen"; @@ -197,6 +201,20 @@ const SettingsContentStack = createNativeStackNavigator({ screen: UsageLimitAccountScreen, options: { title: "Account" }, }), + SettingsOpenSourceLicenses: createNativeStackScreen({ + screen: SettingsOpenSourceLicensesRouteScreen, + linking: "open-source-licenses", + options: { + title: "Open source licenses", + }, + }), + SettingsOpenSourceLicense: createNativeStackScreen({ + screen: SettingsOpenSourceLicenseRouteScreen, + linking: "open-source-licenses/:entryKey", + options: { + title: "License notice", + }, + }), SettingsUsage: createNativeStackScreen({ screen: UsageRouteScreen, linking: "usage", diff --git a/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx new file mode 100644 index 000000000000..37632f670812 --- /dev/null +++ b/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx @@ -0,0 +1,212 @@ +import { LegendList } from "@legendapp/list/react-native"; +import { type StaticScreenProps, useNavigation } from "@react-navigation/native"; +import { + filterThirdPartyLicenseEntries, + findThirdPartyLicenseEntry, + formatLicenseBundles, + thirdPartyLicenseEntryKey, + type ThirdPartyLicenseEntry, +} from "@t3tools/shared/thirdPartyLicenses"; +import { useCallback, useMemo, useState } from "react"; +import { Linking, Platform, Pressable, ScrollView, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; +import { SymbolView } from "../../components/AppSymbol"; +import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; +import { NativeStackScreenOptions } from "../../native/StackHeader"; +import { MOBILE_THIRD_PARTY_LICENSES } from "./mobileThirdPartyLicenses"; + +function LicenseRow(props: { + readonly entry: ThirdPartyLicenseEntry; + readonly onPress: () => void; +}) { + return ( + + + + + {props.entry.name} + + + {props.entry.version ? `${props.entry.version} · ` : ""} + {props.entry.license} + + + + + + ); +} + +export function SettingsOpenSourceLicensesRouteScreen() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + const [query, setQuery] = useState(""); + const entries = MOBILE_THIRD_PARTY_LICENSES.entries; + const filteredEntries = useMemo( + () => filterThirdPartyLicenseEntries(entries, query), + [entries, query], + ); + const renderItem = useCallback( + ({ item }: { readonly item: ThirdPartyLicenseEntry }) => ( + + navigation.navigate("SettingsSheet", { + screen: "SettingsContent", + params: { + screen: "SettingsOpenSourceLicense", + params: { entryKey: thirdPartyLicenseEntryKey(item) }, + }, + }) + } + /> + ), + [navigation], + ); + + return ( + + {Platform.OS === "android" ? ( + <> + + navigation.goBack()} /> + + ) : null} + + + No licenses match that search. + + + } + ListHeaderComponent={ + + + Third-party notices for dependencies and assets included in T3 Code Mobile. + + + + {filteredEntries.length === entries.length + ? `${String(entries.length)} notices` + : `${String(filteredEntries.length)} of ${String(entries.length)} notices`} + + + } + renderItem={renderItem} + showsVerticalScrollIndicator={false} + /> + + ); +} + +type LicenseDetailProps = StaticScreenProps<{ readonly entryKey: string }>; + +export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailProps) { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + const entry = findThirdPartyLicenseEntry( + MOBILE_THIRD_PARTY_LICENSES.entries, + route.params.entryKey, + ); + const sourceUrl = entry?.sourceUrl?.match(/^https?:\/\//) ? entry.sourceUrl : null; + + if (!entry) { + return ( + + {Platform.OS === "android" ? ( + <> + + navigation.goBack()} /> + + ) : null} + + + This license notice is unavailable. + + + + ); + } + + return ( + + {Platform.OS === "android" ? ( + <> + + navigation.goBack()} /> + + ) : null} + + + {entry.name} + + {[entry.version, entry.license, formatLicenseBundles(entry.bundles)] + .filter((value): value is string => Boolean(value)) + .join(" · ")} + + {sourceUrl ? ( + void Linking.openURL(sourceUrl)} + className="min-h-12 flex-row items-center gap-2 self-start py-2 active:opacity-60" + > + Project source + + + ) : null} + + + + + {entry.noticeText} + + + + + ); +} diff --git a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx index d57904b7e7a2..b4614d1c071c 100644 --- a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx @@ -821,6 +821,11 @@ function AppSettingsSection() { return ( + {updateCheckAvailable ? ( . +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. diff --git a/apps/web/licenses/electron-internal-extract-zip.txt b/apps/web/licenses/electron-internal-extract-zip.txt new file mode 100644 index 000000000000..c127edcfc580 --- /dev/null +++ b/apps/web/licenses/electron-internal-extract-zip.txt @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2026 Samuel Attard and Electron contributors +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. diff --git a/apps/web/licenses/glob-to-regexp.txt b/apps/web/licenses/glob-to-regexp.txt new file mode 100644 index 000000000000..11ffa9a34892 --- /dev/null +++ b/apps/web/licenses/glob-to-regexp.txt @@ -0,0 +1,24 @@ +Copyright (c) 2013, Nick Fitzgerald + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. diff --git a/apps/web/licenses/isarray.txt b/apps/web/licenses/isarray.txt new file mode 100644 index 000000000000..2cdc8e4148cc --- /dev/null +++ b/apps/web/licenses/isarray.txt @@ -0,0 +1,21 @@ +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +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. diff --git a/apps/web/licenses/keyv.txt b/apps/web/licenses/keyv.txt new file mode 100644 index 000000000000..a6bad1d22eee --- /dev/null +++ b/apps/web/licenses/keyv.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017-2021 Luke Childs +Copyright (c) 2021-2022 Jared Wray + +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. diff --git a/apps/web/licenses/kubernetes-types.txt b/apps/web/licenses/kubernetes-types.txt new file mode 100644 index 000000000000..d0381d6d04c7 --- /dev/null +++ b/apps/web/licenses/kubernetes-types.txt @@ -0,0 +1,176 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/apps/web/licenses/lazy-val.txt b/apps/web/licenses/lazy-val.txt new file mode 100644 index 000000000000..1d667e735b30 --- /dev/null +++ b/apps/web/licenses/lazy-val.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Vladimir Krivosheev + +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. diff --git a/apps/web/licenses/lru-map.txt b/apps/web/licenses/lru-map.txt new file mode 100644 index 000000000000..10568e2774dc --- /dev/null +++ b/apps/web/licenses/lru-map.txt @@ -0,0 +1,19 @@ +Copyright (c) 2010-2016 Rasmus Andersson + +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. diff --git a/apps/web/licenses/msgpackr-extract-linux-x64.txt b/apps/web/licenses/msgpackr-extract-linux-x64.txt new file mode 100644 index 000000000000..97f48aa82439 --- /dev/null +++ b/apps/web/licenses/msgpackr-extract-linux-x64.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Kris Zyp + +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. diff --git a/apps/web/licenses/pierre-theming.txt b/apps/web/licenses/pierre-theming.txt new file mode 100644 index 000000000000..e2c2b06060d8 --- /dev/null +++ b/apps/web/licenses/pierre-theming.txt @@ -0,0 +1,189 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and + distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the + copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other + entities that control, are controlled by, or are under common control with + that entity. For the purposes of this definition, "control" means (i) the + power, direct or indirect, to cause the direction or management of such + entity, whether by contract or otherwise, or (ii) ownership of fifty percent + (50%) or more of the outstanding shares, or (iii) beneficial ownership of + such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising + permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation source, and + configuration files. + + "Object" form shall mean any form resulting from mechanical transformation + or translation of a Source form, including but not limited to compiled + object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, + made available under the License, as indicated by a copyright notice that is + included in or attached to the work (an example is provided in the Appendix + below). + + "Derivative Works" shall mean any work, whether in Source or Object form, + that is based on (or derived from) the Work and for which the editorial + revisions, annotations, elaborations, or other modifications represent, as a + whole, an original work of authorship. For the purposes of this License, + Derivative Works shall not include works that remain separable from, or + merely link (or bind by name) to the interfaces of, the Work and Derivative + Works thereof. + + "Contribution" shall mean any work of authorship, including the original + version of the Work and any modifications or additions to that Work or + Derivative Works thereof, that is intentionally submitted to Licensor for + inclusion in the Work by the copyright owner or by an individual or Legal + Entity authorized to submit on behalf of the copyright owner. For the + purposes of this definition, "submitted" means any form of electronic, + verbal, or written communication sent to the Licensor or its + representatives, including but not limited to communication on electronic + mailing lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, the Licensor for the purpose of discussing + and improving the Work, but excluding communication that is conspicuously + marked or otherwise designated in writing by the copyright owner as "Not a + Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on + behalf of whom a Contribution has been received by Licensor and subsequently + incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable copyright license to + reproduce, prepare Derivative Works of, publicly display, publicly perform, + sublicense, and distribute the Work and such Derivative Works in Source or + Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable (except as stated in + this section) patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work, where such license applies only to + those patent claims licensable by such Contributor that are necessarily + infringed by their Contribution(s) alone or by combination of their + Contribution(s) with the Work to which such Contribution(s) was submitted. + If You institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work or a + Contribution incorporated within the Work constitutes direct or contributory + patent infringement, then any patent licenses granted to You under this + License for that Work shall terminate as of the date such litigation is + filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or + Derivative Works thereof in any medium, with or without modifications, and + in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a + copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating + that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You + distribute, all copyright, patent, trademark, and attribution notices from + the Source form of the Work, excluding those notices that do not pertain to + any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable copy + of the attribution notices contained within such NOTICE file, excluding + those notices that do not pertain to any part of the Derivative Works, in at + least one of the following places: within a NOTICE text file distributed as + part of the Derivative Works; within the Source form or documentation, if + provided along with the Derivative Works; or, within a display generated by + the Derivative Works, if and wherever such third-party notices normally + appear. The contents of the NOTICE file are for informational purposes only + and do not modify the License. You may add Your own attribution notices + within Derivative Works that You distribute, alongside or as an addendum to + the NOTICE text from the Work, provided that such additional attribution + notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may + provide additional or different license terms and conditions for use, + reproduction, or distribution of Your modifications, or for any such + Derivative Works as a whole, provided Your use, reproduction, and + distribution of the Work otherwise complies with the conditions stated in + this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any + Contribution intentionally submitted for inclusion in the Work by You to the + Licensor shall be under the terms and conditions of this License, without + any additional terms or conditions. Notwithstanding the above, nothing + herein shall supersede or modify the terms of any separate license agreement + you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, + trademarks, service marks, or product names of the Licensor, except as + required for reasonable and customary use in describing the origin of the + Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in + writing, Licensor provides the Work (and each Contributor provides its + Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied, including, without limitation, any + warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or + FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining + the appropriateness of using or redistributing the Work and assume any risks + associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in + tort (including negligence), contract, or otherwise, unless required by + applicable law (such as deliberate and grossly negligent acts) or agreed to + in writing, shall any Contributor be liable to You for damages, including + any direct, indirect, special, incidental, or consequential damages of any + character arising as a result of this License or out of the use or inability + to use the Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all other + commercial damages or losses), even if such Contributor has been advised of + the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or + Derivative Works thereof, You may choose to offer, and charge a fee for, + acceptance of support, warranty, indemnity, or other liability obligations + and/or rights consistent with this License. However, in accepting such + obligations, You may act only on Your own behalf and on Your sole + responsibility, not on behalf of any other Contributor, and only if You + agree to indemnify, defend, and hold each Contributor harmless for any + liability incurred by, or claims asserted against, such Contributor by + reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2025 Pierre Computer Company + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/apps/web/licenses/react-grab-cli.txt b/apps/web/licenses/react-grab-cli.txt new file mode 100644 index 000000000000..9ffd507155b4 --- /dev/null +++ b/apps/web/licenses/react-grab-cli.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Aiden Bai + +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. diff --git a/apps/web/licenses/type-fest.txt b/apps/web/licenses/type-fest.txt new file mode 100644 index 000000000000..fa7ceba3eb4a --- /dev/null +++ b/apps/web/licenses/type-fest.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +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. diff --git a/apps/web/src/components/settings/OpenSourceLicenses.tsx b/apps/web/src/components/settings/OpenSourceLicenses.tsx new file mode 100644 index 000000000000..28932ae10ece --- /dev/null +++ b/apps/web/src/components/settings/OpenSourceLicenses.tsx @@ -0,0 +1,205 @@ +import { ChevronRightIcon, ExternalLinkIcon, SearchIcon } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { + decodeThirdPartyLicenseManifest, + filterThirdPartyLicenseEntries, + formatLicenseBundles, + type ThirdPartyLicenseEntry, + type ThirdPartyLicenseManifest, +} from "@t3tools/shared/thirdPartyLicenses"; + +import { cn } from "../../lib/utils"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { SettingsPageContainer } from "./settingsLayout"; + +type LicenseManifestState = + | { readonly status: "loading" } + | { readonly status: "error"; readonly message: string } + | { readonly status: "ready"; readonly manifest: ThirdPartyLicenseManifest }; + +async function loadLicenseManifest(signal: AbortSignal): Promise { + const response = await fetch( + `${import.meta.env.BASE_URL.replace(/\/$/, "")}/third-party-licenses.json`, + { signal }, + ); + if (!response.ok) { + throw new Error(`The license manifest request failed with status ${String(response.status)}.`); + } + return decodeThirdPartyLicenseManifest((await response.json()) as unknown); +} + +function LicenseNoticeRow({ entry }: { readonly entry: ThirdPartyLicenseEntry }) { + const [open, setOpen] = useState(false); + const panelId = `license-notice-${encodeURIComponent(`${entry.kind}-${entry.name}-${entry.version ?? "custom"}`)}`; + const versionLabel = entry.version ? ` ${entry.version}` : ""; + + return ( +
+ + {open ? ( +
+
+
+

+ {entry.name} + {versionLabel} +

+ {entry.sourceUrl ? ( + + Project source + + + ) : null} +
+
+              {entry.noticeText}
+            
+
+
+ ) : null} +
+ ); +} + +function LicenseManifestError({ message, onRetry }: { message: string; onRetry: () => void }) { + return ( +
+
+

+ Open-source notices are unavailable +

+

+ {message} +

+
+ +
+ ); +} + +export function OpenSourceLicensesPanel() { + const [state, setState] = useState({ status: "loading" }); + const [query, setQuery] = useState(""); + const [requestVersion, setRequestVersion] = useState(0); + + useEffect(() => { + const controller = new AbortController(); + setState({ status: "loading" }); + void loadLicenseManifest(controller.signal).then( + (manifest) => setState({ status: "ready", manifest }), + (error: unknown) => { + if (controller.signal.aborted) return; + setState({ + status: "error", + message: error instanceof Error ? error.message : "The license manifest could not load.", + }); + }, + ); + return () => controller.abort(); + }, [requestVersion]); + + const entries = state.status === "ready" ? state.manifest.entries : []; + const filteredEntries = useMemo( + () => filterThirdPartyLicenseEntries(entries, query), + [entries, query], + ); + const retry = useCallback(() => setRequestVersion((value) => value + 1), []); + + return ( + +
+

+ Open source licenses +

+

+ Third-party notices for dependencies and assets included in T3 Code. +

+
+ + {state.status === "ready" ? ( +
+
+ +

+ {filteredEntries.length === entries.length + ? `${String(entries.length)} notices` + : `${String(filteredEntries.length)} of ${String(entries.length)} notices`} +

+
+ +
+ {filteredEntries.length > 0 ? ( + filteredEntries.map((entry) => ( + + )) + ) : ( +

+ No licenses match that search. +

+ )} +
+
+ ) : state.status === "error" ? ( + + ) : ( +

+ Loading open-source notices… +

+ )} +
+ ); +} diff --git a/apps/web/src/components/settings/SettingsBreadcrumb.tsx b/apps/web/src/components/settings/SettingsBreadcrumb.tsx index 5c1cf73f70da..a21b58d03e0b 100644 --- a/apps/web/src/components/settings/SettingsBreadcrumb.tsx +++ b/apps/web/src/components/settings/SettingsBreadcrumb.tsx @@ -36,6 +36,7 @@ import { const SETTINGS_BREADCRUMB_LABELS: Readonly> = { ...SETTINGS_SECTION_LABELS, "/settings/diagnostics": "Diagnostics", + "/settings/open-source-licenses": "Open source licenses", }; function settingsBreadcrumbLabel(pathname: string): string | null { diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 17af7cc9a115..9a4ccd2386e3 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -2907,6 +2907,19 @@ export function GeneralSettingsPanel() { } /> + } + size="xs" + variant="outline" + > + View licenses + + } + />
diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx index 1893a59acb42..e5b5a887f899 100644 --- a/apps/web/src/components/settings/SettingsSidebarNav.tsx +++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx @@ -322,7 +322,14 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { {navItems.map((item) => { const Icon = item.icon; - const isActive = pathname === item.to || pathname.startsWith(`${item.to}/`); + const isGeneralDetailPage = + item.to === "/settings/general" && + (pathname === "/settings/diagnostics" || + pathname === "/settings/open-source-licenses"); + const isActive = + isGeneralDetailPage || + pathname === item.to || + pathname.startsWith(`${item.to}/`); return ( SettingsRoute, } as any) +const SettingsOpenSourceLicensesRoute = + SettingsOpenSourceLicensesRouteImport.update({ + id: '/open-source-licenses', + path: '/open-source-licenses', + getParentRoute: () => SettingsRoute, + } as any) const SettingsKeybindingsRoute = SettingsKeybindingsRouteImport.update({ id: '/keybindings', path: '/keybindings', @@ -167,6 +174,7 @@ export interface FileRoutesByFullPath { '/settings/integrations': typeof SettingsIntegrationsRoute '/settings/keybindings': typeof SettingsKeybindingsRoute '/settings/projects': typeof SettingsProjectsRoute + '/settings/open-source-licenses': typeof SettingsOpenSourceLicensesRoute '/settings/providers': typeof SettingsProvidersRoute '/settings/snap-shot': typeof SettingsSnapShotRoute '/settings/source-control': typeof SettingsSourceControlRoute @@ -190,6 +198,7 @@ export interface FileRoutesByTo { '/settings/integrations': typeof SettingsIntegrationsRoute '/settings/keybindings': typeof SettingsKeybindingsRoute '/settings/projects': typeof SettingsProjectsRoute + '/settings/open-source-licenses': typeof SettingsOpenSourceLicensesRoute '/settings/providers': typeof SettingsProvidersRoute '/settings/snap-shot': typeof SettingsSnapShotRoute '/settings/source-control': typeof SettingsSourceControlRoute @@ -216,6 +225,7 @@ export interface FileRoutesById { '/settings/integrations': typeof SettingsIntegrationsRoute '/settings/keybindings': typeof SettingsKeybindingsRoute '/settings/projects': typeof SettingsProjectsRoute + '/settings/open-source-licenses': typeof SettingsOpenSourceLicensesRoute '/settings/providers': typeof SettingsProvidersRoute '/settings/snap-shot': typeof SettingsSnapShotRoute '/settings/source-control': typeof SettingsSourceControlRoute @@ -243,6 +253,7 @@ export interface FileRouteTypes { | '/settings/integrations' | '/settings/keybindings' | '/settings/projects' + | '/settings/open-source-licenses' | '/settings/providers' | '/settings/snap-shot' | '/settings/source-control' @@ -266,6 +277,7 @@ export interface FileRouteTypes { | '/settings/integrations' | '/settings/keybindings' | '/settings/projects' + | '/settings/open-source-licenses' | '/settings/providers' | '/settings/snap-shot' | '/settings/source-control' @@ -291,6 +303,7 @@ export interface FileRouteTypes { | '/settings/integrations' | '/settings/keybindings' | '/settings/projects' + | '/settings/open-source-licenses' | '/settings/providers' | '/settings/snap-shot' | '/settings/source-control' @@ -389,6 +402,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SettingsProjectsRouteImport parentRoute: typeof SettingsRoute } + '/settings/open-source-licenses': { + id: '/settings/open-source-licenses' + path: '/open-source-licenses' + fullPath: '/settings/open-source-licenses' + preLoaderRoute: typeof SettingsOpenSourceLicensesRouteImport + parentRoute: typeof SettingsRoute + } '/settings/keybindings': { id: '/settings/keybindings' path: '/keybindings' @@ -501,6 +521,7 @@ interface SettingsRouteChildren { SettingsIntegrationsRoute: typeof SettingsIntegrationsRoute SettingsKeybindingsRoute: typeof SettingsKeybindingsRoute SettingsProjectsRoute: typeof SettingsProjectsRoute + SettingsOpenSourceLicensesRoute: typeof SettingsOpenSourceLicensesRoute SettingsProvidersRoute: typeof SettingsProvidersRoute SettingsSnapShotRoute: typeof SettingsSnapShotRoute SettingsSourceControlRoute: typeof SettingsSourceControlRoute @@ -515,6 +536,7 @@ const SettingsRouteChildren: SettingsRouteChildren = { SettingsIntegrationsRoute: SettingsIntegrationsRoute, SettingsKeybindingsRoute: SettingsKeybindingsRoute, SettingsProjectsRoute: SettingsProjectsRoute, + SettingsOpenSourceLicensesRoute: SettingsOpenSourceLicensesRoute, SettingsProvidersRoute: SettingsProvidersRoute, SettingsSnapShotRoute: SettingsSnapShotRoute, SettingsSourceControlRoute: SettingsSourceControlRoute, diff --git a/apps/web/src/routes/settings.open-source-licenses.tsx b/apps/web/src/routes/settings.open-source-licenses.tsx new file mode 100644 index 000000000000..1737b0b27f40 --- /dev/null +++ b/apps/web/src/routes/settings.open-source-licenses.tsx @@ -0,0 +1,7 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { OpenSourceLicensesPanel } from "../components/settings/OpenSourceLicenses"; + +export const Route = createFileRoute("/settings/open-source-licenses")({ + component: OpenSourceLicensesPanel, +}); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 65b04800e837..a49328545a94 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -32,6 +32,7 @@ "vercel.ts", "test", "scripts/warm-dep-cache.ts", - "../../scripts/lib/public-config.ts" + "../../scripts/lib/public-config.ts", + "../../scripts/lib/third-party-licenses.ts" ] } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index ec4f15fc7eeb..1d710f273cd0 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -12,6 +12,7 @@ import pkg from "./package.json" with { type: "json" }; import { DEV_PROXIED_PATH_PREFIXES } from "@t3tools/shared/devProxy"; import { loadRepoEnv } from "../../scripts/lib/public-config"; +import { thirdPartyLicensesPlugin } from "../../scripts/lib/third-party-licenses"; import { tailwindPlugins } from "./vite/tailwind"; const repoEnv = loadRepoEnv(); @@ -157,6 +158,15 @@ export default defineConfig(() => { assetsInclude: ["**/*.wasm"], plugins: [ devCompressionPlugin(), + thirdPartyLicensesPlugin({ + bundleName: "web", + configFile: new URL("../../third-party-licenses.config.json", import.meta.url), + packageManifests: [ + { bundle: "web", path: new URL("./package.json", import.meta.url) }, + { bundle: "server", path: new URL("../server/package.json", import.meta.url) }, + { bundle: "desktop", path: new URL("../desktop/package.json", import.meta.url) }, + ], + }), // Route components load as split chunks so settings, pull-request, and // usage code stay out of the cold-start payload; the router prefetches // them on navigation intent (see getRouter's defaultPreload). diff --git a/docs/internals/open-source-licenses.md b/docs/internals/open-source-licenses.md new file mode 100644 index 000000000000..fcec578b4890 --- /dev/null +++ b/docs/internals/open-source-licenses.md @@ -0,0 +1,84 @@ +# Open source license notices + +License notices are generated independently for the client that ships them: + +- The web build emits `third-party-licenses.json` beside `index.html`. The Settings page loads that + static file, so the same artifact works in hosted web, the client bundled with `npx t3`, and + desktop. +- The mobile Metro config generates an ignored virtual module before each development, native, or + over-the-air JavaScript bundle. Mobile reads the manifest directly from that module and does not + need a network request. + +Neither path depends on the connected environment or an RPC. + +## What the build collects + +The generator follows installed production and optional dependencies, including dependencies of +workspace packages, and omits first-party `@t3tools/*` packages. The web manifest starts from the +web, server, and desktop package manifests. The mobile manifest starts from the mobile package +manifest. During the web bundle, the generator also checks emitted module ids to catch a bundled +npm import missing from a package manifest. + +The mobile manifest deliberately follows the complete production dependency closure declared by +Expo and React Native. That is conservative and can include build tooling that is not present in +the final JavaScript bundle, but it avoids dropping a notice when platform bundling changes. + +The build fails when a collected package has no distributable license identifier or contains no +license or notice file. This turns missing attribution into a build error instead of an empty row +in a release. + +## Custom notices and package overrides + +The repository-level `third-party-licenses.config.json` holds manually maintained exceptions for +all clients. Add an entry to `customNotices` for adapted icons, fonts, media, native modules, or +another asset that did not come from an npm package: + +```json +{ + "name": "asset-name", + "license": "CC-BY-4.0", + "noticeFile": "licenses/asset-name.txt", + "sourceUrl": "https://example.com/source", + "bundles": ["assets", "web"] +} +``` + +Paths in `noticeFile` are relative to the config file. The notice file should contain the complete +copyright, attribution, and license text required for redistribution. `bundles` controls which +generated manifests include the entry; omit it only when the notice belongs in every client. + +Use `packageOverrides` only when an installed npm archive omits its notice or has incorrect +metadata: + +```json +{ + "name": "package-name", + "version": "1.2.3", + "noticeFile": "licenses/package-name-1.2.3.txt", + "license": "MIT", + "sourceUrl": "https://example.com/package-name" +} +``` + +`version`, `license`, and `sourceUrl` are optional. Omitting `version` applies the override to every +installed version of that package. An override can use `repositoryUrl` instead of `name` when +several packages from one monorepo share the same notice: + +```json +{ + "repositoryUrl": "https://github.com/example/project", + "noticeFile": "licenses/project.txt" +} +``` + +The generator also reuses an installed sibling package's notice when both packages declare the +same normalized repository and license. A name-and-version override always wins over these +repository fallbacks. + +The `@react-grab/cli` override uses the root React Grab repository's MIT license because the CLI's +npm archive omits both its license field and license file. Keep the override until the published +CLI package carries that metadata itself. + +Generated mobile files live under `apps/mobile/.generated/` and are ignored. Do not commit or edit +them; updating dependencies, configuration, or a notice file is enough for the next Metro startup +to refresh the manifest. diff --git a/docs/user/open-source-licenses.md b/docs/user/open-source-licenses.md new file mode 100644 index 000000000000..9ddc90776ac9 --- /dev/null +++ b/docs/user/open-source-licenses.md @@ -0,0 +1,12 @@ +# Open source licenses + +T3 Code includes third-party software and adapted assets. To read their license and attribution +notices: + +- On web and desktop, open **Settings → General**, find **Open source licenses** under **About**, + and select **View licenses**. +- On mobile, open **Settings → App → Open source licenses**. + +The page lists the package version, license identifier, and the parts of T3 Code that may include +it. Select a row to read the complete notice text. Use the search field to find a package, version, +license, or app component. diff --git a/packages/shared/package.json b/packages/shared/package.json index bda80d937b92..4d4fa4bf015b 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -275,6 +275,10 @@ "types": "./src/desktopAppControl.ts", "import": "./src/desktopAppControl.ts" }, + "./thirdPartyLicenses": { + "types": "./src/thirdPartyLicenses.ts", + "import": "./src/thirdPartyLicenses.ts" + }, "./claudeCompaction": { "types": "./src/claudeCompaction.ts", "import": "./src/claudeCompaction.ts" diff --git a/packages/shared/src/thirdPartyLicenses.test.ts b/packages/shared/src/thirdPartyLicenses.test.ts new file mode 100644 index 000000000000..a3f966e2620c --- /dev/null +++ b/packages/shared/src/thirdPartyLicenses.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { + decodeThirdPartyLicenseManifest, + filterThirdPartyLicenseEntries, + findThirdPartyLicenseEntry, + formatLicenseBundles, + thirdPartyLicenseEntryKey, + type ThirdPartyLicenseEntry, +} from "./thirdPartyLicenses.js"; + +const ENTRIES: ReadonlyArray = [ + { + bundles: ["web"], + kind: "package", + license: "MIT", + name: "react", + noticeText: "React license", + sourceUrl: "https://react.dev", + version: "19.2.6", + }, + { + bundles: ["assets", "mobile"], + kind: "custom", + license: "CC-BY-4.0", + name: "sample-icons", + noticeText: "Icon notice", + sourceUrl: null, + version: null, + }, +]; + +describe("third-party license manifests", () => { + it("decodes the generated manifest shape", () => { + expect(decodeThirdPartyLicenseManifest({ schemaVersion: 1, entries: ENTRIES })).toEqual({ + schemaVersion: 1, + entries: ENTRIES, + }); + }); + + it("rejects unsupported manifest versions", () => { + expect(() => decodeThirdPartyLicenseManifest({ schemaVersion: 2, entries: [] })).toThrow( + "unsupported format", + ); + }); + + it("filters by package, license, version, and bundle", () => { + expect(filterThirdPartyLicenseEntries(ENTRIES, "react 19.2")).toEqual([ENTRIES[0]]); + expect(filterThirdPartyLicenseEntries(ENTRIES, "cc-by mobile")).toEqual([ENTRIES[1]]); + expect(filterThirdPartyLicenseEntries(ENTRIES, "apache")).toEqual([]); + }); + + it("formats platform bundle names for display", () => { + expect(formatLicenseBundles(["android", "assets", "ios", "mobile", "plugin"])).toBe( + "Android, Assets, iOS, Mobile, plugin", + ); + }); + + it("finds an entry by its stable navigation key", () => { + const entry = ENTRIES[0]!; + expect(findThirdPartyLicenseEntry(ENTRIES, thirdPartyLicenseEntryKey(entry))).toBe(entry); + }); +}); diff --git a/packages/shared/src/thirdPartyLicenses.ts b/packages/shared/src/thirdPartyLicenses.ts new file mode 100644 index 000000000000..b5e5c913107c --- /dev/null +++ b/packages/shared/src/thirdPartyLicenses.ts @@ -0,0 +1,103 @@ +export interface ThirdPartyLicenseEntry { + readonly bundles: ReadonlyArray; + readonly kind: "custom" | "package"; + readonly license: string; + readonly name: string; + readonly noticeText: string; + readonly sourceUrl: string | null; + readonly version: string | null; +} + +export interface ThirdPartyLicenseManifest { + readonly schemaVersion: 1; + readonly entries: ReadonlyArray; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function isStringArray(value: unknown): value is ReadonlyArray { + return Array.isArray(value) && value.every((entry) => typeof entry === "string"); +} + +function decodeEntry(value: unknown, index: number): ThirdPartyLicenseEntry { + if (!isRecord(value)) { + throw new Error(`License entry ${String(index + 1)} is not an object.`); + } + if ( + !isStringArray(value.bundles) || + value.bundles.length === 0 || + (value.kind !== "custom" && value.kind !== "package") || + typeof value.license !== "string" || + typeof value.name !== "string" || + typeof value.noticeText !== "string" || + (value.sourceUrl !== null && typeof value.sourceUrl !== "string") || + (value.version !== null && typeof value.version !== "string") + ) { + throw new Error(`License entry ${String(index + 1)} has an invalid shape.`); + } + return { + bundles: value.bundles, + kind: value.kind, + license: value.license, + name: value.name, + noticeText: value.noticeText, + sourceUrl: value.sourceUrl, + version: value.version, + }; +} + +export function decodeThirdPartyLicenseManifest(value: unknown): ThirdPartyLicenseManifest { + if (!isRecord(value) || value.schemaVersion !== 1 || !Array.isArray(value.entries)) { + throw new Error("The open-source license manifest has an unsupported format."); + } + return { + schemaVersion: 1, + entries: value.entries.map(decodeEntry), + }; +} + +export function filterThirdPartyLicenseEntries( + entries: ReadonlyArray, + query: string, +): ReadonlyArray { + const terms = query + .trim() + .toLocaleLowerCase() + .split(/\s+/) + .filter((term) => term.length > 0); + if (terms.length === 0) return entries; + return entries.filter((entry) => { + const searchable = [entry.name, entry.version, entry.license, ...entry.bundles] + .filter((value): value is string => value !== null) + .join(" ") + .toLocaleLowerCase(); + return terms.every((term) => searchable.includes(term)); + }); +} + +const BUNDLE_LABELS: Readonly> = { + android: "Android", + assets: "Assets", + desktop: "Desktop", + ios: "iOS", + mobile: "Mobile", + server: "Server", + web: "Web", +}; + +export function formatLicenseBundles(bundles: ReadonlyArray): string { + return bundles.map((bundle) => BUNDLE_LABELS[bundle] ?? bundle).join(", "); +} + +export function thirdPartyLicenseEntryKey(entry: ThirdPartyLicenseEntry): string { + return `${entry.kind}:${entry.name}:${entry.version ?? "custom"}`; +} + +export function findThirdPartyLicenseEntry( + entries: ReadonlyArray, + key: string, +): ThirdPartyLicenseEntry | undefined { + return entries.find((entry) => thirdPartyLicenseEntryKey(entry) === key); +} diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts new file mode 100644 index 000000000000..22f9d623248d --- /dev/null +++ b/scripts/lib/third-party-licenses.test.ts @@ -0,0 +1,245 @@ +// @effect-diagnostics nodeBuiltinImport:off - Tests exercise the Node filesystem build boundary. + +import * as NodeFSP from "node:fs/promises"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; + +import { afterEach, describe, expect, it } from "vite-plus/test"; + +import { generateThirdPartyLicenseManifest } from "./third-party-licenses.js"; + +const tempDirectories: string[] = []; + +async function writeJson(path: string, value: unknown): Promise { + await NodeFSP.mkdir(NodePath.dirname(path), { recursive: true }); + await NodeFSP.writeFile(path, `${JSON.stringify(value)}\n`, "utf8"); +} + +async function createFixture(): Promise<{ + readonly appManifest: string; + readonly configFile: string; + readonly dependencyRoot: string; + readonly root: string; +}> { + const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3code-licenses-")); + tempDirectories.push(root); + const appManifest = NodePath.join(root, "package.json"); + const dependencyRoot = NodePath.join(root, "node_modules", "demo-dependency"); + const configFile = NodePath.join(root, "third-party-licenses.config.json"); + + await writeJson(appManifest, { + name: "fixture-app", + dependencies: { "demo-dependency": "1.2.3" }, + }); + await writeJson(NodePath.join(dependencyRoot, "package.json"), { + name: "demo-dependency", + version: "1.2.3", + license: "MIT", + main: "index.js", + repository: "example/demo-dependency", + }); + await NodeFSP.writeFile(NodePath.join(dependencyRoot, "index.js"), "export {};\n", "utf8"); + await NodeFSP.writeFile( + NodePath.join(dependencyRoot, "LICENSE"), + "Demo MIT license text\n", + "utf8", + ); + await NodeFSP.writeFile(NodePath.join(root, "asset-notice.txt"), "Asset notice text\n", "utf8"); + await writeJson(configFile, { + customNotices: [ + { + name: "demo-asset", + license: "CC-BY-4.0", + noticeFile: "asset-notice.txt", + bundles: ["assets", "web"], + }, + ], + packageOverrides: [], + }); + return { appManifest, configFile, dependencyRoot, root }; +} + +afterEach(async () => { + await Promise.all( + tempDirectories + .splice(0) + .map((directory) => NodeFSP.rm(directory, { force: true, recursive: true })), + ); +}); + +describe("third-party license generation", () => { + it("collects production packages and custom asset notices", async () => { + const fixture = await createFixture(); + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest).toEqual({ + schemaVersion: 1, + entries: [ + { + bundles: ["assets", "web"], + kind: "custom", + license: "CC-BY-4.0", + name: "demo-asset", + noticeText: "Asset notice text", + sourceUrl: null, + version: null, + }, + { + bundles: ["web"], + kind: "package", + license: "MIT", + name: "demo-dependency", + noticeText: "Demo MIT license text", + sourceUrl: "https://github.com/example/demo-dependency", + version: "1.2.3", + }, + ], + }); + }); + + it("fails when a production package has no distributable notice text", async () => { + const fixture = await createFixture(); + await NodeFSP.rm(NodePath.join(fixture.dependencyRoot, "LICENSE")); + + await expect( + generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }), + ).rejects.toThrow("does not include a license or notice file"); + }); + + it("collects notices nested inside a published package", async () => { + const fixture = await createFixture(); + await NodeFSP.rm(NodePath.join(fixture.dependencyRoot, "LICENSE")); + await NodeFSP.mkdir(NodePath.join(fixture.dependencyRoot, "dist", "third-party"), { + recursive: true, + }); + await NodeFSP.writeFile( + NodePath.join(fixture.dependencyRoot, "dist", "third-party", "NOTICE.txt"), + "Nested notice\n", + "utf8", + ); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries.find((entry) => entry.name === "demo-dependency")?.noticeText).toBe( + "Nested notice", + ); + }); + + it("uses package overrides for notices published outside the npm archive", async () => { + const fixture = await createFixture(); + await NodeFSP.rm(NodePath.join(fixture.dependencyRoot, "LICENSE")); + await NodeFSP.writeFile(NodePath.join(fixture.root, "override.txt"), "Override text\n", "utf8"); + await writeJson(fixture.configFile, { + customNotices: [], + packageOverrides: [ + { + name: "demo-dependency", + noticeFile: "override.txt", + }, + ], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries[0]?.noticeText).toBe("Override text"); + }); + + it("applies repository overrides across monorepo packages", async () => { + const fixture = await createFixture(); + await NodeFSP.rm(NodePath.join(fixture.dependencyRoot, "LICENSE")); + await NodeFSP.writeFile( + NodePath.join(fixture.root, "override.txt"), + "Repository text\n", + "utf8", + ); + await writeJson(fixture.configFile, { + customNotices: [], + packageOverrides: [ + { + repositoryUrl: "https://github.com/example/demo-dependency", + noticeFile: "override.txt", + }, + ], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries[0]?.noticeText).toBe("Repository text"); + }); + + it("reuses a repository license for packages from the same monorepo", async () => { + const fixture = await createFixture(); + const siblingRoot = NodePath.join(fixture.root, "node_modules", "demo-sibling"); + await writeJson(fixture.appManifest, { + name: "fixture-app", + dependencies: { "demo-dependency": "1.2.3", "demo-sibling": "2.0.0" }, + }); + await writeJson(NodePath.join(siblingRoot, "package.json"), { + name: "demo-sibling", + version: "2.0.0", + license: "MIT", + main: "index.js", + repository: "example/demo-dependency", + }); + await NodeFSP.writeFile(NodePath.join(siblingRoot, "index.js"), "export {};\n", "utf8"); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries.find((entry) => entry.name === "demo-sibling")?.noticeText).toBe( + "Demo MIT license text", + ); + }); + + it("omits custom notices for other bundles", async () => { + const fixture = await createFixture(); + await writeJson(fixture.configFile, { + customNotices: [ + { + name: "web-only-asset", + license: "MIT", + noticeFile: "asset-notice.txt", + bundles: ["assets", "web"], + }, + ], + packageOverrides: [], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "mobile", path: fixture.appManifest }], + }); + + expect(manifest.entries.some((entry) => entry.name === "web-only-asset")).toBe(false); + }); + + it("fails when a custom notice file is empty", async () => { + const fixture = await createFixture(); + await NodeFSP.writeFile(NodePath.join(fixture.root, "asset-notice.txt"), "\n", "utf8"); + + await expect( + generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }), + ).rejects.toThrow('Custom third-party notice "demo-asset" is empty'); + }); +}); +// @effect-diagnostics nodeBuiltinImport:off - Tests exercise the Node filesystem build boundary. diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts new file mode 100644 index 000000000000..b9eb02e7db67 --- /dev/null +++ b/scripts/lib/third-party-licenses.ts @@ -0,0 +1,710 @@ +// @effect-diagnostics nodeBuiltinImport:off - Vite's build plugin runs before an Effect runtime exists. + +import * as NodeFSP from "node:fs/promises"; +import * as NodePath from "node:path"; +import * as NodeURL from "node:url"; +import * as NodeModule from "node:module"; + +import type { Plugin } from "vite-plus"; + +export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json"; + +export interface ThirdPartyLicenseEntry { + readonly bundles: ReadonlyArray; + readonly kind: "custom" | "package"; + readonly license: string; + readonly name: string; + readonly noticeText: string; + readonly sourceUrl: string | null; + readonly version: string | null; +} + +export interface ThirdPartyLicenseManifest { + readonly schemaVersion: 1; + readonly entries: ReadonlyArray; +} + +export interface ThirdPartyLicensePackageManifest { + readonly bundle: string; + readonly path: string | URL; +} + +export interface ThirdPartyLicensesPluginOptions { + readonly configFile?: string | URL; + readonly packageManifests: ReadonlyArray; + readonly bundleName: string; +} + +interface PackageJson { + readonly dependencies?: Readonly>; + readonly homepage?: unknown; + readonly license?: unknown; + readonly licenses?: unknown; + readonly name?: unknown; + readonly optionalDependencies?: Readonly>; + readonly repository?: unknown; + readonly version?: unknown; +} + +interface CustomNoticeConfigEntry { + readonly bundles?: ReadonlyArray; + readonly license: string; + readonly name: string; + readonly noticeFile: string; + readonly sourceUrl?: string; + readonly version?: string; +} + +interface PackageNoticeOverrideConfigEntry { + readonly license?: string; + readonly name?: string; + readonly noticeFile?: string; + readonly repositoryUrl?: string; + readonly sourceUrl?: string; + readonly version?: string; +} + +interface ThirdPartyLicensesConfig { + readonly customNotices: ReadonlyArray; + readonly packageOverrides: ReadonlyArray; +} + +interface CollectedPackage { + readonly bundles: Set; + readonly packageJson: PackageJson; + readonly packageRoot: string; +} + +interface PackageCollection { + readonly byIdentity: Map; +} + +const EMPTY_CONFIG: ThirdPartyLicensesConfig = { + customNotices: [], + packageOverrides: [], +}; + +const NOTICE_FILE_PATTERN = /^(?:licen[cs]e|copying|notice)(?:[._-].*)?$/i; +const FIRST_PARTY_PACKAGE_PREFIX = "@t3tools/"; + +function asPath(value: string | URL): string { + return value instanceof URL ? NodeURL.fileURLToPath(value) : NodePath.resolve(value); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function readRequiredString(value: Record, key: string, context: string): string { + const field = value[key]; + if (typeof field !== "string" || field.trim().length === 0) { + throw new Error(`${context} must define a non-empty "${key}" string.`); + } + return field.trim(); +} + +function readOptionalString( + value: Record, + key: string, + context: string, +): string | undefined { + const field = value[key]; + if (field === undefined) return undefined; + if (typeof field !== "string" || field.trim().length === 0) { + throw new Error(`${context} must define "${key}" as a non-empty string when present.`); + } + return field.trim(); +} + +function readOptionalStringArray( + value: Record, + key: string, + context: string, +): ReadonlyArray | undefined { + const field = value[key]; + if (field === undefined) return undefined; + if ( + !Array.isArray(field) || + field.length === 0 || + field.some((entry) => typeof entry !== "string" || entry.trim().length === 0) + ) { + throw new Error(`${context} must define "${key}" as a non-empty string array.`); + } + return field.map((entry) => (entry as string).trim()); +} + +function decodeCustomNotices(value: unknown): ReadonlyArray { + if (value === undefined) return []; + if (!Array.isArray(value)) { + throw new Error('Third-party license config field "customNotices" must be an array.'); + } + return value.map((entry, index) => { + const context = `Third-party custom notice at index ${String(index)}`; + if (!isRecord(entry)) throw new Error(`${context} must be an object.`); + const version = readOptionalString(entry, "version", context); + const sourceUrl = readOptionalString(entry, "sourceUrl", context); + const bundles = readOptionalStringArray(entry, "bundles", context); + return { + name: readRequiredString(entry, "name", context), + license: readRequiredString(entry, "license", context), + noticeFile: readRequiredString(entry, "noticeFile", context), + ...(version !== undefined ? { version } : {}), + ...(sourceUrl !== undefined ? { sourceUrl } : {}), + ...(bundles !== undefined ? { bundles } : {}), + }; + }); +} + +function decodePackageOverrides(value: unknown): ReadonlyArray { + if (value === undefined) return []; + if (!Array.isArray(value)) { + throw new Error('Third-party license config field "packageOverrides" must be an array.'); + } + return value.map((entry, index) => { + const context = `Third-party package override at index ${String(index)}`; + if (!isRecord(entry)) throw new Error(`${context} must be an object.`); + const name = readOptionalString(entry, "name", context); + const repositoryUrl = readOptionalString(entry, "repositoryUrl", context); + if ((name === undefined) === (repositoryUrl === undefined)) { + throw new Error(`${context} must define exactly one of "name" or "repositoryUrl".`); + } + const version = readOptionalString(entry, "version", context); + const license = readOptionalString(entry, "license", context); + const noticeFile = readOptionalString(entry, "noticeFile", context); + const sourceUrl = readOptionalString(entry, "sourceUrl", context); + return { + ...(name !== undefined ? { name } : {}), + ...(repositoryUrl !== undefined ? { repositoryUrl } : {}), + ...(version !== undefined ? { version } : {}), + ...(license !== undefined ? { license } : {}), + ...(noticeFile !== undefined ? { noticeFile } : {}), + ...(sourceUrl !== undefined ? { sourceUrl } : {}), + }; + }); +} + +async function readConfig(configFile: string | URL | undefined): Promise<{ + readonly config: ThirdPartyLicensesConfig; + readonly directory: string; +}> { + if (configFile === undefined) { + return { config: EMPTY_CONFIG, directory: NodePath.resolve(".") }; + } + const configPath = asPath(configFile); + const source = await NodeFSP.readFile(configPath, "utf8"); + const decoded = JSON.parse(source) as unknown; + if (!isRecord(decoded)) throw new Error("Third-party license config must contain an object."); + return { + config: { + customNotices: decodeCustomNotices(decoded.customNotices), + packageOverrides: decodePackageOverrides(decoded.packageOverrides), + }, + directory: NodePath.dirname(configPath), + }; +} + +async function readPackageJson(packageJsonPath: string): Promise { + const source = await NodeFSP.readFile(packageJsonPath, "utf8"); + const value = JSON.parse(source) as unknown; + if (!isRecord(value)) throw new Error(`Package manifest is not an object: ${packageJsonPath}`); + return value as PackageJson; +} + +function packageIdentity(packageJson: PackageJson, packageRoot: string): string { + const name = + typeof packageJson.name === "string" ? packageJson.name : NodePath.basename(packageRoot); + const version = typeof packageJson.version === "string" ? packageJson.version : "unknown"; + return `${name}@${version}`; +} + +async function findPackageRoot( + resolvedPath: string, + expectedName?: string, +): Promise<{ readonly packageJson: PackageJson; readonly packageRoot: string } | null> { + let current = NodePath.dirname(resolvedPath); + const root = NodePath.parse(current).root; + + while (current !== root) { + const packageJsonPath = NodePath.join(current, "package.json"); + try { + const packageJson = await readPackageJson(packageJsonPath); + const matchesExpectedPackage = + expectedName !== undefined + ? packageJson.name === expectedName + : typeof packageJson.name === "string" && typeof packageJson.version === "string"; + if (matchesExpectedPackage) { + return { packageJson, packageRoot: await NodeFSP.realpath(current) }; + } + } catch (error) { + const code = isRecord(error) && typeof error.code === "string" ? error.code : null; + if (code !== "ENOENT" && code !== "ENOTDIR") throw error; + } + current = NodePath.dirname(current); + } + return null; +} + +async function resolveDependencyPackage( + dependencyName: string, + fromPackageJsonPath: string, +): Promise<{ readonly packageJson: PackageJson; readonly packageRoot: string } | null> { + const requireFromPackage = NodeModule.createRequire(fromPackageJsonPath); + const candidates = [`${dependencyName}/package.json`, dependencyName]; + for (const candidate of candidates) { + try { + const resolved = requireFromPackage.resolve(candidate); + const found = await findPackageRoot(resolved, dependencyName); + if (found) return found; + } catch (error) { + const code = isRecord(error) && typeof error.code === "string" ? error.code : null; + if (code !== "MODULE_NOT_FOUND" && code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") throw error; + } + } + return null; +} + +function dependencyNames(packageJson: PackageJson): ReadonlyArray { + return [ + ...new Set([ + ...Object.keys(packageJson.dependencies ?? {}), + ...Object.keys(packageJson.optionalDependencies ?? {}), + ]), + ].sort((left, right) => left.localeCompare(right)); +} + +async function collectProductionDependencyPackages( + packageManifests: ReadonlyArray, +): Promise { + const collection: PackageCollection = { + byIdentity: new Map(), + }; + const visited = new Set(); + + const visitManifest = async (packageJsonPath: string, bundle: string): Promise => { + const packageJson = await readPackageJson(packageJsonPath); + for (const dependencyName of dependencyNames(packageJson)) { + const resolved = await resolveDependencyPackage(dependencyName, packageJsonPath); + if (!resolved) continue; + const visitKey = `${bundle}:${resolved.packageRoot}`; + if (visited.has(visitKey)) continue; + visited.add(visitKey); + + const dependencyPackageJsonPath = NodePath.join(resolved.packageRoot, "package.json"); + const name = + typeof resolved.packageJson.name === "string" ? resolved.packageJson.name : dependencyName; + if (!name.startsWith(FIRST_PARTY_PACKAGE_PREFIX)) { + const identity = packageIdentity(resolved.packageJson, resolved.packageRoot); + const existing = collection.byIdentity.get(identity); + if (existing) { + existing.bundles.add(bundle); + } else { + collection.byIdentity.set(identity, { + bundles: new Set([bundle]), + packageJson: resolved.packageJson, + packageRoot: resolved.packageRoot, + }); + } + } + + await visitManifest(dependencyPackageJsonPath, bundle); + } + }; + + for (const manifest of packageManifests) { + await visitManifest(asPath(manifest.path), manifest.bundle); + } + return collection; +} + +function moduleFilePath(moduleId: string): string | null { + if (moduleId.startsWith("\0") || moduleId.includes("\0")) return null; + const withoutQuery = moduleId.split(/[?#]/, 1)[0] ?? moduleId; + const filePath = withoutQuery.startsWith("file:") + ? NodeURL.fileURLToPath(withoutQuery) + : withoutQuery.replace(/^\/@fs\//, "/"); + const normalized = filePath.replaceAll("\\", "/"); + return normalized.includes("/node_modules/") ? filePath : null; +} + +async function addBundledModulePackages( + collection: PackageCollection, + moduleIds: ReadonlyArray, + bundle: string, +): Promise { + for (const moduleId of moduleIds) { + const filePath = moduleFilePath(moduleId); + if (!filePath) continue; + let found: Awaited>; + try { + found = await findPackageRoot(await NodeFSP.realpath(filePath)); + } catch (error) { + const code = isRecord(error) && typeof error.code === "string" ? error.code : null; + if (code === "ENOENT" || code === "ENOTDIR") continue; + throw error; + } + if (!found || typeof found.packageJson.name !== "string") continue; + if (found.packageJson.name.startsWith(FIRST_PARTY_PACKAGE_PREFIX)) continue; + const identity = packageIdentity(found.packageJson, found.packageRoot); + const existing = collection.byIdentity.get(identity); + if (existing) { + existing.bundles.add(bundle); + } else { + collection.byIdentity.set(identity, { + bundles: new Set([bundle]), + packageJson: found.packageJson, + packageRoot: found.packageRoot, + }); + } + } +} + +function normalizeLicense(packageJson: PackageJson): string | null { + if (typeof packageJson.license === "string" && packageJson.license.trim().length > 0) { + return packageJson.license.trim(); + } + if (isRecord(packageJson.license) && typeof packageJson.license.type === "string") { + return packageJson.license.type.trim() || null; + } + if (Array.isArray(packageJson.licenses)) { + const licenses = packageJson.licenses + .map((entry) => { + if (typeof entry === "string") return entry.trim(); + if (isRecord(entry) && typeof entry.type === "string") return entry.type.trim(); + return ""; + }) + .filter((entry) => entry.length > 0); + if (licenses.length > 0) return licenses.join(" OR "); + } + return null; +} + +function normalizeRepositoryUrl(value: unknown): string | null { + const raw = + typeof value === "string" + ? value + : isRecord(value) && typeof value.url === "string" + ? value.url + : null; + if (!raw) return null; + const trimmed = raw.trim(); + if (trimmed.startsWith("github:")) return `https://github.com/${trimmed.slice(7)}`; + const normalized = trimmed + .replace(/^git\+ssh:\/\/git@github\.com\//, "https://github.com/") + .replace(/^git\+/, "") + .replace(/^git@github\.com:/, "https://github.com/") + .replace(/^ssh:\/\/(?:git@)?github\.com\//, "https://github.com/") + .replace(/^git:\/\/github\.com\//, "https://github.com/") + .replace(/\.git$/, ""); + if (/^[\w.-]+\/[\w.-]+$/.test(normalized)) return `https://github.com/${normalized}`; + return normalized; +} + +function packageSourceUrl(packageJson: PackageJson): string | null { + if (typeof packageJson.homepage === "string" && packageJson.homepage.trim().length > 0) { + return packageJson.homepage.trim(); + } + return normalizeRepositoryUrl(packageJson.repository); +} + +async function readPackageNoticeText(packageRoot: string): Promise { + const noticeFiles: string[] = []; + const rootEntries = await NodeFSP.readdir(packageRoot, { withFileTypes: true }); + noticeFiles.push( + ...rootEntries + .filter((entry) => entry.isFile() && NOTICE_FILE_PATTERN.test(entry.name)) + .map((entry) => entry.name), + ); + + const collectNestedNoticeFiles = async (directory: string, depth: number): Promise => { + const directoryEntries = await NodeFSP.readdir(NodePath.join(packageRoot, directory), { + withFileTypes: true, + }); + await Promise.all( + directoryEntries.map(async (entry) => { + const relativePath = NodePath.join(directory, entry.name); + if (entry.isFile() && NOTICE_FILE_PATTERN.test(entry.name)) { + noticeFiles.push(relativePath); + return; + } + if ( + depth > 0 && + entry.isDirectory() && + entry.name !== "node_modules" && + entry.name !== ".git" + ) { + await collectNestedNoticeFiles(relativePath, depth - 1); + } + }), + ); + }; + if (noticeFiles.length === 0) { + await Promise.all( + rootEntries + .filter( + (entry) => entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git", + ) + .map((entry) => collectNestedNoticeFiles(entry.name, 2)), + ); + } + noticeFiles.sort((left, right) => left.localeCompare(right)); + if (noticeFiles.length === 0) return null; + + const sections: string[] = []; + for (const fileName of noticeFiles) { + const contents = (await NodeFSP.readFile(NodePath.join(packageRoot, fileName), "utf8")).trim(); + if (contents.length === 0) continue; + sections.push(noticeFiles.length === 1 ? contents : `${fileName}\n\n${contents}`); + } + return sections.length > 0 ? sections.join("\n\n---\n\n") : null; +} + +function repositoryNoticeKey(packageJson: PackageJson, license: string): string | null { + const repositoryUrl = normalizeRepositoryUrl(packageJson.repository); + return repositoryUrl + ? `${repositoryUrl.toLocaleLowerCase()}\n${license.toLocaleLowerCase()}` + : null; +} + +async function collectRepositoryNotices( + collection: PackageCollection, + packageNotices: Map>, +): Promise> { + const notices = new Map(); + await Promise.all( + [...collection.byIdentity.values()].map(async (collected) => { + const license = normalizeLicense(collected.packageJson); + if (!license) return; + const key = repositoryNoticeKey(collected.packageJson, license); + if (!key || notices.has(key)) return; + const noticeText = await packageNoticeText(collected.packageRoot, packageNotices); + if (noticeText) notices.set(key, noticeText); + }), + ); + return notices; +} + +function packageNoticeText( + packageRoot: string, + cache: Map>, +): Promise { + const existing = cache.get(packageRoot); + if (existing) return existing; + const notice = readPackageNoticeText(packageRoot); + cache.set(packageRoot, notice); + return notice; +} + +function findPackageOverride( + overrides: ReadonlyArray, + name: string, + version: string, + packageJson: PackageJson, +): PackageNoticeOverrideConfigEntry | undefined { + const matchingVersion = (override: PackageNoticeOverrideConfigEntry) => + override.version === undefined || override.version === version; + const repositoryUrl = normalizeRepositoryUrl(packageJson.repository)?.toLocaleLowerCase(); + return ( + overrides.find((override) => override.name === name && override.version === version) ?? + overrides.find((override) => override.name === name && override.version === undefined) ?? + overrides.find( + (override) => + repositoryUrl !== undefined && + override.repositoryUrl !== undefined && + normalizeRepositoryUrl(override.repositoryUrl)?.toLocaleLowerCase() === repositoryUrl && + matchingVersion(override), + ) + ); +} + +async function packageEntry( + collected: CollectedPackage, + config: ThirdPartyLicensesConfig, + configDirectory: string, + packageNotices: Map>, + repositoryNotices: ReadonlyMap, +): Promise { + const name = + typeof collected.packageJson.name === "string" + ? collected.packageJson.name + : NodePath.basename(collected.packageRoot); + const version = + typeof collected.packageJson.version === "string" ? collected.packageJson.version : "unknown"; + const override = findPackageOverride( + config.packageOverrides, + name, + version, + collected.packageJson, + ); + const license = override?.license ?? normalizeLicense(collected.packageJson); + if (!license || /^(?:unlicensed|proprietary)$/i.test(license)) { + throw new Error( + `${name}@${version} does not declare a distributable license. Add a package override in the third-party license config if the package publishes its notice elsewhere.`, + ); + } + + const repositoryKey = repositoryNoticeKey(collected.packageJson, license); + const noticeText = override?.noticeFile + ? ( + await NodeFSP.readFile(NodePath.resolve(configDirectory, override.noticeFile), "utf8") + ).trim() + : ((await packageNoticeText(collected.packageRoot, packageNotices)) ?? + (repositoryKey ? repositoryNotices.get(repositoryKey) : undefined)); + if (!noticeText) { + throw new Error( + `${name}@${version} does not include a license or notice file. Add a package override with "noticeFile" in the third-party license config.`, + ); + } + + return { + bundles: [...collected.bundles].sort((left, right) => left.localeCompare(right)), + kind: "package", + license, + name, + noticeText, + sourceUrl: override?.sourceUrl ?? packageSourceUrl(collected.packageJson), + version, + }; +} + +async function customEntries( + config: ThirdPartyLicensesConfig, + configDirectory: string, + includedBundles: ReadonlySet, +): Promise> { + return Promise.all( + config.customNotices + .filter( + (notice) => + notice.bundles === undefined || + notice.bundles.some((bundle) => includedBundles.has(bundle)), + ) + .map(async (notice) => { + const noticeText = ( + await NodeFSP.readFile(NodePath.resolve(configDirectory, notice.noticeFile), "utf8") + ).trim(); + if (noticeText.length === 0) { + throw new Error(`Custom third-party notice "${notice.name}" is empty.`); + } + return { + bundles: [...(notice.bundles ?? ["assets"])].sort((left, right) => + left.localeCompare(right), + ), + kind: "custom" as const, + license: notice.license, + name: notice.name, + noticeText, + sourceUrl: notice.sourceUrl ?? null, + version: notice.version ?? null, + }; + }), + ); +} + +function entrySort(left: ThirdPartyLicenseEntry, right: ThirdPartyLicenseEntry): number { + return ( + left.name.localeCompare(right.name) || + (left.version ?? "").localeCompare(right.version ?? "") || + left.kind.localeCompare(right.kind) + ); +} + +export async function generateThirdPartyLicenseManifest(input: { + readonly configFile?: string | URL; + readonly packageManifests: ReadonlyArray; + readonly bundledModuleIds?: ReadonlyArray; + readonly bundleName?: string; +}): Promise { + const [{ config, directory }, collection] = await Promise.all([ + readConfig(input.configFile), + collectProductionDependencyPackages(input.packageManifests), + ]); + if (input.bundledModuleIds && input.bundleName) { + await addBundledModulePackages(collection, input.bundledModuleIds, input.bundleName); + } + + const packageNotices = new Map>(); + const repositoryNotices = await collectRepositoryNotices(collection, packageNotices); + + const packageEntryResults = await Promise.allSettled( + [...collection.byIdentity.values()].map((collected) => + packageEntry(collected, config, directory, packageNotices, repositoryNotices), + ), + ); + const failures = packageEntryResults.flatMap((result) => + result.status === "rejected" + ? [result.reason instanceof Error ? result.reason.message : String(result.reason)] + : [], + ); + if (failures.length > 0) { + throw new Error( + `Third-party license generation found ${String(failures.length)} invalid package notice${failures.length === 1 ? "" : "s"}:\n${failures.map((failure) => `- ${failure}`).join("\n")}`, + ); + } + const packageEntries = packageEntryResults.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ); + const includedBundles = new Set(input.packageManifests.map((manifest) => manifest.bundle)); + if (input.bundleName) includedBundles.add(input.bundleName); + const manualEntries = await customEntries(config, directory, includedBundles); + return { + schemaVersion: 1, + entries: [...packageEntries, ...manualEntries].sort(entrySort), + }; +} + +function moduleIdsFromBundle(bundle: unknown): ReadonlyArray { + const ids = new Set(); + if (!isRecord(bundle)) return []; + for (const output of Object.values(bundle)) { + if (!isRecord(output) || output.type !== "chunk" || !isRecord(output.modules)) continue; + for (const id of Object.keys(output.modules)) ids.add(id); + } + return [...ids]; +} + +function serializeManifest(manifest: ThirdPartyLicenseManifest): string { + return `${JSON.stringify(manifest)}\n`; +} + +export function thirdPartyLicensesPlugin(options: ThirdPartyLicensesPluginOptions): Plugin { + return { + name: "t3code:third-party-licenses", + configureServer(server) { + let manifestPromise: Promise | null = null; + server.middlewares.use((request, response, next) => { + if (request.url?.split("?", 1)[0] !== `/${THIRD_PARTY_LICENSES_FILE_NAME}`) { + next(); + return; + } + manifestPromise ??= generateThirdPartyLicenseManifest({ + packageManifests: options.packageManifests, + ...(options.configFile !== undefined ? { configFile: options.configFile } : {}), + }); + void manifestPromise.then( + (manifest) => { + response.statusCode = 200; + response.setHeader("Content-Type", "application/json; charset=utf-8"); + response.setHeader("Cache-Control", "no-store"); + response.end(serializeManifest(manifest)); + }, + (error: unknown) => { + next(error instanceof Error ? error : new Error(String(error))); + }, + ); + }); + }, + async generateBundle(_outputOptions, bundle) { + const manifest = await generateThirdPartyLicenseManifest({ + packageManifests: options.packageManifests, + bundledModuleIds: moduleIdsFromBundle(bundle), + bundleName: options.bundleName, + ...(options.configFile !== undefined ? { configFile: options.configFile } : {}), + }); + this.emitFile({ + type: "asset", + fileName: THIRD_PARTY_LICENSES_FILE_NAME, + source: serializeManifest(manifest), + }); + }, + }; +} diff --git a/third-party-licenses.config.json b/third-party-licenses.config.json new file mode 100644 index 000000000000..cd0ea1f468a3 --- /dev/null +++ b/third-party-licenses.config.json @@ -0,0 +1,181 @@ +{ + "customNotices": [ + { + "bundles": ["assets", "mobile", "web"], + "license": "MIT", + "name": "vscode-icons", + "noticeFile": "apps/web/THIRD_PARTY_NOTICES.md", + "sourceUrl": "https://github.com/vscode-icons/vscode-icons" + }, + { + "bundles": ["android", "assets", "mobile", "web"], + "license": "MIT", + "name": "libghostty-vt", + "noticeFile": "native/libghostty-vt/LICENSE", + "sourceUrl": "https://github.com/ghostty-org/ghostty" + }, + { + "bundles": ["assets", "web"], + "license": "MIT", + "name": "Symbols Nerd Font Mono", + "noticeFile": "apps/web/src/terminal/ghostty/fonts/LICENSE", + "sourceUrl": "https://github.com/ryanoasis/nerd-fonts" + }, + { + "bundles": ["ios", "mobile"], + "license": "MIT", + "name": "GhosttyKit", + "noticeFile": "apps/mobile/licenses/ghostty-kit.txt", + "sourceUrl": "https://github.com/Yash-Singh1/ghostty/tree/custom-io" + }, + { + "bundles": ["android", "assets", "mobile"], + "license": "Apache-2.0", + "name": "MesloLGS NF", + "noticeFile": "apps/mobile/licenses/meslo-lgs-nf.txt", + "sourceUrl": "https://github.com/romkatv/powerlevel10k-media" + }, + { + "bundles": ["mobile"], + "license": "MIT", + "name": "react-native-uitextview", + "noticeFile": "apps/mobile/modules/t3-markdown-text/LICENSE", + "sourceUrl": "https://github.com/bluesky-social/react-native-uitextview", + "version": "2.2.0" + }, + { + "bundles": ["mobile"], + "license": "MIT", + "name": "Expo text editor implementation", + "noticeFile": "apps/mobile/modules/t3-composer-editor/LICENSE", + "sourceUrl": "https://github.com/expo/expo" + } + ], + "packageOverrides": [ + { + "license": "MIT", + "name": "@react-grab/cli", + "noticeFile": "apps/web/licenses/react-grab-cli.txt", + "sourceUrl": "https://github.com/aidenybai/react-grab/tree/main/packages/cli" + }, + { + "name": "glob-to-regexp", + "noticeFile": "apps/web/licenses/glob-to-regexp.txt" + }, + { + "name": "kubernetes-types", + "noticeFile": "apps/web/licenses/kubernetes-types.txt" + }, + { + "name": "@msgpackr-extract/msgpackr-extract-linux-x64", + "noticeFile": "apps/web/licenses/msgpackr-extract-linux-x64.txt" + }, + { + "name": "isarray", + "noticeFile": "apps/web/licenses/isarray.txt" + }, + { + "name": "keyv", + "noticeFile": "apps/web/licenses/keyv.txt" + }, + { + "license": "MIT", + "name": "type-fest", + "noticeFile": "apps/web/licenses/type-fest.txt" + }, + { + "name": "lazy-val", + "noticeFile": "apps/web/licenses/lazy-val.txt" + }, + { + "name": "@pierre/theming", + "noticeFile": "apps/web/licenses/pierre-theming.txt" + }, + { + "name": "lru_map", + "noticeFile": "apps/web/licenses/lru-map.txt" + }, + { + "name": "@electron-internal/extract-zip", + "noticeFile": "apps/web/licenses/electron-internal-extract-zip.txt" + }, + { + "noticeFile": "apps/mobile/licenses/metro.txt", + "repositoryUrl": "https://github.com/facebook/metro" + }, + { + "noticeFile": "apps/mobile/licenses/metro.txt", + "repositoryUrl": "https://github.com/react/metro" + }, + { + "name": "@react-native-ai/apple", + "noticeFile": "apps/mobile/licenses/react-native-ai.txt", + "sourceUrl": "https://github.com/callstackincubator/ai" + }, + { + "name": "react-remove-scroll-bar", + "noticeFile": "apps/mobile/licenses/react-remove-scroll-bar.txt" + }, + { + "name": "standard-navigation", + "noticeFile": "apps/mobile/licenses/standard-navigation.txt" + }, + { + "name": "@expo/sdk-runtime-versions", + "noticeFile": "apps/mobile/modules/t3-composer-editor/LICENSE" + }, + { + "name": "@expo/ws-tunnel", + "noticeFile": "apps/mobile/modules/t3-composer-editor/LICENSE" + }, + { + "name": "stream-buffers", + "noticeFile": "apps/mobile/licenses/stream-buffers.txt" + }, + { + "name": "bplist-parser", + "noticeFile": "apps/mobile/licenses/bplist-parser.txt" + }, + { + "name": "@expo/devcert", + "noticeFile": "apps/mobile/licenses/expo-devcert.txt" + }, + { + "name": "jimp-compact", + "noticeFile": "apps/mobile/licenses/jimp-compact.txt" + }, + { + "name": "fb-watchman", + "noticeFile": "apps/web/licenses/kubernetes-types.txt" + }, + { + "name": "bser", + "noticeFile": "apps/web/licenses/kubernetes-types.txt" + }, + { + "name": "@expo/xcpretty", + "noticeFile": "apps/mobile/licenses/expo-xcpretty.txt" + }, + { + "license": "MIT", + "name": "fb-dotslash", + "noticeFile": "apps/mobile/licenses/fb-dotslash.txt" + }, + { + "name": "structured-headers", + "noticeFile": "apps/mobile/licenses/structured-headers.txt" + }, + { + "name": "badgin", + "noticeFile": "apps/mobile/licenses/badgin.txt" + }, + { + "name": "react-native-nitro-modules", + "noticeFile": "apps/mobile/licenses/react-native-nitro-modules.txt" + }, + { + "name": "boolbase", + "noticeFile": "apps/mobile/licenses/boolbase.txt" + } + ] +} From 733f051f5d84b89be0bf9f26784a970b0f9c26f9 Mon Sep 17 00:00:00 2001 From: maria Date: Mon, 31 Aug 2026 20:36:17 -0400 Subject: [PATCH 02/14] fix(settings): simplify open source license notices (#8970) --- .../settings/OpenSourceLicenses.tsx | 303 +++++++++++------- 1 file changed, 184 insertions(+), 119 deletions(-) diff --git a/apps/web/src/components/settings/OpenSourceLicenses.tsx b/apps/web/src/components/settings/OpenSourceLicenses.tsx index 28932ae10ece..dc4703cad51a 100644 --- a/apps/web/src/components/settings/OpenSourceLicenses.tsx +++ b/apps/web/src/components/settings/OpenSourceLicenses.tsx @@ -5,14 +5,16 @@ import { decodeThirdPartyLicenseManifest, filterThirdPartyLicenseEntries, formatLicenseBundles, + thirdPartyLicenseEntryKey, type ThirdPartyLicenseEntry, type ThirdPartyLicenseManifest, } from "@t3tools/shared/thirdPartyLicenses"; -import { cn } from "../../lib/utils"; import { Button } from "../ui/button"; -import { Input } from "../ui/input"; -import { SettingsPageContainer } from "./settingsLayout"; +import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible"; +import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +import { SettingsPageContainer, SettingsSection } from "./settingsLayout"; type LicenseManifestState = | { readonly status: "loading" } @@ -30,80 +32,154 @@ async function loadLicenseManifest(signal: AbortSignal): Promise void; +}) { return ( -
- - {open ? ( -
-
-
-

- {entry.name} - {versionLabel} -

- {entry.sourceUrl ? ( - - Project source - - + +
+
+ + + + {entry.name} + {entry.version ? ( + {entry.version} ) : null} -
-
-              {entry.noticeText}
-            
-
+ + + {entry.license} · {formatLicenseBundles(entry.bundles)} + + + {entry.sourceUrl ? ( + + ) : null}
- ) : null} -
+ + {open ? ( +
+
+                {entry.noticeText}
+              
+
+ ) : null} +
+ + + ); +} + +function LicenseCount({ + filteredCount, + totalCount, +}: { + filteredCount: number; + totalCount: number; +}) { + return ( +

+ {filteredCount === totalCount + ? `${String(totalCount)} notices` + : `${String(filteredCount)} of ${String(totalCount)}`} +

+ ); +} + +function LicenseHeaderAction({ + query, + onQueryChange, + searchOpen, + onSearchOpenChange, + filteredCount, + totalCount, +}: { + query: string; + onQueryChange: (value: string) => void; + searchOpen: boolean; + onSearchOpenChange: (open: boolean) => void; + filteredCount: number; + totalCount: number; +}) { + if (!searchOpen) { + return ( +
+ + + onSearchOpenChange(true)} + size="icon-micro" + type="button" + variant="ghost-muted" + > + + + } + /> + Search licenses + +
+ ); + } + + return ( +
+
+ +
+ + + + + { + if (query.length === 0) onSearchOpenChange(false); + }} + onChange={(event) => onQueryChange(event.currentTarget.value)} + onKeyDown={(event) => { + if (event.key !== "Escape") return; + event.preventDefault(); + onQueryChange(""); + onSearchOpenChange(false); + }} + placeholder="Search licenses" + size="sm" + type="search" + value={query} + /> + +
); } function LicenseManifestError({ message, onRetry }: { message: string; onRetry: () => void }) { return ( -
+
-

- Open-source notices are unavailable -

-

+

Open-source notices are unavailable

+

{message}

@@ -117,6 +193,8 @@ function LicenseManifestError({ message, onRetry }: { message: string; onRetry: export function OpenSourceLicensesPanel() { const [state, setState] = useState({ status: "loading" }); const [query, setQuery] = useState(""); + const [searchOpen, setSearchOpen] = useState(false); + const [openEntryKey, setOpenEntryKey] = useState(null); const [requestVersion, setRequestVersion] = useState(0); useEffect(() => { @@ -143,63 +221,50 @@ export function OpenSourceLicensesPanel() { const retry = useCallback(() => setRequestVersion((value) => value + 1), []); return ( - -
-

- Open source licenses -

-

- Third-party notices for dependencies and assets included in T3 Code. -

-
- - {state.status === "ready" ? ( -
-
- -

- {filteredEntries.length === entries.length - ? `${String(entries.length)} notices` - : `${String(filteredEntries.length)} of ${String(entries.length)} notices`} -

-
- -
+ + + ) : null + } + > + {state.status === "ready" ? ( +
{filteredEntries.length > 0 ? ( - filteredEntries.map((entry) => ( - - )) + filteredEntries.map((entry) => { + const entryKey = thirdPartyLicenseEntryKey(entry); + return ( + setOpenEntryKey(open ? entryKey : null)} + /> + ); + }) ) : ( -

+

No licenses match that search.

)}
-
- ) : state.status === "error" ? ( - - ) : ( -

- Loading open-source notices… -

- )} + ) : state.status === "error" ? ( + + ) : ( +

+ Loading open-source notices… +

+ )} + ); } From 5f97695454ceda1232feda1c6cbb0e8408cfab51 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 14:51:26 -0700 Subject: [PATCH 03/14] fix(settings): refresh open source notices --- apps/mobile/metro.config.js | 17 +--- .../SettingsOpenSourceLicensesRouteScreen.tsx | 2 +- apps/web/licenses/agent-device.txt | 21 +++++ .../expo-device-hub-serve-sim-notice.txt | 12 +++ apps/web/licenses/expo-device-hub-webrtc.txt | 28 ++++++ apps/web/licenses/expo-device-hub.txt | 27 ++++++ apps/web/licenses/ffi-rs.txt | 21 +++++ apps/web/licenses/isarray.txt | 2 +- apps/web/licenses/map-stream.txt | 24 +++++ apps/web/licenses/xa11y.txt | 21 +++++ .../components/settings/SettingsPanels.tsx | 2 +- .../settings/SettingsSidebarNav.tsx | 4 +- apps/web/src/routeTree.gen.ts | 16 ++-- docs/internals/open-source-licenses.md | 9 +- docs/user/open-source-licenses.md | 7 +- .../shared/src/thirdPartyLicenses.test.ts | 21 +++++ packages/shared/src/thirdPartyLicenses.ts | 30 +++++-- scripts/lib/third-party-licenses.test.ts | 66 +++++++++++++- scripts/lib/third-party-licenses.ts | 83 ++++++++++++------ third-party-licenses.config.json | 87 +++++++++++++++++++ 20 files changed, 431 insertions(+), 69 deletions(-) create mode 100644 apps/web/licenses/agent-device.txt create mode 100644 apps/web/licenses/expo-device-hub-serve-sim-notice.txt create mode 100644 apps/web/licenses/expo-device-hub-webrtc.txt create mode 100644 apps/web/licenses/expo-device-hub.txt create mode 100644 apps/web/licenses/ffi-rs.txt create mode 100644 apps/web/licenses/map-stream.txt create mode 100644 apps/web/licenses/xa11y.txt diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index fb53ff888cbd..3abc0acf66d4 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -1,7 +1,6 @@ const fs = require("node:fs"); const path = require("node:path"); const { pathToFileURL } = require("node:url"); -const TypeScript = require("typescript"); const { getDefaultConfig } = require("expo/metro-config"); const { withUniwindConfig } = require("uniwind/metro"); const extraThemes = require("./generated-uniwind-theme-names.json"); @@ -69,21 +68,9 @@ async function writeFileIfChanged(filePath, contents) { async function generateMobileThirdPartyLicenses() { await fs.promises.mkdir(generatedLicenseModuleRoot, { recursive: true }); - const generatorSource = await fs.promises.readFile(licenseGeneratorSource, "utf8"); - const compiledGenerator = TypeScript.transpileModule(generatorSource, { - compilerOptions: { - module: TypeScript.ModuleKind.ESNext, - target: TypeScript.ScriptTarget.ES2022, - verbatimModuleSyntax: true, - }, - fileName: licenseGeneratorSource, - }).outputText; - const compiledGeneratorPath = path.join(generatedLicenseModuleRoot, "generator.mjs"); - await writeFileIfChanged(compiledGeneratorPath, compiledGenerator); - - const generatorVersion = (await fs.promises.stat(compiledGeneratorPath)).mtimeMs; + const generatorVersion = (await fs.promises.stat(licenseGeneratorSource)).mtimeMs; const { generateThirdPartyLicenseManifest } = await import( - `${pathToFileURL(compiledGeneratorPath).href}?version=${String(generatorVersion)}` + `${pathToFileURL(licenseGeneratorSource).href}?version=${String(generatorVersion)}` ); const manifest = await generateThirdPartyLicenseManifest({ configFile: path.join(workspaceRoot, "third-party-licenses.config.json"), diff --git a/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx index 37632f670812..ca0c99f0d268 100644 --- a/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx @@ -105,7 +105,7 @@ export function SettingsOpenSourceLicensesRouteScreen() { ListHeaderComponent={ - Third-party notices for dependencies and assets included in T3 Code Mobile. + Notices for dependencies, assets, and optional tools used by T3 Code Mobile. 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 diff --git a/apps/web/licenses/map-stream.txt b/apps/web/licenses/map-stream.txt new file mode 100644 index 000000000000..4b0db7b71852 --- /dev/null +++ b/apps/web/licenses/map-stream.txt @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2011 Dominic Tarr + +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. diff --git a/apps/web/licenses/xa11y.txt b/apps/web/licenses/xa11y.txt new file mode 100644 index 000000000000..4ecaab641b82 --- /dev/null +++ b/apps/web/licenses/xa11y.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Stephen Crowe + +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. diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 9a4ccd2386e3..8a78d4138c6b 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -2909,7 +2909,7 @@ export function GeneralSettingsPanel() { /> } diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx index e5b5a887f899..220904e179c5 100644 --- a/apps/web/src/components/settings/SettingsSidebarNav.tsx +++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx @@ -327,9 +327,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { (pathname === "/settings/diagnostics" || pathname === "/settings/open-source-licenses"); const isActive = - isGeneralDetailPage || - pathname === item.to || - pathname.startsWith(`${item.to}/`); + isGeneralDetailPage || pathname === item.to || pathname.startsWith(`${item.to}/`); return ( { ); }); + it("rejects unsafe source links", () => { + expect(() => + decodeThirdPartyLicenseManifest({ + schemaVersion: 1, + entries: [{ ...ENTRIES[0]!, sourceUrl: "javascript:alert(1)" }], + }), + ).toThrow("invalid shape"); + }); + + it("rejects duplicate navigation keys", () => { + expect(() => + decodeThirdPartyLicenseManifest({ schemaVersion: 1, entries: [ENTRIES[0]!, ENTRIES[0]!] }), + ).toThrow("duplicate entry"); + }); + it("filters by package, license, version, and bundle", () => { expect(filterThirdPartyLicenseEntries(ENTRIES, "react 19.2")).toEqual([ENTRIES[0]]); expect(filterThirdPartyLicenseEntries(ENTRIES, "cc-by mobile")).toEqual([ENTRIES[1]]); @@ -60,4 +75,10 @@ describe("third-party license manifests", () => { const entry = ENTRIES[0]!; expect(findThirdPartyLicenseEntry(ENTRIES, thirdPartyLicenseEntryKey(entry))).toBe(entry); }); + + it("uses path-safe navigation keys for scoped packages", () => { + expect(thirdPartyLicenseEntryKey({ ...ENTRIES[0]!, name: "@scope/package" })).not.toContain( + "/", + ); + }); }); diff --git a/packages/shared/src/thirdPartyLicenses.ts b/packages/shared/src/thirdPartyLicenses.ts index b5e5c913107c..09c80d700ce7 100644 --- a/packages/shared/src/thirdPartyLicenses.ts +++ b/packages/shared/src/thirdPartyLicenses.ts @@ -21,6 +21,16 @@ function isStringArray(value: unknown): value is ReadonlyArray { return Array.isArray(value) && value.every((entry) => typeof entry === "string"); } +function isHttpUrl(value: unknown): value is string { + if (typeof value !== "string") return false; + try { + const protocol = new URL(value).protocol; + return protocol === "http:" || protocol === "https:"; + } catch { + return false; + } +} + function decodeEntry(value: unknown, index: number): ThirdPartyLicenseEntry { if (!isRecord(value)) { throw new Error(`License entry ${String(index + 1)} is not an object.`); @@ -32,7 +42,7 @@ function decodeEntry(value: unknown, index: number): ThirdPartyLicenseEntry { typeof value.license !== "string" || typeof value.name !== "string" || typeof value.noticeText !== "string" || - (value.sourceUrl !== null && typeof value.sourceUrl !== "string") || + (value.sourceUrl !== null && !isHttpUrl(value.sourceUrl)) || (value.version !== null && typeof value.version !== "string") ) { throw new Error(`License entry ${String(index + 1)} has an invalid shape.`); @@ -52,9 +62,18 @@ export function decodeThirdPartyLicenseManifest(value: unknown): ThirdPartyLicen if (!isRecord(value) || value.schemaVersion !== 1 || !Array.isArray(value.entries)) { throw new Error("The open-source license manifest has an unsupported format."); } + const entries = value.entries.map(decodeEntry); + const entryKeys = new Set(); + for (const entry of entries) { + const key = thirdPartyLicenseEntryKey(entry); + if (entryKeys.has(key)) { + throw new Error(`The open-source license manifest contains a duplicate entry: ${key}`); + } + entryKeys.add(key); + } return { schemaVersion: 1, - entries: value.entries.map(decodeEntry), + entries, }; } @@ -64,7 +83,7 @@ export function filterThirdPartyLicenseEntries( ): ReadonlyArray { const terms = query .trim() - .toLocaleLowerCase() + .toLowerCase() .split(/\s+/) .filter((term) => term.length > 0); if (terms.length === 0) return entries; @@ -72,7 +91,7 @@ export function filterThirdPartyLicenseEntries( const searchable = [entry.name, entry.version, entry.license, ...entry.bundles] .filter((value): value is string => value !== null) .join(" ") - .toLocaleLowerCase(); + .toLowerCase(); return terms.every((term) => searchable.includes(term)); }); } @@ -81,6 +100,7 @@ const BUNDLE_LABELS: Readonly> = { android: "Android", assets: "Assets", desktop: "Desktop", + "device-tools": "Device tools", ios: "iOS", mobile: "Mobile", server: "Server", @@ -92,7 +112,7 @@ export function formatLicenseBundles(bundles: ReadonlyArray): string { } export function thirdPartyLicenseEntryKey(entry: ThirdPartyLicenseEntry): string { - return `${entry.kind}:${entry.name}:${entry.version ?? "custom"}`; + return encodeURIComponent(`${entry.kind}:${entry.name}:${entry.version ?? "custom"}`); } export function findThirdPartyLicenseEntry( diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index 22f9d623248d..ec3050e91de5 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -194,7 +194,7 @@ describe("third-party license generation", () => { version: "2.0.0", license: "MIT", main: "index.js", - repository: "example/demo-dependency", + repository: "https://github.com/example/demo-dependency.git#main", }); await NodeFSP.writeFile(NodePath.join(siblingRoot, "index.js"), "export {};\n", "utf8"); @@ -208,6 +208,33 @@ describe("third-party license generation", () => { ); }); + it("prefers version-specific repository overrides", async () => { + const fixture = await createFixture(); + await NodeFSP.writeFile(NodePath.join(fixture.root, "generic.txt"), "Generic text\n", "utf8"); + await NodeFSP.writeFile(NodePath.join(fixture.root, "exact.txt"), "Exact text\n", "utf8"); + await writeJson(fixture.configFile, { + customNotices: [], + packageOverrides: [ + { + repositoryUrl: "https://github.com/example/demo-dependency", + noticeFile: "generic.txt", + }, + { + repositoryUrl: "https://github.com/example/demo-dependency", + version: "1.2.3", + noticeFile: "exact.txt", + }, + ], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries[0]?.noticeText).toBe("Exact text"); + }); + it("omits custom notices for other bundles", async () => { const fixture = await createFixture(); await writeJson(fixture.configFile, { @@ -230,6 +257,42 @@ describe("third-party license generation", () => { expect(manifest.entries.some((entry) => entry.name === "web-only-asset")).toBe(false); }); + it("can show a multi-file notice under a label that differs from its client manifests", async () => { + const fixture = await createFixture(); + await NodeFSP.writeFile( + NodePath.join(fixture.root, "tool-license.txt"), + "Tool license\n", + "utf8", + ); + await NodeFSP.writeFile( + NodePath.join(fixture.root, "vendor-notice.txt"), + "Vendor notice\n", + "utf8", + ); + await writeJson(fixture.configFile, { + customNotices: [ + { + name: "optional-tool", + license: "MIT AND Apache-2.0", + noticeFiles: ["tool-license.txt", "vendor-notice.txt"], + bundles: ["device-tools"], + includeInBundles: ["mobile", "web"], + }, + ], + packageOverrides: [], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "mobile", path: fixture.appManifest }], + }); + + expect(manifest.entries.find((entry) => entry.name === "optional-tool")).toMatchObject({ + bundles: ["device-tools"], + noticeText: "Tool license\n\n---\n\nVendor notice", + }); + }); + it("fails when a custom notice file is empty", async () => { const fixture = await createFixture(); await NodeFSP.writeFile(NodePath.join(fixture.root, "asset-notice.txt"), "\n", "utf8"); @@ -242,4 +305,3 @@ describe("third-party license generation", () => { ).rejects.toThrow('Custom third-party notice "demo-asset" is empty'); }); }); -// @effect-diagnostics nodeBuiltinImport:off - Tests exercise the Node filesystem build boundary. diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index b9eb02e7db67..45e9b6d64080 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -48,9 +48,10 @@ interface PackageJson { interface CustomNoticeConfigEntry { readonly bundles?: ReadonlyArray; + readonly includeInBundles?: ReadonlyArray; readonly license: string; readonly name: string; - readonly noticeFile: string; + readonly noticeFiles: ReadonlyArray; readonly sourceUrl?: string; readonly version?: string; } @@ -144,13 +145,20 @@ function decodeCustomNotices(value: unknown): ReadonlyArray { if (typeof entry === "string") return entry.trim(); if (isRecord(entry) && typeof entry.type === "string") return entry.type.trim(); @@ -394,6 +410,7 @@ function normalizeRepositoryUrl(value: unknown): string | null { .replace(/^git@github\.com:/, "https://github.com/") .replace(/^ssh:\/\/(?:git@)?github\.com\//, "https://github.com/") .replace(/^git:\/\/github\.com\//, "https://github.com/") + .replace(/#.*$/, "") .replace(/\.git$/, ""); if (/^[\w.-]+\/[\w.-]+$/.test(normalized)) return `https://github.com/${normalized}`; return normalized; @@ -460,9 +477,7 @@ async function readPackageNoticeText(packageRoot: string): Promise>, ): Promise> { const notices = new Map(); - await Promise.all( + const candidates = await Promise.all( [...collection.byIdentity.values()].map(async (collected) => { const license = normalizeLicense(collected.packageJson); - if (!license) return; + if (!license) return null; const key = repositoryNoticeKey(collected.packageJson, license); - if (!key || notices.has(key)) return; + if (!key) return null; const noticeText = await packageNoticeText(collected.packageRoot, packageNotices); - if (noticeText) notices.set(key, noticeText); + return noticeText ? { key, noticeText } : null; }), ); + for (const candidate of candidates) { + if (candidate && !notices.has(candidate.key)) notices.set(candidate.key, candidate.noticeText); + } return notices; } @@ -500,19 +518,16 @@ function findPackageOverride( version: string, packageJson: PackageJson, ): PackageNoticeOverrideConfigEntry | undefined { - const matchingVersion = (override: PackageNoticeOverrideConfigEntry) => - override.version === undefined || override.version === version; - const repositoryUrl = normalizeRepositoryUrl(packageJson.repository)?.toLocaleLowerCase(); + const repositoryUrl = normalizeRepositoryUrl(packageJson.repository)?.toLowerCase(); + const matchesRepository = (override: PackageNoticeOverrideConfigEntry) => + repositoryUrl !== undefined && + override.repositoryUrl !== undefined && + normalizeRepositoryUrl(override.repositoryUrl)?.toLowerCase() === repositoryUrl; return ( overrides.find((override) => override.name === name && override.version === version) ?? overrides.find((override) => override.name === name && override.version === undefined) ?? - overrides.find( - (override) => - repositoryUrl !== undefined && - override.repositoryUrl !== undefined && - normalizeRepositoryUrl(override.repositoryUrl)?.toLocaleLowerCase() === repositoryUrl && - matchingVersion(override), - ) + overrides.find((override) => matchesRepository(override) && override.version === version) ?? + overrides.find((override) => matchesRepository(override) && override.version === undefined) ); } @@ -575,13 +590,24 @@ async function customEntries( config.customNotices .filter( (notice) => - notice.bundles === undefined || - notice.bundles.some((bundle) => includedBundles.has(bundle)), + (notice.includeInBundles ?? notice.bundles) === undefined || + (notice.includeInBundles ?? notice.bundles)?.some((bundle) => + includedBundles.has(bundle), + ), ) .map(async (notice) => { - const noticeText = ( - await NodeFSP.readFile(NodePath.resolve(configDirectory, notice.noticeFile), "utf8") - ).trim(); + const noticeSections = await Promise.all( + notice.noticeFiles.map(async (noticeFile) => { + const contents = ( + await NodeFSP.readFile(NodePath.resolve(configDirectory, noticeFile), "utf8") + ).trim(); + if (contents.length === 0) { + throw new Error(`Custom third-party notice "${notice.name}" is empty.`); + } + return contents; + }), + ); + const noticeText = noticeSections.join("\n\n---\n\n"); if (noticeText.length === 0) { throw new Error(`Custom third-party notice "${notice.name}" is empty.`); } @@ -679,6 +705,9 @@ export function thirdPartyLicensesPlugin(options: ThirdPartyLicensesPluginOption manifestPromise ??= generateThirdPartyLicenseManifest({ packageManifests: options.packageManifests, ...(options.configFile !== undefined ? { configFile: options.configFile } : {}), + }).catch((error: unknown) => { + manifestPromise = null; + throw error; }); void manifestPromise.then( (manifest) => { diff --git a/third-party-licenses.config.json b/third-party-licenses.config.json index cd0ea1f468a3..a2997b8c0e70 100644 --- a/third-party-licenses.config.json +++ b/third-party-licenses.config.json @@ -7,6 +7,36 @@ "noticeFile": "apps/web/THIRD_PARTY_NOTICES.md", "sourceUrl": "https://github.com/vscode-icons/vscode-icons" }, + { + "bundles": ["assets", "desktop", "web"], + "license": "MIT", + "name": "T3 Code SnapShot sound effects", + "noticeFile": "LICENSE", + "sourceUrl": "https://github.com/pingdotgg/t3code" + }, + { + "bundles": ["device-tools"], + "includeInBundles": ["mobile", "web"], + "license": "MIT", + "name": "agent-device", + "noticeFile": "apps/web/licenses/agent-device.txt", + "sourceUrl": "https://github.com/callstack/agent-device", + "version": "0.20.10" + }, + { + "bundles": ["device-tools"], + "includeInBundles": ["mobile", "web"], + "license": "MIT AND Apache-2.0 AND BSD-3-Clause", + "name": "expo-device-hub", + "noticeFiles": [ + "apps/web/licenses/expo-device-hub.txt", + "apps/web/licenses/kubernetes-types.txt", + "apps/web/licenses/expo-device-hub-serve-sim-notice.txt", + "apps/web/licenses/expo-device-hub-webrtc.txt" + ], + "sourceUrl": "https://github.com/expo/expo-device-hub", + "version": "0.9.0" + }, { "bundles": ["android", "assets", "mobile", "web"], "license": "MIT", @@ -99,6 +129,63 @@ "name": "@electron-internal/extract-zip", "noticeFile": "apps/web/licenses/electron-internal-extract-zip.txt" }, + { + "noticeFile": "apps/web/licenses/xa11y.txt", + "repositoryUrl": "https://github.com/xa11y/xa11y" + }, + { + "license": "MIT", + "name": "map-stream", + "noticeFile": "apps/web/licenses/map-stream.txt" + }, + { + "license": "Apache-2.0", + "name": "jsbi" + }, + { + "name": "@yuuang/ffi-rs-android-arm64", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-darwin-arm64", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-darwin-x64", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-linux-arm-gnueabihf", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-linux-arm64-gnu", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-linux-arm64-musl", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-linux-x64-gnu", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-linux-x64-musl", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-win32-arm64-msvc", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-win32-ia32-msvc", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, + { + "name": "@yuuang/ffi-rs-win32-x64-msvc", + "noticeFile": "apps/web/licenses/ffi-rs.txt" + }, { "noticeFile": "apps/mobile/licenses/metro.txt", "repositoryUrl": "https://github.com/facebook/metro" From 4e99712f2c64bfe39c608d6eeb1fba33f91f04d4 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 15:05:33 -0700 Subject: [PATCH 04/14] fix(settings): make license navigation keys unambiguous --- packages/shared/src/thirdPartyLicenses.test.ts | 13 ++++++++++--- packages/shared/src/thirdPartyLicenses.ts | 8 ++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/thirdPartyLicenses.test.ts b/packages/shared/src/thirdPartyLicenses.test.ts index 6797614e6054..c133cfdfa342 100644 --- a/packages/shared/src/thirdPartyLicenses.test.ts +++ b/packages/shared/src/thirdPartyLicenses.test.ts @@ -66,9 +66,9 @@ describe("third-party license manifests", () => { }); it("formats platform bundle names for display", () => { - expect(formatLicenseBundles(["android", "assets", "ios", "mobile", "plugin"])).toBe( - "Android, Assets, iOS, Mobile, plugin", - ); + expect( + formatLicenseBundles(["android", "assets", "ios", "mobile", "plugin", "constructor"]), + ).toBe("Android, Assets, iOS, Mobile, plugin, constructor"); }); it("finds an entry by its stable navigation key", () => { @@ -81,4 +81,11 @@ describe("third-party license manifests", () => { "/", ); }); + + it("uses distinct navigation keys when names and versions contain delimiters", () => { + const first = thirdPartyLicenseEntryKey({ ...ENTRIES[0]!, name: "a:1", version: null }); + const second = thirdPartyLicenseEntryKey({ ...ENTRIES[0]!, name: "a", version: "1:custom" }); + + expect(first).not.toBe(second); + }); }); diff --git a/packages/shared/src/thirdPartyLicenses.ts b/packages/shared/src/thirdPartyLicenses.ts index 09c80d700ce7..ab570cb2e1e7 100644 --- a/packages/shared/src/thirdPartyLicenses.ts +++ b/packages/shared/src/thirdPartyLicenses.ts @@ -108,11 +108,15 @@ const BUNDLE_LABELS: Readonly> = { }; export function formatLicenseBundles(bundles: ReadonlyArray): string { - return bundles.map((bundle) => BUNDLE_LABELS[bundle] ?? bundle).join(", "); + return bundles + .map((bundle) => + Object.prototype.hasOwnProperty.call(BUNDLE_LABELS, bundle) ? BUNDLE_LABELS[bundle] : bundle, + ) + .join(", "); } export function thirdPartyLicenseEntryKey(entry: ThirdPartyLicenseEntry): string { - return encodeURIComponent(`${entry.kind}:${entry.name}:${entry.version ?? "custom"}`); + return encodeURIComponent(JSON.stringify([entry.kind, entry.name, entry.version])); } export function findThirdPartyLicenseEntry( From e98edcc48706622a32d0b6ea79eca650c9504aba Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 15:37:04 -0700 Subject: [PATCH 05/14] fix(settings): correct bundled asset notices --- .../assets/file-icons/pierre_package.png | Bin 472 -> 0 bytes .../assets/file-icons/pierre_readme.png | Bin 1220 -> 0 bytes .../assets/file-icons/pierre_tsconfig.png | Bin 2023 -> 0 bytes .../scripts/sync-pierre-file-icons.mjs | 4 - .../src/markdownFileIcons.generated.ts | 3 - .../t3-markdown-text/src/markdownLinks.ts | 8 +- apps/web/THIRD_PARTY_NOTICES.md | 24 ---- apps/web/licenses/cc0-1.0.txt | 119 ++++++++++++++++++ apps/web/licenses/contarex-camera-shutter.txt | 6 + apps/web/licenses/folder-whoosh.txt | 6 + apps/web/src/pierre-icons.test.ts | 21 ++-- apps/web/src/pierre-icons.ts | 21 +--- third-party-licenses.config.json | 21 ++-- 13 files changed, 162 insertions(+), 71 deletions(-) delete mode 100644 apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_package.png delete mode 100644 apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_readme.png delete mode 100644 apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_tsconfig.png delete mode 100644 apps/web/THIRD_PARTY_NOTICES.md create mode 100644 apps/web/licenses/cc0-1.0.txt create mode 100644 apps/web/licenses/contarex-camera-shutter.txt create mode 100644 apps/web/licenses/folder-whoosh.txt diff --git a/apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_package.png b/apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_package.png deleted file mode 100644 index 5150250a1b6d014ec0b25260adfcb67feeb96114..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(<^uz(rC1}St4blLz^=H%((7*fIb zcGgDTLk=R&_8oRN83H#tE=XSJai(cX>k9?x07bI~P6hQU($*RQbE8EvjuRX~S;r?5k;ix?m+kv`eSH8?)FggF<-1)AEbJDfEv+waFPd&R7I!V8{B?2cD&@D1@j&lynMb9cS)0ku`jp0lj5 zJoaw;50(S-k6qe0rK9rxSBZ6hquy8ZN2it)S$Y20V!;-)ZPkzR2F4R<)359hc3{%f se#~+r$~|k(50(iQQO8((9Hty#KeA2nsN!U+Q=m}wboFyt=akR{0IAon^Z)<= diff --git a/apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_readme.png b/apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_readme.png deleted file mode 100644 index bfbd4298b015a0935f59f6deb047070bfaecd976..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1220 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(<^uz(rC1}St4blSkcz@q2r;uuoF z_;!}BM@XPZ!~5ENwasj|zU{d8sC_2kQcjbP$?04##OwPOD=DBUah^PDm40qwS0f`dV|QaOZic;jaP%k zV%Z&d#TbG-R{pBd)m+*(GnebYWB#{it|#<9lYN`XqP#a{iH_P_h5(<{xgyyY(;HqY zX1HW41iaSLES>T?_rtnV^7F2}{yOW#+cqxOMSk~`j!%&+kNzJWm#*TztLAB!Pt2aP zS<(A=_0w4v9C@iRSyT40wZ{pEWp2Cnwy*xPcz=nRpWw^PJx%-%<7Qqq6qNZCtaC2= z%CA+;Hd8ol4@m25e=1$L$u_#-(qEyQe^`F|?o26oEU@j9$OFCgr&({xG3XR|tzLY$ z_pO_w>$@xO77EEaEblQow?cn+={80WpPoZ&FV3qnw?BEjv8%VmYJT#E+ZDCzPJClJ zprJZp9&hQ@_rc6}qIT`>4=M^+`#dVM-YaNVYO@aSX6L{~>%1#8=A7QT;OEa9{0(2v z@wpcCK3rJNky^KB&g9&iObjmHe;nS?pYTEIzzfe4HF|*?SQ!=;Ol3H;jF;zyufhk- zwfE~wF02VCQ`UCa=fQO)u(RiNc79aZ%gfC`doW!GUJWK&)q7RH1-}V|L4y(Bj|+9?w3pF z3S>X#bP*F_+WR7=&!K;HeZ_T;oowpAmZwb5@}JW3Cbm!a?q;iLxeP+W`F!_|hbCRW z^@RPw^P-rQe&xP?W)V|A+)WBnT*79Y9+_>t_OaO!*A+H*%}kPCEjGNt)$sLia^Wu{ zr;5E+d<{-hH=C~gD{t~RYE{@z=f@&1SbgSfnc2MH*&`OeidoI_x~2cFpDbe#XBOVi zxn_=+OjpH=%Gtjc&-C$l{W_1cO|&)U;;Wxlr-i2J>SR~!6}?%;kQ3m4`ayWd>umQfx%!rW>tio_udFcD zX|g)rTdG!nEoDw9Wt#az*YDV@3-vC!i#5E$m8?Tt#Zu!KS4hkBoY(U)Pf**FaPvVf z=k`>=Vz0?PdQRTj8_%oqxu<&ZM?^;T?7j5L=e6M_k?D$0E6?q_X7u&C#0|x6{V4V6 zYTjZd%hs%tWL&|`bMB!=q^#MiHy?5;R-U>!ZRS1AYf|d#w;hOTOvx5z*rXb+@wUs3 z*@yq2$M*FX>eVNBBn301{rA_OawYun1;&o`4vv5I9C$;fV9B7s?CLP-fc%<#Bb`y+pz006kmOi^~niu^-%w&Ph% z4ES&?pddRFL*Ut{_{#Bu@^m)4Ze;~19(#5Gltctr|5T1CdQ1QSlR*IZSV4boWS0M= zNHX}p{!h^uW7q%yCoyIygUg|ybu5SXWwgleuifjrG0%~*AT$=NQA{O0=OGZ)`}|u9 z^PMp~Hq(Sl3H{HNWe_l30#C9jwoG0$-cZ2sMM8q8d25R-tF2@LyMk1xq)#Tk^Kf9p zXXe%(u5%sUFrZnqUUZZn{rljO%{-jK2!%m2rbCTl(;W_^cdMsOtT^YABIR5s$H z;9D4x?=Z3Ac%|S8@kll-{6mNfO;tKDyp7;kp(-GDx*=aY_jOsAdFpt@Gc=;Ggb^);&VQPV}}0yt(^k-R0xLb9aJyok-=C*Dr9qJm*&9X^zt^Y`k5$ zJiQc7knK5wDxBXb__9iIq1#y=mL@*c^3x>I42n`FI77M;gzCfncGy}mELX>9V>OL! zW>RrDrdc}+LTWmJ<+T_iJ9!Z!k0fkD4B8Uk<$=W z*e>kn9hFD!8tqlSemanERn;&peN_31P369EnxH<)zNOX5{_a-2+s37wVSJvU#{n-> zWUbY8i0Wt;hD|m9C#F;v%qcprifQ4g@GFv-B;{D@-rdoYXcAH^3Usy~FdI?AQoSnmy)I$VhrHaWZB**T4cq+_-{DT>=&(vYL&_1oDQH03IM!Xz+ej`W1d&=|V2Ltf9zJ zgWx!+549EJD&J&5-fB>eQX}*oic-9qMXdUHnPUA(*ubwRX1&7aH^Io=7MboC8Q)jl zn`RLv5Yo*%P4PnM>t&|nn#{B_GFCJRm4`!BwKei^I3)B6NNM$K=-@u1px2I<-;1u- zWTH(B@e=lg+#aWw@|`aMczhSS)5O-wWckHOS&|$xDb}v@^fS={ehaWM;>AeN0~1`~ zEy%(OJ?oKW+ULMWWA?_>AS|LWHINV55?UND)n$B9GVa`32WdMpqsf3;QxH4VEtGTP zaye&!xNV23sHDfaj-l#+X8-qnQ^s#XY&kz(@KDa6hrZ)X-lUczz6VcU#Z)JL;*%GGw>5F+ z-1i!bzGhn@eZx6|QV?QF=`6!OY<}_W)}j;|6ITQ(#k1Vqneac`QTk#cGci`-%8t~f zbq2joMY%|~GlxBb5;aQWrv9jWH+UOUJo+UmMF8s4YI#?%#7z{YMkf2R+RdtD&Q1~P zDtvS$4Dx(et(m7qB7OHhsunv0`KJ#gUg=U8%MD@+$g*(V`r`4E6Q8Kwen!%j{kcwj z0+mpeRPYIA4}}|lvvDD!&4t~M_8Q*Xz`tjx?ptRW@KdSmJi0fMnn$E?e+Rv75J8J6 z=lnegshR5WzR-T=;}TXs1^NPK8u3oSwGEsQG~%^KX?MdV&jj+4aRaeK_uC{szu&Eg z>nLvxeMS}9Erb`>qsP8@blS#A4J6XaVh zGI=pH(k-1}e;~A$^;iEK@#vheza#Bw7C{z!PhHdeTCAS-@LjDc*pqqqC4AYZdn+ni zClX%20c&i$3Gw9M2R=^y94uUUsiLOJuNSWP`t@V=x2`i`VNpGZf$<=oADqjvAii$x zr!*rkotf2p-5P2+EL>?hifqA0fdWzSfjW{{!mkcTilE|{y(N|C9~^n^VHfpPvlbqi0%c})6%S{J2XgC2#*qi5-&gc;E2_`7 zv)(|e?Dc?wrRwPLMRFJaLS&?$ylO)-0eS7TsTq-pAA`f8-(m8r4z(-8&0)hR!h?#fvotY!ME)lT^t0B2+Y)dTyJGfozpt?1;b}z{pC|#jcj!0 sDT6igQ?*WSgc!oKIiLN1u`IQzl0FBs7&YfA{_|wb(AKDDhHi2H2C?d%2LJ#7 diff --git a/apps/mobile/modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs b/apps/mobile/modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs index 8510ce97ba68..fdbb03382eef 100644 --- a/apps/mobile/modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs +++ b/apps/mobile/modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs @@ -79,11 +79,7 @@ const colors = { const customIcons = { agents: "t3-file-icon-agents", - claude: "t3-file-icon-claude", - package: "t3-file-icon-package-json", pnpm: "t3-file-icon-pnpm", - readme: "t3-file-icon-readme", - tsconfig: "t3-file-icon-tsconfig", video: "t3-file-icon-video", }; diff --git a/apps/mobile/modules/t3-markdown-text/src/markdownFileIcons.generated.ts b/apps/mobile/modules/t3-markdown-text/src/markdownFileIcons.generated.ts index 463e00207d97..fb0209b6a990 100644 --- a/apps/mobile/modules/t3-markdown-text/src/markdownFileIcons.generated.ts +++ b/apps/mobile/modules/t3-markdown-text/src/markdownFileIcons.generated.ts @@ -30,13 +30,11 @@ export const MARKDOWN_FILE_ICON_SOURCES = { nextjs: require("../assets/file-icons/pierre_nextjs.png"), npm: require("../assets/file-icons/pierre_npm.png"), oxc: require("../assets/file-icons/pierre_oxc.png"), - package: require("../assets/file-icons/pierre_package.png"), pnpm: require("../assets/file-icons/pierre_pnpm.png"), postcss: require("../assets/file-icons/pierre_postcss.png"), prettier: require("../assets/file-icons/pierre_prettier.png"), python: require("../assets/file-icons/pierre_python.png"), react: require("../assets/file-icons/pierre_react.png"), - readme: require("../assets/file-icons/pierre_readme.png"), ruby: require("../assets/file-icons/pierre_ruby.png"), rust: require("../assets/file-icons/pierre_rust.png"), sass: require("../assets/file-icons/pierre_sass.png"), @@ -49,7 +47,6 @@ export const MARKDOWN_FILE_ICON_SOURCES = { tailwind: require("../assets/file-icons/pierre_tailwind.png"), terraform: require("../assets/file-icons/pierre_terraform.png"), text: require("../assets/file-icons/pierre_text.png"), - tsconfig: require("../assets/file-icons/pierre_tsconfig.png"), typescript: require("../assets/file-icons/pierre_typescript.png"), video: require("../assets/file-icons/pierre_video.png"), vite: require("../assets/file-icons/pierre_vite.png"), diff --git a/apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts b/apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts index 19f71f631663..4143b2fc8a28 100644 --- a/apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts +++ b/apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts @@ -116,7 +116,7 @@ const FILE_ICON_BY_NAME: Readonly> = { "next.config.mjs": "nextjs", "next.config.mts": "nextjs", "next.config.ts": "nextjs", - "package.json": "package", + "package.json": "npm", "pnpm-lock.yaml": "pnpm", "pnpm-workspace.yaml": "pnpm", "postcss.config.js": "postcss", @@ -127,7 +127,7 @@ const FILE_ICON_BY_NAME: Readonly> = { "prettier.config.cjs": "prettier", "prettier.config.mjs": "prettier", rakefile: "ruby", - "readme.md": "readme", + "readme.md": "markdown", "stylelint.config.js": "stylelint", "stylelint.config.cjs": "stylelint", "stylelint.config.mjs": "stylelint", @@ -139,7 +139,7 @@ const FILE_ICON_BY_NAME: Readonly> = { "tailwind.config.cjs": "tailwind", "tailwind.config.mjs": "tailwind", "tailwind.config.ts": "tailwind", - "tsconfig.json": "tsconfig", + "tsconfig.json": "typescript", "vite.config.js": "vite", "vite.config.mjs": "vite", "vite.config.mts": "vite", @@ -253,7 +253,7 @@ export function resolveMarkdownFileIcon(value: string): MarkdownFileIcon { const exactIcon = FILE_ICON_BY_NAME[basename]; if (exactIcon) return exactIcon; if (basename.startsWith("tsconfig.") && basename.endsWith(".json")) { - return "tsconfig"; + return "typescript"; } const segments = basename.split("."); for (let index = 1; index < segments.length; index += 1) { diff --git a/apps/web/THIRD_PARTY_NOTICES.md b/apps/web/THIRD_PARTY_NOTICES.md deleted file mode 100644 index eea821c3f071..000000000000 --- a/apps/web/THIRD_PARTY_NOTICES.md +++ /dev/null @@ -1,24 +0,0 @@ -vscode-icons - -The custom file icon symbols bundled with T3 Code are adapted from the -vscode-icons project: https://github.com/vscode-icons/vscode-icons - -Copyright (c) 2016 Roberto Huertas - -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. diff --git a/apps/web/licenses/cc0-1.0.txt b/apps/web/licenses/cc0-1.0.txt new file mode 100644 index 000000000000..46b2c10e7f0b --- /dev/null +++ b/apps/web/licenses/cc0-1.0.txt @@ -0,0 +1,119 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL +SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT +RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. +CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE +INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES +RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED +HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with +a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or +her Copyright and Related Rights in the Work and the meaning and intended +legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and +vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations + thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer +makes the Waiver for the benefit of each member of the public at large and to +the detriment of Affirmer's heirs and successors, fully intending that the +Waiver shall not be subject to revocation, rescission, cancellation, +termination, or any other legal or equitable action to disrupt the quiet +enjoyment of the Work by the public as contemplated by Affirmer's express +Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), +(iii) in any current or future medium and for any number of copies, and (iv) +for any purpose whatsoever, including without limitation commercial, +advertising or promotional purposes (the "License"). The License shall be +deemed effective as of the date CC0 was applied by Affirmer to the Work. Should +any part of the License for any reason be judged legally invalid or +ineffective under applicable law, such partial invalidity or ineffectiveness +shall not invalidate the remainder of the License, and in such case Affirmer +hereby affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, + fitness for a particular purpose, non infringement, or the absence of + latent or other defects, accuracy, or the present or absence of errors, + whether or not discoverable, all to the greatest extent permissible under + applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. Further, + Affirmer disclaims responsibility for obtaining any necessary consents, + permissions or other rights required for any use of the Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +Source: https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt diff --git a/apps/web/licenses/contarex-camera-shutter.txt b/apps/web/licenses/contarex-camera-shutter.txt new file mode 100644 index 000000000000..121454be60fc --- /dev/null +++ b/apps/web/licenses/contarex-camera-shutter.txt @@ -0,0 +1,6 @@ +The bundled snap-shot-click.mp3 is derived from Contarex camera shutter.wav by +Tonik1105: + +https://freesound.org/people/Tonik1105/sounds/520684/ + +The original sound is dedicated to the public domain under CC0 1.0 Universal. diff --git a/apps/web/licenses/folder-whoosh.txt b/apps/web/licenses/folder-whoosh.txt new file mode 100644 index 000000000000..ce8d931e823d --- /dev/null +++ b/apps/web/licenses/folder-whoosh.txt @@ -0,0 +1,6 @@ +The bundled snap-shot-whoosh.mp3 is derived from Folder_Whoosh.wav by +BaggoNotes: + +https://freesound.org/people/BaggoNotes/sounds/704258/ + +The original sound is dedicated to the public domain under CC0 1.0 Universal. diff --git a/apps/web/src/pierre-icons.test.ts b/apps/web/src/pierre-icons.test.ts index 46a4e8a80343..5f5aca691e99 100644 --- a/apps/web/src/pierre-icons.test.ts +++ b/apps/web/src/pierre-icons.test.ts @@ -14,18 +14,21 @@ describe("Pierre file icons", () => { assert.equal(resolvePierreIconForEntry("vite.config.ts", "file")?.token, "vite"); }); - it("extends Pierre with T3-specific exact filename icons", () => { + it("uses built-in Pierre icons where available", () => { + assert.equal(resolvePierreIconForEntry("package.json", "file")?.name, "file-tree-builtin-npm"); assert.equal( - resolvePierreIconForEntry("package.json", "file")?.name, - "t3-file-icon-package-json", + resolvePierreIconForEntry("config/tsconfig.json", "file")?.name, + "file-tree-builtin-typescript", ); + assert.equal(resolvePierreIconForEntry("CLAUDE.md", "file")?.name, "file-tree-builtin-claude"); assert.equal( - resolvePierreIconForEntry("config/tsconfig.json", "file")?.name, - "t3-file-icon-tsconfig", + resolvePierreIconForEntry("README.md", "file")?.name, + "file-tree-builtin-markdown", ); + }); + + it("extends Pierre with T3-specific exact filename icons", () => { assert.equal(resolvePierreIconForEntry("AGENTS.md", "file")?.name, "t3-file-icon-agents"); - assert.equal(resolvePierreIconForEntry("CLAUDE.md", "file")?.name, "t3-file-icon-claude"); - assert.equal(resolvePierreIconForEntry("README.md", "file")?.name, "t3-file-icon-readme"); assert.equal(resolvePierreIconForEntry("pnpm-lock.yaml", "file")?.name, "t3-file-icon-pnpm"); assert.equal( resolvePierreIconForEntry("pnpm-workspace.yaml", "file")?.name, @@ -34,7 +37,9 @@ describe("Pierre file icons", () => { }); it("ships every custom icon referenced by the extended resolver", () => { - const customIconNames = new Set(Object.values(T3_PIERRE_ICONS.byFileName)); + const customIconNames = new Set( + Object.values(T3_PIERRE_ICONS.byFileName).filter((name) => name.startsWith("t3-")), + ); for (const iconName of customIconNames) { assert.include(T3_PIERRE_ICONS.spriteSheet, `id="${iconName}"`); } diff --git a/apps/web/src/pierre-icons.ts b/apps/web/src/pierre-icons.ts index b4b83e4df976..49aec9cc4cef 100644 --- a/apps/web/src/pierre-icons.ts +++ b/apps/web/src/pierre-icons.ts @@ -21,24 +21,9 @@ const T3_FILE_ICON_SPRITE = ` - - - - - - - - - - - - - - - @@ -50,11 +35,9 @@ export const T3_PIERRE_ICONS = { colored: true, spriteSheet: T3_FILE_ICON_SPRITE, byFileName: { - "package.json": "t3-file-icon-package-json", - "tsconfig.json": "t3-file-icon-tsconfig", + "package.json": "file-tree-builtin-npm", + "tsconfig.json": "file-tree-builtin-typescript", "agents.md": "t3-file-icon-agents", - "claude.md": "t3-file-icon-claude", - "readme.md": "t3-file-icon-readme", "pnpm-lock.yaml": "t3-file-icon-pnpm", "pnpm-workspace.yaml": "t3-file-icon-pnpm", }, diff --git a/third-party-licenses.config.json b/third-party-licenses.config.json index a2997b8c0e70..c45c353a1434 100644 --- a/third-party-licenses.config.json +++ b/third-party-licenses.config.json @@ -1,18 +1,21 @@ { "customNotices": [ { - "bundles": ["assets", "mobile", "web"], - "license": "MIT", - "name": "vscode-icons", - "noticeFile": "apps/web/THIRD_PARTY_NOTICES.md", - "sourceUrl": "https://github.com/vscode-icons/vscode-icons" + "bundles": ["assets", "desktop", "web"], + "license": "CC0-1.0", + "name": "Folder_Whoosh.wav by BaggoNotes", + "noticeFiles": ["apps/web/licenses/folder-whoosh.txt", "apps/web/licenses/cc0-1.0.txt"], + "sourceUrl": "https://freesound.org/people/BaggoNotes/sounds/704258/" }, { "bundles": ["assets", "desktop", "web"], - "license": "MIT", - "name": "T3 Code SnapShot sound effects", - "noticeFile": "LICENSE", - "sourceUrl": "https://github.com/pingdotgg/t3code" + "license": "CC0-1.0", + "name": "Contarex camera shutter.wav by Tonik1105", + "noticeFiles": [ + "apps/web/licenses/contarex-camera-shutter.txt", + "apps/web/licenses/cc0-1.0.txt" + ], + "sourceUrl": "https://freesound.org/people/Tonik1105/sounds/520684/" }, { "bundles": ["device-tools"], From f2635d2317d8479bb5892ff51759f1db5f117aac Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 15:49:31 -0700 Subject: [PATCH 06/14] fix(settings): fail on unresolved license dependencies --- scripts/lib/third-party-licenses.test.ts | 114 ++++++++++++++++++++++- scripts/lib/third-party-licenses.ts | 42 +++++++-- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index ec3050e91de5..c69fe778e32a 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -6,7 +6,11 @@ import * as NodePath from "node:path"; import { afterEach, describe, expect, it } from "vite-plus/test"; -import { generateThirdPartyLicenseManifest } from "./third-party-licenses.js"; +import { + generateThirdPartyLicenseManifest, + THIRD_PARTY_LICENSES_FILE_NAME, + thirdPartyLicensesPlugin, +} from "./third-party-licenses.js"; const tempDirectories: string[] = []; @@ -100,6 +104,114 @@ describe("third-party license generation", () => { }); }); + it("finds packages whose exports hide both their manifest and entry point", async () => { + const fixture = await createFixture(); + await writeJson(NodePath.join(fixture.dependencyRoot, "package.json"), { + name: "demo-dependency", + version: "1.2.3", + license: "MIT", + exports: {}, + repository: "example/demo-dependency", + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); + }); + + it("fails when a required production dependency cannot be resolved", async () => { + const fixture = await createFixture(); + await writeJson(fixture.appManifest, { + name: "fixture-app", + dependencies: { "missing-dependency": "1.0.0" }, + }); + + await expect( + generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }), + ).rejects.toThrow('Unable to resolve production dependency "missing-dependency"'); + }); + + it("allows unresolved optional dependencies", async () => { + const fixture = await createFixture(); + await writeJson(fixture.appManifest, { + name: "fixture-app", + dependencies: { "demo-dependency": "1.2.3" }, + optionalDependencies: { "platform-only-dependency": "1.0.0" }, + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); + }); + + it("includes custom notices selected by the dev server bundle", async () => { + const fixture = await createFixture(); + await writeJson(fixture.configFile, { + customNotices: [ + { + name: "desktop-only-asset", + license: "CC-BY-4.0", + noticeFile: "asset-notice.txt", + bundles: ["assets"], + includeInBundles: ["desktop"], + }, + ], + packageOverrides: [], + }); + + const plugin = thirdPartyLicensesPlugin({ + bundleName: "desktop", + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + let middleware: + | (( + request: { readonly url: string }, + response: { + statusCode: number; + setHeader(name: string, value: string): void; + end(body: string): void; + }, + next: (error?: Error) => void, + ) => void) + | undefined; + if (typeof plugin.configureServer !== "function") { + throw new Error("Expected the license plugin to define a configureServer hook."); + } + plugin.configureServer({ + middlewares: { + use(handler: typeof middleware) { + middleware = handler; + }, + }, + } as never); + if (!middleware) throw new Error("Expected the license plugin to register middleware."); + + const responseBody = await new Promise((resolve, reject) => { + middleware!( + { url: `/${THIRD_PARTY_LICENSES_FILE_NAME}` }, + { + statusCode: 0, + setHeader() {}, + end: resolve, + }, + (error) => reject(error ?? new Error("License middleware skipped the request.")), + ); + }); + const manifest = JSON.parse(responseBody) as { entries: ReadonlyArray<{ name: string }> }; + + expect(manifest.entries.some((entry) => entry.name === "desktop-only-asset")).toBe(true); + }); + it("fails when a production package has no distributable notice text", async () => { const fixture = await createFixture(); await NodeFSP.rm(NodePath.join(fixture.dependencyRoot, "LICENSE")); diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index 45e9b6d64080..7df014cd484a 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -268,16 +268,36 @@ async function resolveDependencyPackage( if (code !== "MODULE_NOT_FOUND" && code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") throw error; } } + + let current = NodePath.dirname(fromPackageJsonPath); + const root = NodePath.parse(current).root; + while (true) { + const packageRoot = NodePath.join(current, "node_modules", dependencyName); + try { + const packageJson = await readPackageJson(NodePath.join(packageRoot, "package.json")); + if (packageJson.name === dependencyName) { + return { packageJson, packageRoot: await NodeFSP.realpath(packageRoot) }; + } + } catch (error) { + const code = isRecord(error) && typeof error.code === "string" ? error.code : null; + if (code !== "ENOENT" && code !== "ENOTDIR") throw error; + } + if (current === root) break; + current = NodePath.dirname(current); + } return null; } -function dependencyNames(packageJson: PackageJson): ReadonlyArray { +function dependencies( + packageJson: PackageJson, +): ReadonlyArray<{ readonly name: string; readonly optional: boolean }> { + const optional = new Set(Object.keys(packageJson.optionalDependencies ?? {})); return [ - ...new Set([ - ...Object.keys(packageJson.dependencies ?? {}), - ...Object.keys(packageJson.optionalDependencies ?? {}), - ]), - ].sort((left, right) => left.localeCompare(right)); + ...Object.keys(packageJson.dependencies ?? {}) + .filter((name) => !optional.has(name)) + .map((name) => ({ name, optional: false })), + ...[...optional].map((name) => ({ name, optional: true })), + ].sort((left, right) => left.name.localeCompare(right.name)); } async function collectProductionDependencyPackages( @@ -290,9 +310,14 @@ async function collectProductionDependencyPackages( const visitManifest = async (packageJsonPath: string, bundle: string): Promise => { const packageJson = await readPackageJson(packageJsonPath); - for (const dependencyName of dependencyNames(packageJson)) { + for (const { name: dependencyName, optional } of dependencies(packageJson)) { const resolved = await resolveDependencyPackage(dependencyName, packageJsonPath); - if (!resolved) continue; + if (!resolved) { + if (optional) continue; + throw new Error( + `Unable to resolve production dependency "${dependencyName}" declared by ${packageJsonPath} for bundle "${bundle}".`, + ); + } const visitKey = `${bundle}:${resolved.packageRoot}`; if (visited.has(visitKey)) continue; visited.add(visitKey); @@ -704,6 +729,7 @@ export function thirdPartyLicensesPlugin(options: ThirdPartyLicensesPluginOption } manifestPromise ??= generateThirdPartyLicenseManifest({ packageManifests: options.packageManifests, + bundleName: options.bundleName, ...(options.configFile !== undefined ? { configFile: options.configFile } : {}), }).catch((error: unknown) => { manifestPromise = null; From e271d1323c4cacbd61009a04c3df89a358dd4762 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 15:56:03 -0700 Subject: [PATCH 07/14] fix(settings): cover newly resolved package notices --- apps/mobile/src/lib/markdownLinks.test.ts | 2 +- .../mobile/src/lib/nativeMarkdownText.test.ts | 2 +- apps/web/licenses/fff.txt | 21 +++++++++++++++++++ apps/web/licenses/opencode.txt | 21 +++++++++++++++++++ scripts/lib/third-party-licenses.test.ts | 19 +++++++++++++++++ scripts/lib/third-party-licenses.ts | 20 +++++++++++------- third-party-licenses.config.json | 9 ++++++++ 7 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 apps/web/licenses/fff.txt create mode 100644 apps/web/licenses/opencode.txt diff --git a/apps/mobile/src/lib/markdownLinks.test.ts b/apps/mobile/src/lib/markdownLinks.test.ts index 7d02cb71f7b5..38f1f5211421 100644 --- a/apps/mobile/src/lib/markdownLinks.test.ts +++ b/apps/mobile/src/lib/markdownLinks.test.ts @@ -75,7 +75,7 @@ describe("resolveMarkdownLinkPresentation", () => { expect(resolveMarkdownLinkPresentation("package.json")).toEqual({ kind: "file", href: "package.json", - icon: "package", + icon: "npm", label: "package.json", path: "package.json", }); diff --git a/apps/mobile/src/lib/nativeMarkdownText.test.ts b/apps/mobile/src/lib/nativeMarkdownText.test.ts index bf7e90e41973..794c59f98339 100644 --- a/apps/mobile/src/lib/nativeMarkdownText.test.ts +++ b/apps/mobile/src/lib/nativeMarkdownText.test.ts @@ -95,7 +95,7 @@ describe("nativeMarkdownTextRuns", () => { { text: "README.md:12", href: "file:///repo/README.md#L12", - fileIcon: "readme", + fileIcon: "markdown", }, ]); }); diff --git a/apps/web/licenses/fff.txt b/apps/web/licenses/fff.txt new file mode 100644 index 000000000000..ea495f0d6bc5 --- /dev/null +++ b/apps/web/licenses/fff.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Dmitriy Kovalenko + +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. diff --git a/apps/web/licenses/opencode.txt b/apps/web/licenses/opencode.txt new file mode 100644 index 000000000000..6439474beed8 --- /dev/null +++ b/apps/web/licenses/opencode.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 opencode + +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. diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index c69fe778e32a..9cda941f0900 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -153,6 +153,25 @@ describe("third-party license generation", () => { expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); }); + it("allows package-manager-pruned transitive dependencies", async () => { + const fixture = await createFixture(); + await writeJson(NodePath.join(fixture.dependencyRoot, "package.json"), { + name: "demo-dependency", + version: "1.2.3", + license: "MIT", + main: "index.js", + dependencies: { "pruned-transitive-dependency": "1.0.0" }, + repository: "example/demo-dependency", + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); + }); + it("includes custom notices selected by the dev server bundle", async () => { const fixture = await createFixture(); await writeJson(fixture.configFile, { diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index 7df014cd484a..053d5b730318 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -308,15 +308,21 @@ async function collectProductionDependencyPackages( }; const visited = new Set(); - const visitManifest = async (packageJsonPath: string, bundle: string): Promise => { + const visitManifest = async ( + packageJsonPath: string, + bundle: string, + isRootManifest: boolean, + ): Promise => { const packageJson = await readPackageJson(packageJsonPath); for (const { name: dependencyName, optional } of dependencies(packageJson)) { const resolved = await resolveDependencyPackage(dependencyName, packageJsonPath); if (!resolved) { - if (optional) continue; - throw new Error( - `Unable to resolve production dependency "${dependencyName}" declared by ${packageJsonPath} for bundle "${bundle}".`, - ); + if (!optional && isRootManifest) { + throw new Error( + `Unable to resolve production dependency "${dependencyName}" declared by ${packageJsonPath} for bundle "${bundle}".`, + ); + } + continue; } const visitKey = `${bundle}:${resolved.packageRoot}`; if (visited.has(visitKey)) continue; @@ -339,12 +345,12 @@ async function collectProductionDependencyPackages( } } - await visitManifest(dependencyPackageJsonPath, bundle); + await visitManifest(dependencyPackageJsonPath, bundle, false); } }; for (const manifest of packageManifests) { - await visitManifest(asPath(manifest.path), manifest.bundle); + await visitManifest(asPath(manifest.path), manifest.bundle, true); } return collection; } diff --git a/third-party-licenses.config.json b/third-party-licenses.config.json index c45c353a1434..b8d54db854be 100644 --- a/third-party-licenses.config.json +++ b/third-party-licenses.config.json @@ -266,6 +266,15 @@ { "name": "boolbase", "noticeFile": "apps/mobile/licenses/boolbase.txt" + }, + { + "noticeFile": "apps/web/licenses/fff.txt", + "repositoryUrl": "https://github.com/dmtrKovalenko/fff" + }, + { + "name": "@opencode-ai/sdk", + "noticeFile": "apps/web/licenses/opencode.txt", + "sourceUrl": "https://github.com/anomalyco/opencode/tree/dev/packages/sdk/js" } ] } From d25c0c949e7900a2535c09b5f031725378469b39 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 16:00:09 -0700 Subject: [PATCH 08/14] test(settings): invoke license plugin with context --- scripts/lib/third-party-licenses.test.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index 9cda941f0900..990a306eda9c 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -206,13 +206,16 @@ describe("third-party license generation", () => { if (typeof plugin.configureServer !== "function") { throw new Error("Expected the license plugin to define a configureServer hook."); } - plugin.configureServer({ - middlewares: { - use(handler: typeof middleware) { - middleware = handler; + plugin.configureServer.call( + {} as never, + { + middlewares: { + use(handler: typeof middleware) { + middleware = handler; + }, }, - }, - } as never); + } as never, + ); if (!middleware) throw new Error("Expected the license plugin to register middleware."); const responseBody = await new Promise((resolve, reject) => { From b7bf48bc66bcfac8ca49200b1e883dc8a21b32ce Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 16:05:42 -0700 Subject: [PATCH 09/14] fix(build): support filtered license manifests --- scripts/lib/third-party-licenses.test.ts | 50 ------------------------ scripts/lib/third-party-licenses.ts | 36 ++++++----------- 2 files changed, 11 insertions(+), 75 deletions(-) diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index 990a306eda9c..078ec28e875d 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -122,56 +122,6 @@ describe("third-party license generation", () => { expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); }); - it("fails when a required production dependency cannot be resolved", async () => { - const fixture = await createFixture(); - await writeJson(fixture.appManifest, { - name: "fixture-app", - dependencies: { "missing-dependency": "1.0.0" }, - }); - - await expect( - generateThirdPartyLicenseManifest({ - configFile: fixture.configFile, - packageManifests: [{ bundle: "web", path: fixture.appManifest }], - }), - ).rejects.toThrow('Unable to resolve production dependency "missing-dependency"'); - }); - - it("allows unresolved optional dependencies", async () => { - const fixture = await createFixture(); - await writeJson(fixture.appManifest, { - name: "fixture-app", - dependencies: { "demo-dependency": "1.2.3" }, - optionalDependencies: { "platform-only-dependency": "1.0.0" }, - }); - - const manifest = await generateThirdPartyLicenseManifest({ - configFile: fixture.configFile, - packageManifests: [{ bundle: "web", path: fixture.appManifest }], - }); - - expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); - }); - - it("allows package-manager-pruned transitive dependencies", async () => { - const fixture = await createFixture(); - await writeJson(NodePath.join(fixture.dependencyRoot, "package.json"), { - name: "demo-dependency", - version: "1.2.3", - license: "MIT", - main: "index.js", - dependencies: { "pruned-transitive-dependency": "1.0.0" }, - repository: "example/demo-dependency", - }); - - const manifest = await generateThirdPartyLicenseManifest({ - configFile: fixture.configFile, - packageManifests: [{ bundle: "web", path: fixture.appManifest }], - }); - - expect(manifest.entries.some((entry) => entry.name === "demo-dependency")).toBe(true); - }); - it("includes custom notices selected by the dev server bundle", async () => { const fixture = await createFixture(); await writeJson(fixture.configFile, { diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index 053d5b730318..6c07aebf23f9 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -288,16 +288,13 @@ async function resolveDependencyPackage( return null; } -function dependencies( - packageJson: PackageJson, -): ReadonlyArray<{ readonly name: string; readonly optional: boolean }> { - const optional = new Set(Object.keys(packageJson.optionalDependencies ?? {})); +function dependencyNames(packageJson: PackageJson): ReadonlyArray { return [ - ...Object.keys(packageJson.dependencies ?? {}) - .filter((name) => !optional.has(name)) - .map((name) => ({ name, optional: false })), - ...[...optional].map((name) => ({ name, optional: true })), - ].sort((left, right) => left.name.localeCompare(right.name)); + ...new Set([ + ...Object.keys(packageJson.dependencies ?? {}), + ...Object.keys(packageJson.optionalDependencies ?? {}), + ]), + ].sort((left, right) => left.localeCompare(right)); } async function collectProductionDependencyPackages( @@ -308,22 +305,11 @@ async function collectProductionDependencyPackages( }; const visited = new Set(); - const visitManifest = async ( - packageJsonPath: string, - bundle: string, - isRootManifest: boolean, - ): Promise => { + const visitManifest = async (packageJsonPath: string, bundle: string): Promise => { const packageJson = await readPackageJson(packageJsonPath); - for (const { name: dependencyName, optional } of dependencies(packageJson)) { + for (const dependencyName of dependencyNames(packageJson)) { const resolved = await resolveDependencyPackage(dependencyName, packageJsonPath); - if (!resolved) { - if (!optional && isRootManifest) { - throw new Error( - `Unable to resolve production dependency "${dependencyName}" declared by ${packageJsonPath} for bundle "${bundle}".`, - ); - } - continue; - } + if (!resolved) continue; const visitKey = `${bundle}:${resolved.packageRoot}`; if (visited.has(visitKey)) continue; visited.add(visitKey); @@ -345,12 +331,12 @@ async function collectProductionDependencyPackages( } } - await visitManifest(dependencyPackageJsonPath, bundle, false); + await visitManifest(dependencyPackageJsonPath, bundle); } }; for (const manifest of packageManifests) { - await visitManifest(asPath(manifest.path), manifest.bundle, true); + await visitManifest(asPath(manifest.path), manifest.bundle); } return collection; } From 5626cf7f3811a33602f8bbf533beac2c98c89ab6 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 16:52:23 -0700 Subject: [PATCH 10/14] build: generate third-party license notices --- .gitignore | 1 + apps/mobile/licenses/badgin.txt | 23 -- apps/mobile/licenses/boolbase.txt | 13 - apps/mobile/licenses/bplist-parser.txt | 22 -- apps/mobile/licenses/expo-devcert.txt | 21 -- apps/mobile/licenses/expo-xcpretty.txt | 29 -- apps/mobile/licenses/fb-dotslash.txt | 21 -- apps/mobile/licenses/ghostty-kit.txt | 32 -- apps/mobile/licenses/jimp-compact.txt | 21 -- apps/mobile/licenses/meslo-lgs-nf.txt | 13 - apps/mobile/licenses/metro.txt | 21 -- apps/mobile/licenses/react-native-ai.txt | 22 -- .../licenses/react-native-nitro-modules.txt | 21 -- .../licenses/react-remove-scroll-bar.txt | 21 -- apps/mobile/licenses/standard-navigation.txt | 21 -- apps/mobile/licenses/stream-buffers.txt | 24 -- apps/mobile/licenses/structured-headers.txt | 21 -- apps/mobile/metro.config.js | 4 + apps/web/licenses/agent-device.txt | 21 -- apps/web/licenses/cc0-1.0.txt | 119 ------- apps/web/licenses/contarex-camera-shutter.txt | 6 - .../electron-internal-extract-zip.txt | 25 -- .../expo-device-hub-serve-sim-notice.txt | 12 - apps/web/licenses/expo-device-hub-webrtc.txt | 28 -- apps/web/licenses/expo-device-hub.txt | 27 -- apps/web/licenses/fff.txt | 21 -- apps/web/licenses/ffi-rs.txt | 21 -- apps/web/licenses/folder-whoosh.txt | 6 - apps/web/licenses/glob-to-regexp.txt | 24 -- apps/web/licenses/isarray.txt | 21 -- apps/web/licenses/keyv.txt | 22 -- apps/web/licenses/kubernetes-types.txt | 176 ---------- apps/web/licenses/lazy-val.txt | 21 -- apps/web/licenses/lru-map.txt | 19 -- apps/web/licenses/map-stream.txt | 24 -- .../licenses/msgpackr-extract-linux-x64.txt | 21 -- apps/web/licenses/opencode.txt | 21 -- apps/web/licenses/pierre-theming.txt | 189 ----------- apps/web/licenses/react-grab-cli.txt | 21 -- apps/web/licenses/type-fest.txt | 9 - apps/web/licenses/xa11y.txt | 21 -- docs/internals/open-source-licenses.md | 42 ++- package.json | 1 + scripts/lib/third-party-licenses.test.ts | 60 ++++ scripts/lib/third-party-licenses.ts | 277 ++++++++++++++-- scripts/sync-third-party-license-notices.ts | 9 + third-party-licenses.config.json | 310 ++++++++++++++---- 47 files changed, 591 insertions(+), 1334 deletions(-) delete mode 100644 apps/mobile/licenses/badgin.txt delete mode 100644 apps/mobile/licenses/boolbase.txt delete mode 100644 apps/mobile/licenses/bplist-parser.txt delete mode 100644 apps/mobile/licenses/expo-devcert.txt delete mode 100644 apps/mobile/licenses/expo-xcpretty.txt delete mode 100644 apps/mobile/licenses/fb-dotslash.txt delete mode 100644 apps/mobile/licenses/ghostty-kit.txt delete mode 100644 apps/mobile/licenses/jimp-compact.txt delete mode 100644 apps/mobile/licenses/meslo-lgs-nf.txt delete mode 100644 apps/mobile/licenses/metro.txt delete mode 100644 apps/mobile/licenses/react-native-ai.txt delete mode 100644 apps/mobile/licenses/react-native-nitro-modules.txt delete mode 100644 apps/mobile/licenses/react-remove-scroll-bar.txt delete mode 100644 apps/mobile/licenses/standard-navigation.txt delete mode 100644 apps/mobile/licenses/stream-buffers.txt delete mode 100644 apps/mobile/licenses/structured-headers.txt delete mode 100644 apps/web/licenses/agent-device.txt delete mode 100644 apps/web/licenses/cc0-1.0.txt delete mode 100644 apps/web/licenses/contarex-camera-shutter.txt delete mode 100644 apps/web/licenses/electron-internal-extract-zip.txt delete mode 100644 apps/web/licenses/expo-device-hub-serve-sim-notice.txt delete mode 100644 apps/web/licenses/expo-device-hub-webrtc.txt delete mode 100644 apps/web/licenses/expo-device-hub.txt delete mode 100644 apps/web/licenses/fff.txt delete mode 100644 apps/web/licenses/ffi-rs.txt delete mode 100644 apps/web/licenses/folder-whoosh.txt delete mode 100644 apps/web/licenses/glob-to-regexp.txt delete mode 100644 apps/web/licenses/isarray.txt delete mode 100644 apps/web/licenses/keyv.txt delete mode 100644 apps/web/licenses/kubernetes-types.txt delete mode 100644 apps/web/licenses/lazy-val.txt delete mode 100644 apps/web/licenses/lru-map.txt delete mode 100644 apps/web/licenses/map-stream.txt delete mode 100644 apps/web/licenses/msgpackr-extract-linux-x64.txt delete mode 100644 apps/web/licenses/opencode.txt delete mode 100644 apps/web/licenses/pierre-theming.txt delete mode 100644 apps/web/licenses/react-grab-cli.txt delete mode 100644 apps/web/licenses/type-fest.txt delete mode 100644 apps/web/licenses/xa11y.txt create mode 100644 scripts/sync-third-party-license-notices.ts diff --git a/.gitignore b/.gitignore index 0bca4d9ba452..b65fb1ed9914 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ dist-electron/ .showcase/ apps/mobile/.showcase/ apps/mobile/.generated/ +/.generated/ artifacts/app-store/screenshots/ .github/pr-assets/ native/**/target/ diff --git a/apps/mobile/licenses/badgin.txt b/apps/mobile/licenses/badgin.txt deleted file mode 100644 index 36fb0f2975ba..000000000000 --- a/apps/mobile/licenses/badgin.txt +++ /dev/null @@ -1,23 +0,0 @@ -badgin is a refactored fork of Tinycon. - -MIT License - -Copyright (c) Julian Hundeloh and Tinycon contributors - -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. diff --git a/apps/mobile/licenses/boolbase.txt b/apps/mobile/licenses/boolbase.txt deleted file mode 100644 index a689aae96e89..000000000000 --- a/apps/mobile/licenses/boolbase.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2014-2015, Felix Boehm - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -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. diff --git a/apps/mobile/licenses/bplist-parser.txt b/apps/mobile/licenses/bplist-parser.txt deleted file mode 100644 index 43e0a819e8eb..000000000000 --- a/apps/mobile/licenses/bplist-parser.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License - -Copyright (c) 2012 Near Infinity Corporation - -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. diff --git a/apps/mobile/licenses/expo-devcert.txt b/apps/mobile/licenses/expo-devcert.txt deleted file mode 100644 index 1b2a58dc871f..000000000000 --- a/apps/mobile/licenses/expo-devcert.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Dave Wasmer - -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. diff --git a/apps/mobile/licenses/expo-xcpretty.txt b/apps/mobile/licenses/expo-xcpretty.txt deleted file mode 100644 index 4a70306986eb..000000000000 --- a/apps/mobile/licenses/expo-xcpretty.txt +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2016-present, 650 Industries, Inc. (Expo) -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. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -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. diff --git a/apps/mobile/licenses/fb-dotslash.txt b/apps/mobile/licenses/fb-dotslash.txt deleted file mode 100644 index b93be90515cc..000000000000 --- a/apps/mobile/licenses/fb-dotslash.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Meta Platforms, Inc. and affiliates. - -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. diff --git a/apps/mobile/licenses/ghostty-kit.txt b/apps/mobile/licenses/ghostty-kit.txt deleted file mode 100644 index d9f72b0a65ab..000000000000 --- a/apps/mobile/licenses/ghostty-kit.txt +++ /dev/null @@ -1,32 +0,0 @@ -GhosttyKit - -The iOS terminal renderer vendors GhosttyKit.xcframework, a libghostty build produced from T3's -iOS 16 support fork. That fork was created from VVTerm's custom-I/O Ghostty fork. - -Upstream project: https://github.com/ghostty-org/ghostty -Custom-I/O base fork: https://github.com/wiedymi/ghostty/tree/custom-io -Vendored source fork: https://github.com/Yash-Singh1/ghostty/tree/custom-io -Vendored revision: d36c3b8dffd0d756dd5e5f4933962f774a0e6753 -Reference integration: https://github.com/vivy-company/vvterm - -MIT License - -Copyright (c) 2024 Mitchell Hashimoto, Ghostty contributors - -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. diff --git a/apps/mobile/licenses/jimp-compact.txt b/apps/mobile/licenses/jimp-compact.txt deleted file mode 100644 index baa5d48509a1..000000000000 --- a/apps/mobile/licenses/jimp-compact.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Oliver Moran - -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. diff --git a/apps/mobile/licenses/meslo-lgs-nf.txt b/apps/mobile/licenses/meslo-lgs-nf.txt deleted file mode 100644 index e07201aa0327..000000000000 --- a/apps/mobile/licenses/meslo-lgs-nf.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2009, 2010, 2013 André Berg - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/apps/mobile/licenses/metro.txt b/apps/mobile/licenses/metro.txt deleted file mode 100644 index b93be90515cc..000000000000 --- a/apps/mobile/licenses/metro.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Meta Platforms, Inc. and affiliates. - -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. diff --git a/apps/mobile/licenses/react-native-ai.txt b/apps/mobile/licenses/react-native-ai.txt deleted file mode 100644 index b78d113be4e3..000000000000 --- a/apps/mobile/licenses/react-native-ai.txt +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2024-2025 Szymon Rybczak -Copyright (c) 2025-present Callstack - -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. diff --git a/apps/mobile/licenses/react-native-nitro-modules.txt b/apps/mobile/licenses/react-native-nitro-modules.txt deleted file mode 100644 index c9efc1be9e23..000000000000 --- a/apps/mobile/licenses/react-native-nitro-modules.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Marc Rousavy - -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. diff --git a/apps/mobile/licenses/react-remove-scroll-bar.txt b/apps/mobile/licenses/react-remove-scroll-bar.txt deleted file mode 100644 index 669467a25577..000000000000 --- a/apps/mobile/licenses/react-remove-scroll-bar.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Anton Korzunov - -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. diff --git a/apps/mobile/licenses/standard-navigation.txt b/apps/mobile/licenses/standard-navigation.txt deleted file mode 100644 index 887f80b0b9ca..000000000000 --- a/apps/mobile/licenses/standard-navigation.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 React Navigation Contributors - -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. diff --git a/apps/mobile/licenses/stream-buffers.txt b/apps/mobile/licenses/stream-buffers.txt deleted file mode 100644 index 68a49daad8ff..000000000000 --- a/apps/mobile/licenses/stream-buffers.txt +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -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 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. - -For more information, please refer to diff --git a/apps/mobile/licenses/structured-headers.txt b/apps/mobile/licenses/structured-headers.txt deleted file mode 100644 index faa9c812abfc..000000000000 --- a/apps/mobile/licenses/structured-headers.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Evert Pot - -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. diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index 3abc0acf66d4..02a6e2c8666c 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -75,6 +75,10 @@ async function generateMobileThirdPartyLicenses() { const manifest = await generateThirdPartyLicenseManifest({ configFile: path.join(workspaceRoot, "third-party-licenses.config.json"), packageManifests: [{ bundle: "mobile", path: path.join(__dirname, "package.json") }], + allowMissingGeneratedNotices: + process.env.NODE_ENV !== "production" && + process.env.EAS_BUILD !== "true" && + process.env.T3CODE_LICENSES_STRICT !== "1", }); await Promise.all([ diff --git a/apps/web/licenses/agent-device.txt b/apps/web/licenses/agent-device.txt deleted file mode 100644 index e339c7e96e4f..000000000000 --- a/apps/web/licenses/agent-device.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Callstack - -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. diff --git a/apps/web/licenses/cc0-1.0.txt b/apps/web/licenses/cc0-1.0.txt deleted file mode 100644 index 46b2c10e7f0b..000000000000 --- a/apps/web/licenses/cc0-1.0.txt +++ /dev/null @@ -1,119 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - -CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL -SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT -RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. -CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE -INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES -RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED -HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator and -subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for the -purpose of contributing to a commons of creative, cultural and scientific -works ("Commons") that the public can reliably and without fear of later -claims of infringement build upon, modify, incorporate in other works, reuse -and redistribute as freely as possible in any form whatsoever and for any -purposes, including without limitation commercial purposes. These owners may -contribute to the Commons to promote the ideal of a free culture and the -further production of creative, cultural and scientific works, or to gain -reputation or greater distribution for their Work in part through the use and -efforts of others. - -For these and/or other purposes and motivations, and without any expectation -of additional consideration or compensation, the person associating CC0 with -a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright -and Related Rights in the Work, voluntarily elects to apply CC0 to the Work -and publicly distribute the Work under its terms, with knowledge of his or -her Copyright and Related Rights in the Work and the meaning and intended -legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not limited -to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, communicate, - and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or likeness - depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation thereof, - including any amended or successor version of such directive); and -vii. other similar, equivalent or corresponding rights throughout the world - based on applicable law or treaty, and any national implementations - thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention of, -applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and -unconditionally waives, abandons, and surrenders all of Affirmer's Copyright -and Related Rights and associated claims and causes of action, whether now -known or unknown (including existing as well as future claims and causes of -action), in the Work (i) in all territories worldwide, (ii) for the maximum -duration provided by applicable law or treaty (including future time -extensions), (iii) in any current or future medium and for any number of -copies, and (iv) for any purpose whatsoever, including without limitation -commercial, advertising or promotional purposes (the "Waiver"). Affirmer -makes the Waiver for the benefit of each member of the public at large and to -the detriment of Affirmer's heirs and successors, fully intending that the -Waiver shall not be subject to revocation, rescission, cancellation, -termination, or any other legal or equitable action to disrupt the quiet -enjoyment of the Work by the public as contemplated by Affirmer's express -Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason be -judged legally invalid or ineffective under applicable law, then the Waiver -shall be preserved to the maximum extent permitted taking into account -Affirmer's express Statement of Purpose. In addition, to the extent the Waiver -is so judged Affirmer hereby grants to each affected person a royalty-free, -non transferable, non sublicensable, non exclusive, irrevocable and -unconditional license to exercise Affirmer's Copyright and Related Rights in -the Work (i) in all territories worldwide, (ii) for the maximum duration -provided by applicable law or treaty (including future time extensions), -(iii) in any current or future medium and for any number of copies, and (iv) -for any purpose whatsoever, including without limitation commercial, -advertising or promotional purposes (the "License"). The License shall be -deemed effective as of the date CC0 was applied by Affirmer to the Work. Should -any part of the License for any reason be judged legally invalid or -ineffective under applicable law, such partial invalidity or ineffectiveness -shall not invalidate the remainder of the License, and in such case Affirmer -hereby affirms that he or she will not (i) exercise any of his or her remaining -Copyright and Related Rights in the Work or (ii) assert any associated claims -and causes of action with respect to the Work, in either case contrary to -Affirmer's express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or warranties - of any kind concerning the Work, express, implied, statutory or otherwise, - including without limitation warranties of title, merchantability, - fitness for a particular purpose, non infringement, or the absence of - latent or other defects, accuracy, or the present or absence of errors, - whether or not discoverable, all to the greatest extent permissible under - applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. Further, - Affirmer disclaims responsibility for obtaining any necessary consents, - permissions or other rights required for any use of the Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to this - CC0 or use of the Work. - -Source: https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt diff --git a/apps/web/licenses/contarex-camera-shutter.txt b/apps/web/licenses/contarex-camera-shutter.txt deleted file mode 100644 index 121454be60fc..000000000000 --- a/apps/web/licenses/contarex-camera-shutter.txt +++ /dev/null @@ -1,6 +0,0 @@ -The bundled snap-shot-click.mp3 is derived from Contarex camera shutter.wav by -Tonik1105: - -https://freesound.org/people/Tonik1105/sounds/520684/ - -The original sound is dedicated to the public domain under CC0 1.0 Universal. diff --git a/apps/web/licenses/electron-internal-extract-zip.txt b/apps/web/licenses/electron-internal-extract-zip.txt deleted file mode 100644 index c127edcfc580..000000000000 --- a/apps/web/licenses/electron-internal-extract-zip.txt +++ /dev/null @@ -1,25 +0,0 @@ -BSD 2-Clause License - -Copyright (c) 2026 Samuel Attard and Electron contributors -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. diff --git a/apps/web/licenses/expo-device-hub-serve-sim-notice.txt b/apps/web/licenses/expo-device-hub-serve-sim-notice.txt deleted file mode 100644 index 5f9192b91ab7..000000000000 --- a/apps/web/licenses/expo-device-hub-serve-sim-notice.txt +++ /dev/null @@ -1,12 +0,0 @@ -serve-sim attribution notice - -This distribution includes software from the original serve-sim project, -created and open-sourced by Evan Bacon: - -https://github.com/EvanBacon/serve-sim - -Original work copyright 2026 Evan Bacon. - -The original work is licensed under the Apache License, Version 2.0. This -fork contains modifications by Expo and other contributors. See the LICENSE -file for the applicable terms and the Git history for modification details. diff --git a/apps/web/licenses/expo-device-hub-webrtc.txt b/apps/web/licenses/expo-device-hub-webrtc.txt deleted file mode 100644 index 8b05f79acd70..000000000000 --- a/apps/web/licenses/expo-device-hub-webrtc.txt +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * 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. - - * Neither the name of Google nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - -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. diff --git a/apps/web/licenses/expo-device-hub.txt b/apps/web/licenses/expo-device-hub.txt deleted file mode 100644 index 773a3eea8490..000000000000 --- a/apps/web/licenses/expo-device-hub.txt +++ /dev/null @@ -1,27 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) - -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. - -This package bundles third-party code that is licensed separately: - -- serve-sim, Apache License 2.0 -- serve-emu, Apache License 2.0 -- WebRTC, BSD 3-Clause diff --git a/apps/web/licenses/fff.txt b/apps/web/licenses/fff.txt deleted file mode 100644 index ea495f0d6bc5..000000000000 --- a/apps/web/licenses/fff.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Dmitriy Kovalenko - -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. diff --git a/apps/web/licenses/ffi-rs.txt b/apps/web/licenses/ffi-rs.txt deleted file mode 100644 index 510d2a98f1d7..000000000000 --- a/apps/web/licenses/ffi-rs.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 zhangyuang - -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. diff --git a/apps/web/licenses/folder-whoosh.txt b/apps/web/licenses/folder-whoosh.txt deleted file mode 100644 index ce8d931e823d..000000000000 --- a/apps/web/licenses/folder-whoosh.txt +++ /dev/null @@ -1,6 +0,0 @@ -The bundled snap-shot-whoosh.mp3 is derived from Folder_Whoosh.wav by -BaggoNotes: - -https://freesound.org/people/BaggoNotes/sounds/704258/ - -The original sound is dedicated to the public domain under CC0 1.0 Universal. diff --git a/apps/web/licenses/glob-to-regexp.txt b/apps/web/licenses/glob-to-regexp.txt deleted file mode 100644 index 11ffa9a34892..000000000000 --- a/apps/web/licenses/glob-to-regexp.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2013, Nick Fitzgerald - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* 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. diff --git a/apps/web/licenses/isarray.txt b/apps/web/licenses/isarray.txt deleted file mode 100644 index 88ae3522f50b..000000000000 --- a/apps/web/licenses/isarray.txt +++ /dev/null @@ -1,21 +0,0 @@ -(MIT) - -Copyright (c) 2013 Julian Gruber - -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. diff --git a/apps/web/licenses/keyv.txt b/apps/web/licenses/keyv.txt deleted file mode 100644 index a6bad1d22eee..000000000000 --- a/apps/web/licenses/keyv.txt +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2017-2021 Luke Childs -Copyright (c) 2021-2022 Jared Wray - -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. diff --git a/apps/web/licenses/kubernetes-types.txt b/apps/web/licenses/kubernetes-types.txt deleted file mode 100644 index d0381d6d04c7..000000000000 --- a/apps/web/licenses/kubernetes-types.txt +++ /dev/null @@ -1,176 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/apps/web/licenses/lazy-val.txt b/apps/web/licenses/lazy-val.txt deleted file mode 100644 index 1d667e735b30..000000000000 --- a/apps/web/licenses/lazy-val.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Vladimir Krivosheev - -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. diff --git a/apps/web/licenses/lru-map.txt b/apps/web/licenses/lru-map.txt deleted file mode 100644 index 10568e2774dc..000000000000 --- a/apps/web/licenses/lru-map.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2010-2016 Rasmus Andersson - -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. diff --git a/apps/web/licenses/map-stream.txt b/apps/web/licenses/map-stream.txt deleted file mode 100644 index 4b0db7b71852..000000000000 --- a/apps/web/licenses/map-stream.txt +++ /dev/null @@ -1,24 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2011 Dominic Tarr - -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. diff --git a/apps/web/licenses/msgpackr-extract-linux-x64.txt b/apps/web/licenses/msgpackr-extract-linux-x64.txt deleted file mode 100644 index 97f48aa82439..000000000000 --- a/apps/web/licenses/msgpackr-extract-linux-x64.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Kris Zyp - -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. diff --git a/apps/web/licenses/opencode.txt b/apps/web/licenses/opencode.txt deleted file mode 100644 index 6439474beed8..000000000000 --- a/apps/web/licenses/opencode.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 opencode - -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. diff --git a/apps/web/licenses/pierre-theming.txt b/apps/web/licenses/pierre-theming.txt deleted file mode 100644 index e2c2b06060d8..000000000000 --- a/apps/web/licenses/pierre-theming.txt +++ /dev/null @@ -1,189 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, and - distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by the - copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all other - entities that control, are controlled by, or are under common control with - that entity. For the purposes of this definition, "control" means (i) the - power, direct or indirect, to cause the direction or management of such - entity, whether by contract or otherwise, or (ii) ownership of fifty percent - (50%) or more of the outstanding shares, or (iii) beneficial ownership of - such entity. - - "You" (or "Your") shall mean an individual or Legal Entity exercising - permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation source, and - configuration files. - - "Object" form shall mean any form resulting from mechanical transformation - or translation of a Source form, including but not limited to compiled - object code, generated documentation, and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or Object form, - made available under the License, as indicated by a copyright notice that is - included in or attached to the work (an example is provided in the Appendix - below). - - "Derivative Works" shall mean any work, whether in Source or Object form, - that is based on (or derived from) the Work and for which the editorial - revisions, annotations, elaborations, or other modifications represent, as a - whole, an original work of authorship. For the purposes of this License, - Derivative Works shall not include works that remain separable from, or - merely link (or bind by name) to the interfaces of, the Work and Derivative - Works thereof. - - "Contribution" shall mean any work of authorship, including the original - version of the Work and any modifications or additions to that Work or - Derivative Works thereof, that is intentionally submitted to Licensor for - inclusion in the Work by the copyright owner or by an individual or Legal - Entity authorized to submit on behalf of the copyright owner. For the - purposes of this definition, "submitted" means any form of electronic, - verbal, or written communication sent to the Licensor or its - representatives, including but not limited to communication on electronic - mailing lists, source code control systems, and issue tracking systems that - are managed by, or on behalf of, the Licensor for the purpose of discussing - and improving the Work, but excluding communication that is conspicuously - marked or otherwise designated in writing by the copyright owner as "Not a - Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity on - behalf of whom a Contribution has been received by Licensor and subsequently - incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this - License, each Contributor hereby grants to You a perpetual, worldwide, - non-exclusive, no-charge, royalty-free, irrevocable copyright license to - reproduce, prepare Derivative Works of, publicly display, publicly perform, - sublicense, and distribute the Work and such Derivative Works in Source or - Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this - License, each Contributor hereby grants to You a perpetual, worldwide, - non-exclusive, no-charge, royalty-free, irrevocable (except as stated in - this section) patent license to make, have made, use, offer to sell, sell, - import, and otherwise transfer the Work, where such license applies only to - those patent claims licensable by such Contributor that are necessarily - infringed by their Contribution(s) alone or by combination of their - Contribution(s) with the Work to which such Contribution(s) was submitted. - If You institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work or a - Contribution incorporated within the Work constitutes direct or contributory - patent infringement, then any patent licenses granted to You under this - License for that Work shall terminate as of the date such litigation is - filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or - Derivative Works thereof in any medium, with or without modifications, and - in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a - copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating - that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You - distribute, all copyright, patent, trademark, and attribution notices from - the Source form of the Work, excluding those notices that do not pertain to - any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, - then any Derivative Works that You distribute must include a readable copy - of the attribution notices contained within such NOTICE file, excluding - those notices that do not pertain to any part of the Derivative Works, in at - least one of the following places: within a NOTICE text file distributed as - part of the Derivative Works; within the Source form or documentation, if - provided along with the Derivative Works; or, within a display generated by - the Derivative Works, if and wherever such third-party notices normally - appear. The contents of the NOTICE file are for informational purposes only - and do not modify the License. You may add Your own attribution notices - within Derivative Works that You distribute, alongside or as an addendum to - the NOTICE text from the Work, provided that such additional attribution - notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may - provide additional or different license terms and conditions for use, - reproduction, or distribution of Your modifications, or for any such - Derivative Works as a whole, provided Your use, reproduction, and - distribution of the Work otherwise complies with the conditions stated in - this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any - Contribution intentionally submitted for inclusion in the Work by You to the - Licensor shall be under the terms and conditions of this License, without - any additional terms or conditions. Notwithstanding the above, nothing - herein shall supersede or modify the terms of any separate license agreement - you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, - trademarks, service marks, or product names of the Licensor, except as - required for reasonable and customary use in describing the origin of the - Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in - writing, Licensor provides the Work (and each Contributor provides its - Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied, including, without limitation, any - warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or - FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining - the appropriateness of using or redistributing the Work and assume any risks - associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in - tort (including negligence), contract, or otherwise, unless required by - applicable law (such as deliberate and grossly negligent acts) or agreed to - in writing, shall any Contributor be liable to You for damages, including - any direct, indirect, special, incidental, or consequential damages of any - character arising as a result of this License or out of the use or inability - to use the Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all other - commercial damages or losses), even if such Contributor has been advised of - the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or - Derivative Works thereof, You may choose to offer, and charge a fee for, - acceptance of support, warranty, indemnity, or other liability obligations - and/or rights consistent with this License. However, in accepting such - obligations, You may act only on Your own behalf and on Your sole - responsibility, not on behalf of any other Contributor, and only if You - agree to indemnify, defend, and hold each Contributor harmless for any - liability incurred by, or claims asserted against, such Contributor by - reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2025 Pierre Computer Company - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. diff --git a/apps/web/licenses/react-grab-cli.txt b/apps/web/licenses/react-grab-cli.txt deleted file mode 100644 index 9ffd507155b4..000000000000 --- a/apps/web/licenses/react-grab-cli.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Aiden Bai - -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. diff --git a/apps/web/licenses/type-fest.txt b/apps/web/licenses/type-fest.txt deleted file mode 100644 index fa7ceba3eb4a..000000000000 --- a/apps/web/licenses/type-fest.txt +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -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. diff --git a/apps/web/licenses/xa11y.txt b/apps/web/licenses/xa11y.txt deleted file mode 100644 index 4ecaab641b82..000000000000 --- a/apps/web/licenses/xa11y.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Stephen Crowe - -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. diff --git a/docs/internals/open-source-licenses.md b/docs/internals/open-source-licenses.md index 30b5c5d0193e..7d2b703a540b 100644 --- a/docs/internals/open-source-licenses.md +++ b/docs/internals/open-source-licenses.md @@ -24,8 +24,11 @@ Expo and React Native. That is conservative and can include build tooling that i the final JavaScript bundle, but it avoids dropping a notice when platform bundling changes. The build fails when a collected package has no distributable license identifier or contains no -license or notice file. This turns missing attribution into a build error instead of an empty row -in a release. +license or notice text. Generated notices use license templates from the pinned SPDX License List. +Strict web and EAS builds download a missing template into the gitignored `.generated/` cache; +`pnpm licenses:sync` can warm that cache explicitly. Local web and Metro development do not make a +network request and omit generated rows until the cache exists. This keeps dev startup optional +while preventing incomplete release artifacts. ## Custom notices and package overrides @@ -37,18 +40,24 @@ another asset that did not come from an npm package: { "name": "asset-name", "license": "CC-BY-4.0", - "noticeFile": "licenses/asset-name.txt", + "generatedNotices": [ + { + "licenseId": "CC-BY-4.0", + "preamble": ["Asset by Example Author. Changes: converted to MP3."] + } + ], "sourceUrl": "https://example.com/source", "bundles": ["assets", "web"] } ``` -Paths in `noticeFile` are relative to the config file. Use `noticeFiles` when one row needs to join -several notices, such as a tool that vendors separately licensed code. The files should contain the -complete copyright, attribution, and license text required for redistribution. `bundles` controls -which generated manifests include the entry and supplies the label shown to users. Use -`includeInBundles` when those differ, such as an optional server tool that should appear in both -client manifests but is not bundled into either client. +Each `generatedNotices` item names an SPDX license template and can add `copyrights` or a short +`preamble` for attribution and provenance. Multiple items are joined into one row for software +that vendors separately licensed code. Keep `noticeFile` or `noticeFiles` only when a vendored +source tree already carries an intrinsic license file that should remain beside it. Paths are +relative to the config file. `bundles` controls which generated manifests include the entry and +supplies the label shown to users. Use `includeInBundles` when those differ, such as an optional +server tool that should appear in both client manifests but is not bundled into either client. Use `packageOverrides` only when an installed npm archive omits its notice or has incorrect metadata: @@ -57,7 +66,10 @@ metadata: { "name": "package-name", "version": "1.2.3", - "noticeFile": "licenses/package-name-1.2.3.txt", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2026 Example Author"] + }, "license": "MIT", "sourceUrl": "https://example.com/package-name" } @@ -70,7 +82,9 @@ several packages from one monorepo share the same notice: ```json { "repositoryUrl": "https://github.com/example/project", - "noticeFile": "licenses/project.txt" + "generatedNotice": { + "licenseId": "Apache-2.0" + } } ``` @@ -82,6 +96,6 @@ The `@react-grab/cli` override uses the root React Grab repository's MIT license npm archive omits both its license field and license file. Keep the override until the published CLI package carries that metadata itself. -Generated mobile files live under `apps/mobile/.generated/` and are ignored. Do not commit or edit -them; updating dependencies, configuration, or a notice file is enough for the next Metro startup -to refresh the manifest. +Generated mobile files live under `apps/mobile/.generated/`, while fetched SPDX templates live +under the repository `.generated/` directory. Both are ignored. Do not commit or edit them; +updating dependencies or configuration is enough for the next strict build to refresh the output. diff --git a/package.json b/package.json index dd1a106a0409..ab88fbeb4d6e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "screenshots:mobile": "node scripts/mobile-showcase.ts", "icons:export": "node scripts/export-brand-icons.ts", "icons:check": "node scripts/export-brand-icons.ts --check", + "licenses:sync": "node scripts/sync-third-party-license-notices.ts", "icons:export:android": "node scripts/export-android-icons.ts", "build": "vp run --filter './apps/*' build", "build:marketing": "vp run --filter @t3tools/marketing build", diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index 078ec28e875d..1b5307965839 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -104,6 +104,66 @@ describe("third-party license generation", () => { }); }); + it("renders generated notices from the ignored SPDX cache", async () => { + const fixture = await createFixture(); + await writeJson( + NodePath.join(fixture.root, ".generated/third-party-licenses/spdx/v3.28.0/MIT.json"), + { + licenseId: "MIT", + licenseText: "MIT License\n\nCopyright (c) \n\nPermission text", + }, + ); + await writeJson(fixture.configFile, { + customNotices: [ + { + name: "generated-asset", + license: "MIT", + generatedNotices: [ + { + licenseId: "MIT", + copyrights: ["Copyright (c) 2026 Example Author"], + preamble: ["Adapted for T3 Code."], + }, + ], + bundles: ["assets", "web"], + }, + ], + packageOverrides: [], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }); + + expect(manifest.entries.find((entry) => entry.name === "generated-asset")?.noticeText).toBe( + "Adapted for T3 Code.\n\nMIT License\n\nCopyright (c) 2026 Example Author\n\nPermission text", + ); + }); + + it("omits generated rows without a cache during optional development", async () => { + const fixture = await createFixture(); + await writeJson(fixture.configFile, { + customNotices: [ + { + name: "generated-asset", + license: "MIT", + generatedNotices: [{ licenseId: "MIT" }], + bundles: ["assets", "web"], + }, + ], + packageOverrides: [], + }); + + const manifest = await generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + allowMissingGeneratedNotices: true, + }); + + expect(manifest.entries.some((entry) => entry.name === "generated-asset")).toBe(false); + }); + it("finds packages whose exports hide both their manifest and entry point", async () => { const fixture = await createFixture(); await writeJson(NodePath.join(fixture.dependencyRoot, "package.json"), { diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index 6c07aebf23f9..0545db9f6c48 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -1,4 +1,4 @@ -// @effect-diagnostics nodeBuiltinImport:off - Vite's build plugin runs before an Effect runtime exists. +// @effect-diagnostics nodeBuiltinImport:off globalFetch:off - Vite's build plugin runs before an Effect runtime exists. import * as NodeFSP from "node:fs/promises"; import * as NodePath from "node:path"; @@ -8,6 +8,9 @@ import * as NodeModule from "node:module"; import type { Plugin } from "vite-plus"; export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json"; +const SPDX_LICENSE_LIST_VERSION = "v3.28.0"; +const SPDX_LICENSE_LIST_REVISION = "c4a7237ec8f4654e867546f9f409749300f1bf4c"; +const GENERATED_NOTICE_CACHE_DIRECTORY = ".generated/third-party-licenses/spdx"; export interface ThirdPartyLicenseEntry { readonly bundles: ReadonlyArray; @@ -35,6 +38,12 @@ export interface ThirdPartyLicensesPluginOptions { readonly bundleName: string; } +interface GeneratedNoticeConfigEntry { + readonly copyrights?: ReadonlyArray; + readonly licenseId: string; + readonly preamble?: ReadonlyArray; +} + interface PackageJson { readonly dependencies?: Readonly>; readonly homepage?: unknown; @@ -51,12 +60,14 @@ interface CustomNoticeConfigEntry { readonly includeInBundles?: ReadonlyArray; readonly license: string; readonly name: string; - readonly noticeFiles: ReadonlyArray; + readonly generatedNotices?: ReadonlyArray; + readonly noticeFiles?: ReadonlyArray; readonly sourceUrl?: string; readonly version?: string; } interface PackageNoticeOverrideConfigEntry { + readonly generatedNotice?: GeneratedNoticeConfigEntry; readonly license?: string; readonly name?: string; readonly noticeFile?: string; @@ -70,6 +81,11 @@ interface ThirdPartyLicensesConfig { readonly packageOverrides: ReadonlyArray; } +interface SpdxLicenseDetails { + readonly licenseId: string; + readonly licenseText: string; +} + interface CollectedPackage { readonly bundles: Set; readonly packageJson: PackageJson; @@ -134,6 +150,30 @@ function readOptionalStringArray( return field.map((entry) => (entry as string).trim()); } +function decodeGeneratedNotice(value: unknown, context: string): GeneratedNoticeConfigEntry { + if (!isRecord(value)) throw new Error(`${context} must be an object.`); + const copyrights = readOptionalStringArray(value, "copyrights", context); + const preamble = readOptionalStringArray(value, "preamble", context); + return { + licenseId: readRequiredString(value, "licenseId", context), + ...(copyrights !== undefined ? { copyrights } : {}), + ...(preamble !== undefined ? { preamble } : {}), + }; +} + +function decodeGeneratedNotices( + value: unknown, + context: string, +): ReadonlyArray | undefined { + if (value === undefined) return undefined; + if (!Array.isArray(value) || value.length === 0) { + throw new Error(`${context} must define "generatedNotices" as a non-empty array.`); + } + return value.map((entry, index) => + decodeGeneratedNotice(entry, `${context} generated notice at index ${String(index)}`), + ); +} + function decodeCustomNotices(value: unknown): ReadonlyArray { if (value === undefined) return []; if (!Array.isArray(value)) { @@ -148,13 +188,22 @@ function decodeCustomNotices(value: unknown): ReadonlyArray { + try { + const source = await NodeFSP.readFile(spdxLicenseCachePath(configDirectory, licenseId), "utf8"); + return decodeSpdxLicenseDetails(JSON.parse(source) as unknown, licenseId); + } catch (error) { + const code = isRecord(error) && typeof error.code === "string" ? error.code : null; + if (code === "ENOENT") return null; + throw error; + } +} + +async function downloadSpdxLicense( + configDirectory: string, + licenseId: string, +): Promise { + const url = `https://raw.githubusercontent.com/spdx/license-list-data/${SPDX_LICENSE_LIST_REVISION}/json/details/${encodeURIComponent(licenseId)}.json`; + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Could not download SPDX license ${licenseId}: HTTP ${String(response.status)}.`, + ); + } + const details = decodeSpdxLicenseDetails((await response.json()) as unknown, licenseId); + const cachePath = spdxLicenseCachePath(configDirectory, licenseId); + await NodeFSP.mkdir(NodePath.dirname(cachePath), { recursive: true }); + await NodeFSP.writeFile(cachePath, `${JSON.stringify(details)}\n`, "utf8"); + return details; +} + +async function resolveSpdxLicense( + configDirectory: string, + licenseId: string, + allowMissing: boolean, +): Promise { + const cached = await readCachedSpdxLicense(configDirectory, licenseId); + if (cached || allowMissing) return cached; + return downloadSpdxLicense(configDirectory, licenseId); +} + +function renderGeneratedNotice(config: GeneratedNoticeConfigEntry, licenseText: string): string { + const copyrights = config.copyrights ?? []; + let renderedLicense = licenseText; + if (copyrights.length > 0) { + const placeholderPattern = /^Copyright[^\n]*(?:||)[^\n]*$/m; + if (placeholderPattern.test(renderedLicense)) { + renderedLicense = renderedLicense.replace(placeholderPattern, copyrights.join("\n")); + } else if (config.licenseId === "ISC") { + renderedLicense = renderedLicense.replace( + /^(?:Copyright[^\n]*\n)+/m, + `${copyrights.join("\n")}\n`, + ); + } else { + renderedLicense = `${copyrights.join("\n")}\n\n${renderedLicense}`; + } + } + return [...(config.preamble ?? []), renderedLicense].join("\n\n").trim(); +} + +async function generatedNoticeText( + configs: ReadonlyArray, + configDirectory: string, + allowMissing: boolean, +): Promise { + const sections = await Promise.all( + configs.map(async (config) => { + const details = await resolveSpdxLicense(configDirectory, config.licenseId, allowMissing); + return details ? renderGeneratedNotice(config, details.licenseText) : null; + }), + ); + return sections.some((section) => section === null) + ? null + : (sections as ReadonlyArray).join("\n\n---\n\n"); +} + +function configuredGeneratedNotices( + config: ThirdPartyLicensesConfig, +): ReadonlyArray { + return [ + ...config.customNotices.flatMap((notice) => notice.generatedNotices ?? []), + ...config.packageOverrides.flatMap((override) => + override.generatedNotice ? [override.generatedNotice] : [], + ), + ]; +} + +async function syncConfiguredGeneratedNotices( + config: ThirdPartyLicensesConfig, + directory: string, +): Promise { + const licenseIds = [ + ...new Set(configuredGeneratedNotices(config).map((notice) => notice.licenseId)), + ].sort((left, right) => left.localeCompare(right)); + await Promise.all(licenseIds.map((licenseId) => resolveSpdxLicense(directory, licenseId, false))); +} + +export async function syncThirdPartyLicenseNotices(configFile: string | URL): Promise { + const { config, directory } = await readConfig(configFile); + await syncConfiguredGeneratedNotices(config, directory); +} + async function readPackageJson(packageJsonPath: string): Promise { const source = await NodeFSP.readFile(packageJsonPath, "utf8"); const value = JSON.parse(source) as unknown; @@ -554,7 +735,8 @@ async function packageEntry( configDirectory: string, packageNotices: Map>, repositoryNotices: ReadonlyMap, -): Promise { + allowMissingGeneratedNotices: boolean, +): Promise { const name = typeof collected.packageJson.name === "string" ? collected.packageJson.name @@ -575,15 +757,22 @@ async function packageEntry( } const repositoryKey = repositoryNoticeKey(collected.packageJson, license); - const noticeText = override?.noticeFile - ? ( - await NodeFSP.readFile(NodePath.resolve(configDirectory, override.noticeFile), "utf8") - ).trim() - : ((await packageNoticeText(collected.packageRoot, packageNotices)) ?? - (repositoryKey ? repositoryNotices.get(repositoryKey) : undefined)); + const noticeText = override?.generatedNotice + ? await generatedNoticeText( + [override.generatedNotice], + configDirectory, + allowMissingGeneratedNotices, + ) + : override?.noticeFile + ? ( + await NodeFSP.readFile(NodePath.resolve(configDirectory, override.noticeFile), "utf8") + ).trim() + : ((await packageNoticeText(collected.packageRoot, packageNotices)) ?? + (repositoryKey ? repositoryNotices.get(repositoryKey) : undefined)); if (!noticeText) { + if (override?.generatedNotice && allowMissingGeneratedNotices) return null; throw new Error( - `${name}@${version} does not include a license or notice file. Add a package override with "noticeFile" in the third-party license config.`, + `${name}@${version} does not include a license or notice file. Add a package override with "noticeFile" or "generatedNotice" in the third-party license config.`, ); } @@ -602,8 +791,9 @@ async function customEntries( config: ThirdPartyLicensesConfig, configDirectory: string, includedBundles: ReadonlySet, + allowMissingGeneratedNotices: boolean, ): Promise> { - return Promise.all( + const entries = await Promise.all( config.customNotices .filter( (notice) => @@ -613,18 +803,29 @@ async function customEntries( ), ) .map(async (notice) => { - const noticeSections = await Promise.all( - notice.noticeFiles.map(async (noticeFile) => { - const contents = ( - await NodeFSP.readFile(NodePath.resolve(configDirectory, noticeFile), "utf8") - ).trim(); - if (contents.length === 0) { - throw new Error(`Custom third-party notice "${notice.name}" is empty.`); - } - return contents; - }), - ); - const noticeText = noticeSections.join("\n\n---\n\n"); + const noticeText = notice.generatedNotices + ? await generatedNoticeText( + notice.generatedNotices, + configDirectory, + allowMissingGeneratedNotices, + ) + : ( + await Promise.all( + notice.noticeFiles!.map(async (noticeFile) => { + const contents = ( + await NodeFSP.readFile(NodePath.resolve(configDirectory, noticeFile), "utf8") + ).trim(); + if (contents.length === 0) { + throw new Error(`Custom third-party notice "${notice.name}" is empty.`); + } + return contents; + }), + ) + ).join("\n\n---\n\n"); + if (noticeText === null) { + if (allowMissingGeneratedNotices) return null; + throw new Error(`Could not generate custom third-party notice "${notice.name}".`); + } if (noticeText.length === 0) { throw new Error(`Custom third-party notice "${notice.name}" is empty.`); } @@ -641,6 +842,7 @@ async function customEntries( }; }), ); + return entries.flatMap((entry) => (entry ? [entry] : [])); } function entrySort(left: ThirdPartyLicenseEntry, right: ThirdPartyLicenseEntry): number { @@ -656,11 +858,15 @@ export async function generateThirdPartyLicenseManifest(input: { readonly packageManifests: ReadonlyArray; readonly bundledModuleIds?: ReadonlyArray; readonly bundleName?: string; + readonly allowMissingGeneratedNotices?: boolean; }): Promise { const [{ config, directory }, collection] = await Promise.all([ readConfig(input.configFile), collectProductionDependencyPackages(input.packageManifests), ]); + if (!(input.allowMissingGeneratedNotices ?? false)) { + await syncConfiguredGeneratedNotices(config, directory); + } if (input.bundledModuleIds && input.bundleName) { await addBundledModulePackages(collection, input.bundledModuleIds, input.bundleName); } @@ -670,7 +876,14 @@ export async function generateThirdPartyLicenseManifest(input: { const packageEntryResults = await Promise.allSettled( [...collection.byIdentity.values()].map((collected) => - packageEntry(collected, config, directory, packageNotices, repositoryNotices), + packageEntry( + collected, + config, + directory, + packageNotices, + repositoryNotices, + input.allowMissingGeneratedNotices ?? false, + ), ), ); const failures = packageEntryResults.flatMap((result) => @@ -684,11 +897,16 @@ export async function generateThirdPartyLicenseManifest(input: { ); } const packageEntries = packageEntryResults.flatMap((result) => - result.status === "fulfilled" ? [result.value] : [], + result.status === "fulfilled" && result.value ? [result.value] : [], ); const includedBundles = new Set(input.packageManifests.map((manifest) => manifest.bundle)); if (input.bundleName) includedBundles.add(input.bundleName); - const manualEntries = await customEntries(config, directory, includedBundles); + const manualEntries = await customEntries( + config, + directory, + includedBundles, + input.allowMissingGeneratedNotices ?? false, + ); return { schemaVersion: 1, entries: [...packageEntries, ...manualEntries].sort(entrySort), @@ -722,6 +940,7 @@ export function thirdPartyLicensesPlugin(options: ThirdPartyLicensesPluginOption manifestPromise ??= generateThirdPartyLicenseManifest({ packageManifests: options.packageManifests, bundleName: options.bundleName, + allowMissingGeneratedNotices: true, ...(options.configFile !== undefined ? { configFile: options.configFile } : {}), }).catch((error: unknown) => { manifestPromise = null; diff --git a/scripts/sync-third-party-license-notices.ts b/scripts/sync-third-party-license-notices.ts new file mode 100644 index 000000000000..55a3fc0eeae8 --- /dev/null +++ b/scripts/sync-third-party-license-notices.ts @@ -0,0 +1,9 @@ +// @effect-diagnostics nodeBuiltinImport:off - This is a build-time filesystem script. + +import * as NodePath from "node:path"; + +import { syncThirdPartyLicenseNotices } from "./lib/third-party-licenses.ts"; + +const configFile = NodePath.resolve("third-party-licenses.config.json"); + +await syncThirdPartyLicenseNotices(configFile); diff --git a/third-party-licenses.config.json b/third-party-licenses.config.json index b8d54db854be..836655e15c13 100644 --- a/third-party-licenses.config.json +++ b/third-party-licenses.config.json @@ -4,41 +4,67 @@ "bundles": ["assets", "desktop", "web"], "license": "CC0-1.0", "name": "Folder_Whoosh.wav by BaggoNotes", - "noticeFiles": ["apps/web/licenses/folder-whoosh.txt", "apps/web/licenses/cc0-1.0.txt"], - "sourceUrl": "https://freesound.org/people/BaggoNotes/sounds/704258/" + "sourceUrl": "https://freesound.org/people/BaggoNotes/sounds/704258/", + "generatedNotices": [ + { + "licenseId": "CC0-1.0", + "preamble": [ + "The bundled snap-shot-whoosh.mp3 is derived from Folder_Whoosh.wav by BaggoNotes:\nhttps://freesound.org/people/BaggoNotes/sounds/704258/\n\nThe original sound is dedicated to the public domain under CC0 1.0 Universal." + ] + } + ] }, { "bundles": ["assets", "desktop", "web"], "license": "CC0-1.0", "name": "Contarex camera shutter.wav by Tonik1105", - "noticeFiles": [ - "apps/web/licenses/contarex-camera-shutter.txt", - "apps/web/licenses/cc0-1.0.txt" - ], - "sourceUrl": "https://freesound.org/people/Tonik1105/sounds/520684/" + "sourceUrl": "https://freesound.org/people/Tonik1105/sounds/520684/", + "generatedNotices": [ + { + "licenseId": "CC0-1.0", + "preamble": [ + "The bundled snap-shot-click.mp3 is derived from Contarex camera shutter.wav by Tonik1105:\nhttps://freesound.org/people/Tonik1105/sounds/520684/\n\nThe original sound is dedicated to the public domain under CC0 1.0 Universal." + ] + } + ] }, { "bundles": ["device-tools"], "includeInBundles": ["mobile", "web"], "license": "MIT", "name": "agent-device", - "noticeFile": "apps/web/licenses/agent-device.txt", "sourceUrl": "https://github.com/callstack/agent-device", - "version": "0.20.10" + "version": "0.20.10", + "generatedNotices": [ + { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2026 Callstack"] + } + ] }, { "bundles": ["device-tools"], "includeInBundles": ["mobile", "web"], "license": "MIT AND Apache-2.0 AND BSD-3-Clause", "name": "expo-device-hub", - "noticeFiles": [ - "apps/web/licenses/expo-device-hub.txt", - "apps/web/licenses/kubernetes-types.txt", - "apps/web/licenses/expo-device-hub-serve-sim-notice.txt", - "apps/web/licenses/expo-device-hub-webrtc.txt" - ], "sourceUrl": "https://github.com/expo/expo-device-hub", - "version": "0.9.0" + "version": "0.9.0", + "generatedNotices": [ + { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)"] + }, + { + "licenseId": "Apache-2.0", + "preamble": [ + "This distribution includes software from the original serve-sim project, created and open-sourced by Evan Bacon:\nhttps://github.com/EvanBacon/serve-sim\n\nOriginal work copyright 2026 Evan Bacon. This fork contains modifications by Expo and other contributors." + ] + }, + { + "licenseId": "BSD-3-Clause", + "copyrights": ["Copyright (c) 2011, The WebRTC project authors. All rights reserved."] + } + ] }, { "bundles": ["android", "assets", "mobile", "web"], @@ -58,15 +84,28 @@ "bundles": ["ios", "mobile"], "license": "MIT", "name": "GhosttyKit", - "noticeFile": "apps/mobile/licenses/ghostty-kit.txt", - "sourceUrl": "https://github.com/Yash-Singh1/ghostty/tree/custom-io" + "sourceUrl": "https://github.com/Yash-Singh1/ghostty/tree/custom-io", + "generatedNotices": [ + { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2024 Mitchell Hashimoto, Ghostty contributors"], + "preamble": [ + "GhosttyKit\n\nThe iOS terminal renderer vendors GhosttyKit.xcframework, a libghostty build produced from T3's iOS 16 support fork. That fork was created from VVTerm's custom-I/O Ghostty fork.\n\nUpstream project: https://github.com/ghostty-org/ghostty\nCustom-I/O base fork: https://github.com/wiedymi/ghostty/tree/custom-io\nVendored source fork: https://github.com/Yash-Singh1/ghostty/tree/custom-io\nVendored revision: d36c3b8dffd0d756dd5e5f4933962f774a0e6753\nReference integration: https://github.com/vivy-company/vvterm" + ] + } + ] }, { "bundles": ["android", "assets", "mobile"], "license": "Apache-2.0", "name": "MesloLGS NF", - "noticeFile": "apps/mobile/licenses/meslo-lgs-nf.txt", - "sourceUrl": "https://github.com/romkatv/powerlevel10k-media" + "sourceUrl": "https://github.com/romkatv/powerlevel10k-media", + "generatedNotices": [ + { + "licenseId": "Apache-2.0", + "copyrights": ["Copyright 2009, 2010, 2013 André Berg"] + } + ] }, { "bundles": ["mobile"], @@ -88,58 +127,98 @@ { "license": "MIT", "name": "@react-grab/cli", - "noticeFile": "apps/web/licenses/react-grab-cli.txt", - "sourceUrl": "https://github.com/aidenybai/react-grab/tree/main/packages/cli" + "sourceUrl": "https://github.com/aidenybai/react-grab/tree/main/packages/cli", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2025 Aiden Bai"] + } }, { "name": "glob-to-regexp", - "noticeFile": "apps/web/licenses/glob-to-regexp.txt" + "generatedNotice": { + "licenseId": "BSD-2-Clause", + "copyrights": ["Copyright (c) 2013, Nick Fitzgerald"] + } }, { "name": "kubernetes-types", - "noticeFile": "apps/web/licenses/kubernetes-types.txt" + "generatedNotice": { + "licenseId": "Apache-2.0" + } }, { "name": "@msgpackr-extract/msgpackr-extract-linux-x64", - "noticeFile": "apps/web/licenses/msgpackr-extract-linux-x64.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2020 Kris Zyp"] + } }, { "name": "isarray", - "noticeFile": "apps/web/licenses/isarray.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2013 Julian Gruber "] + } }, { "name": "keyv", - "noticeFile": "apps/web/licenses/keyv.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2017-2021 Luke Childs", "Copyright (c) 2021-2022 Jared Wray"] + } }, { "license": "MIT", "name": "type-fest", - "noticeFile": "apps/web/licenses/type-fest.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": [ + "Copyright (c) Sindre Sorhus (https://sindresorhus.com)" + ] + } }, { "name": "lazy-val", - "noticeFile": "apps/web/licenses/lazy-val.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Vladimir Krivosheev"] + } }, { "name": "@pierre/theming", - "noticeFile": "apps/web/licenses/pierre-theming.txt" + "generatedNotice": { + "licenseId": "Apache-2.0", + "copyrights": ["Copyright 2025 Pierre Computer Company"] + } }, { "name": "lru_map", - "noticeFile": "apps/web/licenses/lru-map.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2010-2016 Rasmus Andersson "] + } }, { "name": "@electron-internal/extract-zip", - "noticeFile": "apps/web/licenses/electron-internal-extract-zip.txt" + "generatedNotice": { + "licenseId": "BSD-2-Clause", + "copyrights": ["Copyright (c) 2026 Samuel Attard and Electron contributors"] + } }, { - "noticeFile": "apps/web/licenses/xa11y.txt", - "repositoryUrl": "https://github.com/xa11y/xa11y" + "repositoryUrl": "https://github.com/xa11y/xa11y", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2025 Stephen Crowe"] + } }, { "license": "MIT", "name": "map-stream", - "noticeFile": "apps/web/licenses/map-stream.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2011 Dominic Tarr"] + } }, { "license": "Apache-2.0", @@ -147,68 +226,119 @@ }, { "name": "@yuuang/ffi-rs-android-arm64", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-darwin-arm64", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-darwin-x64", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-linux-arm-gnueabihf", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-linux-arm64-gnu", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-linux-arm64-musl", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-linux-x64-gnu", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-linux-x64-musl", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-win32-arm64-msvc", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-win32-ia32-msvc", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { "name": "@yuuang/ffi-rs-win32-x64-msvc", - "noticeFile": "apps/web/licenses/ffi-rs.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2019 zhangyuang"] + } }, { - "noticeFile": "apps/mobile/licenses/metro.txt", - "repositoryUrl": "https://github.com/facebook/metro" + "repositoryUrl": "https://github.com/facebook/metro", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Meta Platforms, Inc. and affiliates."] + } }, { - "noticeFile": "apps/mobile/licenses/metro.txt", - "repositoryUrl": "https://github.com/react/metro" + "repositoryUrl": "https://github.com/react/metro", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Meta Platforms, Inc. and affiliates."] + } }, { "name": "@react-native-ai/apple", - "noticeFile": "apps/mobile/licenses/react-native-ai.txt", - "sourceUrl": "https://github.com/callstackincubator/ai" + "sourceUrl": "https://github.com/callstackincubator/ai", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": [ + "Copyright (c) 2024-2025 Szymon Rybczak", + "Copyright (c) 2025-present Callstack" + ] + } }, { "name": "react-remove-scroll-bar", - "noticeFile": "apps/mobile/licenses/react-remove-scroll-bar.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Anton Korzunov "] + } }, { "name": "standard-navigation", - "noticeFile": "apps/mobile/licenses/standard-navigation.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2026 React Navigation Contributors"] + } }, { "name": "@expo/sdk-runtime-versions", @@ -220,61 +350,101 @@ }, { "name": "stream-buffers", - "noticeFile": "apps/mobile/licenses/stream-buffers.txt" + "generatedNotice": { + "licenseId": "Unlicense" + } }, { "name": "bplist-parser", - "noticeFile": "apps/mobile/licenses/bplist-parser.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2012 Near Infinity Corporation"] + } }, { "name": "@expo/devcert", - "noticeFile": "apps/mobile/licenses/expo-devcert.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Dave Wasmer"] + } }, { "name": "jimp-compact", - "noticeFile": "apps/mobile/licenses/jimp-compact.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2018 Oliver Moran"] + } }, { "name": "fb-watchman", - "noticeFile": "apps/web/licenses/kubernetes-types.txt" + "generatedNotice": { + "licenseId": "Apache-2.0" + } }, { "name": "bser", - "noticeFile": "apps/web/licenses/kubernetes-types.txt" + "generatedNotice": { + "licenseId": "Apache-2.0" + } }, { "name": "@expo/xcpretty", - "noticeFile": "apps/mobile/licenses/expo-xcpretty.txt" + "generatedNotice": { + "licenseId": "BSD-3-Clause", + "copyrights": ["Copyright (c) 2016-present, 650 Industries, Inc. (Expo)"] + } }, { "license": "MIT", "name": "fb-dotslash", - "noticeFile": "apps/mobile/licenses/fb-dotslash.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Meta Platforms, Inc. and affiliates."] + } }, { "name": "structured-headers", - "noticeFile": "apps/mobile/licenses/structured-headers.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2018 Evert Pot"] + } }, { "name": "badgin", - "noticeFile": "apps/mobile/licenses/badgin.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) Julian Hundeloh and Tinycon contributors"], + "preamble": ["badgin is a refactored fork of Tinycon."] + } }, { "name": "react-native-nitro-modules", - "noticeFile": "apps/mobile/licenses/react-native-nitro-modules.txt" + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2024 Marc Rousavy"] + } }, { "name": "boolbase", - "noticeFile": "apps/mobile/licenses/boolbase.txt" + "generatedNotice": { + "licenseId": "ISC", + "copyrights": ["Copyright (c) 2014-2015, Felix Boehm "] + } }, { - "noticeFile": "apps/web/licenses/fff.txt", - "repositoryUrl": "https://github.com/dmtrKovalenko/fff" + "repositoryUrl": "https://github.com/dmtrKovalenko/fff", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2025 Dmitriy Kovalenko"] + } }, { "name": "@opencode-ai/sdk", - "noticeFile": "apps/web/licenses/opencode.txt", - "sourceUrl": "https://github.com/anomalyco/opencode/tree/dev/packages/sdk/js" + "sourceUrl": "https://github.com/anomalyco/opencode/tree/dev/packages/sdk/js", + "generatedNotice": { + "licenseId": "MIT", + "copyrights": ["Copyright (c) 2025 opencode"] + } } ] } From c6b2d66620b6c43148fd0b84dbb141a58b8a3975 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 16:58:39 -0700 Subject: [PATCH 11/14] fix(build): include nested package notices --- scripts/lib/third-party-licenses.test.ts | 5 ++--- scripts/lib/third-party-licenses.ts | 16 +++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index 1b5307965839..412dd6f5d5ce 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -256,9 +256,8 @@ describe("third-party license generation", () => { ).rejects.toThrow("does not include a license or notice file"); }); - it("collects notices nested inside a published package", async () => { + it("collects nested notices even when a package also has a root license", async () => { const fixture = await createFixture(); - await NodeFSP.rm(NodePath.join(fixture.dependencyRoot, "LICENSE")); await NodeFSP.mkdir(NodePath.join(fixture.dependencyRoot, "dist", "third-party"), { recursive: true, }); @@ -274,7 +273,7 @@ describe("third-party license generation", () => { }); expect(manifest.entries.find((entry) => entry.name === "demo-dependency")?.noticeText).toBe( - "Nested notice", + "dist/third-party/NOTICE.txt\n\nNested notice\n\n---\n\nLICENSE\n\nDemo MIT license text", ); }); diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index 0545db9f6c48..356ea21d9f03 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -652,15 +652,13 @@ async function readPackageNoticeText(packageRoot: string): Promise entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git", - ) - .map((entry) => collectNestedNoticeFiles(entry.name, 2)), - ); - } + await Promise.all( + rootEntries + .filter( + (entry) => entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git", + ) + .map((entry) => collectNestedNoticeFiles(entry.name, 2)), + ); noticeFiles.sort((left, right) => left.localeCompare(right)); if (noticeFiles.length === 0) return null; From aad357c06950f7f622418235b762d510b9f63bbf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 17:07:16 -0700 Subject: [PATCH 12/14] fix(mobile): defer third-party notice decoding --- .../SettingsOpenSourceLicensesRouteScreen.tsx | 41 ++++++++++++++++--- .../settings/mobileThirdPartyLicenses.ts | 15 +++++-- .../settings/SettingsSidebarNav.tsx | 4 +- docs/internals/open-source-licenses.md | 5 ++- scripts/lib/third-party-licenses.test.ts | 28 +++++++++++++ scripts/lib/third-party-licenses.ts | 17 +++++++- 6 files changed, 95 insertions(+), 15 deletions(-) diff --git a/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx index ca0c99f0d268..48be8d4408dc 100644 --- a/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx @@ -15,7 +15,17 @@ import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; import { SymbolView } from "../../components/AppSymbol"; import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; import { NativeStackScreenOptions } from "../../native/StackHeader"; -import { MOBILE_THIRD_PARTY_LICENSES } from "./mobileThirdPartyLicenses"; +import { getMobileThirdPartyLicenses } from "./mobileThirdPartyLicenses"; + +function useMobileThirdPartyLicenses() { + return useMemo(() => { + try { + return getMobileThirdPartyLicenses(); + } catch { + return null; + } + }, []); +} function LicenseRow(props: { readonly entry: ThirdPartyLicenseEntry; @@ -55,7 +65,8 @@ export function SettingsOpenSourceLicensesRouteScreen() { const navigation = useNavigation(); const insets = useSafeAreaInsets(); const [query, setQuery] = useState(""); - const entries = MOBILE_THIRD_PARTY_LICENSES.entries; + const manifest = useMobileThirdPartyLicenses(); + const entries = manifest?.entries ?? []; const filteredEntries = useMemo( () => filterThirdPartyLicenseEntries(entries, query), [entries, query], @@ -78,6 +89,24 @@ export function SettingsOpenSourceLicensesRouteScreen() { [navigation], ); + if (!manifest) { + return ( + + {Platform.OS === "android" ? ( + <> + + navigation.goBack()} /> + + ) : null} + + + License notices are unavailable in this build. + + + + ); + } + return ( {Platform.OS === "android" ? ( @@ -136,10 +165,10 @@ type LicenseDetailProps = StaticScreenProps<{ readonly entryKey: string }>; export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailProps) { const navigation = useNavigation(); const insets = useSafeAreaInsets(); - const entry = findThirdPartyLicenseEntry( - MOBILE_THIRD_PARTY_LICENSES.entries, - route.params.entryKey, - ); + const manifest = useMobileThirdPartyLicenses(); + const entry = manifest + ? findThirdPartyLicenseEntry(manifest.entries, route.params.entryKey) + : undefined; const sourceUrl = entry?.sourceUrl?.match(/^https?:\/\//) ? entry.sourceUrl : null; if (!entry) { diff --git a/apps/mobile/src/features/settings/mobileThirdPartyLicenses.ts b/apps/mobile/src/features/settings/mobileThirdPartyLicenses.ts index c60ed0cb8d88..3857d132c210 100644 --- a/apps/mobile/src/features/settings/mobileThirdPartyLicenses.ts +++ b/apps/mobile/src/features/settings/mobileThirdPartyLicenses.ts @@ -1,4 +1,13 @@ -import generatedManifest from "@t3tools/mobile-third-party-licenses"; -import { decodeThirdPartyLicenseManifest } from "@t3tools/shared/thirdPartyLicenses"; +import { + decodeThirdPartyLicenseManifest, + type ThirdPartyLicenseManifest, +} from "@t3tools/shared/thirdPartyLicenses"; -export const MOBILE_THIRD_PARTY_LICENSES = decodeThirdPartyLicenseManifest(generatedManifest); +let cachedManifest: ThirdPartyLicenseManifest | undefined; + +export function getMobileThirdPartyLicenses(): ThirdPartyLicenseManifest { + if (cachedManifest) return cachedManifest; + const generatedManifest: unknown = require("@t3tools/mobile-third-party-licenses"); + cachedManifest = decodeThirdPartyLicenseManifest(generatedManifest); + return cachedManifest; +} diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx index 220904e179c5..f215d6c25e11 100644 --- a/apps/web/src/components/settings/SettingsSidebarNav.tsx +++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx @@ -323,9 +323,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { {navItems.map((item) => { const Icon = item.icon; const isGeneralDetailPage = - item.to === "/settings/general" && - (pathname === "/settings/diagnostics" || - pathname === "/settings/open-source-licenses"); + item.to === "/settings/general" && pathname === "/settings/open-source-licenses"; const isActive = isGeneralDetailPage || pathname === item.to || pathname.startsWith(`${item.to}/`); return ( diff --git a/docs/internals/open-source-licenses.md b/docs/internals/open-source-licenses.md index 7d2b703a540b..5e882e5c87fa 100644 --- a/docs/internals/open-source-licenses.md +++ b/docs/internals/open-source-licenses.md @@ -6,8 +6,9 @@ License notices are generated independently for the client that ships them: static file, so the same artifact works in hosted web, the client bundled with `npx t3`, and desktop. - The mobile Metro config generates an ignored virtual module before each development, native, or - over-the-air JavaScript bundle. Mobile reads the manifest directly from that module and does not - need a network request. + over-the-air JavaScript bundle. Mobile loads and decodes that module only when a license screen + opens, so the notice text does not occupy memory during ordinary app startup. It does not need a + network request. Neither path depends on the connected environment or an RPC. diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index 412dd6f5d5ce..f75687bce175 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -447,4 +447,32 @@ describe("third-party license generation", () => { }), ).rejects.toThrow('Custom third-party notice "demo-asset" is empty'); }); + + it("fails generation when custom notices produce duplicate navigation keys", async () => { + const fixture = await createFixture(); + await writeJson(fixture.configFile, { + customNotices: [ + { + name: "duplicate-asset", + license: "MIT", + noticeFile: "asset-notice.txt", + bundles: ["assets", "web"], + }, + { + name: "duplicate-asset", + license: "CC0-1.0", + noticeFile: "asset-notice.txt", + bundles: ["assets", "web"], + }, + ], + packageOverrides: [], + }); + + await expect( + generateThirdPartyLicenseManifest({ + configFile: fixture.configFile, + packageManifests: [{ bundle: "web", path: fixture.appManifest }], + }), + ).rejects.toThrow("duplicate custom notice for duplicate-asset"); + }); }); diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index 356ea21d9f03..ac8e8e9ed8f3 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -851,6 +851,19 @@ function entrySort(left: ThirdPartyLicenseEntry, right: ThirdPartyLicenseEntry): ); } +function assertUniqueEntries(entries: ReadonlyArray): void { + const identities = new Set(); + for (const entry of entries) { + const identity = JSON.stringify([entry.kind, entry.name, entry.version]); + if (identities.has(identity)) { + throw new Error( + `Third-party license generation found a duplicate ${entry.kind} notice for ${entry.name}${entry.version ? `@${entry.version}` : ""}.`, + ); + } + identities.add(identity); + } +} + export async function generateThirdPartyLicenseManifest(input: { readonly configFile?: string | URL; readonly packageManifests: ReadonlyArray; @@ -905,9 +918,11 @@ export async function generateThirdPartyLicenseManifest(input: { includedBundles, input.allowMissingGeneratedNotices ?? false, ); + const entries = [...packageEntries, ...manualEntries].sort(entrySort); + assertUniqueEntries(entries); return { schemaVersion: 1, - entries: [...packageEntries, ...manualEntries].sort(entrySort), + entries, }; } From d5320a9fe7fad3230102b01ceede24a9c9058c65 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 17:28:47 -0700 Subject: [PATCH 13/14] fix(build): ignore non-text notice files --- scripts/lib/third-party-licenses.test.ts | 6 +++++ scripts/lib/third-party-licenses.ts | 29 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index f75687bce175..f8b7fcfbb74e 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -261,11 +261,17 @@ describe("third-party license generation", () => { await NodeFSP.mkdir(NodePath.join(fixture.dependencyRoot, "dist", "third-party"), { recursive: true, }); + await NodeFSP.mkdir(NodePath.join(fixture.dependencyRoot, "lib"), { recursive: true }); await NodeFSP.writeFile( NodePath.join(fixture.dependencyRoot, "dist", "third-party", "NOTICE.txt"), "Nested notice\n", "utf8", ); + await NodeFSP.writeFile( + NodePath.join(fixture.dependencyRoot, "lib", "license_header.js"), + "require('not-a-license');\n", + "utf8", + ); const manifest = await generateThirdPartyLicenseManifest({ configFile: fixture.configFile, diff --git a/scripts/lib/third-party-licenses.ts b/scripts/lib/third-party-licenses.ts index ac8e8e9ed8f3..487853fab904 100644 --- a/scripts/lib/third-party-licenses.ts +++ b/scripts/lib/third-party-licenses.ts @@ -102,8 +102,33 @@ const EMPTY_CONFIG: ThirdPartyLicensesConfig = { }; const NOTICE_FILE_PATTERN = /^(?:licen[cs]e|copying|notice)(?:[._-].*)?$/i; +const NOTICE_TEXT_EXTENSIONS = new Set([ + "", + ".0bsd", + ".agpl", + ".apache2", + ".bsd", + ".gpl", + ".isc", + ".lgpl", + ".markdown", + ".md", + ".mit", + ".mpl", + ".mpl2", + ".rst", + ".txt", + ".unlicense", +]); const FIRST_PARTY_PACKAGE_PREFIX = "@t3tools/"; +function isNoticeTextFile(fileName: string): boolean { + return ( + NOTICE_FILE_PATTERN.test(fileName) && + NOTICE_TEXT_EXTENSIONS.has(NodePath.extname(fileName).toLowerCase()) + ); +} + function asPath(value: string | URL): string { return value instanceof URL ? NodeURL.fileURLToPath(value) : NodePath.resolve(value); } @@ -626,7 +651,7 @@ async function readPackageNoticeText(packageRoot: string): Promise entry.isFile() && NOTICE_FILE_PATTERN.test(entry.name)) + .filter((entry) => entry.isFile() && isNoticeTextFile(entry.name)) .map((entry) => entry.name), ); @@ -637,7 +662,7 @@ async function readPackageNoticeText(packageRoot: string): Promise { const relativePath = NodePath.join(directory, entry.name); - if (entry.isFile() && NOTICE_FILE_PATTERN.test(entry.name)) { + if (entry.isFile() && isNoticeTextFile(entry.name)) { noticeFiles.push(relativePath); return; } From c9f7bb1bd6a7a929de1042b62ca0452eeb0625d5 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 11 Sep 2026 17:32:50 -0700 Subject: [PATCH 14/14] fix(mobile): correct GhosttyKit provenance --- scripts/lib/third-party-licenses.test.ts | 20 ++++++++++++++++++++ third-party-licenses.config.json | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/scripts/lib/third-party-licenses.test.ts b/scripts/lib/third-party-licenses.test.ts index f8b7fcfbb74e..997b2bd58d9e 100644 --- a/scripts/lib/third-party-licenses.test.ts +++ b/scripts/lib/third-party-licenses.test.ts @@ -3,6 +3,7 @@ import * as NodeFSP from "node:fs/promises"; import * as NodeOS from "node:os"; import * as NodePath from "node:path"; +import * as NodeURL from "node:url"; import { afterEach, describe, expect, it } from "vite-plus/test"; @@ -13,6 +14,10 @@ import { } from "./third-party-licenses.js"; const tempDirectories: string[] = []; +const REPOSITORY_ROOT = NodePath.resolve( + NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)), + "../..", +); async function writeJson(path: string, value: unknown): Promise { await NodeFSP.mkdir(NodePath.dirname(path), { recursive: true }); @@ -72,6 +77,21 @@ afterEach(async () => { }); describe("third-party license generation", () => { + it("keeps the GhosttyKit notice pinned to the vendored framework revision", async () => { + const [config, revision] = await Promise.all([ + NodeFSP.readFile(NodePath.join(REPOSITORY_ROOT, "third-party-licenses.config.json"), "utf8"), + NodeFSP.readFile( + NodePath.join(REPOSITORY_ROOT, "apps/mobile/modules/t3-terminal/Vendor/libghostty/VERSION"), + "utf8", + ), + ]); + + expect(config).toContain(revision.trim()); + expect(config).toContain( + "https://github.com/Yash-Singh1/ghostty/tree/t3code/custom-io-ordered-feed", + ); + }); + it("collects production packages and custom asset notices", async () => { const fixture = await createFixture(); const manifest = await generateThirdPartyLicenseManifest({ diff --git a/third-party-licenses.config.json b/third-party-licenses.config.json index 836655e15c13..8670eaf64ad5 100644 --- a/third-party-licenses.config.json +++ b/third-party-licenses.config.json @@ -84,13 +84,13 @@ "bundles": ["ios", "mobile"], "license": "MIT", "name": "GhosttyKit", - "sourceUrl": "https://github.com/Yash-Singh1/ghostty/tree/custom-io", + "sourceUrl": "https://github.com/Yash-Singh1/ghostty/tree/t3code/custom-io-ordered-feed", "generatedNotices": [ { "licenseId": "MIT", "copyrights": ["Copyright (c) 2024 Mitchell Hashimoto, Ghostty contributors"], "preamble": [ - "GhosttyKit\n\nThe iOS terminal renderer vendors GhosttyKit.xcframework, a libghostty build produced from T3's iOS 16 support fork. That fork was created from VVTerm's custom-I/O Ghostty fork.\n\nUpstream project: https://github.com/ghostty-org/ghostty\nCustom-I/O base fork: https://github.com/wiedymi/ghostty/tree/custom-io\nVendored source fork: https://github.com/Yash-Singh1/ghostty/tree/custom-io\nVendored revision: d36c3b8dffd0d756dd5e5f4933962f774a0e6753\nReference integration: https://github.com/vivy-company/vvterm" + "GhosttyKit\n\nThe iOS terminal renderer vendors GhosttyKit.xcframework, a libghostty build produced from VVTerm's custom-I/O and live-padding Ghostty branch.\n\nUpstream project: https://github.com/ghostty-org/ghostty\nBased on: https://github.com/wiedymi/ghostty/tree/vvterm/custom-io-padding\nVendored source branch: https://github.com/Yash-Singh1/ghostty/tree/t3code/custom-io-ordered-feed\nVendored revision: cf8edc23f3a6a87a96e41a90013e89e987d34980\nReference integration: https://github.com/vivy-company/vvterm" ] } ]