Commit

Packages pages: Maven, NuGet and RubyGems, routed to the packages service

/-/maven/, /-/nuget/ and /-/rubygems/ go to the packages service as /-/cargo/ does. Each registry has its label, its card on the Packages page with its start command and guide, its install commands on a package's page (a Maven version to fetch, the feed URL for dotnet add package, Bundler's source credentials) and its tint and icon.

syntaqxcommitted Parent8b027dfBrowse files
7 files+153−50/7 viewed
+7−1
1−import { Container, Layers, Package } from "lucide-react";
1+import { Boxes, Container, Feather, Gem, Layers, Package } from "lucide-react";
22
33 import type { Ecosystem } from "@g1t/contracts";
44
1111 composer: "#c08a5b",
1212 go: "#00add8",
1313 cargo: "#dea584",
14+ maven: "#e8762b",
15+ nuget: "#3d8fd6",
16+ rubygems: "#e9573f",
1417 };
1518
1619 /** A small tile marking which registry a package is in. */
2629 {ecosystem === "container" && <Container size={glyph} />}
2730 {ecosystem === "composer" && <Layers size={glyph} />}
2831 {ecosystem === "cargo" && <Package size={glyph} />}
32+ {ecosystem === "maven" && <Feather size={glyph} />}
33+ {ecosystem === "nuget" && <Boxes size={glyph} />}
34+ {ecosystem === "rubygems" && <Gem size={glyph} />}
2935 {ecosystem === "npm" && <span style={{ fontSize: glyph }}>n</span>}
3036 {ecosystem === "go" && <span style={{ fontSize: Math.round(size * 0.38) }}>GO</span>}
3137 </span>
+29−0
4545 assert.equal(installCommands(pkg, null, "ada").install, "cargo add http-client --registry acme");
4646 });
4747
48+test("a Maven artifact is fetched from its workspace's repository, with a settings.xml server for private ones", () => {
49+ const pkg = { ecosystem: "maven" as const, address: "g1t.sh/-/maven/acme/com.acme:web", name: "com.acme:web", workspace: "acme" };
50+ assert.deepEqual(installCommands(pkg, "1.2.0", "ada"), {
51+ login:
52+ "mkdir -p ~/.m2 && [ ! -e ~/.m2/settings.xml ] && printf '<settings><servers><server><id>acme</id><username>ada</username><password>YOUR_TOKEN</password></server></servers></settings>\\n' > ~/.m2/settings.xml",
53+ install: "mvn dependency:get -Dartifact=com.acme:web:1.2.0 -DremoteRepositories=acme::default::https://g1t.sh/-/maven/acme/",
54+ });
55+ assert.match(installCommands(pkg, null, "ada").install, /-Dartifact=com\.acme:web:LATEST /);
56+});
57+
58+test("a NuGet package is added from its workspace's feed, named as a source with credentials for private ones", () => {
59+ const pkg = { ecosystem: "nuget" as const, address: "g1t.sh/-/nuget/acme/Acme.Web", name: "Acme.Web", workspace: "acme" };
60+ assert.deepEqual(installCommands(pkg, "1.0.0", "ada"), {
61+ registry: "dotnet nuget add source https://g1t.sh/-/nuget/acme/v3/index.json --name acme",
62+ login: "dotnet nuget update source acme --username ada --password YOUR_TOKEN --store-password-in-clear-text",
63+ install: "dotnet add package Acme.Web --version 1.0.0 --source https://g1t.sh/-/nuget/acme/v3/index.json",
64+ });
65+ assert.equal(installCommands(pkg, null, "ada").install, "dotnet add package Acme.Web --source https://g1t.sh/-/nuget/acme/v3/index.json");
66+});
67+
68+test("a gem is added with Bundler from its workspace's registry, with credentials for private ones", () => {
69+ const pkg = { ecosystem: "rubygems" as const, address: "g1t.sh/-/rubygems/acme/hello", name: "hello", workspace: "acme" };
70+ assert.deepEqual(installCommands(pkg, "0.1.0", "ada"), {
71+ login: "bundle config set --global https://g1t.sh/-/rubygems/acme/ ada:YOUR_TOKEN",
72+ install: "bundle add hello --version 0.1.0 --source https://g1t.sh/-/rubygems/acme/",
73+ });
74+ assert.equal(installCommands(pkg, null, "ada").install, "bundle add hello --source https://g1t.sh/-/rubygems/acme/");
75+});
76+
4877 test("a container image is pulled by its address and tag", () => {
4978 const pkg = { ecosystem: "container" as const, address: "g1t.sh/acme/web", name: "web", workspace: "acme" };
5079 assert.deepEqual(installCommands(pkg, "latest", "ada"), {
+49−0
1111 composer: "Composer",
1212 cargo: "Cargo",
1313 go: "Go",
14+ maven: "Maven",
15+ nuget: "NuGet",
16+ rubygems: "RubyGems",
1417 };
1518
1619 /**
5356 guide: "/guides/go/",
5457 ready: true,
5558 },
59+ {
60+ ecosystem: "maven",
61+ blurb: "Java and Kotlin libraries in a Maven repository of the workspace's own, deployed with Maven or Gradle, SNAPSHOTs included.",
62+ start: "mvn deploy",
63+ guide: "/guides/maven/",
64+ ready: true,
65+ },
66+ {
67+ ecosystem: "nuget",
68+ blurb: ".NET packages in a NuGet feed of the workspace's own, pushed with dotnet nuget push and restored with dotnet.",
69+ start: "dotnet nuget push <package>.nupkg --source <workspace>",
70+ guide: "/guides/nuget/",
71+ ready: true,
72+ },
73+ {
74+ ecosystem: "rubygems",
75+ blurb: "Ruby gems in a registry of the workspace's own, pushed with gem push and installed with Bundler.",
76+ start: "gem push <gem>.gem --host https://g1t.sh/-/rubygems/<workspace>",
77+ guide: "/guides/rubygems/",
78+ ready: true,
79+ },
5680 ];
5781
5882 export type PackageSort = "updated" | "downloads" | "name";
156180 login: `go env -w GOPRIVATE=${host}/${pkg.workspace}`,
157181 install: `go get ${pkg.address}${version ? `@${version}` : ""}`,
158182 };
183+ case "maven": {
184+ // Maven takes the repository with the artifact; its credentials are a
185+ // <server> of the same id in ~/.m2/settings.xml, written here only
186+ // when there is no settings.xml yet.
187+ const repository = `https://${host}/-/maven/${pkg.workspace}/`;
188+ return {
189+ login: `mkdir -p ~/.m2 && [ ! -e ~/.m2/settings.xml ] && printf '<settings><servers><server><id>${pkg.workspace}</id><username>${you}</username><password>YOUR_TOKEN</password></server></servers></settings>\\n' > ~/.m2/settings.xml`,
190+ install: `mvn dependency:get -Dartifact=${pkg.name}${version ? `:${version}` : ":LATEST"} -DremoteRepositories=${pkg.workspace}::default::${repository}`,
191+ };
192+ }
193+ case "nuget":
194+ // The workspace's feed as a named source (needed for any install),
195+ // then the credentials a private package also needs.
196+ return {
197+ registry: `dotnet nuget add source https://${host}/-/nuget/${pkg.workspace}/v3/index.json --name ${pkg.workspace}`,
198+ login: `dotnet nuget update source ${pkg.workspace} --username ${you} --password YOUR_TOKEN --store-password-in-clear-text`,
199+ install: `dotnet add package ${pkg.name}${version ? ` --version ${version}` : ""} --source https://${host}/-/nuget/${pkg.workspace}/v3/index.json`,
200+ };
201+ case "rubygems": {
202+ const source = `https://${host}/-/rubygems/${pkg.workspace}/`;
203+ return {
204+ login: `bundle config set --global ${source} ${you}:YOUR_TOKEN`,
205+ install: `bundle add ${pkg.name}${version ? ` --version ${version}` : ""} --source ${source}`,
206+ };
207+ }
159208 }
160209 }
+10−1
77 import type { DeploysSetting, Ecosystem, PackageSummary, ProjectEcosystem } from "@g1t/contracts";
88
99 /** As lib/packages.ts names each registry; repeated so this module stands alone in tests. */
10−const REGISTRY: Record<Ecosystem, string> = { container: "Container", npm: "npm", composer: "Composer", cargo: "Cargo", go: "Go" };
10+const REGISTRY: Record<Ecosystem, string> = {
11+ container: "Container",
12+ npm: "npm",
13+ composer: "Composer",
14+ cargo: "Cargo",
15+ go: "Go",
16+ maven: "Maven",
17+ nuget: "NuGet",
18+ rubygems: "RubyGems",
19+};
1120
1221 const DOCS = "https://docs.g1t.sh";
1322
+46−0
6464 assert.equal(servicePath("/acme/-/cargo/x"), null);
6565 });
6666
67+test("the Maven repositories go to the packages service", () => {
68+ for (const path of [
69+ "/-/maven/acme/com/acme/web/maven-metadata.xml",
70+ "/-/maven/acme/com/acme/web/maven-metadata.xml.sha1",
71+ "/-/maven/acme/com/acme/web/1.0.0/web-1.0.0.jar",
72+ "/-/maven/acme/com/acme/web/1.0-SNAPSHOT/web-1.0-20261006.120000-1.pom",
73+ // An artifact named like a git endpoint is still Maven's.
74+ "/-/maven/acme/com/acme/info/refs",
75+ ]) {
76+ assert.equal(servicePath(path), "packages", path);
77+ }
78+ assert.equal(servicePath("/-/maven"), null);
79+ assert.equal(servicePath("/acme/-/maven/x"), null);
80+});
81+
82+test("the NuGet feeds go to the packages service", () => {
83+ for (const path of [
84+ "/-/nuget/acme/v3/index.json",
85+ "/-/nuget/acme/v3/flatcontainer/acme.web/index.json",
86+ "/-/nuget/acme/v3/flatcontainer/acme.web/1.0.0/acme.web.1.0.0.nupkg",
87+ "/-/nuget/acme/v3/registration/acme.web/index.json",
88+ "/-/nuget/acme/v3/query",
89+ "/-/nuget/acme/api/v2/package",
90+ "/-/nuget/acme/api/v2/package/Acme.Web/1.0.0",
91+ ]) {
92+ assert.equal(servicePath(path), "packages", path);
93+ }
94+ assert.equal(servicePath("/-/nuget"), null);
95+ assert.equal(servicePath("/acme/-/nuget/x"), null);
96+});
97+
98+test("the RubyGems registries go to the packages service", () => {
99+ for (const path of [
100+ "/-/rubygems/acme/versions",
101+ "/-/rubygems/acme/info/hello",
102+ "/-/rubygems/acme/names",
103+ "/-/rubygems/acme/gems/hello-0.1.0.gem",
104+ "/-/rubygems/acme/api/v1/gems",
105+ "/-/rubygems/acme/api/v1/gems/yank",
106+ ]) {
107+ assert.equal(servicePath(path), "packages", path);
108+ }
109+ assert.equal(servicePath("/-/rubygems"), null);
110+ assert.equal(servicePath("/acme/-/rubygems/x"), null);
111+});
112+
67113 test("git goes to repos, and everything else is the site's", () => {
68114 assert.equal(servicePath("/acme/web.git/info/refs"), "git");
69115 assert.equal(servicePath("/acme/web/git-receive-pack"), "git");
+9−1
1313 const COMPOSER_PATH = /^\/-\/composer\//;
1414 /** The Cargo registries: `/-/cargo/<workspace>/`, a sparse index and its web API, one per workspace. */
1515 const CARGO_PATH = /^\/-\/cargo\//;
16+/** The Maven repositories: `/-/maven/<workspace>/`, in the standard layout, one per workspace. */
17+const MAVEN_PATH = /^\/-\/maven\//;
18+/** The NuGet feeds: `/-/nuget/<workspace>/v3/index.json` and what it names, one per workspace. */
19+const NUGET_PATH = /^\/-\/nuget\//;
20+/** The RubyGems registries: `/-/rubygems/<workspace>/`, the compact index and `gem push`, one per workspace. */
21+const RUBYGEMS_PATH = /^\/-\/rubygems\//;
22+
23+const REGISTRY_PATHS = [REGISTRY_PATH, NPM_PATH, COMPOSER_PATH, CARGO_PATH, MAVEN_PATH, NUGET_PATH, RUBYGEMS_PATH];
1624
1725 export type ServicePath = "git" | "packages" | null;
1826
1927 export function servicePath(pathname: string): ServicePath {
20− if (REGISTRY_PATH.test(pathname) || NPM_PATH.test(pathname) || COMPOSER_PATH.test(pathname) || CARGO_PATH.test(pathname)) return "packages";
28+ if (REGISTRY_PATHS.some((path) => path.test(pathname))) return "packages";
2129 if (GIT_PATH.test(pathname)) return "git";
2230 return null;
2331 }
+3−2
7272 const { detail, username } = loaderData;
7373 const { package: pkg, versions, tags, permissions } = detail;
7474 const outcome = actionData as Outcome | undefined;
75− const latest = tags.find((tag) => tag.tag === "latest")?.tag ?? tags[0]?.tag ?? null;
75+ // Maven names a version to fetch; the others install their newest without one.
76+ const latest = tags.find((tag) => tag.tag === "latest")?.tag ?? tags[0]?.tag ?? (pkg.ecosystem === "maven" ? pkg.latest : null);
7677 const commands = installCommands(pkg, latest, username);
7778 // npm and Composer versions are numbers; images are digests and tags.
7879 const npm = pkg.ecosystem !== "container";
130131 with <code className="font-mono">packages:read</code>.
131132 </p>
132133 )}
133− {commands.registry && pkg.visibility === "private" && pkg.ecosystem !== "cargo" && (
134+ {pkg.visibility === "private" && commands.login.includes("YOUR_TOKEN") && (
134135 <p className="text-xs text-faint">
135136 Put an{" "}
136137 <Link to="/settings/tokens" className="text-muted hover:text-fg">