| 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,5 +1,4 @@ |
| 1 | import type { DeviceToolVersions } from "@t3tools/contracts"; | 1 | import type { DeviceToolVersions } from "@t3tools/contracts"; |
| 2 | import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions"; | ||
| 3 | /** | 2 | /** |
| 4 | * Pinned installs of the two external tools device support is built on. | 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,6 +24,7 @@ import * as Schema from "effect/Schema"; |
| 25 | import * as Semaphore from "effect/Semaphore"; | 24 | import * as Semaphore from "effect/Semaphore"; |
| 26 | 25 | ||
| 27 | import * as ProcessRunner from "../processRunner.ts"; | 26 | import * as ProcessRunner from "../processRunner.ts"; |
| 27 | import DEVICE_TOOL_VERSIONS from "./deviceToolVersions.json" with { type: "json" }; | ||
| 28 | 28 | ||
| 29 | const DEVICE_HUB_PACKAGE = "expo-device-hub"; | 29 | const DEVICE_HUB_PACKAGE = "expo-device-hub"; |
| 30 | export const DEVICE_HUB_VERSION = DEVICE_TOOL_VERSIONS[DEVICE_HUB_PACKAGE]; | 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,7 +61,7 @@ supplies the label shown to users. Use `includeInBundles` when those differ, suc |
| 61 | server tool that should appear in both client manifests but is not bundled into either client. | 61 | server tool that should appear in both client manifests but is not bundled into either client. |
| 62 | 62 | ||
| 63 | The `agent-device` and `expo-device-hub` custom notices take their versions from the | 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 | in the config; their versions do not need manual updates. | 65 | in the config; their versions do not need manual updates. |
| 66 | 66 | ||
| 67 | Use `packageOverrides` only when an installed npm archive omits its notice or has incorrect | 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,10 +359,6 @@ |
| 359 | "types": "./src/thirdPartyLicenses.ts", | 359 | "types": "./src/thirdPartyLicenses.ts", |
| 360 | "import": "./src/thirdPartyLicenses.ts" | 360 | "import": "./src/thirdPartyLicenses.ts" |
| 361 | }, | 361 | }, |
| 362 | "./deviceToolVersions": { | ||
| 363 | "types": "./src/deviceToolVersions.ts", | ||
| 364 | "import": "./src/deviceToolVersions.ts" | ||
| 365 | }, | ||
| 366 | "./claudeCompaction": { | 362 | "./claudeCompaction": { |
| 367 | "types": "./src/claudeCompaction.ts", | 363 | "types": "./src/claudeCompaction.ts", |
| 368 | "import": "./src/claudeCompaction.ts" | 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,11 +1,11 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off - Tests exercise the Node filesystem build boundary. | 1 | // @effect-diagnostics nodeBuiltinImport:off - Tests exercise the Node filesystem build boundary. |
| 2 | 2 | ||
| 3 | import * as NodeFSP from "node:fs/promises"; | 3 | import * as NodeFSP from "node:fs/promises"; |
| 4 | import * as NodeChildProcess from "node:child_process"; | ||
| 4 | import * as NodeOS from "node:os"; | 5 | import * as NodeOS from "node:os"; |
| 5 | import * as NodePath from "node:path"; | 6 | import * as NodePath from "node:path"; |
| 6 | import * as NodeURL from "node:url"; | 7 | import * as NodeURL from "node:url"; |
| 7 | 8 | ||
| 8 | import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions"; | ||
| 9 | import { afterEach, describe, expect, it } from "vite-plus/test"; | 9 | import { afterEach, describe, expect, it } from "vite-plus/test"; |
| 10 | 10 | ||
| 11 | import { | 11 | import { |
| ... | @@ -19,6 +19,12 @@ const REPOSITORY_ROOT = NodePath.resolve( | ... | @@ -19,6 +19,12 @@ const REPOSITORY_ROOT = NodePath.resolve( |
| 19 | NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)), | 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 | async function writeJson(path: string, value: unknown): Promise<void> { | 29 | async function writeJson(path: string, value: unknown): Promise<void> { |
| 24 | await NodeFSP.mkdir(NodePath.dirname(path), { recursive: true }); | 30 | await NodeFSP.mkdir(NodePath.dirname(path), { recursive: true }); |
| ... | @@ -78,6 +84,77 @@ afterEach(async () => { | ... | @@ -78,6 +84,77 @@ afterEach(async () => { |
| 78 | }); | 84 | }); |
| 79 | 85 | ||
| 80 | describe("third-party license generation", () => { | 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 | it.each(["web", "mobile"])( | 158 | it.each(["web", "mobile"])( |
| 82 | "uses installer pins for device tool notices in the %s manifest", | 159 | "uses installer pins for device tool notices in the %s manifest", |
| 83 | async (bundle) => { | 160 | async (bundle) => { |
scripts/lib/third-party-licenses.ts+11-3| ... | @@ -5,14 +5,12 @@ import * as NodePath from "node:path"; | ... | @@ -5,14 +5,12 @@ import * as NodePath from "node:path"; |
| 5 | import * as NodeURL from "node:url"; | 5 | import * as NodeURL from "node:url"; |
| 6 | import * as NodeModule from "node:module"; | 6 | import * as NodeModule from "node:module"; |
| 7 | 7 | ||
| 8 | import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions"; | ||
| 9 | import type { Plugin } from "vite-plus"; | 8 | import type { Plugin } from "vite-plus"; |
| 10 | 9 | ||
| 11 | export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json"; | 10 | export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json"; |
| 12 | const SPDX_LICENSE_LIST_VERSION = "v3.28.0"; | 11 | const SPDX_LICENSE_LIST_VERSION = "v3.28.0"; |
| 13 | const SPDX_LICENSE_LIST_REVISION = "c4a7237ec8f4654e867546f9f409749300f1bf4c"; | 12 | const SPDX_LICENSE_LIST_REVISION = "c4a7237ec8f4654e867546f9f409749300f1bf4c"; |
| 14 | const GENERATED_NOTICE_CACHE_DIRECTORY = ".generated/third-party-licenses/spdx"; | 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 | export interface ThirdPartyLicenseEntry { | 15 | export interface ThirdPartyLicenseEntry { |
| 18 | readonly bundles: ReadonlyArray<string>; | 16 | readonly bundles: ReadonlyArray<string>; |
| ... | @@ -825,6 +823,13 @@ async function customEntries( | ... | @@ -825,6 +823,13 @@ async function customEntries( |
| 825 | includedBundles: ReadonlySet<string>, | 823 | includedBundles: ReadonlySet<string>, |
| 826 | allowMissingGeneratedNotices: boolean, | 824 | allowMissingGeneratedNotices: boolean, |
| 827 | ): Promise<ReadonlyArray<ThirdPartyLicenseEntry>> { | 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 | const entries = await Promise.all( | 833 | const entries = await Promise.all( |
| 829 | config.customNotices | 834 | config.customNotices |
| 830 | .filter( | 835 | .filter( |
| ... | @@ -870,7 +875,10 @@ async function customEntries( | ... | @@ -870,7 +875,10 @@ async function customEntries( |
| 870 | name: notice.name, | 875 | name: notice.name, |
| 871 | noticeText, | 876 | noticeText, |
| 872 | sourceUrl: notice.sourceUrl ?? null, | 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 | ); |