userland: add archive tools and pinned GoAWK, esbuild, and QuickJS
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
const http = require('http')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const fs = require('fs')
|
||||
|
||||
const js = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'lib', 'browser.js'))
|
||||
const jsMin = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'lib', 'browser.min.js'))
|
||||
const esm = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'esm', 'browser.js'))
|
||||
const esmMin = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'esm', 'browser.min.js'))
|
||||
const wasm = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'esbuild.wasm'))
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', '..', 'scripts', 'browser', 'index.html'))
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
console.log(`[http] ${req.method} ${req.url}`)
|
||||
|
||||
try {
|
||||
if (req.method === 'GET' && req.url) {
|
||||
const parsed = url.parse(req.url)
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/lib/browser.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(js)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/lib/browser.min.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(jsMin)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/esm/browser.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(esm)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/esm/browser.min.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(esmMin)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/esbuild.wasm') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/wasm' })
|
||||
res.end(wasm)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/scripts/browser/index.html') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.end(html)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/scripts/browser/esbuild.wasm.bagel') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/octet-stream' })
|
||||
res.end(wasm)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(404)
|
||||
res.end('404 Not Found')
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
res.writeHead(500)
|
||||
res.end('500 Internal Server Error')
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
server.listen()
|
||||
const { address, port } = server.address()
|
||||
const serverURL = url.format({ protocol: 'http', hostname: address, port })
|
||||
console.log(`[http] listening on ${serverURL}`)
|
||||
|
||||
async function main() {
|
||||
let allTestsPassed = true
|
||||
try {
|
||||
const browser = await require('puppeteer').launch({
|
||||
// This is here because since December 2024, GitHub changed something about
|
||||
// their CI that causes this error:
|
||||
//
|
||||
// [FATAL:zygote_host_impl_linux.cc(128)] No usable sandbox! If you are running
|
||||
// on Ubuntu 23.10+ or another Linux distro that has disabled unprivileged user
|
||||
// namespaces with AppArmor, see https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md.
|
||||
// Otherwise see https://chromium.googlesource.com/chromium/src/+/main/docs/linux/suid_sandbox_development.md
|
||||
// for more information on developing with the (older) SUID sandbox. If you want
|
||||
// to live dangerously and need an immediate workaround, you can try using
|
||||
// --no-sandbox.
|
||||
args: ['--no-sandbox'],
|
||||
})
|
||||
|
||||
const page = await browser.newPage()
|
||||
page.on('console', obj => {
|
||||
console.log(`[console.${obj.type()}] ${obj.text()}`)
|
||||
})
|
||||
|
||||
page.exposeFunction('testBegin', args => {
|
||||
const config = Object.entries(JSON.parse(args)).map(([k, v]) => `${k}=${v}`).join(', ')
|
||||
console.log(`💬 config: ${config}`)
|
||||
})
|
||||
|
||||
page.exposeFunction('testEnd', args => {
|
||||
if (args === null) console.log(`👍 success`)
|
||||
else {
|
||||
const { test, error } = JSON.parse(args)
|
||||
console.log(`❌ error${test ? ` [${test}]` : ``}: ${error}`)
|
||||
allTestsPassed = false
|
||||
}
|
||||
})
|
||||
|
||||
const testDone = new Promise(resolve => {
|
||||
page.exposeFunction('testDone', resolve)
|
||||
})
|
||||
|
||||
await page.goto(`${serverURL}/scripts/browser/index.html`, { waitUntil: 'domcontentloaded' })
|
||||
await testDone
|
||||
await page.close()
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
catch (e) {
|
||||
allTestsPassed = false
|
||||
console.log(`❌ error: ${e && e.stack || e && e.message || e}`)
|
||||
}
|
||||
|
||||
server.close()
|
||||
|
||||
if (!allTestsPassed) {
|
||||
console.error(`❌ browser test failed`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`✅ browser test passed`)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => setTimeout(() => { throw error }))
|
||||
@@ -0,0 +1 @@
|
||||
../../npm/esbuild-wasm/esbuild.wasm
|
||||
@@ -0,0 +1,417 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<style></style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
async function expectThrownError(fn, err) {
|
||||
try {
|
||||
await fn()
|
||||
throw new Error('Expected an error to be thrown')
|
||||
} catch (e) {
|
||||
assertStrictEqual(e.message, err)
|
||||
}
|
||||
}
|
||||
|
||||
function assertStrictEqual(a, b, message = 'Assertion failed') {
|
||||
if (a !== b) {
|
||||
throw new Error(`${message}:
|
||||
Observed: ${JSON.stringify(a)}
|
||||
Expected: ${JSON.stringify(b)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertSameColorsWithNestingTransform(esbuild, { css, html }) {
|
||||
const crawlColors = node => {
|
||||
const { color } = getComputedStyle(node)
|
||||
const children = Array.from(node.children).map(crawlColors)
|
||||
return children.length ? [color, children] : color
|
||||
}
|
||||
const style = document.querySelector('style')
|
||||
document.body.innerHTML = html
|
||||
style.textContent = css
|
||||
const original = JSON.stringify(crawlColors(document.body))
|
||||
|
||||
// Test minified CSS
|
||||
const minified = await esbuild.transform(css, {
|
||||
loader: 'css',
|
||||
minify: true,
|
||||
})
|
||||
style.textContent = minified.code
|
||||
assertStrictEqual(original, JSON.stringify(crawlColors(document.body)), 'Compare minified CSS')
|
||||
|
||||
// Test lowered CSS
|
||||
const lowered = await esbuild.transform(css, {
|
||||
loader: 'css',
|
||||
supported: { nesting: false },
|
||||
})
|
||||
style.textContent = lowered.code
|
||||
assertStrictEqual(original, JSON.stringify(crawlColors(document.body)), 'Compare lowered CSS')
|
||||
}
|
||||
|
||||
const coreTests = ({ esbuild }) => ({
|
||||
async defaultExport() {
|
||||
assertStrictEqual(typeof esbuild.version, 'string')
|
||||
assertStrictEqual(esbuild.version, esbuild.default.version)
|
||||
assertStrictEqual(esbuild.version, esbuild.default.default.version)
|
||||
assertStrictEqual(esbuild.version, esbuild.default.default.default.version)
|
||||
},
|
||||
|
||||
async transformJS() {
|
||||
const { code } = await esbuild.transform('1+2')
|
||||
assertStrictEqual(code, '1 + 2;\n')
|
||||
},
|
||||
|
||||
async transformTS() {
|
||||
const { code } = await esbuild.transform('1 as any + <any>2', { loader: 'ts' })
|
||||
assertStrictEqual(code, '1 + 2;\n')
|
||||
},
|
||||
|
||||
async transformCSS() {
|
||||
const { code } = await esbuild.transform('div { color: red }', { loader: 'css' })
|
||||
assertStrictEqual(code, 'div {\n color: red;\n}\n')
|
||||
},
|
||||
|
||||
async buildFib() {
|
||||
const fibonacciPlugin = {
|
||||
name: 'fib',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^fib\((\d+)\)/ }, args => {
|
||||
return { path: args.path, namespace: 'fib' }
|
||||
})
|
||||
build.onLoad({ filter: /^fib\((\d+)\)/, namespace: 'fib' }, args => {
|
||||
let match = /^fib\((\d+)\)/.exec(args.path), n = +match[1]
|
||||
let contents = n < 2 ? `export default ${n}` : `
|
||||
import n1 from 'fib(${n - 1}) ${args.path}'
|
||||
import n2 from 'fib(${n - 2}) ${args.path}'
|
||||
export default n1 + n2`
|
||||
return { contents }
|
||||
})
|
||||
},
|
||||
}
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: `
|
||||
import x from 'fib(10)'
|
||||
module.exports = x
|
||||
`,
|
||||
},
|
||||
format: 'cjs',
|
||||
bundle: true,
|
||||
plugins: [fibonacciPlugin],
|
||||
})
|
||||
assertStrictEqual(result.outputFiles.length, 1)
|
||||
assertStrictEqual(result.outputFiles[0].path, '<stdout>')
|
||||
const code = result.outputFiles[0].text
|
||||
const answer = {}
|
||||
new Function('module', code)(answer)
|
||||
assertStrictEqual(answer.exports, 55)
|
||||
},
|
||||
|
||||
async buildRelativeIssue693() {
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: `const x=1`,
|
||||
},
|
||||
write: false,
|
||||
outfile: 'esbuild.js',
|
||||
});
|
||||
assertStrictEqual(result.outputFiles.length, 1)
|
||||
assertStrictEqual(result.outputFiles[0].path, '/esbuild.js')
|
||||
assertStrictEqual(result.outputFiles[0].text, 'const x = 1;\n')
|
||||
},
|
||||
|
||||
async watch() {
|
||||
const context = await esbuild.context({})
|
||||
try {
|
||||
await expectThrownError(context.watch, 'Cannot use the "watch" API in this environment')
|
||||
} finally {
|
||||
context.dispose()
|
||||
}
|
||||
},
|
||||
|
||||
async serve() {
|
||||
const context = await esbuild.context({})
|
||||
try {
|
||||
await expectThrownError(context.serve, 'Cannot use the "serve" API in this environment')
|
||||
} finally {
|
||||
context.dispose()
|
||||
}
|
||||
},
|
||||
|
||||
async esbuildBuildSync() {
|
||||
await expectThrownError(esbuild.buildSync, 'The "buildSync" API only works in node')
|
||||
},
|
||||
|
||||
async esbuildTransformSync() {
|
||||
await expectThrownError(esbuild.transformSync, 'The "transformSync" API only works in node')
|
||||
},
|
||||
})
|
||||
|
||||
function setupForProblemCSS(prefix) {
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/2889
|
||||
const original = `
|
||||
/* Variant 1 */
|
||||
.${prefix}-v1 { --a: ; --b: ; max-width: var(--a) var(--b); }
|
||||
.${prefix}-a { --a: 1px; }
|
||||
.${prefix}-b { --b: 2px; }
|
||||
|
||||
/* Variant 2 */
|
||||
.${prefix}-v2 { max-width: var(--a, ) var(--b, ); }
|
||||
.${prefix}-a { --a: 1px; }
|
||||
.${prefix}-b { --b: 2px; }
|
||||
`
|
||||
const style = document.querySelector('style')
|
||||
const test1a = document.createElement('div')
|
||||
const test1b = document.createElement('div')
|
||||
const test2a = document.createElement('div')
|
||||
const test2b = document.createElement('div')
|
||||
test1a.className = `${prefix}-v1 ${prefix}-a`
|
||||
test1b.className = `${prefix}-v1 ${prefix}-b`
|
||||
test2a.className = `${prefix}-v2 ${prefix}-a`
|
||||
test2b.className = `${prefix}-v2 ${prefix}-b`
|
||||
return [original, css => {
|
||||
style.textContent = css
|
||||
document.body.innerHTML = ''
|
||||
document.body.appendChild(test1a)
|
||||
document.body.appendChild(test1b)
|
||||
document.body.appendChild(test2a)
|
||||
document.body.appendChild(test2b)
|
||||
assertStrictEqual(getComputedStyle(test1a).maxWidth, `1px`)
|
||||
assertStrictEqual(getComputedStyle(test1b).maxWidth, `2px`)
|
||||
assertStrictEqual(getComputedStyle(test2a).maxWidth, `1px`)
|
||||
assertStrictEqual(getComputedStyle(test2b).maxWidth, `2px`)
|
||||
}]
|
||||
}
|
||||
|
||||
const cssTests = ({ esbuild }) => ({
|
||||
async problemCSSOriginal() {
|
||||
const [original, runAsserts] = setupForProblemCSS('original')
|
||||
runAsserts(original)
|
||||
},
|
||||
|
||||
async problemCSSPrettyPrinted() {
|
||||
const [original, runAsserts] = setupForProblemCSS('pretty-print')
|
||||
const { code: prettyPrinted } = await esbuild.transform(original, { loader: 'css' })
|
||||
runAsserts(prettyPrinted)
|
||||
},
|
||||
|
||||
async problemCSSMinified() {
|
||||
const [original, runAsserts] = setupForProblemCSS('pretty-print')
|
||||
const { code: minified } = await esbuild.transform(original, { loader: 'css', minify: true })
|
||||
runAsserts(minified)
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/3877
|
||||
async cssNestingIssue3877() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.a .b:has(> span) {
|
||||
.a & span {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
<span>1</span>
|
||||
<div><span>2</span></div>
|
||||
</div>
|
||||
<div class="b">
|
||||
<div><span>3</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/3877#issuecomment-2631385559
|
||||
async cssNestingIssue3877Comment2631385559() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.a {
|
||||
:has(>&) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="a">a</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/3997
|
||||
async cssNestingIssue3997() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.foo {
|
||||
color: blue;
|
||||
&& { color: red; }
|
||||
& { color: green; }
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="foo">x</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/4005
|
||||
async cssNestingIssue4005() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.foo {
|
||||
:where(& > .bar) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="foo">
|
||||
<div class="bar">bar</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/pull/4037
|
||||
async cssNestingIssue4037() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.parent {
|
||||
> .a,
|
||||
> .b1 > .b2 {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="parent">
|
||||
<div class="a">a</div>
|
||||
<div class="b1">
|
||||
<div class="b2">b2</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
async function runTest(test, fn) {
|
||||
try {
|
||||
await fn()
|
||||
} catch (e) {
|
||||
e.test = test
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function loadScript(url) {
|
||||
const tag = document.createElement('script')
|
||||
document.head.appendChild(tag)
|
||||
await new Promise((resolve, reject) => {
|
||||
tag.onload = resolve
|
||||
tag.onerror = () => reject(new Error('Failed to load script: ' + url))
|
||||
tag.src = url
|
||||
})
|
||||
const esbuild = window.esbuild
|
||||
delete window.esbuild
|
||||
return esbuild
|
||||
}
|
||||
|
||||
async function testStart() {
|
||||
let allTestsPassed = true
|
||||
|
||||
if (!window.testBegin) window.testBegin = args => {
|
||||
const config = Object.entries(JSON.parse(args)).map(([k, v]) => `${k}=${v}`).join(', ')
|
||||
console.log(`💬 config: ${config}`)
|
||||
}
|
||||
|
||||
if (!window.testEnd) window.testEnd = args => {
|
||||
if (args === null) console.log(`👍 success`)
|
||||
else {
|
||||
const { test, stack, error } = JSON.parse(args)
|
||||
console.log(`❌ error${test ? ` [${test}]` : ``}: ${error}`)
|
||||
allTestsPassed = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.testDone) window.testDone = error => {
|
||||
console.log(allTestsPassed ? `✅ done` : `❌ done`)
|
||||
}
|
||||
|
||||
// Just run CSS tests through a single configuration, but run each test separately
|
||||
{
|
||||
const url = '/npm/esbuild-wasm/esbuild.wasm?' + Math.random()
|
||||
const initializePromise = import('/npm/esbuild-wasm/esm/browser.js?' + Math.random())
|
||||
.then(esbuild => esbuild.initialize({ wasmURL: new URL(url, location.href) })
|
||||
.then(() => esbuild))
|
||||
const tests = cssTests({ esbuild: await initializePromise.catch(() => null) })
|
||||
for (const test in tests) {
|
||||
try {
|
||||
testBegin(JSON.stringify({ type: 'css', test }))
|
||||
await initializePromise
|
||||
await runTest(test, tests[test])
|
||||
testEnd(null)
|
||||
} catch (e) {
|
||||
testEnd(JSON.stringify({
|
||||
test: e.test || null,
|
||||
stack: e.stack || null,
|
||||
error: (e && e.message || e) + '',
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run all core tests through every configuration, but run all tests together (stop after one failure)
|
||||
for (const esm of [false, true]) {
|
||||
for (const min of [false, true]) {
|
||||
for (const worker of [false, true]) {
|
||||
for (const mime of ['correct', 'incorrect']) {
|
||||
for (const approach of ['string', 'url', 'module']) {
|
||||
try {
|
||||
testBegin(JSON.stringify({ esm, min, worker, mime, approach }))
|
||||
const esbuild = esm
|
||||
? await import('/npm/esbuild-wasm/esm/browser' + (min ? '.min' : '') + '.js?' + Math.random())
|
||||
: await loadScript('/npm/esbuild-wasm/lib/browser' + (min ? '.min' : '') + '.js?' + Math.random())
|
||||
const url = mime === 'correct' ? '/npm/esbuild-wasm/esbuild.wasm' : '/scripts/browser/esbuild.wasm.bagel'
|
||||
const initializePromise = {
|
||||
string: () => esbuild.initialize({ wasmURL: url, worker }),
|
||||
url: () => esbuild.initialize({ wasmURL: new URL(url, location.href), worker }),
|
||||
module: () => fetch(url)
|
||||
.then(r => r.arrayBuffer())
|
||||
.then(bytes => WebAssembly.compile(bytes))
|
||||
.then(module => esbuild.initialize({ wasmModule: module, worker })),
|
||||
}[approach]()
|
||||
await initializePromise
|
||||
const tests = coreTests({ esbuild })
|
||||
const promises = []
|
||||
for (const test in tests) promises.push(runTest(test, tests[test]))
|
||||
await Promise.all(promises)
|
||||
testEnd(null)
|
||||
} catch (e) {
|
||||
testEnd(JSON.stringify({
|
||||
test: e.test || null,
|
||||
stack: e.stack || null,
|
||||
error: (e && e.message || e) + '',
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testDone()
|
||||
}
|
||||
|
||||
testStart()
|
||||
|
||||
</script>
|
||||
+1918
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"puppeteer": "23.11.1"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user