@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.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/dist/index.js +102 -16
  3. 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 the root `tsconfig.json` through
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 path from "path";
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 path.relative(root, file).split(path.sep).join("/");
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 = path.join(root, "package.json");
169666
- if (!existsSync(manifestFile))
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.dependencies?.react && !manifest.devDependencies?.react)
169743
+ if (!hasDependency(manifest, "react"))
169670
169744
  return;
169671
169745
  try {
169672
169746
  createRequire(manifestFile).resolve("react");
169673
- const configFile = path.join(root, "tsconfig.json");
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(root, source.fileName);
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 root tsconfig.json must include React TSX source files.");
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 root tsconfig.json and type-correct TSX files. ${error}`);
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
- if (!reactProject(root))
169848
- throw new Error("REACT_PROJECT_REQUIRED: Select a project with React in its root package.json, or disable the react scanner.");
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 project = reactProject(root);
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.json", "package.json"], exclude: [] },
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.0",
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
  }