cra-audit 0.1.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.
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ /** Reads and parses a JSON file. Returns null if it does not exist. */
7
+ function readJson(filePath) {
8
+ if (!fs.existsSync(filePath)) return null;
9
+ const content = fs.readFileSync(filePath, 'utf8');
10
+ return JSON.parse(content);
11
+ }
12
+
13
+ /** Writes a JSON object to disk with 2-space indentation. */
14
+ function writeJson(filePath, data) {
15
+ const dir = path.dirname(filePath);
16
+ if (!fs.existsSync(dir)) {
17
+ fs.mkdirSync(dir, { recursive: true });
18
+ }
19
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
20
+ }
21
+
22
+ /** Returns true if a path exists. */
23
+ function exists(filePath) {
24
+ return fs.existsSync(filePath);
25
+ }
26
+
27
+ /**
28
+ * Resolves the project root by locating the nearest package.json,
29
+ * walking up from the provided starting directory.
30
+ */
31
+ function findProjectRoot(startDir = process.cwd()) {
32
+ let dir = path.resolve(startDir);
33
+ // eslint-disable-next-line no-constant-condition
34
+ while (true) {
35
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
36
+ return dir;
37
+ }
38
+ const parent = path.dirname(dir);
39
+ if (parent === dir) return null;
40
+ dir = parent;
41
+ }
42
+ }
43
+
44
+ module.exports = { readJson, writeJson, exists, findProjectRoot };
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Tiny HTTP helper built on the global `fetch` (Node >= 18).
5
+ * Adds a per-request timeout and never throws: failures resolve to null so
6
+ * enrichment can degrade gracefully when offline or rate-limited.
7
+ */
8
+
9
+ /**
10
+ * @param {string} url
11
+ * @param {{ timeout?: number, headers?: object }} [options]
12
+ * @returns {Promise<{ ok: boolean, status: number, json: any, headers: Headers }|null>}
13
+ */
14
+ async function fetchJson(url, { timeout = 8000, headers = {} } = {}) {
15
+ if (typeof fetch !== 'function') return null;
16
+
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), timeout);
19
+
20
+ try {
21
+ const res = await fetch(url, {
22
+ headers: { Accept: 'application/json', ...headers },
23
+ signal: controller.signal,
24
+ redirect: 'follow',
25
+ });
26
+ let json = null;
27
+ try {
28
+ json = await res.json();
29
+ } catch {
30
+ json = null;
31
+ }
32
+ return { ok: res.ok, status: res.status, json, headers: res.headers };
33
+ } catch {
34
+ return null;
35
+ } finally {
36
+ clearTimeout(timer);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Runs an async mapper over items with bounded concurrency.
42
+ * @template T, R
43
+ * @param {T[]} items
44
+ * @param {(item: T, index: number) => Promise<R>} mapper
45
+ * @param {number} concurrency
46
+ * @returns {Promise<R[]>}
47
+ */
48
+ async function mapLimit(items, mapper, concurrency = 8) {
49
+ const results = new Array(items.length);
50
+ let cursor = 0;
51
+
52
+ const workers = new Array(Math.min(concurrency, items.length)).fill(0).map(async () => {
53
+ while (cursor < items.length) {
54
+ const index = cursor++;
55
+ results[index] = await mapper(items[index], index);
56
+ }
57
+ });
58
+
59
+ await Promise.all(workers);
60
+ return results;
61
+ }
62
+
63
+ module.exports = { fetchJson, mapLimit };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Minimal ANSI logger with no external dependencies.
5
+ * Colors are automatically disabled when output is not a TTY,
6
+ * when NO_COLOR is set, or when --no-color was requested.
7
+ */
8
+
9
+ const SUPPORTS_COLOR =
10
+ process.stdout.isTTY &&
11
+ !process.env.NO_COLOR &&
12
+ process.env.TERM !== 'dumb';
13
+
14
+ let colorEnabled = SUPPORTS_COLOR;
15
+
16
+ const CODES = {
17
+ reset: '\x1b[0m',
18
+ bold: '\x1b[1m',
19
+ dim: '\x1b[2m',
20
+ red: '\x1b[31m',
21
+ green: '\x1b[32m',
22
+ yellow: '\x1b[33m',
23
+ blue: '\x1b[34m',
24
+ magenta: '\x1b[35m',
25
+ cyan: '\x1b[36m',
26
+ gray: '\x1b[90m',
27
+ };
28
+
29
+ function paint(code, text) {
30
+ if (!colorEnabled) return text;
31
+ return `${CODES[code]}${text}${CODES.reset}`;
32
+ }
33
+
34
+ const color = {
35
+ bold: (t) => paint('bold', t),
36
+ dim: (t) => paint('dim', t),
37
+ red: (t) => paint('red', t),
38
+ green: (t) => paint('green', t),
39
+ yellow: (t) => paint('yellow', t),
40
+ blue: (t) => paint('blue', t),
41
+ magenta: (t) => paint('magenta', t),
42
+ cyan: (t) => paint('cyan', t),
43
+ gray: (t) => paint('gray', t),
44
+ };
45
+
46
+ const SYMBOLS = {
47
+ ok: () => color.green('✔'),
48
+ fail: () => color.red('✖'),
49
+ warn: () => color.yellow('⚠'),
50
+ info: () => color.blue('ℹ'),
51
+ bullet: () => color.gray('•'),
52
+ };
53
+
54
+ const logger = {
55
+ setColor(enabled) {
56
+ colorEnabled = Boolean(enabled) && SUPPORTS_COLOR;
57
+ },
58
+ log(...args) {
59
+ process.stdout.write(args.join(' ') + '\n');
60
+ },
61
+ raw(text) {
62
+ process.stdout.write(text);
63
+ },
64
+ info(msg) {
65
+ this.log(`${SYMBOLS.info()} ${msg}`);
66
+ },
67
+ success(msg) {
68
+ this.log(`${SYMBOLS.ok()} ${msg}`);
69
+ },
70
+ warn(msg) {
71
+ this.log(`${SYMBOLS.warn()} ${color.yellow(msg)}`);
72
+ },
73
+ error(msg) {
74
+ process.stderr.write(`${SYMBOLS.fail()} ${color.red(msg)}\n`);
75
+ },
76
+ heading(msg) {
77
+ this.log('');
78
+ this.log(color.bold(color.cyan(msg)));
79
+ },
80
+ detail(msg) {
81
+ this.log(` ${color.gray(msg)}`);
82
+ },
83
+ };
84
+
85
+ module.exports = { logger, color, SYMBOLS };