@grafana/react-detect 0.6.2 → 0.6.3-canary.2558.23891449991.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/run.js CHANGED
@@ -5,7 +5,7 @@ import { detect19 } from '../commands/detect19.js';
5
5
  const args = process.argv.slice(2);
6
6
  const argv = minimist(args, {
7
7
  boolean: ["json", "skipBuildTooling", "skipDependencies", "noErrorExitCode"],
8
- string: ["pluginRoot"],
8
+ string: ["pluginRoot", "distDir"],
9
9
  default: {
10
10
  json: false,
11
11
  skipBuildTooling: false,
@@ -1,3 +1,4 @@
1
+ import { join } from 'node:path';
1
2
  import { findSourceMapFiles } from '../file-scanner.js';
2
3
  import { generateAnalysisResults } from '../results.js';
3
4
  import { DependencyContext } from '../utils/dependencies.js';
@@ -10,10 +11,11 @@ import { output } from '../utils/output.js';
10
11
  async function detect19(argv) {
11
12
  try {
12
13
  const pluginRoot = argv.pluginRoot || process.cwd();
14
+ const distDir = argv.distDir || join(pluginRoot, "dist");
13
15
  const skipDependencies = argv.skipDependencies || false;
14
16
  const skipBuildTooling = argv.skipBuildTooling || false;
15
17
  const jsonOutput = argv.json || false;
16
- const allMatches = await getAllMatches(pluginRoot);
18
+ const allMatches = await getAllMatches(distDir);
17
19
  let depContext = null;
18
20
  if (!skipDependencies) {
19
21
  depContext = new DependencyContext();
@@ -39,7 +41,7 @@ async function detect19(argv) {
39
41
  }
40
42
  return match;
41
43
  });
42
- const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext, {
44
+ const results = generateAnalysisResults(matchesWithRootDependency, distDir, depContext, {
43
45
  skipBuildTooling,
44
46
  skipDependencies
45
47
  });
@@ -61,10 +63,10 @@ async function detect19(argv) {
61
63
  process.exit(1);
62
64
  }
63
65
  }
64
- async function getAllMatches(pluginRoot) {
65
- const sourcemapPaths = await findSourceMapFiles(pluginRoot);
66
+ async function getAllMatches(distDir) {
67
+ const sourcemapPaths = await findSourceMapFiles(distDir);
66
68
  if (sourcemapPaths.length === 0) {
67
- throw new Error("No source map files found in dist directory. Make sure to build your plugin first.");
69
+ throw new Error(`No source map files found in "${distDir}". Make sure to build your plugin first.`);
68
70
  }
69
71
  const sources = await extractAllSources(sourcemapPaths);
70
72
  if (sources.length === 0) {
@@ -1,18 +1,16 @@
1
1
  import fg from 'fast-glob';
2
- import { join } from 'node:path';
3
2
 
4
- async function findSourceMapFiles(directory) {
5
- const distDirectory = join(directory, "dist");
3
+ async function findSourceMapFiles(distDir) {
6
4
  try {
7
5
  const files = await fg("**/*.js.map", {
8
- cwd: distDirectory,
6
+ cwd: distDir,
9
7
  absolute: true,
10
8
  ignore: ["**/node_modules/**"]
11
9
  });
12
10
  return files;
13
11
  } catch (error) {
14
12
  throw new Error(
15
- `Error finding source map files in ${distDirectory}: ${error instanceof Error ? error.message : "Unknown error"}`
13
+ `Error finding source map files in "${distDir}": ${error instanceof Error ? error.message : "Unknown error"}`
16
14
  );
17
15
  }
18
16
  }
package/dist/results.js CHANGED
@@ -3,9 +3,9 @@ import { getPluginJson, hasExternalisedJsxRuntime } from './utils/plugin.js';
3
3
  import { isExternal } from './utils/dependencies.js';
4
4
  import path from 'node:path';
5
5
 
6
- function generateAnalysisResults(matches, pluginRoot, depContext, options = { skipBuildTooling: false, skipDependencies: false }) {
6
+ function generateAnalysisResults(matches, distDir, depContext, options = { skipBuildTooling: false, skipDependencies: false }) {
7
7
  const filtered = filterMatches(matches, options.skipBuildTooling);
8
- const pluginJson = getPluginJson(pluginRoot);
8
+ const pluginJson = getPluginJson(distDir);
9
9
  const filteredWithoutExternals = filtered.filter((match) => shouldIncludeDependencyMatch(match));
10
10
  const sourceMatches = filteredWithoutExternals.filter((m) => m.type === "source");
11
11
  const dependencyMatches = filteredWithoutExternals.filter((m) => m.type === "dependency");
@@ -4,12 +4,11 @@ import { parseFile } from '../parser.js';
4
4
  import { walk } from './ast.js';
5
5
 
6
6
  let cachedPluginJson = null;
7
- function getPluginJson(dir) {
7
+ function getPluginJson(distDir) {
8
8
  if (cachedPluginJson) {
9
9
  return cachedPluginJson;
10
10
  }
11
- const srcPath = dir ? path.join(dir, "dist") : path.join(process.cwd(), "dist");
12
- const pluginJsonPath = path.join(srcPath, "plugin.json");
11
+ const pluginJsonPath = path.join(distDir, "plugin.json");
13
12
  cachedPluginJson = readJsonFile(pluginJsonPath);
14
13
  return cachedPluginJson;
15
14
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grafana/react-detect",
3
3
  "description": "Run various checks to detect if a Grafana plugin is compatible with React.",
4
- "version": "0.6.2",
4
+ "version": "0.6.3-canary.2558.23891449991.0",
5
5
  "repository": {
6
6
  "directory": "packages/react-detect",
7
7
  "url": "https://github.com/grafana/plugin-tools"
@@ -41,5 +41,5 @@
41
41
  "engines": {
42
42
  "node": ">=20"
43
43
  },
44
- "gitHead": "def49813c11febf54299c9541b0eeb243fb4e495"
44
+ "gitHead": "2cb04b2c307bd2e796b10e3cb1b672801c06ec57"
45
45
  }
package/src/bin/run.ts CHANGED
@@ -6,7 +6,7 @@ import { detect19 } from '../commands/detect19.js';
6
6
  const args = process.argv.slice(2);
7
7
  const argv = minimist(args, {
8
8
  boolean: ['json', 'skipBuildTooling', 'skipDependencies', 'noErrorExitCode'],
9
- string: ['pluginRoot'],
9
+ string: ['pluginRoot', 'distDir'],
10
10
  default: {
11
11
  json: false,
12
12
  skipBuildTooling: false,
@@ -1,4 +1,5 @@
1
1
  import minimist from 'minimist';
2
+ import { join } from 'node:path';
2
3
  import { findSourceMapFiles } from '../file-scanner.js';
3
4
  import { generateAnalysisResults } from '../results.js';
4
5
  import { DependencyContext } from '../utils/dependencies.js';
@@ -13,11 +14,12 @@ import { output } from '../utils/output.js';
13
14
  export async function detect19(argv: minimist.ParsedArgs) {
14
15
  try {
15
16
  const pluginRoot = argv.pluginRoot || process.cwd();
17
+ const distDir = argv.distDir || join(pluginRoot, 'dist');
16
18
  const skipDependencies = argv.skipDependencies || false;
17
19
  const skipBuildTooling = argv.skipBuildTooling || false;
18
20
  const jsonOutput = argv.json || false;
19
21
 
20
- const allMatches = await getAllMatches(pluginRoot);
22
+ const allMatches = await getAllMatches(distDir);
21
23
 
22
24
  // Conditionally load dependencies
23
25
  let depContext: DependencyContext | null = null;
@@ -52,7 +54,7 @@ export async function detect19(argv: minimist.ParsedArgs) {
52
54
  return match;
53
55
  });
54
56
 
55
- const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext, {
57
+ const results = generateAnalysisResults(matchesWithRootDependency, distDir, depContext, {
56
58
  skipBuildTooling,
57
59
  skipDependencies,
58
60
  });
@@ -77,11 +79,11 @@ export async function detect19(argv: minimist.ParsedArgs) {
77
79
  }
78
80
  }
79
81
 
80
- async function getAllMatches(pluginRoot: string) {
81
- const sourcemapPaths = await findSourceMapFiles(pluginRoot);
82
+ async function getAllMatches(distDir: string) {
83
+ const sourcemapPaths = await findSourceMapFiles(distDir);
82
84
 
83
85
  if (sourcemapPaths.length === 0) {
84
- throw new Error('No source map files found in dist directory. Make sure to build your plugin first.');
86
+ throw new Error(`No source map files found in "${distDir}". Make sure to build your plugin first.`);
85
87
  }
86
88
 
87
89
  const sources = await extractAllSources(sourcemapPaths);
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { findSourceMapFiles } from './file-scanner.js';
3
+ import { mkdtempSync, writeFileSync, mkdirSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ describe('findSourceMapFiles', () => {
8
+ it('searches the given directory directly without appending a dist subdirectory', async () => {
9
+ const dir = mkdtempSync(join(tmpdir(), 'react-detect-test-'));
10
+ writeFileSync(join(dir, 'module.js.map'), '{}');
11
+
12
+ const files = await findSourceMapFiles(dir);
13
+
14
+ expect(files).toHaveLength(1);
15
+ expect(files[0]).toContain('module.js.map');
16
+ });
17
+
18
+ it('finds source map files recursively within the given directory', async () => {
19
+ const dir = mkdtempSync(join(tmpdir(), 'react-detect-test-'));
20
+ mkdirSync(join(dir, 'nested'));
21
+ writeFileSync(join(dir, 'a.js.map'), '{}');
22
+ writeFileSync(join(dir, 'nested', 'b.js.map'), '{}');
23
+
24
+ const files = await findSourceMapFiles(dir);
25
+
26
+ expect(files).toHaveLength(2);
27
+ });
28
+
29
+ it('returns empty array when no source map files exist', async () => {
30
+ const dir = mkdtempSync(join(tmpdir(), 'react-detect-test-'));
31
+
32
+ const files = await findSourceMapFiles(dir);
33
+
34
+ expect(files).toHaveLength(0);
35
+ });
36
+ });
@@ -1,19 +1,16 @@
1
1
  import fg from 'fast-glob';
2
- import { join } from 'node:path';
3
-
4
- export async function findSourceMapFiles(directory: string): Promise<string[]> {
5
- const distDirectory = join(directory, 'dist');
6
2
 
3
+ export async function findSourceMapFiles(distDir: string): Promise<string[]> {
7
4
  try {
8
5
  const files = await fg('**/*.js.map', {
9
- cwd: distDirectory,
6
+ cwd: distDir,
10
7
  absolute: true,
11
8
  ignore: ['**/node_modules/**'],
12
9
  });
13
10
  return files;
14
11
  } catch (error) {
15
12
  throw new Error(
16
- `Error finding source map files in ${distDirectory}: ${error instanceof Error ? error.message : 'Unknown error'}`
13
+ `Error finding source map files in "${distDir}": ${error instanceof Error ? error.message : 'Unknown error'}`
17
14
  );
18
15
  }
19
16
  }
@@ -36,7 +36,7 @@ describe('generateAnalysisResults', () => {
36
36
  bundledFilePath: `node_modules/${packageName}/index.js`,
37
37
  });
38
38
 
39
- const pluginRoot = process.cwd();
39
+ const distDir = process.cwd();
40
40
  const depContext = new DependencyContext();
41
41
  const options: AnalysisOptions = {
42
42
  skipBuildTooling: true,
@@ -64,7 +64,7 @@ describe('generateAnalysisResults', () => {
64
64
  bundledFilePath: 'src/components/MyComponent.tsx',
65
65
  };
66
66
  const matches: AnalyzedMatch[] = [sourceMatch, createDependencyMatch('react', 'react')];
67
- const results = generateAnalysisResults(matches, pluginRoot, depContext, options);
67
+ const results = generateAnalysisResults(matches, distDir, depContext, options);
68
68
 
69
69
  expect(results.summary.sourceIssuesCount).toBe(1);
70
70
  expect(results.summary.dependencyIssuesCount).toBe(0);
@@ -75,7 +75,7 @@ describe('generateAnalysisResults', () => {
75
75
  createDependencyMatch('lodash', 'lodash'), // lodash is externalized by Grafana
76
76
  createDependencyMatch('axios', 'axios'),
77
77
  ];
78
- const results = generateAnalysisResults(matches, pluginRoot, depContext, options);
78
+ const results = generateAnalysisResults(matches, distDir, depContext, options);
79
79
 
80
80
  expect(results.issues.dependencies).toHaveLength(1);
81
81
  expect(results.issues.dependencies[0].packageName).toBe('axios');
@@ -87,7 +87,7 @@ describe('generateAnalysisResults', () => {
87
87
  createDependencyMatch('@grafana/ui', '@grafana/ui'),
88
88
  createDependencyMatch('@custom/package', '@custom/package'),
89
89
  ];
90
- const results = generateAnalysisResults(matches, pluginRoot, depContext, options);
90
+ const results = generateAnalysisResults(matches, distDir, depContext, options);
91
91
 
92
92
  expect(results.issues.dependencies).toHaveLength(1);
93
93
  expect(results.issues.dependencies[0].packageName).toBe('@custom/package');
@@ -98,7 +98,7 @@ describe('generateAnalysisResults', () => {
98
98
  createDependencyMatch('@grafana/data/utils', '@grafana/data'),
99
99
  createDependencyMatch('@custom/package/utils', '@custom/package'),
100
100
  ];
101
- const results = generateAnalysisResults(matches, pluginRoot, depContext, options);
101
+ const results = generateAnalysisResults(matches, distDir, depContext, options);
102
102
 
103
103
  expect(results.issues.dependencies).toHaveLength(1);
104
104
  expect(results.issues.dependencies[0].packageName).toBe('@custom/package/utils');
@@ -111,7 +111,7 @@ describe('generateAnalysisResults', () => {
111
111
  createDependencyMatch('scheduler', 'react'),
112
112
  createDependencyMatch('debug', 'axios'), // Non-externalized root dependency
113
113
  ];
114
- const results = generateAnalysisResults(matches, pluginRoot, depContext, options);
114
+ const results = generateAnalysisResults(matches, distDir, depContext, options);
115
115
 
116
116
  expect(results.issues.dependencies).toHaveLength(1);
117
117
  expect(results.issues.dependencies[0].packageName).toBe('debug');
@@ -125,7 +125,7 @@ describe('generateAnalysisResults', () => {
125
125
  createDependencyMatch('@grafana/data', '@grafana/data'),
126
126
  createDependencyMatch('axios', 'axios'),
127
127
  ];
128
- const results = generateAnalysisResults(matches, pluginRoot, depContext, options);
128
+ const results = generateAnalysisResults(matches, distDir, depContext, options);
129
129
  // Filter to only critical dep issues
130
130
  const reportedDeps = results.issues.critical.filter((i) => i.location.type === 'dependency');
131
131
  expect(reportedDeps).toHaveLength(1);
package/src/results.ts CHANGED
@@ -12,12 +12,12 @@ export interface AnalysisOptions {
12
12
 
13
13
  export function generateAnalysisResults(
14
14
  matches: AnalyzedMatch[],
15
- pluginRoot: string,
15
+ distDir: string,
16
16
  depContext: DependencyContext | null,
17
17
  options: AnalysisOptions = { skipBuildTooling: false, skipDependencies: false }
18
18
  ): PluginAnalysisResults {
19
19
  const filtered = filterMatches(matches, options.skipBuildTooling);
20
- const pluginJson = getPluginJson(pluginRoot);
20
+ const pluginJson = getPluginJson(distDir);
21
21
 
22
22
  // Filter out externalized dependencies
23
23
  const filteredWithoutExternals = filtered.filter((match) => shouldIncludeDependencyMatch(match, depContext));
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ // Reset module cache between tests since getPluginJson caches internally
7
+ beforeEach(async () => {
8
+ const { resetPluginJsonCache } = await import('./plugin.js');
9
+ resetPluginJsonCache();
10
+ });
11
+
12
+ describe('getPluginJson', () => {
13
+ it('reads plugin.json from the given distDir directly without appending /dist', async () => {
14
+ const distDir = mkdtempSync(join(tmpdir(), 'react-detect-plugin-test-'));
15
+ const pluginJson = { id: 'my-plugin', name: 'My Plugin', type: 'app', info: { version: '2.0.0' } };
16
+ writeFileSync(join(distDir, 'plugin.json'), JSON.stringify(pluginJson));
17
+
18
+ const { getPluginJson } = await import('./plugin.js');
19
+ const result = getPluginJson(distDir);
20
+
21
+ expect(result?.id).toBe('my-plugin');
22
+ expect(result?.info.version).toBe('2.0.0');
23
+ });
24
+
25
+ it('throws when plugin.json does not exist in the given distDir', async () => {
26
+ const distDir = mkdtempSync(join(tmpdir(), 'react-detect-plugin-test-'));
27
+
28
+ const { getPluginJson } = await import('./plugin.js');
29
+
30
+ expect(() => getPluginJson(distDir)).toThrow('plugin.json');
31
+ });
32
+ });
@@ -14,13 +14,16 @@ interface PluginJson {
14
14
 
15
15
  let cachedPluginJson: PluginJson | null = null;
16
16
 
17
- export function getPluginJson(dir?: string) {
17
+ export function resetPluginJsonCache() {
18
+ cachedPluginJson = null;
19
+ }
20
+
21
+ export function getPluginJson(distDir: string) {
18
22
  if (cachedPluginJson) {
19
23
  return cachedPluginJson;
20
24
  }
21
25
 
22
- const srcPath = dir ? path.join(dir, 'dist') : path.join(process.cwd(), 'dist');
23
- const pluginJsonPath = path.join(srcPath, 'plugin.json');
26
+ const pluginJsonPath = path.join(distDir, 'plugin.json');
24
27
  cachedPluginJson = readJsonFile(pluginJsonPath);
25
28
  return cachedPluginJson;
26
29
  }