-
Notifications
You must be signed in to change notification settings - Fork 68.3k
Expand file tree
/
Copy pathsync.ts
More file actions
149 lines (133 loc) · 5.8 KB
/
Copy pathsync.ts
File metadata and controls
149 lines (133 loc) · 5.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { readFile, writeFile, unlink } from 'fs/promises'
import { existsSync } from 'fs'
import path from 'path'
import { mkdirp } from 'mkdirp'
import { WEBHOOK_DATA_DIR } from '../lib/index'
import Webhook, { WebhookSchema } from '@/webhooks/scripts/webhook'
interface WebhookFile {
webhooks?: {
post: WebhookSchema
}[]
'x-webhooks'?: {
post: WebhookSchema
}[]
}
export async function syncWebhookData(
sourceDirectory: string,
webhookSchemas: string[],
): Promise<void> {
await Promise.all(
webhookSchemas.map(async (schemaName) => {
const file = path.join(sourceDirectory, schemaName)
const schema: WebhookFile = JSON.parse(await readFile(file, 'utf-8'))
// In OpenAPI version 3.1, the schema data is under the `webhooks`
// key, but in 3.0 the schema data was in `x-webhooks`.
// We just fallback to `x-webhooks` for now since there's
// currently no difference in the schema data between versions.
const webhookSchemaData = schema.webhooks ?? schema['x-webhooks']
if (!webhookSchemaData) {
console.log(
`🟡 No webhooks exist in ${sourceDirectory}/${schemaName}. No static webhook files will be generated.`,
)
return
}
const webhooks = Object.values(webhookSchemaData).map((webhook) => new Webhook(webhook.post))
await processWebhookSchema(webhooks)
const data = await formatWebhookData(webhooks)
if (Object.keys(data).length === 0) {
throw new Error(
`Generating Webhook data failed for ${sourceDirectory}/${schemaName}. The generated data file was empty.`,
)
}
const versionName = path.basename(schemaName, '.json')
const targetDirectory = path.join(WEBHOOK_DATA_DIR, versionName)
if (!existsSync(targetDirectory)) {
await mkdirp(targetDirectory)
}
// Write one JSON file per webhook category (e.g. check_run.json) instead
// of a single monolithic schema.json. This allows the server to load only
// the requested webhook on demand rather than the entire version schema.
//
// childParamsGroups are split into a separate file ({category}.child-params.json)
// so the landing page never loads them — they're only fetched on drill-down.
await Promise.all(
Object.entries(data).map(async ([category, categoryData]) => {
const childParams: Record<string, Record<string, unknown[]>> = {}
const slimCategoryData: Record<string, unknown> = {}
for (const [action, actionData] of Object.entries(
categoryData as Record<
string,
{ bodyParameters?: Array<{ name?: string; childParamsGroups?: unknown[] }> }
>,
)) {
const actionChildParams: Record<string, unknown[]> = {}
let hasChildParams = false
const slimAction = {
...actionData,
bodyParameters: actionData.bodyParameters?.map((param) => {
if (param.childParamsGroups && param.childParamsGroups.length > 0 && param.name) {
actionChildParams[param.name] = param.childParamsGroups
hasChildParams = true
}
return { ...param, childParamsGroups: [] }
}),
}
slimCategoryData[action] = slimAction
if (hasChildParams) {
childParams[action] = actionChildParams
}
}
const targetPath = path.join(targetDirectory, `${category}.json`)
await writeFile(targetPath, JSON.stringify(slimCategoryData, null, 2))
console.log(`✅ Wrote ${targetPath}`)
const childParamsPath = path.join(targetDirectory, `${category}.child-params.json`)
if (Object.keys(childParams).length > 0) {
await writeFile(childParamsPath, JSON.stringify(childParams, null, 2))
console.log(`✅ Wrote ${childParamsPath}`)
} else {
// Remove any stale sidecar from a previous sync where this category
// had child params but no longer does. getWebhook() probes for this
// file unconditionally, so leaving it would reintroduce removed
// nested params on drill-down.
for (const stalePath of [childParamsPath, `${childParamsPath}.br`]) {
if (existsSync(stalePath)) {
await unlink(stalePath)
console.log(`🗑️ Removed stale ${stalePath}`)
}
}
}
}),
)
}),
)
}
async function processWebhookSchema(webhooks: Webhook[]): Promise<void> {
try {
if (webhooks.length) {
await Promise.all(webhooks.map((webhook) => webhook.process()))
}
} catch {
throw new Error(
"🐛 Whoops! It looks like the decorator script wasn't able to parse the dereferenced schema. A recent change may not yet be supported by the decorator. Please reach out in the #docs-engineering slack channel for help.",
)
}
}
// Create an object with all webhooks where the key is the webhook name.
// Webhooks typically have a property called `action` that describes the
// events that trigger the webhook. Some webhooks (like `ping`) don't have
// action types -- in that case we set the value of action to 'default'.
async function formatWebhookData(
webhooks: Webhook[],
): Promise<Record<string, Record<string, Webhook>>> {
const categorizedWebhooks: Record<string, Record<string, Webhook>> = {}
for (const webhook of Object.values(webhooks)) {
if (!webhook.action) webhook.action = 'default'
if (categorizedWebhooks[webhook.category]) {
categorizedWebhooks[webhook.category][webhook.action] = webhook
} else {
categorizedWebhooks[webhook.category] = {}
categorizedWebhooks[webhook.category][webhook.action] = webhook
}
}
return categorizedWebhooks
}