1+ // Usage: npm install
2+
3+ const writeTo = './wwwroot/lib'
4+ const defaultPrefix = 'https://unpkg.com'
5+ const files = {
6+ mjs : {
7+ } ,
8+ typings : {
9+ }
10+ }
11+
12+ const path = require ( 'path' )
13+ const fs = require ( 'fs' )
14+ const { pipeline } = require ( 'stream' )
15+ const { promisify } = require ( 'util' )
16+ const { execSync } = require ( 'child_process' )
17+ const pipe = promisify ( pipeline )
18+
19+ ; ( async ( ) => {
20+ const requests = [ ]
21+ Object . keys ( files ) . forEach ( dir => {
22+ const dirFiles = files [ dir ]
23+ Object . keys ( dirFiles ) . forEach ( name => {
24+ let url = dirFiles [ name ]
25+ if ( url . startsWith ( '/' ) )
26+ url = defaultPrefix + url
27+ const toFile = path . join ( writeTo , dir , name )
28+ requests . push ( fetchDownload ( url , toFile , 5 ) )
29+ } )
30+ } )
31+
32+ await Promise . all ( requests )
33+ await downloadTailwindBinary ( )
34+ } ) ( )
35+
36+ async function fetchDownload ( url , toFile , retries ) {
37+ const toDir = path . dirname ( toFile )
38+ fs . mkdirSync ( toDir , { recursive : true } )
39+ for ( let i = retries ; i >= 0 ; -- i ) {
40+ try {
41+ let r = await fetch ( url )
42+ if ( ! r . ok ) {
43+ throw new Error ( `${ r . status } ${ r . statusText } ` ) ;
44+ }
45+ let txt = await r . text ( )
46+ console . log ( `writing ${ url } to ${ toFile } ` )
47+ await fs . writeFileSync ( toFile , txt )
48+ return
49+ } catch ( e ) {
50+ console . log ( `get ${ url } failed: ${ e } ${ i > 0 ? `, ${ i } retries remaining...` : '' } ` )
51+ }
52+ }
53+ }
54+
55+ async function downloadTailwindBinary ( ) {
56+ const platform = process . platform // e.g., 'darwin', 'linux', 'win32'
57+ const arch = process . arch // e.g., 'arm64', 'x64'
58+
59+ // Check if tailwindcss is already in PATH
60+ try {
61+ const command = platform === 'win32' ? 'where tailwindcss' : 'which tailwindcss'
62+ const result = execSync ( command , { stdio : 'pipe' } )
63+ if ( result ) {
64+ // Check version of tailwindcss by looking for 'tailwindcss v4' in `taildwindcss --help`
65+ const helpResult = execSync ( 'tailwindcss --help' , { stdio : 'pipe' } )
66+ const helpOutput = helpResult . toString ( )
67+ if ( helpOutput . includes ( 'tailwindcss v1' ) || helpOutput . includes ( 'tailwindcss v2' ) || helpOutput . includes ( 'tailwindcss v3' ) ) {
68+ console . log ( 'old version of tailwindcss detected, please uninstall and rerun this script.' )
69+ } else {
70+ console . log ( 'tailwindcss is already installed.' )
71+ }
72+ return
73+ }
74+ } catch ( e ) {
75+ // Command failed, tailwindcss not in PATH
76+ }
77+
78+ // if file already exists, exit
79+ const tailwindcssPath = path . join ( process . cwd ( ) , 'tailwindcss' )
80+ if ( fs . existsSync ( tailwindcssPath ) ) {
81+ console . log ( `${ tailwindcssPath } already exists, skipping download.` )
82+ return
83+ }
84+
85+ console . log ( )
86+ function getBinaryFileName ( ) {
87+ // Determine the correct binary file name based on the current OS and architecture
88+ if ( platform === 'darwin' ) { // macOS
89+ if ( arch === 'arm64' ) {
90+ return 'tailwindcss-macos-arm64'
91+ } else if ( arch === 'x64' ) {
92+ return 'tailwindcss-macos-x64'
93+ }
94+ } else if ( platform === 'linux' ) { // Linux
95+ if ( arch === 'arm64' ) {
96+ return 'tailwindcss-linux-arm64'
97+ } else if ( arch === 'x64' ) {
98+ return 'tailwindcss-linux-x64'
99+ }
100+ } else if ( platform === 'win32' ) { // Windows
101+ if ( arch === 'arm64' ) {
102+ return 'arm64-windows'
103+ } else if ( arch === 'x64' ) {
104+ return 'tailwindcss-windows-x64.exe'
105+ }
106+ }
107+ }
108+
109+ let binaryFileName = getBinaryFileName ( )
110+
111+ // If no matching binary is found, exit with an error
112+ if ( ! binaryFileName ) {
113+ console . error ( `Error: Unsupported platform/architecture combination: ${ platform } /${ arch } ` )
114+ console . error ( `Please ensure your system is one of the following:` )
115+ console . error ( ` macOS (arm64, x64)` )
116+ console . error ( ` Linux (arm64, x64)` )
117+ console . error ( ` Windows (arm64, x64)` )
118+ process . exit ( 1 )
119+ }
120+
121+ // Base URL for Tailwind CSS latest release downloads
122+ const downloadTailwindBaseUrl = `https://github.com/tailwindlabs/tailwindcss/releases/latest/download/`
123+ const downloadUrl = `${ downloadTailwindBaseUrl } ${ binaryFileName } `
124+ // Set the output file name. On Windows, it should have a .exe extension.
125+ const outputFileName = ( platform === 'win32' || platform === 'cygwin' || platform === 'msys' ) ? 'tailwindcss.exe' : 'tailwindcss'
126+ const outputPath = path . join ( process . cwd ( ) , outputFileName )
127+
128+ console . log ( `Attempting to download the latest Tailwind CSS binary for ${ platform } /${ arch } ...` )
129+ console . log ( `Downloading ${ downloadUrl } ...` )
130+
131+ try {
132+ const response = await fetch ( downloadUrl )
133+
134+ // Check if the response status is not OK (e.g., 404, 500).
135+ // Fetch automatically handles redirects (3xx status codes).
136+ if ( ! response . ok ) {
137+ console . error ( `Failed to download: HTTP Status Code ${ response . status } - ${ response . statusText } ` )
138+ return
139+ }
140+
141+ // Ensure there's a readable stream body
142+ if ( ! response . body ) {
143+ console . error ( 'No response body received from the download URL.' )
144+ return
145+ }
146+
147+ const fileStream = fs . createWriteStream ( outputPath )
148+ // Pipe the readable stream from the fetch response body directly to the file stream
149+ await pipe ( response . body , fileStream )
150+
151+ // Set executable permissions for non-Windows platforms
152+ if ( platform !== 'win32' && platform !== 'cygwin' && platform !== 'msys' ) {
153+ console . log ( `Setting executable permissions (+x) on ${ outputPath } ...` )
154+ // '755' means: owner can read, write, execute; group and others can read and execute.
155+ fs . chmodSync ( outputPath , '755' )
156+ // console.log('Permissions set successfully.')
157+
158+ const tryFolders = [
159+ `${ process . env . HOME } /.local/bin` ,
160+ `${ process . env . HOME } /.npm-global/bin` ,
161+ '/usr/local/bin' ,
162+ '/usr/bin' ,
163+ '/usr/sbin'
164+ ]
165+
166+ // Move the binary to a common location in PATH
167+ for ( const folder of tryFolders ) {
168+ if ( ! fs . existsSync ( folder ) ) {
169+ // console.log(`Folder ${folder} does not exist, skipping...`);
170+ continue
171+ }
172+ const targetPath = path . join ( folder , outputFileName )
173+ if ( fs . accessSync ( folder , fs . constants . W_OK ) ) {
174+ try {
175+ fs . renameSync ( outputPath , targetPath )
176+ console . log ( `Saved to ${ targetPath } ` )
177+ break
178+ }
179+ catch ( err ) {
180+ console . error ( `Failed to move ${ outputPath } to ${ targetPath } : ${ err . message } ` )
181+ }
182+ }
183+
184+ try {
185+ // try using sudo with process exec
186+ execSync ( `sudo mv ${ outputPath } ${ targetPath } ` )
187+ console . log ( `Saved to ${ targetPath } ` )
188+ break
189+ }
190+ catch ( err ) {
191+ console . log ( `Manually move tailwindcss to ${ targetPath } by running:` )
192+ console . log ( `sudo mv ${ outputPath } ${ targetPath } ` )
193+ break
194+ }
195+ }
196+ } else if ( platform === 'win32' ) {
197+ let moved = false
198+ // Move the binary to a common location in PATH for .NET Devs
199+ const tryFolders = [
200+ `${ process . env . APPDATA } /npm` ,
201+ `${ process . env . USERPROFILE } /.dotnet/tools` ,
202+ ]
203+ for ( const folder of tryFolders ) {
204+ if ( ! fs . existsSync ( folder ) ) {
205+ continue
206+ }
207+ const targetPath = path . join ( folder , outputFileName )
208+ try {
209+ fs . renameSync ( outputPath , targetPath )
210+ console . log ( `Saved to ${ targetPath } ` )
211+ moved = true
212+ break
213+ }
214+ catch ( err ) {
215+ }
216+ }
217+ if ( ! moved ) {
218+ console . log ( )
219+ console . log ( `Saved to ${ outputPath } ` )
220+ console . log ( `Tip: Make ${ outputFileName } globally accessible by moving it to a folder in your PATH` )
221+ }
222+ }
223+
224+ console . log ( )
225+ console . log ( `You can now run it from your terminal using:` )
226+ console . log ( outputFileName === 'tailwindcss.exe' ? `${ outputFileName } --help` : `${ outputFileName } --help` )
227+
228+ } catch ( error ) {
229+ console . error ( `\nError during download or permission setting:` )
230+ console . error ( error . message )
231+ process . exit ( 1 )
232+ }
233+ }
0 commit comments