| author | |
| committer | |
| log | 20254307084f1101ced00dfadeddc7804a7de45d |
| tree | 6822ff33bebf8a798f8986449a527ec569a4a38d |
| parent | 5584d21688fc0e4cf6cd8057167df2514b759d3b |
| signature | Signed by PGP key B5690EEEBB952194 |
7 files changed, 95 insertions(+), 15 deletions(-)
apps/server/src/device/DeviceToolchain.ts+1-1| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | import type { DeviceToolVersions } from "@t3tools/contracts"; |
| 2 | import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions"; | |
| 3 | 2 | /** |
| 4 | 3 | * Pinned installs of the two external tools device support is built on. |
| 5 | 4 | * |
| ... | ... | @@ -25,6 +24,7 @@ import * as Schema from "effect/Schema"; |
| 25 | 24 | import * as Semaphore from "effect/Semaphore"; |
| 26 | 25 | |
| 27 | 26 | import * as ProcessRunner from "../processRunner.ts"; |
| 27 | import DEVICE_TOOL_VERSIONS from "./deviceToolVersions.json" with { type: "json" }; | |
| 28 | 28 | |
| 29 | 29 | const DEVICE_HUB_PACKAGE = "expo-device-hub"; |
| 30 | 30 | export const DEVICE_HUB_VERSION = DEVICE_TOOL_VERSIONS[DEVICE_HUB_PACKAGE]; |
apps/server/src/device/deviceToolVersions.json created+4| ... | ... | @@ -0,0 +1,4 @@ |
| 1 | { | |
| 2 | "agent-device": "0.21.24", | |
| 3 | "expo-device-hub": "0.15.3" | |
| 4 | } |
docs/internals/open-source-licenses.md+1-1| ... | ... | @@ -61,7 +61,7 @@ supplies the label shown to users. Use `includeInBundles` when those differ, suc |
| 61 | 61 | server tool that should appear in both client manifests but is not bundled into either client. |
| 62 | 62 | |
| 63 | 63 | The `agent-device` and `expo-device-hub` custom notices take their versions from the |
| 64 | [shared installer pins](../../packages/shared/src/deviceToolVersions.ts). Keep their package names | |
| 64 | [installer pins](../../apps/server/src/device/deviceToolVersions.json). Keep their package names | |
| 65 | 65 | in the config; their versions do not need manual updates. |
| 66 | 66 | |
| 67 | 67 | Use `packageOverrides` only when an installed npm archive omits its notice or has incorrect |
packages/shared/package.json-4| ... | ... | @@ -359,10 +359,6 @@ |
| 359 | 359 | "types": "./src/thirdPartyLicenses.ts", |
| 360 | 360 | "import": "./src/thirdPartyLicenses.ts" |
| 361 | 361 | }, |
| 362 | "./deviceToolVersions": { | |
| 363 | "types": "./src/deviceToolVersions.ts", | |
| 364 | "import": "./src/deviceToolVersions.ts" | |
| 365 | }, | |
| 366 | 362 | "./claudeCompaction": { |
| 367 | 363 | "types": "./src/claudeCompaction.ts", |
| 368 | 364 | "import": "./src/claudeCompaction.ts" |
packages/shared/src/deviceToolVersions.ts deleted-5| ... | ... | @@ -1,5 +0,0 @@ |
| 1 | // The installer and third-party notices share these pins for tools installed outside the workspace. | |
| 2 | export const DEVICE_TOOL_VERSIONS = { | |
| 3 | "agent-device": "0.21.24", | |
| 4 | "expo-device-hub": "0.15.3", | |
| 5 | } as const; |
scripts/lib/third-party-licenses.test.ts+78-1| ... | ... | @@ -1,11 +1,11 @@ |
| 1 | 1 | // @effect-diagnostics nodeBuiltinImport:off - Tests exercise the Node filesystem build boundary. |
| 2 | 2 | |
| 3 | 3 | import * as NodeFSP from "node:fs/promises"; |
| 4 | import * as NodeChildProcess from "node:child_process"; | |
| 4 | 5 | import * as NodeOS from "node:os"; |
| 5 | 6 | import * as NodePath from "node:path"; |
| 6 | 7 | import * as NodeURL from "node:url"; |
| 7 | 8 | |
| 8 | import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions"; | |
| 9 | 9 | import { afterEach, describe, expect, it } from "vite-plus/test"; |
| 10 | 10 | |
| 11 | 11 | import { |
| ... | ... | @@ -19,6 +19,12 @@ const REPOSITORY_ROOT = NodePath.resolve( |
| 19 | 19 | NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)), |
| 20 | 20 | "../..", |
| 21 | 21 | ); |
| 22 | const DEVICE_TOOL_VERSIONS = JSON.parse( | |
| 23 | await NodeFSP.readFile( | |
| 24 | NodePath.join(REPOSITORY_ROOT, "apps/server/src/device/deviceToolVersions.json"), | |
| 25 | "utf8", | |
| 26 | ), | |
| 27 | ) as { readonly "agent-device": string; readonly "expo-device-hub": string }; | |
| 22 | 28 | |
| 23 | 29 | async function writeJson(path: string, value: unknown): Promise<void> { |
| 24 | 30 | await NodeFSP.mkdir(NodePath.dirname(path), { recursive: true }); |
| ... | ... | @@ -78,6 +84,77 @@ afterEach(async () => { |
| 78 | 84 | }); |
| 79 | 85 | |
| 80 | 86 | describe("third-party license generation", () => { |
| 87 | it("generates mobile notices without scripts workspace dependency links", async () => { | |
| 88 | const fixture = await createFixture(); | |
| 89 | const generatorPath = NodePath.join(fixture.root, "scripts/lib/third-party-licenses.ts"); | |
| 90 | const versionsPath = NodePath.join( | |
| 91 | fixture.root, | |
| 92 | "apps/server/src/device/deviceToolVersions.json", | |
| 93 | ); | |
| 94 | await Promise.all([ | |
| 95 | NodeFSP.mkdir(NodePath.dirname(generatorPath), { recursive: true }), | |
| 96 | NodeFSP.mkdir(NodePath.dirname(versionsPath), { recursive: true }), | |
| 97 | ]); | |
| 98 | await Promise.all([ | |
| 99 | writeJson(NodePath.join(fixture.root, "scripts/package.json"), { type: "module" }), | |
| 100 | NodeFSP.copyFile( | |
| 101 | NodePath.join(REPOSITORY_ROOT, "scripts/lib/third-party-licenses.ts"), | |
| 102 | generatorPath, | |
| 103 | ), | |
| 104 | NodeFSP.copyFile( | |
| 105 | NodePath.join(REPOSITORY_ROOT, "apps/server/src/device/deviceToolVersions.json"), | |
| 106 | versionsPath, | |
| 107 | ), | |
| 108 | writeJson(fixture.configFile, { | |
| 109 | customNotices: Object.keys(DEVICE_TOOL_VERSIONS).map((name) => ({ | |
| 110 | name, | |
| 111 | license: "MIT", | |
| 112 | noticeFile: "asset-notice.txt", | |
| 113 | bundles: ["device-tools"], | |
| 114 | includeInBundles: ["mobile", "web"], | |
| 115 | })), | |
| 116 | }), | |
| 117 | ]); | |
| 118 | ||
| 119 | const output = NodeChildProcess.execFileSync( | |
| 120 | process.execPath, | |
| 121 | [ | |
| 122 | "--input-type=module", | |
| 123 | "--eval", | |
| 124 | ` | |
| 125 | import { pathToFileURL } from "node:url"; | |
| 126 | const { generateThirdPartyLicenseManifest } = await import( | |
| 127 | pathToFileURL(process.argv[1]).href + "?version=1" | |
| 128 | ); | |
| 129 | const manifest = await generateThirdPartyLicenseManifest({ | |
| 130 | configFile: process.argv[2], | |
| 131 | packageManifests: [{ bundle: "mobile", path: process.argv[3] }], | |
| 132 | }); | |
| 133 | process.stdout.write(JSON.stringify(manifest)); | |
| 134 | `, | |
| 135 | generatorPath, | |
| 136 | fixture.configFile, | |
| 137 | fixture.appManifest, | |
| 138 | ], | |
| 139 | { cwd: fixture.root, encoding: "utf8" }, | |
| 140 | ); | |
| 141 | const manifest = JSON.parse(output) as { | |
| 142 | entries: ReadonlyArray<{ name: string; version: string }>; | |
| 143 | }; | |
| 144 | expect(manifest.entries).toEqual( | |
| 145 | expect.arrayContaining([ | |
| 146 | expect.objectContaining({ | |
| 147 | name: "agent-device", | |
| 148 | version: DEVICE_TOOL_VERSIONS["agent-device"], | |
| 149 | }), | |
| 150 | expect.objectContaining({ | |
| 151 | name: "expo-device-hub", | |
| 152 | version: DEVICE_TOOL_VERSIONS["expo-device-hub"], | |
| 153 | }), | |
| 154 | ]), | |
| 155 | ); | |
| 156 | }); | |
| 157 | ||
| 81 | 158 | it.each(["web", "mobile"])( |
| 82 | 159 | "uses installer pins for device tool notices in the %s manifest", |
| 83 | 160 | async (bundle) => { |
scripts/lib/third-party-licenses.ts+11-3| ... | ... | @@ -5,14 +5,12 @@ import * as NodePath from "node:path"; |
| 5 | 5 | import * as NodeURL from "node:url"; |
| 6 | 6 | import * as NodeModule from "node:module"; |
| 7 | 7 | |
| 8 | import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions"; | |
| 9 | 8 | import type { Plugin } from "vite-plus"; |
| 10 | 9 | |
| 11 | 10 | export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json"; |
| 12 | 11 | const SPDX_LICENSE_LIST_VERSION = "v3.28.0"; |
| 13 | 12 | const SPDX_LICENSE_LIST_REVISION = "c4a7237ec8f4654e867546f9f409749300f1bf4c"; |
| 14 | 13 | const GENERATED_NOTICE_CACHE_DIRECTORY = ".generated/third-party-licenses/spdx"; |
| 15 | const DEVICE_TOOL_NOTICE_VERSIONS = new Map(Object.entries(DEVICE_TOOL_VERSIONS)); | |
| 16 | 14 | |
| 17 | 15 | export interface ThirdPartyLicenseEntry { |
| 18 | 16 | readonly bundles: ReadonlyArray<string>; |
| ... | ... | @@ -825,6 +823,13 @@ async function customEntries( |
| 825 | 823 | includedBundles: ReadonlySet<string>, |
| 826 | 824 | allowMissingGeneratedNotices: boolean, |
| 827 | 825 | ): Promise<ReadonlyArray<ThirdPartyLicenseEntry>> { |
| 826 | const deviceToolVersions: unknown = JSON.parse( | |
| 827 | await NodeFSP.readFile( | |
| 828 | new URL("../../apps/server/src/device/deviceToolVersions.json", import.meta.url), | |
| 829 | "utf8", | |
| 830 | ), | |
| 831 | ); | |
| 832 | if (!isRecord(deviceToolVersions)) throw new Error("Device tool versions must be an object."); | |
| 828 | 833 | const entries = await Promise.all( |
| 829 | 834 | config.customNotices |
| 830 | 835 | .filter( |
| ... | ... | @@ -870,7 +875,10 @@ async function customEntries( |
| 870 | 875 | name: notice.name, |
| 871 | 876 | noticeText, |
| 872 | 877 | sourceUrl: notice.sourceUrl ?? null, |
| 873 | version: DEVICE_TOOL_NOTICE_VERSIONS.get(notice.name) ?? notice.version ?? null, | |
| 878 | version: | |
| 879 | readOptionalString(deviceToolVersions, notice.name, "Device tool versions") ?? | |
| 880 | notice.version ?? | |
| 881 | null, | |
| 874 | 882 | }; |
| 875 | 883 | }), |
| 876 | 884 | ); |