{"id":50575970,"url":"https://github.com/tomitribe/tomitribe-nexus","last_synced_at":"2026-06-04T22:01:48.148Z","repository":{"id":362134626,"uuid":"1255613022","full_name":"tomitribe/tomitribe-nexus","owner":"tomitribe","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-02T20:41:49.000Z","size":160,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T21:26:13.221Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Roff","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomitribe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T02:37:55.000Z","updated_at":"2026-06-02T20:41:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tomitribe/tomitribe-nexus","commit_stats":null,"previous_names":["tomitribe/tomitribe-nexus"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/tomitribe/tomitribe-nexus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Ftomitribe-nexus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Ftomitribe-nexus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Ftomitribe-nexus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Ftomitribe-nexus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomitribe","download_url":"https://codeload.github.com/tomitribe/tomitribe-nexus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Ftomitribe-nexus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33921352,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T22:01:47.438Z","updated_at":"2026-06-04T22:01:48.140Z","avatar_url":"https://github.com/tomitribe.png","language":"Roff","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tomitribe-nexus\n\nA read-only [`java.nio.file.FileSystem`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/file/FileSystem.html)\nover a remote Maven repository's HTML index — Nexus 2, Nexus 3, Maven Central, or any\n`mod_autoindex`-style listing.\n\nYou point it at a repository's base URL and get back an ordinary `Path`. From there you use the\nstandard `java.nio.file.Files` API — `walk`, `list`, `copy`, `readAttributes` — to browse and\ndownload artifacts as if the remote repository were a local directory tree. No new vocabulary to\nlearn: if you know NIO, you know this.\n\n```java\nPath root = Nexus.builder()\n        .baseUri(\"https://repo1.maven.org/maven2/\")\n        .build();\n\nPath version = root.resolve(\"org/apache/tomee/apache-tomee/9.0.1\");\n\ntry (Stream\u003cPath\u003e tree = Files.walk(version)) {\n    tree.filter(Files::isRegularFile)\n        .filter(p -\u003e p.getFileName().toString().endsWith(\".tar.gz\"))\n        .forEach(p -\u003e copy(p, target.resolve(p.getFileName().toString())));\n}\n```\n\n## Dependency\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eorg.tomitribe.nexus\u003c/groupId\u003e\n  \u003cartifactId\u003etomitribe-nexus\u003c/artifactId\u003e\n  \u003cversion\u003e1.0.3\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nRequires Java 17+.\n\n## Getting a root\n\n`Nexus.builder()` is the only entry point. `build()` returns the `Path` for the repository root.\n\n```java\n// Anonymous (e.g. Maven Central)\nPath root = Nexus.builder()\n        .baseUri(\"https://repo1.maven.org/maven2/\")\n        .build();\n\n// Authenticated (HTTP Basic)\nPath root = Nexus.builder()\n        .baseUri(\"https://nexus.example/content/repositories/releases/\")\n        .credentials(\"user\", \"pass\")   // optional — omit for anonymous\n        .build();\n```\n\nCredentials, when supplied, are sent only to the configured base host — they are never attached to\na request for any other host.\n\n## Browsing\n\nThe root is a `Path` rooted at the base URL. Build sub-paths with `resolve(...)`, exactly as you\nwould on the default filesystem:\n\n```java\nPath artifact = root.resolve(\"org/apache/tomee/apache-tomee\");\n\n// relative paths built with the ordinary NIO API work too — including on Windows,\n// where the separator differs\nPath same = root.resolve(Path.of(\"org\", \"apache\", \"tomee\", \"apache-tomee\"));\n```\n\n**List the immediate children** of a directory (e.g. the available versions):\n\n```java\ntry (Stream\u003cPath\u003e children = Files.list(artifact)) {\n    children.filter(Files::isDirectory)              // version folders; skip maven-metadata.xml etc.\n            .map(p -\u003e p.getFileName().toString())\n            .sorted()\n            .forEach(System.out::println);\n}\n\n// or a DirectoryStream\ntry (DirectoryStream\u003cPath\u003e children = Files.newDirectoryStream(artifact)) {\n    for (Path child : children) System.out.println(child);\n}\n```\n\n**Walk a whole subtree:**\n\n```java\ntry (Stream\u003cPath\u003e tree = Files.walk(root.resolve(\"org/apache/tomee/apache-tomee/9.0.1\"))) {\n    tree.forEach(System.out::println);\n}\n```\n\n## Downloading\n\nCopy a remote file straight to local disk — it's just `Files.copy` across providers:\n\n```java\nPath remote = root.resolve(\"org/apache/tomee/apache-tomee/9.0.1/apache-tomee-9.0.1.tar.gz\");\nFiles.copy(remote, Path.of(\"/tmp/apache-tomee-9.0.1.tar.gz\"), StandardCopyOption.REPLACE_EXISTING);\n```\n\nOr stream the bytes yourself:\n\n```java\ntry (InputStream in = Files.newInputStream(remote)) {\n    // ...\n}\n```\n\n## Attributes\n\nSize and last-modified come back through the standard attribute API. When they were present in the\ndirectory listing they cost nothing; otherwise they are filled by a single `HEAD` on demand:\n\n```java\nBasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);\nattrs.size();\nattrs.lastModifiedTime();\nattrs.isDirectory();\nattrs.isRegularFile();\n\n// or the convenience methods\nlong size = Files.size(p);\nFileTime modified = Files.getLastModifiedTime(p);\nboolean exists = Files.exists(p);          // false for a 404, never throws\nboolean dir = Files.isDirectory(p);\n```\n\n## How it behaves\n\n- **Read-only.** Every mutating operation (`createDirectory`, `delete`, `move`, writing) throws\n  `ReadOnlyFileSystemException`. Reading bytes from a directory, or listing a file, throws\n  `UnsupportedOperationException`. A path that doesn't exist surfaces as `NoSuchFileException`.\n\n- **Rooted at the base — off-base is unrepresentable.** The root `/` *is* the base URL, and `..`\n  can never climb above it. There is no host or scheme to point elsewhere, so you cannot, even by\n  accident, aim an authenticated client at another location.\n\n- **Lazy.** Naming a path (`root.resolve(...)`) costs nothing — no network. The first operation\n  that needs the remote (`Files.isDirectory`, `newInputStream`, `readAttributes`, ...) resolves it\n  with a single `HEAD`, cached thereafter. A `Files.walk` is request-minimal: one listing per\n  directory, with each entry's kind, size, and date read from that listing — it never `HEAD`s a\n  file just to walk past it.\n\n- **Server-aware.** Directory-vs-file is determined from the listing and, for a hand-built path,\n  from the `HEAD` response — handling Nexus 2 (a directory `HEAD` is `200` with no `Content-Type`),\n  Nexus 3 / autoindex (`text/html` listings), and Maven Central.\n\n## Limitations\n\nIt is a genuine `java.nio.file.Path`, but a *remote, read-only* one, so two NIO operations don't\napply:\n\n- **`Path.toFile()` throws.** A Nexus path is not backed by a local `java.io.File`. Use `Files.copy`\n  to bring it to disk, then work with the local file.\n- **Byte-channel reads aren't implemented.** `Files.newByteChannel` (and therefore\n  `Files.readAllBytes` / `Files.readString`) is unsupported — read with `Files.newInputStream` or\n  copy with `Files.copy` instead.\n\n## License\n\nApache License 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomitribe%2Ftomitribe-nexus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomitribe%2Ftomitribe-nexus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomitribe%2Ftomitribe-nexus/lists"}