[release-11.6.5] Update npm build in daggerbuild & revert some script changes (#109352)

* update daggerbuild/frontend/npm.go

* combine lerna commands

* revert scripts changes
This commit is contained in:
Kevin Minehart 2025-08-07 13:09:33 -05:00 committed by GitHub
parent 2dbf020ca9
commit 2d2d6d1ad5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 34 additions and 488 deletions

View File

@ -12,22 +12,14 @@ import (
// NPMPackages versions and packs the npm packages into tarballs into `npm-packages` directory.
// It then returns the npm-packages directory as a dagger.Directory.
func NPMPackages(builder *dagger.Container, d *dagger.Client, log *slog.Logger, src *dagger.Directory, ersion string) (*dagger.Directory, error) {
// Check if the version of Grafana uses lerna or nx to manage package versioning.
var (
out = fmt.Sprintf("/src/npm-packages/%%s-%v.tgz", "v"+ersion)
lernaBuild = fmt.Sprintf("yarn run packages:build && yarn lerna version %s --exact --no-git-tag-version --no-push --force-publish -y", ersion)
lernaPack = fmt.Sprintf("yarn lerna exec --no-private -- yarn pack --out %s", out)
nxBuild = fmt.Sprintf("yarn run packages:build && yarn nx release version %s --no-git-commit --no-git-tag --no-stage-changes --group grafanaPackages", ersion)
nxPack = fmt.Sprintf("yarn nx exec --projects=$(cat nx.json | jq -r '.release.groups.grafanaPackages.projects | join(\",\")') -- yarn pack --out %s", out)
)
return builder.WithExec([]string{"mkdir", "npm-packages"}).
WithEnvVariable("SHELL", "/bin/bash").
WithExec([]string{"yarn", "install", "--immutable"}).
WithExec([]string{"/bin/bash", "-c", fmt.Sprintf("if [ -f lerna.json ]; then %s; else %s; fi", lernaBuild, nxBuild)}).
WithExec([]string{"/bin/bash", "-c", fmt.Sprintf("if [ -f lerna.json ]; then %s; else %s; fi", lernaPack, nxPack)}).
WithExec([]string{"/bin/bash", "-c", fmt.Sprintf("yarn run packages:build && yarn lerna version %s --exact --no-git-tag-version --no-push --force-publish -y && yarn lerna exec --no-private -- yarn pack --out %s", ersion, out)}).
Directory("./npm-packages"), nil
}

View File

@ -2,10 +2,9 @@ import { writeFile } from 'node:fs/promises';
import { resolve } from 'path';
import { createTheme } from '@grafana/data';
import { darkThemeVarsTemplate } from './themeTemplates/_variables.dark.scss.tmpl';
import { lightThemeVarsTemplate } from './themeTemplates/_variables.light.scss.tmpl';
import { commonThemeVarsTemplate } from './themeTemplates/_variables.scss.tmpl';
import { darkThemeVarsTemplate } from '@grafana/ui/src/themes/_variables.dark.scss.tmpl';
import { lightThemeVarsTemplate } from '@grafana/ui/src/themes/_variables.light.scss.tmpl';
import { commonThemeVarsTemplate } from '@grafana/ui/src/themes/_variables.scss.tmpl';
const darkThemeVariablesPath = resolve(__dirname, 'public', 'sass', '_variables.dark.generated.scss');
const lightThemeVariablesPath = resolve(__dirname, 'public', 'sass', '_variables.light.generated.scss');

View File

@ -30,8 +30,6 @@ const config: ConfigFile = {
'getDashboardByUid',
'getLibraryElementByUid',
'getResourceDependencies',
],
},
'../public/app/features/preferences/api/user/endpoints.gen.ts': {
@ -41,45 +39,25 @@ const config: ConfigFile = {
apiImport: 'baseAPI',
filterEndpoints: ['getUserPreferences', 'updateUserPreferences', 'patchUserPreferences'],
},
'../public/app/api/clients/iam/v0alpha1/endpoints.gen.ts': {
'../public/app/features/iam/api/endpoints.gen.ts': {
schemaFile: '../data/openapi/iam.grafana.app-v0alpha1.json',
apiFile: '../public/app/api/clients/iam/v0alpha1/baseAPI.ts',
apiFile: '../public/app/features/iam/api/api.ts',
apiImport: 'iamApi',
filterEndpoints: ['getDisplayMapping'],
exportName: 'generatedIamApi',
flattenArg: false,
tag: true,
},
'../public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts': {
apiFile: '../public/app/api/clients/provisioning/v0alpha1/baseAPI.ts',
'../public/app/features/provisioning/api/endpoints.gen.ts': {
apiFile: '../public/app/features/provisioning/api/baseAPI.ts',
schemaFile: '../data/openapi/provisioning.grafana.app-v0alpha1.json',
apiImport: 'baseAPI',
filterEndpoints,
argSuffix: 'Arg',
responseSuffix: 'Response',
tag: true,
hooks: true,
},
'../public/app/api/clients/folder/v1beta1/endpoints.gen.ts': {
apiFile: '../public/app/api/clients/folder/v1beta1/baseAPI.ts',
schemaFile: '../data/openapi/folder.grafana.app-v1beta1.json',
tag: true,
},
'../public/app/api/clients/advisor/v0alpha1/endpoints.gen.ts': {
apiFile: '../public/app/api/clients/advisor/v0alpha1/baseAPI.ts',
schemaFile: '../data/openapi/advisor.grafana.app-v0alpha1.json',
filterEndpoints: [
'createCheck',
'getCheck',
'listCheck',
'deleteCheck',
'updateCheck',
'listCheckType',
'updateCheckType',
],
tag: true,
},
'../public/app/api/clients/playlist/v0alpha1/endpoints.gen.ts': {
apiFile: '../public/app/api/clients/playlist/v0alpha1/baseAPI.ts',
schemaFile: '../data/openapi/playlist.grafana.app-v0alpha1.json',
filterEndpoints: ['listPlaylist', 'getPlaylist', 'createPlaylist', 'deletePlaylist', 'replacePlaylist'],
tag: true,
},
// PLOP_INJECT_API_CLIENT - Used by the API client generator
},
};

View File

@ -5,13 +5,7 @@ content_security_policy_template = """require-trusted-types-for 'script'; script
enable_frontend_sandbox_for_plugins = sandbox-app-test,sandbox-test-datasource,sandbox-test-panel
[feature_toggles]
publicDashboards=true
grafanaAPIServer=true
queryLibrary=true
queryService=true
[environment]
stack_id = 12345
enable = publicDashboards
[plugins]
allow_loading_unsigned_plugins=grafana-extensionstest-app,grafana-extensionexample1-app,grafana-extensionexample2-app,grafana-extensionexample3-app,grafana-e2etest-datasource
@ -25,6 +19,3 @@ max_open_conn = 2
[smtp]
enabled = true
host = localhost:7777
[cloud_migration]
developer_mode = true ; Enable developer mode to use in-memory implementations of 3rdparty services needed.

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
. scripts/grafana-server/variables

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
set -eo pipefail
. scripts/grafana-server/variables

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
DEFAULT_RUNDIR=scripts/grafana-server/tmp
RUNDIR=${RUNDIR:-$DEFAULT_RUNDIR}

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
set -eo pipefail
. scripts/grafana-server/variables

View File

@ -1,5 +1,6 @@
import PackageJson from '@npmcli/package-json';
import { mkdir } from 'node:fs/promises';
import { join, dirname } from 'node:path';
const cwd = process.cwd();
@ -7,17 +8,18 @@ try {
const pkgJson = await PackageJson.load(cwd);
const cjsIndex = pkgJson.content.publishConfig?.main ?? pkgJson.content.main;
const esmIndex = pkgJson.content.publishConfig?.module ?? pkgJson.content.module;
const typesIndex = pkgJson.content.publishConfig?.types ?? pkgJson.content.types;
const cjsTypes = pkgJson.content.publishConfig?.types ?? pkgJson.content.types;
const esmTypes = `./${join(dirname(esmIndex), 'index.d.mts')}`;
const exports = {
'./package.json': './package.json',
'.': {
import: {
types: typesIndex,
types: esmTypes,
default: esmIndex,
},
require: {
types: typesIndex,
types: cjsTypes,
default: cjsIndex,
},
},
@ -31,17 +33,9 @@ try {
};
}
// Fix for @grafana/i18n so eslint-plugin can be imported by consumers
if (pkgJson.content.name === '@grafana/i18n') {
exports['./eslint-plugin'] = {
import: './dist/eslint/index.cjs',
require: './dist/eslint/index.cjs',
};
}
pkgJson.update({
main: cjsIndex,
types: typesIndex,
types: cjsTypes,
module: esmIndex,
exports,
});
@ -58,12 +52,12 @@ try {
...pkgJson.content.exports,
[`./${aliasName}`]: {
import: {
types: typesIndex.replace('index', aliasName),
types: esmTypes.replace('index', aliasName),
default: esmIndex.replace('index', aliasName),
},
require: {
types: typesIndex.replace('index', aliasName),
default: cjsIndex.replace('index', aliasName),
types: cjsTypes.replace('index', aliasName),
default: cjsTypes.replace('index', aliasName),
},
},
},
@ -86,7 +80,7 @@ async function createAliasPackageJsonFiles(packageJsonContent, aliasName) {
const pkgJson = await PackageJson.create(pkgJsonPath, {
data: {
name: pkgName,
types: `../dist/types/${aliasName}.d.ts`,
types: `../dist/cjs/${aliasName}.d.cts`,
main: `../dist/cjs/${aliasName}.cjs`,
module: `../dist/esm/${aliasName}.mjs`,
},

View File

@ -1,52 +0,0 @@
# RTK Query API Client Generator
This generator automates the process of creating RTK Query API clients for Grafana's API groups. It replaces the manual steps outlined in the [main API documentation](../../public/app/api/README.md).
## Usage
```bash
yarn generate:api-client
```
The CLI will prompt for:
1. **Enterprise or OSS API** - Whether this is an Enterprise or OSS API. This affects paths and build commands.
2. **API group name** - The basic name for the API (e.g., `dashboard`)
3. **API group** - The full API group name (defaults to `<group-name>.grafana.app`)
4. **API version** - The API version (e.g., `v0alpha1`)
5. **Reducer path** - The Redux reducer path (defaults to `<group-name>API`). This will also be used as the API's named export.
6. **Endpoints** - Optional comma-separated list of endpoints to include (e.g., `createDashboard,updateDashboard`). If not provided, all endpoints will be included.
## What It Does
The generator automates the following:
1. Creates the `baseAPI.ts` file for the API group
2. Updates the appropriate generate script to include the API client
- `scripts/generate-rtk-apis.ts` for OSS APIs
- `local/generate-enterprise-apis.ts` for Enterprise APIs
3. Creates the `index.ts` file with proper exports
4. For OSS APIs only: Registers Redux reducers and middleware in the store. For Enterprise this needs to be done manually
5. Formats all generated files using Prettier and ESLint
6. Automatically runs the appropriate command to generate endpoints from the OpenAPI schema
## Limitations
- The generator is optimized for Kubernetes-style APIs, as it requires Kubernetes resource details. For legacy APIs, manual adjustments may be needed.
- It expects processed OpenAPI specifications to exist in the `openapi_snapshots` directory
## Troubleshooting
### Missing OpenAPI Schema
If an error about a missing OpenAPI schema appears, check that:
1. The API group and version exist in the backend
2. The `TestIntegrationOpenAPIs` test has been run to generate the schema (step 1 in the [main API documentation](../../public/app/api/README.md)).
3. The schema file exists at `data/openapi/<group>-<version>.json`
### Validation Errors
- API group must include `.grafana.app`
- Version must be in format `v0alpha1`, `v1beta2`, etc.
- Reducer path must end with `API`

View File

@ -1,115 +0,0 @@
import { execSync } from 'child_process';
import path from 'path';
type PlopActionFunction = (
answers: Record<string, unknown>,
config?: Record<string, unknown>
) => string | Promise<string>;
// Helper to remove quotes from operation IDs
export const removeQuotes = (str: string | unknown) => {
if (typeof str !== 'string') {
return str;
}
return str.replace(/^['"](.*)['"]$/, '$1');
};
export const formatEndpoints = () => (endpointsInput: string | string[]) => {
if (Array.isArray(endpointsInput)) {
return endpointsInput.map((op) => `'${removeQuotes(op)}'`).join(', ');
}
// Handle string input (comma-separated)
if (typeof endpointsInput === 'string') {
const endpointsArray = endpointsInput
.split(',')
.map((id) => id.trim())
.filter(Boolean);
return endpointsArray.map((op) => `'${removeQuotes(op)}'`).join(', ');
}
return '';
};
// List of created or modified files
export const getFilesToFormat = (groupName: string, version: string, isEnterprise = false) => {
const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients';
const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts';
return [
`${apiClientBasePath}/${groupName}/${version}/baseAPI.ts`,
`${apiClientBasePath}/${groupName}/${version}/index.ts`,
generateScriptPath,
...(isEnterprise ? [] : [`public/app/core/reducers/root.ts`, `public/app/store/configureStore.ts`]),
];
};
export const runGenerateApis =
(basePath: string): PlopActionFunction =>
(answers, config) => {
try {
const isEnterprise = answers.isEnterprise || (config && config.isEnterprise);
let command;
if (isEnterprise) {
command = 'yarn process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts';
} else {
command = 'yarn generate-apis';
}
console.log(`⏳ Running ${command} to generate endpoints...`);
execSync(command, { stdio: 'inherit', cwd: basePath });
return '✅ API endpoints generated successfully!';
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('❌ Failed to generate API endpoints:', errorMessage);
return '❌ Failed to generate API endpoints. See error above.';
}
};
export const formatFiles =
(basePath: string): PlopActionFunction =>
(_, config) => {
if (!config || !Array.isArray(config.files)) {
console.error('Invalid config passed to formatFiles action');
return '❌ Formatting failed: Invalid configuration';
}
const filesToFormat = config.files.map((file: string) => path.join(basePath, file));
try {
const filesList = filesToFormat.map((file: string) => `"${file}"`).join(' ');
console.log('🧹 Running ESLint on generated/modified files...');
try {
execSync(`yarn eslint --fix ${filesList}`, { cwd: basePath });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`⚠️ Warning: ESLint encountered issues: ${errorMessage}`);
}
console.log('🧹 Running Prettier on generated/modified files...');
try {
// '--ignore-path' is necessary so the gitignored files ('local/' folder) can still be formatted
execSync(`yarn prettier --write ${filesList} --ignore-path=./.prettierignore`, { cwd: basePath });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`⚠️ Warning: Prettier encountered issues: ${errorMessage}`);
}
return '✅ Files linted and formatted successfully!';
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('⚠️ Warning: Formatting operations failed:', errorMessage);
return '⚠️ Warning: Formatting operations failed.';
}
};
export const validateGroup = (group: string) => {
return group && group.includes('.grafana.app') ? true : 'Group should be in format: name.grafana.app';
};
export const validateVersion = (version: string) => {
return version && /^v\d+[a-z]*\d+$/.test(version) ? true : 'Version should be in format: v0alpha1, v1beta2, etc.';
};

View File

@ -1,167 +0,0 @@
import path from 'path';
import type { NodePlopAPI, PlopGeneratorConfig } from 'plop';
import {
formatEndpoints,
validateGroup,
validateVersion,
getFilesToFormat,
runGenerateApis,
formatFiles,
// The file extension is necessary to make the imports
// work with the '--experimental-strip-types' flag
// @ts-ignore
} from './helpers.ts';
// @ts-ignore
import { type ActionConfig, type PlopData, isPlopData } from './types.ts';
export default function plopGenerator(plop: NodePlopAPI) {
// Grafana root path
const basePath = path.resolve(import.meta.dirname, '../..');
// Register custom action types
plop.setActionType('runGenerateApis', runGenerateApis(basePath));
plop.setActionType('formatFiles', formatFiles(basePath));
// Used in templates to format endpoints
plop.setHelper('formatEndpoints', formatEndpoints());
const generateRtkApiActions = (data: PlopData) => {
const { reducerPath, groupName, version, isEnterprise } = data;
const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients';
const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts';
// Using app path, so the imports work on any file level
const clientImportPath = isEnterprise ? '../extensions/api/clients' : 'app/api/clients';
const apiPathPrefix = isEnterprise ? '../public/app/extensions/api/clients' : '../public/app/api/clients';
const templateData = {
...data,
apiPathPrefix,
};
// Base actions that are always added
const actions: ActionConfig[] = [
{
type: 'add',
path: path.join(basePath, `${apiClientBasePath}/${groupName}/${version}/baseAPI.ts`),
templateFile: './templates/baseAPI.ts.hbs',
},
{
type: 'modify',
path: path.join(basePath, generateScriptPath),
pattern: '// PLOP_INJECT_API_CLIENT - Used by the API client generator',
templateFile: './templates/config-entry.hbs',
data: templateData,
},
{
type: 'add',
path: path.join(basePath, `${apiClientBasePath}/${groupName}/${version}/index.ts`),
templateFile: './templates/index.ts.hbs',
},
];
// Only add redux reducer and middleware for OSS clients
if (!isEnterprise) {
actions.push(
{
type: 'modify',
path: path.join(basePath, 'public/app/core/reducers/root.ts'),
pattern: '// PLOP_INJECT_IMPORT',
template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}/${version}';\n// PLOP_INJECT_IMPORT`,
},
{
type: 'modify',
path: path.join(basePath, 'public/app/core/reducers/root.ts'),
pattern: '// PLOP_INJECT_REDUCER',
template: `[${reducerPath}.reducerPath]: ${reducerPath}.reducer,\n // PLOP_INJECT_REDUCER`,
},
{
type: 'modify',
path: path.join(basePath, 'public/app/store/configureStore.ts'),
pattern: '// PLOP_INJECT_IMPORT',
template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}/${version}';\n// PLOP_INJECT_IMPORT`,
},
{
type: 'modify',
path: path.join(basePath, 'public/app/store/configureStore.ts'),
pattern: '// PLOP_INJECT_MIDDLEWARE',
template: `${reducerPath}.middleware,\n // PLOP_INJECT_MIDDLEWARE`,
}
);
}
// Add formatting and generation actions
actions.push(
{
type: 'formatFiles',
files: getFilesToFormat(groupName, version, isEnterprise),
},
{
type: 'runGenerateApis',
isEnterprise,
}
);
return actions;
};
const generator: PlopGeneratorConfig = {
description: 'Generate RTK Query API client for a Grafana API group',
prompts: [
{
type: 'confirm',
name: 'isEnterprise',
message: 'Is this a Grafana Enterprise API?',
default: false,
},
{
type: 'input',
name: 'groupName',
message: 'API group name (e.g. dashboard):',
validate: (input: string) => (input?.trim() ? true : 'Group name is required'),
},
{
type: 'input',
name: 'group',
message: 'API group (e.g. dashboard.grafana.app):',
default: (answers: { groupName?: string }) => `${answers.groupName}.grafana.app`,
validate: validateGroup,
},
{
type: 'input',
name: 'version',
message: 'API version (e.g. v0alpha1):',
default: 'v0alpha1',
validate: validateVersion,
},
{
type: 'input',
name: 'reducerPath',
message: 'Reducer path (e.g. dashboardAPIv0alpha1):',
default: (answers: { groupName?: string; version?: string }) => `${answers.groupName}API${answers.version}`,
validate: (input: string) =>
input?.endsWith('API') || input?.match(/API[a-z]\d+[a-z]*\d*$/)
? true
: 'Reducer path should end with "API" or "API<version>" (e.g. dashboardAPI, dashboardAPIv0alpha1)',
},
{
type: 'input',
name: 'endpoints',
message: 'Endpoints to include (comma-separated, optional):',
validate: () => true,
},
],
actions: function (data) {
if (!isPlopData(data)) {
throw new Error('Invalid data format received from prompts');
}
return generateRtkApiActions(data);
},
};
plop.setGenerator('rtk-api-client', generator);
}

View File

@ -1,14 +0,0 @@
import { createApi } from '@reduxjs/toolkit/query/react';
import { createBaseQuery } from 'app/api/createBaseQuery';
import { getAPIBaseURL } from 'app/api/utils';
export const BASE_URL = getAPIBaseURL('{{group}}', '{{version}}');
export const api = createApi({
reducerPath: '{{reducerPath}}',
baseQuery: createBaseQuery({
baseURL: BASE_URL,
}),
endpoints: () => ({}),
});

View File

@ -1,9 +0,0 @@
'{{apiPathPrefix}}/{{groupName}}/{{version}}/endpoints.gen.ts': {
apiFile: '{{apiPathPrefix}}/{{groupName}}/{{version}}/baseAPI.ts',
schemaFile: '../data/openapi/{{group}}-{{version}}.json',
{{#if endpoints}}
filterEndpoints: [{{{formatEndpoints endpoints}}}],
{{/if}}
tag: true,
},
// PLOP_INJECT_API_CLIENT - Used by the API client generator

View File

@ -1,3 +0,0 @@
import { generatedAPI } from './endpoints.gen';
export const {{reducerPath}} = generatedAPI.enhanceEndpoints({});

View File

@ -1,27 +0,0 @@
import type { AddActionConfig, ModifyActionConfig } from 'plop';
export interface FormatFilesActionConfig {
type: 'formatFiles';
files: string[];
}
export interface RunGenerateApisActionConfig {
type: 'runGenerateApis';
isEnterprise: boolean;
}
// Union type of all possible action configs
export type ActionConfig = AddActionConfig | ModifyActionConfig | FormatFilesActionConfig | RunGenerateApisActionConfig;
export interface PlopData {
groupName: string;
group: string;
version: string;
reducerPath: string;
endpoints: string;
isEnterprise: boolean;
}
export function isPlopData(data: unknown): data is PlopData {
return typeof data === 'object' && data !== null;
}

View File

@ -1,4 +1,3 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
@ -69,14 +68,6 @@ module.exports = {
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'public/img',
to: 'img',
},
],
}),
],
module: {
rules: [

View File

@ -4,7 +4,6 @@ const browserslist = require('browserslist');
const { resolveToEsbuildTarget } = require('esbuild-plugin-browserslist');
const ESLintPlugin = require('eslint-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const fs = require('fs');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const { DefinePlugin, EnvironmentPlugin } = require('webpack');
@ -30,21 +29,6 @@ function getDecoupledPlugins() {
return packages.filter((pkg) => pkg.dir.includes('plugins/datasource')).map((pkg) => `${pkg.dir}/**`);
}
// When linking scenes for development, resolve the path to the src directory for sourcemaps
function scenesModule() {
const scenesPath = path.resolve('./node_modules/@grafana/scenes');
try {
const status = fs.lstatSync(scenesPath);
if (status.isSymbolicLink()) {
console.log(`scenes is linked to local scenes repo`);
return path.resolve(scenesPath + '/src');
}
} catch (error) {
console.error(`Error checking scenes path: ${error.message}`);
}
return scenesPath;
}
const envConfig = getEnvConfig();
module.exports = (env = {}) => {
@ -68,10 +52,14 @@ module.exports = (env = {}) => {
// Packages linked for development need react to be resolved from the same location
react: path.resolve('./node_modules/react'),
// Also Grafana packages need to be resolved from the same location so they share
// the same singletons
'@grafana/runtime': path.resolve(__dirname, '../../packages/grafana-runtime'),
'@grafana/data': path.resolve(__dirname, '../../packages/grafana-data'),
// This is required to correctly resolve react-router-dom when linking with
// local version of @grafana/scenes
'react-router-dom': path.resolve('./node_modules/react-router-dom'),
'@grafana/scenes': scenesModule(),
},
},