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.
Files changed (119) hide show
  1. package/README.md +369 -146
  2. package/dist/app/dependency-source-provider.d.ts +2 -0
  3. package/dist/app/dependency-source-provider.js +66 -0
  4. package/dist/app/entry-resolver.d.ts +7 -0
  5. package/dist/app/entry-resolver.js +26 -0
  6. package/dist/app/execute-run.d.ts +2 -2
  7. package/dist/app/execute-run.js +138 -36
  8. package/dist/app/git-source-provider.d.ts +4 -0
  9. package/dist/app/git-source-provider.js +551 -0
  10. package/dist/app/grep-source-provider.d.ts +2 -0
  11. package/dist/app/grep-source-provider.js +226 -0
  12. package/dist/app/planner.d.ts +12 -0
  13. package/dist/app/planner.js +106 -0
  14. package/dist/app/planners/attachment-options.d.ts +2 -0
  15. package/dist/app/planners/attachment-options.js +39 -0
  16. package/dist/app/planners/git-plan.d.ts +13 -0
  17. package/dist/app/planners/git-plan.js +141 -0
  18. package/dist/app/planners/grep-plan.d.ts +13 -0
  19. package/dist/app/planners/grep-plan.js +115 -0
  20. package/dist/app/planners/list-utils.d.ts +1 -0
  21. package/dist/app/planners/list-utils.js +6 -0
  22. package/dist/app/planners/pack-plan.d.ts +13 -0
  23. package/dist/app/planners/pack-plan.js +65 -0
  24. package/dist/app/planners/scope-plan.d.ts +17 -0
  25. package/dist/app/planners/scope-plan.js +143 -0
  26. package/dist/app/planners/trace-plan.d.ts +13 -0
  27. package/dist/app/planners/trace-plan.js +77 -0
  28. package/dist/app/source-collector.d.ts +5 -0
  29. package/dist/app/source-collector.js +33 -0
  30. package/dist/app/target-resolver.d.ts +7 -0
  31. package/dist/app/target-resolver.js +110 -0
  32. package/dist/cli/cli-input.js +70 -19
  33. package/dist/cli/flag-specs.d.ts +7 -3
  34. package/dist/cli/flag-specs.js +58 -7
  35. package/dist/cli/help-specs.d.ts +205 -0
  36. package/dist/cli/help-specs.js +136 -0
  37. package/dist/cli/help.js +107 -28
  38. package/dist/cli/report-command.js +59 -26
  39. package/dist/commands/{run-command.d.ts → git-command.d.ts} +3 -3
  40. package/dist/commands/git-command.js +7 -0
  41. package/dist/commands/grep-command.d.ts +11 -0
  42. package/dist/commands/grep-command.js +7 -0
  43. package/dist/commands/pack-command.d.ts +11 -0
  44. package/dist/commands/pack-command.js +7 -0
  45. package/dist/commands/scope-command.d.ts +12 -0
  46. package/dist/commands/scope-command.js +7 -0
  47. package/dist/commands/shared-runner.d.ts +12 -0
  48. package/dist/commands/shared-runner.js +30 -0
  49. package/dist/commands/trace-command.d.ts +11 -0
  50. package/dist/commands/trace-command.js +7 -0
  51. package/dist/config/default-config.js +7 -11
  52. package/dist/config/load.d.ts +1 -0
  53. package/dist/config/load.js +269 -4
  54. package/dist/config/migration/detect.d.ts +2 -1
  55. package/dist/config/migration/detect.js +10 -2
  56. package/dist/config/migration/messages.js +12 -11
  57. package/dist/config/migration/transform.js +77 -32
  58. package/dist/filesystem/binary.d.ts +2 -0
  59. package/dist/filesystem/binary.js +30 -0
  60. package/dist/filesystem/entry-discovery.d.ts +14 -0
  61. package/dist/filesystem/entry-discovery.js +78 -0
  62. package/dist/filesystem/file-set.d.ts +7 -0
  63. package/dist/filesystem/file-set.js +57 -0
  64. package/dist/filesystem/glob-scan.d.ts +6 -2
  65. package/dist/filesystem/glob-scan.js +12 -7
  66. package/dist/filesystem/path-patterns.d.ts +20 -0
  67. package/dist/filesystem/path-patterns.js +70 -0
  68. package/dist/index.js +51 -29
  69. package/dist/output/markdown-sections.d.ts +5 -0
  70. package/dist/output/markdown-sections.js +103 -0
  71. package/dist/output/markdown-toc.d.ts +31 -0
  72. package/dist/output/markdown-toc.js +287 -0
  73. package/dist/output/markdown.d.ts +5 -8
  74. package/dist/output/markdown.js +80 -127
  75. package/dist/output/produce-output.d.ts +4 -1
  76. package/dist/output/produce-output.js +6 -7
  77. package/dist/output/render-helpers.d.ts +4 -0
  78. package/dist/output/render-helpers.js +14 -0
  79. package/dist/output/text.d.ts +2 -2
  80. package/dist/output/text.js +47 -8
  81. package/dist/resolvers/js/resolve-alias.js +10 -41
  82. package/dist/resolvers/php/bindings.js +6 -6
  83. package/dist/resolvers/php/extract-imports.d.ts +19 -7
  84. package/dist/resolvers/php/extract-imports.js +146 -26
  85. package/dist/resolvers/php/php-resolver.js +55 -24
  86. package/dist/resolvers/php/psr4.d.ts +1 -1
  87. package/dist/resolvers/php/psr4.js +7 -1
  88. package/dist/runtime/shell-command-runner.d.ts +5 -0
  89. package/dist/runtime/shell-command-runner.js +118 -0
  90. package/dist/tracing/collect-trace.d.ts +10 -0
  91. package/dist/tracing/collect-trace.js +23 -0
  92. package/dist/tracing/exclude.d.ts +0 -5
  93. package/dist/tracing/exclude.js +16 -12
  94. package/dist/tracing/follow-chain.js +2 -2
  95. package/dist/types/app.types.d.ts +38 -8
  96. package/dist/types/artifact.types.d.ts +50 -0
  97. package/dist/types/artifact.types.js +2 -0
  98. package/dist/types/cli.types.d.ts +52 -4
  99. package/dist/types/config.types.d.ts +37 -20
  100. package/dist/types/index.d.ts +2 -0
  101. package/dist/types/index.js +2 -0
  102. package/dist/types/output.types.d.ts +12 -3
  103. package/dist/types/planner.types.d.ts +76 -0
  104. package/dist/types/planner.types.js +2 -0
  105. package/dist/types/resolver.types.d.ts +1 -1
  106. package/dist/types/tracing.types.d.ts +0 -11
  107. package/package.json +2 -2
  108. package/schema/prodex.schema.json +59 -41
  109. package/dist/app/run-plans.d.ts +0 -11
  110. package/dist/app/run-plans.js +0 -61
  111. package/dist/commands/profiles-command.d.ts +0 -6
  112. package/dist/commands/profiles-command.js +0 -16
  113. package/dist/commands/run-command.js +0 -17
  114. package/dist/config/build-config.d.ts +0 -13
  115. package/dist/config/build-config.js +0 -127
  116. package/dist/tracing/include-files.d.ts +0 -2
  117. package/dist/tracing/include-files.js +0 -36
  118. package/dist/tracing/trace-run.d.ts +0 -2
  119. package/dist/tracing/trace-run.js +0 -21
@@ -13,39 +13,65 @@ function reportCommandResult(result) {
13
13
  console.error(`Error: ${error}`);
14
14
  if (result.message)
15
15
  console.log(result.message);
16
- if (result.profiles)
17
- reportProfiles(result.profiles);
16
+ if (result.scopes)
17
+ reportScopes(result.scopes);
18
18
  if (result.migration)
19
19
  reportMigration(result.migration);
20
- for (const run of result.runs)
21
- reportRun(run);
20
+ reportRuns(result.runs);
22
21
  }
23
- function reportRun(run) {
24
- const label = run.profile ? ` [${run.profile}]` : "";
22
+ function reportRuns(runs) {
23
+ for (const run of runs) {
24
+ reportRunWarningsAndErrors(run);
25
+ if (run.plannedCommands && run.plannedCommands.length > 0) {
26
+ console.log(`[dry-run] Planned command attachments to run in sequence:`);
27
+ for (const cmd of run.plannedCommands) {
28
+ console.log(` - ${cmd}`);
29
+ }
30
+ }
31
+ }
32
+ const reportableRuns = runs.filter((run) => run.ok || run.outputPath);
33
+ if (!reportableRuns.length)
34
+ return;
35
+ const rows = reportableRuns.map((run) => {
36
+ const sizeMb = run.outputSizeBytes !== undefined ? (run.outputSizeBytes / (1024 * 1024)).toFixed(2) : "0.00";
37
+ return {
38
+ icon: run.ok ? "✓" : "✗",
39
+ label: formatRunLabel(run),
40
+ mode: run.mode,
41
+ files: `${run.files.length} files`,
42
+ size: `${sizeMb} MB`,
43
+ output: run.outputPath ? formatPath(run.outputPath, run.root) : "dry-run",
44
+ };
45
+ });
46
+ const labelWidth = maxWidth(rows.map((row) => row.label));
47
+ const modeWidth = maxWidth(rows.map((row) => row.mode));
48
+ const filesWidth = maxWidth(rows.map((row) => row.files));
49
+ const sizeWidth = maxWidth(rows.map((row) => row.size));
50
+ for (const row of rows) {
51
+ console.log(`${row.icon} ${row.label.padEnd(labelWidth)} ${row.mode.padEnd(modeWidth)} ${row.files.padStart(filesWidth)} ${row.size.padStart(sizeWidth)} ${row.output}`);
52
+ }
53
+ }
54
+ function reportRunWarningsAndErrors(run) {
55
+ const label = run.scopeKey ? ` [${run.scopeKey}]` : "";
25
56
  for (const warning of run.warnings)
26
57
  console.warn(`Warning${label}: ${warning}`);
27
58
  for (const error of run.errors)
28
59
  console.error(`Error${label}: ${error}`);
29
- if (!run.outputPath)
30
- return;
31
- console.log(`Created${label}: ${formatPath(run.outputPath, run.root)}`);
32
- console.log(`Mode${label}: ${formatMode(run)}`);
33
- console.log(`Files${label}: ${run.files.length} total`);
34
60
  }
35
61
  function reportMigration(migration) {
36
62
  if (migration.errors.length)
37
63
  return;
38
64
  if (!migration.needed) {
39
- console.log("prodex.json is already using config version 4.");
65
+ console.log("prodex.json is already using config version 5.");
40
66
  return;
41
67
  }
42
68
  if (migration.written) {
43
69
  console.log(`Backed up prodex.json to ${formatPath(migration.backupPath, path_1.default.dirname(migration.path))}`);
44
- console.log("Migrated prodex.json to version 4.");
70
+ console.log("Migrated prodex.json to version 5.");
45
71
  return;
46
72
  }
47
73
  const from = migration.fromVersion ?? "legacy";
48
- console.log(`prodex.json can be migrated from version ${from} to version 4.`);
74
+ console.log(`prodex.json can be migrated from version ${from} to version 5.`);
49
75
  if (migration.changes.length) {
50
76
  console.log("");
51
77
  console.log("Changes:");
@@ -55,14 +81,25 @@ function reportMigration(migration) {
55
81
  console.log("");
56
82
  console.log("Run `prodex migrate --write` to update prodex.json.");
57
83
  }
58
- function reportProfiles(profiles) {
59
- if (!profiles.length) {
60
- console.log("No profiles configured.");
84
+ function reportScopes(scopes) {
85
+ if (!scopes.length) {
86
+ console.log("No scopes configured.");
61
87
  return;
62
88
  }
63
- console.log("Available profiles:");
64
- for (const profile of profiles)
65
- console.log(` ${profile}`);
89
+ console.log("Available scopes:");
90
+ for (const scope of scopes)
91
+ console.log(` ${scope}`);
92
+ }
93
+ function formatRunLabel(run) {
94
+ if (run.outputName)
95
+ return run.outputName;
96
+ if (run.scopeKey)
97
+ return run.scopeKey;
98
+ if (!run.outputPath)
99
+ return "run";
100
+ const ext = path_1.default.extname(run.outputPath);
101
+ const base = path_1.default.basename(run.outputPath, ext);
102
+ return base.replace(/-trace(?:_\d{6}-\d{4})?$/, "") || "run";
66
103
  }
67
104
  function formatPath(filePath, root) {
68
105
  const absolute = path_1.default.resolve(filePath);
@@ -71,10 +108,6 @@ function formatPath(filePath, root) {
71
108
  return (0, path_2.normalizePath)(relative);
72
109
  return (0, path_2.normalizePath)(absolute);
73
110
  }
74
- function formatMode(run) {
75
- if (run.mode === "include-only")
76
- return `include-only (${run.includes.length} include patterns)`;
77
- if (run.mode === "mixed")
78
- return `trace + includes (${run.entries.length} entries, ${run.includes.length} include patterns)`;
79
- return `trace (${run.entries.length} entries)`;
111
+ function maxWidth(values) {
112
+ return values.reduce((max, value) => Math.max(max, value.length), 0);
80
113
  }
@@ -1,11 +1,11 @@
1
1
  import type { ProdexFlags, RunResult } from "../types";
2
- export interface RunCommandResult {
2
+ export interface GitCommandResult {
3
3
  runs: RunResult[];
4
4
  warnings: string[];
5
5
  errors: string[];
6
6
  }
7
- export declare function runCommand(params: {
7
+ export declare function gitCommand(params: {
8
8
  rootArg?: string;
9
9
  flags?: Partial<ProdexFlags>;
10
10
  cwd?: string;
11
- }): Promise<RunCommandResult>;
11
+ }): Promise<GitCommandResult>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gitCommand = gitCommand;
4
+ const shared_runner_1 = require("./shared-runner");
5
+ async function gitCommand(params) {
6
+ return (0, shared_runner_1.executeCommandWithPlanner)("git", params);
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { ProdexFlags, RunResult } from "../types";
2
+ export interface GrepCommandResult {
3
+ runs: RunResult[];
4
+ warnings: string[];
5
+ errors: string[];
6
+ }
7
+ export declare function grepCommand(params: {
8
+ rootArg?: string;
9
+ flags?: Partial<ProdexFlags>;
10
+ cwd?: string;
11
+ }): Promise<GrepCommandResult>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.grepCommand = grepCommand;
4
+ const shared_runner_1 = require("./shared-runner");
5
+ async function grepCommand(params) {
6
+ return (0, shared_runner_1.executeCommandWithPlanner)("grep", params);
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { ProdexFlags, RunResult } from "../types";
2
+ export interface PackCommandResult {
3
+ runs: RunResult[];
4
+ warnings: string[];
5
+ errors: string[];
6
+ }
7
+ export declare function packCommand(params: {
8
+ rootArg?: string;
9
+ flags?: Partial<ProdexFlags>;
10
+ cwd?: string;
11
+ }): Promise<PackCommandResult>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.packCommand = packCommand;
4
+ const shared_runner_1 = require("./shared-runner");
5
+ async function packCommand(params) {
6
+ return (0, shared_runner_1.executeCommandWithPlanner)("pack", params);
7
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProdexFlags, RunResult } from "../types";
2
+ export interface ScopeCommandResult {
3
+ runs: RunResult[];
4
+ warnings: string[];
5
+ errors: string[];
6
+ scopes?: string[];
7
+ }
8
+ export declare function scopeCommand(params: {
9
+ rootArg?: string;
10
+ flags?: Partial<ProdexFlags>;
11
+ cwd?: string;
12
+ }): Promise<ScopeCommandResult>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scopeCommand = scopeCommand;
4
+ const shared_runner_1 = require("./shared-runner");
5
+ async function scopeCommand(params) {
6
+ return (0, shared_runner_1.executeCommandWithPlanner)("scope", params);
7
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProdexFlags, RunResult } from "../types";
2
+ export interface PlannerCommandResult {
3
+ runs: RunResult[];
4
+ warnings: string[];
5
+ errors: string[];
6
+ scopes?: string[];
7
+ }
8
+ export declare function executeCommandWithPlanner(kind: "pack" | "trace" | "scope" | "git" | "grep", params: {
9
+ rootArg?: string;
10
+ flags?: Partial<ProdexFlags>;
11
+ cwd?: string;
12
+ }): Promise<PlannerCommandResult>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeCommandWithPlanner = executeCommandWithPlanner;
4
+ const project_context_1 = require("../app/project-context");
5
+ const planner_1 = require("../app/planner");
6
+ const execute_run_1 = require("../app/execute-run");
7
+ async function executeCommandWithPlanner(kind, params) {
8
+ const project = (0, project_context_1.loadProjectContext)(params.rootArg, params.cwd);
9
+ const warnings = [...project.warnings];
10
+ const errors = [...project.errors];
11
+ if (errors.length)
12
+ return { runs: [], warnings, errors };
13
+ const planned = (0, planner_1.createExecutionPlans)({
14
+ intent: { kind, rootArg: params.rootArg, flags: params.flags ?? {} },
15
+ userConfig: project.config,
16
+ root: project.root,
17
+ });
18
+ warnings.push(...planned.warnings);
19
+ errors.push(...planned.errors);
20
+ if (errors.length)
21
+ return { runs: [], warnings, errors };
22
+ if (planned.listScopes) {
23
+ return { runs: [], warnings, errors, scopes: planned.listScopes };
24
+ }
25
+ const runs = [];
26
+ for (const plan of planned.plans) {
27
+ runs.push(await (0, execute_run_1.executeRun)(plan));
28
+ }
29
+ return { runs, warnings, errors };
30
+ }
@@ -0,0 +1,11 @@
1
+ import type { ProdexFlags, RunResult } from "../types";
2
+ export interface TraceCommandResult {
3
+ runs: RunResult[];
4
+ warnings: string[];
5
+ errors: string[];
6
+ }
7
+ export declare function traceCommand(params: {
8
+ rootArg?: string;
9
+ flags?: Partial<ProdexFlags>;
10
+ cwd?: string;
11
+ }): Promise<TraceCommandResult>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traceCommand = traceCommand;
4
+ const shared_runner_1 = require("./shared-runner");
5
+ async function traceCommand(params) {
6
+ return (0, shared_runner_1.executeCommandWithPlanner)("trace", params);
7
+ }
@@ -2,22 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_PRODEX_CONFIG = void 0;
4
4
  exports.DEFAULT_PRODEX_CONFIG = {
5
- version: 4,
5
+ version: 5,
6
6
  $schema: "https://raw.githubusercontent.com/emxhive/prodex/main/schema/prodex.schema.json",
7
7
  output: {
8
8
  dir: "prodex",
9
9
  versioned: true,
10
10
  format: "md",
11
11
  },
12
- entry: [],
13
- include: [],
14
- exclude: ["node_modules/**", "vendor/**", "dist/**", "@shadcn/**", "**/components/ui/**"],
15
- resolve: {
16
- aliases: {
17
- "@": "resources/js",
18
- },
19
- maxDepth: 10,
20
- maxFiles: 200,
12
+ exclude: ["node_modules/**", "vendor/**", "dist/**"],
13
+ aliases: {
14
+ "@": "resources/js",
21
15
  },
22
- profiles: {},
16
+ depth: 2,
17
+ maxFiles: 200,
18
+ scopes: {},
23
19
  };
@@ -6,4 +6,5 @@ export interface LoadConfigResult {
6
6
  errors: string[];
7
7
  exists: boolean;
8
8
  }
9
+ export declare function validateConfig(config: any): string[];
9
10
  export declare function loadConfig(root: string): LoadConfigResult;
@@ -3,19 +3,277 @@ 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.validateConfig = validateConfig;
6
7
  exports.loadConfig = loadConfig;
7
8
  const fs_1 = __importDefault(require("fs"));
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const default_config_1 = require("./default-config");
10
11
  const migration_1 = require("./migration");
11
12
  const json_1 = require("./json");
13
+ function cloneDefaultConfig() {
14
+ return JSON.parse(JSON.stringify(default_config_1.DEFAULT_PRODEX_CONFIG));
15
+ }
16
+ function validateConfig(config) {
17
+ const errors = [];
18
+ if (!config || typeof config !== "object") {
19
+ errors.push("Configuration must be a JSON object.");
20
+ return errors;
21
+ }
22
+ if (config.version === undefined) {
23
+ errors.push("Config 'version' is required.");
24
+ }
25
+ else if (config.version !== 5) {
26
+ errors.push("Config 'version' must equal 5.");
27
+ }
28
+ if (config.output !== undefined) {
29
+ if (typeof config.output !== "object" || config.output === null || Array.isArray(config.output)) {
30
+ errors.push("Config 'output' must be an object.");
31
+ }
32
+ else {
33
+ const allowedOutputKeys = new Set(["dir", "versioned", "format"]);
34
+ for (const key of Object.keys(config.output)) {
35
+ if (!allowedOutputKeys.has(key)) {
36
+ errors.push(`Config 'output' contains unknown key '${key}'.`);
37
+ }
38
+ }
39
+ if (config.output.dir !== undefined && typeof config.output.dir !== "string") {
40
+ errors.push("Config 'output.dir' must be a string.");
41
+ }
42
+ if (config.output.versioned !== undefined && typeof config.output.versioned !== "boolean") {
43
+ errors.push("Config 'output.versioned' must be a boolean.");
44
+ }
45
+ if (config.output.format !== undefined && config.output.format !== "md" && config.output.format !== "txt") {
46
+ errors.push("Config 'output.format' must be either 'md' or 'txt'.");
47
+ }
48
+ }
49
+ }
50
+ if (config.exclude !== undefined) {
51
+ if (!Array.isArray(config.exclude)) {
52
+ errors.push("Config 'exclude' must be an array.");
53
+ }
54
+ else {
55
+ for (let i = 0; i < config.exclude.length; i++) {
56
+ if (typeof config.exclude[i] !== "string") {
57
+ errors.push(`Config 'exclude[${i}]' must be a string.`);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ if (config.aliases !== undefined) {
63
+ if (typeof config.aliases !== "object" || config.aliases === null || Array.isArray(config.aliases)) {
64
+ errors.push("Config 'aliases' must be an object.");
65
+ }
66
+ else {
67
+ for (const [key, val] of Object.entries(config.aliases)) {
68
+ if (typeof val !== "string") {
69
+ errors.push(`Config 'aliases.${key}' must be a string.`);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ if (config.depth !== undefined) {
75
+ if (typeof config.depth !== "number" || !Number.isInteger(config.depth) || config.depth < 0) {
76
+ errors.push("Config --depth must be an integer greater than or equal to 0.");
77
+ }
78
+ }
79
+ if (config.maxFiles !== undefined) {
80
+ if (typeof config.maxFiles !== "number" || !Number.isInteger(config.maxFiles) || config.maxFiles <= 0) {
81
+ errors.push("Config --max-files must be an integer greater than 0.");
82
+ }
83
+ }
84
+ if (config.scopes !== undefined) {
85
+ if (typeof config.scopes !== "object" || config.scopes === null || Array.isArray(config.scopes)) {
86
+ errors.push("Config 'scopes' must be an object.");
87
+ }
88
+ else {
89
+ for (const [scopeKey, scopeVal] of Object.entries(config.scopes)) {
90
+ const scope = scopeVal;
91
+ if (typeof scope !== "object" || scope === null || Array.isArray(scope)) {
92
+ errors.push(`Config 'scopes.${scopeKey}' must be an object.`);
93
+ }
94
+ else {
95
+ const allowedScopeKeys = new Set(["name", "entry", "include", "exclude", "grep"]);
96
+ for (const key of Object.keys(scope)) {
97
+ if (!allowedScopeKeys.has(key)) {
98
+ errors.push(`Config 'scopes.${scopeKey}' contains unknown key '${key}'.`);
99
+ }
100
+ }
101
+ if (scope.name !== undefined && typeof scope.name !== "string") {
102
+ errors.push(`Config 'scopes.${scopeKey}.name' must be a string.`);
103
+ }
104
+ if (scope.entry !== undefined) {
105
+ if (!Array.isArray(scope.entry)) {
106
+ errors.push(`Config 'scopes.${scopeKey}.entry' must be an array.`);
107
+ }
108
+ else {
109
+ for (let i = 0; i < scope.entry.length; i++) {
110
+ if (typeof scope.entry[i] !== "string") {
111
+ errors.push(`Config 'scopes.${scopeKey}.entry[${i}]' must be a string.`);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ if (scope.include !== undefined) {
117
+ if (!Array.isArray(scope.include)) {
118
+ errors.push(`Config 'scopes.${scopeKey}.include' must be an array.`);
119
+ }
120
+ else {
121
+ for (let i = 0; i < scope.include.length; i++) {
122
+ if (typeof scope.include[i] !== "string") {
123
+ errors.push(`Config 'scopes.${scopeKey}.include[${i}]' must be a string.`);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ if (scope.exclude !== undefined) {
129
+ if (!Array.isArray(scope.exclude)) {
130
+ errors.push(`Config 'scopes.${scopeKey}.exclude' must be an array.`);
131
+ }
132
+ else {
133
+ for (let i = 0; i < scope.exclude.length; i++) {
134
+ if (typeof scope.exclude[i] !== "string") {
135
+ errors.push(`Config 'scopes.${scopeKey}.exclude[${i}]' must be a string.`);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ if (scope.grep !== undefined) {
141
+ if (typeof scope.grep !== "object" || scope.grep === null || Array.isArray(scope.grep)) {
142
+ errors.push(`Config 'scopes.${scopeKey}.grep' must be an object.`);
143
+ }
144
+ else {
145
+ const allowedGrepKeys = new Set(["query", "any", "all", "regex", "not", "within", "skip"]);
146
+ for (const key of Object.keys(scope.grep)) {
147
+ if (!allowedGrepKeys.has(key)) {
148
+ errors.push(`Config 'scopes.${scopeKey}.grep' contains unknown key '${key}'.`);
149
+ }
150
+ }
151
+ if (scope.entry !== undefined) {
152
+ errors.push(`Scope "${scopeKey}" cannot define both "entry" and "grep".`);
153
+ }
154
+ const searchModes = ["query", "any", "all", "regex"];
155
+ const definedModes = searchModes.filter(m => scope.grep[m] !== undefined);
156
+ if (definedModes.length !== 1) {
157
+ errors.push(`Scope "${scopeKey}" grep config must define exactly one of "query", "any", "all", or "regex".`);
158
+ }
159
+ if (scope.grep.query !== undefined) {
160
+ if (typeof scope.grep.query !== "string") {
161
+ errors.push(`Config 'scopes.${scopeKey}.grep.query' must be a string.`);
162
+ }
163
+ else if (!scope.grep.query.trim()) {
164
+ errors.push(`Config 'scopes.${scopeKey}.grep.query' cannot be blank.`);
165
+ }
166
+ }
167
+ if (scope.grep.regex !== undefined) {
168
+ if (typeof scope.grep.regex !== "string") {
169
+ errors.push(`Config 'scopes.${scopeKey}.grep.regex' must be a string.`);
170
+ }
171
+ else if (!scope.grep.regex.trim()) {
172
+ errors.push(`Config 'scopes.${scopeKey}.grep.regex' cannot be blank.`);
173
+ }
174
+ }
175
+ if (scope.grep.any !== undefined) {
176
+ if (!Array.isArray(scope.grep.any)) {
177
+ errors.push(`Config 'scopes.${scopeKey}.grep.any' must be an array.`);
178
+ }
179
+ else if (scope.grep.any.length === 0) {
180
+ errors.push(`Config 'scopes.${scopeKey}.grep.any' must contain at least one item.`);
181
+ }
182
+ else {
183
+ for (let i = 0; i < scope.grep.any.length; i++) {
184
+ if (typeof scope.grep.any[i] !== "string") {
185
+ errors.push(`Config 'scopes.${scopeKey}.grep.any[${i}]' must be a string.`);
186
+ }
187
+ else if (!scope.grep.any[i].trim()) {
188
+ errors.push(`Config 'scopes.${scopeKey}.grep.any[${i}]' cannot be blank.`);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ if (scope.grep.all !== undefined) {
194
+ if (!Array.isArray(scope.grep.all)) {
195
+ errors.push(`Config 'scopes.${scopeKey}.grep.all' must be an array.`);
196
+ }
197
+ else if (scope.grep.all.length === 0) {
198
+ errors.push(`Config 'scopes.${scopeKey}.grep.all' must contain at least one item.`);
199
+ }
200
+ else {
201
+ for (let i = 0; i < scope.grep.all.length; i++) {
202
+ if (typeof scope.grep.all[i] !== "string") {
203
+ errors.push(`Config 'scopes.${scopeKey}.grep.all[${i}]' must be a string.`);
204
+ }
205
+ else if (!scope.grep.all[i].trim()) {
206
+ errors.push(`Config 'scopes.${scopeKey}.grep.all[${i}]' cannot be blank.`);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ if (scope.grep.not !== undefined) {
212
+ if (!Array.isArray(scope.grep.not)) {
213
+ errors.push(`Config 'scopes.${scopeKey}.grep.not' must be an array.`);
214
+ }
215
+ else {
216
+ for (let i = 0; i < scope.grep.not.length; i++) {
217
+ if (typeof scope.grep.not[i] !== "string") {
218
+ errors.push(`Config 'scopes.${scopeKey}.grep.not[${i}]' must be a string.`);
219
+ }
220
+ else if (!scope.grep.not[i].trim()) {
221
+ errors.push(`Config 'scopes.${scopeKey}.grep.not[${i}]' cannot be blank.`);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ if (scope.grep.within !== undefined) {
227
+ if (!Array.isArray(scope.grep.within)) {
228
+ errors.push(`Config 'scopes.${scopeKey}.grep.within' must be an array.`);
229
+ }
230
+ else {
231
+ for (let i = 0; i < scope.grep.within.length; i++) {
232
+ if (typeof scope.grep.within[i] !== "string") {
233
+ errors.push(`Config 'scopes.${scopeKey}.grep.within[${i}]' must be a string.`);
234
+ }
235
+ else if (!scope.grep.within[i].trim()) {
236
+ errors.push(`Config 'scopes.${scopeKey}.grep.within[${i}]' cannot be blank.`);
237
+ }
238
+ }
239
+ }
240
+ }
241
+ if (scope.grep.skip !== undefined) {
242
+ if (!Array.isArray(scope.grep.skip)) {
243
+ errors.push(`Config 'scopes.${scopeKey}.grep.skip' must be an array.`);
244
+ }
245
+ else {
246
+ for (let i = 0; i < scope.grep.skip.length; i++) {
247
+ if (typeof scope.grep.skip[i] !== "string") {
248
+ errors.push(`Config 'scopes.${scopeKey}.grep.skip[${i}]' must be a string.`);
249
+ }
250
+ else if (!scope.grep.skip[i].trim()) {
251
+ errors.push(`Config 'scopes.${scopeKey}.grep.skip[${i}]' cannot be blank.`);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+ const allowedRootKeys = new Set(["$schema", "version", "output", "exclude", "aliases", "depth", "maxFiles", "scopes"]);
263
+ for (const key of Object.keys(config)) {
264
+ if (!allowedRootKeys.has(key)) {
265
+ errors.push(`Config contains unknown root key '${key}'.`);
266
+ }
267
+ }
268
+ return errors;
269
+ }
12
270
  function loadConfig(root) {
13
271
  const configPath = path_1.default.join(root, "prodex.json");
14
272
  const warnings = [];
15
273
  const errors = [];
16
274
  if (!fs_1.default.existsSync(configPath)) {
17
275
  return {
18
- config: default_config_1.DEFAULT_PRODEX_CONFIG,
276
+ config: cloneDefaultConfig(),
19
277
  path: configPath,
20
278
  warnings,
21
279
  errors,
@@ -24,11 +282,18 @@ function loadConfig(root) {
24
282
  }
25
283
  try {
26
284
  const parsed = (0, json_1.parseJsonFile)(fs_1.default.readFileSync(configPath, "utf8"));
27
- if ((0, migration_1.requiresConfigMigration)(parsed)) {
285
+ if ((0, migration_1.isFutureConfig)(parsed)) {
286
+ errors.push(`prodex.json uses future config version ${parsed.version}, but this Prodex version only supports up to config version ${migration_1.REQUIRED_CONFIG_VERSION}.`);
287
+ }
288
+ else if ((0, migration_1.requiresConfigMigration)(parsed)) {
28
289
  errors.push((0, migration_1.isOutdatedConfig)(parsed) ? (0, migration_1.configVersionError)(parsed.version) : (0, migration_1.legacyConfigShapeError)());
29
290
  }
291
+ else {
292
+ const validationErrors = validateConfig(parsed);
293
+ errors.push(...validationErrors);
294
+ }
30
295
  return {
31
- config: parsed,
296
+ config: errors.length > 0 ? cloneDefaultConfig() : parsed,
32
297
  path: configPath,
33
298
  warnings,
34
299
  errors,
@@ -38,7 +303,7 @@ function loadConfig(root) {
38
303
  catch (err) {
39
304
  errors.push(`Invalid prodex.json: ${err?.message || err}`);
40
305
  return {
41
- config: default_config_1.DEFAULT_PRODEX_CONFIG,
306
+ config: cloneDefaultConfig(),
42
307
  path: configPath,
43
308
  warnings,
44
309
  errors,
@@ -1,4 +1,5 @@
1
- export declare const REQUIRED_CONFIG_VERSION = 4;
1
+ export declare const REQUIRED_CONFIG_VERSION = 5;
2
2
  export declare function isOutdatedConfig(config: any): boolean;
3
+ export declare function isFutureConfig(config: any): boolean;
3
4
  export declare function requiresConfigMigration(config: any): boolean;
4
5
  export declare function looksLikeLegacyConfig(config: any): boolean;
@@ -2,17 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.REQUIRED_CONFIG_VERSION = void 0;
4
4
  exports.isOutdatedConfig = isOutdatedConfig;
5
+ exports.isFutureConfig = isFutureConfig;
5
6
  exports.requiresConfigMigration = requiresConfigMigration;
6
7
  exports.looksLikeLegacyConfig = looksLikeLegacyConfig;
7
- exports.REQUIRED_CONFIG_VERSION = 4;
8
+ exports.REQUIRED_CONFIG_VERSION = 5;
8
9
  function isOutdatedConfig(config) {
9
10
  return typeof config?.version === "number" && config.version < exports.REQUIRED_CONFIG_VERSION;
10
11
  }
12
+ function isFutureConfig(config) {
13
+ return typeof config?.version === "number" && config.version > exports.REQUIRED_CONFIG_VERSION;
14
+ }
11
15
  function requiresConfigMigration(config) {
12
16
  return isOutdatedConfig(config) || looksLikeLegacyConfig(config);
13
17
  }
14
18
  function looksLikeLegacyConfig(config) {
15
- return !!(config?.shortcuts ||
19
+ return !!(config?.profiles ||
20
+ config?.resolve ||
21
+ config?.shortcuts ||
22
+ config?.entry !== undefined ||
23
+ config?.include !== undefined ||
16
24
  config?.entry?.files ||
17
25
  config?.resolve?.include ||
18
26
  config?.resolve?.exclude ||