@lumy-pack/line-lore 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -71,10 +71,11 @@ var init_errors = __esm({
71
71
 
72
72
  // src/git/executor.ts
73
73
  import { execa } from "execa";
74
- async function gitExec(args, options) {
74
+ async function execCommand(command, args, options, errorCode) {
75
75
  const { cwd, timeout, allowExitCodes = [] } = options ?? {};
76
+ const failCode = errorCode ?? LineLoreErrorCode.GIT_COMMAND_FAILED;
76
77
  try {
77
- const result = await execa("git", args, {
78
+ const result = await execa(command, args, {
78
79
  cwd,
79
80
  timeout,
80
81
  reject: false
@@ -82,9 +83,9 @@ async function gitExec(args, options) {
82
83
  const exitCode = result.exitCode ?? 0;
83
84
  if (exitCode !== 0 && !allowExitCodes.includes(exitCode)) {
84
85
  throw new LineLoreError(
85
- LineLoreErrorCode.GIT_COMMAND_FAILED,
86
- `git ${args[0]} failed with exit code ${exitCode}: ${result.stderr}`,
87
- { args, exitCode, stderr: result.stderr, cwd }
86
+ failCode,
87
+ `${command} ${args[0]} failed with exit code ${exitCode}: ${result.stderr}`,
88
+ { command, args, exitCode, stderr: result.stderr, cwd }
88
89
  );
89
90
  }
90
91
  return {
@@ -98,17 +99,33 @@ async function gitExec(args, options) {
98
99
  if (isTimeout) {
99
100
  throw new LineLoreError(
100
101
  LineLoreErrorCode.GIT_TIMEOUT,
101
- `git ${args[0]} timed out after ${timeout}ms`,
102
- { args, timeout, cwd }
102
+ `${command} ${args[0]} timed out after ${timeout}ms`,
103
+ { command, args, timeout, cwd }
103
104
  );
104
105
  }
105
106
  throw new LineLoreError(
106
- LineLoreErrorCode.GIT_COMMAND_FAILED,
107
- `git ${args[0]} failed: ${error instanceof Error ? error.message : String(error)}`,
108
- { args, cwd }
107
+ failCode,
108
+ `${command} ${args[0]} failed: ${error instanceof Error ? error.message : String(error)}`,
109
+ { command, args, cwd }
109
110
  );
110
111
  }
111
112
  }
113
+ async function gitExec(args, options) {
114
+ return execCommand(
115
+ "git",
116
+ args,
117
+ options,
118
+ LineLoreErrorCode.GIT_COMMAND_FAILED
119
+ );
120
+ }
121
+ async function shellExec(command, args, options) {
122
+ return execCommand(
123
+ command,
124
+ args,
125
+ options,
126
+ LineLoreErrorCode.API_REQUEST_FAILED
127
+ );
128
+ }
112
129
  var init_executor = __esm({
113
130
  "src/git/executor.ts"() {
114
131
  "use strict";
@@ -294,9 +311,8 @@ async function computePatchId(commitSha, options) {
294
311
  const cached = await cache.get(commitSha);
295
312
  if (cached) return cached;
296
313
  try {
297
- const { execa: execaFn } = await import("execa");
298
314
  const cwd = options?.cwd ?? ".";
299
- const result = await execaFn(
315
+ const result = await shellExec(
300
316
  "bash",
301
317
  [
302
318
  "-c",
@@ -798,17 +814,16 @@ var GitHubAdapter = class {
798
814
  }
799
815
  async checkAuth() {
800
816
  try {
801
- const result = await gitExec(
802
- ["gh", "auth", "status", "--hostname", this.hostname].slice(1),
817
+ const result = await shellExec(
818
+ "gh",
819
+ ["auth", "token", "--hostname", this.hostname],
803
820
  {
804
821
  allowExitCodes: [1]
805
822
  }
806
823
  );
807
- const output = result.stdout + result.stderr;
808
- const usernameMatch = /Logged in to .+ as (\S+)/.exec(output);
824
+ const hasToken = result.exitCode === 0 && result.stdout.trim().length > 0;
809
825
  return {
810
- authenticated: result.exitCode === 0,
811
- username: usernameMatch?.[1],
826
+ authenticated: hasToken,
812
827
  hostname: this.hostname
813
828
  };
814
829
  } catch {
@@ -818,17 +833,14 @@ var GitHubAdapter = class {
818
833
  async getPRForCommit(sha) {
819
834
  if (this.scheduler.isRateLimited()) return null;
820
835
  try {
821
- const result = await gitExec(
822
- [
823
- "gh",
824
- "api",
825
- `repos/{owner}/{repo}/commits/${sha}/pulls`,
826
- "--hostname",
827
- this.hostname,
828
- "--jq",
829
- ".[0] | {number, title, user: .user.login, html_url, merge_commit_sha, base: .base.ref, merged_at}"
830
- ].slice(1)
831
- );
836
+ const result = await shellExec("gh", [
837
+ "api",
838
+ `repos/{owner}/{repo}/commits/${sha}/pulls`,
839
+ "--hostname",
840
+ this.hostname,
841
+ "--jq",
842
+ ".[0] | {number, title, user: .user.login, html_url, merge_commit_sha, base: .base.ref, merged_at}"
843
+ ]);
832
844
  const data = JSON.parse(result.stdout);
833
845
  if (!data?.number) return null;
834
846
  return {
@@ -846,17 +858,14 @@ var GitHubAdapter = class {
846
858
  }
847
859
  async getPRCommits(prNumber) {
848
860
  try {
849
- const result = await gitExec(
850
- [
851
- "gh",
852
- "api",
853
- `repos/{owner}/{repo}/pulls/${prNumber}/commits`,
854
- "--hostname",
855
- this.hostname,
856
- "--jq",
857
- ".[].sha"
858
- ].slice(1)
859
- );
861
+ const result = await shellExec("gh", [
862
+ "api",
863
+ `repos/{owner}/{repo}/pulls/${prNumber}/commits`,
864
+ "--hostname",
865
+ this.hostname,
866
+ "--jq",
867
+ ".[].sha"
868
+ ]);
860
869
  return result.stdout.trim().split("\n").filter(Boolean);
861
870
  } catch {
862
871
  return [];
@@ -864,19 +873,16 @@ var GitHubAdapter = class {
864
873
  }
865
874
  async getLinkedIssues(prNumber) {
866
875
  try {
867
- const result = await gitExec(
868
- [
869
- "gh",
870
- "api",
871
- "graphql",
872
- "--hostname",
873
- this.hostname,
874
- "-f",
875
- `query=query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: ${prNumber}) { closingIssuesReferences(first: 10) { nodes { number title url state labels(first: 5) { nodes { name } } } } } } }`,
876
- "--jq",
877
- ".data.repository.pullRequest.closingIssuesReferences.nodes"
878
- ].slice(1)
879
- );
876
+ const result = await shellExec("gh", [
877
+ "api",
878
+ "graphql",
879
+ "--hostname",
880
+ this.hostname,
881
+ "-f",
882
+ `query=query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: ${prNumber}) { closingIssuesReferences(first: 10) { nodes { number title url state labels(first: 5) { nodes { name } } } } } } }`,
883
+ "--jq",
884
+ ".data.repository.pullRequest.closingIssuesReferences.nodes"
885
+ ]);
880
886
  const nodes = JSON.parse(result.stdout);
881
887
  if (!Array.isArray(nodes)) return [];
882
888
  return nodes.map((node) => ({
@@ -892,17 +898,14 @@ var GitHubAdapter = class {
892
898
  }
893
899
  async getLinkedPRs(issueNumber) {
894
900
  try {
895
- const result = await gitExec(
896
- [
897
- "gh",
898
- "api",
899
- `repos/{owner}/{repo}/issues/${issueNumber}/timeline`,
900
- "--hostname",
901
- this.hostname,
902
- "--jq",
903
- '[.[] | select(.source.issue.pull_request) | .source.issue] | map({number, title, user: .user.login, html_url, merge_commit_sha: .pull_request.merge_commit_sha, base: "main", merged_at: .pull_request.merged_at})'
904
- ].slice(1)
905
- );
901
+ const result = await shellExec("gh", [
902
+ "api",
903
+ `repos/{owner}/{repo}/issues/${issueNumber}/timeline`,
904
+ "--hostname",
905
+ this.hostname,
906
+ "--jq",
907
+ '[.[] | select(.source.issue.pull_request) | .source.issue] | map({number, title, user: .user.login, html_url, merge_commit_sha: .pull_request.merge_commit_sha, base: "main", merged_at: .pull_request.merged_at})'
908
+ ]);
906
909
  const prs = JSON.parse(result.stdout);
907
910
  if (!Array.isArray(prs)) return [];
908
911
  return prs.map((pr) => ({
@@ -920,17 +923,14 @@ var GitHubAdapter = class {
920
923
  }
921
924
  async getRateLimit() {
922
925
  try {
923
- const result = await gitExec(
924
- [
925
- "gh",
926
- "api",
927
- "rate_limit",
928
- "--hostname",
929
- this.hostname,
930
- "--jq",
931
- ".rate | {limit, remaining, reset}"
932
- ].slice(1)
933
- );
926
+ const result = await shellExec("gh", [
927
+ "api",
928
+ "rate_limit",
929
+ "--hostname",
930
+ this.hostname,
931
+ "--jq",
932
+ ".rate | {limit, remaining, reset}"
933
+ ]);
934
934
  const data = JSON.parse(result.stdout);
935
935
  const info = {
936
936
  limit: data.limit ?? 5e3,
@@ -969,16 +969,17 @@ var GitLabAdapter = class {
969
969
  this.scheduler = options?.scheduler ?? new RequestScheduler();
970
970
  }
971
971
  async checkAuth() {
972
+ if (process.env.GITLAB_TOKEN) {
973
+ return { authenticated: true, hostname: this.hostname };
974
+ }
972
975
  try {
973
- const result = await gitExec(
974
- ["glab", "auth", "status", "--hostname", this.hostname].slice(1),
976
+ const result = await shellExec(
977
+ "glab",
978
+ ["auth", "status", "--hostname", this.hostname],
975
979
  { allowExitCodes: [1] }
976
980
  );
977
- const output = result.stdout + result.stderr;
978
- const usernameMatch = /Logged in to .+ as (\S+)/.exec(output);
979
981
  return {
980
982
  authenticated: result.exitCode === 0,
981
- username: usernameMatch?.[1],
982
983
  hostname: this.hostname
983
984
  };
984
985
  } catch {
@@ -988,15 +989,12 @@ var GitLabAdapter = class {
988
989
  async getPRForCommit(sha) {
989
990
  if (this.scheduler.isRateLimited()) return null;
990
991
  try {
991
- const result = await gitExec(
992
- [
993
- "glab",
994
- "api",
995
- `projects/:id/repository/commits/${sha}/merge_requests`,
996
- "--hostname",
997
- this.hostname
998
- ].slice(1)
999
- );
992
+ const result = await shellExec("glab", [
993
+ "api",
994
+ `projects/:id/repository/commits/${sha}/merge_requests`,
995
+ "--hostname",
996
+ this.hostname
997
+ ]);
1000
998
  const mrs = JSON.parse(result.stdout);
1001
999
  if (!Array.isArray(mrs) || mrs.length === 0) return null;
1002
1000
  const mr = mrs[0];
@@ -1015,15 +1013,12 @@ var GitLabAdapter = class {
1015
1013
  }
1016
1014
  async getPRCommits(prNumber) {
1017
1015
  try {
1018
- const result = await gitExec(
1019
- [
1020
- "glab",
1021
- "api",
1022
- `projects/:id/merge_requests/${prNumber}/commits`,
1023
- "--hostname",
1024
- this.hostname
1025
- ].slice(1)
1026
- );
1016
+ const result = await shellExec("glab", [
1017
+ "api",
1018
+ `projects/:id/merge_requests/${prNumber}/commits`,
1019
+ "--hostname",
1020
+ this.hostname
1021
+ ]);
1027
1022
  const commits = JSON.parse(result.stdout);
1028
1023
  if (!Array.isArray(commits)) return [];
1029
1024
  return commits.map((c) => c.id);
@@ -1033,15 +1028,12 @@ var GitLabAdapter = class {
1033
1028
  }
1034
1029
  async getLinkedIssues(prNumber) {
1035
1030
  try {
1036
- const result = await gitExec(
1037
- [
1038
- "glab",
1039
- "api",
1040
- `projects/:id/merge_requests/${prNumber}/closes_issues`,
1041
- "--hostname",
1042
- this.hostname
1043
- ].slice(1)
1044
- );
1031
+ const result = await shellExec("glab", [
1032
+ "api",
1033
+ `projects/:id/merge_requests/${prNumber}/closes_issues`,
1034
+ "--hostname",
1035
+ this.hostname
1036
+ ]);
1045
1037
  const issues = JSON.parse(result.stdout);
1046
1038
  if (!Array.isArray(issues)) return [];
1047
1039
  return issues.map((issue) => ({
@@ -1057,15 +1049,12 @@ var GitLabAdapter = class {
1057
1049
  }
1058
1050
  async getLinkedPRs(issueNumber) {
1059
1051
  try {
1060
- const result = await gitExec(
1061
- [
1062
- "glab",
1063
- "api",
1064
- `projects/:id/issues/${issueNumber}/related_merge_requests`,
1065
- "--hostname",
1066
- this.hostname
1067
- ].slice(1)
1068
- );
1052
+ const result = await shellExec("glab", [
1053
+ "api",
1054
+ `projects/:id/issues/${issueNumber}/related_merge_requests`,
1055
+ "--hostname",
1056
+ this.hostname
1057
+ ]);
1069
1058
  const mrs = JSON.parse(result.stdout);
1070
1059
  if (!Array.isArray(mrs)) return [];
1071
1060
  return mrs.map((mr) => ({
@@ -1655,18 +1644,20 @@ async function executeBlame(file, lineRange, options) {
1655
1644
  async function analyzeBlameResults(results, options) {
1656
1645
  const uniqueShas = [...new Set(results.map((r) => r.commitHash))];
1657
1646
  const cosmeticMap = /* @__PURE__ */ new Map();
1658
- for (const sha of uniqueShas) {
1659
- if (sha === "0".repeat(40)) continue;
1660
- try {
1661
- const blameResult = results.find((r) => r.commitHash === sha);
1662
- if (!blameResult) continue;
1663
- const file = blameResult.originalFile ?? results.find((r) => r.commitHash === sha)?.lineContent;
1664
- const diff = await getCosmeticDiff(sha, file ?? "", options);
1665
- cosmeticMap.set(sha, isCosmeticDiff(diff));
1666
- } catch {
1667
- cosmeticMap.set(sha, { isCosmetic: false });
1668
- }
1669
- }
1647
+ const zeroSha = "0".repeat(40);
1648
+ await Promise.all(
1649
+ uniqueShas.filter((sha) => sha !== zeroSha).map(async (sha) => {
1650
+ try {
1651
+ const blameResult = results.find((r) => r.commitHash === sha);
1652
+ if (!blameResult) return;
1653
+ const file = blameResult.originalFile ?? results.find((r) => r.commitHash === sha)?.lineContent;
1654
+ const diff = await getCosmeticDiff(sha, file ?? "", options);
1655
+ cosmeticMap.set(sha, isCosmeticDiff(diff));
1656
+ } catch {
1657
+ cosmeticMap.set(sha, { isCosmetic: false });
1658
+ }
1659
+ })
1660
+ );
1670
1661
  return results.map((blame) => {
1671
1662
  const cosmetic = cosmeticMap.get(blame.commitHash);
1672
1663
  return {
@@ -1679,10 +1670,17 @@ async function analyzeBlameResults(results, options) {
1679
1670
 
1680
1671
  // src/core/core.ts
1681
1672
  init_pr_lookup2();
1682
- async function trace(options) {
1673
+ function computeFeatureFlags(operatingLevel, options) {
1674
+ return {
1675
+ astDiff: isAstAvailable() && !options.noAst,
1676
+ deepTrace: operatingLevel === 2 && (options.deep ?? false),
1677
+ commitGraph: false,
1678
+ issueGraph: operatingLevel === 2 && (options.graphDepth ?? 0) > 0,
1679
+ graphql: operatingLevel === 2
1680
+ };
1681
+ }
1682
+ async function detectPlatform2(options) {
1683
1683
  const warnings = [];
1684
- const nodes = [];
1685
- const execOptions = { cwd: void 0 };
1686
1684
  let adapter = null;
1687
1685
  let operatingLevel = 0;
1688
1686
  try {
@@ -1690,8 +1688,27 @@ async function trace(options) {
1690
1688
  remoteName: options.remote
1691
1689
  });
1692
1690
  adapter = detectedAdapter;
1693
- const authStatus = await adapter.checkAuth();
1694
- if (authStatus.authenticated) {
1691
+ } catch {
1692
+ operatingLevel = 0;
1693
+ warnings.push("Could not detect platform. Running in Level 0 (git only).");
1694
+ }
1695
+ return { adapter, operatingLevel, warnings };
1696
+ }
1697
+ async function runBlameAndAuth(adapter, options, execOptions) {
1698
+ const warnings = [];
1699
+ const lineRange = parseLineRange(
1700
+ options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
1701
+ );
1702
+ const blameChain = executeBlame(options.file, lineRange, execOptions).then(
1703
+ (results) => analyzeBlameResults(results, execOptions)
1704
+ );
1705
+ const [authResult, blameResult] = await Promise.allSettled([
1706
+ adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
1707
+ blameChain
1708
+ ]);
1709
+ let operatingLevel = 0;
1710
+ if (adapter && authResult.status === "fulfilled") {
1711
+ if (authResult.value.authenticated) {
1695
1712
  operatingLevel = 2;
1696
1713
  } else {
1697
1714
  operatingLevel = 1;
@@ -1699,22 +1716,14 @@ async function trace(options) {
1699
1716
  "Platform CLI not authenticated. Running in Level 1 (local only)."
1700
1717
  );
1701
1718
  }
1702
- } catch {
1703
- operatingLevel = 0;
1704
- warnings.push("Could not detect platform. Running in Level 0 (git only).");
1705
1719
  }
1706
- const featureFlags = {
1707
- astDiff: isAstAvailable() && !options.noAst,
1708
- deepTrace: operatingLevel === 2 && (options.deep ?? false),
1709
- commitGraph: false,
1710
- issueGraph: operatingLevel === 2 && (options.graphDepth ?? 0) > 0,
1711
- graphql: operatingLevel === 2
1712
- };
1713
- const lineRange = parseLineRange(
1714
- options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
1715
- );
1716
- const blameResults = await executeBlame(options.file, lineRange, execOptions);
1717
- const analyzed = await analyzeBlameResults(blameResults, execOptions);
1720
+ if (blameResult.status === "rejected") {
1721
+ throw blameResult.reason;
1722
+ }
1723
+ return { analyzed: blameResult.value, operatingLevel, warnings };
1724
+ }
1725
+ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions) {
1726
+ const nodes = [];
1718
1727
  for (const entry of analyzed) {
1719
1728
  const commitNode = {
1720
1729
  type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
@@ -1757,6 +1766,26 @@ async function trace(options) {
1757
1766
  }
1758
1767
  }
1759
1768
  }
1769
+ return nodes;
1770
+ }
1771
+ async function trace(options) {
1772
+ const execOptions = { cwd: void 0 };
1773
+ const platform = await detectPlatform2(options);
1774
+ const blameAuth = await runBlameAndAuth(
1775
+ platform.adapter,
1776
+ options,
1777
+ execOptions
1778
+ );
1779
+ const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
1780
+ const warnings = [...platform.warnings, ...blameAuth.warnings];
1781
+ const featureFlags = computeFeatureFlags(operatingLevel, options);
1782
+ const nodes = await buildTraceNodes(
1783
+ blameAuth.analyzed,
1784
+ featureFlags,
1785
+ platform.adapter,
1786
+ options,
1787
+ execOptions
1788
+ );
1760
1789
  return { nodes, operatingLevel, featureFlags, warnings };
1761
1790
  }
1762
1791
  async function health(options) {
package/dist/version.d.ts CHANGED
@@ -1 +1,5 @@
1
- export declare const VERSION = "0.0.1";
1
+ /**
2
+ * Current package version from package.json
3
+ * Automatically synchronized during build process
4
+ */
5
+ export declare const VERSION = "0.0.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumy-pack/line-lore",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "CLI tool for tracing code lines to their originating Pull Requests via git blame",
5
5
  "keywords": [
6
6
  "cli",