Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { z } from "zod";

const dockerManifestListContentType = "application/vnd.docker.distribution.manifest.list.v2+json";
import { dockerManifestListContentType } from "./media-types";

const platformSchema = z.object({
"architecture": z.string(),
Expand Down
10 changes: 10 additions & 0 deletions src/media-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Named constants for the manifest media types the registry recognizes during content
// negotiation. These strings are the OCI/Docker content-negotiation contract and must stay
// byte-identical to the specification, so each is defined exactly once here and referenced
// everywhere else. This module has no imports, keeping it a safe leaf to import from anywhere.

export const dockerManifestListContentType = "application/vnd.docker.distribution.manifest.list.v2+json";
export const ociImageIndexContentType = "application/vnd.oci.image.index.v1+json";
export const dockerManifestV1PrettyJwsContentType = "application/vnd.docker.distribution.manifest.v1+prettyjws";
export const ociImageManifestContentType = "application/vnd.oci.image.manifest.v1+json";
export const dockerManifestV2ContentType = "application/vnd.docker.distribution.manifest.v2+json";
18 changes: 12 additions & 6 deletions src/registry/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import {
UploadId,
UploadObject,
} from "./registry";
import { ociImageIndexContentType } from "./r2";
import {
dockerManifestListContentType,
ociImageIndexContentType,
dockerManifestV1PrettyJwsContentType,
ociImageManifestContentType,
dockerManifestV2ContentType,
} from "../media-types";

type AuthContext = {
authType: AuthType;
Expand All @@ -45,12 +51,12 @@ type HTTPContext = {
};

export const manifestTypes = [
"application/vnd.docker.distribution.manifest.list.v2+json",
"application/vnd.oci.image.index.v1+json",
"application/vnd.docker.distribution.manifest.v1+prettyjws",
dockerManifestListContentType,
ociImageIndexContentType,
dockerManifestV1PrettyJwsContentType,
"application/json",
"application/vnd.oci.image.manifest.v1+json",
"application/vnd.docker.distribution.manifest.v2+json",
ociImageManifestContentType,
dockerManifestV2ContentType,
] as const;

export type ManifestType = (typeof manifestTypes)[number];
Expand Down
2 changes: 0 additions & 2 deletions src/registry/r2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ import {
import { GarbageCollectionMode, GarbageCollector } from "./garbage-collector";
import { ManifestSchema, manifestSchema } from "../manifest";

export const ociImageIndexContentType = "application/vnd.oci.image.index.v1+json";

function referrersPrefix(name: string, digest: string): string {
return `${name}/_referrers/${digest}/`;
}
Expand Down
2 changes: 1 addition & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
registries,
} from "./registry/registry";
import { RegistryHTTPClient } from "./registry/http";
import { ociImageIndexContentType } from "./registry/r2";
import { ociImageIndexContentType } from "./media-types";

const maxReferrersListLimit = 1000;
const isOpaqueReferrersCursor = (cursor: string) => cursor.startsWith("/v2/");
Expand Down
33 changes: 33 additions & 0 deletions test/media-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, test } from "vitest";
import { manifestTypes } from "../src/registry/http";
import {
dockerManifestListContentType,
ociImageIndexContentType,
dockerManifestV1PrettyJwsContentType,
ociImageManifestContentType,
dockerManifestV2ContentType,
} from "../src/media-types";

describe("manifest media types", () => {
// The Accept header is emitted as manifestTypes.join(", "), so both the exact string values
// and the array order are part of the content-negotiation wire contract. This pins them so a
// value edit or a reorder fails here rather than silently changing what the registry sends.
test("manifestTypes holds the expected set in the expected order", () => {
expect(manifestTypes).toEqual([
"application/vnd.docker.distribution.manifest.list.v2+json",
"application/vnd.oci.image.index.v1+json",
"application/vnd.docker.distribution.manifest.v1+prettyjws",
"application/json",
"application/vnd.oci.image.manifest.v1+json",
"application/vnd.docker.distribution.manifest.v2+json",
]);
});

test("each manifest media-type constant holds its spec string", () => {
expect(dockerManifestListContentType).toBe("application/vnd.docker.distribution.manifest.list.v2+json");
expect(ociImageIndexContentType).toBe("application/vnd.oci.image.index.v1+json");
expect(dockerManifestV1PrettyJwsContentType).toBe("application/vnd.docker.distribution.manifest.v1+prettyjws");
expect(ociImageManifestContentType).toBe("application/vnd.oci.image.manifest.v1+json");
expect(dockerManifestV2ContentType).toBe("application/vnd.docker.distribution.manifest.v2+json");
});
});