Skip to content

Commit 06a3514

Browse files
authored
Merge branch 'main' into release/4.1.6
2 parents 6b5b68e + 47bb007 commit 06a3514

File tree

5 files changed

+288
-8
lines changed

5 files changed

+288
-8
lines changed

crates/node/package.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
}
3333
},
3434
"license": "MIT",
35+
"dependencies": {
36+
"tar": "^7.4.3",
37+
"detect-libc": "^2.0.4"
38+
},
3539
"devDependencies": {
3640
"@napi-rs/cli": "^3.0.0-alpha.78",
3741
"@napi-rs/wasm-runtime": "^0.2.9",
@@ -42,7 +46,8 @@
4246
},
4347
"files": [
4448
"index.js",
45-
"index.d.ts"
49+
"index.d.ts",
50+
"scripts/install.js"
4651
],
4752
"publishConfig": {
4853
"provenance": true,
@@ -57,7 +62,8 @@
5762
"postbuild:wasm": "node ./scripts/move-artifacts.mjs",
5863
"dev": "cargo watch --quiet --shell 'npm run build'",
5964
"build:debug": "napi build --platform --no-const-enum",
60-
"version": "napi version"
65+
"version": "napi version",
66+
"postinstall": "node ./scripts/install.js"
6167
},
6268
"optionalDependencies": {
6369
"@tailwindcss/oxide-android-arm64": "workspace:*",

crates/node/scripts/install.js

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* @tailwindcss/oxide postinstall script
5+
*
6+
* This script ensures that the correct binary for the current platform and
7+
* architecture is downloaded and available.
8+
*/
9+
10+
const fs = require('fs')
11+
const path = require('path')
12+
const https = require('https')
13+
const { extract } = require('tar')
14+
const packageJson = require('../package.json')
15+
const detectLibc = require('detect-libc')
16+
17+
const version = packageJson.version
18+
19+
function getPlatformPackageName() {
20+
let platform = process.platform
21+
let arch = process.arch
22+
23+
let libc = ''
24+
if (platform === 'linux') {
25+
libc = detectLibc.isNonGlibcLinuxSync() ? 'musl' : 'gnu'
26+
}
27+
28+
// Map to our package naming conventions
29+
switch (platform) {
30+
case 'darwin':
31+
return arch === 'arm64' ? '@tailwindcss/oxide-darwin-arm64' : '@tailwindcss/oxide-darwin-x64'
32+
case 'win32':
33+
if (arch === 'arm64') return '@tailwindcss/oxide-win32-arm64-msvc'
34+
if (arch === 'ia32') return '@tailwindcss/oxide-win32-ia32-msvc'
35+
return '@tailwindcss/oxide-win32-x64-msvc'
36+
case 'linux':
37+
if (arch === 'x64') {
38+
return libc === 'musl'
39+
? '@tailwindcss/oxide-linux-x64-musl'
40+
: '@tailwindcss/oxide-linux-x64-gnu'
41+
} else if (arch === 'arm64') {
42+
return libc === 'musl'
43+
? '@tailwindcss/oxide-linux-arm64-musl'
44+
: '@tailwindcss/oxide-linux-arm64-gnu'
45+
} else if (arch === 'arm') {
46+
return '@tailwindcss/oxide-linux-arm-gnueabihf'
47+
}
48+
break
49+
case 'freebsd':
50+
return '@tailwindcss/oxide-freebsd-x64'
51+
case 'android':
52+
return '@tailwindcss/oxide-android-arm64'
53+
default:
54+
return '@tailwindcss/oxide-wasm32-wasi'
55+
}
56+
}
57+
58+
function isPackageAvailable(packageName) {
59+
try {
60+
require.resolve(packageName)
61+
return true
62+
} catch (e) {
63+
return false
64+
}
65+
}
66+
67+
// Extract all files from a tarball to a destination directory
68+
async function extractTarball(tarballStream, destDir) {
69+
if (!fs.existsSync(destDir)) {
70+
fs.mkdirSync(destDir, { recursive: true })
71+
}
72+
73+
return new Promise((resolve, reject) => {
74+
tarballStream
75+
.pipe(extract({ cwd: destDir, strip: 1 }))
76+
.on('error', (err) => reject(err))
77+
.on('end', () => resolve())
78+
})
79+
}
80+
81+
async function downloadAndExtractBinary(packageName) {
82+
let tarballUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.replace('@tailwindcss/', '')}-${version}.tgz`
83+
console.log(`Downloading ${tarballUrl}...`)
84+
85+
return new Promise((resolve) => {
86+
https
87+
.get(tarballUrl, (response) => {
88+
if (response.statusCode === 302 || response.statusCode === 301) {
89+
// Handle redirects
90+
https.get(response.headers.location, handleResponse).on('error', (err) => {
91+
console.error('Download error:', err)
92+
resolve()
93+
})
94+
return
95+
}
96+
97+
handleResponse(response)
98+
99+
async function handleResponse(response) {
100+
try {
101+
if (response.statusCode !== 200) {
102+
throw new Error(`Download failed with status code: ${response.statusCode}`)
103+
}
104+
105+
await extractTarball(
106+
response,
107+
path.join(__dirname, '..', 'node_modules', ...packageName.split('/')),
108+
)
109+
console.log(`Successfully downloaded and installed ${packageName}`)
110+
} catch (error) {
111+
console.error('Error during extraction:', error)
112+
resolve()
113+
} finally {
114+
resolve()
115+
}
116+
}
117+
})
118+
.on('error', (err) => {
119+
console.error('Download error:', err)
120+
resolve()
121+
})
122+
})
123+
}
124+
125+
async function main() {
126+
// Don't run this script in the package source
127+
try {
128+
if (fs.existsSync(path.join(__dirname, '..', 'build.rs'))) {
129+
return
130+
}
131+
132+
let packageName = getPlatformPackageName()
133+
if (!packageName) return
134+
if (isPackageAvailable(packageName)) return
135+
136+
await downloadAndExtractBinary(packageName)
137+
} catch (error) {
138+
console.error(error)
139+
return
140+
}
141+
}
142+
143+
main()
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import fs from 'node:fs/promises'
2+
import path from 'node:path'
3+
import { js, json, test } from '../utils'
4+
5+
test(
6+
'@tailwindcss/oxide will fail when architecture-specific packages are missing',
7+
{
8+
fs: {
9+
'package.json': json`
10+
{
11+
"dependencies": {
12+
"@tailwindcss/oxide": "workspace:^"
13+
}
14+
}
15+
`,
16+
'test.js': js`
17+
try {
18+
let Scanner = require('@tailwindcss/oxide')
19+
console.log('SUCCESS: @tailwindcss/oxide loaded successfully', Scanner)
20+
} catch (error) {
21+
console.log('FAILURE: Failed to load @tailwindcss/oxide:', error.message)
22+
}
23+
`,
24+
},
25+
},
26+
async ({ exec, root, expect, fs }) => {
27+
await removePlatformSpecificExtensions(path.join(root, 'node_modules'))
28+
29+
// Get last published version
30+
let version = (await exec('npm show @tailwindcss/oxide version')).trim()
31+
// Ensure that we don't depend on a specific version number in the download
32+
// script in case we bump the version number in the repository and CI is run
33+
// before a release
34+
let packageJson = JSON.parse(await fs.read('node_modules/@tailwindcss/oxide/package.json'))
35+
packageJson.version = version
36+
await fs.write(
37+
'node_modules/@tailwindcss/oxide/package.json',
38+
JSON.stringify(packageJson, null, 2),
39+
)
40+
41+
let opts = {
42+
// Ensure that we don't include any node paths from the test runner
43+
env: { NODE_PATH: '' },
44+
}
45+
46+
expect(await exec('node test.js', opts)).toMatch(/FAILURE/)
47+
48+
// Now run the post-install script
49+
await exec('node node_modules/@tailwindcss/oxide/scripts/install.js', opts)
50+
51+
expect(await exec('node test.js', opts)).toMatch(/SUCCESS/)
52+
},
53+
)
54+
55+
async function removePlatformSpecificExtensions(directory: string) {
56+
let entries = await fs.readdir(directory, { withFileTypes: true })
57+
58+
for (let entry of entries) {
59+
let fullPath = path.join(directory, entry.name)
60+
61+
if (entry.name.startsWith('oxide-')) {
62+
if (entry.isSymbolicLink()) {
63+
await fs.unlink(fullPath)
64+
} else if (entry.isFile()) {
65+
await fs.unlink(fullPath)
66+
} else if (entry.isDirectory()) {
67+
await fs.rm(fullPath, { recursive: true, force: true })
68+
}
69+
} else if (entry.isDirectory()) {
70+
await removePlatformSpecificExtensions(fullPath)
71+
}
72+
}
73+
}

integrations/utils.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ export function test(
126126
{
127127
cwd,
128128
...childProcessOptions,
129-
env: childProcessOptions.env,
129+
env: {
130+
...process.env,
131+
...childProcessOptions.env,
132+
},
130133
},
131134
(error, stdout, stderr) => {
132135
if (error) {

0 commit comments

Comments
 (0)