This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 296bd7e56b5 docs(extensions): fix extension developer documentation
and CLI scaffolding (#38472)
296bd7e56b5 is described below
commit 296bd7e56b58d3c8c5f920e996f45d3ccd753ee8
Author: Michael S. Molina <[email protected]>
AuthorDate: Fri Mar 6 13:10:41 2026 -0300
docs(extensions): fix extension developer documentation and CLI scaffolding
(#38472)
---
docs/developer_docs/extensions/architecture.md | 63 ++++----
.../extensions/contribution-types.md | 105 ++++++++++---
docs/developer_docs/extensions/development.md | 38 +++--
.../extensions/extension-points/editors.md | 47 ++----
.../extensions/extension-points/sqllab.md | 173 +++++++--------------
docs/developer_docs/extensions/quick-start.md | 100 ++++++------
docs/developer_docs/extensions/registry.md | 14 +-
docs/scripts/generate-superset-components.mjs | 8 +-
docs/src/components/StorybookWrapper.jsx | 4 +-
docs/src/theme/Playground/Preview/index.tsx | 2 +-
docs/src/theme/ReactLiveScope/index.tsx | 2 +-
docs/src/types/apache-superset-core/index.d.ts | 2 +-
docs/src/webpack.extend.ts | 4 +-
.../{sql-snippets.png => editor-snippets.png} | Bin
docs/tsconfig.json | 4 +-
.../src/superset_extensions_cli/cli.py | 6 -
.../templates/backend/src/package/__init__.py.j2 | 0
17 files changed, 277 insertions(+), 295 deletions(-)
diff --git a/docs/developer_docs/extensions/architecture.md
b/docs/developer_docs/extensions/architecture.md
index 568f8feff47..2dd642fd997 100644
--- a/docs/developer_docs/extensions/architecture.md
+++ b/docs/developer_docs/extensions/architecture.md
@@ -33,13 +33,15 @@ The extension architecture is built on six core principles
that guide all techni
### 1. Lean Core
Superset's core should remain minimal, with many features delegated to
extensions. Built-in features use the same APIs and extension mechanisms
available to external developers. This approach:
+
- Reduces maintenance burden and complexity
- Encourages modularity
- Allows the community to innovate independently of the main codebase
### 2. Explicit Contribution Points
-All extension points are clearly defined and documented. Extension authors
know exactly where and how they can interact with the host system. Backend
contributions are declared in `extension.json`. Frontend contributions are
registered directly in code at module load time, giving the host clear
visibility into what each extension provides:
+All extension points are clearly defined and documented. Extension authors
know exactly where and how they can interact with the host system. Both backend
and frontend contributions are registered directly in code — backend
contributions via classes decorated with `@api` (and other decorators) imported
from the auto-discovered entrypoint, frontend contributions via calls like
`views.registerView` and `commands.registerCommand` executed at module load
time in `index.tsx`. This gives the h [...]
+
- Manage the extension lifecycle
- Provide a consistent user experience
- Validate extension compatibility
@@ -47,6 +49,7 @@ All extension points are clearly defined and documented.
Extension authors know
### 3. Versioned and Stable APIs
Public interfaces for extensions follow semantic versioning, allowing for:
+
- Safe evolution of the platform
- Backward compatibility
- Clear upgrade paths for extension authors
@@ -54,6 +57,7 @@ Public interfaces for extensions follow semantic versioning,
allowing for:
### 4. Lazy Loading and Activation
Extensions are loaded and activated only when needed, which:
+
- Minimizes performance overhead
- Reduces resource consumption
- Improves startup time
@@ -61,6 +65,7 @@ Extensions are loaded and activated only when needed, which:
### 5. Composability and Reuse
The architecture encourages reusing extension points and patterns across
different modules, promoting:
+
- Consistency across extensions
- Reduced duplication
- Shared best practices
@@ -80,6 +85,7 @@ Two core packages provide the foundation for extension
development:
**Frontend: `@apache-superset/core`**
This package provides essential building blocks for frontend extensions and
the host application:
+
- Shared UI components
- Utility functions
- APIs and hooks
@@ -90,6 +96,7 @@ By centralizing these resources, both extensions and built-in
features use the s
**Backend: `apache-superset-core`**
This package exposes key classes and APIs for backend extensions:
+
- Database connectors
- API extensions
- Security manager customization
@@ -102,6 +109,7 @@ It includes dependencies on critical libraries like
Flask-AppBuilder and SQLAlch
**`apache-superset-extensions-cli`**
The CLI provides comprehensive commands for extension development:
+
- Project scaffolding
- Code generation
- Building and bundling
@@ -114,6 +122,7 @@ By standardizing these processes, the CLI ensures
extensions are built consisten
The Superset host application serves as the runtime environment for extensions:
**Extension Management**
+
- Exposes `/api/v1/extensions` endpoint for registration and management
- Provides a dedicated UI for managing extensions
- Stores extension metadata in the `extensions` database table
@@ -121,6 +130,7 @@ The Superset host application serves as the runtime
environment for extensions:
**Extension Storage**
The extensions table contains:
+
- Extension name, version, and author
- Metadata and configuration
- Built frontend and/or backend code
@@ -132,6 +142,7 @@ The following diagram illustrates how these components work
together:
<img width="955" height="586" alt="Extension System Architecture"
src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6"
/>
The diagram shows:
+
1. **Extension projects** depend on core packages for development
2. **Core packages** provide APIs and type definitions
3. **The host application** implements the APIs and manages extensions
@@ -151,23 +162,25 @@ The architecture leverages Webpack's Module Federation to
enable dynamic loading
Extensions configure Webpack to expose their entry points:
-``` typescript
-new ModuleFederationPlugin({
- name: 'my_extension',
- filename: 'remoteEntry.[contenthash].js',
- exposes: {
- './index': './src/index.tsx',
- },
- externalsType: 'window',
- externals: {
- '@apache-superset/core': 'superset',
- },
- shared: {
- react: { singleton: true },
- 'react-dom': { singleton: true },
- 'antd-v5': { singleton: true }
- }
-})
+```javascript
+externalsType: 'window',
+externals: {
+ '@apache-superset/core': 'superset',
+},
+plugins: [
+ new ModuleFederationPlugin({
+ name: 'my_extension',
+ filename: 'remoteEntry.[contenthash].js',
+ exposes: {
+ './index': './src/index.tsx',
+ },
+ shared: {
+ react: { singleton: true, import: false },
+ 'react-dom': { singleton: true, import: false },
+ antd: { singleton: true, import: false },
+ },
+ }),
+]
```
This configuration does several important things:
@@ -195,24 +208,12 @@ Here's what happens at runtime:
On the Superset side, the APIs are mapped to `window.superset` during
application bootstrap:
-``` typescript
+```typescript
import * as supersetCore from '@apache-superset/core';
-import {
- authentication,
- core,
- commands,
- extensions,
- sqlLab,
-} from 'src/extensions';
export default function setupExtensionsAPI() {
window.superset = {
...supersetCore,
- authentication,
- core,
- commands,
- extensions,
- sqlLab,
};
}
```
diff --git a/docs/developer_docs/extensions/contribution-types.md
b/docs/developer_docs/extensions/contribution-types.md
index cf959005b6c..6fa8bf7f9a5 100644
--- a/docs/developer_docs/extensions/contribution-types.md
+++ b/docs/developer_docs/extensions/contribution-types.md
@@ -28,7 +28,7 @@ To facilitate the development of extensions, we define a set
of well-defined con
## Frontend
-Frontend contribution types allow extensions to extend Superset's user
interface with new views, commands, and menu items. Frontend contributions are
registered directly in code from your extension's `index.tsx` entry point —
they do not need to be declared in `extension.json`.
+Frontend contribution types allow extensions to extend Superset's user
interface with new views, commands, and menu items. Frontend contributions are
registered directly in code from your extension's `index.tsx` entry point.
### Views
@@ -68,25 +68,28 @@ commands.registerCommand(
### Menus
-Extensions can contribute new menu items or context menus to the host
application, providing users with additional actions and options. Each menu
item specifies the target area, the command to execute, and its placement
(primary, secondary, or context). Menu contribution areas are uniquely
identified (e.g., `sqllab.editor` for the SQL Lab editor).
+Extensions can contribute new menu items or context menus to the host
application, providing users with additional actions and options. Each menu
item specifies the view and command to execute, the target area, and the
location (`primary`, `secondary`, or `context`). Menu contribution areas are
uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
```typescript
import { menus } from '@apache-superset/core';
-menus.addMenuItem('sqllab.editor', {
- placement: 'primary',
- command: 'my-extension.copy-query',
-});
+menus.registerMenuItem(
+ { view: 'sqllab.editor', command: 'my-extension.copy-query' },
+ 'sqllab.editor',
+ 'primary',
+);
-menus.addMenuItem('sqllab.editor', {
- placement: 'secondary',
- command: 'my-extension.prettify',
-});
+menus.registerMenuItem(
+ { view: 'sqllab.editor', command: 'my-extension.prettify' },
+ 'sqllab.editor',
+ 'secondary',
+);
-menus.addMenuItem('sqllab.editor', {
- placement: 'context',
- command: 'my-extension.clear',
-});
+menus.registerMenuItem(
+ { view: 'sqllab.editor', command: 'my-extension.clear' },
+ 'sqllab.editor',
+ 'context',
+);
```
### Editors
@@ -111,24 +114,31 @@ See [Editors Extension Point](./extension-points/editors)
for implementation det
## Backend
-Backend contribution types allow extensions to extend Superset's server-side
capabilities with new API endpoints, MCP tools, and MCP prompts.
+Backend contribution types allow extensions to extend Superset's server-side
capabilities. Backend contributions are registered at startup via classes and
functions imported from the auto-discovered `entrypoint.py` file.
### REST API Endpoints
Extensions can register custom REST API endpoints under the `/extensions/`
namespace. This dedicated namespace prevents conflicts with built-in endpoints
and provides a clear separation between core and extension functionality.
```python
-from superset_core.rest_api.api import RestApi, api
-from flask_appbuilder.api import expose, protect
+from flask import Response
+from flask_appbuilder.api import expose, permission_name, protect, safe
+from superset_core.rest_api.api import RestApi
+from superset_core.rest_api.decorators import api
@api(
id="my_extension_api",
name="My Extension API",
- description="Custom API endpoints for my extension"
+ description="Custom API endpoints for my extension",
)
class MyExtensionAPI(RestApi):
+ openapi_spec_tag = "My Extension"
+ class_permission_name = "my_extension_api"
+
@expose("/hello", methods=("GET",))
@protect()
+ @safe
+ @permission_name("read")
def hello(self) -> Response:
return self.response(200, result={"message": "Hello from extension!"})
@@ -136,7 +146,7 @@ class MyExtensionAPI(RestApi):
from .api import MyExtensionAPI
```
-**Note**: The [`@api`](superset-core/src/superset_core/api/rest_api.py:59)
decorator automatically detects context and generates appropriate paths:
+**Note**: The [`@api`](superset-core/src/superset_core/rest_api/decorators.py)
decorator automatically detects context and generates appropriate paths:
- **Extension context**: `/extensions/{publisher}/{name}/` with ID prefixed as
`extensions.{publisher}.{name}.{id}`
- **Host context**: `/api/v1/` with original ID
@@ -152,16 +162,65 @@ You can also specify a `resource_name` parameter to add
an additional path segme
@api(
id="analytics_api",
name="Analytics API",
- resource_name="analytics" # Adds /analytics to the path
+ resource_name="analytics", # Adds /analytics to the path
)
class AnalyticsAPI(RestApi):
+
@expose("/insights", methods=("GET",))
- def insights(self):
+ @protect()
+ @safe
+ @permission_name("read")
+ def insights(self) -> Response:
# This endpoint will be available at:
# /extensions/my-org/dataset-tools/analytics/insights
return self.response(200, result={"insights": []})
```
-### MCP Tools and Prompts
+### MCP Tools
+
+Extensions can register Python functions as MCP tools that AI agents can
discover and call. Tools provide executable functionality such as data
processing, custom analytics, or integration with external services. Each tool
specifies a unique name and an optional description that helps AI agents decide
when to use it.
+
+```python
+from superset_core.mcp.decorators import tool
+
+@tool(
+ name="my-extension.get_summary",
+ description="Get a summary of recent query activity",
+ tags=["analytics", "queries"],
+)
+def get_summary() -> dict:
+ """Returns a summary of recent query activity."""
+ return {"status": "success", "result": {"queries_today": 42}}
+```
+
+See [MCP Integration](./mcp) for implementation details.
+
+### MCP Prompts
+
+Extensions can register MCP prompts that provide interactive guidance and
context to AI agents. Prompts help agents understand domain-specific workflows,
best practices, or troubleshooting steps for your extension's use cases.
+
+```python
+from superset_core.mcp.decorators import prompt
+from fastmcp import Context
+
+@prompt(
+ "my-extension.analysis_guide",
+ title="Analysis Guide",
+ description="Step-by-step guidance for data analysis workflows",
+)
+async def analysis_guide(ctx: Context) -> str:
+ """Provides guidance for data analysis workflows."""
+ return """
+ # Data Analysis Guide
+
+ Follow these steps for effective analysis:
+
+ 1. **Explore your data** - Review available datasets and schema
+ 2. **Build your query** - Use SQL Lab to craft and test queries
+ 3. **Visualize results** - Choose the right chart type for your data
+
+ What would you like to analyze today?
+ """
+```
-Extensions can contribute Model Context Protocol (MCP) tools and prompts that
AI agents can discover and use. See [MCP Integration](./mcp) for detailed
documentation.
+See [MCP Integration](./mcp) for implementation details.
diff --git a/docs/developer_docs/extensions/development.md
b/docs/developer_docs/extensions/development.md
index 244d0f386f6..f920e2f1a87 100644
--- a/docs/developer_docs/extensions/development.md
+++ b/docs/developer_docs/extensions/development.md
@@ -38,12 +38,14 @@ superset-extensions build: Builds extension assets.
superset-extensions bundle: Packages the extension into a .supx file.
superset-extensions dev: Automatically rebuilds the extension as files change.
+
+superset-extensions validate: Validates the extension structure and metadata.
```
When creating a new extension with `superset-extensions init`, the CLI
generates a standardized folder structure:
```
-dataset-references/
+my-org.dataset-references/
├── extension.json
├── frontend/
│ ├── src/
@@ -53,8 +55,10 @@ dataset-references/
├── backend/
│ ├── src/
│ │ └── superset_extensions/
-│ │ └── dataset_references/
-│ ├── tests/
+│ │ └── my_org/
+│ │ └── dataset_references/
+│ │ ├── api.py
+│ │ └── entrypoint.py
│ ├── pyproject.toml
│ └── requirements.txt
├── dist/
@@ -65,19 +69,20 @@ dataset-references/
│ │ └── 900.038b20cdff6d49cfa8d9.js
│ └── backend
│ └── superset_extensions/
-│ └── dataset_references/
-│ ├── __init__.py
-│ ├── api.py
-│ └── entrypoint.py
-├── dataset-references-1.0.0.supx
+│ └── my_org/
+│ └── dataset_references/
+│ ├── api.py
+│ └── entrypoint.py
+├── my-org.dataset-references-1.0.0.supx
└── README.md
```
-**Note**: The extension ID (`dataset-references`) serves as the basis for all
technical names:
-- Directory name: `dataset-references` (kebab-case)
-- Backend Python package: `dataset_references` (snake_case)
-- Frontend package name: `dataset-references` (kebab-case)
-- Module Federation name: `datasetReferences` (camelCase)
+**Note**: With publisher `my-org` and name `dataset-references`, the technical
names are:
+- Directory name: `my-org.dataset-references` (kebab-case)
+- Backend Python namespace: `superset_extensions.my_org.dataset_references`
+- Backend distribution package: `my_org-dataset_references`
+- Frontend package name: `@my-org/dataset-references` (scoped)
+- Module Federation name: `myOrg_datasetReferences` (camelCase)
The `extension.json` file serves as the declared metadata for the extension,
containing the extension's name, version, author, description, and a list of
capabilities. This file is essential for the host application to understand how
to load and manage the extension.
@@ -203,7 +208,8 @@ Extension endpoints are registered under a dedicated
`/extensions` namespace to
```python
from superset_core.common.models import Database, get_session
from superset_core.common.daos import DatabaseDAO
-from superset_core.rest_api.api import RestApi, api
+from superset_core.rest_api.api import RestApi
+from superset_core.rest_api.decorators import api
from flask_appbuilder.api import expose, protect
@api(
@@ -244,7 +250,7 @@ class DatasetReferencesAPI(RestApi):
### Automatic Context Detection
-The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator
automatically detects whether it's being used in host or extension code:
+The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator
automatically detects whether it's being used in host or extension code:
- **Extension APIs**: Registered under `/extensions/{publisher}/{name}/` with
IDs prefixed as `extensions.{publisher}.{name}.{id}`
- **Host APIs**: Registered under `/api/v1/` with original IDs
@@ -262,7 +268,7 @@ LOCAL_EXTENSIONS = [
]
```
-This instructs Superset to load and serve extensions directly from disk, so
you can iterate quickly. Running `superset-extensions dev` watches for file
changes and rebuilds assets automatically, while the Webpack development server
(started separately with `npm run dev-server`) serves updated files as soon as
they're modified. This enables immediate feedback for React components, styles,
and other frontend code. Changes to backend files are also detected
automatically and immediately syn [...]
+This instructs Superset to load and serve extensions directly from disk, so
you can iterate quickly. Running `superset-extensions dev` watches for file
changes and rebuilds assets automatically, while the Webpack development server
(started separately with `npm run start`) serves updated files as soon as
they're modified. This enables immediate feedback for React components, styles,
and other frontend code. Changes to backend files are also detected
automatically and immediately synced, [...]
Example output when running in development mode:
diff --git a/docs/developer_docs/extensions/extension-points/editors.md
b/docs/developer_docs/extensions/extension-points/editors.md
index 001ca149300..1a0ff2d4c81 100644
--- a/docs/developer_docs/extensions/extension-points/editors.md
+++ b/docs/developer_docs/extensions/extension-points/editors.md
@@ -37,32 +37,12 @@ Superset uses text editors in various places throughout the
application:
| `css` | Dashboard Properties, CSS Template Modal |
| `markdown` | Dashboard Markdown component |
| `yaml` | Template Params Editor |
+| `javascript` | Custom JavaScript editor contexts |
+| `python` | Custom Python editor contexts |
+| `text` | Plain text editor contexts |
By registering an editor provider for a language, your extension replaces the
default Ace editor in **all** locations that use that language.
-## Manifest Configuration
-
-Declare editor contributions in your `extension.json` manifest:
-
-```json
-{
- "name": "monaco-editor",
- "version": "1.0.0",
- "frontend": {
- "contributions": {
- "editors": [
- {
- "id": "monaco-editor.sql",
- "name": "Monaco SQL Editor",
- "languages": ["sql"],
- "description": "Monaco-based SQL editor with IntelliSense"
- }
- ]
- }
- }
-}
-```
-
## Implementing an Editor
Your editor component must implement the `EditorProps` interface and expose an
`EditorHandle` via `forwardRef`. For the complete interface definitions, see
`@apache-superset/core/api/editors.ts`.
@@ -165,21 +145,22 @@ const MonacoSQLEditor = forwardRef<editors.EditorHandle,
editors.EditorProps>(
export default MonacoSQLEditor;
```
-### activate.ts
+### index.tsx
+
+Register the editor at module load time from your extension's entry point:
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
-export function activate(context) {
- // Register the Monaco editor for SQL using the contribution ID from
extension.json
- const disposable = editors.registerEditorProvider(
- 'monaco-sql-editor.sql',
- MonacoSQLEditor,
- );
-
- context.subscriptions.push(disposable);
-}
+editors.registerEditor(
+ {
+ id: 'my-extension.monaco-sql',
+ name: 'Monaco SQL Editor',
+ languages: ['sql'],
+ },
+ MonacoSQLEditor,
+);
```
## Handling Hotkeys
diff --git a/docs/developer_docs/extensions/extension-points/sqllab.md
b/docs/developer_docs/extensions/extension-points/sqllab.md
index bc9db014914..fd31f7b22b6 100644
--- a/docs/developer_docs/extensions/extension-points/sqllab.md
+++ b/docs/developer_docs/extensions/extension-points/sqllab.md
@@ -86,132 +86,73 @@ Extensions can replace the default SQL editor with custom
implementations (Monac
This example adds a "Data Profiler" panel to SQL Lab:
-```json
-{
- "name": "data_profiler",
- "version": "1.0.0",
- "frontend": {
- "contributions": {
- "views": {
- "sqllab": {
- "panels": [
- {
- "id": "data_profiler.main",
- "name": "Data Profiler"
- }
- ]
- }
- }
- }
- }
-}
-```
-
```typescript
-import { core } from '@apache-superset/core';
+import React from 'react';
+import { views } from '@apache-superset/core';
import DataProfilerPanel from './DataProfilerPanel';
-export function activate(context) {
- // Register the panel view with the ID declared in extension.json
- const disposable = core.registerView('data_profiler.main',
<DataProfilerPanel />);
- context.subscriptions.push(disposable);
-}
+views.registerView(
+ { id: 'my-extension.data-profiler', name: 'Data Profiler' },
+ 'sqllab.panels',
+ () => <DataProfilerPanel />,
+);
```
### Adding Actions to the Editor
This example adds primary, secondary, and context actions to the editor:
-```json
-{
- "name": "query_tools",
- "version": "1.0.0",
- "frontend": {
- "contributions": {
- "commands": [
- {
- "command": "query_tools.format",
- "title": "Format Query",
- "icon": "FormatPainterOutlined"
- },
- {
- "command": "query_tools.explain",
- "title": "Explain Query"
- },
- {
- "command": "query_tools.copy_as_cte",
- "title": "Copy as CTE"
- }
- ],
- "menus": {
- "sqllab": {
- "editor": {
- "primary": [
- {
- "view": "builtin.editor",
- "command": "query_tools.format"
- }
- ],
- "secondary": [
- {
- "view": "builtin.editor",
- "command": "query_tools.explain"
- }
- ],
- "context": [
- {
- "view": "builtin.editor",
- "command": "query_tools.copy_as_cte"
- }
- ]
- }
- }
- }
- }
- }
-}
-```
-
```typescript
-import { commands, sqlLab } from '@apache-superset/core';
-
-export function activate(context) {
- // Register the commands declared in extension.json
- const formatCommand = commands.registerCommand(
- 'query_tools.format',
- async () => {
- const tab = sqlLab.getCurrentTab();
- if (tab) {
- const editor = await tab.getEditor();
- // Format the SQL query
- }
- },
- );
-
- const explainCommand = commands.registerCommand(
- 'query_tools.explain',
- async () => {
- const tab = sqlLab.getCurrentTab();
- if (tab) {
- const editor = await tab.getEditor();
- // Show query explanation
- }
- },
- );
-
- const copyAsCteCommand = commands.registerCommand(
- 'query_tools.copy_as_cte',
- async () => {
- const tab = sqlLab.getCurrentTab();
- if (tab) {
- const editor = await tab.getEditor();
- // Copy selected text as CTE
- }
- },
- );
-
- context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
-}
+import { commands, menus, sqlLab } from '@apache-superset/core';
+
+commands.registerCommand(
+ { id: 'my-extension.format', title: 'Format Query', icon:
'FormatPainterOutlined' },
+ async () => {
+ const tab = sqlLab.getCurrentTab();
+ if (tab) {
+ const editor = await tab.getEditor();
+ // Format the SQL query
+ }
+ },
+);
+
+commands.registerCommand(
+ { id: 'my-extension.explain', title: 'Explain Query' },
+ async () => {
+ const tab = sqlLab.getCurrentTab();
+ if (tab) {
+ const editor = await tab.getEditor();
+ // Show query explanation
+ }
+ },
+);
+
+commands.registerCommand(
+ { id: 'my-extension.copy-as-cte', title: 'Copy as CTE' },
+ async () => {
+ const tab = sqlLab.getCurrentTab();
+ if (tab) {
+ const editor = await tab.getEditor();
+ // Copy selected text as CTE
+ }
+ },
+);
+
+menus.registerMenuItem(
+ { view: 'builtin.editor', command: 'my-extension.format' },
+ 'sqllab.editor',
+ 'primary',
+);
+menus.registerMenuItem(
+ { view: 'builtin.editor', command: 'my-extension.explain' },
+ 'sqllab.editor',
+ 'secondary',
+);
+menus.registerMenuItem(
+ { view: 'builtin.editor', command: 'my-extension.copy-as-cte' },
+ 'sqllab.editor',
+ 'context',
+);
```
## Next Steps
diff --git a/docs/developer_docs/extensions/quick-start.md
b/docs/developer_docs/extensions/quick-start.md
index a856185cbd3..13ff29f7a0b 100644
--- a/docs/developer_docs/extensions/quick-start.md
+++ b/docs/developer_docs/extensions/quick-start.md
@@ -64,6 +64,7 @@ Include backend? [Y/n]: Y
```
**Publisher Namespaces**: Extensions use organizational namespaces similar to
VS Code extensions, providing collision-safe naming across organizations:
+
- **NPM package**: `@my-org/hello-world` (scoped package for frontend
distribution)
- **Module Federation name**: `myOrg_helloWorld` (collision-safe JavaScript
identifier)
- **Backend package**: `my_org-hello_world` (collision-safe Python
distribution name)
@@ -80,9 +81,7 @@ my-org.hello-world/
│ ├── src/
│ │ └── superset_extensions/
│ │ └── my_org/
-│ │ ├── __init__.py
│ │ └── hello_world/
-│ │ ├── __init__.py
│ │ └── entrypoint.py # Backend registration
│ └── pyproject.toml
└── frontend/ # Frontend TypeScript/React code
@@ -95,7 +94,7 @@ my-org.hello-world/
## Step 3: Configure Extension Metadata
-The generated `extension.json` contains the extension's metadata. It is used
to identify the extension and declare its backend entry points. Frontend
contributions are registered directly in code (see Step 5).
+The generated `extension.json` contains the extension's metadata.
```json
{
@@ -104,10 +103,6 @@ The generated `extension.json` contains the extension's
metadata. It is used to
"displayName": "Hello World",
"version": "0.1.0",
"license": "Apache-2.0",
- "backend": {
- "entryPoints": ["superset_extensions.my_org.hello_world.entrypoint"],
- "files": ["backend/src/superset_extensions/my_org/hello_world/**/*.py"]
- },
"permissions": ["can_read"]
}
```
@@ -117,8 +112,7 @@ The generated `extension.json` contains the extension's
metadata. It is used to
- `publisher`: Organizational namespace for the extension
- `name`: Technical identifier (kebab-case)
- `displayName`: Human-readable name shown to users
-- `backend.entryPoints`: Python modules to load eagerly when the extension
starts
-- `backend.files`: Glob patterns for Python source files to include in the
bundle
+- `permissions`: List of permissions the extension requires
## Step 4: Create Backend API
@@ -129,7 +123,8 @@ The CLI generated a basic
`backend/src/superset_extensions/my_org/hello_world/en
```python
from flask import Response
from flask_appbuilder.api import expose, protect, safe
-from superset_core.rest_api.api import RestApi, api
+from superset_core.rest_api.api import RestApi
+from superset_core.rest_api.decorators import api
@api(
@@ -174,7 +169,7 @@ class HelloWorldAPI(RestApi):
**Key points:**
-- Uses [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator
with automatic context detection
+- Uses [`@api`](superset-core/src/superset_core/rest_api/decorators.py)
decorator with automatic context detection
- Extends `RestApi` from `superset_core.rest_api.api`
- Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`)
- Returns responses using `self.response(status_code, result=data)`
@@ -187,12 +182,10 @@ Replace the generated print statement with API import to
trigger registration:
```python
# Importing the API class triggers the @api decorator registration
-from .api import HelloWorldAPI
-
-print("Hello World extension loaded successfully!")
+from .api import HelloWorldAPI # noqa: F401
```
-The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator
automatically detects extension context and registers your API with proper
namespacing.
+The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator
automatically detects extension context and registers your API with proper
namespacing.
## Step 5: Create Frontend Component
@@ -236,52 +229,53 @@ The webpack configuration requires specific settings for
Module Federation. Key
**Convention**: Superset always loads extensions by requesting the `./index`
module from the Module Federation container. The `exposes` entry must be
exactly `'./index': './src/index.tsx'` — do not rename or add additional
entries. All API registrations must be reachable from that file. See
[Architecture](./architecture#module-federation) for a full explanation.
```javascript
-const path = require("path");
-const { ModuleFederationPlugin } = require("webpack").container;
-const packageConfig = require("./package.json");
+const path = require('path');
+const { ModuleFederationPlugin } = require('webpack').container;
+const packageConfig = require('./package');
+const extensionConfig = require('../extension.json');
module.exports = (env, argv) => {
- const isProd = argv.mode === "production";
+ const isProd = argv.mode === 'production';
return {
- entry: isProd ? {} : "./src/index.tsx",
- mode: isProd ? "production" : "development",
+ entry: isProd ? {} : './src/index.tsx',
+ mode: isProd ? 'production' : 'development',
devServer: {
- port: 3001,
+ port: 3000,
headers: {
- "Access-Control-Allow-Origin": "*",
+ 'Access-Control-Allow-Origin': '*',
},
},
output: {
- filename: isProd ? undefined : "[name].[contenthash].js",
- chunkFilename: "[name].[contenthash].js",
clean: true,
- path: path.resolve(__dirname, "dist"),
- publicPath: `/api/v1/extensions/my-org/hello-world/`,
+ filename: isProd ? undefined : '[name].[contenthash].js',
+ chunkFilename: '[name].[contenthash].js',
+ path: path.resolve(__dirname, 'dist'),
+ publicPath:
`/api/v1/extensions/${extensionConfig.publisher}/${extensionConfig.name}/`,
},
resolve: {
- extensions: [".ts", ".tsx", ".js", ".jsx"],
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
// Map @apache-superset/core imports to window.superset at runtime
- externalsType: "window",
+ externalsType: 'window',
externals: {
- "@apache-superset/core": "superset",
+ '@apache-superset/core': 'superset',
},
module: {
rules: [
{
test: /\.tsx?$/,
- use: "ts-loader",
+ use: 'ts-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new ModuleFederationPlugin({
- name: "myOrg_helloWorld",
- filename: "remoteEntry.[contenthash].js",
+ name: 'myOrg_helloWorld',
+ filename: 'remoteEntry.[contenthash].js',
exposes: {
- "./index": "./src/index.tsx",
+ './index': './src/index.tsx',
},
shared: {
react: {
@@ -289,9 +283,14 @@ module.exports = (env, argv) => {
requiredVersion: packageConfig.peerDependencies.react,
import: false, // Use host's React, don't bundle
},
- "react-dom": {
+ 'react-dom': {
singleton: true,
- requiredVersion: packageConfig.peerDependencies["react-dom"],
+ requiredVersion: packageConfig.peerDependencies['react-dom'],
+ import: false,
+ },
+ antd: {
+ singleton: true,
+ requiredVersion: packageConfig.peerDependencies['antd'],
import: false,
},
},
@@ -306,8 +305,9 @@ module.exports = (env, argv) => {
```json
{
"compilerOptions": {
- "baseUrl": ".",
- "moduleResolution": "node",
+ "target": "es5",
+ "module": "esnext",
+ "moduleResolution": "node10",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
@@ -332,16 +332,16 @@ const HelloWorldPanel: React.FC = () => {
const [error, setError] = useState<string>('');
useEffect(() => {
- const fetchMessage = async () => {
- try {
- const csrfToken = await authentication.getCSRFToken();
- const response = await
fetch('/extensions/my-org/hello-world/message', {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRFToken': csrfToken!,
- },
- });
+ const fetchMessage = async () => {
+ try {
+ const csrfToken = await authentication.getCSRFToken();
+ const response = await fetch('/extensions/my-org/hello-world/message',
{
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': csrfToken!,
+ },
+ });
if (!response.ok) {
throw new Error(`Server returned ${response.status}`);
@@ -496,8 +496,8 @@ Superset will extract and validate the extension metadata,
load the assets, regi
Here's what happens when your extension loads:
-1. **Superset starts**: Reads `extension.json` and loads the backend entrypoint
-2. **Backend registration**: `entrypoint.py` imports your API class,
triggering the [`@api`](superset-core/src/superset_core/api/rest_api.py:59)
decorator to register it automatically
+1. **Superset starts**: Reads `manifest.json` from the `.supx` bundle and
loads the backend entrypoint
+2. **Backend registration**: `entrypoint.py` imports your API class,
triggering the [`@api`](superset-core/src/superset_core/rest_api/decorators.py)
decorator to register it automatically
3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry
file
4. **Module Federation**: Webpack loads your extension module and resolves
`@apache-superset/core` to `window.superset`
5. **Registration**: The module executes at load time, calling
`views.registerView` to register your panel
diff --git a/docs/developer_docs/extensions/registry.md
b/docs/developer_docs/extensions/registry.md
index 36c05f39803..9fbdb0f2074 100644
--- a/docs/developer_docs/extensions/registry.md
+++ b/docs/developer_docs/extensions/registry.md
@@ -30,15 +30,15 @@ This page serves as a registry of community-created
Superset extensions. These e
| Name
| Description
| Author | Preview
[...]
|
-------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| ------------------ |
---------------------------------------------------------------------------------------------------------
[...]
-| [Extensions API
Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer)
| A SQL Lab panel that demonstrates the Extensions API by providing
an interactive explorer for testing commands like getTabs, getCurrentTab, and
getDatabases. Useful for extension developers to understand and experiment with
the available APIs. | Michael S. Molina | <a
href="/img/extensions/api-explorer.png" target="_blank"><img
src="/img/extensions/api-explorer.png" al [...]
+| [Extensions API
Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api-explorer)
| A SQL Lab panel that demonstrates the Extensions API by providing
an interactive explorer for testing commands like getTabs, getCurrentTab, and
getDatabases. Useful for extension developers to understand and experiment with
the available APIs. | Michael S. Molina | <a
href="/img/extensions/api-explorer.png" target="_blank"><img
src="/img/extensions/api-explorer.png" al [...]
| [SQL Query Flow
Visualizer](https://github.com/msyavuz/superset-sql-visualizer)
| A SQL Lab panel that transforms SQL queries into
interactive flow diagrams, helping developers and analysts understand query
execution paths and data relationships.
| Mehmet Salih Yavuz | <a
href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img
src="/img/extensions/sql-flow-visu [...]
-| [SQL Lab Export to Google
Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets)
| A Superset extension that allows users to export SQL Lab query results
directly to Google Sheets.
| Michael S. Molina | <a
href="/img/extensions/gsheets-export.png" target="_blank"><img
src="/img/extensions/gsheets-export.png [...]
+| [SQL Lab Export to Google
Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab-gsheets)
| A Superset extension that allows users to export SQL Lab query results
directly to Google Sheets.
| Michael S. Molina | <a
href="/img/extensions/gsheets-export.png" target="_blank"><img
src="/img/extensions/gsheets-export.png [...]
| [SQL Lab Export to
Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet)
| Export SQL Lab query results directly to Apache Parquet format
with Snappy compression.
| Evan Rusackas | <a
href="/img/extensions/parquet-export.png" target="_blank"><img
src="/img/extensions/parquet-export.png [...]
-| [SQL Lab Query
Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison)
| A SQL Lab extension that enables side-by-side comparison of query
results across different tabs, with GitHub-style diff visualization showing
added/removed rows and columns.
| Michael S. Molina | <a
href="/img/extensions/query-comparison.png" target="_blank"><img
src="/img/extensions/query-comparison [...]
-| [SQL Lab Result
Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats)
| A SQL Lab extension that automatically computes statistics for
query results, providing type-aware analysis including numeric metrics (min,
max, mean, median, std dev), string analysis (length, empty counts), and date
range information. | Michael S. Molina | <a
href="/img/extensions/result-stats.png" target="_blank"><img
src="/img/extensions/result-stats.png" al [...]
-| [SQL
Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets)
| A SQL Lab extension that provides reusable SQL code
snippets, enabling quick insertion of commonly used code blocks such as license
headers, author information, and frequently used SQL patterns.
| Michael S. Molina | <a
href="/img/extensions/sql-snippets.png" target="_blank"><img
src="/img/extensions/sql-snippets.png" al [...]
-| [SQL Lab Query
Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator)
| A SQL Lab panel that analyzes query execution plans to estimate
resource impact, detect performance issues like Cartesian products and
high-cost operations, and visualize the query plan tree.
| Michael S. Molina | <a
href="/img/extensions/query-estimator.png" target="_blank"><img
src="/img/extensions/query-estimator.p [...]
-| [Editors
Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle)
| A Superset extension that demonstrates how to provide
custom code editors for different languages. This extension showcases the
editor contribution system by registering alternative editors that can replace
Superset's default Ace editor. | Michael S. Molina | <a
href="/img/extensions/editors-bundle.png" target="_blank"><img
src="/img/extensions/editors-bundle.pn [...]
+| [SQL Lab Query
Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query-comparison)
| A SQL Lab extension that enables side-by-side comparison of query
results across different tabs, with GitHub-style diff visualization showing
added/removed rows and columns.
| Michael S. Molina | <a
href="/img/extensions/query-comparison.png" target="_blank"><img
src="/img/extensions/query-comparison [...]
+| [SQL Lab Result
Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result-stats)
| A SQL Lab extension that automatically computes statistics for
query results, providing type-aware analysis including numeric metrics (min,
max, mean, median, std dev), string analysis (length, empty counts), and date
range information. | Michael S. Molina | <a
href="/img/extensions/result-stats.png" target="_blank"><img
src="/img/extensions/result-stats.png" al [...]
+| [Editor
Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/editor-snippets)
| A SQL Lab extension for managing and inserting reusable code
snippets into the editor, with server-side persistence per user.
| Michael S. Molina | <a
href="/img/extensions/editor-snippets.png" target="_blank"><img
src="/img/extensions/editor-snippets.pn [...]
+| [SQL Lab Query
Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query-estimator)
| A SQL Lab panel that analyzes query execution plans to estimate
resource impact, detect performance issues like Cartesian products and
high-cost operations, and visualize the query plan tree.
| Michael S. Molina | <a
href="/img/extensions/query-estimator.png" target="_blank"><img
src="/img/extensions/query-estimator.p [...]
+| [Editors
Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors-bundle)
| A Superset extension that demonstrates how to provide
custom code editors for different languages. This extension showcases the
editor contribution system by registering alternative editors that can replace
Superset's default Ace editor. | Michael S. Molina | <a
href="/img/extensions/editors-bundle.png" target="_blank"><img
src="/img/extensions/editors-bundle.pn [...]
## How to Add Your Extension
diff --git a/docs/scripts/generate-superset-components.mjs
b/docs/scripts/generate-superset-components.mjs
index 55ee639293f..b3de2ba1dad 100644
--- a/docs/scripts/generate-superset-components.mjs
+++ b/docs/scripts/generate-superset-components.mjs
@@ -152,8 +152,8 @@ const SOURCES = [
{
name: 'Extension Components',
path: 'packages/superset-core/src',
- importPrefix: '@apache-superset/core/ui',
- docImportPrefix: '@apache-superset/core/ui',
+ importPrefix: '@apache-superset/core/components',
+ docImportPrefix: '@apache-superset/core/components',
category: 'extension',
enabled: true,
extensionCompatible: true,
@@ -1155,7 +1155,7 @@ Help improve it by [editing the story
file](https://github.com/apache/superset/e
const CATEGORY_LABELS = {
ui: { title: 'Core Components', sidebarLabel: 'Core Components',
description: 'Buttons, inputs, modals, selects, and other fundamental UI
elements.' },
'design-system': { title: 'Layout Components', sidebarLabel: 'Layout
Components', description: 'Grid, Layout, Table, Flex, Space, and container
components for page structure.' },
- extension: { title: 'Extension Components', sidebarLabel: 'Extension
Components', description: 'Components available to extension developers via
@apache-superset/core/ui.' },
+ extension: { title: 'Extension Components', sidebarLabel: 'Extension
Components', description: 'Components available to extension developers via
@apache-superset/core/components.' },
};
/**
@@ -1463,7 +1463,7 @@ function
generateExtensionTypeDeclarations(extensionComponents) {
*/
/**
- * Type declarations for @apache-superset/core/ui
+ * Type declarations for @apache-superset/core/components
*
* AUTO-GENERATED by scripts/generate-superset-components.mjs
* Do not edit manually - regenerate by running: yarn
generate:superset-components
diff --git a/docs/src/components/StorybookWrapper.jsx
b/docs/src/components/StorybookWrapper.jsx
index 86b1569a872..1220b56add5 100644
--- a/docs/src/components/StorybookWrapper.jsx
+++ b/docs/src/components/StorybookWrapper.jsx
@@ -39,7 +39,7 @@ function getComponentRegistry() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
- const CoreUI = require('@apache-superset/core/ui');
+ const CoreUI = require('@apache-superset/core/components');
// Build component registry with antd as base fallback layer.
// Some Superset components (e.g., Typography) use styled-components that
may
@@ -65,7 +65,7 @@ function getProviders() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
- const { themeObject } = require('@apache-superset/core/ui');
+ const { themeObject } = require('@apache-superset/core/theme');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App, ConfigProvider } = require('antd');
diff --git a/docs/src/theme/Playground/Preview/index.tsx
b/docs/src/theme/Playground/Preview/index.tsx
index 42552c185b3..f3af23389bf 100644
--- a/docs/src/theme/Playground/Preview/index.tsx
+++ b/docs/src/theme/Playground/Preview/index.tsx
@@ -35,7 +35,7 @@ function getThemeWrapper() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
- const { themeObject } = require('@apache-superset/core/ui');
+ const { themeObject } = require('@apache-superset/core/theme');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App } = require('antd');
diff --git a/docs/src/theme/ReactLiveScope/index.tsx
b/docs/src/theme/ReactLiveScope/index.tsx
index 27d35cd6052..fade92f93cf 100644
--- a/docs/src/theme/ReactLiveScope/index.tsx
+++ b/docs/src/theme/ReactLiveScope/index.tsx
@@ -50,7 +50,7 @@ if (isBrowser) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
- const { Alert } = require('@apache-superset/core/ui');
+ const { Alert } = require('@apache-superset/core/components');
console.log('[ReactLiveScope] SupersetComponents keys:',
Object.keys(SupersetComponents || {}).slice(0, 10));
console.log('[ReactLiveScope] Has Button?', 'Button' in
(SupersetComponents || {}));
diff --git a/docs/src/types/apache-superset-core/index.d.ts
b/docs/src/types/apache-superset-core/index.d.ts
index 6e3a121e1b6..8dbc096ef75 100644
--- a/docs/src/types/apache-superset-core/index.d.ts
+++ b/docs/src/types/apache-superset-core/index.d.ts
@@ -18,7 +18,7 @@
*/
/**
- * Type declarations for @apache-superset/core/ui
+ * Type declarations for @apache-superset/core/components
*
* AUTO-GENERATED by scripts/generate-superset-components.mjs
* Do not edit manually - regenerate by running: yarn
generate:superset-components
diff --git a/docs/src/webpack.extend.ts b/docs/src/webpack.extend.ts
index 0a96ce2d1f5..c86498f09c0 100644
--- a/docs/src/webpack.extend.ts
+++ b/docs/src/webpack.extend.ts
@@ -156,9 +156,9 @@ export default function webpackExtendPlugin(): Plugin<void>
{
// to source so the docs build doesn't depend on pre-built lib/
artifacts.
// More specific sub-path aliases must come first; webpack matches
the
// longest prefix.
- '@apache-superset/core/ui': path.resolve(
+ '@apache-superset/core/components': path.resolve(
__dirname,
- '../../superset-frontend/packages/superset-core/src/ui',
+ '../../superset-frontend/packages/superset-core/src/components',
),
'@apache-superset/core/api/core': path.resolve(
__dirname,
diff --git a/docs/static/img/extensions/sql-snippets.png
b/docs/static/img/extensions/editor-snippets.png
similarity index 100%
rename from docs/static/img/extensions/sql-snippets.png
rename to docs/static/img/extensions/editor-snippets.png
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index cdfed3f9f56..ff0f5ac238c 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -14,10 +14,10 @@
"paths": {
"@superset-ui/core":
["../superset-frontend/packages/superset-ui-core/src"],
"@superset-ui/core/*":
["../superset-frontend/packages/superset-ui-core/src/*"],
- // Types for @apache-superset/core/ui are auto-generated by
scripts/generate-superset-components.mjs
+ // Types for @apache-superset/core/components are auto-generated by
scripts/generate-superset-components.mjs
// Runtime resolution uses webpack alias pointing to actual source (see
src/webpack.extend.ts)
// Using /ui path matches the established pattern used throughout the
Superset codebase
- "@apache-superset/core/ui": ["./src/types/apache-superset-core"],
+ "@apache-superset/core/components": ["./src/types/apache-superset-core"],
"*": ["src/*", "node_modules/*"]
}
},
diff --git a/superset-extensions-cli/src/superset_extensions_cli/cli.py
b/superset-extensions-cli/src/superset_extensions_cli/cli.py
index 18d45f4b682..3ff4f2b3a36 100644
--- a/superset-extensions-cli/src/superset_extensions_cli/cli.py
+++ b/superset-extensions-cli/src/superset_extensions_cli/cli.py
@@ -738,13 +738,7 @@ def init(
pyproject_toml =
env.get_template("backend/pyproject.toml.j2").render(ctx)
(backend_dir / "pyproject.toml").write_text(pyproject_toml)
- # Namespace package __init__.py (empty for namespace)
- (namespace_dir / "__init__.py").write_text("")
- (publisher_dir / "__init__.py").write_text("")
-
# Extension package files
- init_py =
env.get_template("backend/src/package/__init__.py.j2").render(ctx)
- (extension_package_dir / "__init__.py").write_text(init_py)
entrypoint_py =
env.get_template("backend/src/package/entrypoint.py.j2").render(
ctx
)
diff --git
a/superset-extensions-cli/src/superset_extensions_cli/templates/backend/src/package/__init__.py.j2
b/superset-extensions-cli/src/superset_extensions_cli/templates/backend/src/package/__init__.py.j2
deleted file mode 100644
index e69de29bb2d..00000000000