prodex 1.1.0 → 1.2.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.
Files changed (57) hide show
  1. package/README.md +174 -86
  2. package/bin/prodex.js +1 -17
  3. package/dist/cli/cli-input.js +86 -0
  4. package/dist/cli/flags.js +42 -0
  5. package/dist/cli/init.js +21 -0
  6. package/dist/cli/picker.js +82 -0
  7. package/dist/cli/summary.js +14 -0
  8. package/dist/constants/config-loader.js +90 -0
  9. package/dist/constants/config.js +17 -0
  10. package/dist/constants/default-config.js +48 -0
  11. package/dist/constants/render-constants.js +23 -0
  12. package/dist/core/combine.js +74 -0
  13. package/dist/core/dependency.js +51 -0
  14. package/dist/core/file-utils.js +44 -0
  15. package/dist/core/helpers.js +81 -0
  16. package/dist/core/parsers/extract-imports.js +51 -0
  17. package/dist/core/renderers.js +42 -0
  18. package/dist/index.js +29 -0
  19. package/dist/lib/logger.js +14 -0
  20. package/dist/lib/polyfills.js +12 -0
  21. package/dist/lib/utils.js +15 -0
  22. package/dist/resolvers/js/alias-loader.js +52 -0
  23. package/dist/resolvers/js/js-resolver.js +153 -0
  24. package/dist/resolvers/php/bindings.js +32 -0
  25. package/dist/resolvers/php/patterns.js +17 -0
  26. package/dist/resolvers/php/php-resolver.js +88 -0
  27. package/dist/resolvers/php/psr4.js +26 -0
  28. package/dist/resolvers/shared/excludes.js +11 -0
  29. package/dist/resolvers/shared/file-cache.js +29 -0
  30. package/dist/resolvers/shared/stats.js +17 -0
  31. package/dist/types/cli.types.js +12 -0
  32. package/dist/types/config.types.js +2 -0
  33. package/dist/types/core.types.js +2 -0
  34. package/dist/types/index.js +21 -0
  35. package/dist/types/resolver.types.js +2 -0
  36. package/dist/types/utils.types.js +2 -0
  37. package/package.json +16 -12
  38. package/dist/LICENSE +0 -21
  39. package/dist/README.md +0 -140
  40. package/dist/bin/prodex.js +0 -18
  41. package/dist/package.json +0 -45
  42. package/dist/src/cli/init.js +0 -18
  43. package/dist/src/cli/picker.js +0 -59
  44. package/dist/src/cli/summary.js +0 -6
  45. package/dist/src/constants/config-loader.js +0 -87
  46. package/dist/src/constants/config.js +0 -13
  47. package/dist/src/constants/default-config.js +0 -36
  48. package/dist/src/constants/render-constants.js +0 -22
  49. package/dist/src/core/alias-loader.js +0 -8
  50. package/dist/src/core/combine.js +0 -145
  51. package/dist/src/core/file-utils.js +0 -45
  52. package/dist/src/core/helpers.js +0 -77
  53. package/dist/src/core/renderers.js +0 -58
  54. package/dist/src/index.js +0 -15
  55. package/dist/src/resolvers/js-resolver.js +0 -180
  56. package/dist/src/resolvers/php-bindings.js +0 -31
  57. package/dist/src/resolvers/php-resolver.js +0 -155
@@ -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
- }