prodex 1.1.0 â 1.3.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/README.md +234 -140
- package/bin/prodex.js +1 -17
- package/dist/cli/cli-input.js +106 -0
- package/dist/cli/flags.js +42 -0
- package/dist/cli/init.js +21 -0
- package/dist/cli/picker.js +82 -0
- package/dist/cli/summary.js +26 -0
- package/dist/constants/config-loader.js +95 -0
- package/dist/constants/config.js +26 -0
- package/dist/constants/default-config.js +34 -0
- package/dist/constants/render-constants.js +28 -0
- package/dist/core/combine.js +38 -0
- package/dist/core/dependency.js +55 -0
- package/dist/core/file-utils.js +41 -0
- package/dist/core/helpers.js +81 -0
- package/dist/core/output.js +48 -0
- package/dist/core/parsers/extract-imports.js +51 -0
- package/dist/core/renderers.js +42 -0
- package/dist/index.js +26 -0
- package/dist/lib/logger.js +14 -0
- package/dist/lib/polyfills.js +27 -0
- package/dist/lib/prompt.js +34 -0
- package/dist/lib/questions.js +28 -0
- package/dist/lib/utils.js +51 -0
- package/dist/resolvers/js/alias-loader.js +52 -0
- package/dist/resolvers/js/js-resolver.js +153 -0
- package/dist/resolvers/php/bindings.js +32 -0
- package/dist/resolvers/php/patterns.js +17 -0
- package/dist/resolvers/php/php-resolver.js +88 -0
- package/dist/resolvers/php/psr4.js +26 -0
- package/dist/resolvers/shared/excludes.js +11 -0
- package/dist/resolvers/shared/file-cache.js +29 -0
- package/dist/resolvers/shared/stats.js +17 -0
- package/dist/types/cli.types.js +12 -0
- package/dist/types/config.types.js +2 -0
- package/dist/types/core.types.js +2 -0
- package/dist/types/index.js +21 -0
- package/dist/types/resolver.types.js +2 -0
- package/dist/types/utils.types.js +2 -0
- package/package.json +16 -12
- package/dist/LICENSE +0 -21
- package/dist/README.md +0 -140
- package/dist/bin/prodex.js +0 -18
- package/dist/package.json +0 -45
- package/dist/src/cli/init.js +0 -18
- package/dist/src/cli/picker.js +0 -59
- package/dist/src/cli/summary.js +0 -6
- package/dist/src/constants/config-loader.js +0 -87
- package/dist/src/constants/config.js +0 -13
- package/dist/src/constants/default-config.js +0 -36
- package/dist/src/constants/render-constants.js +0 -22
- package/dist/src/core/alias-loader.js +0 -8
- package/dist/src/core/combine.js +0 -145
- package/dist/src/core/file-utils.js +0 -45
- package/dist/src/core/helpers.js +0 -77
- package/dist/src/core/renderers.js +0 -58
- package/dist/src/index.js +0 -15
- package/dist/src/resolvers/js-resolver.js +0 -180
- package/dist/src/resolvers/php-bindings.js +0 -31
- package/dist/src/resolvers/php-resolver.js +0 -155
package/dist/src/core/helpers.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import micromatch from "micromatch";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Get a root-relative version of a path.
|
|
7
|
-
*/
|
|
8
|
-
export function rel(p, root = process.cwd()) {
|
|
9
|
-
return path.relative(root, p).replaceAll("\\", "/");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Safe text read.
|
|
14
|
-
*/
|
|
15
|
-
export function read(p) {
|
|
16
|
-
try {
|
|
17
|
-
return fs.readFileSync(p, "utf8");
|
|
18
|
-
} catch {
|
|
19
|
-
return "";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check if a path/file matches any of the provided glob patterns.
|
|
25
|
-
*/
|
|
26
|
-
export function isExcluded(p, patterns, root = process.cwd()) {
|
|
27
|
-
if (!patterns?.length) return false;
|
|
28
|
-
const relPath = rel(p, root);
|
|
29
|
-
return micromatch.isMatch(relPath, patterns);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Recursive walker that respects glob excludes.
|
|
34
|
-
* Returns all files under the given directory tree.
|
|
35
|
-
*/
|
|
36
|
-
export function* walk(dir, cfg, depth = 0) {
|
|
37
|
-
const { scanDepth, entry } = cfg;
|
|
38
|
-
const root = process.cwd();
|
|
39
|
-
if (depth > scanDepth) return;
|
|
40
|
-
|
|
41
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
42
|
-
for (const e of entries) {
|
|
43
|
-
const full = path.join(dir, e.name);
|
|
44
|
-
|
|
45
|
-
if (e.isDirectory()) {
|
|
46
|
-
// Skip excluded directories entirely
|
|
47
|
-
const relPath = rel(full, root);
|
|
48
|
-
if (isExcluded(relPath, entry.excludes)) continue;
|
|
49
|
-
yield* walk(full, cfg, depth + 1);
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (e.isFile()) {
|
|
54
|
-
const relPath = rel(full, root);
|
|
55
|
-
if (isExcluded(relPath, entry.excludes)) continue;
|
|
56
|
-
yield full;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Sorts files so that priority items appear first.
|
|
63
|
-
*/
|
|
64
|
-
export function sortWithPriority(files, priorityList = []) {
|
|
65
|
-
if (!priorityList.length) return files;
|
|
66
|
-
const prioritized = [];
|
|
67
|
-
const normal = [];
|
|
68
|
-
|
|
69
|
-
for (const f of files) {
|
|
70
|
-
const normalized = f.replaceAll("\\", "/").toLowerCase();
|
|
71
|
-
if (priorityList.some(p => micromatch.isMatch(normalized, p.toLowerCase())))
|
|
72
|
-
prioritized.push(f);
|
|
73
|
-
else normal.push(f);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return [...new Set([...prioritized, ...normal])];
|
|
77
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { read, rel } from "./helpers.js";
|
|
3
|
-
import { LANG_MAP } from "../constants/render-constants.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generate Markdown Table of Contents
|
|
7
|
-
* Sorted alphabetically for deterministic structure.
|
|
8
|
-
*/
|
|
9
|
-
export function tocMd(files) {
|
|
10
|
-
const sorted = [...files].sort((a, b) => a.localeCompare(b));
|
|
11
|
-
const items = sorted.map(f => "- " + rel(f)).join("\n");
|
|
12
|
-
return `# Included Source Files\n\n${items}\n\n---\n`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Render a single file section in Markdown format.
|
|
17
|
-
* The first file skips the leading separator to avoid duplicates.
|
|
18
|
-
*/
|
|
19
|
-
export function renderMd(p, isFirst = false) {
|
|
20
|
-
const rp = rel(p);
|
|
21
|
-
const ext = path.extname(p).toLowerCase();
|
|
22
|
-
const lang = LANG_MAP[ext] || "txt";
|
|
23
|
-
const code = read(p).trimEnd();
|
|
24
|
-
|
|
25
|
-
return [
|
|
26
|
-
isFirst ? "" : "---", // only add separator *after* the first file
|
|
27
|
-
`\`File: ${rp}\``,
|
|
28
|
-
"",
|
|
29
|
-
"```" + lang,
|
|
30
|
-
code,
|
|
31
|
-
"```",
|
|
32
|
-
""
|
|
33
|
-
]
|
|
34
|
-
.filter(Boolean)
|
|
35
|
-
.join("\n");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* TXT version (unchanged)
|
|
40
|
-
*/
|
|
41
|
-
export function tocTxt(files) {
|
|
42
|
-
const sorted = [...files].sort((a, b) => a.localeCompare(b));
|
|
43
|
-
return (
|
|
44
|
-
["##==== Combined Scope ====", ...sorted.map(f => "## - " + rel(f))].join("\n") + "\n\n"
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function renderTxt(p) {
|
|
49
|
-
const relPath = rel(p);
|
|
50
|
-
const code = read(p);
|
|
51
|
-
return [
|
|
52
|
-
"##==== path: " + relPath + " ====",
|
|
53
|
-
"##region " + relPath,
|
|
54
|
-
code,
|
|
55
|
-
"##endregion",
|
|
56
|
-
""
|
|
57
|
-
].join("\n");
|
|
58
|
-
}
|
package/dist/src/index.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { runCombine } from "./core/combine.js";
|
|
2
|
-
import { initProdex } from "./cli/init.js";
|
|
3
|
-
|
|
4
|
-
export default async function startProdex() {
|
|
5
|
-
const args = process.argv.slice(2);
|
|
6
|
-
if (args.includes("init")) return await initProdex();
|
|
7
|
-
|
|
8
|
-
const entryArgs = args.filter(a => !a.startsWith("--"));
|
|
9
|
-
const hasEntries = entryArgs.length > 0;
|
|
10
|
-
|
|
11
|
-
console.clear();
|
|
12
|
-
console.log("ð§Đ Prodex â Project Dependency Extractor\n");
|
|
13
|
-
|
|
14
|
-
await runCombine({ entries: hasEntries ? entryArgs : null });
|
|
15
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import micromatch from "micromatch";
|
|
4
|
-
import { safeMicromatchScan } from "../core/file-utils.js";
|
|
5
|
-
|
|
6
|
-
const debug = process.env.PRODEX_DEBUG === "1";
|
|
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 = [
|
|
17
|
-
"vite.config.ts",
|
|
18
|
-
"vite.config.js",
|
|
19
|
-
"vite.config.mts",
|
|
20
|
-
"vite.config.mjs",
|
|
21
|
-
"vite.config.cjs"
|
|
22
|
-
];
|
|
23
|
-
const map = {};
|
|
24
|
-
|
|
25
|
-
// --- Vite aliases ---------------------------------------------------------
|
|
26
|
-
for (const f of viteFiles) {
|
|
27
|
-
const p = path.join(ROOT, f);
|
|
28
|
-
if (!fs.existsSync(p)) continue;
|
|
29
|
-
const s = fs.readFileSync(p, "utf8");
|
|
30
|
-
const block = /resolve\s*:\s*{[\s\S]*?alias\s*:\s*{([\s\S]*?)}/m.exec(s);
|
|
31
|
-
if (!block) continue;
|
|
32
|
-
const re = /['"]([^'"]+)['"]\s*:\s*['"]([^'"]+)['"]/g;
|
|
33
|
-
let m;
|
|
34
|
-
while ((m = re.exec(block[1]))) {
|
|
35
|
-
const key = m[1];
|
|
36
|
-
const raw = m[2].replace(/^\/+/, "");
|
|
37
|
-
map[key] = path.resolve(ROOT, raw);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
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 {}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return map;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Try to resolve an import path into a real file.
|
|
67
|
-
*/
|
|
68
|
-
function tryResolveImport(basePath) {
|
|
69
|
-
const ext = path.extname(basePath);
|
|
70
|
-
const tries = [];
|
|
71
|
-
if (ext) tries.push(basePath);
|
|
72
|
-
else {
|
|
73
|
-
for (const x of [".ts", ".tsx", ".d.ts", ".js", ".jsx", ".mjs"])
|
|
74
|
-
tries.push(basePath + x, path.join(basePath, "index" + x));
|
|
75
|
-
}
|
|
76
|
-
for (const t of tries)
|
|
77
|
-
if (fs.existsSync(t) && fs.statSync(t).isFile()) return path.resolve(t);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
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();
|
|
100
|
-
|
|
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() } };
|
|
109
|
-
visited.add(filePath);
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(filePath) || isExcluded(filePath))
|
|
112
|
-
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
113
|
-
|
|
114
|
-
const code = fs.readFileSync(filePath, "utf8");
|
|
115
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
116
|
-
if (![".ts", ".tsx", ".d.ts", ".js", ".jsx", ".mjs"].includes(ext))
|
|
117
|
-
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
118
|
-
|
|
119
|
-
const patterns = [
|
|
120
|
-
/import\s+[^'"]*['"]([^'"]+)['"]/g,
|
|
121
|
-
/import\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
122
|
-
/require\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
123
|
-
/export\s+\*\s+from\s+['"]([^'"]+)['"]/g
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
const matches = new Set();
|
|
127
|
-
for (const r of patterns) {
|
|
128
|
-
let m;
|
|
129
|
-
while ((m = r.exec(code))) matches.add(m[1]);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const resolved = [];
|
|
133
|
-
const expectedImports = new Set();
|
|
134
|
-
const resolvedImports = new Set();
|
|
135
|
-
|
|
136
|
-
for (const imp of matches) {
|
|
137
|
-
if (!imp.startsWith(".") && !imp.startsWith("/") && !imp.startsWith("@")) continue;
|
|
138
|
-
if (isExcluded(imp)) continue;
|
|
139
|
-
|
|
140
|
-
expectedImports.add(imp);
|
|
141
|
-
|
|
142
|
-
let importPath;
|
|
143
|
-
if (imp.startsWith("@")) {
|
|
144
|
-
const aliasKey = Object.keys(aliases).find((a) => imp.startsWith(a));
|
|
145
|
-
if (aliasKey) {
|
|
146
|
-
const relPart = imp.slice(aliasKey.length).replace(/^\/+/, "");
|
|
147
|
-
importPath = path.join(aliases[aliasKey], relPart);
|
|
148
|
-
} else continue;
|
|
149
|
-
} else {
|
|
150
|
-
importPath = path.resolve(path.dirname(filePath), imp);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const resolvedPath = tryResolveImport(importPath);
|
|
154
|
-
if (!resolvedPath || isExcluded(resolvedPath)) continue;
|
|
155
|
-
resolved.push(resolvedPath);
|
|
156
|
-
resolvedImports.add(resolvedPath);
|
|
157
|
-
|
|
158
|
-
if (depth < maxDepth) {
|
|
159
|
-
const sub = await resolveJsImports(resolvedPath, cfg, visited, depth + 1, maxDepth, ctx);
|
|
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));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
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
|
-
};
|
|
180
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { ROOT } from "../constants/config.js";
|
|
4
|
-
|
|
5
|
-
export function loadLaravelBindings() {
|
|
6
|
-
const providersDir = path.join(ROOT, "app", "Providers");
|
|
7
|
-
const bindings = {};
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(providersDir)) return bindings;
|
|
10
|
-
|
|
11
|
-
const files = fs
|
|
12
|
-
.readdirSync(providersDir)
|
|
13
|
-
.filter(f => f.endsWith(".php"))
|
|
14
|
-
.map(f => path.join(providersDir, f));
|
|
15
|
-
|
|
16
|
-
// Match: $this->app->bind(Interface::class, Implementation::class)
|
|
17
|
-
const re =
|
|
18
|
-
/$this->app->(?:bind|singleton)\s*\(\s*([A-Za-z0-9_:\\\\]+)::class\s*,\s*([A-Za-z0-9_:\\\\]+)::class/g;
|
|
19
|
-
|
|
20
|
-
for (const file of files) {
|
|
21
|
-
const code = fs.readFileSync(file, "utf8");
|
|
22
|
-
let m;
|
|
23
|
-
while ((m = re.exec(code))) {
|
|
24
|
-
const iface = m[1].replace(/\\\\/g, "\\");
|
|
25
|
-
const impl = m[2].replace(/\\\\/g, "\\");
|
|
26
|
-
bindings[iface] = impl;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return bindings;
|
|
31
|
-
}
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import micromatch from "micromatch";
|
|
4
|
-
import { loadLaravelBindings } from "./php-bindings.js";
|
|
5
|
-
import { safeMicromatchScan } from "../core/file-utils.js";
|
|
6
|
-
|
|
7
|
-
const debug = process.env.PRODEX_DEBUG === "1";
|
|
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) {
|
|
16
|
-
const composerPath = path.join(ROOT, "composer.json");
|
|
17
|
-
if (!fs.existsSync(composerPath)) return {};
|
|
18
|
-
try {
|
|
19
|
-
const data = JSON.parse(fs.readFileSync(composerPath, "utf8"));
|
|
20
|
-
const psr4 = data.autoload?.["psr-4"] || {};
|
|
21
|
-
const map = {};
|
|
22
|
-
for (const ns in psr4)
|
|
23
|
-
map[ns.replace(/\\+$/, "")] = path.resolve(ROOT, psr4[ns]);
|
|
24
|
-
return map;
|
|
25
|
-
} catch {
|
|
26
|
-
return {};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Try to resolve a PHP include/namespace to a file.
|
|
32
|
-
*/
|
|
33
|
-
function tryResolvePhpImport(basePath) {
|
|
34
|
-
if (!basePath || typeof basePath !== "string") return null;
|
|
35
|
-
const tries = [basePath, basePath + ".php", path.join(basePath, "index.php")];
|
|
36
|
-
for (const t of tries)
|
|
37
|
-
if (fs.existsSync(t) && fs.statSync(t).isFile()) return path.resolve(t);
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Core PHP resolver â PSR-4 + Laravel bindings + glob excludes.
|
|
43
|
-
* Returns unique expected + resolved import sets.
|
|
44
|
-
*/
|
|
45
|
-
export async function resolvePhpImports(
|
|
46
|
-
filePath,
|
|
47
|
-
cfg,
|
|
48
|
-
visited = new Set(),
|
|
49
|
-
depth = 0,
|
|
50
|
-
maxDepth = 10,
|
|
51
|
-
ctx = {}
|
|
52
|
-
) {
|
|
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() } };
|
|
71
|
-
visited.add(filePath);
|
|
72
|
-
|
|
73
|
-
if (!fs.existsSync(filePath) || isExcluded(filePath))
|
|
74
|
-
return { files: [], visited, stats: { expected: new Set(), resolved: new Set() } };
|
|
75
|
-
|
|
76
|
-
const code = fs.readFileSync(filePath, "utf8");
|
|
77
|
-
const resolved = [];
|
|
78
|
-
const expectedImports = new Set();
|
|
79
|
-
const resolvedImports = new Set();
|
|
80
|
-
|
|
81
|
-
const patterns = [
|
|
82
|
-
/\b(?:require|include|require_once|include_once)\s*\(?['"]([^'"]+)['"]\)?/g,
|
|
83
|
-
/\buse\s+([A-Z][\w\\]+(?:\s*{[^}]+})?)/g
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const rawMatches = new Set();
|
|
87
|
-
for (const r of patterns) {
|
|
88
|
-
let m;
|
|
89
|
-
while ((m = r.exec(code))) rawMatches.add(m[1]);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Expand grouped uses
|
|
93
|
-
const matches = new Set();
|
|
94
|
-
for (const imp of rawMatches) {
|
|
95
|
-
const groupMatch = imp.match(/^(.+?)\s*{([^}]+)}/);
|
|
96
|
-
if (groupMatch) {
|
|
97
|
-
const base = groupMatch[1].trim().replace(/\\+$/, "");
|
|
98
|
-
const parts = groupMatch[2]
|
|
99
|
-
.split(",")
|
|
100
|
-
.map((x) => x.trim())
|
|
101
|
-
.filter(Boolean);
|
|
102
|
-
for (const p of parts) matches.add(`${base}\\${p}`);
|
|
103
|
-
} else {
|
|
104
|
-
matches.add(imp.trim());
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
for (const imp0 of matches) {
|
|
109
|
-
let imp = imp0;
|
|
110
|
-
|
|
111
|
-
if (bindings[imp]) {
|
|
112
|
-
imp = bindings[imp];
|
|
113
|
-
log("ð Interface resolved via binding:", imp0, "â", imp);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let importPath;
|
|
117
|
-
if (imp.includes("\\")) {
|
|
118
|
-
const nsKey = Object.keys(namespaces).find((k) => imp.startsWith(k));
|
|
119
|
-
if (!nsKey) continue;
|
|
120
|
-
const relPart = imp.slice(nsKey.length).replace(/\\/g, "/");
|
|
121
|
-
importPath = path.join(namespaces[nsKey], `${relPart}.php`);
|
|
122
|
-
} else {
|
|
123
|
-
importPath = path.resolve(path.dirname(filePath), imp);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!importPath || isExcluded(importPath)) continue;
|
|
127
|
-
|
|
128
|
-
expectedImports.add(imp);
|
|
129
|
-
|
|
130
|
-
const resolvedPath = tryResolvePhpImport(importPath);
|
|
131
|
-
if (!resolvedPath || isExcluded(resolvedPath)) continue;
|
|
132
|
-
resolved.push(resolvedPath);
|
|
133
|
-
resolvedImports.add(resolvedPath);
|
|
134
|
-
|
|
135
|
-
if (depth < maxDepth) {
|
|
136
|
-
const sub = await resolvePhpImports(resolvedPath, cfg, visited, depth + 1, maxDepth, ctx);
|
|
137
|
-
resolved.push(...sub.files);
|
|
138
|
-
sub.stats?.expected?.forEach((i) => expectedImports.add(i));
|
|
139
|
-
sub.stats?.resolved?.forEach((i) => resolvedImports.add(i));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
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
|
-
};
|
|
155
|
-
}
|