prodex 2.0.1 → 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 +367 -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 -37
  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 -8
  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 +35 -23
  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 +37 -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
@@ -3,14 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.configVersionError = configVersionError;
4
4
  exports.legacyConfigShapeError = legacyConfigShapeError;
5
5
  const detect_1 = require("./detect");
6
- const V4_CHANGES = [
7
- "Prodex v4 changed the config shape:",
8
- " entry.files -> entry",
9
- " resolve.include -> include",
10
- " resolve.exclude -> exclude",
11
- " resolve.depth -> resolve.maxDepth",
12
- " resolve.limit -> resolve.maxFiles",
13
- " shortcuts -> profiles",
6
+ const V5_CHANGES = [
7
+ "Prodex v5 changed the config shape:",
8
+ " entry -> scopes.default.entry",
9
+ " include -> scopes.default.include",
10
+ " profiles -> scopes",
11
+ " profiles.*.name -> scopes.*.name",
12
+ " resolve.aliases -> aliases",
13
+ " resolve.maxDepth -> depth",
14
+ " resolve.maxFiles -> maxFiles",
14
15
  "",
15
16
  "Preview migration:",
16
17
  " prodex migrate",
@@ -23,13 +24,13 @@ function configVersionError(version) {
23
24
  return [
24
25
  `prodex.json uses config version ${label}, but this Prodex version requires config version ${detect_1.REQUIRED_CONFIG_VERSION}.`,
25
26
  "",
26
- ...V4_CHANGES,
27
+ ...V5_CHANGES,
27
28
  ].join("\n");
28
29
  }
29
30
  function legacyConfigShapeError() {
30
31
  return [
31
- "prodex.json contains legacy config fields that must be migrated to config version 4.",
32
+ "prodex.json contains legacy config fields that must be migrated to config version 5.",
32
33
  "",
33
- ...V4_CHANGES,
34
+ ...V5_CHANGES,
34
35
  ].join("\n");
35
36
  }
@@ -15,41 +15,86 @@ function migrateConfig(input) {
15
15
  };
16
16
  }
17
17
  const changes = [];
18
+ const inputOutput = input.output ?? {};
18
19
  const output = {
19
- ...default_config_1.DEFAULT_PRODEX_CONFIG.output,
20
- ...(input.output ?? {}),
20
+ dir: inputOutput.dir !== undefined ? String(inputOutput.dir) : default_config_1.DEFAULT_PRODEX_CONFIG.output.dir,
21
+ versioned: inputOutput.versioned !== undefined ? Boolean(inputOutput.versioned) : default_config_1.DEFAULT_PRODEX_CONFIG.output.versioned,
22
+ format: (inputOutput.format === "md" || inputOutput.format === "txt") ? inputOutput.format : default_config_1.DEFAULT_PRODEX_CONFIG.output.format,
21
23
  };
22
- delete output.prefix;
23
24
  if (input.output?.prefix !== undefined) {
24
- changes.push("output.prefix removed; use --name or profile.name when you need to override the profile key.");
25
+ changes.push("output.prefix removed; use scope.name when you need to override scope output names.");
26
+ }
27
+ const aliases = input.aliases ?? input.resolve?.aliases ?? default_config_1.DEFAULT_PRODEX_CONFIG.aliases;
28
+ if (input.resolve?.aliases !== undefined)
29
+ changes.push("resolve.aliases -> aliases");
30
+ const depth = input.depth ?? input.resolve?.maxDepth ?? input.resolve?.depth ?? default_config_1.DEFAULT_PRODEX_CONFIG.depth;
31
+ if (input.resolve?.maxDepth !== undefined || input.resolve?.depth !== undefined)
32
+ changes.push("resolve.maxDepth -> depth");
33
+ const maxFiles = input.maxFiles ?? input.resolve?.maxFiles ?? input.resolve?.limit ?? default_config_1.DEFAULT_PRODEX_CONFIG.maxFiles;
34
+ if (input.resolve?.maxFiles !== undefined || input.resolve?.limit !== undefined)
35
+ changes.push("resolve.maxFiles -> maxFiles");
36
+ const exclude = (0, string_list_1.toStringList)(input.exclude ?? input.resolve?.exclude ?? default_config_1.DEFAULT_PRODEX_CONFIG.exclude);
37
+ if (input.resolve?.exclude !== undefined)
38
+ changes.push("resolve.exclude -> exclude");
39
+ if (input.profiles !== undefined) {
40
+ changes.push("profiles -> scopes");
41
+ let hasProfileName = false;
42
+ for (const key of Object.keys(input.profiles)) {
43
+ if (input.profiles[key]?.name !== undefined) {
44
+ hasProfileName = true;
45
+ break;
46
+ }
47
+ }
48
+ if (hasProfileName) {
49
+ changes.push("profiles.*.name -> scopes.*.name");
50
+ }
51
+ }
52
+ if (input.shortcuts !== undefined)
53
+ changes.push("shortcuts -> scopes");
54
+ const scopes = migrateScopes(input.scopes ?? input.profiles ?? input.shortcuts ?? {}, changes);
55
+ const entryList = input.entry !== undefined ? (0, string_list_1.toStringList)(input.entry?.files ?? input.entry) : [];
56
+ const includeList = (input.include !== undefined || input.resolve?.include !== undefined)
57
+ ? (0, string_list_1.toStringList)(input.include ?? input.resolve?.include)
58
+ : [];
59
+ const hasRootEntry = entryList.length > 0;
60
+ const hasRootInclude = includeList.length > 0;
61
+ if (hasRootEntry || hasRootInclude) {
62
+ if (!scopes.default) {
63
+ scopes.default = {};
64
+ }
65
+ const defScope = scopes.default;
66
+ if (hasRootEntry) {
67
+ const existingEntry = defScope.entry ?? [];
68
+ defScope.entry = unique([...existingEntry, ...entryList]);
69
+ changes.push("entry -> scopes.default.entry");
70
+ }
71
+ if (hasRootInclude) {
72
+ const existingInclude = defScope.include ?? [];
73
+ defScope.include = unique([...existingInclude, ...includeList]);
74
+ changes.push("include -> scopes.default.include");
75
+ }
76
+ if (input.output?.prefix !== undefined && defScope.name === undefined) {
77
+ defScope.name = input.output.prefix;
78
+ }
79
+ }
80
+ else {
81
+ if (input.entry !== undefined) {
82
+ changes.push("entry -> scopes.default.entry");
83
+ }
84
+ if (input.include !== undefined || input.resolve?.include !== undefined) {
85
+ changes.push("include -> scopes.default.include");
86
+ }
25
87
  }
26
- const resolve = {
27
- aliases: input.resolve?.aliases ?? default_config_1.DEFAULT_PRODEX_CONFIG.resolve.aliases,
28
- maxDepth: input.resolve?.maxDepth ?? input.resolve?.depth ?? default_config_1.DEFAULT_PRODEX_CONFIG.resolve.maxDepth,
29
- maxFiles: input.resolve?.maxFiles ?? input.resolve?.limit ?? default_config_1.DEFAULT_PRODEX_CONFIG.resolve.maxFiles,
30
- };
31
88
  const config = {
32
89
  version: detect_1.REQUIRED_CONFIG_VERSION,
33
90
  $schema: input.$schema ?? default_config_1.DEFAULT_PRODEX_CONFIG.$schema,
34
91
  output,
35
- entry: (0, string_list_1.toStringList)(input.entry?.files ?? input.entry),
36
- include: (0, string_list_1.toStringList)(input.include ?? input.resolve?.include),
37
- exclude: (0, string_list_1.toStringList)(input.exclude ?? input.resolve?.exclude ?? default_config_1.DEFAULT_PRODEX_CONFIG.exclude),
38
- resolve,
39
- profiles: migrateProfiles(input.profiles ?? input.shortcuts ?? {}, changes),
92
+ exclude,
93
+ aliases,
94
+ depth,
95
+ maxFiles,
96
+ scopes,
40
97
  };
41
- if (input.entry?.files !== undefined)
42
- changes.push("entry.files -> entry");
43
- if (input.resolve?.include !== undefined)
44
- changes.push("resolve.include -> include");
45
- if (input.resolve?.exclude !== undefined)
46
- changes.push("resolve.exclude -> exclude");
47
- if (input.resolve?.depth !== undefined)
48
- changes.push("resolve.depth -> resolve.maxDepth");
49
- if (input.resolve?.limit !== undefined)
50
- changes.push("resolve.limit -> resolve.maxFiles");
51
- if (input.shortcuts !== undefined)
52
- changes.push("shortcuts -> profiles");
53
98
  return {
54
99
  needed: true,
55
100
  fromVersion: typeof input?.version === "number" ? input.version : undefined,
@@ -58,10 +103,10 @@ function migrateConfig(input) {
58
103
  config,
59
104
  };
60
105
  }
61
- function migrateProfiles(input, changes) {
62
- const profiles = {};
106
+ function migrateScopes(input, changes) {
107
+ const scopes = {};
63
108
  for (const [key, value] of Object.entries(input || {})) {
64
- profiles[key] = {
109
+ scopes[key] = {
65
110
  ...(value?.prefix ? { name: value.prefix } : {}),
66
111
  ...(value?.name ? { name: value.name } : {}),
67
112
  ...(value?.files || value?.entry ? { entry: (0, string_list_1.toStringList)(value.entry ?? value.files) } : {}),
@@ -69,11 +114,11 @@ function migrateProfiles(input, changes) {
69
114
  ...(value?.exclude ? { exclude: (0, string_list_1.toStringList)(value.exclude) } : {}),
70
115
  };
71
116
  if (value?.prefix !== undefined)
72
- changes.push("shortcuts.*.prefix -> profiles.*.name");
117
+ changes.push("shortcuts.*.prefix -> scopes.*.name");
73
118
  if (value?.files !== undefined)
74
- changes.push("shortcuts.*.files -> profiles.*.entry");
119
+ changes.push("shortcuts.*.files -> scopes.*.entry");
75
120
  }
76
- return profiles;
121
+ return scopes;
77
122
  }
78
123
  function unique(items) {
79
124
  return [...new Set(items)];
@@ -0,0 +1,2 @@
1
+ export declare function isBinaryFile(filePath: string): boolean;
2
+ export declare function isBinaryBuffer(buffer: Buffer): boolean;
@@ -0,0 +1,30 @@
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.isBinaryFile = isBinaryFile;
7
+ exports.isBinaryBuffer = isBinaryBuffer;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ function isBinaryFile(filePath) {
10
+ try {
11
+ const stat = fs_1.default.statSync(filePath);
12
+ if (!stat.isFile())
13
+ return false;
14
+ const fd = fs_1.default.openSync(filePath, "r");
15
+ const buffer = Buffer.alloc(1024);
16
+ const bytesRead = fs_1.default.readSync(fd, buffer, 0, 1024, 0);
17
+ fs_1.default.closeSync(fd);
18
+ for (let i = 0; i < bytesRead; i++) {
19
+ if (buffer[i] === 0)
20
+ return true;
21
+ }
22
+ return false;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ function isBinaryBuffer(buffer) {
29
+ return buffer.subarray(0, 1024).includes(0);
30
+ }
@@ -0,0 +1,14 @@
1
+ export declare const ENTRY_RESOLVE_EXTS: string[];
2
+ /**
3
+ * Perform exact/glob match search.
4
+ */
5
+ export declare function scanGlob(patterns: string[], root: string): Promise<string[]>;
6
+ /**
7
+ * For a given path-like input, generate possible extensionless path expansions.
8
+ * Returns only files that physically exist.
9
+ */
10
+ export declare function expandPathLike(pattern: string, root: string): string[];
11
+ /**
12
+ * Searches the workspace for files that match the bare name stem exactly.
13
+ */
14
+ export declare function discoverBareName(bareName: string, root: string, caseSensitive?: boolean): Promise<string[]>;
@@ -0,0 +1,78 @@
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.ENTRY_RESOLVE_EXTS = void 0;
7
+ exports.scanGlob = scanGlob;
8
+ exports.expandPathLike = expandPathLike;
9
+ exports.discoverBareName = discoverBareName;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const glob_scan_1 = require("./glob-scan");
13
+ exports.ENTRY_RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".php"];
14
+ /**
15
+ * Perform exact/glob match search.
16
+ */
17
+ async function scanGlob(patterns, root) {
18
+ const { files } = await (0, glob_scan_1.globScan)(patterns, { cwd: root });
19
+ return files;
20
+ }
21
+ /**
22
+ * For a given path-like input, generate possible extensionless path expansions.
23
+ * Returns only files that physically exist.
24
+ */
25
+ function expandPathLike(pattern, root) {
26
+ const candidates = [];
27
+ for (const ext of exports.ENTRY_RESOLVE_EXTS) {
28
+ candidates.push(path_1.default.resolve(root, pattern + ext));
29
+ candidates.push(path_1.default.resolve(root, pattern, "index" + ext));
30
+ }
31
+ return candidates.filter((candidate) => {
32
+ try {
33
+ const stat = fs_1.default.statSync(candidate);
34
+ return stat.isFile();
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ });
40
+ }
41
+ /**
42
+ * Searches the workspace for files that match the bare name stem exactly.
43
+ */
44
+ async function discoverBareName(bareName, root, caseSensitive = false) {
45
+ const searchPatterns = [
46
+ `**/${bareName}`,
47
+ ...exports.ENTRY_RESOLVE_EXTS.map((ext) => `**/${bareName}${ext}`),
48
+ ...exports.ENTRY_RESOLVE_EXTS.map((ext) => `**/${bareName}/index${ext}`),
49
+ ];
50
+ const { files } = await (0, glob_scan_1.globScan)(searchPatterns, {
51
+ cwd: root,
52
+ caseSensitiveMatch: caseSensitive,
53
+ });
54
+ const resolvedMatches = new Set();
55
+ const targetStem = caseSensitive ? bareName : bareName.toLowerCase();
56
+ for (const file of files) {
57
+ const absFile = path_1.default.resolve(file);
58
+ const ext = path_1.default.extname(absFile);
59
+ const extLower = ext.toLowerCase();
60
+ // Ensure extension is empty or is one of the supported extensions
61
+ if (extLower !== "" && !exports.ENTRY_RESOLVE_EXTS.includes(extLower)) {
62
+ continue;
63
+ }
64
+ const stemRaw = path_1.default.basename(absFile, ext);
65
+ const stem = caseSensitive ? stemRaw : stemRaw.toLowerCase();
66
+ if (stem === targetStem) {
67
+ resolvedMatches.add(absFile);
68
+ }
69
+ else if (stem === "index") {
70
+ const parentDirRaw = path_1.default.basename(path_1.default.dirname(absFile));
71
+ const parentDir = caseSensitive ? parentDirRaw : parentDirRaw.toLowerCase();
72
+ if (parentDir === targetStem) {
73
+ resolvedMatches.add(absFile);
74
+ }
75
+ }
76
+ }
77
+ return Array.from(resolvedMatches);
78
+ }
@@ -0,0 +1,7 @@
1
+ export interface BuildFinalFileSetParams {
2
+ root: string;
3
+ sources: string[];
4
+ include: string[];
5
+ exclude: string[];
6
+ }
7
+ export declare function buildFinalFileSet(params: BuildFinalFileSetParams): Promise<string[]>;
@@ -0,0 +1,57 @@
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.buildFinalFileSet = buildFinalFileSet;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const glob_scan_1 = require("./glob-scan");
10
+ const path_2 = require("./path");
11
+ const exclude_1 = require("../tracing/exclude");
12
+ async function buildFinalFileSet(params) {
13
+ const { root, sources, include, exclude } = params;
14
+ const absoluteFiles = new Set();
15
+ // 1. Start with selected source files
16
+ for (const src of sources) {
17
+ absoluteFiles.add((0, path_2.normalizePath)(path_1.default.resolve(root, src)));
18
+ }
19
+ // 2. Resolve and add include path/glob inputs
20
+ const patterns = [];
21
+ for (const inc of include) {
22
+ const candidate = String(inc ?? "").trim();
23
+ if (!candidate)
24
+ continue;
25
+ const normalized = (0, path_2.normalizePath)(candidate);
26
+ if (path_1.default.isAbsolute(normalized)) {
27
+ try {
28
+ if (fs_1.default.statSync(normalized).isFile()) {
29
+ absoluteFiles.add((0, path_2.normalizePath)(path_1.default.resolve(normalized)));
30
+ continue;
31
+ }
32
+ }
33
+ catch {
34
+ // Treat unreadable absolute paths as glob patterns
35
+ }
36
+ }
37
+ patterns.push(normalized);
38
+ }
39
+ if (patterns.length > 0) {
40
+ const { files: scanFiles } = await (0, glob_scan_1.globScan)(patterns, { cwd: root });
41
+ for (const f of scanFiles) {
42
+ absoluteFiles.add((0, path_2.normalizePath)(f));
43
+ }
44
+ }
45
+ // 3. Deduplicate (Set takes care of this)
46
+ let list = Array.from(absoluteFiles);
47
+ // 4. Apply exclude path/glob inputs
48
+ list = list.filter((p) => !(0, exclude_1.isExcluded)(p, exclude, root));
49
+ // 5. Sort deterministically by root-relative normalized path
50
+ list.sort((a, b) => {
51
+ const relA = (0, path_2.normalizePath)(path_1.default.relative(root, a));
52
+ const relB = (0, path_2.normalizePath)(path_1.default.relative(root, b));
53
+ return relA.localeCompare(relB);
54
+ });
55
+ // 6. Return absolute paths
56
+ return list;
57
+ }
@@ -1,4 +1,8 @@
1
- import { Options } from "fast-glob";
2
- export declare function globScan(patterns: string[], opts: Options): Promise<{
1
+ export interface GlobScanOptions {
2
+ cwd?: string;
3
+ caseSensitiveMatch?: boolean;
4
+ ignore?: string[];
5
+ }
6
+ export declare function globScan(patterns: string[], opts?: GlobScanOptions): Promise<{
3
7
  files: string[];
4
8
  }>;
@@ -8,19 +8,24 @@ const path_1 = __importDefault(require("path"));
8
8
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
9
  const logger_1 = require("../diagnostics/logger");
10
10
  const inspect_1 = require("./inspect");
11
- const GLOBAL_IGNORE = ["**/node_modules/**", "**/vendor/**", "**/dist/**"];
12
- async function globScan(patterns, opts) {
13
- const { absolute = true, cwd = process.cwd() } = opts;
11
+ const path_2 = require("./path");
12
+ async function globScan(patterns, opts = {}) {
13
+ const { cwd = process.cwd(), caseSensitiveMatch, ignore } = opts;
14
14
  if (!patterns?.length)
15
15
  return { files: [] };
16
- const files = (await (0, fast_glob_1.default)(patterns, {
16
+ const rawFiles = await (0, fast_glob_1.default)(patterns, {
17
17
  cwd,
18
18
  extglob: true,
19
19
  dot: true,
20
20
  onlyFiles: true,
21
- ignore: GLOBAL_IGNORE,
22
- absolute,
23
- })).map((file) => path_1.default.resolve(file));
21
+ ignore,
22
+ absolute: true,
23
+ caseSensitiveMatch,
24
+ });
25
+ // Slash normalization & absolute path resolution
26
+ const files = rawFiles.map((file) => (0, path_2.normalizePath)(path_1.default.resolve(cwd, file)));
27
+ // Deterministic file sorting
28
+ files.sort();
24
29
  logger_1.logger.debug("globScan ->", (0, inspect_1.inspectValue)(files));
25
30
  return { files };
26
31
  }
@@ -0,0 +1,20 @@
1
+ export declare function isGlobPattern(str: string): boolean;
2
+ export interface NormalizationOptions {
3
+ role?: "entry" | "include" | "exclude" | "skip" | "within";
4
+ }
5
+ /**
6
+ * Normalizes a path or glob pattern.
7
+ *
8
+ * For "exclude" or "skip" roles:
9
+ * - If the pattern is not a glob and does not exist as a file, and has no extension,
10
+ * it is assumed to be a directory shorthand and is expanded recursively (e.g., "dist" -> "dist/**").
11
+ * - Note: Ambiguous extensionless filenames (such as "README", "LICENSE", "Dockerfile", "Makefile")
12
+ * are also treated as directories and expanded recursively if they do not exist on disk.
13
+ *
14
+ * For "entry" or "include" roles:
15
+ * - Non-existing directory-like inputs are NOT expanded and are kept as literal paths to preserve strictness.
16
+ *
17
+ * For all roles:
18
+ * - Existing directories are always expanded recursively (e.g., "src" -> "src/**").
19
+ */
20
+ export declare function normalizePathOrGlob(pattern: string, root: string, options?: NormalizationOptions): string;
@@ -0,0 +1,70 @@
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.isGlobPattern = isGlobPattern;
7
+ exports.normalizePathOrGlob = normalizePathOrGlob;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const path_2 = require("./path");
11
+ function isGlobPattern(str) {
12
+ return str.includes("*") || str.includes("?") || str.includes("[") || str.includes("{");
13
+ }
14
+ /**
15
+ * Normalizes a path or glob pattern.
16
+ *
17
+ * For "exclude" or "skip" roles:
18
+ * - If the pattern is not a glob and does not exist as a file, and has no extension,
19
+ * it is assumed to be a directory shorthand and is expanded recursively (e.g., "dist" -> "dist/**").
20
+ * - Note: Ambiguous extensionless filenames (such as "README", "LICENSE", "Dockerfile", "Makefile")
21
+ * are also treated as directories and expanded recursively if they do not exist on disk.
22
+ *
23
+ * For "entry" or "include" roles:
24
+ * - Non-existing directory-like inputs are NOT expanded and are kept as literal paths to preserve strictness.
25
+ *
26
+ * For all roles:
27
+ * - Existing directories are always expanded recursively (e.g., "src" -> "src/**").
28
+ */
29
+ function normalizePathOrGlob(pattern, root, options) {
30
+ const trimmed = String(pattern ?? "").trim();
31
+ if (!trimmed)
32
+ return "";
33
+ // 1. If it has glob characters, return normalized pattern
34
+ if (isGlobPattern(trimmed)) {
35
+ return (0, path_2.normalizePath)(trimmed);
36
+ }
37
+ // 2. Resolve absolute path to check disk status
38
+ const absPath = path_1.default.resolve(root, trimmed);
39
+ let isDir = false;
40
+ let isFile = false;
41
+ try {
42
+ const stat = fs_1.default.statSync(absPath);
43
+ isDir = stat.isDirectory();
44
+ isFile = stat.isFile();
45
+ }
46
+ catch {
47
+ // Path does not exist or cannot be read
48
+ }
49
+ // 3. Existing directories are expanded recursively
50
+ if (isDir) {
51
+ const normalized = (0, path_2.normalizePath)(trimmed);
52
+ if (normalized.endsWith("/")) {
53
+ return normalized + "**";
54
+ }
55
+ return normalized + "/**";
56
+ }
57
+ // 4. Role-aware normalization for exclude/skip roles (Option B)
58
+ const role = options?.role;
59
+ if (role === "exclude" || role === "skip") {
60
+ if (!isFile) {
61
+ const normalized = (0, path_2.normalizePath)(trimmed);
62
+ const hasExtension = path_1.default.extname(trimmed) !== "";
63
+ // Expand if it ends with a slash or has no extension
64
+ if (normalized.endsWith("/") || !hasExtension) {
65
+ return normalized.endsWith("/") ? normalized + "**" : normalized + "/**";
66
+ }
67
+ }
68
+ }
69
+ return (0, path_2.normalizePath)(trimmed);
70
+ }
package/dist/index.js CHANGED
@@ -4,8 +4,11 @@ exports.default = startProdex;
4
4
  exports.runProdexCommand = runProdexCommand;
5
5
  const init_command_1 = require("./commands/init-command");
6
6
  const migrate_command_1 = require("./commands/migrate-command");
7
- const profiles_command_1 = require("./commands/profiles-command");
8
- const run_command_1 = require("./commands/run-command");
7
+ const pack_command_1 = require("./commands/pack-command");
8
+ const trace_command_1 = require("./commands/trace-command");
9
+ const scope_command_1 = require("./commands/scope-command");
10
+ const git_command_1 = require("./commands/git-command");
11
+ const grep_command_1 = require("./commands/grep-command");
9
12
  const cli_input_1 = require("./cli/cli-input");
10
13
  const reporter_1 = require("./cli/reporter");
11
14
  async function startProdex(args = process.argv) {
@@ -38,19 +41,6 @@ async function runProdexCommand(args = process.argv, cwd = process.cwd()) {
38
41
  runs: [],
39
42
  };
40
43
  }
41
- if (parsed.command.kind === "profiles") {
42
- const listed = (0, profiles_command_1.profilesCommand)(parsed.command.rootArg, cwd);
43
- warnings.push(...listed.warnings);
44
- errors.push(...listed.errors);
45
- return {
46
- ok: !errors.length,
47
- exitCode: errors.length ? 1 : 0,
48
- profiles: errors.length ? undefined : listed.profiles,
49
- warnings,
50
- errors,
51
- runs: [],
52
- };
53
- }
54
44
  if (parsed.command.kind === "migrate") {
55
45
  const migration = (0, migrate_command_1.migrateCommand)({
56
46
  rootArg: parsed.command.rootArg,
@@ -67,21 +57,53 @@ async function runProdexCommand(args = process.argv, cwd = process.cwd()) {
67
57
  runs: [],
68
58
  };
69
59
  }
70
- const run = await (0, run_command_1.runCommand)({
71
- rootArg: parsed.command.rootArg,
72
- flags: parsed.command.flags,
73
- cwd,
74
- });
75
- warnings.push(...run.warnings);
76
- errors.push(...run.errors);
77
- if (errors.length)
78
- return { ok: false, exitCode: 1, warnings, errors, runs: [] };
79
- const ok = run.runs.every((item) => item.ok);
60
+ const kind = parsed.command.kind;
61
+ if (kind === "pack" || kind === "trace" || kind === "scope" || kind === "git" || kind === "grep") {
62
+ let cmdRes;
63
+ const params = { rootArg: parsed.command.rootArg, flags: parsed.command.flags, cwd };
64
+ if (kind === "pack") {
65
+ cmdRes = await (0, pack_command_1.packCommand)(params);
66
+ }
67
+ else if (kind === "trace") {
68
+ cmdRes = await (0, trace_command_1.traceCommand)(params);
69
+ }
70
+ else if (kind === "scope") {
71
+ cmdRes = await (0, scope_command_1.scopeCommand)(params);
72
+ }
73
+ else if (kind === "git") {
74
+ cmdRes = await (0, git_command_1.gitCommand)(params);
75
+ }
76
+ else {
77
+ cmdRes = await (0, grep_command_1.grepCommand)(params);
78
+ }
79
+ warnings.push(...cmdRes.warnings);
80
+ errors.push(...cmdRes.errors);
81
+ if (errors.length)
82
+ return { ok: false, exitCode: 1, warnings, errors, runs: [] };
83
+ if (kind === "scope" && cmdRes.scopes) {
84
+ return {
85
+ ok: true,
86
+ exitCode: 0,
87
+ scopes: cmdRes.scopes,
88
+ warnings,
89
+ errors,
90
+ runs: [],
91
+ };
92
+ }
93
+ const ok = cmdRes.runs.every((item) => item.ok);
94
+ return {
95
+ ok,
96
+ exitCode: ok ? 0 : 1,
97
+ warnings,
98
+ errors,
99
+ runs: cmdRes.runs,
100
+ };
101
+ }
80
102
  return {
81
- ok,
82
- exitCode: ok ? 0 : 1,
103
+ ok: false,
104
+ exitCode: 1,
83
105
  warnings,
84
- errors,
85
- runs: run.runs,
106
+ errors: [...errors, "Unknown command kind"],
107
+ runs: [],
86
108
  };
87
109
  }
@@ -0,0 +1,5 @@
1
+ import type { ArtifactSection, CommandOutputResult, FileSnapshot } from "../types";
2
+ export declare function getDynamicFence(output: string): string;
3
+ export declare function renderFileSection(file: FileSnapshot, index: number, root: string, navLine: string): string;
4
+ export declare function renderGenericSection(sec: ArtifactSection, index: number, navLine: string): string;
5
+ export declare function renderMdCmdResults(cmdResults?: CommandOutputResult[], root?: string, totalCount?: number, sectionCount?: number, orderedAnchors?: string[]): string;