@grafana/react-detect 0.3.0 → 0.4.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/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/dist/bin/run.js +9 -1
- package/dist/commands/detect19.js +24 -4
- package/dist/reporters/console.js +19 -0
- package/dist/results.js +8 -6
- package/package.json +2 -2
- package/src/bin/run.ts +9 -1
- package/src/commands/detect19.ts +35 -8
- package/src/reporters/console.ts +21 -0
- package/src/results.ts +19 -6
- package/src/types/reporters.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# v0.4.0 (Thu Jan 15 2026)
|
|
2
|
+
|
|
3
|
+
#### 🚀 Enhancement
|
|
4
|
+
|
|
5
|
+
- feat: allow skipping dependency and build tooling analysis [#2397](https://github.com/grafana/plugin-tools/pull/2397) ([@jackw](https://github.com/jackw))
|
|
6
|
+
|
|
7
|
+
#### Authors: 1
|
|
8
|
+
|
|
9
|
+
- Jack Westbrook ([@jackw](https://github.com/jackw))
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
1
13
|
# v0.3.0 (Thu Jan 15 2026)
|
|
2
14
|
|
|
3
15
|
#### 🚀 Enhancement
|
package/README.md
CHANGED
|
@@ -21,3 +21,5 @@ npx @grafana/react-detect
|
|
|
21
21
|
|
|
22
22
|
- `--pluginRoot`: Pass the root directory of the plugin. Defaults to the current working directory.
|
|
23
23
|
- `--json`: Output the results as json.
|
|
24
|
+
- `--skipBuildTooling`: Skip webpack configuration checks (jsx-runtime externalization). Useful when build config is missing or not applicable.
|
|
25
|
+
- `--skipDependencies`: Skip dependency tree analysis. Useful when lockfile is missing or for faster analysis of source code only.
|
package/dist/bin/run.js
CHANGED
|
@@ -3,7 +3,15 @@ import minimist from 'minimist';
|
|
|
3
3
|
import { detect19 } from '../commands/detect19.js';
|
|
4
4
|
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
|
-
const argv = minimist(args
|
|
6
|
+
const argv = minimist(args, {
|
|
7
|
+
boolean: ["json", "skipBuildTooling", "skipDependencies"],
|
|
8
|
+
string: ["pluginRoot"],
|
|
9
|
+
default: {
|
|
10
|
+
json: false,
|
|
11
|
+
skipBuildTooling: false,
|
|
12
|
+
skipDependencies: false
|
|
13
|
+
}
|
|
14
|
+
});
|
|
7
15
|
const commands = {
|
|
8
16
|
detect19
|
|
9
17
|
};
|
|
@@ -10,16 +10,36 @@ import { output } from '../utils/output.js';
|
|
|
10
10
|
async function detect19(argv) {
|
|
11
11
|
try {
|
|
12
12
|
const pluginRoot = argv.pluginRoot || process.cwd();
|
|
13
|
+
const skipDependencies = argv.skipDependencies || false;
|
|
14
|
+
const skipBuildTooling = argv.skipBuildTooling || false;
|
|
13
15
|
const allMatches = await getAllMatches(pluginRoot);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
let depContext = null;
|
|
17
|
+
if (!skipDependencies) {
|
|
18
|
+
depContext = new DependencyContext();
|
|
19
|
+
try {
|
|
20
|
+
await depContext.loadDependencies(pluginRoot);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
output.warning({
|
|
23
|
+
title: "Failed to load dependencies",
|
|
24
|
+
body: [
|
|
25
|
+
error.message,
|
|
26
|
+
"Continuing without dependency analysis.",
|
|
27
|
+
"Use --skipDependencies to suppress this warning."
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
depContext = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const matchesWithRootDependency = depContext === null ? allMatches : allMatches.map((match) => {
|
|
17
34
|
if (match.type === "dependency" && match.packageName) {
|
|
18
35
|
return { ...match, rootDependency: depContext.findRootDependency(match.packageName) };
|
|
19
36
|
}
|
|
20
37
|
return match;
|
|
21
38
|
});
|
|
22
|
-
const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext
|
|
39
|
+
const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext, {
|
|
40
|
+
skipBuildTooling,
|
|
41
|
+
skipDependencies
|
|
42
|
+
});
|
|
23
43
|
if (argv.json) {
|
|
24
44
|
jsonReporter(results);
|
|
25
45
|
} else {
|
|
@@ -14,6 +14,25 @@ const summaryMsg = [
|
|
|
14
14
|
`Grafana developer documentation: ${output.formatUrl("https://grafana.com/developers/plugin-tools/set-up/set-up-docker")}`
|
|
15
15
|
];
|
|
16
16
|
function consoleReporter(results) {
|
|
17
|
+
if (!results.summary.analyzedBuildTooling || !results.summary.analyzedDependencies) {
|
|
18
|
+
const skippedFeatures = [];
|
|
19
|
+
if (!results.summary.analyzedBuildTooling) {
|
|
20
|
+
skippedFeatures.push("Build tooling analysis (webpack config checks)");
|
|
21
|
+
}
|
|
22
|
+
if (!results.summary.analyzedDependencies) {
|
|
23
|
+
skippedFeatures.push("Dependency tree analysis (lockfile parsing)");
|
|
24
|
+
}
|
|
25
|
+
output.warning({
|
|
26
|
+
title: "Analysis Skipped",
|
|
27
|
+
body: [
|
|
28
|
+
"The following analyses were skipped:",
|
|
29
|
+
...output.bulletList(skippedFeatures),
|
|
30
|
+
"",
|
|
31
|
+
"Results may be incomplete. Remove skip flags for full analysis."
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
output.addHorizontalLine("yellow");
|
|
35
|
+
}
|
|
17
36
|
if (results.summary.totalIssues === 0) {
|
|
18
37
|
output.success({
|
|
19
38
|
title: "No React 19 breaking changes detected.",
|
package/dist/results.js
CHANGED
|
@@ -2,8 +2,8 @@ import { getPattern } from './patterns/definitions.js';
|
|
|
2
2
|
import { getPluginJson, hasExternalisedJsxRuntime } from './utils/plugin.js';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
function generateAnalysisResults(matches, pluginRoot, depContext) {
|
|
6
|
-
const filtered = filterMatches(matches);
|
|
5
|
+
function generateAnalysisResults(matches, pluginRoot, depContext, options = { skipBuildTooling: false, skipDependencies: false }) {
|
|
6
|
+
const filtered = filterMatches(matches, options.skipBuildTooling);
|
|
7
7
|
const pluginJson = getPluginJson(pluginRoot);
|
|
8
8
|
const sourceMatches = filtered.filter((m) => m.type === "source");
|
|
9
9
|
const dependencyMatches = filtered.filter((m) => m.type === "dependency");
|
|
@@ -36,7 +36,9 @@ function generateAnalysisResults(matches, pluginRoot, depContext) {
|
|
|
36
36
|
sourceIssuesCount: sourceMatches.length,
|
|
37
37
|
dependencyIssuesCount: dependencyMatches.length,
|
|
38
38
|
status: totalIssues > 0 ? "action_required" : "no_action_required",
|
|
39
|
-
affectedDependencies: Array.from(affectedDeps)
|
|
39
|
+
affectedDependencies: Array.from(affectedDeps),
|
|
40
|
+
analyzedBuildTooling: !options.skipBuildTooling,
|
|
41
|
+
analyzedDependencies: !options.skipDependencies
|
|
40
42
|
},
|
|
41
43
|
issues: {
|
|
42
44
|
critical,
|
|
@@ -45,8 +47,8 @@ function generateAnalysisResults(matches, pluginRoot, depContext) {
|
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
|
-
function filterMatches(matches) {
|
|
49
|
-
const externalisedJsxRuntime = hasExternalisedJsxRuntime();
|
|
50
|
+
function filterMatches(matches, skipBuildTooling) {
|
|
51
|
+
const externalisedJsxRuntime = skipBuildTooling ? false : hasExternalisedJsxRuntime();
|
|
50
52
|
const filtered = matches.filter((match) => {
|
|
51
53
|
if (match.type === "source" && (match.confidence === "none" || match.confidence === "unknown")) {
|
|
52
54
|
return false;
|
|
@@ -109,7 +111,7 @@ function buildDependencyIssues(dependencyMatches, depContext) {
|
|
|
109
111
|
const issues = [];
|
|
110
112
|
for (const [packageName, matches] of byPackage) {
|
|
111
113
|
const rootDep = matches[0].rootDependency || packageName;
|
|
112
|
-
const version = depContext
|
|
114
|
+
const version = depContext?.getVersion(packageName) || null;
|
|
113
115
|
issues.push({
|
|
114
116
|
packageName,
|
|
115
117
|
version: version || "unknown",
|
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.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/react-detect",
|
|
7
7
|
"url": "https://github.com/grafana/plugin-tools"
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=20"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "1875c3b665a6bb447a6d2fdea23df11de9c066f2"
|
|
44
44
|
}
|
package/src/bin/run.ts
CHANGED
|
@@ -4,7 +4,15 @@ import minimist from 'minimist';
|
|
|
4
4
|
import { detect19 } from '../commands/detect19.js';
|
|
5
5
|
|
|
6
6
|
const args = process.argv.slice(2);
|
|
7
|
-
const argv = minimist(args
|
|
7
|
+
const argv = minimist(args, {
|
|
8
|
+
boolean: ['json', 'skipBuildTooling', 'skipDependencies'],
|
|
9
|
+
string: ['pluginRoot'],
|
|
10
|
+
default: {
|
|
11
|
+
json: false,
|
|
12
|
+
skipBuildTooling: false,
|
|
13
|
+
skipDependencies: false,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
8
16
|
|
|
9
17
|
const commands: Record<string, (argv: minimist.ParsedArgs) => Promise<void>> = {
|
|
10
18
|
detect19,
|
package/src/commands/detect19.ts
CHANGED
|
@@ -13,19 +13,46 @@ import { output } from '../utils/output.js';
|
|
|
13
13
|
export async function detect19(argv: minimist.ParsedArgs) {
|
|
14
14
|
try {
|
|
15
15
|
const pluginRoot = argv.pluginRoot || process.cwd();
|
|
16
|
+
const skipDependencies = argv.skipDependencies || false;
|
|
17
|
+
const skipBuildTooling = argv.skipBuildTooling || false;
|
|
16
18
|
|
|
17
19
|
const allMatches = await getAllMatches(pluginRoot);
|
|
18
|
-
const depContext = new DependencyContext();
|
|
19
|
-
await depContext.loadDependencies(pluginRoot);
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// Conditionally load dependencies
|
|
22
|
+
let depContext: DependencyContext | null = null;
|
|
23
|
+
if (!skipDependencies) {
|
|
24
|
+
depContext = new DependencyContext();
|
|
25
|
+
try {
|
|
26
|
+
await depContext.loadDependencies(pluginRoot);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// Log warning but continue with null context
|
|
29
|
+
output.warning({
|
|
30
|
+
title: 'Failed to load dependencies',
|
|
31
|
+
body: [
|
|
32
|
+
(error as Error).message,
|
|
33
|
+
'Continuing without dependency analysis.',
|
|
34
|
+
'Use --skipDependencies to suppress this warning.',
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
depContext = null;
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
});
|
|
39
|
+
}
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
// Enrich matches with root dependency only if we have context
|
|
42
|
+
const matchesWithRootDependency =
|
|
43
|
+
depContext === null
|
|
44
|
+
? allMatches
|
|
45
|
+
: allMatches.map((match) => {
|
|
46
|
+
if (match.type === 'dependency' && match.packageName) {
|
|
47
|
+
return { ...match, rootDependency: depContext.findRootDependency(match.packageName) };
|
|
48
|
+
}
|
|
49
|
+
return match;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext, {
|
|
53
|
+
skipBuildTooling,
|
|
54
|
+
skipDependencies,
|
|
55
|
+
});
|
|
29
56
|
|
|
30
57
|
if (argv.json) {
|
|
31
58
|
jsonReporter(results);
|
package/src/reporters/console.ts
CHANGED
|
@@ -16,6 +16,27 @@ const summaryMsg = [
|
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
export function consoleReporter(results: PluginAnalysisResults) {
|
|
19
|
+
if (!results.summary.analyzedBuildTooling || !results.summary.analyzedDependencies) {
|
|
20
|
+
const skippedFeatures: string[] = [];
|
|
21
|
+
if (!results.summary.analyzedBuildTooling) {
|
|
22
|
+
skippedFeatures.push('Build tooling analysis (webpack config checks)');
|
|
23
|
+
}
|
|
24
|
+
if (!results.summary.analyzedDependencies) {
|
|
25
|
+
skippedFeatures.push('Dependency tree analysis (lockfile parsing)');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
output.warning({
|
|
29
|
+
title: 'Analysis Skipped',
|
|
30
|
+
body: [
|
|
31
|
+
'The following analyses were skipped:',
|
|
32
|
+
...output.bulletList(skippedFeatures),
|
|
33
|
+
'',
|
|
34
|
+
'Results may be incomplete. Remove skip flags for full analysis.',
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
output.addHorizontalLine('yellow');
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
if (results.summary.totalIssues === 0) {
|
|
20
41
|
output.success({
|
|
21
42
|
title: 'No React 19 breaking changes detected.',
|
package/src/results.ts
CHANGED
|
@@ -5,12 +5,18 @@ import { getPluginJson, hasExternalisedJsxRuntime } from './utils/plugin.js';
|
|
|
5
5
|
import { DependencyContext } from './utils/dependencies.js';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
|
|
8
|
+
export interface AnalysisOptions {
|
|
9
|
+
skipBuildTooling: boolean;
|
|
10
|
+
skipDependencies: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export function generateAnalysisResults(
|
|
9
14
|
matches: AnalyzedMatch[],
|
|
10
15
|
pluginRoot: string,
|
|
11
|
-
depContext: DependencyContext
|
|
16
|
+
depContext: DependencyContext | null,
|
|
17
|
+
options: AnalysisOptions = { skipBuildTooling: false, skipDependencies: false }
|
|
12
18
|
): PluginAnalysisResults {
|
|
13
|
-
const filtered = filterMatches(matches);
|
|
19
|
+
const filtered = filterMatches(matches, options.skipBuildTooling);
|
|
14
20
|
const pluginJson = getPluginJson(pluginRoot);
|
|
15
21
|
const sourceMatches = filtered.filter((m) => m.type === 'source');
|
|
16
22
|
const dependencyMatches = filtered.filter((m) => m.type === 'dependency');
|
|
@@ -49,6 +55,8 @@ export function generateAnalysisResults(
|
|
|
49
55
|
dependencyIssuesCount: dependencyMatches.length,
|
|
50
56
|
status: totalIssues > 0 ? 'action_required' : 'no_action_required',
|
|
51
57
|
affectedDependencies: Array.from(affectedDeps),
|
|
58
|
+
analyzedBuildTooling: !options.skipBuildTooling,
|
|
59
|
+
analyzedDependencies: !options.skipDependencies,
|
|
52
60
|
},
|
|
53
61
|
issues: {
|
|
54
62
|
critical,
|
|
@@ -58,8 +66,9 @@ export function generateAnalysisResults(
|
|
|
58
66
|
};
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
function filterMatches(matches: AnalyzedMatch[]): AnalyzedMatch[] {
|
|
62
|
-
|
|
69
|
+
function filterMatches(matches: AnalyzedMatch[], skipBuildTooling: boolean): AnalyzedMatch[] {
|
|
70
|
+
// Only check webpack config if NOT skipping build tooling
|
|
71
|
+
const externalisedJsxRuntime = skipBuildTooling ? false : hasExternalisedJsxRuntime();
|
|
63
72
|
const filtered = matches.filter((match) => {
|
|
64
73
|
// TODO: add mode for strict / loose filtering
|
|
65
74
|
if (match.type === 'source' && (match.confidence === 'none' || match.confidence === 'unknown')) {
|
|
@@ -139,7 +148,10 @@ function generateResult(match: AnalyzedMatch): AnalysisResult {
|
|
|
139
148
|
/**
|
|
140
149
|
* Build DependencyIssue objects grouped by package
|
|
141
150
|
*/
|
|
142
|
-
function buildDependencyIssues(
|
|
151
|
+
function buildDependencyIssues(
|
|
152
|
+
dependencyMatches: AnalyzedMatch[],
|
|
153
|
+
depContext: DependencyContext | null
|
|
154
|
+
): DependencyIssue[] {
|
|
143
155
|
// Group by package
|
|
144
156
|
const byPackage = new Map<string, AnalyzedMatch[]>();
|
|
145
157
|
for (const match of dependencyMatches) {
|
|
@@ -154,7 +166,8 @@ function buildDependencyIssues(dependencyMatches: AnalyzedMatch[], depContext: D
|
|
|
154
166
|
const issues: DependencyIssue[] = [];
|
|
155
167
|
for (const [packageName, matches] of byPackage) {
|
|
156
168
|
const rootDep = matches[0].rootDependency || packageName;
|
|
157
|
-
|
|
169
|
+
// Handle null depContext gracefully with optional chaining
|
|
170
|
+
const version = depContext?.getVersion(packageName) || null;
|
|
158
171
|
|
|
159
172
|
issues.push({
|
|
160
173
|
packageName,
|
package/src/types/reporters.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface PluginAnalysisResults {
|
|
|
32
32
|
dependencyIssuesCount: number;
|
|
33
33
|
status: 'action_required' | 'no_action_required';
|
|
34
34
|
affectedDependencies: string[];
|
|
35
|
+
analyzedBuildTooling: boolean;
|
|
36
|
+
analyzedDependencies: boolean;
|
|
35
37
|
};
|
|
36
38
|
issues: {
|
|
37
39
|
critical: AnalysisResult[];
|