library-skills 0.0.7 → 0.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "library-skills",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Library Skills, AI Agents using libraries, as intended, always up to date.",
5
5
  "type": "module",
6
6
  "exports": {
package/ts/dist/cli.d.ts CHANGED
@@ -15,12 +15,20 @@ interface InstallTarget {
15
15
  path: string;
16
16
  }
17
17
 
18
+ interface UvWorkspace {
19
+ root: string;
20
+ members: string[];
21
+ currentMember: string | null;
22
+ }
23
+
18
24
  interface ProjectContext {
19
25
  cwd: string;
20
26
  projectRoot: string;
21
27
  targetEnvironment: string | null;
22
28
  sitePackagesDir: string | null;
23
29
  nodeModulesDir: string | null;
30
+ workspace: UvWorkspace | null;
31
+ dependencyFiles: string[];
24
32
  }
25
33
  interface InstalledStatus {
26
34
  target: InstallTarget;
package/ts/dist/cli.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // ts/src/cli.ts
4
4
  import checkbox from "@inquirer/checkbox";
5
5
  import { Command } from "commander";
6
- import { realpathSync as realpathSync2, statSync as statSync4 } from "fs";
7
- import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve4 } from "path";
6
+ import { realpathSync as realpathSync2, statSync as statSync5 } from "fs";
7
+ import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5 } from "path";
8
8
  import { fileURLToPath as fileURLToPath2 } from "url";
9
9
 
10
10
  // ts/src/deps.ts
@@ -468,13 +468,30 @@ function getPythonTopLevelDeps(projectRoot) {
468
468
  if (!existsSync2(pyproject)) {
469
469
  return null;
470
470
  }
471
- let data;
472
- try {
473
- data = parse2(readFileSync2(pyproject, "utf8"));
474
- } catch {
471
+ return getPythonTopLevelDepsFromFiles([pyproject]);
472
+ }
473
+ function getPythonTopLevelDepsFromFiles(pyprojects) {
474
+ if (pyprojects.length === 0) {
475
475
  return null;
476
476
  }
477
477
  const deps = /* @__PURE__ */ new Set();
478
+ let found = false;
479
+ for (const pyproject of pyprojects) {
480
+ if (!existsSync2(pyproject)) {
481
+ continue;
482
+ }
483
+ let data;
484
+ try {
485
+ data = parse2(readFileSync2(pyproject, "utf8"));
486
+ } catch {
487
+ return null;
488
+ }
489
+ found = true;
490
+ extractPythonTopLevelDeps(data, deps);
491
+ }
492
+ return found ? deps : null;
493
+ }
494
+ function extractPythonTopLevelDeps(data, deps) {
478
495
  const project = data["project"];
479
496
  if (isRecord2(project)) {
480
497
  const dependencies = project["dependencies"];
@@ -496,7 +513,6 @@ function getPythonTopLevelDeps(projectRoot) {
496
513
  extractDepsFromDependencyGroup(groupName, dependencyGroups, deps, /* @__PURE__ */ new Set());
497
514
  }
498
515
  }
499
- return deps;
500
516
  }
501
517
  function getNodeTopLevelDeps(projectRoot) {
502
518
  const packageJson = join2(projectRoot, "package.json");
@@ -539,12 +555,15 @@ function getTopLevelDeps(projectRoot) {
539
555
  }
540
556
  return new Set(dependencySets.flatMap((deps) => [...deps]));
541
557
  }
558
+ function getWorkspaceTopLevelDeps(pyprojects) {
559
+ return getPythonTopLevelDepsFromFiles(pyprojects);
560
+ }
542
561
  function extractDepsFromSpecs(depSpecs, deps) {
543
562
  for (const depSpec of depSpecs) {
544
563
  if (typeof depSpec !== "string") {
545
564
  continue;
546
565
  }
547
- const packageName = depSpec.split(/[>=<![\];,\s]/)[0]?.trim();
566
+ const packageName = depSpec.split(/[~>=<![\];,\s]/)[0]?.trim();
548
567
  if (packageName && !packageName.startsWith("#")) {
549
568
  deps.add(normalizePackageName(packageName));
550
569
  }
@@ -688,17 +707,201 @@ function isDirectory2(path) {
688
707
  }
689
708
 
690
709
  // ts/src/python-env.ts
691
- import { readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
692
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve3, sep } from "path";
693
- function findProjectRoot(cwd) {
710
+ import { readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
711
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "path";
712
+
713
+ // ts/src/workspace.ts
714
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
715
+ import { relative as relative3, resolve as resolve3, sep } from "path";
716
+ import { parse as parse3 } from "smol-toml";
717
+ function findUvWorkspace(cwd) {
694
718
  for (const directory of ancestors(cwd)) {
695
- if (isFile(join4(directory, "pyproject.toml")) || isFile(join4(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile(join4(directory, "package.json")) || isDirectory3(join4(directory, "node_modules"))) {
719
+ const pyproject = `${directory}/pyproject.toml`;
720
+ if (!existsSync4(pyproject)) {
721
+ continue;
722
+ }
723
+ const data = readPyproject(pyproject);
724
+ if (!hasUvWorkspace(data)) {
725
+ continue;
726
+ }
727
+ const members = findWorkspaceMembers(directory, data);
728
+ return {
729
+ root: directory,
730
+ members,
731
+ currentMember: findCurrentMember(cwd, members)
732
+ };
733
+ }
734
+ return null;
735
+ }
736
+ function workspaceDependencyFiles(workspace) {
737
+ if (workspace.currentMember !== null) {
738
+ return [`${workspace.currentMember}/pyproject.toml`];
739
+ }
740
+ return [
741
+ `${workspace.root}/pyproject.toml`,
742
+ ...workspace.members.map((member) => `${member}/pyproject.toml`)
743
+ ].filter((path) => existsSync4(path));
744
+ }
745
+ function readPyproject(path) {
746
+ try {
747
+ const data = parse3(readFileSync3(path, "utf8"));
748
+ return isRecord3(data) ? data : {};
749
+ } catch {
750
+ return {};
751
+ }
752
+ }
753
+ function hasUvWorkspace(data) {
754
+ const tool = data["tool"];
755
+ if (!isRecord3(tool)) {
756
+ return false;
757
+ }
758
+ const uv = tool["uv"];
759
+ if (!isRecord3(uv)) {
760
+ return false;
761
+ }
762
+ return isRecord3(uv["workspace"]);
763
+ }
764
+ function findWorkspaceMembers(root, data) {
765
+ const workspace = getWorkspaceTable(data);
766
+ if (workspace === null || !Array.isArray(workspace["members"])) {
767
+ return [];
768
+ }
769
+ const excludes = Array.isArray(workspace["exclude"]) ? workspace["exclude"].filter(
770
+ (value) => typeof value === "string"
771
+ ) : [];
772
+ const members = /* @__PURE__ */ new Set();
773
+ for (const memberGlob of workspace["members"]) {
774
+ if (typeof memberGlob !== "string") {
775
+ continue;
776
+ }
777
+ for (const member of expandMemberGlob(root, memberGlob)) {
778
+ const relativePath = relative3(root, member).split(sep).join("/");
779
+ if (isExcluded(relativePath, excludes)) {
780
+ continue;
781
+ }
782
+ if (isFile(`${member}/pyproject.toml`)) {
783
+ members.add(resolve3(member));
784
+ }
785
+ }
786
+ }
787
+ return [...members].sort();
788
+ }
789
+ function getWorkspaceTable(data) {
790
+ const tool = data["tool"];
791
+ if (!isRecord3(tool)) {
792
+ return null;
793
+ }
794
+ const uv = tool["uv"];
795
+ if (!isRecord3(uv)) {
796
+ return null;
797
+ }
798
+ const workspace = uv["workspace"];
799
+ return isRecord3(workspace) ? workspace : null;
800
+ }
801
+ function expandMemberGlob(root, pattern) {
802
+ const parts = pattern.split("/");
803
+ const results = [];
804
+ walkGlob(root, parts, results);
805
+ return results;
806
+ }
807
+ function walkGlob(directory, parts, results) {
808
+ if (parts.length === 0) {
809
+ if (isDirectory3(directory)) {
810
+ results.push(directory);
811
+ }
812
+ return;
813
+ }
814
+ const [part, ...rest] = parts;
815
+ if (part === "*") {
816
+ for (const entry of readDirSafe(directory)) {
817
+ const child = `${directory}/${entry}`;
818
+ if (isDirectory3(child)) {
819
+ walkGlob(child, rest, results);
820
+ }
821
+ }
822
+ return;
823
+ }
824
+ walkGlob(`${directory}/${part}`, rest, results);
825
+ }
826
+ function isExcluded(relativePath, excludes) {
827
+ return excludes.some(
828
+ (pattern) => globMatches(relativePath, pattern.replace(/\/$/, ""))
829
+ );
830
+ }
831
+ function globMatches(value, pattern) {
832
+ if (pattern === value) {
833
+ return true;
834
+ }
835
+ const regex = new RegExp(`^${pattern.split("*").map(escapeRegex).join(".*")}$`);
836
+ return regex.test(value);
837
+ }
838
+ function findCurrentMember(cwd, members) {
839
+ const resolvedCwd = resolve3(cwd);
840
+ const matches = members.filter((member) => isRelativeTo2(resolvedCwd, member));
841
+ if (matches.length === 0) {
842
+ return null;
843
+ }
844
+ return matches.sort((left, right) => right.length - left.length)[0] ?? null;
845
+ }
846
+ function ancestors(start) {
847
+ const result = [];
848
+ let directory = resolve3(start);
849
+ while (true) {
850
+ result.push(directory);
851
+ const parent = resolve3(directory, "..");
852
+ if (parent === directory) {
853
+ break;
854
+ }
855
+ directory = parent;
856
+ }
857
+ return result;
858
+ }
859
+ function isRelativeTo2(path, parent) {
860
+ const normalizedPath = resolve3(path);
861
+ const normalizedParent = resolve3(parent);
862
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${sep}`);
863
+ }
864
+ function isRecord3(value) {
865
+ return typeof value === "object" && value !== null && !Array.isArray(value);
866
+ }
867
+ function isFile(path) {
868
+ try {
869
+ return statSync3(path).isFile();
870
+ } catch {
871
+ return false;
872
+ }
873
+ }
874
+ function isDirectory3(path) {
875
+ try {
876
+ return statSync3(path).isDirectory();
877
+ } catch {
878
+ return false;
879
+ }
880
+ }
881
+ function readDirSafe(path) {
882
+ try {
883
+ return isDirectory3(path) ? readdirSync3(path) : [];
884
+ } catch {
885
+ return [];
886
+ }
887
+ }
888
+ var escapeRegex = (value) => value.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
889
+
890
+ // ts/src/python-env.ts
891
+ function findProjectRoot(cwd) {
892
+ const workspace = findUvWorkspace(cwd);
893
+ if (workspace !== null) {
894
+ return workspace.root;
895
+ }
896
+ for (const directory of ancestors2(cwd)) {
897
+ if (isFile2(join4(directory, "pyproject.toml")) || isFile2(join4(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile2(join4(directory, "package.json")) || isDirectory4(join4(directory, "node_modules"))) {
696
898
  return directory;
697
899
  }
698
900
  }
699
901
  return null;
700
902
  }
701
903
  function findVenv(cwd = process.cwd()) {
904
+ const workspace = findUvWorkspace(cwd);
702
905
  const projectRoot = findProjectRoot(cwd) ?? cwd;
703
906
  const uvProjectEnvironment = process.env["UV_PROJECT_ENVIRONMENT"];
704
907
  if (uvProjectEnvironment) {
@@ -707,35 +910,38 @@ function findVenv(cwd = process.cwd()) {
707
910
  return path;
708
911
  }
709
912
  }
710
- for (const directory of ancestors(cwd)) {
913
+ if (workspace !== null) {
914
+ return venvFromDotVenv(workspace.root);
915
+ }
916
+ for (const directory of ancestors2(cwd)) {
711
917
  const venv = venvFromDotVenv(directory);
712
918
  if (venv !== null) {
713
919
  return venv;
714
920
  }
715
921
  }
716
922
  const virtualEnv = process.env["VIRTUAL_ENV"];
717
- if (virtualEnv && isVenvDir(virtualEnv) && isRelativeTo2(virtualEnv, projectRoot)) {
923
+ if (virtualEnv && isVenvDir(virtualEnv) && isRelativeTo3(virtualEnv, projectRoot)) {
718
924
  return virtualEnv;
719
925
  }
720
926
  const condaPrefix = process.env["CONDA_PREFIX"];
721
- if (condaPrefix && isDirectory3(condaPrefix)) {
927
+ if (condaPrefix && isDirectory4(condaPrefix)) {
722
928
  return condaPrefix;
723
929
  }
724
930
  return null;
725
931
  }
726
932
  function getSitePackagesDir(venvPath) {
727
933
  const windowsSitePackages = join4(venvPath, "Lib", "site-packages");
728
- if (isDirectory3(windowsSitePackages)) {
934
+ if (isDirectory4(windowsSitePackages)) {
729
935
  return windowsSitePackages;
730
936
  }
731
937
  for (const libName of ["lib", "lib64"]) {
732
938
  const libDir = join4(venvPath, libName);
733
- if (!isDirectory3(libDir)) {
939
+ if (!isDirectory4(libDir)) {
734
940
  continue;
735
941
  }
736
- for (const child of readdirSync3(libDir).sort().reverse()) {
942
+ for (const child of readdirSync4(libDir).sort().reverse()) {
737
943
  const sitePackages = join4(libDir, child, "site-packages");
738
- if (child.startsWith("python") && isDirectory3(sitePackages)) {
944
+ if (child.startsWith("python") && isDirectory4(sitePackages)) {
739
945
  return sitePackages;
740
946
  }
741
947
  }
@@ -743,23 +949,23 @@ function getSitePackagesDir(venvPath) {
743
949
  return null;
744
950
  }
745
951
  function findNodeModules(cwd = process.cwd()) {
746
- for (const directory of ancestors(cwd)) {
952
+ for (const directory of ancestors2(cwd)) {
747
953
  const nodeModules = join4(directory, "node_modules");
748
- if (isDirectory3(nodeModules)) {
954
+ if (isDirectory4(nodeModules)) {
749
955
  return nodeModules;
750
956
  }
751
957
  }
752
958
  return null;
753
959
  }
754
960
  function isVenvDir(directory) {
755
- return isFile(join4(directory, "pyvenv.cfg"));
961
+ return isFile2(join4(directory, "pyvenv.cfg"));
756
962
  }
757
963
  function venvFromDotVenv(projectRoot) {
758
964
  const dotVenv = join4(projectRoot, ".venv");
759
- if (isDirectory3(dotVenv)) {
965
+ if (isDirectory4(dotVenv)) {
760
966
  return isVenvDir(dotVenv) ? dotVenv : null;
761
967
  }
762
- if (isFile(dotVenv)) {
968
+ if (isFile2(dotVenv)) {
763
969
  return readVenvRedirectFile(dotVenv);
764
970
  }
765
971
  return null;
@@ -767,7 +973,7 @@ function venvFromDotVenv(projectRoot) {
767
973
  function readVenvRedirectFile(path) {
768
974
  let content;
769
975
  try {
770
- content = readFileSync3(path, "utf8");
976
+ content = readFileSync4(path, "utf8");
771
977
  } catch {
772
978
  return null;
773
979
  }
@@ -778,9 +984,9 @@ function readVenvRedirectFile(path) {
778
984
  const venv = isAbsolute2(redirect) ? redirect : join4(dirname3(path), redirect);
779
985
  return isVenvDir(venv) ? venv : null;
780
986
  }
781
- function ancestors(start) {
987
+ function ancestors2(start) {
782
988
  const result = [];
783
- let directory = resolve3(start);
989
+ let directory = resolve4(start);
784
990
  while (true) {
785
991
  result.push(directory);
786
992
  const parent = dirname3(directory);
@@ -791,21 +997,21 @@ function ancestors(start) {
791
997
  }
792
998
  return result;
793
999
  }
794
- function isRelativeTo2(path, parent) {
795
- const normalizedPath = resolve3(path);
796
- const normalizedParent = resolve3(parent);
797
- return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${sep}`);
1000
+ function isRelativeTo3(path, parent) {
1001
+ const normalizedPath = resolve4(path);
1002
+ const normalizedParent = resolve4(parent);
1003
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${sep2}`);
798
1004
  }
799
- function isFile(path) {
1005
+ function isFile2(path) {
800
1006
  try {
801
- return statSync3(path).isFile();
1007
+ return statSync4(path).isFile();
802
1008
  } catch {
803
1009
  return false;
804
1010
  }
805
1011
  }
806
- function isDirectory3(path) {
1012
+ function isDirectory4(path) {
807
1013
  try {
808
- return statSync3(path).isDirectory();
1014
+ return statSync4(path).isDirectory();
809
1015
  } catch {
810
1016
  return false;
811
1017
  }
@@ -813,11 +1019,20 @@ function isDirectory3(path) {
813
1019
 
814
1020
  // ts/src/cli.ts
815
1021
  function getProjectContext(cwd = process.cwd()) {
816
- const projectRoot = findProjectRoot(cwd) ?? cwd;
1022
+ const workspace = findUvWorkspace(cwd);
1023
+ const projectRoot = workspace?.root ?? findProjectRoot(cwd) ?? cwd;
817
1024
  const targetEnvironment = findVenv(cwd);
818
1025
  const sitePackagesDir = targetEnvironment === null ? null : getSitePackagesDir(targetEnvironment);
819
1026
  const nodeModulesDir = findNodeModules(cwd);
820
- return { cwd, projectRoot, targetEnvironment, sitePackagesDir, nodeModulesDir };
1027
+ return {
1028
+ cwd,
1029
+ projectRoot,
1030
+ targetEnvironment,
1031
+ sitePackagesDir,
1032
+ nodeModulesDir,
1033
+ workspace,
1034
+ dependencyFiles: workspace ? workspaceDependencyFiles(workspace) : []
1035
+ };
821
1036
  }
822
1037
  function scanContext(context) {
823
1038
  const result = {
@@ -830,6 +1045,14 @@ function scanContext(context) {
830
1045
  result.skills.push(...pythonResult.skills);
831
1046
  result.warnings.push(...pythonResult.warnings);
832
1047
  }
1048
+ if (context.workspace?.currentMember) {
1049
+ const memberVenv = `${context.workspace.currentMember}/.venv`;
1050
+ if (exists(memberVenv)) {
1051
+ result.warnings.push(
1052
+ `Ignoring member-local .venv in uv workspace: ${memberVenv}`
1053
+ );
1054
+ }
1055
+ }
833
1056
  if (context.nodeModulesDir !== null) {
834
1057
  const nodeResult = scanNodePackages(context.nodeModulesDir);
835
1058
  result.skills.push(...nodeResult.skills);
@@ -851,11 +1074,13 @@ function topLevelSkills({
851
1074
  return skills;
852
1075
  }
853
1076
  const topLevelDeps = getTopLevelDeps(context.projectRoot);
854
- if (topLevelDeps === null) {
1077
+ const workspaceTopLevelDeps = context.workspace === null ? null : getWorkspaceTopLevelDeps(context.dependencyFiles);
1078
+ const selectedTopLevelDeps = context.workspace === null ? topLevelDeps : workspaceTopLevelDeps;
1079
+ if (selectedTopLevelDeps === null) {
855
1080
  return skills;
856
1081
  }
857
1082
  return skills.filter(
858
- (skill) => topLevelDeps.has(normalizePackageName(skill.packageName))
1083
+ (skill) => selectedTopLevelDeps.has(normalizePackageName(skill.packageName))
859
1084
  );
860
1085
  }
861
1086
  function printWarnings(warnings) {
@@ -867,7 +1092,7 @@ function displayPath(path, projectRoot) {
867
1092
  if (!path) {
868
1093
  return "";
869
1094
  }
870
- const relativePath = relative3(resolve4(projectRoot), resolve4(path));
1095
+ const relativePath = relative4(resolve5(projectRoot), resolve5(path));
871
1096
  if (relativePath === "") {
872
1097
  return ".";
873
1098
  }
@@ -878,6 +1103,12 @@ function displayPath(path, projectRoot) {
878
1103
  }
879
1104
  function printContext(context) {
880
1105
  console.log(`Project root: ${context.projectRoot}`);
1106
+ if (context.workspace !== null) {
1107
+ console.log(`Workspace root: ${context.workspace.root}`);
1108
+ if (context.workspace.currentMember !== null) {
1109
+ console.log(`Workspace member: ${context.workspace.currentMember}`);
1110
+ }
1111
+ }
881
1112
  console.log(
882
1113
  `Target Python environment: ${context.targetEnvironment ? displayPath(context.targetEnvironment, context.projectRoot) : "not found"}`
883
1114
  );
@@ -917,6 +1148,9 @@ function scanJsonPayload({
917
1148
  }) {
918
1149
  return {
919
1150
  project_root: context.projectRoot,
1151
+ workspace_root: context.workspace?.root ?? "",
1152
+ workspace_member: context.workspace?.currentMember ?? "",
1153
+ dependency_files: context.dependencyFiles,
920
1154
  target_environment: context.targetEnvironment ?? "",
921
1155
  node_modules: context.nodeModulesDir ?? "",
922
1156
  skills: skills.map((skill) => ({
@@ -972,13 +1206,13 @@ function installedStatuses({
972
1206
  skills
973
1207
  }) {
974
1208
  const skillsByDir = new Map(
975
- skills.map((skill) => [resolve4(skill.skillDir), skill])
1209
+ skills.map((skill) => [resolve5(skill.skillDir), skill])
976
1210
  );
977
1211
  const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
978
1212
  const statuses = [];
979
1213
  for (const target of targets) {
980
1214
  for (const installed of listInstalledSkills(target.path)) {
981
- const skill = installed.target ? skillsByDir.get(resolve4(installed.target)) : null;
1215
+ const skill = installed.target ? skillsByDir.get(resolve5(installed.target)) : null;
982
1216
  let matchedSkill = skill ?? null;
983
1217
  let status = "hand-authored";
984
1218
  if (installed.type === "symlink") {
@@ -1342,13 +1576,13 @@ function collect(value, previous) {
1342
1576
  }
1343
1577
  function exists(path) {
1344
1578
  try {
1345
- statSync4(path);
1579
+ statSync5(path);
1346
1580
  return true;
1347
1581
  } catch {
1348
1582
  return false;
1349
1583
  }
1350
1584
  }
1351
- if (process.argv[1] && realpathSync2(fileURLToPath2(import.meta.url)) === realpathSync2(resolve4(process.argv[1]))) {
1585
+ if (process.argv[1] && realpathSync2(fileURLToPath2(import.meta.url)) === realpathSync2(resolve5(process.argv[1]))) {
1352
1586
  main().catch((error) => {
1353
1587
  console.error(error);
1354
1588
  process.exit(1);
package/ts/dist/deps.cjs CHANGED
@@ -22,7 +22,9 @@ var deps_exports = {};
22
22
  __export(deps_exports, {
23
23
  getNodeTopLevelDeps: () => getNodeTopLevelDeps,
24
24
  getPythonTopLevelDeps: () => getPythonTopLevelDeps,
25
+ getPythonTopLevelDepsFromFiles: () => getPythonTopLevelDepsFromFiles,
25
26
  getTopLevelDeps: () => getTopLevelDeps,
27
+ getWorkspaceTopLevelDeps: () => getWorkspaceTopLevelDeps,
26
28
  testing: () => testing
27
29
  });
28
30
  module.exports = __toCommonJS(deps_exports);
@@ -45,13 +47,30 @@ function getPythonTopLevelDeps(projectRoot) {
45
47
  if (!(0, import_node_fs2.existsSync)(pyproject)) {
46
48
  return null;
47
49
  }
48
- let data;
49
- try {
50
- data = (0, import_smol_toml.parse)((0, import_node_fs2.readFileSync)(pyproject, "utf8"));
51
- } catch {
50
+ return getPythonTopLevelDepsFromFiles([pyproject]);
51
+ }
52
+ function getPythonTopLevelDepsFromFiles(pyprojects) {
53
+ if (pyprojects.length === 0) {
52
54
  return null;
53
55
  }
54
56
  const deps = /* @__PURE__ */ new Set();
57
+ let found = false;
58
+ for (const pyproject of pyprojects) {
59
+ if (!(0, import_node_fs2.existsSync)(pyproject)) {
60
+ continue;
61
+ }
62
+ let data;
63
+ try {
64
+ data = (0, import_smol_toml.parse)((0, import_node_fs2.readFileSync)(pyproject, "utf8"));
65
+ } catch {
66
+ return null;
67
+ }
68
+ found = true;
69
+ extractPythonTopLevelDeps(data, deps);
70
+ }
71
+ return found ? deps : null;
72
+ }
73
+ function extractPythonTopLevelDeps(data, deps) {
55
74
  const project = data["project"];
56
75
  if (isRecord(project)) {
57
76
  const dependencies = project["dependencies"];
@@ -73,7 +92,6 @@ function getPythonTopLevelDeps(projectRoot) {
73
92
  extractDepsFromDependencyGroup(groupName, dependencyGroups, deps, /* @__PURE__ */ new Set());
74
93
  }
75
94
  }
76
- return deps;
77
95
  }
78
96
  function getNodeTopLevelDeps(projectRoot) {
79
97
  const packageJson = (0, import_node_path2.join)(projectRoot, "package.json");
@@ -116,12 +134,15 @@ function getTopLevelDeps(projectRoot) {
116
134
  }
117
135
  return new Set(dependencySets.flatMap((deps) => [...deps]));
118
136
  }
137
+ function getWorkspaceTopLevelDeps(pyprojects) {
138
+ return getPythonTopLevelDepsFromFiles(pyprojects);
139
+ }
119
140
  function extractDepsFromSpecs(depSpecs, deps) {
120
141
  for (const depSpec of depSpecs) {
121
142
  if (typeof depSpec !== "string") {
122
143
  continue;
123
144
  }
124
- const packageName = depSpec.split(/[>=<![\];,\s]/)[0]?.trim();
145
+ const packageName = depSpec.split(/[~>=<![\];,\s]/)[0]?.trim();
125
146
  if (packageName && !packageName.startsWith("#")) {
126
147
  deps.add(normalizePackageName(packageName));
127
148
  }
@@ -164,6 +185,8 @@ var testing = {
164
185
  0 && (module.exports = {
165
186
  getNodeTopLevelDeps,
166
187
  getPythonTopLevelDeps,
188
+ getPythonTopLevelDepsFromFiles,
167
189
  getTopLevelDeps,
190
+ getWorkspaceTopLevelDeps,
168
191
  testing
169
192
  });
@@ -1,6 +1,8 @@
1
1
  declare function getPythonTopLevelDeps(projectRoot: string): Set<string> | null;
2
+ declare function getPythonTopLevelDepsFromFiles(pyprojects: string[]): Set<string> | null;
2
3
  declare function getNodeTopLevelDeps(projectRoot: string): Set<string> | null;
3
4
  declare function getTopLevelDeps(projectRoot: string): Set<string> | null;
5
+ declare function getWorkspaceTopLevelDeps(pyprojects: string[]): Set<string> | null;
4
6
  declare function extractDepsFromSpecs(depSpecs: unknown[], deps: Set<string>): void;
5
7
  declare function extractDepsFromDependencyGroup(groupName: unknown, dependencyGroups: Record<string, unknown>, deps: Set<string>, visited: Set<unknown>): void;
6
8
  declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -10,4 +12,4 @@ declare const testing: {
10
12
  isRecord: typeof isRecord;
11
13
  };
12
14
 
13
- export { getNodeTopLevelDeps, getPythonTopLevelDeps, getTopLevelDeps, testing };
15
+ export { getNodeTopLevelDeps, getPythonTopLevelDeps, getPythonTopLevelDepsFromFiles, getTopLevelDeps, getWorkspaceTopLevelDeps, testing };
package/ts/dist/deps.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  declare function getPythonTopLevelDeps(projectRoot: string): Set<string> | null;
2
+ declare function getPythonTopLevelDepsFromFiles(pyprojects: string[]): Set<string> | null;
2
3
  declare function getNodeTopLevelDeps(projectRoot: string): Set<string> | null;
3
4
  declare function getTopLevelDeps(projectRoot: string): Set<string> | null;
5
+ declare function getWorkspaceTopLevelDeps(pyprojects: string[]): Set<string> | null;
4
6
  declare function extractDepsFromSpecs(depSpecs: unknown[], deps: Set<string>): void;
5
7
  declare function extractDepsFromDependencyGroup(groupName: unknown, dependencyGroups: Record<string, unknown>, deps: Set<string>, visited: Set<unknown>): void;
6
8
  declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -10,4 +12,4 @@ declare const testing: {
10
12
  isRecord: typeof isRecord;
11
13
  };
12
14
 
13
- export { getNodeTopLevelDeps, getPythonTopLevelDeps, getTopLevelDeps, testing };
15
+ export { getNodeTopLevelDeps, getPythonTopLevelDeps, getPythonTopLevelDepsFromFiles, getTopLevelDeps, getWorkspaceTopLevelDeps, testing };
package/ts/dist/deps.js CHANGED
@@ -11,13 +11,30 @@ function getPythonTopLevelDeps(projectRoot) {
11
11
  if (!existsSync(pyproject)) {
12
12
  return null;
13
13
  }
14
- let data;
15
- try {
16
- data = parse(readFileSync(pyproject, "utf8"));
17
- } catch {
14
+ return getPythonTopLevelDepsFromFiles([pyproject]);
15
+ }
16
+ function getPythonTopLevelDepsFromFiles(pyprojects) {
17
+ if (pyprojects.length === 0) {
18
18
  return null;
19
19
  }
20
20
  const deps = /* @__PURE__ */ new Set();
21
+ let found = false;
22
+ for (const pyproject of pyprojects) {
23
+ if (!existsSync(pyproject)) {
24
+ continue;
25
+ }
26
+ let data;
27
+ try {
28
+ data = parse(readFileSync(pyproject, "utf8"));
29
+ } catch {
30
+ return null;
31
+ }
32
+ found = true;
33
+ extractPythonTopLevelDeps(data, deps);
34
+ }
35
+ return found ? deps : null;
36
+ }
37
+ function extractPythonTopLevelDeps(data, deps) {
21
38
  const project = data["project"];
22
39
  if (isRecord(project)) {
23
40
  const dependencies = project["dependencies"];
@@ -39,7 +56,6 @@ function getPythonTopLevelDeps(projectRoot) {
39
56
  extractDepsFromDependencyGroup(groupName, dependencyGroups, deps, /* @__PURE__ */ new Set());
40
57
  }
41
58
  }
42
- return deps;
43
59
  }
44
60
  function getNodeTopLevelDeps(projectRoot) {
45
61
  const packageJson = join(projectRoot, "package.json");
@@ -82,12 +98,15 @@ function getTopLevelDeps(projectRoot) {
82
98
  }
83
99
  return new Set(dependencySets.flatMap((deps) => [...deps]));
84
100
  }
101
+ function getWorkspaceTopLevelDeps(pyprojects) {
102
+ return getPythonTopLevelDepsFromFiles(pyprojects);
103
+ }
85
104
  function extractDepsFromSpecs(depSpecs, deps) {
86
105
  for (const depSpec of depSpecs) {
87
106
  if (typeof depSpec !== "string") {
88
107
  continue;
89
108
  }
90
- const packageName = depSpec.split(/[>=<![\];,\s]/)[0]?.trim();
109
+ const packageName = depSpec.split(/[~>=<![\];,\s]/)[0]?.trim();
91
110
  if (packageName && !packageName.startsWith("#")) {
92
111
  deps.add(normalizePackageName(packageName));
93
112
  }
@@ -129,6 +148,8 @@ var testing = {
129
148
  export {
130
149
  getNodeTopLevelDeps,
131
150
  getPythonTopLevelDeps,
151
+ getPythonTopLevelDepsFromFiles,
132
152
  getTopLevelDeps,
153
+ getWorkspaceTopLevelDeps,
133
154
  testing
134
155
  };
@@ -27,54 +27,232 @@ __export(python_env_exports, {
27
27
  getSitePackagesDir: () => getSitePackagesDir
28
28
  });
29
29
  module.exports = __toCommonJS(python_env_exports);
30
+ var import_node_fs2 = require("fs");
31
+ var import_node_path2 = require("path");
32
+
33
+ // ts/src/workspace.ts
30
34
  var import_node_fs = require("fs");
31
35
  var import_node_path = require("path");
32
- function findProjectRoot(cwd) {
36
+ var import_smol_toml = require("smol-toml");
37
+ function findUvWorkspace(cwd) {
33
38
  for (const directory of ancestors(cwd)) {
34
- if (isFile((0, import_node_path.join)(directory, "pyproject.toml")) || isFile((0, import_node_path.join)(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile((0, import_node_path.join)(directory, "package.json")) || isDirectory((0, import_node_path.join)(directory, "node_modules"))) {
39
+ const pyproject = `${directory}/pyproject.toml`;
40
+ if (!(0, import_node_fs.existsSync)(pyproject)) {
41
+ continue;
42
+ }
43
+ const data = readPyproject(pyproject);
44
+ if (!hasUvWorkspace(data)) {
45
+ continue;
46
+ }
47
+ const members = findWorkspaceMembers(directory, data);
48
+ return {
49
+ root: directory,
50
+ members,
51
+ currentMember: findCurrentMember(cwd, members)
52
+ };
53
+ }
54
+ return null;
55
+ }
56
+ function readPyproject(path) {
57
+ try {
58
+ const data = (0, import_smol_toml.parse)((0, import_node_fs.readFileSync)(path, "utf8"));
59
+ return isRecord(data) ? data : {};
60
+ } catch {
61
+ return {};
62
+ }
63
+ }
64
+ function hasUvWorkspace(data) {
65
+ const tool = data["tool"];
66
+ if (!isRecord(tool)) {
67
+ return false;
68
+ }
69
+ const uv = tool["uv"];
70
+ if (!isRecord(uv)) {
71
+ return false;
72
+ }
73
+ return isRecord(uv["workspace"]);
74
+ }
75
+ function findWorkspaceMembers(root, data) {
76
+ const workspace = getWorkspaceTable(data);
77
+ if (workspace === null || !Array.isArray(workspace["members"])) {
78
+ return [];
79
+ }
80
+ const excludes = Array.isArray(workspace["exclude"]) ? workspace["exclude"].filter(
81
+ (value) => typeof value === "string"
82
+ ) : [];
83
+ const members = /* @__PURE__ */ new Set();
84
+ for (const memberGlob of workspace["members"]) {
85
+ if (typeof memberGlob !== "string") {
86
+ continue;
87
+ }
88
+ for (const member of expandMemberGlob(root, memberGlob)) {
89
+ const relativePath = (0, import_node_path.relative)(root, member).split(import_node_path.sep).join("/");
90
+ if (isExcluded(relativePath, excludes)) {
91
+ continue;
92
+ }
93
+ if (isFile(`${member}/pyproject.toml`)) {
94
+ members.add((0, import_node_path.resolve)(member));
95
+ }
96
+ }
97
+ }
98
+ return [...members].sort();
99
+ }
100
+ function getWorkspaceTable(data) {
101
+ const tool = data["tool"];
102
+ if (!isRecord(tool)) {
103
+ return null;
104
+ }
105
+ const uv = tool["uv"];
106
+ if (!isRecord(uv)) {
107
+ return null;
108
+ }
109
+ const workspace = uv["workspace"];
110
+ return isRecord(workspace) ? workspace : null;
111
+ }
112
+ function expandMemberGlob(root, pattern) {
113
+ const parts = pattern.split("/");
114
+ const results = [];
115
+ walkGlob(root, parts, results);
116
+ return results;
117
+ }
118
+ function walkGlob(directory, parts, results) {
119
+ if (parts.length === 0) {
120
+ if (isDirectory(directory)) {
121
+ results.push(directory);
122
+ }
123
+ return;
124
+ }
125
+ const [part, ...rest] = parts;
126
+ if (part === "*") {
127
+ for (const entry of readDirSafe(directory)) {
128
+ const child = `${directory}/${entry}`;
129
+ if (isDirectory(child)) {
130
+ walkGlob(child, rest, results);
131
+ }
132
+ }
133
+ return;
134
+ }
135
+ walkGlob(`${directory}/${part}`, rest, results);
136
+ }
137
+ function isExcluded(relativePath, excludes) {
138
+ return excludes.some(
139
+ (pattern) => globMatches(relativePath, pattern.replace(/\/$/, ""))
140
+ );
141
+ }
142
+ function globMatches(value, pattern) {
143
+ if (pattern === value) {
144
+ return true;
145
+ }
146
+ const regex = new RegExp(`^${pattern.split("*").map(escapeRegex).join(".*")}$`);
147
+ return regex.test(value);
148
+ }
149
+ function findCurrentMember(cwd, members) {
150
+ const resolvedCwd = (0, import_node_path.resolve)(cwd);
151
+ const matches = members.filter((member) => isRelativeTo(resolvedCwd, member));
152
+ if (matches.length === 0) {
153
+ return null;
154
+ }
155
+ return matches.sort((left, right) => right.length - left.length)[0] ?? null;
156
+ }
157
+ function ancestors(start) {
158
+ const result = [];
159
+ let directory = (0, import_node_path.resolve)(start);
160
+ while (true) {
161
+ result.push(directory);
162
+ const parent = (0, import_node_path.resolve)(directory, "..");
163
+ if (parent === directory) {
164
+ break;
165
+ }
166
+ directory = parent;
167
+ }
168
+ return result;
169
+ }
170
+ function isRelativeTo(path, parent) {
171
+ const normalizedPath = (0, import_node_path.resolve)(path);
172
+ const normalizedParent = (0, import_node_path.resolve)(parent);
173
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${import_node_path.sep}`);
174
+ }
175
+ function isRecord(value) {
176
+ return typeof value === "object" && value !== null && !Array.isArray(value);
177
+ }
178
+ function isFile(path) {
179
+ try {
180
+ return (0, import_node_fs.statSync)(path).isFile();
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+ function isDirectory(path) {
186
+ try {
187
+ return (0, import_node_fs.statSync)(path).isDirectory();
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+ function readDirSafe(path) {
193
+ try {
194
+ return isDirectory(path) ? (0, import_node_fs.readdirSync)(path) : [];
195
+ } catch {
196
+ return [];
197
+ }
198
+ }
199
+ var escapeRegex = (value) => value.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
200
+
201
+ // ts/src/python-env.ts
202
+ function findProjectRoot(cwd) {
203
+ const workspace = findUvWorkspace(cwd);
204
+ if (workspace !== null) {
205
+ return workspace.root;
206
+ }
207
+ for (const directory of ancestors2(cwd)) {
208
+ if (isFile2((0, import_node_path2.join)(directory, "pyproject.toml")) || isFile2((0, import_node_path2.join)(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile2((0, import_node_path2.join)(directory, "package.json")) || isDirectory2((0, import_node_path2.join)(directory, "node_modules"))) {
35
209
  return directory;
36
210
  }
37
211
  }
38
212
  return null;
39
213
  }
40
214
  function findVenv(cwd = process.cwd()) {
215
+ const workspace = findUvWorkspace(cwd);
41
216
  const projectRoot = findProjectRoot(cwd) ?? cwd;
42
217
  const uvProjectEnvironment = process.env["UV_PROJECT_ENVIRONMENT"];
43
218
  if (uvProjectEnvironment) {
44
- const path = (0, import_node_path.isAbsolute)(uvProjectEnvironment) ? uvProjectEnvironment : (0, import_node_path.join)(projectRoot, uvProjectEnvironment);
219
+ const path = (0, import_node_path2.isAbsolute)(uvProjectEnvironment) ? uvProjectEnvironment : (0, import_node_path2.join)(projectRoot, uvProjectEnvironment);
45
220
  if (isVenvDir(path)) {
46
221
  return path;
47
222
  }
48
223
  }
49
- for (const directory of ancestors(cwd)) {
224
+ if (workspace !== null) {
225
+ return venvFromDotVenv(workspace.root);
226
+ }
227
+ for (const directory of ancestors2(cwd)) {
50
228
  const venv = venvFromDotVenv(directory);
51
229
  if (venv !== null) {
52
230
  return venv;
53
231
  }
54
232
  }
55
233
  const virtualEnv = process.env["VIRTUAL_ENV"];
56
- if (virtualEnv && isVenvDir(virtualEnv) && isRelativeTo(virtualEnv, projectRoot)) {
234
+ if (virtualEnv && isVenvDir(virtualEnv) && isRelativeTo2(virtualEnv, projectRoot)) {
57
235
  return virtualEnv;
58
236
  }
59
237
  const condaPrefix = process.env["CONDA_PREFIX"];
60
- if (condaPrefix && isDirectory(condaPrefix)) {
238
+ if (condaPrefix && isDirectory2(condaPrefix)) {
61
239
  return condaPrefix;
62
240
  }
63
241
  return null;
64
242
  }
65
243
  function getSitePackagesDir(venvPath) {
66
- const windowsSitePackages = (0, import_node_path.join)(venvPath, "Lib", "site-packages");
67
- if (isDirectory(windowsSitePackages)) {
244
+ const windowsSitePackages = (0, import_node_path2.join)(venvPath, "Lib", "site-packages");
245
+ if (isDirectory2(windowsSitePackages)) {
68
246
  return windowsSitePackages;
69
247
  }
70
248
  for (const libName of ["lib", "lib64"]) {
71
- const libDir = (0, import_node_path.join)(venvPath, libName);
72
- if (!isDirectory(libDir)) {
249
+ const libDir = (0, import_node_path2.join)(venvPath, libName);
250
+ if (!isDirectory2(libDir)) {
73
251
  continue;
74
252
  }
75
- for (const child of (0, import_node_fs.readdirSync)(libDir).sort().reverse()) {
76
- const sitePackages = (0, import_node_path.join)(libDir, child, "site-packages");
77
- if (child.startsWith("python") && isDirectory(sitePackages)) {
253
+ for (const child of (0, import_node_fs2.readdirSync)(libDir).sort().reverse()) {
254
+ const sitePackages = (0, import_node_path2.join)(libDir, child, "site-packages");
255
+ if (child.startsWith("python") && isDirectory2(sitePackages)) {
78
256
  return sitePackages;
79
257
  }
80
258
  }
@@ -83,23 +261,23 @@ function getSitePackagesDir(venvPath) {
83
261
  }
84
262
  var getSitePackages = getSitePackagesDir;
85
263
  function findNodeModules(cwd = process.cwd()) {
86
- for (const directory of ancestors(cwd)) {
87
- const nodeModules = (0, import_node_path.join)(directory, "node_modules");
88
- if (isDirectory(nodeModules)) {
264
+ for (const directory of ancestors2(cwd)) {
265
+ const nodeModules = (0, import_node_path2.join)(directory, "node_modules");
266
+ if (isDirectory2(nodeModules)) {
89
267
  return nodeModules;
90
268
  }
91
269
  }
92
270
  return null;
93
271
  }
94
272
  function isVenvDir(directory) {
95
- return isFile((0, import_node_path.join)(directory, "pyvenv.cfg"));
273
+ return isFile2((0, import_node_path2.join)(directory, "pyvenv.cfg"));
96
274
  }
97
275
  function venvFromDotVenv(projectRoot) {
98
- const dotVenv = (0, import_node_path.join)(projectRoot, ".venv");
99
- if (isDirectory(dotVenv)) {
276
+ const dotVenv = (0, import_node_path2.join)(projectRoot, ".venv");
277
+ if (isDirectory2(dotVenv)) {
100
278
  return isVenvDir(dotVenv) ? dotVenv : null;
101
279
  }
102
- if (isFile(dotVenv)) {
280
+ if (isFile2(dotVenv)) {
103
281
  return readVenvRedirectFile(dotVenv);
104
282
  }
105
283
  return null;
@@ -107,7 +285,7 @@ function venvFromDotVenv(projectRoot) {
107
285
  function readVenvRedirectFile(path) {
108
286
  let content;
109
287
  try {
110
- content = (0, import_node_fs.readFileSync)(path, "utf8");
288
+ content = (0, import_node_fs2.readFileSync)(path, "utf8");
111
289
  } catch {
112
290
  return null;
113
291
  }
@@ -115,15 +293,15 @@ function readVenvRedirectFile(path) {
115
293
  if (!redirect) {
116
294
  return null;
117
295
  }
118
- const venv = (0, import_node_path.isAbsolute)(redirect) ? redirect : (0, import_node_path.join)((0, import_node_path.dirname)(path), redirect);
296
+ const venv = (0, import_node_path2.isAbsolute)(redirect) ? redirect : (0, import_node_path2.join)((0, import_node_path2.dirname)(path), redirect);
119
297
  return isVenvDir(venv) ? venv : null;
120
298
  }
121
- function ancestors(start) {
299
+ function ancestors2(start) {
122
300
  const result = [];
123
- let directory = (0, import_node_path.resolve)(start);
301
+ let directory = (0, import_node_path2.resolve)(start);
124
302
  while (true) {
125
303
  result.push(directory);
126
- const parent = (0, import_node_path.dirname)(directory);
304
+ const parent = (0, import_node_path2.dirname)(directory);
127
305
  if (parent === directory) {
128
306
  break;
129
307
  }
@@ -131,21 +309,21 @@ function ancestors(start) {
131
309
  }
132
310
  return result;
133
311
  }
134
- function isRelativeTo(path, parent) {
135
- const normalizedPath = (0, import_node_path.resolve)(path);
136
- const normalizedParent = (0, import_node_path.resolve)(parent);
137
- return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${import_node_path.sep}`);
312
+ function isRelativeTo2(path, parent) {
313
+ const normalizedPath = (0, import_node_path2.resolve)(path);
314
+ const normalizedParent = (0, import_node_path2.resolve)(parent);
315
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${import_node_path2.sep}`);
138
316
  }
139
- function isFile(path) {
317
+ function isFile2(path) {
140
318
  try {
141
- return (0, import_node_fs.statSync)(path).isFile();
319
+ return (0, import_node_fs2.statSync)(path).isFile();
142
320
  } catch {
143
321
  return false;
144
322
  }
145
323
  }
146
- function isDirectory(path) {
324
+ function isDirectory2(path) {
147
325
  try {
148
- return (0, import_node_fs.statSync)(path).isDirectory();
326
+ return (0, import_node_fs2.statSync)(path).isDirectory();
149
327
  } catch {
150
328
  return false;
151
329
  }
@@ -1,15 +1,190 @@
1
1
  // ts/src/python-env.ts
2
- import { readFileSync, readdirSync, statSync } from "fs";
3
- import { dirname, isAbsolute, join, resolve, sep } from "path";
4
- function findProjectRoot(cwd) {
2
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3
+ import { dirname, isAbsolute, join, resolve as resolve2, sep as sep2 } from "path";
4
+
5
+ // ts/src/workspace.ts
6
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
7
+ import { relative, resolve, sep } from "path";
8
+ import { parse } from "smol-toml";
9
+ function findUvWorkspace(cwd) {
5
10
  for (const directory of ancestors(cwd)) {
6
- if (isFile(join(directory, "pyproject.toml")) || isFile(join(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile(join(directory, "package.json")) || isDirectory(join(directory, "node_modules"))) {
11
+ const pyproject = `${directory}/pyproject.toml`;
12
+ if (!existsSync(pyproject)) {
13
+ continue;
14
+ }
15
+ const data = readPyproject(pyproject);
16
+ if (!hasUvWorkspace(data)) {
17
+ continue;
18
+ }
19
+ const members = findWorkspaceMembers(directory, data);
20
+ return {
21
+ root: directory,
22
+ members,
23
+ currentMember: findCurrentMember(cwd, members)
24
+ };
25
+ }
26
+ return null;
27
+ }
28
+ function readPyproject(path) {
29
+ try {
30
+ const data = parse(readFileSync(path, "utf8"));
31
+ return isRecord(data) ? data : {};
32
+ } catch {
33
+ return {};
34
+ }
35
+ }
36
+ function hasUvWorkspace(data) {
37
+ const tool = data["tool"];
38
+ if (!isRecord(tool)) {
39
+ return false;
40
+ }
41
+ const uv = tool["uv"];
42
+ if (!isRecord(uv)) {
43
+ return false;
44
+ }
45
+ return isRecord(uv["workspace"]);
46
+ }
47
+ function findWorkspaceMembers(root, data) {
48
+ const workspace = getWorkspaceTable(data);
49
+ if (workspace === null || !Array.isArray(workspace["members"])) {
50
+ return [];
51
+ }
52
+ const excludes = Array.isArray(workspace["exclude"]) ? workspace["exclude"].filter(
53
+ (value) => typeof value === "string"
54
+ ) : [];
55
+ const members = /* @__PURE__ */ new Set();
56
+ for (const memberGlob of workspace["members"]) {
57
+ if (typeof memberGlob !== "string") {
58
+ continue;
59
+ }
60
+ for (const member of expandMemberGlob(root, memberGlob)) {
61
+ const relativePath = relative(root, member).split(sep).join("/");
62
+ if (isExcluded(relativePath, excludes)) {
63
+ continue;
64
+ }
65
+ if (isFile(`${member}/pyproject.toml`)) {
66
+ members.add(resolve(member));
67
+ }
68
+ }
69
+ }
70
+ return [...members].sort();
71
+ }
72
+ function getWorkspaceTable(data) {
73
+ const tool = data["tool"];
74
+ if (!isRecord(tool)) {
75
+ return null;
76
+ }
77
+ const uv = tool["uv"];
78
+ if (!isRecord(uv)) {
79
+ return null;
80
+ }
81
+ const workspace = uv["workspace"];
82
+ return isRecord(workspace) ? workspace : null;
83
+ }
84
+ function expandMemberGlob(root, pattern) {
85
+ const parts = pattern.split("/");
86
+ const results = [];
87
+ walkGlob(root, parts, results);
88
+ return results;
89
+ }
90
+ function walkGlob(directory, parts, results) {
91
+ if (parts.length === 0) {
92
+ if (isDirectory(directory)) {
93
+ results.push(directory);
94
+ }
95
+ return;
96
+ }
97
+ const [part, ...rest] = parts;
98
+ if (part === "*") {
99
+ for (const entry of readDirSafe(directory)) {
100
+ const child = `${directory}/${entry}`;
101
+ if (isDirectory(child)) {
102
+ walkGlob(child, rest, results);
103
+ }
104
+ }
105
+ return;
106
+ }
107
+ walkGlob(`${directory}/${part}`, rest, results);
108
+ }
109
+ function isExcluded(relativePath, excludes) {
110
+ return excludes.some(
111
+ (pattern) => globMatches(relativePath, pattern.replace(/\/$/, ""))
112
+ );
113
+ }
114
+ function globMatches(value, pattern) {
115
+ if (pattern === value) {
116
+ return true;
117
+ }
118
+ const regex = new RegExp(`^${pattern.split("*").map(escapeRegex).join(".*")}$`);
119
+ return regex.test(value);
120
+ }
121
+ function findCurrentMember(cwd, members) {
122
+ const resolvedCwd = resolve(cwd);
123
+ const matches = members.filter((member) => isRelativeTo(resolvedCwd, member));
124
+ if (matches.length === 0) {
125
+ return null;
126
+ }
127
+ return matches.sort((left, right) => right.length - left.length)[0] ?? null;
128
+ }
129
+ function ancestors(start) {
130
+ const result = [];
131
+ let directory = resolve(start);
132
+ while (true) {
133
+ result.push(directory);
134
+ const parent = resolve(directory, "..");
135
+ if (parent === directory) {
136
+ break;
137
+ }
138
+ directory = parent;
139
+ }
140
+ return result;
141
+ }
142
+ function isRelativeTo(path, parent) {
143
+ const normalizedPath = resolve(path);
144
+ const normalizedParent = resolve(parent);
145
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${sep}`);
146
+ }
147
+ function isRecord(value) {
148
+ return typeof value === "object" && value !== null && !Array.isArray(value);
149
+ }
150
+ function isFile(path) {
151
+ try {
152
+ return statSync(path).isFile();
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+ function isDirectory(path) {
158
+ try {
159
+ return statSync(path).isDirectory();
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+ function readDirSafe(path) {
165
+ try {
166
+ return isDirectory(path) ? readdirSync(path) : [];
167
+ } catch {
168
+ return [];
169
+ }
170
+ }
171
+ var escapeRegex = (value) => value.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
172
+
173
+ // ts/src/python-env.ts
174
+ function findProjectRoot(cwd) {
175
+ const workspace = findUvWorkspace(cwd);
176
+ if (workspace !== null) {
177
+ return workspace.root;
178
+ }
179
+ for (const directory of ancestors2(cwd)) {
180
+ if (isFile2(join(directory, "pyproject.toml")) || isFile2(join(directory, "uv.lock")) || venvFromDotVenv(directory) !== null || isFile2(join(directory, "package.json")) || isDirectory2(join(directory, "node_modules"))) {
7
181
  return directory;
8
182
  }
9
183
  }
10
184
  return null;
11
185
  }
12
186
  function findVenv(cwd = process.cwd()) {
187
+ const workspace = findUvWorkspace(cwd);
13
188
  const projectRoot = findProjectRoot(cwd) ?? cwd;
14
189
  const uvProjectEnvironment = process.env["UV_PROJECT_ENVIRONMENT"];
15
190
  if (uvProjectEnvironment) {
@@ -18,35 +193,38 @@ function findVenv(cwd = process.cwd()) {
18
193
  return path;
19
194
  }
20
195
  }
21
- for (const directory of ancestors(cwd)) {
196
+ if (workspace !== null) {
197
+ return venvFromDotVenv(workspace.root);
198
+ }
199
+ for (const directory of ancestors2(cwd)) {
22
200
  const venv = venvFromDotVenv(directory);
23
201
  if (venv !== null) {
24
202
  return venv;
25
203
  }
26
204
  }
27
205
  const virtualEnv = process.env["VIRTUAL_ENV"];
28
- if (virtualEnv && isVenvDir(virtualEnv) && isRelativeTo(virtualEnv, projectRoot)) {
206
+ if (virtualEnv && isVenvDir(virtualEnv) && isRelativeTo2(virtualEnv, projectRoot)) {
29
207
  return virtualEnv;
30
208
  }
31
209
  const condaPrefix = process.env["CONDA_PREFIX"];
32
- if (condaPrefix && isDirectory(condaPrefix)) {
210
+ if (condaPrefix && isDirectory2(condaPrefix)) {
33
211
  return condaPrefix;
34
212
  }
35
213
  return null;
36
214
  }
37
215
  function getSitePackagesDir(venvPath) {
38
216
  const windowsSitePackages = join(venvPath, "Lib", "site-packages");
39
- if (isDirectory(windowsSitePackages)) {
217
+ if (isDirectory2(windowsSitePackages)) {
40
218
  return windowsSitePackages;
41
219
  }
42
220
  for (const libName of ["lib", "lib64"]) {
43
221
  const libDir = join(venvPath, libName);
44
- if (!isDirectory(libDir)) {
222
+ if (!isDirectory2(libDir)) {
45
223
  continue;
46
224
  }
47
- for (const child of readdirSync(libDir).sort().reverse()) {
225
+ for (const child of readdirSync2(libDir).sort().reverse()) {
48
226
  const sitePackages = join(libDir, child, "site-packages");
49
- if (child.startsWith("python") && isDirectory(sitePackages)) {
227
+ if (child.startsWith("python") && isDirectory2(sitePackages)) {
50
228
  return sitePackages;
51
229
  }
52
230
  }
@@ -55,23 +233,23 @@ function getSitePackagesDir(venvPath) {
55
233
  }
56
234
  var getSitePackages = getSitePackagesDir;
57
235
  function findNodeModules(cwd = process.cwd()) {
58
- for (const directory of ancestors(cwd)) {
236
+ for (const directory of ancestors2(cwd)) {
59
237
  const nodeModules = join(directory, "node_modules");
60
- if (isDirectory(nodeModules)) {
238
+ if (isDirectory2(nodeModules)) {
61
239
  return nodeModules;
62
240
  }
63
241
  }
64
242
  return null;
65
243
  }
66
244
  function isVenvDir(directory) {
67
- return isFile(join(directory, "pyvenv.cfg"));
245
+ return isFile2(join(directory, "pyvenv.cfg"));
68
246
  }
69
247
  function venvFromDotVenv(projectRoot) {
70
248
  const dotVenv = join(projectRoot, ".venv");
71
- if (isDirectory(dotVenv)) {
249
+ if (isDirectory2(dotVenv)) {
72
250
  return isVenvDir(dotVenv) ? dotVenv : null;
73
251
  }
74
- if (isFile(dotVenv)) {
252
+ if (isFile2(dotVenv)) {
75
253
  return readVenvRedirectFile(dotVenv);
76
254
  }
77
255
  return null;
@@ -79,7 +257,7 @@ function venvFromDotVenv(projectRoot) {
79
257
  function readVenvRedirectFile(path) {
80
258
  let content;
81
259
  try {
82
- content = readFileSync(path, "utf8");
260
+ content = readFileSync2(path, "utf8");
83
261
  } catch {
84
262
  return null;
85
263
  }
@@ -90,9 +268,9 @@ function readVenvRedirectFile(path) {
90
268
  const venv = isAbsolute(redirect) ? redirect : join(dirname(path), redirect);
91
269
  return isVenvDir(venv) ? venv : null;
92
270
  }
93
- function ancestors(start) {
271
+ function ancestors2(start) {
94
272
  const result = [];
95
- let directory = resolve(start);
273
+ let directory = resolve2(start);
96
274
  while (true) {
97
275
  result.push(directory);
98
276
  const parent = dirname(directory);
@@ -103,21 +281,21 @@ function ancestors(start) {
103
281
  }
104
282
  return result;
105
283
  }
106
- function isRelativeTo(path, parent) {
107
- const normalizedPath = resolve(path);
108
- const normalizedParent = resolve(parent);
109
- return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${sep}`);
284
+ function isRelativeTo2(path, parent) {
285
+ const normalizedPath = resolve2(path);
286
+ const normalizedParent = resolve2(parent);
287
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}${sep2}`);
110
288
  }
111
- function isFile(path) {
289
+ function isFile2(path) {
112
290
  try {
113
- return statSync(path).isFile();
291
+ return statSync2(path).isFile();
114
292
  } catch {
115
293
  return false;
116
294
  }
117
295
  }
118
- function isDirectory(path) {
296
+ function isDirectory2(path) {
119
297
  try {
120
- return statSync(path).isDirectory();
298
+ return statSync2(path).isDirectory();
121
299
  } catch {
122
300
  return false;
123
301
  }