prodex 2.0.0 → 2.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/README.md +369 -146
- package/dist/app/dependency-source-provider.d.ts +2 -0
- package/dist/app/dependency-source-provider.js +66 -0
- package/dist/app/entry-resolver.d.ts +7 -0
- package/dist/app/entry-resolver.js +26 -0
- package/dist/app/execute-run.d.ts +2 -2
- package/dist/app/execute-run.js +138 -36
- package/dist/app/git-source-provider.d.ts +4 -0
- package/dist/app/git-source-provider.js +551 -0
- package/dist/app/grep-source-provider.d.ts +2 -0
- package/dist/app/grep-source-provider.js +226 -0
- package/dist/app/planner.d.ts +12 -0
- package/dist/app/planner.js +106 -0
- package/dist/app/planners/attachment-options.d.ts +2 -0
- package/dist/app/planners/attachment-options.js +39 -0
- package/dist/app/planners/git-plan.d.ts +13 -0
- package/dist/app/planners/git-plan.js +141 -0
- package/dist/app/planners/grep-plan.d.ts +13 -0
- package/dist/app/planners/grep-plan.js +115 -0
- package/dist/app/planners/list-utils.d.ts +1 -0
- package/dist/app/planners/list-utils.js +6 -0
- package/dist/app/planners/pack-plan.d.ts +13 -0
- package/dist/app/planners/pack-plan.js +65 -0
- package/dist/app/planners/scope-plan.d.ts +17 -0
- package/dist/app/planners/scope-plan.js +143 -0
- package/dist/app/planners/trace-plan.d.ts +13 -0
- package/dist/app/planners/trace-plan.js +77 -0
- package/dist/app/source-collector.d.ts +5 -0
- package/dist/app/source-collector.js +33 -0
- package/dist/app/target-resolver.d.ts +7 -0
- package/dist/app/target-resolver.js +110 -0
- package/dist/cli/cli-input.js +70 -19
- package/dist/cli/flag-specs.d.ts +7 -3
- package/dist/cli/flag-specs.js +58 -7
- package/dist/cli/help-specs.d.ts +205 -0
- package/dist/cli/help-specs.js +136 -0
- package/dist/cli/help.js +107 -28
- package/dist/cli/report-command.js +59 -26
- package/dist/commands/{run-command.d.ts → git-command.d.ts} +3 -3
- package/dist/commands/git-command.js +7 -0
- package/dist/commands/grep-command.d.ts +11 -0
- package/dist/commands/grep-command.js +7 -0
- package/dist/commands/pack-command.d.ts +11 -0
- package/dist/commands/pack-command.js +7 -0
- package/dist/commands/scope-command.d.ts +12 -0
- package/dist/commands/scope-command.js +7 -0
- package/dist/commands/shared-runner.d.ts +12 -0
- package/dist/commands/shared-runner.js +30 -0
- package/dist/commands/trace-command.d.ts +11 -0
- package/dist/commands/trace-command.js +7 -0
- package/dist/config/default-config.js +7 -11
- package/dist/config/load.d.ts +1 -0
- package/dist/config/load.js +269 -4
- package/dist/config/migration/detect.d.ts +2 -1
- package/dist/config/migration/detect.js +10 -2
- package/dist/config/migration/messages.js +12 -11
- package/dist/config/migration/transform.js +77 -32
- package/dist/filesystem/binary.d.ts +2 -0
- package/dist/filesystem/binary.js +30 -0
- package/dist/filesystem/entry-discovery.d.ts +14 -0
- package/dist/filesystem/entry-discovery.js +78 -0
- package/dist/filesystem/file-set.d.ts +7 -0
- package/dist/filesystem/file-set.js +57 -0
- package/dist/filesystem/glob-scan.d.ts +6 -2
- package/dist/filesystem/glob-scan.js +12 -7
- package/dist/filesystem/path-patterns.d.ts +20 -0
- package/dist/filesystem/path-patterns.js +70 -0
- package/dist/index.js +51 -29
- package/dist/output/markdown-sections.d.ts +5 -0
- package/dist/output/markdown-sections.js +103 -0
- package/dist/output/markdown-toc.d.ts +31 -0
- package/dist/output/markdown-toc.js +287 -0
- package/dist/output/markdown.d.ts +5 -8
- package/dist/output/markdown.js +80 -127
- package/dist/output/produce-output.d.ts +4 -1
- package/dist/output/produce-output.js +6 -7
- package/dist/output/render-helpers.d.ts +4 -0
- package/dist/output/render-helpers.js +14 -0
- package/dist/output/text.d.ts +2 -2
- package/dist/output/text.js +47 -8
- package/dist/resolvers/js/resolve-alias.js +10 -41
- package/dist/resolvers/php/bindings.js +6 -6
- package/dist/resolvers/php/extract-imports.d.ts +19 -7
- package/dist/resolvers/php/extract-imports.js +146 -26
- package/dist/resolvers/php/php-resolver.js +55 -24
- package/dist/resolvers/php/psr4.d.ts +1 -1
- package/dist/resolvers/php/psr4.js +7 -1
- package/dist/runtime/shell-command-runner.d.ts +5 -0
- package/dist/runtime/shell-command-runner.js +118 -0
- package/dist/tracing/collect-trace.d.ts +10 -0
- package/dist/tracing/collect-trace.js +23 -0
- package/dist/tracing/exclude.d.ts +0 -5
- package/dist/tracing/exclude.js +16 -12
- package/dist/tracing/follow-chain.js +2 -2
- package/dist/types/app.types.d.ts +38 -8
- package/dist/types/artifact.types.d.ts +50 -0
- package/dist/types/artifact.types.js +2 -0
- package/dist/types/cli.types.d.ts +52 -4
- package/dist/types/config.types.d.ts +37 -20
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/output.types.d.ts +12 -3
- package/dist/types/planner.types.d.ts +76 -0
- package/dist/types/planner.types.js +2 -0
- package/dist/types/resolver.types.d.ts +1 -1
- package/dist/types/tracing.types.d.ts +0 -11
- package/package.json +2 -2
- package/schema/prodex.schema.json +59 -41
- package/dist/app/run-plans.d.ts +0 -11
- package/dist/app/run-plans.js +0 -61
- package/dist/commands/profiles-command.d.ts +0 -6
- package/dist/commands/profiles-command.js +0 -16
- package/dist/commands/run-command.js +0 -17
- package/dist/config/build-config.d.ts +0 -13
- package/dist/config/build-config.js +0 -127
- package/dist/tracing/include-files.d.ts +0 -2
- package/dist/tracing/include-files.js +0 -36
- package/dist/tracing/trace-run.d.ts +0 -2
- package/dist/tracing/trace-run.js +0 -21
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.collectGrepSources = collectGrepSources;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const file_set_1 = require("../filesystem/file-set");
|
|
11
|
+
const path_2 = require("../filesystem/path");
|
|
12
|
+
async function collectGrepSources(plan) {
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const errors = [];
|
|
15
|
+
const opts = plan.grepOptions;
|
|
16
|
+
if (!opts) {
|
|
17
|
+
errors.push("Missing grep options in execution plan.");
|
|
18
|
+
return { files: [], entries: [], includes: plan.include ?? [], mode: "grep", warnings, errors };
|
|
19
|
+
}
|
|
20
|
+
// 1. Build ripgrep arguments
|
|
21
|
+
const args = ["--json"];
|
|
22
|
+
if (opts.mode === "regex") {
|
|
23
|
+
args.push("-e", opts.terms[0]);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// query, any, all mode use fixed-strings
|
|
27
|
+
args.push("-F");
|
|
28
|
+
if (opts.mode === "all") {
|
|
29
|
+
// Search for the first term with ripgrep, we will filter the rest in JS
|
|
30
|
+
args.push("-e", opts.terms[0]);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// query (one term) or any (multiple terms in OR search)
|
|
34
|
+
for (const term of opts.terms) {
|
|
35
|
+
args.push("-e", term);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Add within path boundaries
|
|
40
|
+
for (const w of opts.within) {
|
|
41
|
+
const absPath = path_1.default.isAbsolute(w) ? w : path_1.default.resolve(plan.root, w);
|
|
42
|
+
const relPath = path_1.default.relative(plan.root, absPath).replace(/\\/g, "/");
|
|
43
|
+
args.push("-g", relPath);
|
|
44
|
+
}
|
|
45
|
+
// Add skip path boundaries
|
|
46
|
+
for (const s of opts.skip) {
|
|
47
|
+
const absPath = path_1.default.isAbsolute(s) ? s : path_1.default.resolve(plan.root, s);
|
|
48
|
+
const relPath = path_1.default.relative(plan.root, absPath).replace(/\\/g, "/");
|
|
49
|
+
args.push("-g", `!${relPath}`);
|
|
50
|
+
}
|
|
51
|
+
// Target the current directory explicitly to avoid ripgrep default behavior of reading stdin
|
|
52
|
+
args.push(".");
|
|
53
|
+
// 2. Execute ripgrep
|
|
54
|
+
const res = (0, child_process_1.spawnSync)("rg", args, {
|
|
55
|
+
cwd: plan.root,
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
58
|
+
});
|
|
59
|
+
if (res.error) {
|
|
60
|
+
if (res.error.code === "ENOENT") {
|
|
61
|
+
errors.push('Command "grep" requires ripgrep (`rg`) to be installed.');
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
errors.push(`Ripgrep execution error: ${res.error.message}`);
|
|
65
|
+
}
|
|
66
|
+
return { files: [], entries: [], includes: plan.include ?? [], mode: "grep", warnings, errors };
|
|
67
|
+
}
|
|
68
|
+
const matchedFilesSet = new Set();
|
|
69
|
+
const matchCounts = new Map();
|
|
70
|
+
if (res.status === 0 && res.stdout) {
|
|
71
|
+
const lines = res.stdout.split("\n");
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
continue;
|
|
76
|
+
try {
|
|
77
|
+
const msg = JSON.parse(trimmed);
|
|
78
|
+
if (msg.type === "match") {
|
|
79
|
+
const relPath = msg.data?.path?.text;
|
|
80
|
+
if (relPath) {
|
|
81
|
+
const absPath = (0, path_2.normalizePath)(path_1.default.resolve(plan.root, relPath));
|
|
82
|
+
matchedFilesSet.add(absPath);
|
|
83
|
+
matchCounts.set(absPath, (matchCounts.get(absPath) || 0) + 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Ignore non-json or malformed output lines
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (res.status !== 0 && res.status !== 1) {
|
|
93
|
+
errors.push(`Ripgrep error (exit code ${res.status}): ${res.stderr || "unknown error"}`);
|
|
94
|
+
return { files: [], entries: [], includes: plan.include ?? [], mode: "grep", warnings, errors };
|
|
95
|
+
}
|
|
96
|
+
let matchedFiles = Array.from(matchedFilesSet);
|
|
97
|
+
// 3. Filter for AND search (mode === "all")
|
|
98
|
+
if (opts.mode === "all" && opts.terms.length > 1) {
|
|
99
|
+
const remainingTerms = opts.terms.slice(1);
|
|
100
|
+
const filteredMatchedFiles = [];
|
|
101
|
+
for (const file of matchedFiles) {
|
|
102
|
+
try {
|
|
103
|
+
if (fs_1.default.existsSync(file)) {
|
|
104
|
+
const content = fs_1.default.readFileSync(file, "utf8");
|
|
105
|
+
const matchesAll = remainingTerms.every((term) => content.includes(term));
|
|
106
|
+
if (matchesAll) {
|
|
107
|
+
filteredMatchedFiles.push(file);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
matchCounts.delete(file);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
warnings.push(`Failed to read file content for filtering: ${file}. Error: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
matchedFiles = filteredMatchedFiles;
|
|
119
|
+
}
|
|
120
|
+
// 4. Apply --not content filter
|
|
121
|
+
if (opts.negativeTerms.length > 0) {
|
|
122
|
+
const filteredMatchedFiles = [];
|
|
123
|
+
for (const file of matchedFiles) {
|
|
124
|
+
try {
|
|
125
|
+
if (fs_1.default.existsSync(file)) {
|
|
126
|
+
const content = fs_1.default.readFileSync(file, "utf8");
|
|
127
|
+
const hasNegative = opts.negativeTerms.some((neg) => content.includes(neg));
|
|
128
|
+
if (!hasNegative) {
|
|
129
|
+
filteredMatchedFiles.push(file);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
matchCounts.delete(file);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
warnings.push(`Failed to read file content for negative filtering: ${file}. Error: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
matchedFiles = filteredMatchedFiles;
|
|
141
|
+
}
|
|
142
|
+
// 5. Sort matched files deterministically by normalized root-relative path
|
|
143
|
+
matchedFiles.sort((a, b) => {
|
|
144
|
+
const relA = (0, path_2.normalizePath)(path_1.default.relative(plan.root, a));
|
|
145
|
+
const relB = (0, path_2.normalizePath)(path_1.default.relative(plan.root, b));
|
|
146
|
+
return relA.localeCompare(relB);
|
|
147
|
+
});
|
|
148
|
+
const matchedCountBeforeLimit = matchedFiles.length;
|
|
149
|
+
// 6. Apply --max-files limit to matched files
|
|
150
|
+
const resolvedMaxFiles = plan.maxFiles;
|
|
151
|
+
let maxFilesApplied = false;
|
|
152
|
+
if (resolvedMaxFiles !== undefined && resolvedMaxFiles !== null && matchedFiles.length > resolvedMaxFiles) {
|
|
153
|
+
matchedFiles = matchedFiles.slice(0, resolvedMaxFiles);
|
|
154
|
+
maxFilesApplied = true;
|
|
155
|
+
}
|
|
156
|
+
// 7. Add --include and apply --exclude through buildFinalFileSet
|
|
157
|
+
const finalFiles = await (0, file_set_1.buildFinalFileSet)({
|
|
158
|
+
root: plan.root,
|
|
159
|
+
sources: matchedFiles,
|
|
160
|
+
include: plan.include ?? [],
|
|
161
|
+
exclude: plan.exclude ?? [],
|
|
162
|
+
});
|
|
163
|
+
// 8. No-match behavior validation
|
|
164
|
+
const hasMatchedFiles = matchedCountBeforeLimit > 0;
|
|
165
|
+
const hasIncludes = plan.include && plan.include.length > 0;
|
|
166
|
+
if (!hasMatchedFiles && !hasIncludes) {
|
|
167
|
+
errors.push("No files matched grep search.");
|
|
168
|
+
return { files: [], entries: [], includes: plan.include ?? [], mode: "grep", warnings, errors };
|
|
169
|
+
}
|
|
170
|
+
if (!hasMatchedFiles && hasIncludes) {
|
|
171
|
+
warnings.push("No files matched grep search.");
|
|
172
|
+
}
|
|
173
|
+
// 9. Generate metadata sections
|
|
174
|
+
const sections = [];
|
|
175
|
+
const formatList = (list) => (list.length > 0 ? list.map((item) => `\`${item}\``).join(", ") : "(none)");
|
|
176
|
+
let queryTermsDesc = "";
|
|
177
|
+
if (opts.mode === "regex") {
|
|
178
|
+
queryTermsDesc = `\`${opts.terms[0]}\` (regex)`;
|
|
179
|
+
}
|
|
180
|
+
else if (opts.mode === "query") {
|
|
181
|
+
queryTermsDesc = `\`${opts.terms[0]}\``;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
queryTermsDesc = opts.terms.map((t) => `\`${t}\``).join(", ");
|
|
185
|
+
}
|
|
186
|
+
const summaryContent = [
|
|
187
|
+
`- **Search Mode**: ${opts.mode}`,
|
|
188
|
+
`- **Query/Terms/Pattern**: ${queryTermsDesc}`,
|
|
189
|
+
`- **Within Boundaries**: ${formatList(opts.within)}`,
|
|
190
|
+
`- **Skip Boundaries**: ${formatList(opts.skip)}`,
|
|
191
|
+
`- **Negative Filters**: ${formatList(opts.negativeTerms)}`,
|
|
192
|
+
`- **Matched File Count (before includes & limits)**: ${matchedCountBeforeLimit}`,
|
|
193
|
+
`- **Max Files Limit**: ${resolvedMaxFiles ?? "(none)"}${maxFilesApplied ? " (applied)" : ""}`,
|
|
194
|
+
`- **Final File Count (after includes/excludes)**: ${finalFiles.length}`,
|
|
195
|
+
].join("\n");
|
|
196
|
+
sections.push({
|
|
197
|
+
id: "grep-summary",
|
|
198
|
+
title: "Grep Summary",
|
|
199
|
+
kind: "text",
|
|
200
|
+
content: summaryContent,
|
|
201
|
+
});
|
|
202
|
+
const finalFileSet = new Set(finalFiles);
|
|
203
|
+
const finalMatchedFiles = matchedFiles.filter((file) => finalFileSet.has(file));
|
|
204
|
+
const matchesContentList = [];
|
|
205
|
+
for (const file of finalMatchedFiles) {
|
|
206
|
+
const rel = path_1.default.relative(plan.root, file).replace(/\\/g, "/");
|
|
207
|
+
const count = matchCounts.get(file) || 0;
|
|
208
|
+
const countText = count === 1 ? "1 matching line" : `${count} matching lines`;
|
|
209
|
+
matchesContentList.push(`- ${rel} (${countText})`);
|
|
210
|
+
}
|
|
211
|
+
sections.push({
|
|
212
|
+
id: "grep-matches",
|
|
213
|
+
title: "Grep Matches",
|
|
214
|
+
kind: "text",
|
|
215
|
+
content: matchesContentList.length > 0 ? matchesContentList.join("\n") : "(no content matches)",
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
files: finalFiles,
|
|
219
|
+
entries: [],
|
|
220
|
+
includes: plan.include ?? [],
|
|
221
|
+
mode: "grep",
|
|
222
|
+
warnings,
|
|
223
|
+
errors,
|
|
224
|
+
sections,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProdexConfigFile, ExecutionPlan, CommandIntent } from "../types";
|
|
2
|
+
export interface PlannerResult {
|
|
3
|
+
plans: ExecutionPlan[];
|
|
4
|
+
warnings: string[];
|
|
5
|
+
errors: string[];
|
|
6
|
+
listScopes?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function createExecutionPlans(params: {
|
|
9
|
+
intent: CommandIntent;
|
|
10
|
+
userConfig: ProdexConfigFile;
|
|
11
|
+
root: string;
|
|
12
|
+
}): PlannerResult;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createExecutionPlans = createExecutionPlans;
|
|
4
|
+
const default_config_1 = require("../config/default-config");
|
|
5
|
+
const pack_plan_1 = require("./planners/pack-plan");
|
|
6
|
+
const trace_plan_1 = require("./planners/trace-plan");
|
|
7
|
+
const scope_plan_1 = require("./planners/scope-plan");
|
|
8
|
+
const git_plan_1 = require("./planners/git-plan");
|
|
9
|
+
const grep_plan_1 = require("./planners/grep-plan");
|
|
10
|
+
const attachment_options_1 = require("./planners/attachment-options");
|
|
11
|
+
function createExecutionPlans(params) {
|
|
12
|
+
const { intent, userConfig, root } = params;
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const errors = [];
|
|
15
|
+
const aliases = userConfig.aliases ?? default_config_1.DEFAULT_PRODEX_CONFIG.aliases;
|
|
16
|
+
const depth = userConfig.depth ?? default_config_1.DEFAULT_PRODEX_CONFIG.depth;
|
|
17
|
+
const maxFiles = userConfig.maxFiles ?? default_config_1.DEFAULT_PRODEX_CONFIG.maxFiles;
|
|
18
|
+
const defaultOutput = {
|
|
19
|
+
dir: userConfig.output?.dir ?? default_config_1.DEFAULT_PRODEX_CONFIG.output.dir,
|
|
20
|
+
versioned: userConfig.output?.versioned ?? default_config_1.DEFAULT_PRODEX_CONFIG.output.versioned,
|
|
21
|
+
format: userConfig.output?.format ?? default_config_1.DEFAULT_PRODEX_CONFIG.output.format,
|
|
22
|
+
};
|
|
23
|
+
const flags = intent.flags ?? {};
|
|
24
|
+
const attachmentOptions = (0, attachment_options_1.parseCommandAttachmentOptions)(flags, errors);
|
|
25
|
+
if (intent.kind === "pack") {
|
|
26
|
+
const plans = (0, pack_plan_1.buildPackPlan)({
|
|
27
|
+
intent,
|
|
28
|
+
userConfig,
|
|
29
|
+
root,
|
|
30
|
+
aliases,
|
|
31
|
+
depth,
|
|
32
|
+
maxFiles,
|
|
33
|
+
defaultOutput,
|
|
34
|
+
attachmentOptions,
|
|
35
|
+
warnings,
|
|
36
|
+
errors,
|
|
37
|
+
});
|
|
38
|
+
return { plans, warnings, errors };
|
|
39
|
+
}
|
|
40
|
+
if (intent.kind === "trace") {
|
|
41
|
+
const plans = (0, trace_plan_1.buildTracePlan)({
|
|
42
|
+
intent,
|
|
43
|
+
userConfig,
|
|
44
|
+
root,
|
|
45
|
+
aliases,
|
|
46
|
+
depth,
|
|
47
|
+
maxFiles,
|
|
48
|
+
defaultOutput,
|
|
49
|
+
attachmentOptions,
|
|
50
|
+
warnings,
|
|
51
|
+
errors,
|
|
52
|
+
});
|
|
53
|
+
return { plans, warnings, errors };
|
|
54
|
+
}
|
|
55
|
+
if (intent.kind === "scope") {
|
|
56
|
+
const scopeResult = (0, scope_plan_1.buildScopePlan)({
|
|
57
|
+
intent,
|
|
58
|
+
userConfig,
|
|
59
|
+
root,
|
|
60
|
+
aliases,
|
|
61
|
+
depth,
|
|
62
|
+
maxFiles,
|
|
63
|
+
defaultOutput,
|
|
64
|
+
attachmentOptions,
|
|
65
|
+
warnings,
|
|
66
|
+
errors,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
plans: scopeResult.plans,
|
|
70
|
+
warnings,
|
|
71
|
+
errors,
|
|
72
|
+
listScopes: scopeResult.listScopes,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (intent.kind === "git") {
|
|
76
|
+
const plans = (0, git_plan_1.buildGitPlan)({
|
|
77
|
+
intent,
|
|
78
|
+
userConfig,
|
|
79
|
+
root,
|
|
80
|
+
aliases,
|
|
81
|
+
depth,
|
|
82
|
+
maxFiles,
|
|
83
|
+
defaultOutput,
|
|
84
|
+
attachmentOptions,
|
|
85
|
+
warnings,
|
|
86
|
+
errors,
|
|
87
|
+
});
|
|
88
|
+
return { plans, warnings, errors };
|
|
89
|
+
}
|
|
90
|
+
if (intent.kind === "grep") {
|
|
91
|
+
const plans = (0, grep_plan_1.buildGrepPlan)({
|
|
92
|
+
intent,
|
|
93
|
+
userConfig,
|
|
94
|
+
root,
|
|
95
|
+
aliases,
|
|
96
|
+
depth,
|
|
97
|
+
maxFiles,
|
|
98
|
+
defaultOutput,
|
|
99
|
+
attachmentOptions,
|
|
100
|
+
warnings,
|
|
101
|
+
errors,
|
|
102
|
+
});
|
|
103
|
+
return { plans, warnings, errors };
|
|
104
|
+
}
|
|
105
|
+
return { plans: [], warnings, errors };
|
|
106
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCommandAttachmentOptions = parseCommandAttachmentOptions;
|
|
4
|
+
function parseCommandAttachmentOptions(flags, errors) {
|
|
5
|
+
const commands = flags.cmd ?? [];
|
|
6
|
+
const hasCmd = commands.length > 0;
|
|
7
|
+
const hasTimeout = flags.cmdTimeout !== undefined && flags.cmdTimeout !== null;
|
|
8
|
+
const hasFailOnError = flags.failOnCmdError !== undefined;
|
|
9
|
+
// Reject command-specific flags when no command is provided
|
|
10
|
+
if ((hasTimeout || hasFailOnError) && !hasCmd) {
|
|
11
|
+
errors.push("Command attachment options (--cmd-timeout, --fail-on-cmd-error) require providing at least one command using --cmd.");
|
|
12
|
+
}
|
|
13
|
+
// Reject blank commands
|
|
14
|
+
for (const cmd of commands) {
|
|
15
|
+
if (!cmd.trim()) {
|
|
16
|
+
errors.push("Command string specified via --cmd cannot be blank.");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Always validate timeout if specified
|
|
20
|
+
let timeoutSeconds = 180;
|
|
21
|
+
if (hasTimeout) {
|
|
22
|
+
const val = flags.cmdTimeout;
|
|
23
|
+
if (typeof val !== "number" || !Number.isFinite(val) || val <= 0) {
|
|
24
|
+
errors.push("Flag --cmd-timeout expects a positive finite number.");
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
timeoutSeconds = val;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (commands.length === 0) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const failOnError = !!flags.failOnCmdError;
|
|
34
|
+
return {
|
|
35
|
+
commands,
|
|
36
|
+
timeoutSeconds,
|
|
37
|
+
failOnError,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExecutionPlan, CommandIntent, ProdexConfigFile, CommandAttachmentOptions } from "../../types";
|
|
2
|
+
export declare function buildGitPlan(params: {
|
|
3
|
+
intent: CommandIntent;
|
|
4
|
+
userConfig: ProdexConfigFile;
|
|
5
|
+
root: string;
|
|
6
|
+
aliases: Record<string, string>;
|
|
7
|
+
depth: number;
|
|
8
|
+
maxFiles: number;
|
|
9
|
+
defaultOutput: ExecutionPlan["output"];
|
|
10
|
+
attachmentOptions: CommandAttachmentOptions | undefined;
|
|
11
|
+
warnings: string[];
|
|
12
|
+
errors: string[];
|
|
13
|
+
}): ExecutionPlan[];
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildGitPlan = buildGitPlan;
|
|
4
|
+
const path_patterns_1 = require("../../filesystem/path-patterns");
|
|
5
|
+
const list_utils_1 = require("./list-utils");
|
|
6
|
+
function buildGitPlan(params) {
|
|
7
|
+
const { intent, userConfig, root, aliases, depth, maxFiles, defaultOutput, attachmentOptions, warnings, errors } = params;
|
|
8
|
+
const flags = intent.flags ?? {};
|
|
9
|
+
if (flags.entry !== undefined ||
|
|
10
|
+
flags.scope !== undefined ||
|
|
11
|
+
flags.key !== undefined ||
|
|
12
|
+
flags.all !== undefined ||
|
|
13
|
+
flags.list !== undefined ||
|
|
14
|
+
flags.depth !== undefined ||
|
|
15
|
+
flags.maxFiles !== undefined) {
|
|
16
|
+
errors.push('Command "git" does not accept "--entry", "--scope", "--key", "--all", "--list", "--depth", or "--max-files".');
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const rootExclude = userConfig.exclude ?? [];
|
|
20
|
+
const mergedInclude = (flags.include ?? []).map((item) => (0, path_patterns_1.normalizePathOrGlob)(item, root, { role: "include" }));
|
|
21
|
+
const mergedExclude = [...rootExclude, ...(flags.exclude ?? [])].map((item) => (0, path_patterns_1.normalizePathOrGlob)(item, root, { role: "exclude" }));
|
|
22
|
+
const hasGroupB = flags.commit !== undefined || flags.range !== undefined || flags.against !== undefined;
|
|
23
|
+
const hasGroupA = flags.changed !== undefined || flags.staged !== undefined || flags.unstaged !== undefined || flags.untracked !== undefined;
|
|
24
|
+
// Check mutual exclusivity within Group B
|
|
25
|
+
const activeGroupBFlags = [];
|
|
26
|
+
if (flags.commit !== undefined)
|
|
27
|
+
activeGroupBFlags.push("--commit");
|
|
28
|
+
if (flags.range !== undefined)
|
|
29
|
+
activeGroupBFlags.push("--range");
|
|
30
|
+
if (flags.against !== undefined)
|
|
31
|
+
activeGroupBFlags.push("--against");
|
|
32
|
+
if (activeGroupBFlags.length > 1) {
|
|
33
|
+
errors.push("--commit, --range, and --against are mutually exclusive.");
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
// Check compatibility between Group A and Group B
|
|
37
|
+
if (hasGroupB && hasGroupA) {
|
|
38
|
+
errors.push("--commit/--range/--against cannot be combined with --changed, --staged, --unstaged, or --untracked.");
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
let gitOptions;
|
|
42
|
+
let defaultOutputName = "git-changes";
|
|
43
|
+
function sanitizeName(name) {
|
|
44
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-");
|
|
45
|
+
}
|
|
46
|
+
if (flags.commit !== undefined) {
|
|
47
|
+
const rev = flags.commit.trim();
|
|
48
|
+
if (!rev) {
|
|
49
|
+
errors.push("--commit requires a non-empty revision.");
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
gitOptions = {
|
|
53
|
+
mode: "commit",
|
|
54
|
+
rev,
|
|
55
|
+
includeDiff: !!flags.includeDiff,
|
|
56
|
+
};
|
|
57
|
+
defaultOutputName = `git-commit-${sanitizeName(rev.slice(0, 8))}`;
|
|
58
|
+
}
|
|
59
|
+
else if (flags.range !== undefined) {
|
|
60
|
+
const spec = flags.range.trim();
|
|
61
|
+
const match = spec.match(/^(.+?)(\.{2,3})(.+)$/);
|
|
62
|
+
if (!match) {
|
|
63
|
+
errors.push('Invalid range format. Expected "base..head" or "base...head".');
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const base = match[1];
|
|
67
|
+
const separator = match[2];
|
|
68
|
+
const head = match[3];
|
|
69
|
+
if (!base || !head) {
|
|
70
|
+
errors.push('Invalid range format. Expected "base..head" or "base...head".');
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
gitOptions = {
|
|
74
|
+
mode: "range",
|
|
75
|
+
spec,
|
|
76
|
+
base,
|
|
77
|
+
head,
|
|
78
|
+
includeDiff: !!flags.includeDiff,
|
|
79
|
+
};
|
|
80
|
+
defaultOutputName = "git-range";
|
|
81
|
+
}
|
|
82
|
+
else if (flags.against !== undefined) {
|
|
83
|
+
const base = flags.against.trim();
|
|
84
|
+
if (!base) {
|
|
85
|
+
errors.push("--against requires a non-empty base branch/commit.");
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
gitOptions = {
|
|
89
|
+
mode: "against",
|
|
90
|
+
base,
|
|
91
|
+
includeDiff: !!flags.includeDiff,
|
|
92
|
+
};
|
|
93
|
+
defaultOutputName = `git-against-${sanitizeName(base)}`;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Working-tree mode
|
|
97
|
+
const changed = !!flags.changed;
|
|
98
|
+
let staged = !!flags.staged;
|
|
99
|
+
let unstaged = !!flags.unstaged;
|
|
100
|
+
let untracked = !!flags.untracked;
|
|
101
|
+
if (!changed && !staged && !unstaged && !untracked) {
|
|
102
|
+
staged = true;
|
|
103
|
+
unstaged = true;
|
|
104
|
+
untracked = true;
|
|
105
|
+
}
|
|
106
|
+
else if (changed) {
|
|
107
|
+
staged = true;
|
|
108
|
+
unstaged = true;
|
|
109
|
+
untracked = true;
|
|
110
|
+
}
|
|
111
|
+
gitOptions = {
|
|
112
|
+
mode: "working-tree",
|
|
113
|
+
changed: changed || (!staged && !unstaged && !untracked),
|
|
114
|
+
staged,
|
|
115
|
+
unstaged,
|
|
116
|
+
untracked,
|
|
117
|
+
includeDiff: !!flags.includeDiff,
|
|
118
|
+
};
|
|
119
|
+
defaultOutputName = "git-changes";
|
|
120
|
+
}
|
|
121
|
+
const plan = {
|
|
122
|
+
root,
|
|
123
|
+
command: "git",
|
|
124
|
+
outputName: flags.name ?? defaultOutputName,
|
|
125
|
+
entry: [],
|
|
126
|
+
include: (0, list_utils_1.uniqueTrimmed)(mergedInclude),
|
|
127
|
+
exclude: (0, list_utils_1.uniqueTrimmed)(mergedExclude),
|
|
128
|
+
depth,
|
|
129
|
+
maxFiles,
|
|
130
|
+
aliases,
|
|
131
|
+
output: {
|
|
132
|
+
dir: defaultOutput.dir,
|
|
133
|
+
versioned: defaultOutput.versioned,
|
|
134
|
+
format: flags.format ?? defaultOutput.format,
|
|
135
|
+
},
|
|
136
|
+
dryRun: !!flags.dryRun,
|
|
137
|
+
attachmentOptions,
|
|
138
|
+
gitOptions,
|
|
139
|
+
};
|
|
140
|
+
return [plan];
|
|
141
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExecutionPlan, CommandIntent, ProdexConfigFile, CommandAttachmentOptions } from "../../types";
|
|
2
|
+
export declare function buildGrepPlan(params: {
|
|
3
|
+
intent: CommandIntent;
|
|
4
|
+
userConfig: ProdexConfigFile;
|
|
5
|
+
root: string;
|
|
6
|
+
aliases: Record<string, string>;
|
|
7
|
+
depth: number;
|
|
8
|
+
maxFiles: number;
|
|
9
|
+
defaultOutput: ExecutionPlan["output"];
|
|
10
|
+
attachmentOptions: CommandAttachmentOptions | undefined;
|
|
11
|
+
warnings: string[];
|
|
12
|
+
errors: string[];
|
|
13
|
+
}): ExecutionPlan[];
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildGrepPlan = buildGrepPlan;
|
|
4
|
+
const path_patterns_1 = require("../../filesystem/path-patterns");
|
|
5
|
+
const list_utils_1 = require("./list-utils");
|
|
6
|
+
function buildGrepPlan(params) {
|
|
7
|
+
const { intent, userConfig, root, aliases, depth, maxFiles, defaultOutput, attachmentOptions, warnings, errors } = params;
|
|
8
|
+
const flags = intent.flags ?? {};
|
|
9
|
+
// Validate unsupported options
|
|
10
|
+
if (flags.entry !== undefined || flags.target !== undefined || flags.scope !== undefined || flags.key !== undefined || flags.all !== undefined || flags.list !== undefined) {
|
|
11
|
+
errors.push('Command "grep" does not accept "--entry", "--target", "--scope", "--key", "--all", or "--list".');
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
// Mode validation
|
|
15
|
+
const modesCount = (flags.query !== undefined ? 1 : 0) +
|
|
16
|
+
(flags.any !== undefined ? 1 : 0) +
|
|
17
|
+
(flags.grepAll !== undefined ? 1 : 0) +
|
|
18
|
+
(flags.regex !== undefined ? 1 : 0);
|
|
19
|
+
if (modesCount === 0) {
|
|
20
|
+
errors.push('Command "grep" requires one search mode: --query, --any, --all, or --regex.');
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
else if (modesCount > 1) {
|
|
24
|
+
errors.push('Command "grep" accepts only one positive search mode.');
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
let mode = "query";
|
|
28
|
+
let terms = [];
|
|
29
|
+
if (flags.query !== undefined) {
|
|
30
|
+
mode = "query";
|
|
31
|
+
if (!flags.query.trim()) {
|
|
32
|
+
errors.push("Search terms cannot be blank.");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
terms = [flags.query];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (flags.any !== undefined) {
|
|
39
|
+
mode = "any";
|
|
40
|
+
if (flags.any.length === 0 || flags.any.some(t => !t.trim())) {
|
|
41
|
+
errors.push("Search terms cannot be blank.");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
terms = flags.any;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (flags.grepAll !== undefined) {
|
|
48
|
+
mode = "all";
|
|
49
|
+
if (flags.grepAll.length === 0 || flags.grepAll.some(t => !t.trim())) {
|
|
50
|
+
errors.push("Search terms cannot be blank.");
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
terms = flags.grepAll;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (flags.regex !== undefined) {
|
|
57
|
+
mode = "regex";
|
|
58
|
+
if (!flags.regex.trim()) {
|
|
59
|
+
errors.push("Search terms cannot be blank.");
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
terms = [flags.regex];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let negativeTerms = [];
|
|
66
|
+
if (flags.not !== undefined) {
|
|
67
|
+
if (flags.not.length === 0 || flags.not.some(t => !t.trim())) {
|
|
68
|
+
errors.push("Search terms cannot be blank.");
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
negativeTerms = flags.not;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Invalid max files validation
|
|
75
|
+
const resolvedMaxFiles = flags.maxFiles !== undefined ? flags.maxFiles : maxFiles;
|
|
76
|
+
if (resolvedMaxFiles !== undefined && resolvedMaxFiles !== null) {
|
|
77
|
+
if (!Number.isInteger(resolvedMaxFiles) || resolvedMaxFiles <= 0) {
|
|
78
|
+
errors.push("--max-files must be an integer greater than 0.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (errors.length) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const rootExclude = userConfig.exclude ?? [];
|
|
85
|
+
const mergedWithin = (flags.within ?? []).map((item) => (0, path_patterns_1.normalizePathOrGlob)(item, root, { role: "within" }));
|
|
86
|
+
const mergedSkip = (flags.skip ?? []).map((item) => (0, path_patterns_1.normalizePathOrGlob)(item, root, { role: "skip" }));
|
|
87
|
+
const mergedInclude = (flags.include ?? []).map((item) => (0, path_patterns_1.normalizePathOrGlob)(item, root, { role: "include" }));
|
|
88
|
+
const mergedExclude = [...rootExclude, ...(flags.exclude ?? [])].map((item) => (0, path_patterns_1.normalizePathOrGlob)(item, root, { role: "exclude" }));
|
|
89
|
+
const plan = {
|
|
90
|
+
root,
|
|
91
|
+
command: "grep",
|
|
92
|
+
outputName: flags.name ?? "grep-results",
|
|
93
|
+
entry: [],
|
|
94
|
+
include: (0, list_utils_1.uniqueTrimmed)(mergedInclude),
|
|
95
|
+
exclude: (0, list_utils_1.uniqueTrimmed)(mergedExclude),
|
|
96
|
+
depth: flags.depth ?? depth,
|
|
97
|
+
maxFiles: flags.maxFiles ?? maxFiles,
|
|
98
|
+
aliases,
|
|
99
|
+
output: {
|
|
100
|
+
dir: defaultOutput.dir,
|
|
101
|
+
versioned: defaultOutput.versioned,
|
|
102
|
+
format: flags.format ?? defaultOutput.format,
|
|
103
|
+
},
|
|
104
|
+
dryRun: !!flags.dryRun,
|
|
105
|
+
attachmentOptions,
|
|
106
|
+
grepOptions: {
|
|
107
|
+
mode,
|
|
108
|
+
terms,
|
|
109
|
+
negativeTerms: (0, list_utils_1.uniqueTrimmed)(negativeTerms),
|
|
110
|
+
within: (0, list_utils_1.uniqueTrimmed)(mergedWithin),
|
|
111
|
+
skip: (0, list_utils_1.uniqueTrimmed)(mergedSkip),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
return [plan];
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function uniqueTrimmed(items: string[]): string[];
|