Skip to content

Commit 86d3047

Browse files
authored
feat: allow running programmatically (#54)
1 parent 85309e2 commit 86d3047

File tree

9 files changed

+170
-40
lines changed

9 files changed

+170
-40
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88

99
strategy:
1010
matrix:
11-
node-version: [12.18.2]
11+
node-version: [16.17.0]
1212

1313
steps:
1414
- name: Checkout

.nvmrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v14.16.1
1+
v16.17.0

README.md

+19-5
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Running `react-scanner` on it will create the following JSON report:
4444
4545
<details>
4646
<summary>Click to see it</summary>
47-
47+
4848
```json
4949
{
5050
"BasisProvider": {
@@ -157,6 +157,7 @@ Running `react-scanner` on it will create the following JSON report:
157157
}
158158
}
159159
```
160+
160161
</details>
161162
162163
This raw JSON report is used then to generate something that is useful to you. For example, you might want to know:
@@ -179,7 +180,7 @@ npm install --save-dev react-scanner
179180
npx react-scanner -c /path/to/react-scanner.config.js
180181
```
181182
182-
## Config file
183+
### Config file
183184
184185
Everything that `react-scanner` does is controlled by a config file.
185186
@@ -220,10 +221,23 @@ Running `react-scanner` with this config would output something like this to the
220221
}
221222
```
222223
224+
### Running programmatically
225+
226+
It is also possible to run the scanner programmatically. In this case, the config options should be passed directly to the `run` function.
227+
228+
```js
229+
import scanner from "react-scanner";
230+
231+
const output = await scanner.run(config);
232+
```
233+
234+
## Config options
235+
223236
Here are all the available config options:
224237
225238
| Option | Type | Description |
226239
| ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
240+
| `rootDir` | string | The path to the root directory of your project. <br>If using a config file, this defaults to the config directory. |
227241
| `crawlFrom` | string | The path of the directory to start crawling from.<br>Absolute or relative to the config file location. |
228242
| `exclude` | array or function | Each array item should be a string or a regex. When crawling, if directory name matches exactly the string item or matches the regex item, it will be excluded from crawling.<br>For more complex scenarios, `exclude` can be a a function that accepts a directory name and should return `true` if the directory should be excluded from crawling. |
229243
| `globs` | array | Only files matching these globs will be scanned. See [here](https://github.com/micromatch/picomatch#globbing-features) for glob syntax.<br>Default: `["**/!(*.test\|*.spec).@(js\|ts)?(x)"]` |
@@ -258,9 +272,9 @@ processors: [
258272
259273
All the built-in processors support the following options:
260274
261-
| Option | Type | Description |
262-
| ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
263-
| `outputTo` | string | Where to output the result.<br>Absolute or relative to the config file location.<br>When omitted, the result is printed out to the console. |
275+
| Option | Type | Description |
276+
| ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
277+
| `outputTo` | string | Where to output the result.<br>Absolute or relative to the root directory.<br>When omitted, the result is printed out to the console. |
264278
265279
Here are the built-in processors that `react-scanner` comes with:
266280

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "1.0.4",
44
"description": "Extract React components and props usage from code.",
55
"bin": "bin/react-scanner",
6+
"main": "src/scanner.js",
67
"scripts": {
78
"prepare": "husky install",
89
"build": "node scripts/processors.js",

src/index.js

+2-22
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
const startTime = process.hrtime.bigint();
2-
31
const path = require("path");
42
const sade = require("sade");
5-
const { validateConfig } = require("./utils");
6-
const run = require("./run");
3+
const { run } = require("./scanner");
74
const packageJson = require("../package.json");
85

96
sade("react-scanner", true)
@@ -15,23 +12,6 @@ sade("react-scanner", true)
1512
const configPath = path.resolve(process.cwd(), options.config);
1613
const configDir = path.dirname(configPath);
1714
const config = require(configPath);
18-
const { crawlFrom, errors } = validateConfig(config, configDir);
19-
20-
if (errors.length === 0) {
21-
run({
22-
config,
23-
configDir,
24-
crawlFrom,
25-
startTime,
26-
});
27-
} else {
28-
console.error(`Config errors:`);
29-
30-
errors.forEach((error) => {
31-
console.error(`- ${error}`);
32-
});
33-
34-
process.exit(1);
35-
}
15+
run(config, configDir, "cli");
3616
})
3717
.parse(process.argv);

src/run.js

+28-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ const {
1313
const DEFAULT_GLOBS = ["**/!(*.test|*.spec).@(js|ts)?(x)"];
1414
const DEFAULT_PROCESSORS = ["count-components-and-props"];
1515

16-
async function run({ config, configDir, crawlFrom, startTime }) {
16+
async function run({
17+
config,
18+
configDir,
19+
crawlFrom,
20+
startTime,
21+
method = "cli",
22+
}) {
23+
const rootDir = config.rootDir || configDir;
1724
const globs = config.globs || DEFAULT_GLOBS;
1825
const files = new fdir()
1926
.glob(...globs)
@@ -66,19 +73,28 @@ async function run({ config, configDir, crawlFrom, startTime }) {
6673
? config.processors
6774
: DEFAULT_PROCESSORS;
6875
const prevResults = [];
69-
const output = (data, destination = "stdout") => {
76+
const output = (data, destination) => {
77+
const defaultDestination = method === "cli" ? "stdout" : "return";
78+
const dest = destination || defaultDestination;
7079
const dataStr = isPlainObject(data)
7180
? JSON.stringify(data, null, 2)
7281
: String(data);
7382

74-
if (destination === "stdout") {
75-
// eslint-disable-next-line no-console
76-
console.log(dataStr);
77-
} else {
78-
const filePath = path.resolve(configDir, destination);
79-
80-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
81-
fs.writeFileSync(filePath, dataStr);
83+
switch (dest) {
84+
case "stdout": {
85+
// eslint-disable-next-line no-console
86+
console.log(dataStr);
87+
break;
88+
}
89+
case "return": {
90+
break;
91+
}
92+
default: {
93+
const filePath = path.resolve(rootDir, destination);
94+
95+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
96+
fs.writeFileSync(filePath, dataStr);
97+
}
8298
}
8399
};
84100

@@ -104,6 +120,8 @@ async function run({ config, configDir, crawlFrom, startTime }) {
104120

105121
prevResults.push(result);
106122
}
123+
124+
return prevResults[prevResults.length - 1];
107125
}
108126

109127
module.exports = run;

src/scanner.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const startTime = process.hrtime.bigint();
2+
3+
const { validateConfig } = require("./utils");
4+
const runScan = require("./run");
5+
6+
const scanner = {
7+
run: async function run(config, configDir, method = "programmatic") {
8+
const { crawlFrom, errors } = validateConfig(config, configDir);
9+
10+
if (errors.length === 0) {
11+
return await runScan({
12+
config,
13+
configDir,
14+
crawlFrom,
15+
startTime,
16+
method: method,
17+
});
18+
} else {
19+
console.error(`Config errors:`);
20+
21+
errors.forEach((error) => {
22+
console.error(`- ${error}`);
23+
});
24+
25+
process.exit(1);
26+
}
27+
},
28+
};
29+
30+
module.exports = scanner;

src/scanner.test.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const path = require("path");
2+
const { suite } = require("uvu");
3+
const assert = require("uvu/assert");
4+
5+
const scanner = require("./scanner");
6+
7+
const Scanner = suite("Scanner");
8+
9+
Scanner("no processors", async () => {
10+
const output = await scanner.run({
11+
crawlFrom: "code",
12+
rootDir: path.resolve("./test"),
13+
});
14+
15+
assert.snapshot(
16+
JSON.stringify(output, undefined, 2),
17+
JSON.stringify(
18+
{
19+
Text: {
20+
instances: 2,
21+
props: {
22+
margin: 1,
23+
textStyle: 1,
24+
},
25+
},
26+
App: {
27+
instances: 1,
28+
props: {},
29+
},
30+
BasisProvider: {
31+
instances: 1,
32+
props: {
33+
theme: 1,
34+
},
35+
},
36+
Home: {
37+
instances: 1,
38+
props: {},
39+
},
40+
Link: {
41+
instances: 1,
42+
props: {
43+
href: 1,
44+
newTab: 1,
45+
},
46+
},
47+
div: {
48+
instances: 1,
49+
props: {
50+
style: 1,
51+
},
52+
},
53+
},
54+
null,
55+
2
56+
)
57+
);
58+
});
59+
60+
Scanner("single processor", async () => {
61+
const output = await scanner.run({
62+
crawlFrom: "code",
63+
rootDir: path.resolve("./test"),
64+
processors: ["count-components"],
65+
});
66+
67+
assert.snapshot(
68+
JSON.stringify(output, undefined, 2),
69+
JSON.stringify(
70+
{
71+
Text: 2,
72+
App: 1,
73+
BasisProvider: 1,
74+
Home: 1,
75+
Link: 1,
76+
div: 1,
77+
},
78+
null,
79+
2
80+
)
81+
);
82+
});
83+
84+
Scanner.run();

src/utils.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ function validateConfig(config, configDir) {
1717
} else if (typeof config.crawlFrom !== "string") {
1818
result.errors.push(`crawlFrom should be a string`);
1919
} else {
20-
const crawlFrom = path.resolve(configDir, config.crawlFrom);
20+
const crawlFrom = path.resolve(
21+
config.rootDir || configDir,
22+
config.crawlFrom
23+
);
2124

2225
if (fs.existsSync(crawlFrom)) {
2326
result.crawlFrom = crawlFrom;

0 commit comments

Comments
 (0)