authorgravatar for saiansh2525@gmail.comYash Singh <saiansh2525@gmail.com> 2026-10-11 02:31:06-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 02:31:06-05:00
log20254307084f1101ced00dfadeddc7804a7de45d
tree6822ff33bebf8a798f8986449a527ec569a4a38d
parent5584d21688fc0e4cf6cd8057167df2514b759d3b
signature Signed by PGP key B5690EEEBB952194

fix(mobile): license generation works with filtered installs (#18127)


7 files changed, 95 insertions(+), 15 deletions(-)

apps/server/src/device/DeviceToolchain.ts+1-1
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1import type { DeviceToolVersions } from "@t3tools/contracts";1import type { DeviceToolVersions } from "@t3tools/contracts";
2import { 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";
25import * as Semaphore from "effect/Semaphore";24import * as Semaphore from "effect/Semaphore";
2625
27import * as ProcessRunner from "../processRunner.ts";26import * as ProcessRunner from "../processRunner.ts";
27import DEVICE_TOOL_VERSIONS from "./deviceToolVersions.json" with { type: "json" };
2828
29const DEVICE_HUB_PACKAGE = "expo-device-hub";29const DEVICE_HUB_PACKAGE = "expo-device-hub";
30export const DEVICE_HUB_VERSION = DEVICE_TOOL_VERSIONS[DEVICE_HUB_PACKAGE];30export 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
61server tool that should appear in both client manifests but is not bundled into either client.61server tool that should appear in both client manifests but is not bundled into either client.
6262
63The `agent-device` and `expo-device-hub` custom notices take their versions from the63The `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 names64[installer pins](../../apps/server/src/device/deviceToolVersions.json). Keep their package names
65in the config; their versions do not need manual updates.65in the config; their versions do not need manual updates.
6666
67Use `packageOverrides` only when an installed npm archive omits its notice or has incorrect67Use `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.
2export 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.
22
3import * as NodeFSP from "node:fs/promises";3import * as NodeFSP from "node:fs/promises";
4import * as NodeChildProcess from "node:child_process";
4import * as NodeOS from "node:os";5import * as NodeOS from "node:os";
5import * as NodePath from "node:path";6import * as NodePath from "node:path";
6import * as NodeURL from "node:url";7import * as NodeURL from "node:url";
78
8import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions";
9import { afterEach, describe, expect, it } from "vite-plus/test";9import { afterEach, describe, expect, it } from "vite-plus/test";
1010
11import {11import {
...@@ -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);
22const 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 };
2228
23async function writeJson(path: string, value: unknown): Promise<void> {29async 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});
7985
80describe("third-party license generation", () => {86describe("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";
5import * as NodeURL from "node:url";5import * as NodeURL from "node:url";
6import * as NodeModule from "node:module";6import * as NodeModule from "node:module";
77
8import { DEVICE_TOOL_VERSIONS } from "@t3tools/shared/deviceToolVersions";
9import type { Plugin } from "vite-plus";8import type { Plugin } from "vite-plus";
109
11export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json";10export const THIRD_PARTY_LICENSES_FILE_NAME = "third-party-licenses.json";
12const SPDX_LICENSE_LIST_VERSION = "v3.28.0";11const SPDX_LICENSE_LIST_VERSION = "v3.28.0";
13const SPDX_LICENSE_LIST_REVISION = "c4a7237ec8f4654e867546f9f409749300f1bf4c";12const SPDX_LICENSE_LIST_REVISION = "c4a7237ec8f4654e867546f9f409749300f1bf4c";
14const GENERATED_NOTICE_CACHE_DIRECTORY = ".generated/third-party-licenses/spdx";13const GENERATED_NOTICE_CACHE_DIRECTORY = ".generated/third-party-licenses/spdx";
15const DEVICE_TOOL_NOTICE_VERSIONS = new Map(Object.entries(DEVICE_TOOL_VERSIONS));
1614
17export interface ThirdPartyLicenseEntry {15export 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.customNotices834 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 );