-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Download platform specific package if optionalDependencies
are skipped
#17929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
philipp-spiess
wants to merge
8
commits into
main
Choose a base branch
from
fix/add-postinstall-script
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6e16360
Download platform specific package if `optionalDependencies` are skipped
philipp-spiess 5d2970b
What's up with that?
philipp-spiess 2164265
Fix
philipp-spiess 713663b
Try again I guess
philipp-spiess 32263b6
Thanks Claude
philipp-spiess a589725
Update crates/node/scripts/install.js
philipp-spiess 7eeba14
Update crates/node/scripts/install.js
philipp-spiess f889eb9
Wrap everything in a trycatch
philipp-spiess File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can somehow re-use this logic from what NAPI generated for us. Since that is generated and using require already it might not be as easy.
Just have to make sure that if we make changes, that we keep this in sync
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking at the napi stuff and it seems to be much more (it generates names for packages that we don't even have etc). I agree that ideally we can reuse it but the function for that is not exported so I'm not sure how. MAYBE we can just try and load the index.js file tbh but then we still don't need which file to load haha