prodex 1.0.8 â 1.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.
- package/dist/package.json +4 -2
- package/dist/src/cli/init.js +8 -40
- package/dist/src/cli/picker.js +21 -26
- package/dist/src/cli/summary.js +2 -5
- package/dist/src/constants/config-loader.js +74 -51
- package/dist/src/constants/config.js +3 -80
- package/dist/src/constants/default-config.js +36 -0
- package/dist/src/constants/render-constants.js +22 -0
- package/dist/src/core/combine.js +92 -88
- package/dist/src/core/file-utils.js +34 -2
- package/dist/src/core/helpers.js +41 -91
- package/dist/src/core/renderers.js +58 -0
- package/dist/src/index.js +5 -1
- package/dist/src/resolvers/js-resolver.js +104 -59
- package/dist/src/resolvers/php-resolver.js +70 -30
- package/package.json +4 -2
|
@@ -1,70 +1,70 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import
|
|
3
|
+
import micromatch from "micromatch";
|
|
4
|
+
import { safeMicromatchScan } from "../core/file-utils.js";
|
|
4
5
|
|
|
5
6
|
const debug = process.env.PRODEX_DEBUG === "1";
|
|
6
|
-
const log = (...
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const log = (...a) => {
|
|
8
|
+
if (debug) console.log("ðŠķ [js-resolver]", ...a);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load alias mappings from vite.config.* or tsconfig.json.
|
|
13
|
+
* Local config aliases (cfg.imports.aliases) override these.
|
|
14
|
+
*/
|
|
15
|
+
function loadProjectAliases(ROOT) {
|
|
16
|
+
const viteFiles = [
|
|
12
17
|
"vite.config.ts",
|
|
13
18
|
"vite.config.js",
|
|
14
19
|
"vite.config.mts",
|
|
15
20
|
"vite.config.mjs",
|
|
16
|
-
"vite.config.cjs"
|
|
21
|
+
"vite.config.cjs"
|
|
17
22
|
];
|
|
18
23
|
const map = {};
|
|
19
|
-
|
|
24
|
+
|
|
25
|
+
// --- Vite aliases ---------------------------------------------------------
|
|
26
|
+
for (const f of viteFiles) {
|
|
20
27
|
const p = path.join(ROOT, f);
|
|
21
28
|
if (!fs.existsSync(p)) continue;
|
|
22
29
|
const s = fs.readFileSync(p, "utf8");
|
|
23
|
-
const
|
|
24
|
-
if (!
|
|
30
|
+
const block = /resolve\s*:\s*{[\s\S]*?alias\s*:\s*{([\s\S]*?)}/m.exec(s);
|
|
31
|
+
if (!block) continue;
|
|
25
32
|
const re = /['"]([^'"]+)['"]\s*:\s*['"]([^'"]+)['"]/g;
|
|
26
33
|
let m;
|
|
27
|
-
while ((m = re.exec(
|
|
34
|
+
while ((m = re.exec(block[1]))) {
|
|
28
35
|
const key = m[1];
|
|
29
36
|
const raw = m[2].replace(/^\/+/, "");
|
|
30
|
-
|
|
31
|
-
map[key] = abs;
|
|
37
|
+
map[key] = path.resolve(ROOT, raw);
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
|
-
return map;
|
|
35
|
-
}
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
// --- TSConfig aliases -----------------------------------------------------
|
|
42
|
+
const ts = path.join(ROOT, "tsconfig.json");
|
|
43
|
+
if (fs.existsSync(ts)) {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs
|
|
46
|
+
.readFileSync(ts, "utf8")
|
|
47
|
+
.replace(/("(?:\\.|[^"\\])*")|\/\/.*$|\/\*[\s\S]*?\*\//gm, (_, q) => q || "")
|
|
48
|
+
.replace(/,\s*([}\]])/g, "$1");
|
|
49
|
+
const j = JSON.parse(content);
|
|
50
|
+
const paths = j.compilerOptions?.paths || {};
|
|
51
|
+
const base = j.compilerOptions?.baseUrl || ".";
|
|
52
|
+
for (const k in paths) {
|
|
53
|
+
const arr = paths[k];
|
|
54
|
+
if (!Array.isArray(arr) || !arr.length) continue;
|
|
55
|
+
const from = k.replace(/\*$/, "");
|
|
56
|
+
const to = arr[0].replace(/\*$/, "");
|
|
57
|
+
map[from] = path.resolve(ROOT, base, to);
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
48
60
|
}
|
|
49
|
-
const paths = j.compilerOptions?.paths || {};
|
|
50
|
-
const base = j.compilerOptions?.baseUrl || ".";
|
|
51
|
-
const map = {};
|
|
52
|
-
for (const k in paths) {
|
|
53
|
-
const arr = paths[k];
|
|
54
|
-
if (!Array.isArray(arr) || !arr.length) continue;
|
|
55
|
-
const from = k.replace(/\*$/, "");
|
|
56
|
-
const to = arr[0].replace(/\*$/, "");
|
|
57
|
-
map[from] = path.resolve(ROOT, base, to);
|
|
58
|
-
}
|
|
59
|
-
return map;
|
|
60
|
-
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
return { ...loadTsconfigAliases(), ...loadViteAliases() };
|
|
62
|
+
return map;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Try to resolve an import path into a real file.
|
|
67
|
+
*/
|
|
68
68
|
function tryResolveImport(basePath) {
|
|
69
69
|
const ext = path.extname(basePath);
|
|
70
70
|
const tries = [];
|
|
@@ -78,27 +78,49 @@ function tryResolveImport(basePath) {
|
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Core: resolve JS / TS imports recursively.
|
|
83
|
+
* Returns unique project-level expected + resolved import sets.
|
|
84
|
+
*/
|
|
85
|
+
export async function resolveJsImports(
|
|
86
|
+
filePath,
|
|
87
|
+
cfg,
|
|
88
|
+
visited = new Set(),
|
|
89
|
+
depth = 0,
|
|
90
|
+
maxDepth = 10,
|
|
91
|
+
ctx = {}
|
|
92
|
+
) {
|
|
93
|
+
if (depth >= maxDepth) {
|
|
94
|
+
if (debug) console.log(`â ïļ JS resolver depth (${maxDepth}) reached at ${filePath}`);
|
|
95
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { imports } = cfg;
|
|
99
|
+
const ROOT = process.cwd();
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
if (!ctx.aliases)
|
|
102
|
+
ctx.aliases = { ...loadProjectAliases(ROOT), ...imports.aliases };
|
|
103
|
+
const aliases = ctx.aliases;
|
|
104
|
+
|
|
105
|
+
const isExcluded = (p) =>
|
|
106
|
+
micromatch.isMatch(p.replaceAll("\\", "/"), imports.excludes || []);
|
|
107
|
+
if (visited.has(filePath))
|
|
108
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
87
109
|
visited.add(filePath);
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(filePath) || isExcluded(filePath))
|
|
112
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
90
113
|
|
|
91
114
|
const code = fs.readFileSync(filePath, "utf8");
|
|
92
115
|
const ext = path.extname(filePath).toLowerCase();
|
|
93
116
|
if (![".ts", ".tsx", ".d.ts", ".js", ".jsx", ".mjs"].includes(ext))
|
|
94
|
-
return { files: [], visited };
|
|
117
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
95
118
|
|
|
96
|
-
const aliases = loadJsAliases();
|
|
97
119
|
const patterns = [
|
|
98
120
|
/import\s+[^'"]*['"]([^'"]+)['"]/g,
|
|
99
121
|
/import\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
100
122
|
/require\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
101
|
-
/export\s+\*\s+from\s+['"]([^'"]+)['"]/g
|
|
123
|
+
/export\s+\*\s+from\s+['"]([^'"]+)['"]/g
|
|
102
124
|
];
|
|
103
125
|
|
|
104
126
|
const matches = new Set();
|
|
@@ -108,28 +130,51 @@ export async function resolveJsImports(filePath, visited = new Set(), depth = 0,
|
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
const resolved = [];
|
|
133
|
+
const expectedImports = new Set();
|
|
134
|
+
const resolvedImports = new Set();
|
|
135
|
+
|
|
111
136
|
for (const imp of matches) {
|
|
112
137
|
if (!imp.startsWith(".") && !imp.startsWith("/") && !imp.startsWith("@")) continue;
|
|
113
|
-
if (
|
|
138
|
+
if (isExcluded(imp)) continue;
|
|
139
|
+
|
|
140
|
+
expectedImports.add(imp);
|
|
114
141
|
|
|
115
142
|
let importPath;
|
|
116
143
|
if (imp.startsWith("@")) {
|
|
117
|
-
const aliasKey = Object.keys(aliases).find(a => imp.startsWith(a));
|
|
144
|
+
const aliasKey = Object.keys(aliases).find((a) => imp.startsWith(a));
|
|
118
145
|
if (aliasKey) {
|
|
119
146
|
const relPart = imp.slice(aliasKey.length).replace(/^\/+/, "");
|
|
120
147
|
importPath = path.join(aliases[aliasKey], relPart);
|
|
121
148
|
} else continue;
|
|
122
|
-
} else
|
|
149
|
+
} else {
|
|
150
|
+
importPath = path.resolve(path.dirname(filePath), imp);
|
|
151
|
+
}
|
|
123
152
|
|
|
124
153
|
const resolvedPath = tryResolveImport(importPath);
|
|
125
|
-
if (!resolvedPath ||
|
|
154
|
+
if (!resolvedPath || isExcluded(resolvedPath)) continue;
|
|
126
155
|
resolved.push(resolvedPath);
|
|
156
|
+
resolvedImports.add(resolvedPath);
|
|
127
157
|
|
|
128
158
|
if (depth < maxDepth) {
|
|
129
|
-
const sub = await resolveJsImports(resolvedPath, visited, depth + 1, maxDepth);
|
|
159
|
+
const sub = await resolveJsImports(resolvedPath, cfg, visited, depth + 1, maxDepth, ctx);
|
|
130
160
|
resolved.push(...sub.files);
|
|
161
|
+
sub.stats?.expected?.forEach((i) => expectedImports.add(i));
|
|
162
|
+
sub.stats?.resolved?.forEach((i) => resolvedImports.add(i));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Always-include patterns (no recursion)
|
|
167
|
+
for (const pattern of imports.includes || []) {
|
|
168
|
+
const scan = safeMicromatchScan(pattern, { cwd: ROOT, absolute: true });
|
|
169
|
+
if (scan?.files) {
|
|
170
|
+
for (const f of scan.files) resolved.push(path.resolve(ROOT, f));
|
|
131
171
|
}
|
|
132
172
|
}
|
|
133
173
|
|
|
134
|
-
|
|
174
|
+
log("â
JS resolver completed for", filePath, "â", resolved.length, "files");
|
|
175
|
+
return {
|
|
176
|
+
files: [...new Set(resolved)],
|
|
177
|
+
visited,
|
|
178
|
+
stats: { expected: expectedImports, resolved: resolvedImports }
|
|
179
|
+
};
|
|
135
180
|
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import
|
|
3
|
+
import micromatch from "micromatch";
|
|
4
4
|
import { loadLaravelBindings } from "./php-bindings.js";
|
|
5
|
+
import { safeMicromatchScan } from "../core/file-utils.js";
|
|
5
6
|
|
|
6
7
|
const debug = process.env.PRODEX_DEBUG === "1";
|
|
7
|
-
const log = (...
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const log = (...a) => {
|
|
9
|
+
if (debug) console.log("ðŠķ [php-resolver]", ...a);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load PSR-4 namespaces from composer.json.
|
|
14
|
+
*/
|
|
15
|
+
function loadComposerNamespaces(ROOT) {
|
|
12
16
|
const composerPath = path.join(ROOT, "composer.json");
|
|
13
17
|
if (!fs.existsSync(composerPath)) return {};
|
|
14
18
|
try {
|
|
@@ -23,8 +27,9 @@ function loadComposerNamespaces() {
|
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Try to resolve a PHP include/namespace to a file.
|
|
32
|
+
*/
|
|
28
33
|
function tryResolvePhpImport(basePath) {
|
|
29
34
|
if (!basePath || typeof basePath !== "string") return null;
|
|
30
35
|
const tries = [basePath, basePath + ".php", path.join(basePath, "index.php")];
|
|
@@ -33,24 +38,49 @@ function tryResolvePhpImport(basePath) {
|
|
|
33
38
|
return null;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Core PHP resolver â PSR-4 + Laravel bindings + glob excludes.
|
|
43
|
+
* Returns unique expected + resolved import sets.
|
|
44
|
+
*/
|
|
38
45
|
export async function resolvePhpImports(
|
|
39
46
|
filePath,
|
|
47
|
+
cfg,
|
|
40
48
|
visited = new Set(),
|
|
41
49
|
depth = 0,
|
|
42
|
-
maxDepth = 10
|
|
50
|
+
maxDepth = 10,
|
|
51
|
+
ctx = {}
|
|
43
52
|
) {
|
|
44
|
-
if (
|
|
53
|
+
if (depth >= maxDepth) {
|
|
54
|
+
if (debug) console.log(`â ïļ PHP resolver depth (${maxDepth}) reached at ${filePath}`);
|
|
55
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { imports } = cfg;
|
|
59
|
+
const ROOT = process.cwd();
|
|
60
|
+
|
|
61
|
+
if (!ctx.namespaces) ctx.namespaces = loadComposerNamespaces(ROOT);
|
|
62
|
+
if (!ctx.bindings) ctx.bindings = loadLaravelBindings();
|
|
63
|
+
|
|
64
|
+
const namespaces = ctx.namespaces;
|
|
65
|
+
const bindings = ctx.bindings;
|
|
66
|
+
const isExcluded = (p) =>
|
|
67
|
+
micromatch.isMatch(p.replaceAll("\\", "/"), imports.excludes || []);
|
|
68
|
+
|
|
69
|
+
if (visited.has(filePath))
|
|
70
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
45
71
|
visited.add(filePath);
|
|
46
|
-
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(filePath) || isExcluded(filePath))
|
|
74
|
+
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
47
75
|
|
|
48
76
|
const code = fs.readFileSync(filePath, "utf8");
|
|
77
|
+
const resolved = [];
|
|
78
|
+
const expectedImports = new Set();
|
|
79
|
+
const resolvedImports = new Set();
|
|
49
80
|
|
|
50
|
-
// find include/require + grouped and single use statements
|
|
51
81
|
const patterns = [
|
|
52
82
|
/\b(?:require|include|require_once|include_once)\s*\(?['"]([^'"]+)['"]\)?/g,
|
|
53
|
-
/\buse\s+([A-Z][\w\\]+(?:\s*{[^}]+})?)/g
|
|
83
|
+
/\buse\s+([A-Z][\w\\]+(?:\s*{[^}]+})?)/g
|
|
54
84
|
];
|
|
55
85
|
|
|
56
86
|
const rawMatches = new Set();
|
|
@@ -67,7 +97,7 @@ export async function resolvePhpImports(
|
|
|
67
97
|
const base = groupMatch[1].trim().replace(/\\+$/, "");
|
|
68
98
|
const parts = groupMatch[2]
|
|
69
99
|
.split(",")
|
|
70
|
-
.map(x => x.trim())
|
|
100
|
+
.map((x) => x.trim())
|
|
71
101
|
.filter(Boolean);
|
|
72
102
|
for (const p of parts) matches.add(`${base}\\${p}`);
|
|
73
103
|
} else {
|
|
@@ -75,41 +105,51 @@ export async function resolvePhpImports(
|
|
|
75
105
|
}
|
|
76
106
|
}
|
|
77
107
|
|
|
78
|
-
const namespaces = loadComposerNamespaces();
|
|
79
|
-
const bindings = loadLaravelBindings();
|
|
80
|
-
const resolved = [];
|
|
81
|
-
|
|
82
108
|
for (const imp0 of matches) {
|
|
83
109
|
let imp = imp0;
|
|
84
110
|
|
|
85
|
-
// Interface â Implementation mapping via Service Providers
|
|
86
111
|
if (bindings[imp]) {
|
|
87
112
|
imp = bindings[imp];
|
|
88
|
-
log("ð Interface resolved via
|
|
113
|
+
log("ð Interface resolved via binding:", imp0, "â", imp);
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
let importPath;
|
|
92
|
-
|
|
93
|
-
// PSR-4 namespace resolution
|
|
94
117
|
if (imp.includes("\\")) {
|
|
95
|
-
const nsKey = Object.keys(namespaces).find(k => imp.startsWith(k));
|
|
96
|
-
if (!nsKey) continue;
|
|
118
|
+
const nsKey = Object.keys(namespaces).find((k) => imp.startsWith(k));
|
|
119
|
+
if (!nsKey) continue;
|
|
97
120
|
const relPart = imp.slice(nsKey.length).replace(/\\/g, "/");
|
|
98
121
|
importPath = path.join(namespaces[nsKey], `${relPart}.php`);
|
|
99
122
|
} else {
|
|
100
123
|
importPath = path.resolve(path.dirname(filePath), imp);
|
|
101
124
|
}
|
|
102
125
|
|
|
103
|
-
if (!importPath ||
|
|
126
|
+
if (!importPath || isExcluded(importPath)) continue;
|
|
127
|
+
|
|
128
|
+
expectedImports.add(imp);
|
|
129
|
+
|
|
104
130
|
const resolvedPath = tryResolvePhpImport(importPath);
|
|
105
|
-
if (!resolvedPath) continue;
|
|
131
|
+
if (!resolvedPath || isExcluded(resolvedPath)) continue;
|
|
106
132
|
resolved.push(resolvedPath);
|
|
133
|
+
resolvedImports.add(resolvedPath);
|
|
107
134
|
|
|
108
135
|
if (depth < maxDepth) {
|
|
109
|
-
const sub = await resolvePhpImports(resolvedPath, visited, depth + 1, maxDepth);
|
|
136
|
+
const sub = await resolvePhpImports(resolvedPath, cfg, visited, depth + 1, maxDepth, ctx);
|
|
110
137
|
resolved.push(...sub.files);
|
|
138
|
+
sub.stats?.expected?.forEach((i) => expectedImports.add(i));
|
|
139
|
+
sub.stats?.resolved?.forEach((i) => resolvedImports.add(i));
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
142
|
|
|
114
|
-
|
|
143
|
+
// Always-include patterns (no recursion)
|
|
144
|
+
for (const pattern of imports.includes || []) {
|
|
145
|
+
const scan = safeMicromatchScan(pattern, { cwd: ROOT, absolute: true });
|
|
146
|
+
if (scan?.files) for (const f of scan.files) resolved.push(path.resolve(ROOT, f));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
log("â
PHP resolver completed for", filePath, "â", resolved.length, "files");
|
|
150
|
+
return {
|
|
151
|
+
files: [...new Set(resolved)],
|
|
152
|
+
visited,
|
|
153
|
+
stats: { expected: expectedImports, resolved: resolvedImports }
|
|
154
|
+
};
|
|
115
155
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prodex",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Unified Project Indexer & Dependency Extractor for Laravel + React + Node stacks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,10 +34,12 @@
|
|
|
34
34
|
"author": "emxhive",
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"devDependencies": {
|
|
37
|
+
"@types/node": "^24.9.1",
|
|
37
38
|
"tsup": "^8.5.0",
|
|
38
39
|
"typescript": "^5.9.3"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"inquirer": "^12.10.0"
|
|
42
|
+
"inquirer": "^12.10.0",
|
|
43
|
+
"micromatch": "^4.0.8"
|
|
42
44
|
}
|
|
43
45
|
}
|