mirror of
https://github.com/apache/superset.git
synced 2026-05-22 00:05:15 +00:00
oxlint 1.66 (bumped in #40318) added support for two ESLint rules that were previously listed as not-implemented: - import/newline-after-import -> enabled as "error" (3 violations, auto-fixed via `--fix`) - react/no-unstable-nested-components -> enabled as "warn" with a TODO to graduate to "error" after a cleanup pass (~150 violations that require hoisting nested component definitions out of their parent render functions) Also dropped these two rules from the "not implemented in oxlint" comment block since they are implemented now. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
217 lines
6.8 KiB
JavaScript
217 lines
6.8 KiB
JavaScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
const zlib = require('zlib');
|
|
const { ZSTDDecompress } = require('simple-zstd');
|
|
|
|
const yargs = require('yargs');
|
|
const { hideBin } = require('yargs/helpers');
|
|
|
|
const parsedArgs = yargs(hideBin(process.argv)).parse();
|
|
|
|
const parsedEnvArg = () => {
|
|
let envArgs = {};
|
|
if (parsedArgs.env) {
|
|
envArgs = yargs(parsedArgs.env).argv;
|
|
}
|
|
return { ...process.env, ...envArgs };
|
|
};
|
|
const { supersetPort = 8088, superset: supersetUrl = null } = parsedEnvArg();
|
|
const backend = (supersetUrl || `http://localhost:${supersetPort}`).replace(
|
|
'//+$/',
|
|
'',
|
|
); // strip ending backslash
|
|
|
|
let manifest;
|
|
function isHTML(res) {
|
|
const CONTENT_TYPE_HEADER = 'content-type';
|
|
const contentType = res.getHeader
|
|
? res.getHeader(CONTENT_TYPE_HEADER)
|
|
: res.headers[CONTENT_TYPE_HEADER];
|
|
return contentType.includes('text/html');
|
|
}
|
|
|
|
function toDevHTML(originalHtml) {
|
|
let html = originalHtml.replace(
|
|
/(<head>\s*<title>)([\s\S]*)(<\/title>)/i,
|
|
'$1[DEV] $2 $3',
|
|
);
|
|
|
|
if (manifest) {
|
|
const loaded = new Set();
|
|
// replace bundled asset files, HTML comment tags generated by Jinja macros
|
|
// in superset/templates/superset/partials/asset_bundle.html
|
|
html = html.replace(
|
|
/<!-- Bundle (css|js) (.*?) START -->[\s\S]*?<!-- Bundle \1 \2 END -->/gi,
|
|
(match, assetType, bundleName) => {
|
|
if (bundleName in manifest.entrypoints) {
|
|
return `<!-- DEV bundle: ${bundleName} ${assetType} START -->\n ${(
|
|
manifest.entrypoints[bundleName][assetType] || []
|
|
)
|
|
.filter(chunkFilePath => {
|
|
if (loaded.has(chunkFilePath)) {
|
|
return false;
|
|
}
|
|
loaded.add(chunkFilePath);
|
|
return true;
|
|
})
|
|
.map(chunkFilePath =>
|
|
assetType === 'css'
|
|
? `<link rel="stylesheet" type="text/css" href="${chunkFilePath}" />`
|
|
: `<script src="${chunkFilePath}"></script>`,
|
|
)
|
|
.join(
|
|
'\n ',
|
|
)}\n <!-- DEV bundle: ${bundleName} ${assetType} END -->`;
|
|
}
|
|
return match;
|
|
},
|
|
);
|
|
}
|
|
return html;
|
|
}
|
|
|
|
function copyHeaders(originalResponse, response) {
|
|
response.statusCode = originalResponse.statusCode;
|
|
response.statusMessage = originalResponse.statusMessage;
|
|
if (response.setHeader) {
|
|
let keys = Object.keys(originalResponse.headers);
|
|
if (isHTML(originalResponse)) {
|
|
keys = keys.filter(
|
|
key => key !== 'content-encoding' && key !== 'content-length',
|
|
);
|
|
}
|
|
keys.forEach(key => {
|
|
let value = originalResponse.headers[key];
|
|
if (key === 'set-cookie') {
|
|
// remove cookie domain
|
|
value = Array.isArray(value) ? value : [value];
|
|
value = value.map(x => x.replace(/Domain=[^;]+?/i, ''));
|
|
} else if (key === 'location') {
|
|
// set redirects to use local URL
|
|
value = (value || '').replace(backend, '');
|
|
}
|
|
response.setHeader(key, value);
|
|
});
|
|
} else {
|
|
response.headers = originalResponse.headers;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manipulate HTML server response to replace asset files with
|
|
* local webpack-dev-server build.
|
|
*/
|
|
function processHTML(proxyResponse, response) {
|
|
let body = Buffer.from([]);
|
|
let originalResponse = proxyResponse;
|
|
let uncompress;
|
|
const responseEncoding = originalResponse.headers['content-encoding'];
|
|
|
|
// decode GZIP response
|
|
if (responseEncoding === 'gzip') {
|
|
uncompress = zlib.createGunzip();
|
|
} else if (responseEncoding === 'br') {
|
|
uncompress = zlib.createBrotliDecompress();
|
|
} else if (responseEncoding === 'deflate') {
|
|
uncompress = zlib.createInflate();
|
|
} else if (responseEncoding === 'zstd') {
|
|
uncompress = ZSTDDecompress();
|
|
}
|
|
if (uncompress) {
|
|
originalResponse.pipe(uncompress);
|
|
originalResponse = uncompress;
|
|
}
|
|
|
|
originalResponse
|
|
.on('data', data => {
|
|
body = Buffer.concat([body, data]);
|
|
})
|
|
.on('error', error => {
|
|
// eslint-disable-next-line no-console
|
|
console.error(error);
|
|
response.end(`Error fetching proxied request: ${error.message}`);
|
|
})
|
|
.on('end', () => {
|
|
response.end(toDevHTML(body.toString()));
|
|
});
|
|
}
|
|
|
|
module.exports = newManifest => {
|
|
manifest = newManifest;
|
|
return {
|
|
context: path => {
|
|
// Don't proxy hot update files - webpack-dev-server needs to serve these directly for HMR
|
|
if (path.includes('.hot-update.')) {
|
|
return false;
|
|
}
|
|
// Don't proxy WebSocket connections for HMR
|
|
if (path === '/ws') {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
target: backend,
|
|
hostRewrite: true,
|
|
changeOrigin: true,
|
|
cookieDomainRewrite: '', // remove cookie domain
|
|
selfHandleResponse: true, // so that the onProxyRes takes care of sending the response
|
|
onProxyRes(proxyResponse, request, response) {
|
|
try {
|
|
copyHeaders(proxyResponse, response);
|
|
if (isHTML(response)) {
|
|
// For HTML responses, flush headers before processing starts
|
|
// processHTML sets up async handlers that will call response.end()
|
|
response.flushHeaders();
|
|
processHTML(proxyResponse, response);
|
|
} else {
|
|
const isCSV = (proxyResponse.headers['content-type'] || '').includes(
|
|
'text/csv',
|
|
);
|
|
|
|
if (isCSV) {
|
|
response.flushHeaders();
|
|
proxyResponse.on('data', chunk => {
|
|
response.write(chunk);
|
|
if (response.flush) {
|
|
response.flush();
|
|
}
|
|
});
|
|
proxyResponse.on('end', () => {
|
|
response.end();
|
|
});
|
|
proxyResponse.on('error', () => {
|
|
response.end();
|
|
});
|
|
} else {
|
|
response.flushHeaders();
|
|
proxyResponse.pipe(response);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Only try to set headers if they haven't been sent yet
|
|
if (!response.headersSent) {
|
|
response.setHeader('content-type', 'text/plain');
|
|
}
|
|
response.write(`Error requesting ${request.path} from proxy:\n\n`);
|
|
response.end(e.stack);
|
|
}
|
|
},
|
|
};
|
|
};
|