-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
executable file
·158 lines (135 loc) · 4.93 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env node
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { createInterface } from 'node:readline/promises'
import { URL } from 'node:url'
const { argv } = process
const helpText = 'Usage: vendurl [--clean | -c [--yes | -y]] [--verbose | -v] [--no-color]'
const help = ['help', '--help', '-h'].some((arg) => argv.includes(arg))
if (help) {
console.log(helpText)
process.exit(0)
}
const s = (s) => s
const { red, blue, green, grey } = process.env.NO_COLOR || argv.includes('--no-color')
? { red: s, blue: s, green: s, grey: s }
: {
red: s => `\u001b[31m${s}\u001b[39m`,
blue: s => `\u001b[34m${s}\u001b[39m`,
green: s => `\u001b[32m${s}\u001b[39m`,
grey: s => `\u001b[90m${s}\u001b[39m`,
}
function createURL (specifier, bundleParam, base) {
try {
return new URL(specifier)
} catch (err) {
try {
const url = new URL(specifier, base)
if (bundleParam) url.searchParams.set('bundle', 'true')
return url
} catch {
throw new Error(`Unable to parse "${specifier}" to URL`)
}
}
}
const ESM_SH = 'https://esm.sh/'
const cwd = process.cwd()
const packagePath = path.join(cwd, 'package.json')
const packageString = fs.readFileSync(packagePath, 'utf8')
const pkg = JSON.parse(packageString)
const { name, vendurl } = pkg
const verbose = ['--verbose', '-v'].some((arg) => argv.includes(arg))
const clean = ['--clean', '-c'].some((arg) => argv.includes(arg))
let yes = ['--yes', '-y'].some((arg) => argv.includes(arg))
if (!(vendurl && vendurl.packages && typeof vendurl.packages === 'object')) {
console.error(red(`"${name} is missing "vendurl" in package.json`))
process.exit(1)
}
// defaults
const { bundle = true, destination = './vendor', packages, provider = ESM_SH } = vendurl
if (clean) {
const fullDestination = path.join(cwd, destination)
try {
if (!yes) {
const rl = createInterface({ input: process.stdin, output: process.stdout })
const confirm = await rl.question(`Delete "${fullDestination.replace(cwd, '')}"? (y/N) `)
rl.close()
yes = confirm.toLowerCase() === 'y'
}
if (!yes) {
console.log('Aborting')
process.exit(1)
}
fs.rmSync(fullDestination, { recursive: true, force: true })
if (verbose) console.log(grey(`Deleted "${fullDestination.replace(cwd, '')}"`))
} catch (error) {
throw new Error(`Unable to remove "${fullDestination}"`)
}
}
// parse packages
let ok = true
const manifest = []
for (const [filename, specifier] of Object.entries(packages)) {
if (Array.isArray(specifier)) {
ok = false
console.error(`Specifier should be a string or object. ${red(`Found Array. Skipping "${filename}".`)}`)
} else if (typeof specifier === 'object') {
const { specifier: pSpecifier, provider: pProvider, bundle: pBundle, ...rest } = specifier
const url = createURL(pSpecifier, pBundle || bundle, pProvider || provider)
manifest.push({ filename, url, specifier: pSpecifier, ...rest })
} else if (typeof specifier === 'string') {
const url = createURL(specifier, bundle, provider)
manifest.push({ filename, url, specifier })
} else {
ok = false
console.error(`Specifier should be a string or object. ${red(`Skipping "${filename}".`)}`)
}
}
// download packages
for (let { filename, specifier, url, destination: pDestination } of manifest) {
const outputPath = pDestination || destination
const fullOutputPath = path.join(cwd, outputPath)
if (url) {
if (verbose) console.log(grey(`Creating "${filename}" from "${specifier}"`))
try {
let response = await fetch(url)
if (response.status !== 200) {
ok = false
console.error(red(`Unable to fetch "${specifier}" from constructed URL: ${url}`))
continue
}
const esmId = response.headers.get('X-Esm-Id') // esm.sh final URL path
if (esmId) {
url = new URL(esmId, provider)
if (verbose) console.log(grey(`Redirected to ${url}`))
response = await fetch(url)
}
const contents = await response.text()
try {
if (!fs.existsSync(fullOutputPath)) {
fs.mkdirSync(fullOutputPath, { recursive: true })
if (verbose) console.log(grey(`Created "${outputPath}"`))
}
} catch (error) {
console.error(red(`Unable to create "${fullOutputPath}"`))
if (verbose) console.error(error)
continue
}
try {
fs.writeFileSync(path.join(fullOutputPath, filename), contents)
console.log(`${blue('Saved')} "${filename}"${verbose ? `to ${outputPath}` : ''}`)
} catch (error) {
ok = false
console.error(red(`Unable to save "${filename}" to ${fullOutputPath}`))
if (verbose) console.error(error)
}
} catch (error) {
ok = false
console.error(red(`Unable to download "${filename}" from ${url}`))
if (verbose) console.error(error)
}
}
}
console.log(green('✔ Vendored.'))
process.exit(ok ? 0 : 1)