Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions patches/@ubjs__node@0.31.0-3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
diff --git a/typescript/dist/resolve-lib.js b/typescript/dist/resolve-lib.js
index e8837bb2337c42eaffe15942923b8af39fca2b1a..b0806b7ebb9b4995ee6a318b66875a7ee9471219 100644
--- a/typescript/dist/resolve-lib.js
+++ b/typescript/dist/resolve-lib.js
@@ -166,5 +166,16 @@ function resolveNpmPackage(crateName, callerUrl, npmPackageBase, tripleStyle) {
attempted: [binaryPath],
});
}
- return binaryPath;
+ return resolveUnpackedAsarPath(binaryPath);
+}
+// dlopen cannot read inside an Electron asar archive. Packagers move native
+// libraries to the archive's `.asar.unpacked` sibling, so load them from there.
+function resolveUnpackedAsarPath(binaryPath) {
+ const segments = binaryPath.split(node_path_1.sep);
+ const asarIndex = segments.findLastIndex((segment) => segment.endsWith(".asar"));
+ if (asarIndex === -1)
+ return binaryPath;
+ segments[asarIndex] = `${segments[asarIndex]}.unpacked`;
+ const unpackedPath = segments.join(node_path_1.sep);
+ return (0, node_fs_1.existsSync)(unpackedPath) ? unpackedPath : binaryPath;
}
5 changes: 3 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ packageExtensions:
vite-plus: "catalog:"

patchedDependencies:
"@ubjs/node@0.31.0-3": patches/@ubjs__node@0.31.0-3.patch
alchemy@2.0.0-beta.80: patches/alchemy@2.0.0-beta.80.patch
expo-audio@58.0.9: patches/expo-audio@58.0.9.patch
"@clerk/expo-native-components@0.2.0": patches/@clerk__expo-native-components@0.2.0.patch
Expand Down
367 changes: 352 additions & 15 deletions scripts/build-desktop-artifact.test.ts

Large diffs are not rendered by default.

199 changes: 179 additions & 20 deletions scripts/build-desktop-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { fromYaml } from "@t3tools/shared/schemaYaml";
import * as HostProcess from "@t3tools/shared/HostProcess";
import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth";
import { resolveSpawnCommand } from "@t3tools/shared/shell";
import { cuaDriverRelease } from "@t3tools/shared/cuaDriverRelease";
import rootPackageJson from "../package.json" with { type: "json" };
import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
import gnomeCaptureBundle from "../apps/desktop/gnome-extension/bundle.json" with { type: "json" };
Expand Down Expand Up @@ -416,7 +417,10 @@ const WINDOWS_DESKTOP_BUILD_PREREQUISITES = [
id: "msvc",
description: "Visual Studio Build Tools with C++, Windows SDK, and Spectre libraries",
},
{ id: "tar", description: "tar to inspect the bundled WSL runtime archive" },
{
id: "tar",
description: "tar.exe to extract Cua Driver and inspect the bundled WSL runtime archive",
},
] as const;

export class WindowsDesktopBuildPrerequisitesMissingError extends Schema.TaggedError<WindowsDesktopBuildPrerequisitesMissingError>()(
Expand Down Expand Up @@ -988,6 +992,10 @@ export const DESKTOP_FILE_EXCLUSIONS = [
"!apps/desktop/prod-resources/wsl-runtime.tar.gz.sha256",
"!apps/desktop/gnome-extension",
"!apps/desktop/gnome-extension/**/*",
"!apps/desktop/prod-resources/cua-driver",
"!apps/desktop/prod-resources/cua-driver/**/*",
"!apps/desktop/resources/cua-driver",
"!apps/desktop/resources/cua-driver/**/*",
] as const;
// Windows terminal helpers cannot run on macOS and slow signing and notarization.
export const MAC_FILE_EXCLUSIONS = [
Expand Down Expand Up @@ -1086,6 +1094,7 @@ export const WSL_RUNTIME_EXTRA_RESOURCES = [
WSL_RUNTIME_ARCHIVE_HASH_EXTRA_RESOURCE,
] as const;
export const DESKTOP_EXTRA_RESOURCES = [
{ from: "apps/desktop/prod-resources/cua-driver", to: "cua-driver" },
{
from: "apps/desktop/prod-resources/cursor-sdk",
to: "node_modules/@cursor",
Expand Down Expand Up @@ -1617,9 +1626,14 @@ export function createStagePatchedDependencies(
dependencies: Record<string, unknown>,
): Record<string, string> {
return Object.fromEntries(
Object.entries(patchedDependencies).filter(([patchKey]) =>
Object.hasOwn(dependencies, getPatchedDependencyPackageName(patchKey)),
),
Object.entries(patchedDependencies).filter(([patchKey]) => {
const name = getPatchedDependencyPackageName(patchKey);
// The Cua SDK's transitive loader needs the ASAR path fix too.
return (
Object.hasOwn(dependencies, name) ||
(name === "@ubjs/node" && Object.hasOwn(dependencies, "@trycua/cua-driver"))
);
}),
);
}

Expand Down Expand Up @@ -1915,7 +1929,7 @@ function windowsVswherePrerequisiteScript(arch: typeof BuildArch.Type): string {
}

export const preflightWindowsDesktopBuild = Effect.fn("preflightWindowsDesktopBuild")(
function* (input: { readonly arch: typeof BuildArch.Type; readonly bundlesWslRuntime: boolean }) {
function* (input: { readonly arch: typeof BuildArch.Type }) {
const rustTarget = resolveResourceMonitorRustTargets("win", input.arch)[0]!;
const reuseResourceMonitor = yield* Config.Boolean(
"T3CODE_DESKTOP_REUSE_RESOURCE_MONITOR",
Expand All @@ -1942,9 +1956,7 @@ export const preflightWindowsDesktopBuild = Effect.fn("preflightWindowsDesktopBu
]),
"Visual Studio Build Tools",
),
tar: input.bundlesWslRuntime
? desktopBuildProbeSucceeds(ChildProcess.make("tar.exe", ["--version"]), "tar")
: Effect.succeed(true),
tar: desktopBuildProbeSucceeds(ChildProcess.make("tar.exe", ["--version"]), "tar"),
},
{ concurrency: "unbounded" },
);
Expand Down Expand Up @@ -2373,6 +2385,134 @@ export const stageBrowserSecret = Effect.fn("stageBrowserSecret")(function* (inp
);
});

class CuaDriverArtifactError extends Schema.TaggedError<CuaDriverArtifactError>()(
"CuaDriverArtifactError",
{ message: Schema.String },
) {}

export const stageCuaDriverExecutable = Effect.fn("stageCuaDriverExecutable")(function* (input: {
readonly repoRoot: string;
readonly stageRoot: string;
readonly stageResourcesDir: string;
readonly platform: typeof BuildPlatform.Type;
readonly arch: typeof BuildArch.Type;
readonly verbose: boolean;
}) {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const release = cuaDriverRelease(
input.platform === "mac" ? "darwin" : input.platform === "win" ? "win32" : "linux",
input.arch === "universal" ? "arm64" : input.arch,
);
if (!release) {
return yield* new CuaDriverArtifactError({ message: "Unsupported Cua Driver build target." });
}
const cacheDir = path.join(input.repoRoot, "node_modules/.cache/t3code/cua-driver");
const archivePath = path.join(cacheDir, release.archiveName);
const verified = Effect.fn("verifyCuaDriverArchive")(function* (file: string) {
if (!(yield* fs.exists(file))) return false;
if (Number((yield* fs.stat(file)).size) !== release.bytes) return false;
const hash = NodeCrypto.createHash("sha256");
yield* fs
.stream(file)
.pipe(Stream.runForEach((chunk) => Effect.sync(() => hash.update(chunk))));
return hash.digest("hex") === release.sha256;
});
yield* fs.makeDirectory(cacheDir, { recursive: true });
if (!(yield* verified(archivePath))) {
yield* fs.remove(archivePath, { force: true });
yield* Effect.scoped(
Effect.gen(function* () {
const temporary = yield* fs.makeTempDirectoryScoped({ directory: cacheDir });
const downloadPath = path.join(temporary, release.archiveName);
yield* runCommand(
ChildProcess.make("curl", [
"--fail",
"--location",
"--silent",
"--show-error",
"--connect-timeout",
"30",
"--max-time",
"600",
"--retry",
"3",
"--output",
downloadPath,
release.url,
]),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
{ label: "download Cua Driver", verbose: input.verbose },
);
if (!(yield* verified(downloadPath))) {
return yield* new CuaDriverArtifactError({
message: `Cua Driver archive failed size/SHA-256 verification: ${release.archiveName}`,
});
}
yield* fs.rename(downloadPath, archivePath);
}),
);
}
yield* fs.makeDirectory(input.stageRoot, { recursive: true });
yield* Effect.scoped(
Effect.gen(function* () {
const extracted = yield* fs.makeTempDirectoryScoped({ directory: input.stageRoot });
const hostPlatform = yield* HostProcess.Platform;
const command =
release.format === "zip" && hostPlatform !== "win32"
? ChildProcess.make("unzip", ["-q", archivePath, "-d", extracted])
: ChildProcess.make(hostPlatform === "win32" ? "tar.exe" : "tar", [
"-xf",
archivePath,
"-C",
extracted,
]);
yield* runCommand(command, { label: "extract Cua Driver", verbose: input.verbose });
const requiredFiles = [
release.executable,
...(input.platform === "win"
? ["cua-driver-uia.exe", "cua-cursor-theme.exe"]
: input.platform === "linux"
? [
"cua-cursor-theme",
"wayland-helper/install.sh",
"wayland-helper/winrects@cua/metadata.json",
"wayland-helper/winrects@cua/extension.js",
]
: []),
];
for (const file of requiredFiles) {
if ((yield* fs.stat(path.join(extracted, file))).type !== "File") {
return yield* new CuaDriverArtifactError({
message: `Cua Driver release is missing ${file}.`,
});
}
}
yield* fs.makeDirectory(input.stageResourcesDir, { recursive: true });
const destination = path.join(input.stageResourcesDir, "cua-driver");
yield* fs.remove(destination, { recursive: true, force: true });
if (input.platform === "mac") {
yield* fs.copyFile(path.join(extracted, release.executable), destination);
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Resolved
yield* fs.chmod(destination, 0o755);
} else {
yield* fs.makeDirectory(destination, { recursive: true });
// SDK libraries come from the app's npm dependencies, not this release.
const entries =
input.platform === "linux"
? [release.executable, "cua-cursor-theme", "wayland-helper"]
: requiredFiles;
for (const entry of entries) {
yield* fs.copy(path.join(extracted, entry), path.join(destination, entry));
}
if (input.platform === "linux") {
for (const executable of [release.executable, "cua-cursor-theme"]) {
yield* fs.chmod(path.join(destination, executable), 0o755);
}
}
}
}),
);
});

function generateMacIconSet(
sourcePng: string,
targetIcns: string,
Expand Down Expand Up @@ -2712,7 +2852,12 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
// not inflate the loose-file count and slow NSIS installation.
...(platform === "win"
? { asar: { smartUnpack: false }, asarUnpack: [WINDOWS_NATIVE_ASAR_UNPACK_GLOB] }
: {}),
: {
asarUnpack: [
"**/node_modules/@trycua/cua-driver-*/**/*.{node,so,dylib,dll}",
"**/node_modules/@ubjs/node-*/**/*.node",
],
}),
extraResources: [
...DESKTOP_EXTRA_RESOURCES,
...(platform === "linux" ? LINUX_CAPTURE_EXTRA_RESOURCES : []),
Expand Down Expand Up @@ -3017,7 +3162,12 @@ export const stageWindowsServerSidecar = Effect.fn("stageWindowsServerSidecar")(
yield* fs.copy(input.serverDistDir, path.join(serverStageDir, "apps/server/dist"));

const sidecarDependencies = {
...input.runtimeExternalDependencies,
// The desktop host owns Cua; this server reaches it through telemetry IPC.
...Object.fromEntries(
Object.entries(input.runtimeExternalDependencies).filter(
([name]) => name !== "@trycua/cua-driver",
),
),
...resolveFffNativeDependencies("win", input.arch, input.fffNodeVersion),
};
const sidecarPatchedDependencies = createStagePatchedDependencies(
Expand Down Expand Up @@ -3354,13 +3504,18 @@ export const validateWindowsPackagedPayload = Effect.fn(
);
}

const hostPlatform = yield* HostProcess.Platform;
const listing = yield* spawnAndCollectOutput(
ChildProcess.make("tar", ["-tzf", WSL_RUNTIME_ARCHIVE_NAME], {
cwd: resourcesDir,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
}),
ChildProcess.make(
hostPlatform === "win32" ? "tar.exe" : "tar",
["-tzf", WSL_RUNTIME_ARCHIVE_NAME],
{
cwd: resourcesDir,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
},
),
).pipe(Effect.mapError(invalidWslRuntime));
if (listing.exitCode !== 0) {
return yield* invalidWslRuntime(
Expand Down Expand Up @@ -3449,10 +3604,6 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
if (hostPlatform === "win32" && options.platform === "win") {
yield* preflightWindowsDesktopBuild({
arch: options.arch,
bundlesWslRuntime: bundlesWslRuntime({
platform: options.platform,
runtimeArchivePath: options.wslRuntime,
}),
});
}
const workspaceConfig = yield* readWorkspaceConfig();
Expand Down Expand Up @@ -3664,6 +3815,14 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
arch: options.arch,
verbose: options.verbose,
});
yield* stageCuaDriverExecutable({
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Resolved
repoRoot,
stageRoot,
stageResourcesDir,
platform: options.platform,
arch: options.arch,
verbose: options.verbose,
});

yield* assertPlatformBuildResources(
options.platform,
Expand Down
3 changes: 3 additions & 0 deletions scripts/lib/cli-external-packages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe("shouldBundleCliDependency", () => {
"@napi-rs/keyring",
"@clerk/electron-passkeys",
"node-addon-api",
"@trycua/cua-driver/embedded",
"@ubjs/node",
]) {
assert.strictEqual(shouldBundleCliDependency(id), false, id);
}
Expand Down Expand Up @@ -83,6 +85,7 @@ describe("selectCliRuntimeExternalDependencies", () => {
"@cursor/sdk",
"@ff-labs/fff-node",
"@napi-rs/keyring",
"@trycua/cua-driver",
"node-pty",
"playwright-core",
"undici",
Expand Down
2 changes: 2 additions & 0 deletions scripts/lib/cli-external-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
export const CLI_RUNTIME_EXTERNAL_PREFIXES = [
// Cursor ships computed Webpack imports and platform helper packages.
"@cursor/sdk",
"@trycua/",
"@ubjs/",
// Playwright reads package.json and browsers.json beside its runtime modules.
"playwright-core",
"node-pty",
Expand Down
2 changes: 2 additions & 0 deletions scripts/lib/desktop-external-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const DESKTOP_RUNTIME_EXTERNAL_PREFIXES = [
"electron-webauthn",
"ffi-rs",
"@yuuang/",
"@trycua/",
"@ubjs/",
// Reads its own bundle from disk by resolving `playwright-core/package.json`
// at runtime and ships the browser driver alongside; there is nothing to
// gain from inlining a 10 MB file the code re-reads as text.
Expand Down
Loading