@groma/scanner-react 0.1.0 → 0.1.1
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/README.md +11 -1
- package/dist/index.js +102 -16
- package/package.json +2 -5
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ or installation script is required. Public naming and publication are separate
|
|
|
26
26
|
release decisions.
|
|
27
27
|
|
|
28
28
|
Install the application's declared dependencies and React types with its
|
|
29
|
-
package manager and lockfile. The scanner reads
|
|
29
|
+
package manager and lockfile. The scanner reads each selected project's `tsconfig.json` through
|
|
30
30
|
the TypeScript compiler and inventories its owned TSX source files. Missing
|
|
31
31
|
React dependencies, invalid configuration, syntax errors, and semantic errors
|
|
32
32
|
in those TSX files fail preparation with instructions. It does not claim to
|
|
@@ -71,3 +71,13 @@ establish a callback interaction.
|
|
|
71
71
|
TSX and TypeScript edits use the existing watch lifecycle. All enabled scanners
|
|
72
72
|
must complete before architecture changes. See [validation](validation.md) for
|
|
73
73
|
the executed artifact checks and remaining release gates.
|
|
74
|
+
|
|
75
|
+
## Nested projects
|
|
76
|
+
|
|
77
|
+
Run Groma from the repository root. The scanner finds package declarations in
|
|
78
|
+
tracked and unignored files, including nested apps and libraries. Dependencies,
|
|
79
|
+
dev dependencies, peer dependencies and optional dependencies identify framework
|
|
80
|
+
projects. Each compiler uses that project's configuration and installed dependencies;
|
|
81
|
+
imported source in sibling repository libraries keeps its original source path.
|
|
82
|
+
Readiness checks all selected projects. A project error fails the complete scan.
|
|
83
|
+
Source and nested package/configuration changes use the shared scanner watch flow.
|
package/dist/index.js
CHANGED
|
@@ -169483,9 +169483,9 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
|
|
|
169483
169483
|
});
|
|
169484
169484
|
|
|
169485
169485
|
// plugins/scanners/react/src/scan.ts
|
|
169486
|
-
import { existsSync, readFileSync } from "fs";
|
|
169486
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
169487
169487
|
import { createRequire } from "module";
|
|
169488
|
-
import
|
|
169488
|
+
import path2 from "path";
|
|
169489
169489
|
|
|
169490
169490
|
// packages/scanner/src/index.ts
|
|
169491
169491
|
function compare(...values) {
|
|
@@ -169651,8 +169651,82 @@ function sourcePosition(position) {
|
|
|
169651
169651
|
|
|
169652
169652
|
// plugins/scanners/react/src/scan.ts
|
|
169653
169653
|
var import_typescript = __toESM(require_typescript(), 1);
|
|
169654
|
+
|
|
169655
|
+
// plugins/scanners/projects.ts
|
|
169656
|
+
import { execFile } from "child_process";
|
|
169657
|
+
import { existsSync } from "fs";
|
|
169658
|
+
import { readFile } from "fs/promises";
|
|
169659
|
+
import path from "path";
|
|
169660
|
+
import { promisify } from "util";
|
|
169661
|
+
var execute = promisify(execFile);
|
|
169662
|
+
var excluded = new Set([
|
|
169663
|
+
".git",
|
|
169664
|
+
"node_modules",
|
|
169665
|
+
"vendor",
|
|
169666
|
+
"target",
|
|
169667
|
+
"dist",
|
|
169668
|
+
"build",
|
|
169669
|
+
"bin",
|
|
169670
|
+
"obj",
|
|
169671
|
+
".gradle",
|
|
169672
|
+
".angular",
|
|
169673
|
+
"coverage",
|
|
169674
|
+
"generated",
|
|
169675
|
+
"groma",
|
|
169676
|
+
".groma"
|
|
169677
|
+
]);
|
|
169678
|
+
async function projectFiles(root, matches) {
|
|
169679
|
+
const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
|
|
169680
|
+
return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && !file.split("/").some((part) => excluded.has(part)) && existsSync(path.join(root, file))))].sort();
|
|
169681
|
+
}
|
|
169682
|
+
function hasDependency(manifest, dependency) {
|
|
169683
|
+
return ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"].some((section) => typeof manifest[section]?.[dependency] === "string");
|
|
169684
|
+
}
|
|
169685
|
+
async function frameworkProjects(root, dependency) {
|
|
169686
|
+
const projects = [];
|
|
169687
|
+
for (const file of await projectFiles(root, (file) => path.posix.basename(file) === "package.json")) {
|
|
169688
|
+
const manifest = JSON.parse(await readFile(path.join(root, file), "utf8"));
|
|
169689
|
+
if (hasDependency(manifest, dependency))
|
|
169690
|
+
projects.push(path.dirname(path.join(root, file)));
|
|
169691
|
+
}
|
|
169692
|
+
return projects;
|
|
169693
|
+
}
|
|
169694
|
+
|
|
169695
|
+
// plugins/scanners/observations.ts
|
|
169696
|
+
function combineObservations(parts) {
|
|
169697
|
+
if (!parts.length)
|
|
169698
|
+
return;
|
|
169699
|
+
const roots = [];
|
|
169700
|
+
const files = new Map;
|
|
169701
|
+
const operations = [];
|
|
169702
|
+
const invocations = [];
|
|
169703
|
+
const diagnostics = [];
|
|
169704
|
+
for (const { key, observation } of parts) {
|
|
169705
|
+
const id = (value) => JSON.stringify([key, value]);
|
|
169706
|
+
roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
|
|
169707
|
+
for (const source of observation.files) {
|
|
169708
|
+
const combined = files.get(source.file) ?? { file: source.file, roots: [], symbols: [] };
|
|
169709
|
+
combined.roots.push(...source.roots.map(id));
|
|
169710
|
+
combined.symbols.push(...source.symbols.map((symbol) => ({ ...symbol, id: id(symbol.id) })));
|
|
169711
|
+
files.set(source.file, combined);
|
|
169712
|
+
}
|
|
169713
|
+
operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
|
|
169714
|
+
invocations.push(...(observation.invocations ?? []).map((call) => ({ ...call, source: id(call.source), targets: call.targets.map(id) })));
|
|
169715
|
+
diagnostics.push(...observation.diagnostics);
|
|
169716
|
+
}
|
|
169717
|
+
return createScanObservation({
|
|
169718
|
+
scanner: parts[0].observation.scanner,
|
|
169719
|
+
roots,
|
|
169720
|
+
files: [...files.values()],
|
|
169721
|
+
operations,
|
|
169722
|
+
invocations,
|
|
169723
|
+
diagnostics
|
|
169724
|
+
});
|
|
169725
|
+
}
|
|
169726
|
+
|
|
169727
|
+
// plugins/scanners/react/src/scan.ts
|
|
169654
169728
|
function relative(root, file) {
|
|
169655
|
-
return
|
|
169729
|
+
return path2.relative(root, file).split(path2.sep).join("/");
|
|
169656
169730
|
}
|
|
169657
169731
|
function failDiagnostics(diagnostics) {
|
|
169658
169732
|
const errors = diagnostics.filter((item) => item.category === import_typescript.default.DiagnosticCategory.Error);
|
|
@@ -169661,16 +169735,16 @@ function failDiagnostics(diagnostics) {
|
|
|
169661
169735
|
`)}`).join(`
|
|
169662
169736
|
`));
|
|
169663
169737
|
}
|
|
169664
|
-
function reactProject(root) {
|
|
169665
|
-
const manifestFile =
|
|
169666
|
-
if (!
|
|
169738
|
+
function reactProject(root, repositoryRoot = root) {
|
|
169739
|
+
const manifestFile = path2.join(root, "package.json");
|
|
169740
|
+
if (!existsSync2(manifestFile))
|
|
169667
169741
|
return;
|
|
169668
169742
|
const manifest = JSON.parse(readFileSync(manifestFile, "utf8"));
|
|
169669
|
-
if (!manifest
|
|
169743
|
+
if (!hasDependency(manifest, "react"))
|
|
169670
169744
|
return;
|
|
169671
169745
|
try {
|
|
169672
169746
|
createRequire(manifestFile).resolve("react");
|
|
169673
|
-
const configFile =
|
|
169747
|
+
const configFile = path2.join(root, "tsconfig.json");
|
|
169674
169748
|
const config = import_typescript.default.readConfigFile(configFile, import_typescript.default.sys.readFile);
|
|
169675
169749
|
if (config.error)
|
|
169676
169750
|
failDiagnostics([config.error]);
|
|
@@ -169678,11 +169752,11 @@ function reactProject(root) {
|
|
|
169678
169752
|
failDiagnostics(parsed.errors);
|
|
169679
169753
|
const program = import_typescript.default.createProgram(parsed.fileNames, { ...parsed.options, noEmit: true });
|
|
169680
169754
|
const sources = program.getSourceFiles().filter((source) => {
|
|
169681
|
-
const file = relative(
|
|
169755
|
+
const file = relative(repositoryRoot, source.fileName);
|
|
169682
169756
|
return file.endsWith(".tsx") && !source.isDeclarationFile && !file.startsWith("../") && !file.includes("node_modules/");
|
|
169683
169757
|
});
|
|
169684
169758
|
if (!sources.length)
|
|
169685
|
-
throw new Error("The
|
|
169759
|
+
throw new Error("The project tsconfig.json must include React TSX source files.");
|
|
169686
169760
|
const react = import_typescript.default.resolveModuleName("react", sources[0].fileName, parsed.options, import_typescript.default.sys).resolvedModule;
|
|
169687
169761
|
if (!react?.resolvedFileName.endsWith(".d.ts"))
|
|
169688
169762
|
throw new Error("Install @types/react in the project.");
|
|
@@ -169692,7 +169766,7 @@ function reactProject(root) {
|
|
|
169692
169766
|
failDiagnostics(program.getSemanticDiagnostics(source));
|
|
169693
169767
|
return { manifest, program, sources };
|
|
169694
169768
|
} catch (error) {
|
|
169695
|
-
throw new Error(`REACT_PROJECT_PREPARATION: Install the project dependencies with its declared package manager and lockfile; provide a valid
|
|
169769
|
+
throw new Error(`REACT_PROJECT_PREPARATION: Install the project dependencies with its declared package manager and lockfile; provide a valid project tsconfig.json and type-correct TSX files. ${error}`);
|
|
169696
169770
|
}
|
|
169697
169771
|
}
|
|
169698
169772
|
function executable(node) {
|
|
@@ -169844,11 +169918,23 @@ class Evidence {
|
|
|
169844
169918
|
}
|
|
169845
169919
|
}
|
|
169846
169920
|
async function checkReactReadiness(root) {
|
|
169847
|
-
|
|
169848
|
-
|
|
169921
|
+
const projects = await frameworkProjects(root, "react");
|
|
169922
|
+
if (!projects.length)
|
|
169923
|
+
throw new Error("REACT_PROJECT_REQUIRED: No React project declaration was found.");
|
|
169924
|
+
for (const project of projects)
|
|
169925
|
+
reactProject(project, root);
|
|
169849
169926
|
}
|
|
169850
169927
|
async function scanReact(root) {
|
|
169851
|
-
const
|
|
169928
|
+
const parts = [];
|
|
169929
|
+
for (const project of await frameworkProjects(root, "react")) {
|
|
169930
|
+
const observation = await scanReactProject(project, root);
|
|
169931
|
+
if (observation)
|
|
169932
|
+
parts.push({ key: relative(root, project), observation });
|
|
169933
|
+
}
|
|
169934
|
+
return combineObservations(parts);
|
|
169935
|
+
}
|
|
169936
|
+
async function scanReactProject(projectRoot, root) {
|
|
169937
|
+
const project = reactProject(projectRoot, root);
|
|
169852
169938
|
if (!project)
|
|
169853
169939
|
return;
|
|
169854
169940
|
const { manifest, program, sources } = project;
|
|
@@ -169858,7 +169944,7 @@ async function scanReact(root) {
|
|
|
169858
169944
|
const files = sources.map((source) => ({ file: relative(root, source.fileName), symbols: [] }));
|
|
169859
169945
|
return createScanObservation({
|
|
169860
169946
|
scanner: { id: "react", technology: "typescript/react", engine: "typescript-sdk", engineVersion: import_typescript.default.version },
|
|
169861
|
-
roots: [{ id: "react-project", kind: "package", name: manifest.name, file: "package.json" }],
|
|
169947
|
+
roots: [{ id: "react-project", kind: "package", name: manifest.name, file: relative(root, path2.join(projectRoot, "package.json")) }],
|
|
169862
169948
|
files: files.map((file) => ({ ...file, roots: ["react-project"] })),
|
|
169863
169949
|
operations: [...evidence.operations.values()],
|
|
169864
169950
|
invocations: evidence.invocations,
|
|
@@ -169869,7 +169955,7 @@ async function scanReact(root) {
|
|
|
169869
169955
|
// plugins/scanners/react/src/index.ts
|
|
169870
169956
|
var src_default = {
|
|
169871
169957
|
id: "react",
|
|
169872
|
-
watch: { include: ["**/*.tsx", "**/*.ts", "tsconfig
|
|
169958
|
+
watch: { include: ["**/*.tsx", "**/*.ts", "**/tsconfig*.json", "**/package.json"], exclude: [] },
|
|
169873
169959
|
checkReadiness: checkReactReadiness,
|
|
169874
169960
|
scan: scanReact
|
|
169875
169961
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groma/scanner-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Complementary React framework evidence; a dependency declaration does not verify runtime use.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -25,10 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
],
|
|
27
27
|
"compatibility": {
|
|
28
|
-
"groma": "^0.3.0"
|
|
29
|
-
"technologyVersions": {
|
|
30
|
-
"react": "19.2.7"
|
|
31
|
-
}
|
|
28
|
+
"groma": "^0.3.0"
|
|
32
29
|
}
|
|
33
30
|
}
|
|
34
31
|
}
|