| ... | @@ -88,9 +88,22 @@ export function npmPlatformPackageName(platformKey: CliArchivePlatformKey): stri | ... | @@ -88,9 +88,22 @@ export function npmPlatformPackageName(platformKey: CliArchivePlatformKey): stri |
| 88 | return `${NPM_PLATFORM_PACKAGE_SCOPE}/t3-${platformKey}`; | 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. */ | 91 | /** |
| 92 | export function npmPlatformPackageManifest(platformKey: CliArchivePlatformKey, version: string) { | 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 | const [os, cpu] = platformKey.split("-") as [string, string]; | 105 | const [os, cpu] = platformKey.split("-") as [string, string]; |
| | 106 | const bundleDependencies = Object.keys(bundled).sort(); |
| 94 | return { | 107 | return { |
| 95 | name: npmPlatformPackageName(platformKey), | 108 | name: npmPlatformPackageName(platformKey), |
| 96 | version, | 109 | version, |
| ... | @@ -101,9 +114,38 @@ export function npmPlatformPackageManifest(platformKey: CliArchivePlatformKey, v | ... | @@ -101,9 +114,38 @@ export function npmPlatformPackageManifest(platformKey: CliArchivePlatformKey, v |
| 101 | cpu: [cpu], | 114 | cpu: [cpu], |
| 102 | files: ["t3", "t3.exe", "client", "resource-monitor", "node_modules"], | 115 | files: ["t3", "t3.exe", "client", "resource-monitor", "node_modules"], |
| 103 | preferUnplugged: true, | 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 | * README for one platform package. Without one at the package root, npm | 150 | * README for one platform package. Without one at the package root, npm |
| 109 | * shows the first README it finds in the tarball, which is a bundled | 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,9 +344,10 @@ const stagePlatformPackage = Effect.fn("stagePlatformPackage")(function* (input: |
| 302 | if (executableName === "t3") { | 344 | if (executableName === "t3") { |
| 303 | yield* fs.chmod(executable, 0o755); | 345 | yield* fs.chmod(executable, 0o755); |
| 304 | } | 346 | } |
| | 347 | const bundled = yield* readBundledPackages(path.join(contentDir, "node_modules")); |
| 305 | yield* fs.writeFileString( | 348 | yield* fs.writeFileString( |
| 306 | path.join(contentDir, "package.json"), | 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 | yield* fs.writeFileString( | 352 | yield* fs.writeFileString( |
| 310 | path.join(contentDir, "README.md"), | 353 | path.join(contentDir, "README.md"), |