prodex 1.0.8 → 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 (54) 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 +17 -11
  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 -43
  42. package/dist/src/cli/init.js +0 -50
  43. package/dist/src/cli/picker.js +0 -64
  44. package/dist/src/cli/summary.js +0 -9
  45. package/dist/src/constants/config-loader.js +0 -64
  46. package/dist/src/constants/config.js +0 -90
  47. package/dist/src/core/alias-loader.js +0 -8
  48. package/dist/src/core/combine.js +0 -141
  49. package/dist/src/core/file-utils.js +0 -13
  50. package/dist/src/core/helpers.js +0 -127
  51. package/dist/src/index.js +0 -11
  52. package/dist/src/resolvers/js-resolver.js +0 -135
  53. package/dist/src/resolvers/php-bindings.js +0 -31
  54. package/dist/src/resolvers/php-resolver.js +0 -115
package/dist/package.json DELETED
@@ -1,43 +0,0 @@
1
- {
2
- "name": "prodex",
3
- "version": "1.0.8",
4
- "description": "Unified Project Indexer & Dependency Extractor for Laravel + React + Node stacks.",
5
- "type": "module",
6
- "bin": {
7
- "prodex": "./bin/prodex.js"
8
- },
9
- "main": "./dist/core/combine.js",
10
- "exports": {
11
- ".": "./dist/core/combine.js"
12
- },
13
- "files": [
14
- "dist/",
15
- "bin/",
16
- "README.md",
17
- "LICENSE"
18
- ],
19
- "keywords": [
20
- "laravel",
21
- "react",
22
- "typescript",
23
- "dependency",
24
- "analyzer",
25
- "cli",
26
- "node",
27
- "indexer"
28
- ],
29
- "scripts": {
30
- "clean": "rm -rf dist",
31
- "build": "npm run clean && node -e \"require('fs').mkdirSync('dist',{recursive:true})\" && cp -r src bin package.json README.md LICENSE dist/",
32
- "prepare": "npm run build"
33
- },
34
- "author": "emxhive",
35
- "license": "MIT",
36
- "devDependencies": {
37
- "tsup": "^8.5.0",
38
- "typescript": "^5.9.3"
39
- },
40
- "dependencies": {
41
- "inquirer": "^12.10.0"
42
- }
43
- }
@@ -1,50 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import inquirer from "inquirer";
4
-
5
- export async function initProdex() {
6
- console.log("🪄 Prodex Init — Configuration Wizard\n");
7
-
8
- const dest = path.join(process.cwd(), ".prodex.json");
9
- if (fs.existsSync(dest)) {
10
- const { overwrite } = await inquirer.prompt([
11
- { type: "confirm", name: "overwrite", message: ".prodex.json already exists. Overwrite?", default: false }
12
- ]);
13
- if (!overwrite) {
14
- console.log("❌ Cancelled.");
15
- return;
16
- }
17
- }
18
-
19
- const jsonc = `{
20
- "$schema": "https://raw.githubusercontent.com/emxhive/prodex/main/schema/prodex.schema.json",
21
- "output": "prodex",
22
- "scanDepth": 2,
23
- "limit": 200,
24
- "baseDirs": ["app", "routes", "resources/js"],
25
- "aliasOverrides": {
26
- "@hooks": "resources/js/hooks",
27
- "@data": "resources/js/data"
28
- },
29
- "priorityFiles": [
30
- "routes/web.php",
31
- "routes/api.php",
32
- "index.",
33
- "main.",
34
- "app."
35
- ],
36
- "entryExcludes": [
37
- "resources/js/components/ui/",
38
- "app/DTOs/"
39
- ],
40
- "importExcludes": [
41
- "node_modules",
42
- "@shadcn/"
43
- ]
44
- }
45
- `;
46
-
47
- fs.writeFileSync(dest, jsonc, "utf8");
48
- console.log(`✅ Created ${dest}`);
49
- console.log("💡 You can edit it anytime or rerun 'prodex init' to reset.");
50
- }
@@ -1,64 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import inquirer from "inquirer";
4
- import { ROOT } from "../constants/config.js";
5
- import { walk, rel, sortWithPriority } from "../core/helpers.js";
6
-
7
- export async function pickEntries(baseDirs, depth = 2, cfg = {}) {
8
- let selected = [];
9
- while (true) {
10
- const files = [];
11
- for (const base of baseDirs) {
12
- const full = path.join(ROOT, base);
13
- if (!fs.existsSync(full)) continue;
14
- for (const f of walk(full, 0, depth)) files.push(f);
15
- }
16
-
17
- const sorted = sortWithPriority(files, cfg.priorityFiles);
18
-
19
- const prioritized = sorted.filter(f =>
20
- cfg.priorityFiles?.some(p =>
21
- rel(f).replaceAll("\\", "/").toLowerCase().includes(p.toLowerCase())
22
- )
23
- );
24
-
25
- const choices = sorted.map(f => ({
26
- name: rel(f),
27
- value: f
28
- }));
29
-
30
- // if (prioritized.length) {
31
- // choices.unshift(new inquirer.Separator("⭐ Recommended entries"));
32
- // choices.splice(prioritized.length + 1, 0, new inquirer.Separator("─ Other files"));
33
- // }
34
-
35
- const { picks } = await inquirer.prompt([
36
- {
37
- type: "checkbox",
38
- name: "picks",
39
- message: `Select entry files (depth ${depth})`,
40
- choices,
41
- loop: false,
42
- pageSize: 20,
43
- default: selected
44
- }
45
- ]);
46
-
47
- if (picks.includes("__manual")) {
48
- const { manual } = await inquirer.prompt([
49
- { name: "manual", message: "Enter relative path:" }
50
- ]);
51
- if (manual.trim()) selected.push(path.resolve(ROOT, manual.trim()));
52
- }
53
-
54
- if (picks.includes("__loadmore")) {
55
- depth++;
56
- selected = picks.filter(p => !["__manual", "__loadmore"].includes(p));
57
- continue;
58
- }
59
-
60
- selected = picks.filter(p => !["__manual", "__loadmore"].includes(p));
61
- break;
62
- }
63
- return [...new Set(selected)];
64
- }
@@ -1,9 +0,0 @@
1
- export function showSummary({ outputDir, fileName, entries, scanDepth, limit, chain }) {
2
- console.log("\n🧩 Active Run:");
3
- console.log(" • Output Directory:", outputDir);
4
- console.log(" • File Name:", fileName);
5
- console.log(" • Entries:", entries.length);
6
- console.log(" • Scan Depth:", scanDepth);
7
- console.log(" • Limit:", limit);
8
- console.log(" • Chain:", chain ? "Enabled" : "Disabled");
9
- }
@@ -1,64 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import {
4
- ROOT,
5
- CODE_EXTS,
6
- ENTRY_EXCLUDES,
7
- IMPORT_EXCLUDES,
8
- BASE_DIRS,
9
- PRIORITY_FILES
10
- } from "./config.js";
11
-
12
- /**
13
- * Loads and merges the Prodex configuration.
14
- * - `output` is treated strictly as a directory.
15
- * - Defaults to ROOT/prodex when not defined.
16
- */
17
- export function loadProdexConfig() {
18
- const configPath = path.join(ROOT, ".prodex.json");
19
- let userConfig = {};
20
-
21
- if (fs.existsSync(configPath)) {
22
- try {
23
- const data = fs.readFileSync(configPath, "utf8");
24
- userConfig = JSON.parse(data);
25
- console.log("? Loaded .prodex.json overrides");
26
- } catch (err) {
27
- console.warn("?? Failed to parse .prodex.json:", err.message);
28
- }
29
- }
30
-
31
- const outputDir = userConfig.output
32
- ? path.resolve(ROOT, userConfig.output)
33
- : path.join(ROOT, "prodex");
34
-
35
- try {
36
- fs.mkdirSync(outputDir, { recursive: true });
37
- } catch (e) {
38
- console.warn("?? Could not create output directory:", outputDir);
39
- }
40
-
41
- const merged = {
42
- output: outputDir,
43
- scanDepth: userConfig.scanDepth || 2,
44
- codeExts: userConfig.codeExts || CODE_EXTS,
45
- entryExcludes: [...ENTRY_EXCLUDES, ...(userConfig.entryExcludes || [])],
46
- importExcludes: [...IMPORT_EXCLUDES, ...(userConfig.importExcludes || [])],
47
- baseDirs: [...new Set([...(userConfig.baseDirs || []), ...BASE_DIRS])],
48
- aliasOverrides: userConfig.aliasOverrides || {},
49
- limit: userConfig.limit || 200,
50
- priorityFiles: userConfig.priorityFiles || PRIORITY_FILES
51
- };
52
-
53
- // console.log("?? Active Config:");
54
- // console.log(" • Output Directory:", merged.output);
55
- // console.log(" • Scan Depth:", merged.scanDepth);
56
- // console.log(" • Base Dirs:", merged.baseDirs.join(", "));
57
- // if (userConfig.entryExcludes || userConfig.importExcludes)
58
- // console.log(" • Custom Exclusions:", {
59
- // entries: userConfig.entryExcludes?.length || 0,
60
- // imports: userConfig.importExcludes?.length || 0
61
- // });
62
-
63
- return merged;
64
- }
@@ -1,90 +0,0 @@
1
- import { resolveJsImports } from "../resolvers/js-resolver.js";
2
- import { resolvePhpImports } from "../resolvers/php-resolver.js";
3
-
4
-
5
- export const ROOT = process.cwd();
6
-
7
- export const CODE_EXTS = [".js", ".mjs", ".ts", ".tsx", ".d.ts", ".php"];
8
- export const ENTRY_EXCLUDES = [
9
- "resources/js/components/ui/",
10
- "app/Enums/",
11
- "app/DTOs/",
12
- "app/Models/",
13
- "app/Data/",
14
- "resources/js/wayfinder/",
15
- "resources/js/routes/",
16
- "resources/js/actions/",
17
- "resources/js/hooks/"
18
- ];
19
- export const IMPORT_EXCLUDES = [
20
- "node_modules",
21
- "@shadcn/",
22
- "@/components/ui/",
23
- "@components/ui/",
24
- "resources/js/components/ui/",
25
- "resources/js/hooks/",
26
- "resources/js/wayfinder/",
27
- "resources/js/routes/",
28
- "resources/js/actions/"
29
- ];
30
- export const BASE_DIRS = ["src", "bin", "schema", "app", "routes", "resources/js"];
31
-
32
- export const PRIORITY_FILES = [
33
- "routes/web.php",
34
- "routes/api.php",
35
- "index.",
36
- "main.",
37
- "app."
38
- ]
39
- /**
40
- * Resolver map — links file extensions to their resolver functions.
41
- * Extend this to support new formats (.vue, .jsx, etc.).
42
- */
43
- export const RESOLVERS = {
44
- ".php": resolvePhpImports,
45
- ".ts": resolveJsImports,
46
- ".tsx": resolveJsImports,
47
- ".d.ts": resolveJsImports,
48
- ".js": resolveJsImports
49
- };
50
-
51
- /**
52
- * Prompt definitions used by Inquirer in combine.js.
53
- * These are constants to keep UI consistent across releases.
54
- */
55
- export const PROMPTS = {
56
- yesToAll: {
57
- type: "confirm",
58
- name: "yesToAll",
59
- message: "Proceed automatically with default settings (Yes to all)?",
60
- default: true
61
- },
62
- combine: [
63
- {
64
- type: "input",
65
- name: "outputBase",
66
- message: "Output base name (without extension):",
67
- default: null, // will be set dynamically
68
- filter: v => v.trim()
69
- },
70
- {
71
- type: "number",
72
- name: "limit",
73
- message: "Limit number of merged files:",
74
- default: 200, // will be overridden at runtime
75
- validate: v => (!isNaN(v) && v > 0) || "Enter a valid positive number"
76
- },
77
- {
78
- type: "confirm",
79
- name: "chain",
80
- message: "Follow dependency chain?",
81
- default: true
82
- },
83
- {
84
- type: "confirm",
85
- name: "proceed",
86
- message: "Proceed with combine?",
87
- default: true
88
- }
89
- ]
90
- };
@@ -1,8 +0,0 @@
1
- export function loadJsAliases() {
2
- // will later handle vite + tsconfig
3
- return {};
4
- }
5
- export function loadComposerAliases() {
6
- // will later handle composer.json PSR-4
7
- return {};
8
- }
@@ -1,141 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import inquirer from "inquirer";
4
- import {
5
- ROOT,
6
- CODE_EXTS,
7
- RESOLVERS,
8
- PROMPTS
9
- } from "../constants/config.js";
10
- import { loadProdexConfig } from "../constants/config-loader.js";
11
- import { read, normalizeIndent, stripComments, rel } from "./helpers.js";
12
- import { pickEntries } from "../cli/picker.js";
13
- import { showSummary } from "../cli/summary.js";
14
- import { generateOutputName, resolveOutputPath } from "./file-utils.js";
15
-
16
- export async function runCombine() {
17
- const cliLimitFlag = process.argv.find(arg => arg.startsWith("--limit="));
18
- const customLimit = cliLimitFlag ? parseInt(cliLimitFlag.split("=")[1], 10) : null;
19
-
20
- const cfg = loadProdexConfig();
21
- const { baseDirs, scanDepth } = cfg;
22
-
23
- const entries = await pickEntries(baseDirs, scanDepth, cfg);
24
- if (!entries.length) {
25
- console.log("❌ No entries selected.");
26
- return;
27
- }
28
-
29
- const autoName = generateOutputName(entries);
30
- const outputDir = cfg.output || path.join(ROOT, "prodex");
31
- const defaultLimit = customLimit || cfg.limit || 200;
32
-
33
- console.log("\n📋 You selected:");
34
- for (const e of entries) console.log(" -", rel(e));
35
-
36
- const { yesToAll } = await inquirer.prompt([PROMPTS.yesToAll]);
37
-
38
- let outputBase = autoName,
39
- limit = defaultLimit,
40
- chain = true,
41
- proceed = true;
42
-
43
- if (!yesToAll) {
44
- const combinePrompts = PROMPTS.combine.map(p => ({
45
- ...p,
46
- default:
47
- p.name === "outputBase"
48
- ? autoName
49
- : p.name === "limit"
50
- ? defaultLimit
51
- : p.default
52
- }));
53
-
54
- const ans = await inquirer.prompt(combinePrompts);
55
- outputBase = ans.outputBase || autoName;
56
- limit = ans.limit;
57
- chain = ans.chain;
58
- proceed = ans.proceed;
59
- }
60
-
61
- if (!proceed) {
62
- console.log("⚙️ Aborted.");
63
- return;
64
- }
65
-
66
- try {
67
- fs.mkdirSync(outputDir, { recursive: true });
68
- } catch {
69
- console.warn("⚠️ Could not create output directory:", outputDir);
70
- }
71
-
72
- const output = resolveOutputPath(outputDir, outputBase);
73
-
74
- showSummary({
75
- outputDir,
76
- fileName: path.basename(output),
77
- entries,
78
- scanDepth: cfg.scanDepth,
79
- limit,
80
- chain
81
- });
82
-
83
- const finalFiles = chain ? await followChain(entries, limit) : entries;
84
-
85
- fs.writeFileSync(
86
- output,
87
- [toc(finalFiles), ...finalFiles.map(render)].join(""),
88
- "utf8"
89
- );
90
-
91
- console.log(`\n✅ ${output} written (${finalFiles.length} file(s)).`);
92
- }
93
-
94
- function header(p) {
95
- return `##==== path: ${rel(p)} ====`;
96
- }
97
- function regionStart(p) {
98
- return `##region ${rel(p)}`;
99
- }
100
- const regionEnd = "##endregion";
101
-
102
- function render(p) {
103
- const ext = path.extname(p);
104
- let s = read(p);
105
- return `${header(p)}\n${regionStart(p)}\n${s}\n${regionEnd}\n\n`;
106
- }
107
-
108
- function toc(files) {
109
- return (
110
- ["##==== Combined Scope ====", ...files.map(f => "## - " + rel(f))].join(
111
- "\n"
112
- ) + "\n\n"
113
- );
114
- }
115
-
116
- async function followChain(entryFiles, limit = 200) {
117
- console.log("🧩 Following dependency chain...");
118
- const visited = new Set();
119
- const all = [];
120
-
121
- for (const f of entryFiles) {
122
- if (visited.has(f)) continue;
123
- all.push(f);
124
-
125
- const ext = path.extname(f);
126
- if (!CODE_EXTS.includes(ext)) continue;
127
-
128
- const resolver = RESOLVERS[ext];
129
- if (resolver) {
130
- const { files } = await resolver(f, visited);
131
- all.push(...files);
132
- }
133
-
134
- if (all.length >= limit) {
135
- console.log("⚠️ Limit reached:", limit);
136
- break;
137
- }
138
- }
139
-
140
- return [...new Set(all)];
141
- }
@@ -1,13 +0,0 @@
1
- import path from "path";
2
-
3
- export function generateOutputName(entries) {
4
- const names = entries.map(f => path.basename(f, path.extname(f)));
5
- if (names.length === 1) return names[0];
6
- if (names.length === 2) return `${names[0]}-${names[1]}`;
7
- if (names.length > 2) return `${names[0]}-and-${names.length - 1}more`;
8
- return "unknown";
9
- }
10
-
11
- export function resolveOutputPath(outputDir, base) {
12
- return path.join(outputDir, `prodex-${base}-combined.txt`);
13
- }
@@ -1,127 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { ROOT, CODE_EXTS, ENTRY_EXCLUDES } from "../constants/config.js";
4
-
5
- export function rel(p) {
6
- return path.relative(ROOT, p).replaceAll("\\", "/");
7
- }
8
-
9
- export function read(p) {
10
- try {
11
- return fs.readFileSync(p, "utf8");
12
- } catch {
13
- return "";
14
- }
15
- }
16
-
17
- export function normalizeIndent(s) {
18
- return s
19
- .replace(/\t/g, " ")
20
- .split("\n")
21
- .map(l => l.replace(/[ \t]+$/, ""))
22
- .join("\n");
23
- }
24
-
25
- export function stripComments(code, ext) {
26
- if (ext === ".php") {
27
- return code
28
- .replace(/\/\*[\s\S]*?\*\//g, "")
29
- .replace(/^\s*#.*$/gm, "");
30
- }
31
-
32
- let out = "";
33
- let inStr = false;
34
- let strChar = "";
35
- let inBlockComment = false;
36
- let inLineComment = false;
37
-
38
- for (let i = 0; i < code.length; i++) {
39
- const c = code[i];
40
- const next = code[i + 1];
41
-
42
- if (inBlockComment) {
43
- if (c === "*" && next === "/") {
44
- inBlockComment = false;
45
- i++;
46
- }
47
- continue;
48
- }
49
-
50
- if (inLineComment) {
51
- if (c === "\n") {
52
- inLineComment = false;
53
- out += c;
54
- }
55
- continue;
56
- }
57
-
58
- if (inStr) {
59
- if (c === "\\" && next) {
60
- out += c + next;
61
- i++;
62
- continue;
63
- }
64
- if (c === strChar) inStr = false;
65
- out += c;
66
- continue;
67
- }
68
-
69
- if (c === '"' || c === "'" || c === "`") {
70
- inStr = true;
71
- strChar = c;
72
- out += c;
73
- continue;
74
- }
75
-
76
- if (c === "/" && next === "*") {
77
- inBlockComment = true;
78
- i++;
79
- continue;
80
- }
81
-
82
- if (c === "/" && next === "/") {
83
- inLineComment = true;
84
- i++;
85
- continue;
86
- }
87
-
88
- out += c;
89
- }
90
-
91
- return out;
92
- }
93
-
94
- export function isEntryExcluded(p) {
95
- const r = rel(p);
96
- return ENTRY_EXCLUDES.some(ex => r.startsWith(ex) || r.includes(ex));
97
- }
98
-
99
- export function* walk(dir, depth = 0, maxDepth = 2) {
100
- if (depth > maxDepth) return;
101
- const entries = fs.readdirSync(dir, { withFileTypes: true });
102
- for (const e of entries) {
103
- const full = path.join(dir, e.name);
104
- if (e.isDirectory()) yield* walk(full, depth + 1, maxDepth);
105
- else if (e.isFile()) {
106
- const ext = path.extname(e.name).toLowerCase();
107
- const relPath = rel(full);
108
- if (CODE_EXTS.includes(ext) && !ENTRY_EXCLUDES.some(ex => relPath.startsWith(ex))) {
109
- yield full;
110
- }
111
- }
112
- }
113
- }
114
-
115
- export function sortWithPriority(files, priorityList = []) {
116
- if (!priorityList.length) return files;
117
- const prioritized = [];
118
- const normal = [];
119
-
120
- for (const f of files) {
121
- const normalized = f.replaceAll("\\", "/").toLowerCase();
122
- if (priorityList.some(p => normalized.includes(p.toLowerCase()))) prioritized.push(f);
123
- else normal.push(f);
124
- }
125
-
126
- return [...new Set([...prioritized, ...normal])];
127
- }
package/dist/src/index.js DELETED
@@ -1,11 +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
- console.clear();
9
- console.log("🧩 Prodex — Project Dependency Extractor\n");
10
- await runCombine();
11
- }