mirror of
https://github.com/apache/superset.git
synced 2026-09-09 08:44:32 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd489851fc | ||
|
|
3be843a5bf | ||
|
|
e62265c795 | ||
|
|
4309b758ce | ||
|
|
9db1bb8933 | ||
|
|
2d0603d543 | ||
|
|
daed810eff | ||
|
|
b1940c8108 | ||
|
|
2871895dc1 | ||
|
|
e60dd25ced | ||
|
|
93bebdc957 |
@@ -101,7 +101,15 @@ export default {
|
||||
...config,
|
||||
module: {
|
||||
...config.module,
|
||||
rules: disableDevModeInRules(customConfig.module.rules),
|
||||
rules: [
|
||||
...(config.module?.rules ?? []).filter(
|
||||
rule => {
|
||||
const test = rule.test?.toString() ?? '';
|
||||
return test !== '/\\.css$/' && !test.includes('svg');
|
||||
},
|
||||
),
|
||||
...disableDevModeInRules(customConfig.module.rules),
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const packageConfig = require('./package');
|
||||
import packageConfig from './package.json' with { type: 'json' };
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
sourceMaps: true,
|
||||
sourceType: 'module',
|
||||
retainLines: true,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
header: `<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
|
||||
@@ -134,7 +134,7 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
messageId: 'eager',
|
||||
data: { property: keyName, fn: callee.callee.name },
|
||||
fix(fixer) {
|
||||
const source = context.getSourceCode().getText(prop.value);
|
||||
const source = context.sourceCode.getText(prop.value);
|
||||
return fixer.replaceText(prop.value, `() => ${source}`);
|
||||
},
|
||||
});
|
||||
@@ -229,4 +229,4 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
export default plugin;
|
||||
|
||||
+3
-5
@@ -16,14 +16,12 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type { Rule } from 'eslint';
|
||||
|
||||
const { RuleTester } = require('eslint');
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
|
||||
import { type Rule, RuleTester } from 'eslint';
|
||||
import plugin from '.';
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: { ecmaVersion: 2020, sourceType: 'module' },
|
||||
});
|
||||
} as any);
|
||||
const rule: Rule.RuleModule = plugin.rules['no-eager-t-in-config'];
|
||||
|
||||
ruleTester.run('no-eager-t-in-config', rule, {
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
* @author Apache
|
||||
*/
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import type { Rule } from 'eslint';
|
||||
|
||||
const { RuleTester } = require('eslint');
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
|
||||
import { type Rule, RuleTester } from 'eslint';
|
||||
import plugin from '.';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"keywords": [],
|
||||
"license": "Apache-2.0",
|
||||
"author": "Apache",
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
||||
@@ -44,11 +44,11 @@ interface JSXElementNode {
|
||||
openingElement: JSXOpeningElement;
|
||||
}
|
||||
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
const plugin = {
|
||||
rules: {
|
||||
'no-fa-icons-usage': {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
type: 'problem' as const,
|
||||
docs: {
|
||||
description:
|
||||
'Disallow the usage of FontAwesome icons in the codebase',
|
||||
@@ -91,4 +91,4 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
export default plugin;
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
* @author Apache
|
||||
*/
|
||||
|
||||
import type { Rule } from 'eslint';
|
||||
|
||||
const { RuleTester } = require('eslint');
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
|
||||
import { RuleTester } from 'eslint';
|
||||
import plugin from '.';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
@@ -36,7 +34,7 @@ const ruleTester = new RuleTester({
|
||||
parserOptions: { ecmaFeatures: { jsx: true } },
|
||||
},
|
||||
});
|
||||
const rule: Rule.RuleModule = plugin.rules['no-fa-icons-usage'];
|
||||
const rule = plugin.rules['no-fa-icons-usage'];
|
||||
|
||||
const errors: Array<{ message: string }> = [
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"keywords": [],
|
||||
"license": "Apache-2.0",
|
||||
"author": "Apache",
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
||||
@@ -159,4 +159,4 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
export default plugin;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"keywords": [],
|
||||
"license": "Apache-2.0",
|
||||
"author": "Apache",
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
||||
@@ -34,14 +34,14 @@
|
||||
|
||||
// Register the TypeScript require hook so ESLint can load the .ts plugin files
|
||||
// from eslint-rules/*.
|
||||
require('tsx/cjs');
|
||||
import 'tsx/cjs';
|
||||
|
||||
const tsParser = require('@typescript-eslint/parser');
|
||||
const themeColorsPlugin = require('@superset-ui/eslint-plugin-theme-colors');
|
||||
const iconsPlugin = require('@superset-ui/eslint-plugin-icons');
|
||||
const i18nStringsPlugin = require('@superset-ui/eslint-plugin-i18n-strings');
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import themeColorsPlugin from '@superset-ui/eslint-plugin-theme-colors';
|
||||
import iconsPlugin from '@superset-ui/eslint-plugin-icons';
|
||||
import i18nStringsPlugin from '@superset-ui/eslint-plugin-i18n-strings';
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
// Files this config applies to. Flat config has no `--ext`; globs live here.
|
||||
// Only check src/ files where the theme/icon/i18n rules matter.
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ if (!process.env.CI) {
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
// [/\\] matches both path separators so the suite also collects on
|
||||
// native Windows, where jest hands the regex backslash-separated paths.
|
||||
testRegex:
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"plugins/*",
|
||||
"src/setup/*"
|
||||
],
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"_format": "oxfmt './{src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.scss,.sass,.json}'",
|
||||
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --color --mode production",
|
||||
@@ -82,7 +83,7 @@
|
||||
"prune": "rm -rf ./{packages,plugins}/*/{node_modules,lib,esm,tsconfig.tsbuildinfo,package-lock.json} ./.temp_cache ./coverage ./storybook-static",
|
||||
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development storybook dev -p 6006",
|
||||
"test-storybook": "test-storybook",
|
||||
"test-storybook:ci": "concurrently --kill-others --success first --names \"SB,TEST\" --prefix-colors \"magenta,blue\" \"python3 -m http.server 6006 --directory storybook-static\" \"npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook -- --maxWorkers=2\"",
|
||||
"test-storybook:ci": "concurrently --kill-others --success first --names \"SB,TEST\" --prefix-colors \"magenta,blue\" \"python3 -m http.server 6006 --directory storybook-static\" \"npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook -- --maxWorkers=2 --testTimeout 60000\"",
|
||||
"tdd": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --watch",
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
|
||||
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
|
||||
|
||||
+1
@@ -17,4 +17,5 @@ config.plugins = [
|
||||
['babel-plugin-typescript-to-proptypes', { loose: true }],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
+1
@@ -16,4 +16,5 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
module.exports = 'test-file-stub';
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
module.exports = {};
|
||||
|
||||
export default {};
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
module.exports = 'test-file-stub';
|
||||
|
||||
export default 'test-file-stub';
|
||||
|
||||
@@ -39,6 +39,9 @@ import path from 'path';
|
||||
import { Page, test, expect } from '@playwright/test';
|
||||
import { URL } from '../../utils/urls';
|
||||
import { apiDelete, apiGet } from '../../helpers/api/requests';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const DOCS_STATIC = path.resolve(__dirname, '../../../../docs/static/img');
|
||||
const SCREENSHOTS_DIR = path.join(DOCS_STATIC, 'screenshots');
|
||||
|
||||
@@ -38,6 +38,9 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Page, test, expect } from '@playwright/test';
|
||||
import { URL } from '../../utils/urls';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const MOBILE_SCREENSHOTS_DIR = path.resolve(
|
||||
__dirname,
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
import path from 'path';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const serverURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8088';
|
||||
const baseURL = serverURL.endsWith('/') ? serverURL : `${serverURL}/`;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
|
||||
import { createServer, IncomingMessage, ServerResponse, Server } from 'http';
|
||||
import { AddressInfo, Socket } from 'net';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { dirname, join } from 'path';
|
||||
import {
|
||||
apiEnableEmbedding,
|
||||
getAccessToken,
|
||||
@@ -29,6 +29,9 @@ import {
|
||||
} from '../../helpers/api/embedded';
|
||||
import { getDashboardBySlug } from '../../helpers/api/dashboard';
|
||||
import { EmbeddedPage } from '../../pages/EmbeddedPage';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* Superset domain (Flask server) — set by CI or defaults to local dev
|
||||
|
||||
@@ -51,7 +51,7 @@ import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
|
||||
import { createServer, IncomingMessage, ServerResponse, Server } from 'http';
|
||||
import { AddressInfo, Socket } from 'net';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { dirname, join } from 'path';
|
||||
import {
|
||||
apiEnableEmbedding,
|
||||
getAccessToken,
|
||||
@@ -66,6 +66,7 @@ import {
|
||||
import { apiDeleteChart } from '../../helpers/api/chart';
|
||||
import { EmbeddedPage } from '../../pages/EmbeddedPage';
|
||||
import { EMBEDDED } from '../../utils/constants';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const SUPERSET_DOMAIN = (() => {
|
||||
const url = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8088';
|
||||
@@ -75,6 +76,8 @@ const SUPERSET_BASE_URL = SUPERSET_DOMAIN.endsWith('/')
|
||||
? SUPERSET_DOMAIN
|
||||
: `${SUPERSET_DOMAIN}/`;
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const SDK_BUNDLE_PATH = join(
|
||||
__dirname,
|
||||
'../../../../superset-embedded-sdk/bundle/index.js',
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
/**
|
||||
* Build packages/plugins filtered by globs
|
||||
*/
|
||||
process.env.PATH = `./node_modules/.bin:${process.env.PATH}`;
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
const fastGlob = require('fast-glob');
|
||||
const yargs = require('yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fastGlob from 'fast-glob';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
process.env.PATH = `./node_modules/.bin:${process.env.PATH}`;
|
||||
|
||||
const { globs } = yargs(hideBin(process.argv)).parse();
|
||||
const glob = globs?.length > 1 ? `{${globs.join(',')}}` : globs?.[0] || '*';
|
||||
|
||||
@@ -26,61 +26,11 @@
|
||||
//
|
||||
// Usage: node scripts/bundle-size-summary.js <path-to-stats.json>
|
||||
|
||||
const fs = require('fs');
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { main } from './internal/bundle-size-summary.js';
|
||||
|
||||
// Entrypoints worth tracking: the two user-facing app shells. `menu`,
|
||||
// `preamble`, `theme`, and `service-worker` are small, low-variance
|
||||
// infrastructure chunks, not where bundle bloat actually shows up.
|
||||
const TRACKED_ENTRYPOINTS = ['spa', 'embedded'];
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
function entrypointSizeByExt(entrypoint, ext) {
|
||||
return (entrypoint.assets || [])
|
||||
.filter(asset => asset.name.endsWith(ext))
|
||||
.reduce((total, asset) => total + asset.size, 0);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const statsPath = process.argv[2];
|
||||
if (!statsPath) {
|
||||
console.error('Usage: bundle-size-summary.js <path-to-stats.json>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
|
||||
const { entrypoints } = stats;
|
||||
if (!entrypoints) {
|
||||
console.error(
|
||||
'stats.json has no `entrypoints` key -- was it built with ' +
|
||||
'`BUNDLE_SIZE_STATS=true` set? Without it, webpack.config.js uses ' +
|
||||
'`stats: "minimal"`, which omits `entrypoints`.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
TRACKED_ENTRYPOINTS.forEach(name => {
|
||||
const entrypoint = entrypoints[name];
|
||||
if (!entrypoint) {
|
||||
console.error(`stats.json is missing the "${name}" entrypoint`);
|
||||
process.exit(1);
|
||||
}
|
||||
results.push({
|
||||
name: `${name} entrypoint (JS)`,
|
||||
unit: 'bytes',
|
||||
value: entrypointSizeByExt(entrypoint, '.js'),
|
||||
});
|
||||
results.push({
|
||||
name: `${name} entrypoint (CSS)`,
|
||||
unit: 'bytes',
|
||||
value: entrypointSizeByExt(entrypoint, '.css'),
|
||||
});
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
if (__filename === process.argv[1]) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { entrypointSizeByExt, main, TRACKED_ENTRYPOINTS };
|
||||
|
||||
@@ -23,11 +23,16 @@
|
||||
* Runs as a separate check without needing custom binaries
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const parser = require('@babel/parser');
|
||||
const traverse = require('@babel/traverse').default;
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import glob from 'glob';
|
||||
import * as parser from '@babel/parser';
|
||||
import traverseModule from '@babel/traverse';
|
||||
|
||||
const traverse = traverseModule.default;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// ANSI color codes
|
||||
const RED = '\x1B[31m';
|
||||
@@ -779,11 +784,11 @@ function main() {
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
if (__filename === process.argv[1]) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
checkNoLiteralColors,
|
||||
checkNoFaIcons,
|
||||
checkI18nTemplates,
|
||||
|
||||
@@ -21,10 +21,13 @@
|
||||
// This script checks that UI components and plugins have corresponding
|
||||
// Storybook story files. Run with --fix to see suggestions.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import glob from 'glob';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
// Directories to check for storybook coverage
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
/**
|
||||
* Check commit messages only for the first commit in branch.
|
||||
*/
|
||||
const { execSync, spawnSync } = require('child_process');
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
|
||||
const envVariable = process.argv[2] || 'GIT_PARAMS';
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
const { sync } = require('fast-glob');
|
||||
const { copy } = require('fs-extra');
|
||||
import { sync } from 'fast-glob';
|
||||
import { copy } from 'fs-extra';
|
||||
|
||||
const pkgGlob = process.argv[2] || '*';
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Reduces a webpack `--json` stats file down to the handful of headline
|
||||
// numbers worth tracking over time, in the flat array format
|
||||
// benchmark-action/github-action-benchmark expects for its
|
||||
// "customSmallerIsBetter" tool. The full stats file also includes a
|
||||
// `modules`/`chunks` graph across ~15k modules, which is enormous and not
|
||||
// useful for this purpose, so we only ever read `entrypoints`.
|
||||
//
|
||||
// Usage: node scripts/bundle-size-summary.js <path-to-stats.json>
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
// Entrypoints worth tracking: the two user-facing app shells. `menu`,
|
||||
// `preamble`, `theme`, and `service-worker` are small, low-variance
|
||||
// infrastructure chunks, not where bundle bloat actually shows up.
|
||||
const TRACKED_ENTRYPOINTS = ['spa', 'embedded'];
|
||||
|
||||
function entrypointSizeByExt(entrypoint, ext) {
|
||||
return (entrypoint.assets || [])
|
||||
.filter(asset => asset.name.endsWith(ext))
|
||||
.reduce((total, asset) => total + asset.size, 0);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const statsPath = process.argv[2];
|
||||
if (!statsPath) {
|
||||
console.error('Usage: bundle-size-summary.js <path-to-stats.json>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
|
||||
const { entrypoints } = stats;
|
||||
if (!entrypoints) {
|
||||
console.error(
|
||||
'stats.json has no `entrypoints` key -- was it built with ' +
|
||||
'`BUNDLE_SIZE_STATS=true` set? Without it, webpack.config.js uses ' +
|
||||
'`stats: "minimal"`, which omits `entrypoints`.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
TRACKED_ENTRYPOINTS.forEach(name => {
|
||||
const entrypoint = entrypoints[name];
|
||||
if (!entrypoint) {
|
||||
console.error(`stats.json is missing the "${name}" entrypoint`);
|
||||
process.exit(1);
|
||||
}
|
||||
results.push({
|
||||
name: `${name} entrypoint (JS)`,
|
||||
unit: 'bytes',
|
||||
value: entrypointSizeByExt(entrypoint, '.js'),
|
||||
});
|
||||
results.push({
|
||||
name: `${name} entrypoint (CSS)`,
|
||||
unit: 'bytes',
|
||||
value: entrypointSizeByExt(entrypoint, '.css'),
|
||||
});
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
export { entrypointSizeByExt, main, TRACKED_ENTRYPOINTS };
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { execSync } from 'node:child_process';
|
||||
import { GoogleAuth } from 'google-auth-library';
|
||||
import googleSheets from '@googleapis/sheets';
|
||||
|
||||
const { SPREADSHEET_ID } = process.env;
|
||||
const SERVICE_ACCOUNT_KEY = JSON.parse(process.env.SERVICE_ACCOUNT_KEY || '{}');
|
||||
|
||||
// Only set up Google Sheets if we have credentials
|
||||
let sheets;
|
||||
if (SERVICE_ACCOUNT_KEY.client_email) {
|
||||
const auth = new GoogleAuth({
|
||||
credentials: SERVICE_ACCOUNT_KEY,
|
||||
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
||||
});
|
||||
sheets = googleSheets.sheets({ version: 'v4', auth });
|
||||
}
|
||||
|
||||
const DATETIME = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
||||
|
||||
/**
|
||||
* Turn an oxlint diagnostic code into the canonical rule id used by the metrics
|
||||
* series.
|
||||
*
|
||||
* oxlint reports `<plugin>(<rule>)`, where the plugin is the linter the rule came
|
||||
* from: `eslint(no-console)`, `react-hooks(exhaustive-deps)`, `react(jsx-key)`,
|
||||
* `jest(no-conditional-expect)`, `oxc(erasing-op)`, and the legacy
|
||||
* `eslint-plugin-unicorn(no-new-array)` spelling.
|
||||
*
|
||||
* `eslint` is the implicit namespace, so its rules keep their bare name and stay
|
||||
* comparable with the rows recorded before the oxlint migration. Every other
|
||||
* plugin becomes `<plugin>/<rule>`, which is the id those rules are known by in
|
||||
* config and in the pre-migration history.
|
||||
*
|
||||
* @param {string | undefined} code the diagnostic's `code` field
|
||||
* @returns {string} the rule id to record
|
||||
*/
|
||||
function parseRuleId(code) {
|
||||
if (!code) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const match = code.match(/^([\w-]+)\(([^)]+)\)$/);
|
||||
if (!match) {
|
||||
return code;
|
||||
}
|
||||
|
||||
const [, namespace, rule] = match;
|
||||
if (namespace === 'eslint') {
|
||||
return rule;
|
||||
}
|
||||
|
||||
// `eslint-plugin-unicorn(...)` is the same rule as `unicorn/...`
|
||||
const plugin = namespace.replace(/^eslint-plugin-/, '');
|
||||
return `${plugin}/${rule}`;
|
||||
}
|
||||
|
||||
async function writeToGoogleSheet(data, range, headers, append = false) {
|
||||
if (!sheets) {
|
||||
console.log('No Google Sheets credentials, skipping upload');
|
||||
return;
|
||||
}
|
||||
|
||||
const request = {
|
||||
spreadsheetId: SPREADSHEET_ID,
|
||||
range,
|
||||
valueInputOption: 'USER_ENTERED',
|
||||
resource: { values: append ? data : [headers, ...data] },
|
||||
};
|
||||
|
||||
const method = append ? 'append' : 'update';
|
||||
await sheets.spreadsheets.values[method](request);
|
||||
}
|
||||
|
||||
// Run OXC and get JSON output
|
||||
async function runOxlintAndProcess() {
|
||||
const enrichedRules = {
|
||||
'react-prefer-function-component/react-prefer-function-component': {
|
||||
description: 'We prefer function components to class-based components',
|
||||
},
|
||||
'react/jsx-filename-extension': {
|
||||
description:
|
||||
'We prefer Typescript - all JSX files should be converted to TSX',
|
||||
},
|
||||
'react/forbid-component-props': {
|
||||
description:
|
||||
'We prefer Emotion for styling rather than `className` or `style` props',
|
||||
},
|
||||
'no-restricted-imports': {
|
||||
description:
|
||||
"This rule catches several things that shouldn't be used anymore. LESS, antD, etc. See individual occurrence messages for details",
|
||||
},
|
||||
'no-console': {
|
||||
description:
|
||||
"We don't want a bunch of console noise, but you can use the `logger` from `@superset-ui/core` when there's a reason to.",
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
// Run OXC with JSON format
|
||||
console.log('Running OXC linter...');
|
||||
// `oxlint.json` is not the `.oxlintrc.json` oxlint auto-discovers, so the
|
||||
// config has to be passed explicitly or the run reports oxlint's defaults
|
||||
// instead of the project's ruleset. Matches the `lint` scripts in
|
||||
// package.json.
|
||||
const oxlintOutput = execSync(
|
||||
'npx oxlint --config oxlint.json --format json',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large outputs
|
||||
stdio: ['pipe', 'pipe', 'ignore'], // Ignore stderr to avoid error output
|
||||
},
|
||||
);
|
||||
|
||||
const results = JSON.parse(oxlintOutput);
|
||||
|
||||
// Process OXC JSON output
|
||||
const metricsByRule = {};
|
||||
let occurrencesData = [];
|
||||
|
||||
// OXC JSON format has diagnostics array
|
||||
if (results.diagnostics && Array.isArray(results.diagnostics)) {
|
||||
results.diagnostics.forEach(diagnostic => {
|
||||
const ruleId = parseRuleId(diagnostic.code);
|
||||
|
||||
const file = diagnostic.filename || 'unknown';
|
||||
const line = diagnostic.labels?.[0]?.span?.line || 0;
|
||||
const column = diagnostic.labels?.[0]?.span?.column || 0;
|
||||
const message = diagnostic.message || '';
|
||||
|
||||
const ruleData = metricsByRule[ruleId] || { count: 0 };
|
||||
ruleData.count += 1;
|
||||
metricsByRule[ruleId] = ruleData;
|
||||
|
||||
occurrencesData.push({
|
||||
rule: ruleId,
|
||||
message,
|
||||
file,
|
||||
line,
|
||||
column,
|
||||
ts: DATETIME,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
`OXC found ${results.diagnostics?.length || 0} issues across ${results.number_of_files} files`,
|
||||
);
|
||||
|
||||
// Also run minimal ESLint for custom rules and merge results
|
||||
console.log('Running minimal ESLint for custom rules...');
|
||||
let eslintOutput = '[]';
|
||||
try {
|
||||
// Run ESLint and capture output directly.
|
||||
// Flat config (eslint.config.minimal.js) is explicitly selected via
|
||||
// --config; ESLint v9+/v10 no longer support eslintrc or --no-eslintrc.
|
||||
eslintOutput = execSync(
|
||||
'npx eslint --config eslint.config.minimal.js --no-inline-config --format json src',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
stdio: ['pipe', 'pipe', 'ignore'], // Ignore stderr
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// ESLint exits with non-zero when it finds issues, capture the stdout
|
||||
if (e.stdout) {
|
||||
eslintOutput = e.stdout.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse minimal ESLint output
|
||||
try {
|
||||
const eslintResults = JSON.parse(eslintOutput);
|
||||
|
||||
eslintResults.forEach(result => {
|
||||
result.messages.forEach(({ ruleId, line, column, message }) => {
|
||||
const ruleData = metricsByRule[ruleId] || { count: 0 };
|
||||
ruleData.count += 1;
|
||||
metricsByRule[ruleId] = ruleData;
|
||||
|
||||
occurrencesData.push({
|
||||
rule: ruleId,
|
||||
message,
|
||||
file: result.filePath,
|
||||
line,
|
||||
column,
|
||||
ts: DATETIME,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log(
|
||||
`ESLint found ${eslintResults.reduce((sum, r) => sum + r.messages.length, 0)} custom rule violations`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log('No ESLint issues found or parsing error:', e.message);
|
||||
}
|
||||
|
||||
// Transform data for Google Sheets
|
||||
const metricsData = Object.entries(metricsByRule).map(
|
||||
([rule, { count }]) => [
|
||||
'OXC+ESLint',
|
||||
rule,
|
||||
enrichedRules[rule]?.description || 'N/A',
|
||||
`${count}`,
|
||||
DATETIME,
|
||||
],
|
||||
);
|
||||
|
||||
occurrencesData = occurrencesData.map(
|
||||
({ rule, message, file, line, column }) => [
|
||||
rule,
|
||||
enrichedRules[rule]?.description || 'N/A',
|
||||
message,
|
||||
file,
|
||||
`${line}`,
|
||||
`${column}`,
|
||||
DATETIME,
|
||||
],
|
||||
);
|
||||
|
||||
const aggregatedHistoryHeaders = [
|
||||
'Process',
|
||||
'Rule',
|
||||
'Description',
|
||||
'Count',
|
||||
'Timestamp',
|
||||
];
|
||||
const eslintBacklogHeaders = [
|
||||
'Rule',
|
||||
'Rule Description',
|
||||
'ESLint Message',
|
||||
'File',
|
||||
'Line',
|
||||
'Column',
|
||||
'Timestamp',
|
||||
];
|
||||
|
||||
console.log(
|
||||
`Found ${Object.keys(metricsByRule).length} unique rules with ${occurrencesData.length} total occurrences`,
|
||||
);
|
||||
|
||||
await writeToGoogleSheet(
|
||||
metricsData,
|
||||
'Aggregated History!A:E',
|
||||
aggregatedHistoryHeaders,
|
||||
true,
|
||||
);
|
||||
|
||||
await writeToGoogleSheet(
|
||||
occurrencesData,
|
||||
'ESLint Backlog!A:G',
|
||||
eslintBacklogHeaders,
|
||||
);
|
||||
|
||||
console.log('Successfully uploaded metrics to Google Sheets');
|
||||
} catch (error) {
|
||||
console.error('Error processing lint results:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export { parseRuleId, runOxlintAndProcess };
|
||||
@@ -16,272 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const { execSync } = require('child_process');
|
||||
const { GoogleAuth } = require('google-auth-library');
|
||||
const googleSheets = require('@googleapis/sheets');
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { runOxlintAndProcess } from './internal/oxlint-metrics-uploader.js';
|
||||
|
||||
const { SPREADSHEET_ID } = process.env;
|
||||
const SERVICE_ACCOUNT_KEY = JSON.parse(process.env.SERVICE_ACCOUNT_KEY || '{}');
|
||||
|
||||
// Only set up Google Sheets if we have credentials
|
||||
let sheets;
|
||||
if (SERVICE_ACCOUNT_KEY.client_email) {
|
||||
const auth = new GoogleAuth({
|
||||
credentials: SERVICE_ACCOUNT_KEY,
|
||||
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
||||
});
|
||||
sheets = googleSheets.sheets({ version: 'v4', auth });
|
||||
}
|
||||
|
||||
const DATETIME = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
||||
|
||||
/**
|
||||
* Turn an oxlint diagnostic code into the canonical rule id used by the metrics
|
||||
* series.
|
||||
*
|
||||
* oxlint reports `<plugin>(<rule>)`, where the plugin is the linter the rule came
|
||||
* from: `eslint(no-console)`, `react-hooks(exhaustive-deps)`, `react(jsx-key)`,
|
||||
* `jest(no-conditional-expect)`, `oxc(erasing-op)`, and the legacy
|
||||
* `eslint-plugin-unicorn(no-new-array)` spelling.
|
||||
*
|
||||
* `eslint` is the implicit namespace, so its rules keep their bare name and stay
|
||||
* comparable with the rows recorded before the oxlint migration. Every other
|
||||
* plugin becomes `<plugin>/<rule>`, which is the id those rules are known by in
|
||||
* config and in the pre-migration history.
|
||||
*
|
||||
* @param {string | undefined} code the diagnostic's `code` field
|
||||
* @returns {string} the rule id to record
|
||||
*/
|
||||
function parseRuleId(code) {
|
||||
if (!code) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const match = code.match(/^([\w-]+)\(([^)]+)\)$/);
|
||||
if (!match) {
|
||||
return code;
|
||||
}
|
||||
|
||||
const [, namespace, rule] = match;
|
||||
if (namespace === 'eslint') {
|
||||
return rule;
|
||||
}
|
||||
|
||||
// `eslint-plugin-unicorn(...)` is the same rule as `unicorn/...`
|
||||
const plugin = namespace.replace(/^eslint-plugin-/, '');
|
||||
return `${plugin}/${rule}`;
|
||||
}
|
||||
|
||||
async function writeToGoogleSheet(data, range, headers, append = false) {
|
||||
if (!sheets) {
|
||||
console.log('No Google Sheets credentials, skipping upload');
|
||||
return;
|
||||
}
|
||||
|
||||
const request = {
|
||||
spreadsheetId: SPREADSHEET_ID,
|
||||
range,
|
||||
valueInputOption: 'USER_ENTERED',
|
||||
resource: { values: append ? data : [headers, ...data] },
|
||||
};
|
||||
|
||||
const method = append ? 'append' : 'update';
|
||||
await sheets.spreadsheets.values[method](request);
|
||||
}
|
||||
|
||||
// Run OXC and get JSON output
|
||||
async function runOxlintAndProcess() {
|
||||
const enrichedRules = {
|
||||
'react-prefer-function-component/react-prefer-function-component': {
|
||||
description: 'We prefer function components to class-based components',
|
||||
},
|
||||
'react/jsx-filename-extension': {
|
||||
description:
|
||||
'We prefer Typescript - all JSX files should be converted to TSX',
|
||||
},
|
||||
'react/forbid-component-props': {
|
||||
description:
|
||||
'We prefer Emotion for styling rather than `className` or `style` props',
|
||||
},
|
||||
'no-restricted-imports': {
|
||||
description:
|
||||
"This rule catches several things that shouldn't be used anymore. LESS, antD, etc. See individual occurrence messages for details",
|
||||
},
|
||||
'no-console': {
|
||||
description:
|
||||
"We don't want a bunch of console noise, but you can use the `logger` from `@superset-ui/core` when there's a reason to.",
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
// Run OXC with JSON format
|
||||
console.log('Running OXC linter...');
|
||||
// `oxlint.json` is not the `.oxlintrc.json` oxlint auto-discovers, so the
|
||||
// config has to be passed explicitly or the run reports oxlint's defaults
|
||||
// instead of the project's ruleset. Matches the `lint` scripts in
|
||||
// package.json.
|
||||
const oxlintOutput = execSync(
|
||||
'npx oxlint --config oxlint.json --format json',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large outputs
|
||||
stdio: ['pipe', 'pipe', 'ignore'], // Ignore stderr to avoid error output
|
||||
},
|
||||
);
|
||||
|
||||
const results = JSON.parse(oxlintOutput);
|
||||
|
||||
// Process OXC JSON output
|
||||
const metricsByRule = {};
|
||||
let occurrencesData = [];
|
||||
|
||||
// OXC JSON format has diagnostics array
|
||||
if (results.diagnostics && Array.isArray(results.diagnostics)) {
|
||||
results.diagnostics.forEach(diagnostic => {
|
||||
const ruleId = parseRuleId(diagnostic.code);
|
||||
|
||||
const file = diagnostic.filename || 'unknown';
|
||||
const line = diagnostic.labels?.[0]?.span?.line || 0;
|
||||
const column = diagnostic.labels?.[0]?.span?.column || 0;
|
||||
const message = diagnostic.message || '';
|
||||
|
||||
const ruleData = metricsByRule[ruleId] || { count: 0 };
|
||||
ruleData.count += 1;
|
||||
metricsByRule[ruleId] = ruleData;
|
||||
|
||||
occurrencesData.push({
|
||||
rule: ruleId,
|
||||
message,
|
||||
file,
|
||||
line,
|
||||
column,
|
||||
ts: DATETIME,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
`OXC found ${results.diagnostics?.length || 0} issues across ${results.number_of_files} files`,
|
||||
);
|
||||
|
||||
// Also run minimal ESLint for custom rules and merge results
|
||||
console.log('Running minimal ESLint for custom rules...');
|
||||
let eslintOutput = '[]';
|
||||
try {
|
||||
// Run ESLint and capture output directly.
|
||||
// Flat config (eslint.config.minimal.js) is explicitly selected via
|
||||
// --config; ESLint v9+/v10 no longer support eslintrc or --no-eslintrc.
|
||||
eslintOutput = execSync(
|
||||
'npx eslint --config eslint.config.minimal.js --no-inline-config --format json src',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
stdio: ['pipe', 'pipe', 'ignore'], // Ignore stderr
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// ESLint exits with non-zero when it finds issues, capture the stdout
|
||||
if (e.stdout) {
|
||||
eslintOutput = e.stdout.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse minimal ESLint output
|
||||
try {
|
||||
const eslintResults = JSON.parse(eslintOutput);
|
||||
|
||||
eslintResults.forEach(result => {
|
||||
result.messages.forEach(({ ruleId, line, column, message }) => {
|
||||
const ruleData = metricsByRule[ruleId] || { count: 0 };
|
||||
ruleData.count += 1;
|
||||
metricsByRule[ruleId] = ruleData;
|
||||
|
||||
occurrencesData.push({
|
||||
rule: ruleId,
|
||||
message,
|
||||
file: result.filePath,
|
||||
line,
|
||||
column,
|
||||
ts: DATETIME,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log(
|
||||
`ESLint found ${eslintResults.reduce((sum, r) => sum + r.messages.length, 0)} custom rule violations`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log('No ESLint issues found or parsing error:', e.message);
|
||||
}
|
||||
|
||||
// Transform data for Google Sheets
|
||||
const metricsData = Object.entries(metricsByRule).map(
|
||||
([rule, { count }]) => [
|
||||
'OXC+ESLint',
|
||||
rule,
|
||||
enrichedRules[rule]?.description || 'N/A',
|
||||
`${count}`,
|
||||
DATETIME,
|
||||
],
|
||||
);
|
||||
|
||||
occurrencesData = occurrencesData.map(
|
||||
({ rule, message, file, line, column }) => [
|
||||
rule,
|
||||
enrichedRules[rule]?.description || 'N/A',
|
||||
message,
|
||||
file,
|
||||
`${line}`,
|
||||
`${column}`,
|
||||
DATETIME,
|
||||
],
|
||||
);
|
||||
|
||||
const aggregatedHistoryHeaders = [
|
||||
'Process',
|
||||
'Rule',
|
||||
'Description',
|
||||
'Count',
|
||||
'Timestamp',
|
||||
];
|
||||
const eslintBacklogHeaders = [
|
||||
'Rule',
|
||||
'Rule Description',
|
||||
'ESLint Message',
|
||||
'File',
|
||||
'Line',
|
||||
'Column',
|
||||
'Timestamp',
|
||||
];
|
||||
|
||||
console.log(
|
||||
`Found ${Object.keys(metricsByRule).length} unique rules with ${occurrencesData.length} total occurrences`,
|
||||
);
|
||||
|
||||
await writeToGoogleSheet(
|
||||
metricsData,
|
||||
'Aggregated History!A:E',
|
||||
aggregatedHistoryHeaders,
|
||||
true,
|
||||
);
|
||||
|
||||
await writeToGoogleSheet(
|
||||
occurrencesData,
|
||||
'ESLint Backlog!A:G',
|
||||
eslintBacklogHeaders,
|
||||
);
|
||||
|
||||
console.log('Successfully uploaded metrics to Google Sheets');
|
||||
} catch (error) {
|
||||
console.error('Error processing lint results:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
// Run the process, unless this file was imported (e.g. by a test) rather than
|
||||
// executed, in which case nothing should be linted or uploaded on import.
|
||||
if (require.main === module) {
|
||||
if (__filename === process.argv[1]) {
|
||||
runOxlintAndProcess().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = { parseRuleId };
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
module.exports = {};
|
||||
|
||||
export default {};
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
module.exports = 'test-file-stub';
|
||||
|
||||
export default 'test-file-stub';
|
||||
|
||||
+4
-4
@@ -16,13 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const {
|
||||
import fs from 'fs';
|
||||
import {
|
||||
entrypointSizeByExt,
|
||||
main,
|
||||
} = require('../../scripts/bundle-size-summary');
|
||||
} from '../../scripts/internal/bundle-size-summary';
|
||||
|
||||
function mockStats(entrypoints) {
|
||||
function mockStats(entrypoints: object) {
|
||||
jest
|
||||
.spyOn(fs, 'readFileSync')
|
||||
.mockReturnValue(JSON.stringify({ entrypoints }));
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const { parseRuleId } = require('../../scripts/oxlint-metrics-uploader');
|
||||
import { parseRuleId } from '../../scripts/internal/oxlint-metrics-uploader';
|
||||
|
||||
test('eslint rules keep their bare name', () => {
|
||||
expect(parseRuleId('eslint(no-console)')).toBe('no-console');
|
||||
@@ -61,7 +61,7 @@ const irregularDocumentationLinks = {
|
||||
};
|
||||
|
||||
const documentationLink = (engine: string | undefined) => {
|
||||
if (!engine) return null;
|
||||
if (!engine) return undefined;
|
||||
|
||||
if (supersetTextDocs) {
|
||||
// override doc link for superset_txt yml
|
||||
|
||||
@@ -1315,7 +1315,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
className="preferred-item"
|
||||
onClick={() => setDatabaseModel(database.name)}
|
||||
buttonText={database.name}
|
||||
icon={dbImages?.[database.engine]}
|
||||
icon={dbImages?.[database.engine] || ''}
|
||||
key={`${database.name}`}
|
||||
/>
|
||||
))}
|
||||
|
||||
+8
-13
@@ -17,17 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jest configuration for @storybook/test-runner
|
||||
*
|
||||
* This extends the default test-runner config with custom timeouts
|
||||
* to handle slow story rendering in CI environments.
|
||||
*/
|
||||
const { getJestConfig } = require('@storybook/test-runner');
|
||||
const testRunnerConfig = getJestConfig();
|
||||
declare module '*.yaml' {
|
||||
const content: Record<string, any>;
|
||||
export default content;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...testRunnerConfig,
|
||||
// Increase timeout from default 15s to 60s for CI environments
|
||||
testTimeout: 60000,
|
||||
};
|
||||
declare module '*.yml' {
|
||||
const content: Record<string, any>;
|
||||
export default content;
|
||||
}
|
||||
@@ -15,15 +15,49 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const loadModule = () => {
|
||||
|
||||
export interface SupersetTextConfig {
|
||||
DB_IMAGES?: Record<string, string>;
|
||||
DB_CONNECTION_ALERTS?: {
|
||||
DEFAULT?: {
|
||||
message?: string;
|
||||
description?: string;
|
||||
};
|
||||
ADD_DATABASE?: {
|
||||
message?: string;
|
||||
description?: string;
|
||||
contact_link?: string;
|
||||
contact_description_link?: string;
|
||||
};
|
||||
REGIONAL_IPS?: Record<string, string>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
DB_CONNECTION_DOC_LINKS?: Record<string, string> & {
|
||||
default?: string;
|
||||
support?: string;
|
||||
};
|
||||
DB_MODAL_SQLALCHEMY_FORM?: {
|
||||
SQLALCHEMY_DOCS_URL?: string;
|
||||
SQLALCHEMY_DISPLAY_TEXT?: string;
|
||||
};
|
||||
THEME_MODAL?: {
|
||||
THEME_EDITOR_URL?: string;
|
||||
DOCUMENTATION_URL?: string;
|
||||
};
|
||||
ERRORS?: Record<string, string>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const loadModule = (): SupersetTextConfig => {
|
||||
try {
|
||||
// eslint-disable-next-line global-require, import/no-unresolved
|
||||
return require('../../../superset_text') || {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require
|
||||
const config = require('../../../superset_text.yml') as SupersetTextConfig;
|
||||
return config || {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const supersetText = loadModule();
|
||||
const supersetText: SupersetTextConfig = loadModule();
|
||||
|
||||
export default supersetText;
|
||||
|
||||
@@ -904,9 +904,9 @@ export const copyQueryLink = (
|
||||
});
|
||||
};
|
||||
|
||||
export const getDatabaseImages = () => SupersetText.DB_IMAGES;
|
||||
export const getDatabaseImages = () => SupersetText?.DB_IMAGES;
|
||||
|
||||
export const getConnectionAlert = () => SupersetText.DB_CONNECTION_ALERTS;
|
||||
export const getConnectionAlert = () => SupersetText?.DB_CONNECTION_ALERTS;
|
||||
export const getDatabaseDocumentationLinks = () =>
|
||||
SupersetText.DB_CONNECTION_DOC_LINKS;
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
export default {
|
||||
customSyntax: 'postcss-styled-syntax',
|
||||
rules: {
|
||||
'property-no-unknown': true,
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const http = require('http');
|
||||
const zlib = require('zlib');
|
||||
const { ZSTDCompress } = require('simple-zstd');
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
import http from 'node:http';
|
||||
import zlib from 'node:zlib';
|
||||
import { ZSTDCompress } from 'simple-zstd';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
|
||||
// yargs ships ESM-only and jest's default transform doesn't cover
|
||||
// node_modules; webpack.proxy-config.js only uses it to parse a `--env`
|
||||
@@ -40,11 +40,11 @@ const HANG_GUARD_MS = 2000;
|
||||
async function startProxy(backendPort) {
|
||||
const previousPort = process.env.supersetPort;
|
||||
// webpack.proxy-config.js resolves its target port from process.env at
|
||||
// require()-time, so the module must be (re-)required after this is set.
|
||||
// import-time, so the module must be re-imported after this is set.
|
||||
process.env.supersetPort = String(backendPort);
|
||||
jest.resetModules();
|
||||
// eslint-disable-next-line global-require
|
||||
const getProxyConfig = require('../webpack.proxy-config');
|
||||
const { default: getProxyConfig } =
|
||||
await import('../webpack.proxy-config.js');
|
||||
process.env.supersetPort = previousPort;
|
||||
|
||||
const proxyMiddleware = createProxyMiddleware(getProxyConfig(undefined));
|
||||
|
||||
@@ -17,29 +17,40 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const { ModuleFederationPlugin } = webpack.container;
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const MinimizerPlugin = require('minimizer-webpack-plugin');
|
||||
const LightningCSS = require('lightningcss');
|
||||
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||
const {
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import webpack from 'webpack';
|
||||
import * as webpackBundleAnalyzer from 'webpack-bundle-analyzer';
|
||||
import CopyPlugin from 'copy-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import MinimizerPlugin from 'minimizer-webpack-plugin';
|
||||
import * as LightningCSS from 'lightningcss';
|
||||
import SpeedMeasurePlugin from 'speed-measure-webpack-plugin';
|
||||
import {
|
||||
WebpackManifestPlugin,
|
||||
getCompilerHooks,
|
||||
} = require('webpack-manifest-plugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const yargs = require('yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
const Visualizer = require('webpack-visualizer-plugin2');
|
||||
const getProxyConfig = require('./webpack.proxy-config');
|
||||
const packageConfig = require('./package.json');
|
||||
} from 'webpack-manifest-plugin';
|
||||
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import Visualizer from 'webpack-visualizer-plugin2';
|
||||
import getProxyConfig from './webpack.proxy-config.js';
|
||||
|
||||
const { ModuleFederationPlugin } = webpack.container;
|
||||
const { BundleAnalyzerPlugin } = webpackBundleAnalyzer;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const resolveEsmModule = specifier =>
|
||||
fileURLToPath(import.meta.resolve(specifier));
|
||||
|
||||
const packageConfig = JSON.parse(
|
||||
fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8'),
|
||||
);
|
||||
|
||||
const parsedArgs = yargs(hideBin(process.argv)).parse();
|
||||
|
||||
@@ -478,8 +489,8 @@ const config = {
|
||||
fs: false,
|
||||
vm: false,
|
||||
path: false,
|
||||
stream: require.resolve('stream-browserify'),
|
||||
...(isDevMode ? { buffer: require.resolve('buffer/') } : {}), // Fix plugin-chart-paired-t-test broken Story
|
||||
stream: resolveEsmModule('stream-browserify'),
|
||||
...(isDevMode ? { buffer: resolveEsmModule('buffer/') } : {}), // Fix plugin-chart-paired-t-test broken Story
|
||||
},
|
||||
},
|
||||
context: APP_DIR, // to automatically find tsconfig.json
|
||||
@@ -600,20 +611,6 @@ const config = {
|
||||
test: /\.geojson$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// {
|
||||
// test: /\.mdx?$/,
|
||||
// use: [
|
||||
// {
|
||||
// loader: require.resolve('@storybook/mdx2-csf/loader'),
|
||||
// options: {
|
||||
// skipCsf: false,
|
||||
// mdxCompileOptions: {
|
||||
// remarkPlugins: [remarkGfm],
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
@@ -748,4 +745,4 @@ if (process.env.BUNDLE_SIZE_STATS) {
|
||||
config.stats = { all: false, assets: true, entrypoints: true };
|
||||
}
|
||||
|
||||
module.exports = smp.wrap(config);
|
||||
export default smp.wrap(config);
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const zlib = require('zlib');
|
||||
const { Writable } = require('stream');
|
||||
const { pipeline } = require('stream/promises');
|
||||
const { ZSTDDecompress } = require('simple-zstd');
|
||||
import zlib from 'node:zlib';
|
||||
import { Writable } from 'node:stream';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import { ZSTDDecompress } from 'simple-zstd';
|
||||
|
||||
const yargs = require('yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
const parsedArgs = yargs(hideBin(process.argv)).parse();
|
||||
|
||||
@@ -185,7 +185,7 @@ async function processHTML(proxyResponse, response) {
|
||||
response.end(toDevHTML(Buffer.concat(chunks).toString()));
|
||||
}
|
||||
|
||||
module.exports = newManifest => {
|
||||
export default function getProxyConfig(newManifest) {
|
||||
manifest = newManifest;
|
||||
return {
|
||||
context: path => {
|
||||
@@ -269,4 +269,4 @@ module.exports = newManifest => {
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user