|
1 | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
2 | 2 | import { assert, it } from "@effect/vitest"; |
3 | 3 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 4 | +import * as FileSystem from "effect/FileSystem"; |
| 5 | +import * as Path from "effect/Path"; |
4 | 6 | import * as Effect from "effect/Effect"; |
5 | 7 | import * as Layer from "effect/Layer"; |
6 | 8 | import * as Option from "effect/Option"; |
@@ -43,6 +45,8 @@ import { |
43 | 45 | stageLinuxIconSize, |
44 | 46 | STAGE_INSTALL_ARGS, |
45 | 47 | WINDOWS_ASAR_UNPACK, |
| 48 | + ancestorNodeModulesPaths, |
| 49 | + copyDirectoryPreservingSymlinks, |
46 | 50 | } from "./build-desktop-artifact.ts"; |
47 | 51 | import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; |
48 | 52 | import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
@@ -767,3 +771,85 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
767 | 771 | }), |
768 | 772 | ); |
769 | 773 | }); |
| 774 | + |
| 775 | +// The self-containment check runs the packaged tree in a scratch directory. Its |
| 776 | +// own node_modules holds the unpacked externals and must be ignored, but any |
| 777 | +// node_modules *above* it would let Node's parent walk satisfy an import that is |
| 778 | +// missing from the package, so the probe refuses to run in that case. |
| 779 | +it("lists ancestor node_modules, nearest first, excluding the start directory", () => { |
| 780 | + assert.deepStrictEqual(ancestorNodeModulesPaths("C:\\tmp\\probe\\app", "\\"), [ |
| 781 | + "C:\\tmp\\probe\\node_modules", |
| 782 | + "C:\\tmp\\node_modules", |
| 783 | + "C:\\node_modules", |
| 784 | + ]); |
| 785 | +}); |
| 786 | + |
| 787 | +it("includes the filesystem root for posix paths", () => { |
| 788 | + assert.deepStrictEqual(ancestorNodeModulesPaths("/tmp/probe", "/"), [ |
| 789 | + "/tmp/node_modules", |
| 790 | + "/node_modules", |
| 791 | + ]); |
| 792 | +}); |
| 793 | + |
| 794 | +// A UNC root must keep its \\server\share prefix. Rebuilding it from segments |
| 795 | +// produced relative paths, which fs.exists resolves against the build cwd, so |
| 796 | +// the guard checked directories that do not exist and silently passed. |
| 797 | +it("keeps the prefix of a UNC path instead of going relative", () => { |
| 798 | + const paths = ancestorNodeModulesPaths("\\\\server\\share\\tmp\\app", "\\"); |
| 799 | + for (const candidate of paths) { |
| 800 | + assert.ok(candidate.startsWith("\\\\server\\share"), candidate); |
| 801 | + } |
| 802 | + assert.deepStrictEqual(paths[0], "\\\\server\\share\\tmp\\node_modules"); |
| 803 | +}); |
| 804 | + |
| 805 | +it.effect("rebases packaged links into the isolated tree", () => |
| 806 | + Effect.gen(function* () { |
| 807 | + const fs = yield* FileSystem.FileSystem; |
| 808 | + const path = yield* Path.Path; |
| 809 | + const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-copy-symlinks-" }); |
| 810 | + const source = path.join(root, "source"); |
| 811 | + const destination = path.join(root, "destination"); |
| 812 | + const packageDir = path.join(source, "node_modules/.pnpm/example@1/node_modules/example"); |
| 813 | + const relativePackageLink = path.join(source, "node_modules/example-relative"); |
| 814 | + const absolutePackageLink = path.join(source, "node_modules/example-absolute"); |
| 815 | + |
| 816 | + yield* fs.makeDirectory(packageDir, { recursive: true }); |
| 817 | + yield* fs.writeFileString(path.join(packageDir, "index.js"), "module.exports = true;\n"); |
| 818 | + yield* fs.symlink( |
| 819 | + path.join(".pnpm", "example@1", "node_modules", "example"), |
| 820 | + relativePackageLink, |
| 821 | + ); |
| 822 | + yield* fs.symlink(packageDir, absolutePackageLink); |
| 823 | + |
| 824 | + yield* copyDirectoryPreservingSymlinks(source, destination); |
| 825 | + |
| 826 | + const copiedPackage = path.join( |
| 827 | + destination, |
| 828 | + "node_modules/.pnpm/example@1/node_modules/example", |
| 829 | + ); |
| 830 | + const resolvedCopiedPackage = yield* fs.realPath(copiedPackage); |
| 831 | + assert.equal( |
| 832 | + yield* fs.readLink(path.join(destination, "node_modules/example-relative")), |
| 833 | + copiedPackage, |
| 834 | + ); |
| 835 | + assert.equal( |
| 836 | + yield* fs.readLink(path.join(destination, "node_modules/example-absolute")), |
| 837 | + copiedPackage, |
| 838 | + ); |
| 839 | + assert.equal( |
| 840 | + yield* fs.realPath(path.join(destination, "node_modules/example-relative")), |
| 841 | + resolvedCopiedPackage, |
| 842 | + ); |
| 843 | + assert.equal( |
| 844 | + yield* fs.realPath(path.join(destination, "node_modules/example-absolute")), |
| 845 | + resolvedCopiedPackage, |
| 846 | + ); |
| 847 | + }).pipe(Effect.provide(NodeServices.layer)), |
| 848 | +); |
| 849 | + |
| 850 | +it("ignores trailing separators", () => { |
| 851 | + assert.deepStrictEqual( |
| 852 | + ancestorNodeModulesPaths("C:\\tmp\\probe\\app\\", "\\"), |
| 853 | + ancestorNodeModulesPaths("C:\\tmp\\probe\\app", "\\"), |
| 854 | + ); |
| 855 | +}); |
0 commit comments