Files
superset2/superset-frontend/webpack.proxy-config.js
Claude 6eec5f192b chore(oxlint): enable import/newline-after-import + react/no-unstable-nested-components
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>
2026-05-20 22:48:05 -07:00

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);
}
},
};
};