| ... | ... | @@ -88,9 +88,22 @@ export function npmPlatformPackageName(platformKey: CliArchivePlatformKey): stri |
| 88 | 88 | return `${NPM_PLATFORM_PACKAGE_SCOPE}/t3-${platformKey}`; |
| 89 | 89 | } |
| 90 | 90 | |
| 91 | | /** package.json for one platform package; `os`/`cpu` let npm skip the other five. */ |
| 92 | | export function npmPlatformPackageManifest(platformKey: CliArchivePlatformKey, version: string) { |
| 91 | /** |
| 92 | * package.json for one platform package; `os`/`cpu` let npm skip the other |
| 93 | * five. The archive's runtime `node_modules` (native addons and their |
| 94 | * loaders) ships inside the tarball, and npm only keeps a nested tree it can |
| 95 | * account for: anything not declared is extraneous and pruned on the next |
| 96 | * `npm install` in that project, which then breaks the executable. Declaring |
| 97 | * every bundled package as a bundled dependency at the exact version on disk |
| 98 | * makes npm treat the tree as part of this package and leave it alone. |
| 99 | */ |
| 100 | export function npmPlatformPackageManifest( |
| 101 | platformKey: CliArchivePlatformKey, |
| 102 | version: string, |
| 103 | bundled: Readonly<Record<string, string>>, |
| 104 | ) { |
| 93 | 105 | const [os, cpu] = platformKey.split("-") as [string, string]; |
| 106 | const bundleDependencies = Object.keys(bundled).sort(); |
| 94 | 107 | return { |
| 95 | 108 | name: npmPlatformPackageName(platformKey), |
| 96 | 109 | version, |
| ... | ... | @@ -101,9 +114,38 @@ export function npmPlatformPackageManifest(platformKey: CliArchivePlatformKey, v |
| 101 | 114 | cpu: [cpu], |
| 102 | 115 | files: ["t3", "t3.exe", "client", "resource-monitor", "node_modules"], |
| 103 | 116 | preferUnplugged: true, |
| 117 | dependencies: Object.fromEntries(bundleDependencies.map((name) => [name, bundled[name]])), |
| 118 | bundleDependencies, |
| 104 | 119 | }; |
| 105 | 120 | } |
| 106 | 121 | |
| 122 | const PackageVersion = Schema.Struct({ version: Schema.String }); |
| 123 | const decodePackageVersion = Schema.decodeUnknownEffect(Schema.fromJsonString(PackageVersion)); |
| 124 | |
| 125 | /** Every top-level package under `node_modules`, scoped ones included, at the version its manifest names. */ |
| 126 | const readBundledPackages = Effect.fn("readBundledPackages")(function* (nodeModulesDir: string) { |
| 127 | const fs = yield* FileSystem.FileSystem; |
| 128 | const path = yield* Path.Path; |
| 129 | const bundled: Record<string, string> = {}; |
| 130 | const packageDirs: Array<{ readonly name: string; readonly dir: string }> = []; |
| 131 | for (const entry of yield* fs.readDirectory(nodeModulesDir)) { |
| 132 | if (entry.startsWith(".")) continue; |
| 133 | const dir = path.join(nodeModulesDir, entry); |
| 134 | if (entry.startsWith("@")) { |
| 135 | for (const scoped of yield* fs.readDirectory(dir)) { |
| 136 | packageDirs.push({ name: `${entry}/${scoped}`, dir: path.join(dir, scoped) }); |
| 137 | } |
| 138 | } else { |
| 139 | packageDirs.push({ name: entry, dir }); |
| 140 | } |
| 141 | } |
| 142 | for (const { name, dir } of packageDirs) { |
| 143 | const manifest = yield* fs.readFileString(path.join(dir, "package.json")); |
| 144 | bundled[name] = (yield* decodePackageVersion(manifest)).version; |
| 145 | } |
| 146 | return bundled; |
| 147 | }); |
| 148 | |
| 107 | 149 | /** |
| 108 | 150 | * README for one platform package. Without one at the package root, npm |
| 109 | 151 | * shows the first README it finds in the tarball, which is a bundled |
| ... | ... | @@ -302,9 +344,10 @@ const stagePlatformPackage = Effect.fn("stagePlatformPackage")(function* (input: |
| 302 | 344 | if (executableName === "t3") { |
| 303 | 345 | yield* fs.chmod(executable, 0o755); |
| 304 | 346 | } |
| 347 | const bundled = yield* readBundledPackages(path.join(contentDir, "node_modules")); |
| 305 | 348 | yield* fs.writeFileString( |
| 306 | 349 | path.join(contentDir, "package.json"), |
| 307 | | `${yield* encodePackageJson(npmPlatformPackageManifest(input.key, input.version))}\n`, |
| 350 | `${yield* encodePackageJson(npmPlatformPackageManifest(input.key, input.version, bundled))}\n`, |
| 308 | 351 | ); |
| 309 | 352 | yield* fs.writeFileString( |
| 310 | 353 | path.join(contentDir, "README.md"), |