prodex 1.4.7 → 1.4.8
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/cli/cli-input.js +49 -15
- package/dist/constants/render-constants.js +2 -1
- package/dist/core/combine.js +2 -2
- package/dist/core/dependency.js +26 -3
- package/dist/core/managers/config.js +4 -1
- package/dist/core/output.js +4 -3
- package/dist/core/renderers.js +151 -18
- package/dist/index.js +43 -4
- package/dist/resolvers/php/extract-imports.js +18 -4
- package/dist/resolvers/php/php-resolver.js +25 -17
- package/package.json +1 -1
package/dist/cli/cli-input.js
CHANGED
|
@@ -9,6 +9,27 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const flags_1 = require("../constants/flags");
|
|
12
|
+
function extractShortcutTokens(argv) {
|
|
13
|
+
const cleaned = [];
|
|
14
|
+
const shortcuts = [];
|
|
15
|
+
let shortcutAll = false;
|
|
16
|
+
for (const arg of argv) {
|
|
17
|
+
if (arg === "@") {
|
|
18
|
+
shortcutAll = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg?.startsWith("@")) {
|
|
22
|
+
const name = arg.slice(1).trim();
|
|
23
|
+
if (!name)
|
|
24
|
+
shortcutAll = true;
|
|
25
|
+
else
|
|
26
|
+
shortcuts.push(name);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
cleaned.push(arg);
|
|
30
|
+
}
|
|
31
|
+
return { argv: cleaned, shortcutAll, shortcuts };
|
|
32
|
+
}
|
|
12
33
|
/**
|
|
13
34
|
* Unified CLI parser powered by Sade and FLAG_MAP.
|
|
14
35
|
* Returns { root, flags, warnings, errors }.
|
|
@@ -18,23 +39,34 @@ function parseCliInput(argv = process.argv) {
|
|
|
18
39
|
console.log(`prodex v${package_json_1.default.version}`);
|
|
19
40
|
process.exit(0);
|
|
20
41
|
}
|
|
42
|
+
const extracted = extractShortcutTokens(argv);
|
|
43
|
+
const argvCleaned = extracted.argv;
|
|
21
44
|
const program = (0, sade_1.default)("prodex [root]");
|
|
22
45
|
registerFlags(program);
|
|
23
46
|
let parsed = { rootArg: "", root: undefined, flags: {} };
|
|
24
47
|
program.action((root, opts) => {
|
|
25
|
-
let lroot = root;
|
|
26
48
|
const cwd = process.cwd();
|
|
27
|
-
if (root?.startsWith("@")) {
|
|
28
|
-
opts.shortcut = root.slice(1).trim();
|
|
29
|
-
lroot = undefined;
|
|
30
|
-
}
|
|
31
49
|
parsed = {
|
|
32
|
-
rootArg:
|
|
33
|
-
root:
|
|
50
|
+
rootArg: root,
|
|
51
|
+
root: root ? path_1.default.resolve(cwd, root) : cwd,
|
|
34
52
|
flags: { ...opts },
|
|
35
53
|
};
|
|
36
54
|
});
|
|
37
|
-
program.parse(
|
|
55
|
+
program.parse(argvCleaned);
|
|
56
|
+
// Merge shortcut tokens (@a @b @c / @) with existing --shortcut usage.
|
|
57
|
+
const fromTokens = extracted.shortcuts;
|
|
58
|
+
const shortcutAll = extracted.shortcutAll;
|
|
59
|
+
const fromFlag = typeof parsed.flags.shortcut === "string" ? parsed.flags.shortcut.trim() : "";
|
|
60
|
+
const selected = [...fromTokens, ...(fromFlag ? [fromFlag] : [])].filter(Boolean);
|
|
61
|
+
const uniq = Array.from(new Set(selected));
|
|
62
|
+
if (shortcutAll)
|
|
63
|
+
parsed.flags.shortcutAll = true;
|
|
64
|
+
if (uniq.length)
|
|
65
|
+
parsed.flags.shortcuts = uniq;
|
|
66
|
+
if (!shortcutAll && uniq.length === 1)
|
|
67
|
+
parsed.flags.shortcut = uniq[0];
|
|
68
|
+
else if (uniq.length > 1 || shortcutAll)
|
|
69
|
+
delete parsed.flags.shortcut;
|
|
38
70
|
const warnings = [];
|
|
39
71
|
const errors = [];
|
|
40
72
|
parsed.flags = normalizeFlags(parsed.flags, warnings, errors);
|
|
@@ -43,14 +75,12 @@ function parseCliInput(argv = process.argv) {
|
|
|
43
75
|
}
|
|
44
76
|
function registerFlags(program) {
|
|
45
77
|
for (const [key, meta] of Object.entries(flags_1.FLAG_MAP)) {
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
program.option(`${short}${long}`, desc, defaultVal);
|
|
78
|
+
//@ts-ignore
|
|
79
|
+
const alias = meta.alias ? `-${meta.alias},` : "";
|
|
80
|
+
//@ts-ignore
|
|
81
|
+
program.option(`${alias}--${key}`, meta.desc);
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
|
-
/** Convert flag values to correct types based on FLAG_MAP metadata. */
|
|
54
84
|
function normalizeFlags(flags, warnings, errors) {
|
|
55
85
|
for (const [key, meta] of Object.entries(flags_1.FLAG_MAP)) {
|
|
56
86
|
const raw = flags[key];
|
|
@@ -72,6 +102,10 @@ function normalizeFlags(flags, warnings, errors) {
|
|
|
72
102
|
.filter(Boolean);
|
|
73
103
|
break;
|
|
74
104
|
}
|
|
105
|
+
case "boolean": {
|
|
106
|
+
flags[key] = Boolean(raw);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
75
109
|
default: {
|
|
76
110
|
if (meta.type === "string")
|
|
77
111
|
flags[key] = String(raw);
|
|
@@ -85,7 +119,7 @@ function validateArgs(parsed, warnings, errors) {
|
|
|
85
119
|
const { rootArg } = parsed;
|
|
86
120
|
if (rootArg) {
|
|
87
121
|
if (!fs_1.default.existsSync(parsed.root)) {
|
|
88
|
-
errors.push(`Invalid path
|
|
122
|
+
errors.push(`Invalid path "${rootArg}"`);
|
|
89
123
|
}
|
|
90
124
|
else if (!fs_1.default.statSync(parsed.root).isDirectory()) {
|
|
91
125
|
errors.push(`Path argument "${rootArg}" is not a directory.`);
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.MD_FOOTER = exports.TEXT_HEADERS = exports.LANG_MAP = void 0;
|
|
6
|
+
exports.MD_HEADER = exports.MD_FOOTER = exports.TEXT_HEADERS = exports.LANG_MAP = void 0;
|
|
7
7
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
8
8
|
exports.LANG_MAP = {
|
|
9
9
|
"": "js",
|
|
@@ -22,3 +22,4 @@ exports.TEXT_HEADERS = {
|
|
|
22
22
|
regionEnd: "##endregion",
|
|
23
23
|
};
|
|
24
24
|
exports.MD_FOOTER = ["\n---", "*Generated with [Prodex](https://github.com/emxhive/prodex) — Codebase decoded.*", `<!-- PRODEx v${package_json_1.default.version} | ${new Date().toISOString()} -->`].join("\n");
|
|
25
|
+
exports.MD_HEADER = "*Generated by [Prodex](https://github.com/emxhive/prodex#readme)*";
|
package/dist/core/combine.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runCombine = runCombine;
|
|
4
4
|
const picker_1 = require("../cli/picker");
|
|
5
5
|
const summary_1 = require("../cli/summary");
|
|
6
|
-
const
|
|
6
|
+
const constants_1 = require("../constants");
|
|
7
7
|
const cache_1 = require("./managers/cache");
|
|
8
8
|
const config_1 = require("./managers/config");
|
|
9
9
|
const logger_1 = require("../lib/logger");
|
|
@@ -49,7 +49,7 @@ async function resolveEntries(showUi, cfg) {
|
|
|
49
49
|
* 🧩 Persist discovered aliases (if any)
|
|
50
50
|
*/
|
|
51
51
|
function persistAliases() {
|
|
52
|
-
const aliases = cache_1.CacheManager.dump(
|
|
52
|
+
const aliases = cache_1.CacheManager.dump(constants_1.CACHE_KEYS.ALIASES);
|
|
53
53
|
if (Object.keys(aliases).length) {
|
|
54
54
|
config_1.ConfigManager.persist({ resolve: { aliases } });
|
|
55
55
|
}
|
package/dist/core/dependency.js
CHANGED
|
@@ -10,6 +10,7 @@ const config_1 = require("../constants/config");
|
|
|
10
10
|
const helpers_1 = require("./helpers");
|
|
11
11
|
const logger_1 = require("../lib/logger");
|
|
12
12
|
const collections_1 = require("../shared/collections");
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
14
|
/**
|
|
14
15
|
* 🧩 followChain()
|
|
15
16
|
* Traverses all dependencies starting from the given entry files.
|
|
@@ -67,9 +68,31 @@ async function followChain(entryFiles, cfg) {
|
|
|
67
68
|
* 🧩 applyIncludes()
|
|
68
69
|
* Scans and appends additional files defined in config.resolve.include.
|
|
69
70
|
*/
|
|
71
|
+
// src/core/dependency.ts
|
|
72
|
+
// (existing imports stay)
|
|
70
73
|
async function applyIncludes(cfg, files) {
|
|
71
74
|
const { resolve, root } = cfg;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
const absFiles = [];
|
|
76
|
+
const patterns = [];
|
|
77
|
+
for (const raw of resolve.include) {
|
|
78
|
+
const p = String(raw ?? "").trim();
|
|
79
|
+
if (!p)
|
|
80
|
+
continue;
|
|
81
|
+
const norm = p.norm(); // uses your polyfill (slashes -> "/")
|
|
82
|
+
// absolute *file* paths bypass globScan (and its ignores)
|
|
83
|
+
if (path_1.default.isAbsolute(norm)) {
|
|
84
|
+
try {
|
|
85
|
+
if (fs_1.default.statSync(norm).isFile()) {
|
|
86
|
+
absFiles.push(path_1.default.resolve(norm));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// doesn't exist / can't stat → treat as pattern
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
patterns.push(norm);
|
|
95
|
+
}
|
|
96
|
+
const scan = await (0, helpers_1.globScan)(patterns, { cwd: root });
|
|
97
|
+
return (0, collections_1.unique)([...files, ...absFiles, ...scan.files]);
|
|
75
98
|
}
|
|
@@ -58,7 +58,10 @@ class ConfigManager {
|
|
|
58
58
|
def.apply(cfg, val);
|
|
59
59
|
}
|
|
60
60
|
const hasFiles = Array.isArray(flags.files) ? flags.files.length > 0 : !!flags.files;
|
|
61
|
-
|
|
61
|
+
const hasInclude = Array.isArray(flags.include) ? flags.include.length > 0 : !!flags.include;
|
|
62
|
+
if (hasInclude && !hasFiles)
|
|
63
|
+
cfg.entry.files = [];
|
|
64
|
+
if (hasFiles && !hasInclude)
|
|
62
65
|
cfg.resolve.include = [];
|
|
63
66
|
if (flags.shortcut && cfg.shortcuts && cfg.shortcuts[flags.shortcut])
|
|
64
67
|
return this.applyShortcuts(cfg, flags);
|
package/dist/core/output.js
CHANGED
|
@@ -11,7 +11,6 @@ const renderers_1 = require("./renderers");
|
|
|
11
11
|
const logger_1 = require("../lib/logger");
|
|
12
12
|
const utils_1 = require("../lib/utils");
|
|
13
13
|
const questions_1 = require("../lib/questions");
|
|
14
|
-
const render_constants_1 = require("../constants/render-constants");
|
|
15
14
|
const config_1 = require("../constants/config");
|
|
16
15
|
/**
|
|
17
16
|
* 🧩 produceOutput()
|
|
@@ -20,7 +19,6 @@ const config_1 = require("../constants/config");
|
|
|
20
19
|
*/
|
|
21
20
|
async function produceOutput({ name, files, cfg, showUi }) {
|
|
22
21
|
const { output: { format, versioned, dir }, } = cfg;
|
|
23
|
-
;
|
|
24
22
|
// 1️⃣ Determine base filename
|
|
25
23
|
let outputBase = name;
|
|
26
24
|
if (showUi) {
|
|
@@ -42,7 +40,10 @@ async function produceOutput({ name, files, cfg, showUi }) {
|
|
|
42
40
|
// 4️⃣ Prepare and write content
|
|
43
41
|
const outputPath = path_1.default.join(dir, `${outputBase}.${format}`);
|
|
44
42
|
const sorted = [...files].sort((a, b) => a.localeCompare(b));
|
|
45
|
-
|
|
43
|
+
//[tocMd(sorted), ...sorted.map((f, i) => renderMd(f, i)), MD_FOOTER].join("\n")
|
|
44
|
+
const content = format === "txt"
|
|
45
|
+
? [(0, renderers_1.tocTxt)(sorted), ...sorted.map(renderers_1.renderTxt)].join("")
|
|
46
|
+
: (0, renderers_1.renderTraceMd)(sorted).content;
|
|
46
47
|
fs_1.default.writeFileSync(outputPath, content, "utf8");
|
|
47
48
|
return outputPath;
|
|
48
49
|
}
|
package/dist/core/renderers.js
CHANGED
|
@@ -3,41 +3,174 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderTraceMd = renderTraceMd;
|
|
6
7
|
exports.tocMd = tocMd;
|
|
7
8
|
exports.renderMd = renderMd;
|
|
8
9
|
exports.tocTxt = tocTxt;
|
|
9
10
|
exports.renderTxt = renderTxt;
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const shared_1 = require("../shared");
|
|
13
|
+
const shared_2 = require("../shared");
|
|
14
|
+
const constants_1 = require("../constants");
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* renderTraceMd()
|
|
17
|
+
* Builds the full markdown document AND computes:
|
|
18
|
+
* - listing start/end lines
|
|
19
|
+
* - each file section start/end lines (in the final output)
|
|
16
20
|
*/
|
|
17
|
-
|
|
21
|
+
// src/core/renderers.ts
|
|
22
|
+
function renderTraceMd(files) {
|
|
18
23
|
const count = files.length;
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
// Render sections once (content of each file)
|
|
25
|
+
const sections = files.map((f, i) => renderMd(f, i));
|
|
26
|
+
// 1) Build a FIRST PASS doc with placeholder TOC (no line ranges)
|
|
27
|
+
const pass1Toc = buildToc({
|
|
28
|
+
files,
|
|
29
|
+
count,
|
|
30
|
+
listingStart: 0,
|
|
31
|
+
listingEnd: 0,
|
|
32
|
+
trace: null,
|
|
33
|
+
withRanges: false,
|
|
34
|
+
});
|
|
35
|
+
const pass1Content = [pass1Toc, ...sections, constants_1.MD_FOOTER].join("\n");
|
|
36
|
+
// 2) Analyze the FINAL STRING (pass 1) to get real line indexes
|
|
37
|
+
const pass1Analysis = analyzeTrace(pass1Content, count);
|
|
38
|
+
// 3) Build pass 2 TOC including listing range + per-file ranges
|
|
39
|
+
const pass2Toc = buildToc({
|
|
40
|
+
files,
|
|
41
|
+
count,
|
|
42
|
+
listingStart: pass1Analysis.listingStart,
|
|
43
|
+
listingEnd: pass1Analysis.listingEnd,
|
|
44
|
+
trace: pass1Analysis.trace,
|
|
45
|
+
withRanges: true,
|
|
46
|
+
});
|
|
47
|
+
const pass2Content = [pass2Toc, ...sections, constants_1.MD_FOOTER].join("\n");
|
|
48
|
+
// 4) Re-analyze pass 2 content (should be identical; extra safety)
|
|
49
|
+
const pass2Analysis = analyzeTrace(pass2Content, count);
|
|
50
|
+
// If anything drifted (it really shouldn’t), trust pass 2’s analysis.
|
|
51
|
+
// Rebuilding again would still not change line counts, so this is stable.
|
|
52
|
+
return {
|
|
53
|
+
content: pass2Content,
|
|
54
|
+
trace: pass2Analysis.trace,
|
|
55
|
+
listingStart: pass2Analysis.listingStart,
|
|
56
|
+
listingEnd: pass2Analysis.listingEnd,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const rangeText = (start, end) => ` L${start}-L${end}`;
|
|
60
|
+
function buildToc(opts) {
|
|
61
|
+
const { files, count, listingStart, listingEnd, trace, withRanges } = opts;
|
|
62
|
+
const tocHead = [
|
|
63
|
+
`# Index ${withRanges ? rangeText(listingStart, listingEnd) : ""} `,
|
|
64
|
+
"",
|
|
65
|
+
constants_1.MD_HEADER,
|
|
66
|
+
"",
|
|
67
|
+
`Included Source Files: ${count}`,
|
|
68
|
+
];
|
|
69
|
+
const items = files.map((f, i) => {
|
|
70
|
+
const rp = (0, shared_2.rel)(f);
|
|
71
|
+
if (!withRanges || !trace)
|
|
72
|
+
return `- [${rp}](#${i + 1})`;
|
|
73
|
+
const t = trace[i];
|
|
74
|
+
return `- [${rp}](#${i + 1}) ${rangeText(t.startLine, t.endLine)}`;
|
|
75
|
+
});
|
|
76
|
+
const tocTail = ["", "---"];
|
|
77
|
+
return [...tocHead, ...items, ...tocTail].join("\n");
|
|
21
78
|
}
|
|
22
79
|
/**
|
|
23
|
-
*
|
|
80
|
+
* Analyze the already-generated markdown to compute:
|
|
81
|
+
* - listing start/end lines
|
|
82
|
+
* - each section start/end line (per file index)
|
|
83
|
+
*
|
|
84
|
+
* All line numbers are 1-based.
|
|
24
85
|
*/
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
86
|
+
function analyzeTrace(content, count) {
|
|
87
|
+
const lines = content.split("\n");
|
|
88
|
+
// --- Listing range ---
|
|
89
|
+
const includedIdx = lines.findIndex((l) => /^Included Source Files\b/.test(l.trim()));
|
|
90
|
+
// listing begins on the next line after the header
|
|
91
|
+
const listingStart = includedIdx >= 0 ? includedIdx + 2 : 0; // 1-based
|
|
92
|
+
const listingEnd = count ? listingStart + count - 1 : listingStart;
|
|
93
|
+
// --- Footer start (exclude footer from last file range) ---
|
|
94
|
+
let footerMarkerIdx = lines.findIndex((l) => l.includes("<!-- PRODEx v"));
|
|
95
|
+
if (footerMarkerIdx < 0)
|
|
96
|
+
footerMarkerIdx = lines.findIndex((l) => l.includes("*Generated with [Prodex]"));
|
|
97
|
+
let footerStartIdx = footerMarkerIdx >= 0 ? footerMarkerIdx : lines.length; // 0-based
|
|
98
|
+
if (footerStartIdx > 0 && lines[footerStartIdx - 1].trim() === "---")
|
|
99
|
+
footerStartIdx = footerStartIdx - 1;
|
|
100
|
+
// --- Section markers: find "#### N" lines ---
|
|
101
|
+
const markerLineIdxByN = new Map();
|
|
102
|
+
for (let i = 0; i < lines.length; i++) {
|
|
103
|
+
const m = lines[i].trim().match(/^####\s+(\d+)\s*$/);
|
|
104
|
+
if (!m)
|
|
105
|
+
continue;
|
|
106
|
+
const n = Number(m[1]);
|
|
107
|
+
if (!Number.isFinite(n))
|
|
108
|
+
continue;
|
|
109
|
+
if (n < 1 || n > count)
|
|
110
|
+
continue;
|
|
111
|
+
if (!markerLineIdxByN.has(n))
|
|
112
|
+
markerLineIdxByN.set(n, i);
|
|
113
|
+
}
|
|
114
|
+
// Compute each section's start idx (prefer the preceding "---" line if present)
|
|
115
|
+
const startIdxs = [];
|
|
116
|
+
for (let n = 1; n <= count; n++) {
|
|
117
|
+
const markerIdx = markerLineIdxByN.get(n);
|
|
118
|
+
if (markerIdx == null) {
|
|
119
|
+
// Fallback: if marker missing, make it non-crashy.
|
|
120
|
+
// Put start at footerStart (it'll produce tiny/empty ranges rather than exploding).
|
|
121
|
+
startIdxs.push(footerStartIdx);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const maybeStart = markerIdx > 0 && lines[markerIdx - 1].trim() === "---" ? markerIdx - 1 : markerIdx;
|
|
125
|
+
startIdxs.push(maybeStart);
|
|
126
|
+
}
|
|
127
|
+
// Compute end idx using next section start, or footer start
|
|
128
|
+
const trace = [];
|
|
129
|
+
for (let i = 0; i < count; i++) {
|
|
130
|
+
const startIdx = startIdxs[i];
|
|
131
|
+
const nextStartIdx = i < count - 1 ? startIdxs[i + 1] : footerStartIdx;
|
|
132
|
+
const endIdx = Math.max(startIdx, nextStartIdx - 1);
|
|
133
|
+
trace.push({
|
|
134
|
+
file: "", // filled by caller if needed; TOC uses rel(files[i]) anyway
|
|
135
|
+
anchor: i + 1,
|
|
136
|
+
startLine: startIdx + 1,
|
|
137
|
+
endLine: endIdx + 1,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return { listingStart, listingEnd, trace };
|
|
31
141
|
}
|
|
32
142
|
/**
|
|
33
|
-
*
|
|
143
|
+
* Existing functions kept as-is.
|
|
144
|
+
* (renderMd is used by renderTraceMd above)
|
|
34
145
|
*/
|
|
146
|
+
function tocMd(files) {
|
|
147
|
+
const count = files.length;
|
|
148
|
+
const items = files.map((f, i) => `- [${(0, shared_2.rel)(f)}](#${i + 1})`).join("\n");
|
|
149
|
+
return ["# Index ", `\nIncluded Source Files (${count})`, items, "", "---"].join("\n");
|
|
150
|
+
}
|
|
151
|
+
function renderMd(p, i) {
|
|
152
|
+
const rp = (0, shared_2.rel)(p);
|
|
153
|
+
const ext = path_1.default.extname(p).toLowerCase();
|
|
154
|
+
const lang = constants_1.LANG_MAP[ext] || "txt";
|
|
155
|
+
const code = (0, shared_1.readFileSafe)(p).trimEnd();
|
|
156
|
+
return [
|
|
157
|
+
`---\n#### ${i + 1}`,
|
|
158
|
+
"\n",
|
|
159
|
+
"` File: " + rp + "` [↑ Back to top](#index)",
|
|
160
|
+
"",
|
|
161
|
+
"```" + lang,
|
|
162
|
+
code,
|
|
163
|
+
"```",
|
|
164
|
+
"",
|
|
165
|
+
].join("\n");
|
|
166
|
+
}
|
|
167
|
+
// TXT versions unchanged
|
|
35
168
|
function tocTxt(files) {
|
|
36
169
|
const sorted = [...files].sort((a, b) => a.localeCompare(b));
|
|
37
|
-
return ["##==== Combined Scope ====", ...sorted.map((f) => "## - " + (0,
|
|
170
|
+
return ["##==== Combined Scope ====", ...sorted.map((f) => "## - " + (0, shared_2.rel)(f))].join("\n") + "\n\n";
|
|
38
171
|
}
|
|
39
172
|
function renderTxt(p) {
|
|
40
|
-
const relPath = (0,
|
|
41
|
-
const code = (0,
|
|
173
|
+
const relPath = (0, shared_2.rel)(p);
|
|
174
|
+
const code = (0, shared_1.readFileSafe)(p);
|
|
42
175
|
return ["##==== path: " + relPath + " ====", "##region " + relPath, code, "##endregion", ""].join("\n");
|
|
43
176
|
}
|
package/dist/index.js
CHANGED
|
@@ -44,12 +44,51 @@ async function startProdex(args = process.argv) {
|
|
|
44
44
|
return (0, init_1.initProdex)();
|
|
45
45
|
const { root, flags } = (0, cli_input_1.parseCliInput)(args);
|
|
46
46
|
const userConfig = config_1.ConfigManager.load(root);
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Determine which shortcut runs to execute (order irrelevant).
|
|
48
|
+
const selected = [];
|
|
49
|
+
if (flags.shortcutAll) {
|
|
50
|
+
selected.push(...Object.keys(userConfig.shortcuts ?? {}));
|
|
51
|
+
}
|
|
52
|
+
else if (Array.isArray(flags.shortcuts) && flags.shortcuts.length) {
|
|
53
|
+
selected.push(...flags.shortcuts);
|
|
54
|
+
}
|
|
55
|
+
else if (flags.shortcut) {
|
|
56
|
+
selected.push(flags.shortcut);
|
|
57
|
+
}
|
|
58
|
+
const shortcutRuns = Array.from(new Set(selected.map((s) => String(s).trim()).filter(Boolean))).sort();
|
|
59
|
+
await Promise.resolve().then(() => __importStar(require("./lib/polyfills")));
|
|
60
|
+
// Multi-run mode (shortcuts)
|
|
61
|
+
if (shortcutRuns.length) {
|
|
62
|
+
for (const shortcut of shortcutRuns) {
|
|
63
|
+
const runFlags = {
|
|
64
|
+
...flags,
|
|
65
|
+
shortcut,
|
|
66
|
+
shortcuts: undefined,
|
|
67
|
+
shortcutAll: false,
|
|
68
|
+
};
|
|
69
|
+
const config = config_1.ConfigManager.merge(userConfig, runFlags, root);
|
|
70
|
+
(0, store_1.setGlobals)(config, runFlags);
|
|
71
|
+
const opts = {
|
|
72
|
+
showUi: false,
|
|
73
|
+
// Avoid output collisions when running multiple shortcuts.
|
|
74
|
+
cliName: config.name ?? shortcut,
|
|
75
|
+
};
|
|
76
|
+
await (0, combine_1.runCombine)({ cfg: config, opts });
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// If "@" was used but no shortcuts exist, fall back to a normal single run.
|
|
81
|
+
const baseFlags = flags.shortcutAll ? { ...flags, shortcutAll: false } : flags;
|
|
82
|
+
const config = config_1.ConfigManager.merge(userConfig, baseFlags, root);
|
|
83
|
+
(0, store_1.setGlobals)(config, baseFlags);
|
|
49
84
|
const opts = {
|
|
50
|
-
showUi: !
|
|
85
|
+
showUi: !baseFlags.ci &&
|
|
86
|
+
!baseFlags?.files?.length &&
|
|
87
|
+
config?.entry?.ui?.enablePicker &&
|
|
88
|
+
!baseFlags.shortcut &&
|
|
89
|
+
!baseFlags.shortcuts?.length &&
|
|
90
|
+
!baseFlags.shortcutAll,
|
|
51
91
|
cliName: config.name,
|
|
52
92
|
};
|
|
53
|
-
await Promise.resolve().then(() => __importStar(require("./lib/polyfills")));
|
|
54
93
|
await (0, combine_1.runCombine)({ cfg: config, opts });
|
|
55
94
|
}
|
|
@@ -10,10 +10,7 @@ exports.expandGroupedUses = expandGroupedUses;
|
|
|
10
10
|
*/
|
|
11
11
|
function extractPhpImports(code) {
|
|
12
12
|
const out = new Set();
|
|
13
|
-
const patterns = [
|
|
14
|
-
/\b(?:require|include|require_once|include_once)\s*\(?['"]([^'"]+)['"]\)?/g,
|
|
15
|
-
/\buse\s+([A-Z][\w\\]+(?:\s*{[^}]+})?)/g,
|
|
16
|
-
];
|
|
13
|
+
const patterns = [/\b(?:require|include|require_once|include_once)\s*\(?['"]([^'"]+)['"]\)?/g, /\buse\s+([A-Z][\w\\]+(?:\s*{[^}]+})?)/g];
|
|
17
14
|
for (const r of patterns) {
|
|
18
15
|
let m;
|
|
19
16
|
while ((m = r.exec(code))) {
|
|
@@ -22,6 +19,23 @@ function extractPhpImports(code) {
|
|
|
22
19
|
out.add(val);
|
|
23
20
|
}
|
|
24
21
|
}
|
|
22
|
+
// Detect short class names
|
|
23
|
+
const shortClassPatterns = [/\bnew\s+([A-Z][A-Za-z0-9_]*)\b/g, /\b([A-Z][A-Za-z0-9_]*)::class\b/g, /\b([A-Z][A-Za-z0-9_]*)::[A-Za-z_]/g, /:\s*([A-Z][A-Za-z0-9_]*)\b/g];
|
|
24
|
+
// Direct matches
|
|
25
|
+
for (const r of shortClassPatterns) {
|
|
26
|
+
let m;
|
|
27
|
+
while ((m = r.exec(code)))
|
|
28
|
+
out.add(m[1]);
|
|
29
|
+
}
|
|
30
|
+
// Type-hinted parameters in function signatures
|
|
31
|
+
let m;
|
|
32
|
+
const paramPattern = /\(([^)]*)\)/g;
|
|
33
|
+
while ((m = paramPattern.exec(code))) {
|
|
34
|
+
const block = m[1];
|
|
35
|
+
const types = block.match(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
36
|
+
if (types)
|
|
37
|
+
types.forEach((t) => out.add(t));
|
|
38
|
+
}
|
|
25
39
|
return out;
|
|
26
40
|
}
|
|
27
41
|
/**
|
|
@@ -16,22 +16,6 @@ const constants_1 = require("../../constants");
|
|
|
16
16
|
const cache_1 = require("../../core/managers/cache");
|
|
17
17
|
const collections_1 = require("../../shared/collections");
|
|
18
18
|
const promises_1 = __importDefault(require("fs/promises")); // (add near the top if not present)
|
|
19
|
-
/** Ensure we have a PHP resolver context for the current root */
|
|
20
|
-
function buildPhpCtx(root, prev) {
|
|
21
|
-
if (prev?.kind === "php")
|
|
22
|
-
return prev;
|
|
23
|
-
const psr4 = (0, psr4_1.resolvePsr4)(root);
|
|
24
|
-
const nsKeys = Object.keys(psr4).sort((a, b) => b.length - a.length);
|
|
25
|
-
const bindings = (0, bindings_1.loadLaravelBindings)(root);
|
|
26
|
-
return { kind: "php", psr4, nsKeys, bindings };
|
|
27
|
-
}
|
|
28
|
-
/** Namespace prefix check */
|
|
29
|
-
function startsWithAnyNamespace(imp, nsKeys) {
|
|
30
|
-
for (const k of nsKeys)
|
|
31
|
-
if (imp.startsWith(k))
|
|
32
|
-
return true;
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
19
|
/**
|
|
36
20
|
* Typed PHP resolver (aligned with JS resolver signature).
|
|
37
21
|
* - Uses global config via getConfig()
|
|
@@ -52,6 +36,8 @@ async function resolvePhpImports({ filePath, visited = new Set(), depth = 0, max
|
|
|
52
36
|
const code = (0, shared_1.readFileSafe)(filePath);
|
|
53
37
|
if (!code)
|
|
54
38
|
return (0, collections_1.emptyResult)(visited);
|
|
39
|
+
const nsMatch = code.match(/\bnamespace\s+([^;]+);/);
|
|
40
|
+
const currentNamespace = nsMatch ? nsMatch[1].trim() : null;
|
|
55
41
|
// Context + exclusions
|
|
56
42
|
const phpCtx = buildPhpCtx(ROOT, ctx);
|
|
57
43
|
// Parse imports (expand grouped `use` syntax)
|
|
@@ -62,6 +48,13 @@ async function resolvePhpImports({ filePath, visited = new Set(), depth = 0, max
|
|
|
62
48
|
const filesOut = [];
|
|
63
49
|
for (const imp0 of imports) {
|
|
64
50
|
let imp = imp0;
|
|
51
|
+
if (!imp || typeof imp !== "string")
|
|
52
|
+
continue;
|
|
53
|
+
// Fully-qualified check
|
|
54
|
+
const isFullyQualified = imp.includes("\\") || imp.startsWith("\\");
|
|
55
|
+
if (!isFullyQualified && currentNamespace) {
|
|
56
|
+
imp = `${currentNamespace}\\${imp}`;
|
|
57
|
+
}
|
|
65
58
|
// Respect Laravel container bindings (Interface → Implementation)
|
|
66
59
|
if (phpCtx.bindings[imp]) {
|
|
67
60
|
// logger.debug("[php-resolver] binding:", imp, "→", _2j(phpCtx.bindings[imp]));
|
|
@@ -70,7 +63,6 @@ async function resolvePhpImports({ filePath, visited = new Set(), depth = 0, max
|
|
|
70
63
|
// Only resolve PSR-4 mapped namespaces
|
|
71
64
|
if (!startsWithAnyNamespace(imp, phpCtx.nsKeys))
|
|
72
65
|
continue;
|
|
73
|
-
// if (isExcluded(imp, excludePatterns, ROOT)) continue;
|
|
74
66
|
// Resolve namespace → file path (sync helper retained)
|
|
75
67
|
const resolvedPath = await tryResolvePhpFile(imp, filePath, phpCtx.psr4);
|
|
76
68
|
// Exclusion check after final resolution
|
|
@@ -126,3 +118,19 @@ async function tryResolvePhpFile(imp, fromFile, psr4) {
|
|
|
126
118
|
cache_1.CacheManager.set(constants_1.CACHE_KEYS.PHP_FILECACHE, key, resolved);
|
|
127
119
|
return resolved;
|
|
128
120
|
}
|
|
121
|
+
/** Ensure we have a PHP resolver context for the current root */
|
|
122
|
+
function buildPhpCtx(root, prev) {
|
|
123
|
+
if (prev?.kind === "php")
|
|
124
|
+
return prev;
|
|
125
|
+
const psr4 = (0, psr4_1.resolvePsr4)(root);
|
|
126
|
+
const nsKeys = Object.keys(psr4).sort((a, b) => b.length - a.length);
|
|
127
|
+
const bindings = (0, bindings_1.loadLaravelBindings)(root);
|
|
128
|
+
return { kind: "php", psr4, nsKeys, bindings };
|
|
129
|
+
}
|
|
130
|
+
/** Namespace prefix check */
|
|
131
|
+
function startsWithAnyNamespace(imp, nsKeys) {
|
|
132
|
+
for (const k of nsKeys)
|
|
133
|
+
if (imp.startsWith(k))
|
|
134
|
+
return true;
|
|
135
|
+
return false;
|
|
136
|
+
}
|