@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.
- package/README.md +14 -6
- package/dist/api-types.d.ts +1168 -0
- package/dist/api-types.d.ts.map +1 -0
- package/dist/api-types.js +134 -0
- package/dist/schemas/account-profile.d.ts +29 -0
- package/dist/schemas/account-profile.d.ts.map +1 -0
- package/dist/schemas/account-profile.js +24 -0
- package/dist/schemas/ai-models.d.ts +67 -0
- package/dist/schemas/ai-models.d.ts.map +1 -0
- package/{src/schemas/ai-models.ts → dist/schemas/ai-models.js} +25 -31
- package/dist/schemas/api-keys.d.ts +71 -0
- package/dist/schemas/api-keys.d.ts.map +1 -0
- package/dist/schemas/api-keys.js +32 -0
- package/dist/schemas/classifications.d.ts +134 -0
- package/dist/schemas/classifications.d.ts.map +1 -0
- package/dist/schemas/classifications.js +96 -0
- package/dist/schemas/collections.d.ts +605 -0
- package/dist/schemas/collections.d.ts.map +1 -0
- package/dist/schemas/collections.js +320 -0
- package/dist/schemas/datasets.d.ts +34 -0
- package/dist/schemas/datasets.d.ts.map +1 -0
- package/dist/schemas/datasets.js +11 -0
- package/dist/schemas/errors.d.ts +43 -0
- package/dist/schemas/errors.d.ts.map +1 -0
- package/dist/schemas/errors.js +61 -0
- package/dist/schemas/feedback.d.ts +134 -0
- package/dist/schemas/feedback.d.ts.map +1 -0
- package/dist/schemas/feedback.js +76 -0
- package/dist/schemas/follows.d.ts +154 -0
- package/dist/schemas/follows.d.ts.map +1 -0
- package/dist/schemas/follows.js +42 -0
- package/dist/schemas/listing.d.ts +374 -0
- package/dist/schemas/listing.d.ts.map +1 -0
- package/dist/schemas/listing.js +175 -0
- package/dist/schemas/lookups.d.ts +285 -0
- package/dist/schemas/lookups.d.ts.map +1 -0
- package/{src/schemas/lookups.ts → dist/schemas/lookups.js} +67 -80
- package/dist/schemas/marketing-classifier.d.ts +32 -0
- package/dist/schemas/marketing-classifier.d.ts.map +1 -0
- package/{src/schemas/marketing-classifier.ts → dist/schemas/marketing-classifier.js} +14 -18
- package/dist/schemas/orgs.d.ts +950 -0
- package/dist/schemas/orgs.d.ts.map +1 -0
- package/dist/schemas/orgs.js +473 -0
- package/dist/schemas/overviews.d.ts +205 -0
- package/dist/schemas/overviews.d.ts.map +1 -0
- package/{src/schemas/overviews.ts → dist/schemas/overviews.js} +41 -54
- package/dist/schemas/products.d.ts +595 -0
- package/dist/schemas/products.d.ts.map +1 -0
- package/{src/schemas/products.ts → dist/schemas/products.js} +113 -136
- package/dist/schemas/publish-tokens.d.ts +55 -0
- package/dist/schemas/publish-tokens.d.ts.map +1 -0
- package/{src/schemas/publish-tokens.ts → dist/schemas/publish-tokens.js} +18 -29
- package/dist/schemas/recommendations.d.ts +52 -0
- package/dist/schemas/recommendations.d.ts.map +1 -0
- package/dist/schemas/recommendations.js +58 -0
- package/dist/schemas/related.d.ts +331 -0
- package/dist/schemas/related.d.ts.map +1 -0
- package/dist/schemas/related.js +115 -0
- package/dist/schemas/releases.d.ts +623 -0
- package/dist/schemas/releases.d.ts.map +1 -0
- package/dist/schemas/releases.js +414 -0
- package/dist/schemas/resolve.d.ts +259 -0
- package/dist/schemas/resolve.d.ts.map +1 -0
- package/{src/schemas/resolve.ts → dist/schemas/resolve.js} +2 -5
- package/dist/schemas/search.d.ts +475 -0
- package/dist/schemas/search.d.ts.map +1 -0
- package/dist/schemas/search.js +258 -0
- package/dist/schemas/semantic-alert-summary.d.ts +54 -0
- package/dist/schemas/semantic-alert-summary.d.ts.map +1 -0
- package/dist/schemas/semantic-alert-summary.js +57 -0
- package/dist/schemas/semantic-alerts.d.ts +41 -0
- package/dist/schemas/semantic-alerts.d.ts.map +1 -0
- package/dist/schemas/semantic-alerts.js +42 -0
- package/dist/schemas/shared.d.ts +247 -0
- package/dist/schemas/shared.d.ts.map +1 -0
- package/dist/schemas/shared.js +248 -0
- package/dist/schemas/site-notice.d.ts +35 -0
- package/dist/schemas/site-notice.d.ts.map +1 -0
- package/{src/schemas/site-notice.ts → dist/schemas/site-notice.js} +14 -18
- package/dist/schemas/sitemap.d.ts +97 -0
- package/dist/schemas/sitemap.d.ts.map +1 -0
- package/dist/schemas/sitemap.js +107 -0
- package/dist/schemas/sources.d.ts +1145 -0
- package/dist/schemas/sources.d.ts.map +1 -0
- package/dist/schemas/sources.js +645 -0
- package/dist/schemas/stats.d.ts +82 -0
- package/dist/schemas/stats.d.ts.map +1 -0
- package/dist/schemas/stats.js +59 -0
- package/dist/schemas/taxonomy.d.ts +344 -0
- package/dist/schemas/taxonomy.d.ts.map +1 -0
- package/{src/schemas/taxonomy.ts → dist/schemas/taxonomy.js} +47 -58
- package/dist/schemas/webhooks.d.ts +618 -0
- package/dist/schemas/webhooks.d.ts.map +1 -0
- package/dist/schemas/webhooks.js +191 -0
- package/dist/schemas/well-known.d.ts +302 -0
- package/dist/schemas/well-known.d.ts.map +1 -0
- package/dist/schemas/well-known.js +192 -0
- package/dist/schemas/workspace-integrations.d.ts +27 -0
- package/dist/schemas/workspace-integrations.d.ts.map +1 -0
- package/dist/schemas/workspace-integrations.js +22 -0
- package/package.json +16 -4
- package/src/api-types.ts +0 -2223
- package/src/schemas/account-profile.ts +0 -28
- package/src/schemas/ai-models.test.ts +0 -29
- package/src/schemas/classifications.ts +0 -109
- package/src/schemas/collections.ts +0 -346
- package/src/schemas/errors.test.ts +0 -55
- package/src/schemas/errors.ts +0 -81
- package/src/schemas/feedback.ts +0 -56
- package/src/schemas/listing.ts +0 -226
- package/src/schemas/orgs.ts +0 -533
- package/src/schemas/recommendations.ts +0 -39
- package/src/schemas/related.ts +0 -126
- package/src/schemas/releases.ts +0 -436
- package/src/schemas/search.ts +0 -274
- package/src/schemas/semantic-alert-summary.ts +0 -65
- package/src/schemas/shared.notice.test.ts +0 -36
- package/src/schemas/shared.ts +0 -267
- package/src/schemas/site-notice.test.ts +0 -67
- package/src/schemas/sitemap.ts +0 -114
- package/src/schemas/sources.ts +0 -674
- package/src/schemas/stats.ts +0 -62
- package/src/schemas/well-known.test.ts +0 -264
- package/src/schemas/well-known.ts +0 -252
- package/src/schemas/workspace-integrations.ts +0 -26
package/src/schemas/stats.ts
DELETED
|
@@ -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
|
-
});
|