mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Download platform specific package if optionalDependencies are skipped (#17929)
Closes #15806 This PR adds a new `postinstall` script to `@tailwindcss/oxide` that will attempt to download the platform-specific optional dependency to avoid issues when the package manager does not do that automatically (see #15806). The implementation for this is fairly simple: The npm package is downloaded from the official npm servers and extracted into the `node_modules` directory of the `@tailwidncss/oxide` installation. ## Test plan Since we still lack a solid repro of #15806, the way I tested this change was to manually remove all automatically-installed optional dependencies and then running the postinstall script manually. The script then downloads the right version package which makes the import to `@tailwidncss/oxide` work. An appropriate integration test was added too. I furthermore also validated that: - This works across CI platforms [ci-all] - The postinstall script bails out when running `pnpm install` in the dev setup. This is necessary since doing the initial install won't have any binary dependencies yet so it would download invalid versions from npm (as the version numbers locally refer to the last released version). We can safely bail out here though since this was never an issue with local development. - The postinstall script does not do anything when the `@tailwindcss/oxide` library is added _unless_ the issue is detected. [ci-all] --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
2f6679abfe
commit
47bb007eae
@ -32,6 +32,10 @@
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tar": "^7.4.3",
|
||||
"detect-libc": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^3.0.0-alpha.78",
|
||||
"@napi-rs/wasm-runtime": "^0.2.9",
|
||||
@ -42,7 +46,8 @@
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
"index.d.ts",
|
||||
"scripts/install.js"
|
||||
],
|
||||
"publishConfig": {
|
||||
"provenance": true,
|
||||
@ -57,7 +62,8 @@
|
||||
"postbuild:wasm": "node ./scripts/move-artifacts.mjs",
|
||||
"dev": "cargo watch --quiet --shell 'npm run build'",
|
||||
"build:debug": "napi build --platform --no-const-enum",
|
||||
"version": "napi version"
|
||||
"version": "napi version",
|
||||
"postinstall": "node ./scripts/install.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "workspace:*",
|
||||
|
||||
143
crates/node/scripts/install.js
Normal file
143
crates/node/scripts/install.js
Normal file
@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* @tailwindcss/oxide postinstall script
|
||||
*
|
||||
* This script ensures that the correct binary for the current platform and
|
||||
* architecture is downloaded and available.
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const https = require('https')
|
||||
const { extract } = require('tar')
|
||||
const packageJson = require('../package.json')
|
||||
const detectLibc = require('detect-libc')
|
||||
|
||||
const version = packageJson.version
|
||||
|
||||
function getPlatformPackageName() {
|
||||
let platform = process.platform
|
||||
let arch = process.arch
|
||||
|
||||
let libc = ''
|
||||
if (platform === 'linux') {
|
||||
libc = detectLibc.isNonGlibcLinuxSync() ? 'musl' : 'gnu'
|
||||
}
|
||||
|
||||
// Map to our package naming conventions
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return arch === 'arm64' ? '@tailwindcss/oxide-darwin-arm64' : '@tailwindcss/oxide-darwin-x64'
|
||||
case 'win32':
|
||||
if (arch === 'arm64') return '@tailwindcss/oxide-win32-arm64-msvc'
|
||||
if (arch === 'ia32') return '@tailwindcss/oxide-win32-ia32-msvc'
|
||||
return '@tailwindcss/oxide-win32-x64-msvc'
|
||||
case 'linux':
|
||||
if (arch === 'x64') {
|
||||
return libc === 'musl'
|
||||
? '@tailwindcss/oxide-linux-x64-musl'
|
||||
: '@tailwindcss/oxide-linux-x64-gnu'
|
||||
} else if (arch === 'arm64') {
|
||||
return libc === 'musl'
|
||||
? '@tailwindcss/oxide-linux-arm64-musl'
|
||||
: '@tailwindcss/oxide-linux-arm64-gnu'
|
||||
} else if (arch === 'arm') {
|
||||
return '@tailwindcss/oxide-linux-arm-gnueabihf'
|
||||
}
|
||||
break
|
||||
case 'freebsd':
|
||||
return '@tailwindcss/oxide-freebsd-x64'
|
||||
case 'android':
|
||||
return '@tailwindcss/oxide-android-arm64'
|
||||
default:
|
||||
return '@tailwindcss/oxide-wasm32-wasi'
|
||||
}
|
||||
}
|
||||
|
||||
function isPackageAvailable(packageName) {
|
||||
try {
|
||||
require.resolve(packageName)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Extract all files from a tarball to a destination directory
|
||||
async function extractTarball(tarballStream, destDir) {
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true })
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
tarballStream
|
||||
.pipe(extract({ cwd: destDir, strip: 1 }))
|
||||
.on('error', (err) => reject(err))
|
||||
.on('end', () => resolve())
|
||||
})
|
||||
}
|
||||
|
||||
async function downloadAndExtractBinary(packageName) {
|
||||
let tarballUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.replace('@tailwindcss/', '')}-${version}.tgz`
|
||||
console.log(`Downloading ${tarballUrl}...`)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
https
|
||||
.get(tarballUrl, (response) => {
|
||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||
// Handle redirects
|
||||
https.get(response.headers.location, handleResponse).on('error', (err) => {
|
||||
console.error('Download error:', err)
|
||||
resolve()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
handleResponse(response)
|
||||
|
||||
async function handleResponse(response) {
|
||||
try {
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`Download failed with status code: ${response.statusCode}`)
|
||||
}
|
||||
|
||||
await extractTarball(
|
||||
response,
|
||||
path.join(__dirname, '..', 'node_modules', ...packageName.split('/')),
|
||||
)
|
||||
console.log(`Successfully downloaded and installed ${packageName}`)
|
||||
} catch (error) {
|
||||
console.error('Error during extraction:', error)
|
||||
resolve()
|
||||
} finally {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('error', (err) => {
|
||||
console.error('Download error:', err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Don't run this script in the package source
|
||||
try {
|
||||
if (fs.existsSync(path.join(__dirname, '..', 'build.rs'))) {
|
||||
return
|
||||
}
|
||||
|
||||
let packageName = getPlatformPackageName()
|
||||
if (!packageName) return
|
||||
if (isPackageAvailable(packageName)) return
|
||||
|
||||
await downloadAndExtractBinary(packageName)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
73
integrations/oxide/postinstall.test.ts
Normal file
73
integrations/oxide/postinstall.test.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { js, json, test } from '../utils'
|
||||
|
||||
test(
|
||||
'@tailwindcss/oxide will fail when architecture-specific packages are missing',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/oxide": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'test.js': js`
|
||||
try {
|
||||
let Scanner = require('@tailwindcss/oxide')
|
||||
console.log('SUCCESS: @tailwindcss/oxide loaded successfully', Scanner)
|
||||
} catch (error) {
|
||||
console.log('FAILURE: Failed to load @tailwindcss/oxide:', error.message)
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, root, expect, fs }) => {
|
||||
await removePlatformSpecificExtensions(path.join(root, 'node_modules'))
|
||||
|
||||
// Get last published version
|
||||
let version = (await exec('npm show @tailwindcss/oxide version')).trim()
|
||||
// Ensure that we don't depend on a specific version number in the download
|
||||
// script in case we bump the version number in the repository and CI is run
|
||||
// before a release
|
||||
let packageJson = JSON.parse(await fs.read('node_modules/@tailwindcss/oxide/package.json'))
|
||||
packageJson.version = version
|
||||
await fs.write(
|
||||
'node_modules/@tailwindcss/oxide/package.json',
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
)
|
||||
|
||||
let opts = {
|
||||
// Ensure that we don't include any node paths from the test runner
|
||||
env: { NODE_PATH: '' },
|
||||
}
|
||||
|
||||
expect(await exec('node test.js', opts)).toMatch(/FAILURE/)
|
||||
|
||||
// Now run the post-install script
|
||||
await exec('node node_modules/@tailwindcss/oxide/scripts/install.js', opts)
|
||||
|
||||
expect(await exec('node test.js', opts)).toMatch(/SUCCESS/)
|
||||
},
|
||||
)
|
||||
|
||||
async function removePlatformSpecificExtensions(directory: string) {
|
||||
let entries = await fs.readdir(directory, { withFileTypes: true })
|
||||
|
||||
for (let entry of entries) {
|
||||
let fullPath = path.join(directory, entry.name)
|
||||
|
||||
if (entry.name.startsWith('oxide-')) {
|
||||
if (entry.isSymbolicLink()) {
|
||||
await fs.unlink(fullPath)
|
||||
} else if (entry.isFile()) {
|
||||
await fs.unlink(fullPath)
|
||||
} else if (entry.isDirectory()) {
|
||||
await fs.rm(fullPath, { recursive: true, force: true })
|
||||
}
|
||||
} else if (entry.isDirectory()) {
|
||||
await removePlatformSpecificExtensions(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,7 +126,10 @@ export function test(
|
||||
{
|
||||
cwd,
|
||||
...childProcessOptions,
|
||||
env: childProcessOptions.env,
|
||||
env: {
|
||||
...process.env,
|
||||
...childProcessOptions.env,
|
||||
},
|
||||
},
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
|
||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@ -87,6 +87,13 @@ importers:
|
||||
version: 2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)
|
||||
|
||||
crates/node:
|
||||
dependencies:
|
||||
detect-libc:
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
tar:
|
||||
specifier: ^7.4.3
|
||||
version: 7.4.3
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64':
|
||||
specifier: workspace:*
|
||||
@ -1546,6 +1553,10 @@ packages:
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@ -2690,6 +2701,10 @@ packages:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
chownr@3.0.0:
|
||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
citty@0.1.6:
|
||||
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
|
||||
|
||||
@ -2846,8 +2861,8 @@ packages:
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
detect-libc@2.0.3:
|
||||
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
|
||||
detect-libc@2.0.4:
|
||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
didyoumean@1.2.2:
|
||||
@ -3641,6 +3656,15 @@ packages:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minizlib@3.0.2:
|
||||
resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
mkdirp@3.0.1:
|
||||
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mlly@1.7.3:
|
||||
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
|
||||
|
||||
@ -4233,6 +4257,10 @@ packages:
|
||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar@7.4.3:
|
||||
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
terser@5.31.6:
|
||||
resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==}
|
||||
engines: {node: '>=10'}
|
||||
@ -4616,6 +4644,10 @@ packages:
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
yallist@5.0.0:
|
||||
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
yaml@2.6.0:
|
||||
resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@ -5309,6 +5341,10 @@ snapshots:
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
@ -6404,6 +6440,8 @@ snapshots:
|
||||
dependencies:
|
||||
readdirp: 4.1.1
|
||||
|
||||
chownr@3.0.0: {}
|
||||
|
||||
citty@0.1.6:
|
||||
dependencies:
|
||||
consola: 3.2.3
|
||||
@ -6533,7 +6571,7 @@ snapshots:
|
||||
|
||||
detect-libc@1.0.3: {}
|
||||
|
||||
detect-libc@2.0.3: {}
|
||||
detect-libc@2.0.4: {}
|
||||
|
||||
didyoumean@1.2.2: {}
|
||||
|
||||
@ -7492,7 +7530,7 @@ snapshots:
|
||||
|
||||
lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm):
|
||||
dependencies:
|
||||
detect-libc: 2.0.3
|
||||
detect-libc: 2.0.4
|
||||
optionalDependencies:
|
||||
lightningcss-darwin-arm64: 1.29.2
|
||||
lightningcss-darwin-x64: 1.29.2
|
||||
@ -7598,6 +7636,12 @@ snapshots:
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
minizlib@3.0.2:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
mkdirp@3.0.1: {}
|
||||
|
||||
mlly@1.7.3:
|
||||
dependencies:
|
||||
acorn: 8.14.0
|
||||
@ -8040,7 +8084,7 @@ snapshots:
|
||||
sharp@0.34.1:
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
detect-libc: 2.0.3
|
||||
detect-libc: 2.0.4
|
||||
semver: 7.7.1
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.34.1
|
||||
@ -8233,6 +8277,15 @@ snapshots:
|
||||
|
||||
tapable@2.2.1: {}
|
||||
|
||||
tar@7.4.3:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
minipass: 7.1.2
|
||||
minizlib: 3.0.2
|
||||
mkdirp: 3.0.1
|
||||
yallist: 5.0.0
|
||||
|
||||
terser@5.31.6:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
@ -8623,6 +8676,8 @@ snapshots:
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yallist@5.0.0: {}
|
||||
|
||||
yaml@2.6.0: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user