@buildinternet/releases-api-types 0.30.0 → 0.31.0

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": "@buildinternet/releases-api-types",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Wire protocol — request/response types for the Releases registry API. Shared by web, workers, and clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/api-types.ts CHANGED
@@ -12,6 +12,7 @@ export type {
12
12
  } from "@buildinternet/releases-core/source-enums";
13
13
  // Re-export above doesn't create local bindings; import for use in interfaces below.
14
14
  import type { SourceType, SourceFetchPriority } from "@buildinternet/releases-core/source-enums";
15
+ import type { ApiScope } from "@buildinternet/releases-core/api-token";
15
16
  import type {
16
17
  MediaItemSchema,
17
18
  PaginationSchema,
@@ -288,6 +289,12 @@ export {
288
289
  OrgRecentReleasesResponseSchema,
289
290
  DeleteOrgAccountResponseSchema,
290
291
  } from "./schemas/orgs.js";
292
+ export {
293
+ ReleasesJsonConfigSchema,
294
+ ReleasesJsonProductSchema,
295
+ SyncWellKnownResponseSchema,
296
+ } from "./schemas/well-known.js";
297
+ export type { ReleasesJsonConfig, ReleasesJsonProduct } from "./schemas/well-known.js";
291
298
  export {
292
299
  SourceListItemSchema,
293
300
  SourceWithOrgSchema,
@@ -489,6 +496,38 @@ export interface ListResponse<T> {
489
496
  pagination: Pagination;
490
497
  }
491
498
 
499
+ // === User-owned API keys (relu_) — self-serve surface served by /v1/api-keys ===
500
+
501
+ /** A user-owned API key (relu_) as returned by GET /v1/api-keys. */
502
+ export interface UserApiKey {
503
+ id: string;
504
+ name: string | null;
505
+ start: string | null;
506
+ scope: ApiScope | null;
507
+ enabled: boolean | null;
508
+ remaining: number | null;
509
+ lastRequest: string | null; // ISO 8601
510
+ createdAt: string; // ISO 8601
511
+ expiresAt: string | null; // ISO 8601
512
+ }
513
+
514
+ /** POST /v1/api-keys create response — includes the full key string exactly once. */
515
+ export interface CreatedUserApiKey extends Omit<UserApiKey, "enabled" | "lastRequest"> {
516
+ key: string;
517
+ }
518
+
519
+ /** GET /v1/api-keys response envelope. */
520
+ export interface ListUserApiKeysResponse {
521
+ apiKeys: UserApiKey[];
522
+ }
523
+
524
+ /** POST /v1/api-keys request body. Self-serve mints are capped at read server-side. */
525
+ export interface CreateUserApiKeyBody {
526
+ name: string;
527
+ scope?: ApiScope;
528
+ expiresInDays?: number;
529
+ }
530
+
492
531
  export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
493
532
 
494
533
  // ── Sitemap (bulk URL emission) ──
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { ReleasesJsonConfigSchema } from "./well-known.js";
3
+
4
+ describe("ReleasesJsonConfigSchema", () => {
5
+ it("accepts an empty object (no-op file)", () => {
6
+ expect(ReleasesJsonConfigSchema.safeParse({}).success).toBe(true);
7
+ });
8
+
9
+ it("accepts a full org-scope file", () => {
10
+ const r = ReleasesJsonConfigSchema.safeParse({
11
+ $schema: "https://releases.sh/schemas/releases.json",
12
+ name: "Acme",
13
+ description: "CI for teams.",
14
+ category: "developer-tools",
15
+ avatar: "https://acme.com/logo.png",
16
+ tags: ["ci", "observability"],
17
+ social: { twitter: "acmehq", github: "acme" },
18
+ notice: { message: "Docs moved", href: "https://acme.com/docs" },
19
+ });
20
+ expect(r.success).toBe(true);
21
+ });
22
+
23
+ it("accepts a product-scope file", () => {
24
+ const r = ReleasesJsonConfigSchema.safeParse({
25
+ product: { name: "Acme Cloud", slug: "acme-cloud", category: "cloud", kind: "platform" },
26
+ });
27
+ expect(r.success).toBe(true);
28
+ });
29
+
30
+ it("rejects a non-https avatar", () => {
31
+ expect(ReleasesJsonConfigSchema.safeParse({ avatar: "http://acme.com/x.png" }).success).toBe(
32
+ false,
33
+ );
34
+ });
35
+
36
+ it("rejects a product with no name", () => {
37
+ expect(ReleasesJsonConfigSchema.safeParse({ product: { slug: "x" } }).success).toBe(false);
38
+ });
39
+
40
+ it("rejects an unknown product kind", () => {
41
+ expect(
42
+ ReleasesJsonConfigSchema.safeParse({ product: { name: "Acme Cloud", kind: "saas" } }).success,
43
+ ).toBe(false);
44
+ });
45
+
46
+ it("rejects an over-long notice message", () => {
47
+ const r = ReleasesJsonConfigSchema.safeParse({ notice: { message: "x".repeat(281) } });
48
+ expect(r.success).toBe(false);
49
+ });
50
+
51
+ it("strips unknown top-level keys", () => {
52
+ const r = ReleasesJsonConfigSchema.parse({ name: "Acme", bogus: 1 });
53
+ expect("bogus" in r).toBe(false);
54
+ });
55
+ });
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { KIND_VALUES } from "@buildinternet/releases-core/kinds";
3
+ import { NoticeSchema } from "./shared.js";
4
+
5
+ /** A single social handle/URL. Bare handles allowed; URLs must be https. */
6
+ const SocialValueSchema = z.string().min(1).max(200);
7
+
8
+ /** Product-scope block: declares the hosting repo's source belongs to this product. */
9
+ export const ReleasesJsonProductSchema = z
10
+ .object({
11
+ name: z.string().min(1).max(120),
12
+ slug: z.string().min(1).max(120).optional(),
13
+ description: z.string().max(2000).optional(),
14
+ // Accepted leniently as a string; resolved/validated against CATEGORIES at apply time.
15
+ category: z.string().min(1).max(120).optional(),
16
+ kind: z.enum(KIND_VALUES).optional(),
17
+ })
18
+ .strict();
19
+
20
+ /**
21
+ * One file name, two hosting scopes. Org-identity keys are honored only from a
22
+ * domain `.well-known/releases.json`; `product` is honored only from a repo-root
23
+ * file. The server enforces which keys it honors based on the host the file came
24
+ * from — this schema only validates shape.
25
+ */
26
+ export const ReleasesJsonConfigSchema = z
27
+ .object({
28
+ $schema: z.url().optional(),
29
+ // Org scope
30
+ name: z.string().min(1).max(120).optional(),
31
+ description: z.string().max(2000).optional(),
32
+ category: z.string().min(1).max(120).optional(),
33
+ avatar: z
34
+ .url()
35
+ .refine((u) => u.startsWith("https://"), "avatar must be an https URL")
36
+ .optional(),
37
+ tags: z.array(z.string().min(1).max(60)).max(50).optional(),
38
+ social: z.record(z.string().min(1).max(40), SocialValueSchema).optional(),
39
+ notice: NoticeSchema.optional(),
40
+ // Source/product scope
41
+ product: ReleasesJsonProductSchema.optional(),
42
+ })
43
+ .strip();
44
+
45
+ export type ReleasesJsonConfig = z.infer<typeof ReleasesJsonConfigSchema>;
46
+ export type ReleasesJsonProduct = z.infer<typeof ReleasesJsonProductSchema>;
47
+
48
+ /** Response shape of POST /v1/orgs/:slug/sync-well-known. */
49
+ export const SyncWellKnownResponseSchema = z.object({
50
+ fetched: z.boolean(),
51
+ applied: z.boolean(),
52
+ skippedReason: z.string().optional(),
53
+ plan: z.unknown().optional(),
54
+ });