@buildinternet/releases-api-types 0.58.3 → 0.60.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.
Files changed (125) hide show
  1. package/README.md +14 -6
  2. package/dist/api-types.d.ts +1168 -0
  3. package/dist/api-types.d.ts.map +1 -0
  4. package/dist/api-types.js +134 -0
  5. package/dist/schemas/account-profile.d.ts +29 -0
  6. package/dist/schemas/account-profile.d.ts.map +1 -0
  7. package/dist/schemas/account-profile.js +24 -0
  8. package/dist/schemas/ai-models.d.ts +67 -0
  9. package/dist/schemas/ai-models.d.ts.map +1 -0
  10. package/{src/schemas/ai-models.ts → dist/schemas/ai-models.js} +25 -31
  11. package/dist/schemas/api-keys.d.ts +71 -0
  12. package/dist/schemas/api-keys.d.ts.map +1 -0
  13. package/dist/schemas/api-keys.js +32 -0
  14. package/dist/schemas/classifications.d.ts +134 -0
  15. package/dist/schemas/classifications.d.ts.map +1 -0
  16. package/dist/schemas/classifications.js +96 -0
  17. package/dist/schemas/collections.d.ts +605 -0
  18. package/dist/schemas/collections.d.ts.map +1 -0
  19. package/dist/schemas/collections.js +320 -0
  20. package/dist/schemas/datasets.d.ts +34 -0
  21. package/dist/schemas/datasets.d.ts.map +1 -0
  22. package/dist/schemas/datasets.js +11 -0
  23. package/dist/schemas/errors.d.ts +43 -0
  24. package/dist/schemas/errors.d.ts.map +1 -0
  25. package/dist/schemas/errors.js +61 -0
  26. package/dist/schemas/feedback.d.ts +134 -0
  27. package/dist/schemas/feedback.d.ts.map +1 -0
  28. package/dist/schemas/feedback.js +76 -0
  29. package/dist/schemas/follows.d.ts +154 -0
  30. package/dist/schemas/follows.d.ts.map +1 -0
  31. package/dist/schemas/follows.js +42 -0
  32. package/dist/schemas/listing.d.ts +374 -0
  33. package/dist/schemas/listing.d.ts.map +1 -0
  34. package/dist/schemas/listing.js +175 -0
  35. package/dist/schemas/lookups.d.ts +285 -0
  36. package/dist/schemas/lookups.d.ts.map +1 -0
  37. package/{src/schemas/lookups.ts → dist/schemas/lookups.js} +67 -80
  38. package/dist/schemas/marketing-classifier.d.ts +32 -0
  39. package/dist/schemas/marketing-classifier.d.ts.map +1 -0
  40. package/{src/schemas/marketing-classifier.ts → dist/schemas/marketing-classifier.js} +14 -18
  41. package/dist/schemas/orgs.d.ts +950 -0
  42. package/dist/schemas/orgs.d.ts.map +1 -0
  43. package/dist/schemas/orgs.js +473 -0
  44. package/dist/schemas/overviews.d.ts +205 -0
  45. package/dist/schemas/overviews.d.ts.map +1 -0
  46. package/{src/schemas/overviews.ts → dist/schemas/overviews.js} +41 -54
  47. package/dist/schemas/products.d.ts +595 -0
  48. package/dist/schemas/products.d.ts.map +1 -0
  49. package/{src/schemas/products.ts → dist/schemas/products.js} +113 -136
  50. package/dist/schemas/publish-tokens.d.ts +55 -0
  51. package/dist/schemas/publish-tokens.d.ts.map +1 -0
  52. package/{src/schemas/publish-tokens.ts → dist/schemas/publish-tokens.js} +18 -29
  53. package/dist/schemas/recommendations.d.ts +52 -0
  54. package/dist/schemas/recommendations.d.ts.map +1 -0
  55. package/dist/schemas/recommendations.js +58 -0
  56. package/dist/schemas/related.d.ts +331 -0
  57. package/dist/schemas/related.d.ts.map +1 -0
  58. package/dist/schemas/related.js +115 -0
  59. package/dist/schemas/releases.d.ts +623 -0
  60. package/dist/schemas/releases.d.ts.map +1 -0
  61. package/dist/schemas/releases.js +414 -0
  62. package/dist/schemas/resolve.d.ts +259 -0
  63. package/dist/schemas/resolve.d.ts.map +1 -0
  64. package/{src/schemas/resolve.ts → dist/schemas/resolve.js} +2 -5
  65. package/dist/schemas/search.d.ts +475 -0
  66. package/dist/schemas/search.d.ts.map +1 -0
  67. package/dist/schemas/search.js +258 -0
  68. package/dist/schemas/semantic-alert-summary.d.ts +54 -0
  69. package/dist/schemas/semantic-alert-summary.d.ts.map +1 -0
  70. package/dist/schemas/semantic-alert-summary.js +57 -0
  71. package/dist/schemas/semantic-alerts.d.ts +41 -0
  72. package/dist/schemas/semantic-alerts.d.ts.map +1 -0
  73. package/dist/schemas/semantic-alerts.js +42 -0
  74. package/dist/schemas/shared.d.ts +247 -0
  75. package/dist/schemas/shared.d.ts.map +1 -0
  76. package/dist/schemas/shared.js +248 -0
  77. package/dist/schemas/site-notice.d.ts +35 -0
  78. package/dist/schemas/site-notice.d.ts.map +1 -0
  79. package/{src/schemas/site-notice.ts → dist/schemas/site-notice.js} +14 -18
  80. package/dist/schemas/sitemap.d.ts +97 -0
  81. package/dist/schemas/sitemap.d.ts.map +1 -0
  82. package/dist/schemas/sitemap.js +107 -0
  83. package/dist/schemas/sources.d.ts +1145 -0
  84. package/dist/schemas/sources.d.ts.map +1 -0
  85. package/dist/schemas/sources.js +645 -0
  86. package/dist/schemas/stats.d.ts +82 -0
  87. package/dist/schemas/stats.d.ts.map +1 -0
  88. package/dist/schemas/stats.js +59 -0
  89. package/dist/schemas/taxonomy.d.ts +344 -0
  90. package/dist/schemas/taxonomy.d.ts.map +1 -0
  91. package/{src/schemas/taxonomy.ts → dist/schemas/taxonomy.js} +47 -58
  92. package/dist/schemas/webhooks.d.ts +618 -0
  93. package/dist/schemas/webhooks.d.ts.map +1 -0
  94. package/dist/schemas/webhooks.js +191 -0
  95. package/dist/schemas/well-known.d.ts +302 -0
  96. package/dist/schemas/well-known.d.ts.map +1 -0
  97. package/dist/schemas/well-known.js +192 -0
  98. package/dist/schemas/workspace-integrations.d.ts +27 -0
  99. package/dist/schemas/workspace-integrations.d.ts.map +1 -0
  100. package/dist/schemas/workspace-integrations.js +22 -0
  101. package/package.json +16 -4
  102. package/src/api-types.ts +0 -2223
  103. package/src/schemas/account-profile.ts +0 -28
  104. package/src/schemas/ai-models.test.ts +0 -29
  105. package/src/schemas/classifications.ts +0 -109
  106. package/src/schemas/collections.ts +0 -346
  107. package/src/schemas/errors.test.ts +0 -55
  108. package/src/schemas/errors.ts +0 -81
  109. package/src/schemas/feedback.ts +0 -56
  110. package/src/schemas/listing.ts +0 -226
  111. package/src/schemas/orgs.ts +0 -533
  112. package/src/schemas/recommendations.ts +0 -39
  113. package/src/schemas/related.ts +0 -126
  114. package/src/schemas/releases.ts +0 -436
  115. package/src/schemas/search.ts +0 -274
  116. package/src/schemas/semantic-alert-summary.ts +0 -65
  117. package/src/schemas/shared.notice.test.ts +0 -36
  118. package/src/schemas/shared.ts +0 -267
  119. package/src/schemas/site-notice.test.ts +0 -67
  120. package/src/schemas/sitemap.ts +0 -114
  121. package/src/schemas/sources.ts +0 -674
  122. package/src/schemas/stats.ts +0 -62
  123. package/src/schemas/well-known.test.ts +0 -264
  124. package/src/schemas/well-known.ts +0 -252
  125. package/src/schemas/workspace-integrations.ts +0 -26
@@ -1,62 +0,0 @@
1
- import { z } from "zod";
2
- import { StatsSchema } from "./shared.js";
3
-
4
- /**
5
- * Per-source activity row in `GET /v1/stats`. `orgName` is nullable because
6
- * the source-to-org JOIN is left-joined (legacy orphan sources still exist).
7
- */
8
- export const StatsSourceActivitySchema = z.object({
9
- sourceName: z.string(),
10
- sourceSlug: z.string(),
11
- sourceType: z.string(),
12
- orgName: z.string().nullable(),
13
- lastFetchedAt: z.string().nullable(),
14
- totalReleases: z.number(),
15
- recentReleases: z.number(),
16
- });
17
-
18
- /**
19
- * Recent fetch-log row, joined with source + org for display. `durationMs`
20
- * and `error` are nullable on the underlying table; `orgName` is nullable
21
- * for the same orphan-source reason as above.
22
- */
23
- export const StatsRecentActivitySchema = z.object({
24
- sourceName: z.string(),
25
- sourceSlug: z.string(),
26
- orgName: z.string().nullable(),
27
- releasesFound: z.number(),
28
- releasesInserted: z.number(),
29
- totalReleases: z.number(),
30
- status: z.string(),
31
- durationMs: z.number().nullable(),
32
- error: z.string().nullable(),
33
- createdAt: z.string(),
34
- });
35
-
36
- /**
37
- * Response shape for `GET /v1/stats`. The handler returns a hybrid payload —
38
- * the flat `Stats` counts (legacy `orgs/sources/releases/products` fields used
39
- * by the web homepage banner) merged with the richer `StatsSummary` shape
40
- * (period window + totals + sourceHealth + sourceActivity + recentActivity).
41
- * Modelled with `z.object` over the union of both shapes; consumers may
42
- * pick either subset.
43
- */
44
- export const StatsResponseSchema = StatsSchema.extend({
45
- period: z.object({
46
- days: z.number().int().min(1),
47
- cutoff: z.string(),
48
- }),
49
- totals: z.object({
50
- organizations: z.number().int().min(0),
51
- sources: z.number().int().min(0),
52
- releases: z.number().int().min(0),
53
- releasesInPeriod: z.number().int().min(0),
54
- }),
55
- sourceHealth: z.object({
56
- upToDate: z.number().int().min(0),
57
- stale: z.number().int().min(0),
58
- neverFetched: z.number().int().min(0),
59
- }),
60
- sourceActivity: z.array(StatsSourceActivitySchema),
61
- recentActivity: z.array(StatsRecentActivitySchema),
62
- });
@@ -1,264 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- ReleasesJsonConfigSchema,
4
- ReleasesJsonDomainSchema,
5
- ReleasesJsonRepoSchema,
6
- } from "./well-known.js";
7
-
8
- const location = (n: number) => ({ url: `https://acme.com/releases/${n}` });
9
-
10
- describe("releases.json v2 schemas", () => {
11
- it("accepts the minimal domain manifest", () => {
12
- expect(
13
- ReleasesJsonDomainSchema.parse({
14
- version: 2,
15
- releases: [{ url: "https://acme.com/updates", feed: "https://acme.com/updates.xml" }],
16
- }),
17
- ).toEqual({
18
- version: 2,
19
- releases: [{ url: "https://acme.com/updates", feed: "https://acme.com/updates.xml" }],
20
- });
21
- });
22
-
23
- it("accepts the full domain shape with advisory taxonomy", () => {
24
- const parsed = ReleasesJsonDomainSchema.parse({
25
- $schema: "https://releases.sh/schemas/releases.json",
26
- version: 2,
27
- name: "Acme",
28
- description: "CI for teams.",
29
- category: "future-category",
30
- avatar: "https://acme.com/logo.png",
31
- tags: ["future-tag"],
32
- social: { twitter: "acmehq", github: "acme" },
33
- products: [
34
- {
35
- name: "Acme Cloud",
36
- slug: "acme-cloud",
37
- kind: "future-kind",
38
- category: "future-category",
39
- description: "Managed CI runners.",
40
- website: "https://acme.com/cloud",
41
- docs: "https://docs.acme.com/cloud",
42
- support: "https://acme.com/support",
43
- social: { twitter: "acmecloud" },
44
- tags: ["ci", "cloud"],
45
- archived: true,
46
- releases: [{ github: "acme/cloud", canonical: true }],
47
- },
48
- ],
49
- registries: {
50
- "releases.sh": {
51
- org: "org_abc123",
52
- verification: "verification-token",
53
- futureKey: true,
54
- },
55
- "example.com": { anything: "goes" },
56
- },
57
- });
58
-
59
- expect(parsed.products?.[0]?.kind).toBe("future-kind");
60
- expect(parsed.products?.[0]?.tags).toEqual(["ci", "cloud"]);
61
- expect(parsed.registries?.["example.com"]).toEqual({ anything: "goes" });
62
- });
63
-
64
- it("accepts product-level tags and enforces the same bounds as org tags", () => {
65
- expect(
66
- ReleasesJsonDomainSchema.safeParse({
67
- version: 2,
68
- products: [{ name: "Cloud", tags: ["analytics", "sdk"] }],
69
- }).success,
70
- ).toBe(true);
71
- // too many tags (> 50)
72
- expect(
73
- ReleasesJsonDomainSchema.safeParse({
74
- version: 2,
75
- products: [{ name: "Cloud", tags: Array.from({ length: 51 }, (_, i) => `tag-${i}`) }],
76
- }).success,
77
- ).toBe(false);
78
- // empty tag string
79
- expect(
80
- ReleasesJsonDomainSchema.safeParse({
81
- version: 2,
82
- products: [{ name: "Cloud", tags: [""] }],
83
- }).success,
84
- ).toBe(false);
85
- });
86
-
87
- it("accepts the repo variant including github self", () => {
88
- expect(
89
- ReleasesJsonRepoSchema.safeParse({
90
- version: 2,
91
- product: { name: "Acme Cloud", slug: "acme-cloud" },
92
- releases: [{ github: "self", canonical: true }],
93
- registries: { "releases.sh": { product: "prd_abc123" } },
94
- }).success,
95
- ).toBe(true);
96
- });
97
-
98
- it("requires https for locator URLs and avatar", () => {
99
- expect(
100
- ReleasesJsonDomainSchema.safeParse({ version: 2, releases: [{ url: "https://acme.com/x" }] })
101
- .success,
102
- ).toBe(true);
103
- expect(
104
- ReleasesJsonDomainSchema.safeParse({ version: 2, releases: [{ url: "http://acme.com/x" }] })
105
- .success,
106
- ).toBe(false);
107
- expect(
108
- ReleasesJsonDomainSchema.safeParse({ version: 2, avatar: "http://acme.com/logo.png" })
109
- .success,
110
- ).toBe(false);
111
- });
112
-
113
- it("requires the version 2 literal with no v1 compatibility", () => {
114
- expect(ReleasesJsonConfigSchema.safeParse({ releases: [location(1)] }).success).toBe(false);
115
- expect(ReleasesJsonConfigSchema.safeParse({ version: 1 }).success).toBe(false);
116
- expect(ReleasesJsonConfigSchema.safeParse({ version: 3 }).success).toBe(false);
117
- });
118
-
119
- it("requires at least one locator per release location", () => {
120
- expect(
121
- ReleasesJsonDomainSchema.safeParse({ version: 2, releases: [{ title: "Updates" }] }).success,
122
- ).toBe(false);
123
- });
124
-
125
- it("accepts a push-publish locator on github + path", () => {
126
- expect(
127
- ReleasesJsonDomainSchema.parse({
128
- version: 2,
129
- releases: [
130
- { github: "acme/docs", path: "changelog/**/*.mdx", publish: "push", title: "Docs" },
131
- ],
132
- }),
133
- ).toEqual({
134
- version: 2,
135
- releases: [
136
- { github: "acme/docs", path: "changelog/**/*.mdx", publish: "push", title: "Docs" },
137
- ],
138
- });
139
- expect(
140
- ReleasesJsonRepoSchema.safeParse({
141
- version: 2,
142
- releases: [{ github: "self", path: "CHANGELOG.md", publish: "push" }],
143
- }).success,
144
- ).toBe(true);
145
- });
146
-
147
- it("rejects an incomplete or unsafe push-publish locator", () => {
148
- expect(
149
- ReleasesJsonDomainSchema.safeParse({
150
- version: 2,
151
- releases: [{ github: "acme/docs", publish: "push" }],
152
- }).success,
153
- ).toBe(false);
154
- expect(
155
- ReleasesJsonDomainSchema.safeParse({
156
- version: 2,
157
- releases: [{ path: "CHANGELOG.md", publish: "push" }],
158
- }).success,
159
- ).toBe(false);
160
- expect(
161
- ReleasesJsonDomainSchema.safeParse({
162
- version: 2,
163
- releases: [{ github: "acme/docs", path: "CHANGELOG.md" }],
164
- }).success,
165
- ).toBe(false);
166
- expect(
167
- ReleasesJsonDomainSchema.safeParse({
168
- version: 2,
169
- releases: [{ github: "acme/docs", path: "CHANGELOG.md", publish: "poll" }],
170
- }).success,
171
- ).toBe(false);
172
- expect(
173
- ReleasesJsonDomainSchema.safeParse({
174
- version: 2,
175
- releases: [{ github: "acme/docs", path: "../secrets", publish: "push" }],
176
- }).success,
177
- ).toBe(false);
178
- expect(
179
- ReleasesJsonDomainSchema.safeParse({
180
- version: 2,
181
- releases: [{ github: "acme/docs", path: "/etc/passwd", publish: "push" }],
182
- }).success,
183
- ).toBe(false);
184
- });
185
-
186
- it("allows github self only in repo files", () => {
187
- expect(
188
- ReleasesJsonDomainSchema.safeParse({ version: 2, releases: [{ github: "self" }] }).success,
189
- ).toBe(false);
190
- expect(
191
- ReleasesJsonRepoSchema.safeParse({ version: 2, releases: [{ github: "self" }] }).success,
192
- ).toBe(true);
193
- });
194
-
195
- it("enforces product and release-location caps", () => {
196
- expect(
197
- ReleasesJsonDomainSchema.safeParse({
198
- version: 2,
199
- products: Array.from({ length: 25 }, (_, i) => ({ name: `Product ${i}` })),
200
- }).success,
201
- ).toBe(false);
202
- expect(
203
- ReleasesJsonDomainSchema.safeParse({
204
- version: 2,
205
- products: [{ name: "Cloud", releases: Array.from({ length: 9 }, (_, i) => location(i)) }],
206
- }).success,
207
- ).toBe(false);
208
- expect(
209
- ReleasesJsonDomainSchema.safeParse({
210
- version: 2,
211
- releases: Array.from({ length: 33 }, (_, i) => location(i)),
212
- }).success,
213
- ).toBe(false);
214
- expect(
215
- ReleasesJsonDomainSchema.safeParse({
216
- version: 2,
217
- releases: Array.from({ length: 25 }, (_, i) => location(i)),
218
- products: [
219
- { name: "Cloud", releases: Array.from({ length: 8 }, (_, i) => location(i + 25)) },
220
- ],
221
- }).success,
222
- ).toBe(false);
223
- });
224
-
225
- it("allows at most one canonical location per scope", () => {
226
- const canonical = (n: number) => ({ ...location(n), canonical: true });
227
- expect(
228
- ReleasesJsonDomainSchema.safeParse({
229
- version: 2,
230
- releases: [canonical(1), canonical(2)],
231
- }).success,
232
- ).toBe(false);
233
- expect(
234
- ReleasesJsonDomainSchema.safeParse({
235
- version: 2,
236
- products: [{ name: "Cloud", releases: [canonical(1), canonical(2)] }],
237
- }).success,
238
- ).toBe(false);
239
- });
240
-
241
- it("rejects removed v1 identity fields", () => {
242
- expect(
243
- ReleasesJsonDomainSchema.safeParse({ version: 2, website: "https://acme.com" }).success,
244
- ).toBe(false);
245
- expect(
246
- ReleasesJsonDomainSchema.safeParse({ version: 2, notice: { message: "Moved" } }).success,
247
- ).toBe(false);
248
- });
249
-
250
- it("requires typed stable ids for the releases.sh registry", () => {
251
- expect(
252
- ReleasesJsonDomainSchema.safeParse({
253
- version: 2,
254
- registries: { "releases.sh": { org: "prd_wrong" } },
255
- }).success,
256
- ).toBe(false);
257
- expect(
258
- ReleasesJsonRepoSchema.safeParse({
259
- version: 2,
260
- registries: { "releases.sh": { product: "org_wrong" } },
261
- }).success,
262
- ).toBe(false);
263
- });
264
- });
@@ -1,252 +0,0 @@
1
- import { z } from "zod";
2
-
3
- const MAX_PRODUCTS = 24;
4
- const MAX_PRODUCT_RELEASES = 8;
5
- const MAX_FILE_RELEASES = 32;
6
-
7
- /** A single social handle/URL. Bare handles are intentionally accepted. */
8
- const SocialValueSchema = z.string().min(1).max(200);
9
- const SocialSchema = z.record(z.string().min(1).max(40), SocialValueSchema);
10
-
11
- // `z.url().startsWith(...)` keeps the runtime URL + https check AND serializes to
12
- // JSON Schema as `format: "uri"` + `pattern: "^https://"` — so the generated
13
- // public schema rejects non-https schemes too (a plain `.refine()` would be
14
- // silently dropped by z.toJSONSchema).
15
- const HttpsUrlSchema = z.url().startsWith("https://", "locator must be an https URL");
16
-
17
- /** Shared tag-array bounds — kept identical for org-level and product-level tags. */
18
- const TagsSchema = z.array(z.string().min(1).max(60)).max(50);
19
-
20
- /**
21
- * Changelog file or glob inside the declared repo (Action `changelog-path` /
22
- * `changelog-glob`). Relative only — no scheme, no leading slash, no `..`.
23
- */
24
- const PublishPathSchema = z
25
- .string()
26
- .min(1)
27
- .max(200)
28
- .regex(
29
- /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._~@%+\-/*?[\]{}]+$/,
30
- "path must be a relative changelog file or glob",
31
- );
32
-
33
- const ReleaseLocationFields = {
34
- url: HttpsUrlSchema.optional(),
35
- feed: HttpsUrlSchema.optional(),
36
- appstore: HttpsUrlSchema.optional(),
37
- file: HttpsUrlSchema.optional(),
38
- title: z.string().min(1).max(200).optional(),
39
- canonical: z.boolean().optional(),
40
- /**
41
- * `"push"` means the owner publishes this changelog (GitHub Action / publish
42
- * token) instead of us polling it. Requires `github` + `path`.
43
- */
44
- publish: z.literal("push").optional(),
45
- /** Required with `publish: "push"`. The file or glob the Action watches. */
46
- path: PublishPathSchema.optional(),
47
- };
48
-
49
- function hasLocator(value: {
50
- url?: string;
51
- feed?: string;
52
- github?: string;
53
- appstore?: string;
54
- file?: string;
55
- }): boolean {
56
- return Boolean(value.url || value.feed || value.github || value.appstore || value.file);
57
- }
58
-
59
- /** `publish: "push"` is a mode on a github locator, not a locator by itself. */
60
- function pushPublishComplete(value: { github?: string; publish?: "push"; path?: string }): boolean {
61
- if (value.publish === "push") return Boolean(value.github && value.path);
62
- return value.path === undefined;
63
- }
64
-
65
- /** A domain manifest can name a concrete GitHub repository, but never `self`. */
66
- export const ReleasesJsonDomainReleaseSchema = z
67
- .strictObject({
68
- ...ReleaseLocationFields,
69
- github: z
70
- .string()
71
- .regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/, "github must be owner/repo")
72
- .optional(),
73
- })
74
- .refine(hasLocator, "at least one release locator is required")
75
- .refine(
76
- pushPublishComplete,
77
- 'publish: "push" requires github (owner/repo) and path (changelog file or glob)',
78
- );
79
-
80
- /** A repo manifest additionally accepts `github: "self"`. */
81
- export const ReleasesJsonRepoReleaseSchema = z
82
- .strictObject({
83
- ...ReleaseLocationFields,
84
- github: z
85
- .union([
86
- z.literal("self"),
87
- z.string().regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/, "github must be owner/repo"),
88
- ])
89
- .optional(),
90
- })
91
- .refine(hasLocator, "at least one release locator is required")
92
- .refine(
93
- pushPublishComplete,
94
- 'publish: "push" requires github (owner/repo or "self") and path (changelog file or glob)',
95
- );
96
-
97
- function releasesArray<T extends z.ZodType>(item: T, max: number) {
98
- return z
99
- .array(item)
100
- .max(max)
101
- .refine(
102
- (items) =>
103
- items.filter((entry) => Boolean((entry as { canonical?: boolean }).canonical)).length <= 1,
104
- "at most one canonical release location is allowed per scope",
105
- );
106
- }
107
-
108
- const DomainProductReleasesSchema = releasesArray(
109
- ReleasesJsonDomainReleaseSchema,
110
- MAX_PRODUCT_RELEASES,
111
- );
112
- const RepoReleasesSchema = releasesArray(ReleasesJsonRepoReleaseSchema, MAX_FILE_RELEASES);
113
- const DomainReleasesSchema = releasesArray(ReleasesJsonDomainReleaseSchema, MAX_FILE_RELEASES);
114
-
115
- /**
116
- * Product declaration in a domain manifest. Taxonomy strings are deliberately
117
- * lenient here; the reconciler resolves known values and ignores the rest.
118
- */
119
- export const ReleasesJsonProductSchema = z.strictObject({
120
- name: z.string().min(1).max(120),
121
- slug: z.string().min(1).max(120).optional(),
122
- kind: z.string().min(1).max(120).optional(),
123
- category: z.string().min(1).max(120).optional(),
124
- description: z.string().max(2000).optional(),
125
- website: z.url().optional(),
126
- docs: z.url().optional(),
127
- support: z.url().optional(),
128
- social: SocialSchema.optional(),
129
- tags: TagsSchema.optional(),
130
- archived: z.boolean().optional(),
131
- releases: DomainProductReleasesSchema.optional(),
132
- });
133
-
134
- /** Repo-scope product binding. Slug is only a creation suggestion. */
135
- export const ReleasesJsonRepoProductSchema = z.strictObject({
136
- name: z.string().min(1).max(120),
137
- slug: z.string().min(1).max(120).optional(),
138
- });
139
-
140
- const ReleasesRegistrySchema = z
141
- .object({
142
- org: z
143
- .string()
144
- .regex(/^org_[A-Za-z0-9]+$/)
145
- .optional(),
146
- product: z
147
- .string()
148
- .regex(/^prd_[A-Za-z0-9]+$/)
149
- .optional(),
150
- verification: z.string().min(1).max(500).optional(),
151
- })
152
- .catchall(z.unknown());
153
-
154
- /** Registry-specific extensions are forward-compatible by design. */
155
- export const ReleasesJsonRegistriesSchema = z
156
- .object({
157
- "releases.sh": ReleasesRegistrySchema.optional(),
158
- })
159
- .catchall(z.unknown());
160
-
161
- /** Top-level fields shared by every manifest scope. */
162
- const BaseFields = {
163
- $schema: z.url().optional(),
164
- version: z.literal(2),
165
- registries: ReleasesJsonRegistriesSchema.optional(),
166
- };
167
-
168
- const CommonFields = {
169
- ...BaseFields,
170
- releases: DomainReleasesSchema.optional(),
171
- };
172
-
173
- /** Domain-hosted variant: flat org identity plus product and release declarations. */
174
- export const ReleasesJsonDomainSchema = z
175
- .strictObject({
176
- ...CommonFields,
177
- name: z.string().min(1).max(120).optional(),
178
- description: z.string().max(2000).optional(),
179
- category: z.string().min(1).max(120).optional(),
180
- avatar: HttpsUrlSchema.optional(),
181
- tags: TagsSchema.optional(),
182
- social: SocialSchema.optional(),
183
- products: z.array(ReleasesJsonProductSchema).max(MAX_PRODUCTS).optional(),
184
- })
185
- .refine(
186
- (manifest) =>
187
- (manifest.releases?.length ?? 0) +
188
- (manifest.products?.reduce((sum, product) => sum + (product.releases?.length ?? 0), 0) ??
189
- 0) <=
190
- MAX_FILE_RELEASES,
191
- `at most ${MAX_FILE_RELEASES} release locations are allowed per file`,
192
- );
193
-
194
- /** Repo-root variant: product binding plus repo-scoped release declarations. */
195
- export const ReleasesJsonRepoSchema = z.strictObject({
196
- ...BaseFields,
197
- product: ReleasesJsonRepoProductSchema.optional(),
198
- releases: RepoReleasesSchema.optional(),
199
- });
200
-
201
- /** Public schema accepts either hosting scope; reconcilers use the scoped schema. */
202
- export const ReleasesJsonConfigSchema = z.union([ReleasesJsonDomainSchema, ReleasesJsonRepoSchema]);
203
-
204
- export type ReleasesJsonConfig = z.infer<typeof ReleasesJsonConfigSchema>;
205
- export type ReleasesJsonDomain = z.infer<typeof ReleasesJsonDomainSchema>;
206
- export type ReleasesJsonRepo = z.infer<typeof ReleasesJsonRepoSchema>;
207
- export type ReleasesJsonProduct = z.infer<typeof ReleasesJsonProductSchema>;
208
- export type ReleasesJsonDomainRelease = z.infer<typeof ReleasesJsonDomainReleaseSchema>;
209
- export type ReleasesJsonRepoRelease = z.infer<typeof ReleasesJsonRepoReleaseSchema>;
210
-
211
- /**
212
- * Body for `POST /v1/orgs/stub` — curator-authored stub org (#1947). Mirrors
213
- * the domain-manifest identity + product + locator shape (so a curator can hand
214
- * the same declaration a manifest would carry), minus manifest-transport fields
215
- * (`version`/`$schema`/`registries`) and plus the registry-identity fields a
216
- * stub org needs to be resolvable (`slug`/`domain`). Locators are optional — an
217
- * identity-only stub (locations TBD) is valid. `category`/taxonomy strings stay
218
- * lenient; the route resolves them and ignores the rest, same as the reconciler.
219
- */
220
- export const CreateStubOrgBodySchema = z.strictObject({
221
- name: z.string().min(1).max(120),
222
- slug: z.string().min(1).max(120).optional(),
223
- domain: z.string().min(1).max(255).optional(),
224
- description: z.string().max(2000).optional(),
225
- category: z.string().min(1).max(120).optional(),
226
- avatar: HttpsUrlSchema.optional(),
227
- tags: TagsSchema.optional(),
228
- social: SocialSchema.optional(),
229
- products: z.array(ReleasesJsonProductSchema).max(MAX_PRODUCTS).optional(),
230
- releases: DomainReleasesSchema.optional(),
231
- });
232
-
233
- export type CreateStubOrgBody = z.infer<typeof CreateStubOrgBodySchema>;
234
-
235
- /**
236
- * Body for `POST /v1/orgs/stub-from-domain` (#1947) — the unlisted-domain path.
237
- * The worker fetches https://{domain}/.well-known/releases.json and, if valid
238
- * and the domain has no org yet, creates a stub org + declared locators.
239
- */
240
- export const StubFromDomainBodySchema = z.strictObject({
241
- domain: z.string().min(1).max(255),
242
- });
243
-
244
- export type StubFromDomainBody = z.infer<typeof StubFromDomainBodySchema>;
245
-
246
- /** Response shape of POST /v1/orgs/:slug/sync-well-known. */
247
- export const SyncWellKnownResponseSchema = z.object({
248
- fetched: z.boolean(),
249
- applied: z.boolean(),
250
- skippedReason: z.string().optional(),
251
- plan: z.unknown().optional(),
252
- });
@@ -1,26 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const UploadsIntegrationStatusSchema = z.object({
4
- provider: z.literal("uploads"),
5
- connected: z.boolean(),
6
- /** False when the token-encryption key is missing. */
7
- configured: z.boolean(),
8
- connectedAt: z.string().nullable(),
9
- scope: z.string().nullable(),
10
- /** uploads.sh workspace slug from the access-token JWT (`workspace`, else `workspaces[]`). */
11
- uploadsWorkspace: z.string().nullable(),
12
- });
13
-
14
- export const UploadsOAuthConnectResponseSchema = z.object({
15
- authorizeUrl: z.string(),
16
- redirectUri: z.string(),
17
- });
18
-
19
- export const UploadsOAuthCallbackBodySchema = z.object({
20
- code: z.string().min(1),
21
- state: z.string().min(1),
22
- });
23
-
24
- export const UploadsOAuthCallbackResponseSchema = UploadsIntegrationStatusSchema.extend({
25
- workspaceId: z.string(),
26
- });