@intentius/chant-lexicon-grafana 0.124.30 → 0.124.32
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/dist/actions/grafana.js +36 -0
- package/dist/actions/index.js +1 -0
- package/dist/alerting-build.js +262 -0
- package/dist/alerting.js +125 -0
- package/dist/annotations.js +47 -0
- package/dist/api/apply.js +320 -0
- package/dist/api/bind.js +44 -0
- package/dist/api/client.js +113 -0
- package/dist/api/converge.js +61 -0
- package/dist/api/dashboards.js +210 -0
- package/dist/api/datasources.js +24 -0
- package/dist/api/fake-grafana-writes.js +185 -0
- package/dist/api/fake-grafana.js +161 -0
- package/dist/api/folders.js +290 -0
- package/dist/api/library-panels.js +82 -0
- package/dist/build.js +767 -0
- package/dist/catalog.js +198 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +130 -0
- package/dist/codegen/generate-cli.js +7 -0
- package/dist/codegen/generate.js +97 -0
- package/dist/codegen/package.js +28 -0
- package/dist/codegen/schema-types.js +159 -0
- package/dist/composites/agent-dashboard.js +460 -0
- package/dist/composites/catalog.js +306 -0
- package/dist/composites/index.js +8 -0
- package/dist/composites/red-dashboard.js +135 -0
- package/dist/composites/shared.js +74 -0
- package/dist/composites/slo-alert-rules.js +96 -0
- package/dist/composites/slo-dashboard.js +153 -0
- package/dist/config.js +110 -0
- package/dist/contact-point-check.js +61 -0
- package/dist/contact-point-secrets.js +32 -0
- package/dist/contact-point-settings.gen.js +421 -0
- package/dist/dashboard.js +60 -0
- package/dist/datasource-refs.js +170 -0
- package/dist/datasource-settings.js +57 -0
- package/dist/datasource.js +68 -0
- package/dist/deep-observe-hooks.js +266 -0
- package/dist/deep-observe.js +171 -0
- package/dist/describe-resources.js +249 -0
- package/dist/detect.js +119 -0
- package/dist/export-resources.js +73 -0
- package/dist/folder.js +86 -0
- package/dist/import/alerting.js +514 -0
- package/dist/import/edits.js +90 -0
- package/dist/import/embedded.js +60 -0
- package/dist/import/generator.js +656 -0
- package/dist/import/live-export.js +246 -0
- package/dist/import/mappings.js +317 -0
- package/dist/import/model.js +23 -0
- package/dist/import/normalize-alerting.js +106 -0
- package/dist/import/normalize.js +426 -0
- package/dist/import/operator.js +252 -0
- package/dist/import/parser.js +1480 -0
- package/dist/import/report.js +58 -0
- package/dist/import/testdata/fixtures.js +122 -0
- package/dist/import/v2.js +814 -0
- package/dist/index.js +59 -0
- package/dist/init-templates.js +305 -0
- package/dist/integrity.json +2 -2
- package/dist/k8s.js +548 -0
- package/dist/library-panel.js +83 -0
- package/dist/lint/audit-catalog.js +40 -0
- package/dist/lint/audit-lineage.js +9 -0
- package/dist/lint/post-synth/graf101.js +13 -0
- package/dist/lint/post-synth/graf102.js +13 -0
- package/dist/lint/post-synth/graf103.js +13 -0
- package/dist/lint/post-synth/graf104.js +13 -0
- package/dist/lint/post-synth/graf105.js +13 -0
- package/dist/lint/post-synth/graf106.js +13 -0
- package/dist/lint/post-synth/graf107.js +13 -0
- package/dist/lint/post-synth/graf108.js +13 -0
- package/dist/lint/post-synth/graf109.js +13 -0
- package/dist/lint/post-synth/graf110.js +13 -0
- package/dist/lint/post-synth/graf111.js +13 -0
- package/dist/lint/post-synth/graf112.js +13 -0
- package/dist/lint/post-synth/graf113.js +13 -0
- package/dist/lint/post-synth/graf114.js +13 -0
- package/dist/lint/post-synth/graf115.js +13 -0
- package/dist/lint/post-synth/graf116.js +13 -0
- package/dist/lint/post-synth/graf117.js +13 -0
- package/dist/lint/post-synth/graf118.js +37 -0
- package/dist/lint/post-synth/grafana-helpers.js +89 -0
- package/dist/lint/post-synth/index.js +38 -0
- package/dist/lint/rules/grafana-ast.js +70 -0
- package/dist/lint/rules/index.js +6 -0
- package/dist/lint/rules/literal-secret.js +109 -0
- package/dist/lint/rules/uid-syntax.js +53 -0
- package/dist/lsp/completions.js +11 -0
- package/dist/lsp/hover.js +29 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/grafana-apply.js +147 -0
- package/dist/op/activities/index.js +7 -0
- package/dist/op/builders.js +19 -0
- package/dist/ownership.js +102 -0
- package/dist/package-cli.js +14 -0
- package/dist/panel-options.js +12 -0
- package/dist/panels.js +296 -0
- package/dist/pin.js +113 -0
- package/dist/plugin.js +172 -0
- package/dist/promql-check.js +176 -0
- package/dist/query-models.js +13 -0
- package/dist/query-syntax.js +129 -0
- package/dist/query.js +227 -0
- package/dist/schema/annotationslist.gen.js +5 -0
- package/dist/schema/azuremonitor.gen.js +5 -0
- package/dist/schema/barchart.gen.js +5 -0
- package/dist/schema/bargauge.gen.js +5 -0
- package/dist/schema/bigquery.gen.js +5 -0
- package/dist/schema/candlestick.gen.js +5 -0
- package/dist/schema/canvas.gen.js +5 -0
- package/dist/schema/cloudwatch.gen.js +5 -0
- package/dist/schema/dashboard.gen.js +6 -0
- package/dist/schema/dashboardlist.gen.js +5 -0
- package/dist/schema/datagrid.gen.js +5 -0
- package/dist/schema/elasticsearch.gen.js +5 -0
- package/dist/schema/expr.gen.js +5 -0
- package/dist/schema/gauge.gen.js +5 -0
- package/dist/schema/geomap.gen.js +5 -0
- package/dist/schema/googlecloudmonitoring.gen.js +5 -0
- package/dist/schema/grafanapyroscope.gen.js +5 -0
- package/dist/schema/heatmap.gen.js +5 -0
- package/dist/schema/histogram.gen.js +5 -0
- package/dist/schema/index.js +40 -0
- package/dist/schema/logs.gen.js +5 -0
- package/dist/schema/loki.gen.js +5 -0
- package/dist/schema/news.gen.js +5 -0
- package/dist/schema/nodegraph.gen.js +5 -0
- package/dist/schema/piechart.gen.js +5 -0
- package/dist/schema/prometheus.gen.js +5 -0
- package/dist/schema/stat.gen.js +5 -0
- package/dist/schema/statetimeline.gen.js +5 -0
- package/dist/schema/statushistory.gen.js +5 -0
- package/dist/schema/table.gen.js +5 -0
- package/dist/schema/tempo.gen.js +5 -0
- package/dist/schema/text.gen.js +5 -0
- package/dist/schema/timeseries.gen.js +5 -0
- package/dist/schema/trend.gen.js +5 -0
- package/dist/schema/xychart.gen.js +5 -0
- package/dist/schema-validate.js +265 -0
- package/dist/serializer.js +40 -0
- package/dist/skill-defs.js +60 -0
- package/dist/spec/fetch-cli.js +26 -0
- package/dist/spec/fetch-notifiers-cli.js +53 -0
- package/dist/spec/fetch-units-cli.js +40 -0
- package/dist/spec/notifiers.js +126 -0
- package/dist/spec/overlay.js +92 -0
- package/dist/spec/schemas.gen.js +42 -0
- package/dist/spec/schemas.js +62 -0
- package/dist/spec/units.gen.js +280 -0
- package/dist/spec/units.js +72 -0
- package/dist/transformations.js +127 -0
- package/dist/util.js +34 -0
- package/dist/validate-alerting.js +444 -0
- package/dist/validate-cli.js +7 -0
- package/dist/validate-output.js +635 -0
- package/dist/validate.js +122 -0
- package/dist/validation.js +13 -0
- package/dist/variables.js +70 -0
- package/package.json +10 -10
- package/src/codegen/package.ts +2 -2
- package/src/spec/fetch-notifiers-cli.ts +2 -2
- package/src/spec/fetch-units-cli.ts +2 -2
- package/src/spec/overlay.ts +2 -2
- package/src/spec/schemas.ts +2 -2
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in entity catalog: every class this package exports, keyed by
|
|
3
|
+
* class name. It feeds the packaged registry (`dist/meta.json`), LSP
|
|
4
|
+
* completions and hover, and the docs.
|
|
5
|
+
*/
|
|
6
|
+
import { DATASOURCE_PROVISIONING_TYPE, DATASOURCE_TYPE, EXTERNAL_DATASOURCE_TYPE } from "./datasource.js";
|
|
7
|
+
import { FOLDER_TYPE } from "./folder.js";
|
|
8
|
+
import { LIBRARY_PANEL_REF_TYPE, LIBRARY_PANEL_TYPE } from "./library-panel.js";
|
|
9
|
+
import { DASHBOARD_TYPE, DASHBOARD_PROVIDER_TYPE } from "./dashboard.js";
|
|
10
|
+
import * as panels from "./panels.js";
|
|
11
|
+
import * as queries from "./query.js";
|
|
12
|
+
import { VARIABLE_TYPE_PREFIX } from "./variables.js";
|
|
13
|
+
import { ALERT_QUERY_TYPE, ALERT_RULE_GROUP_TYPE, ALERT_RULE_TYPE, CONTACT_POINT_TYPE, EXPRESSION_TYPE_PREFIX, MUTE_TIMING_TYPE, NOTIFICATION_POLICY_TYPE, NOTIFICATION_TEMPLATE_TYPE, } from "./alerting.js";
|
|
14
|
+
function isClassWith(v, key) {
|
|
15
|
+
return typeof v === "function" && typeof v[key] === "object";
|
|
16
|
+
}
|
|
17
|
+
const panelEntries = Object.entries(panels)
|
|
18
|
+
.filter((e) => isClassWith(e[1], "definition"))
|
|
19
|
+
.map(([className, cls]) => ({
|
|
20
|
+
className,
|
|
21
|
+
entityType: `${panels.PANEL_TYPE_PREFIX}${cls.definition.type}`,
|
|
22
|
+
kind: "panel",
|
|
23
|
+
entityKind: "property",
|
|
24
|
+
pluginId: cls.definition.type,
|
|
25
|
+
description: cls.definition.description ?? "",
|
|
26
|
+
}));
|
|
27
|
+
const queryEntries = Object.entries(queries)
|
|
28
|
+
.filter((e) => isClassWith(e[1], "definition"))
|
|
29
|
+
.map(([className, cls]) => ({
|
|
30
|
+
className,
|
|
31
|
+
entityType: `${queries.QUERY_TYPE_PREFIX}${cls.definition.datasourceType}`,
|
|
32
|
+
kind: "query",
|
|
33
|
+
entityKind: "property",
|
|
34
|
+
pluginId: cls.definition.datasourceType,
|
|
35
|
+
description: cls.definition.description ?? "",
|
|
36
|
+
}));
|
|
37
|
+
const VARIABLES = [
|
|
38
|
+
["QueryVariable", "query", "A variable whose values come from a datasource query"],
|
|
39
|
+
["CustomVariable", "custom", "A variable with a fixed list of values"],
|
|
40
|
+
["IntervalVariable", "interval", "A time interval to choose, for range selectors"],
|
|
41
|
+
["DatasourceVariable", "datasource", "A choice of datasource of one plugin type; usable wherever a datasource is"],
|
|
42
|
+
["ConstantVariable", "constant", "A hidden, fixed value"],
|
|
43
|
+
["TextboxVariable", "textbox", "A free-text box"],
|
|
44
|
+
["AdhocVariable", "adhoc", "Key/value filters Grafana adds to every query sent to the variable's datasource"],
|
|
45
|
+
["GroupByVariable", "groupby", "A choice of label keys Grafana groups every query to the variable's datasource by (experimental in Grafana)"],
|
|
46
|
+
["SwitchVariable", "switch", "An on/off switch with a value for each state (Grafana 12.3 and later)"],
|
|
47
|
+
];
|
|
48
|
+
const EXPRESSIONS = [
|
|
49
|
+
["ReduceExpression", "reduce", "A server-side expression reducing each series to one number (last, mean, max, ...)"],
|
|
50
|
+
["MathExpression", "math", "A server-side math expression over other results by refId, e.g. $A / $B"],
|
|
51
|
+
["ThresholdExpression", "threshold", "A server-side threshold, with an optional recovery threshold"],
|
|
52
|
+
["ResampleExpression", "resample", "A server-side expression resampling a time series to a fixed window"],
|
|
53
|
+
["ClassicConditionsExpression", "classic_conditions", "Grafana's legacy classic conditions, as a server-side expression"],
|
|
54
|
+
["SqlExpression", "sql", "A server-side SQL expression over the other results"],
|
|
55
|
+
];
|
|
56
|
+
export const BUILTIN_CATALOG = [
|
|
57
|
+
{
|
|
58
|
+
className: "Dashboard",
|
|
59
|
+
entityType: DASHBOARD_TYPE,
|
|
60
|
+
kind: "dashboard",
|
|
61
|
+
entityKind: "resource",
|
|
62
|
+
description: "A dashboard: title, uid, time range, variables, rows, panels and links, written as the JSON Grafana imports",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
className: "Datasource",
|
|
66
|
+
entityType: DATASOURCE_TYPE,
|
|
67
|
+
kind: "datasource",
|
|
68
|
+
entityKind: "resource",
|
|
69
|
+
description: "A datasource, declared once and referenced by panels, queries and variables",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
className: "ExternalDatasource",
|
|
73
|
+
entityType: EXTERNAL_DATASOURCE_TYPE,
|
|
74
|
+
kind: "datasource",
|
|
75
|
+
entityKind: "resource",
|
|
76
|
+
description: "A datasource that already exists in Grafana: referenced like a Datasource and checked by GRAF101/GRAF102, never provisioned",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
className: "Folder",
|
|
80
|
+
entityType: FOLDER_TYPE,
|
|
81
|
+
kind: "folder",
|
|
82
|
+
entityKind: "resource",
|
|
83
|
+
description: "A folder with a stable uid, nested with parent; a dashboard's folder may be one instead of a path",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
className: "LibraryPanel",
|
|
87
|
+
entityType: LIBRARY_PANEL_TYPE,
|
|
88
|
+
kind: "library",
|
|
89
|
+
entityKind: "resource",
|
|
90
|
+
description: "A panel kept in Grafana's library and shared by dashboards; the build writes it into their __elements, and the API applier into the library",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
className: "DatasourceProvisioning",
|
|
94
|
+
entityType: DATASOURCE_PROVISIONING_TYPE,
|
|
95
|
+
kind: "provider",
|
|
96
|
+
entityKind: "resource",
|
|
97
|
+
description: "The datasource provisioning file's prune (on by default) and deleteDatasources",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
className: "DashboardProvider",
|
|
101
|
+
entityType: DASHBOARD_PROVIDER_TYPE,
|
|
102
|
+
kind: "provider",
|
|
103
|
+
entityKind: "resource",
|
|
104
|
+
description: "An entry in the dashboard provisioning file; a default one is written when none is declared",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
className: "Row",
|
|
108
|
+
entityType: panels.ROW_TYPE,
|
|
109
|
+
kind: "row",
|
|
110
|
+
entityKind: "property",
|
|
111
|
+
description: "A full-width row header; its panels are placed below it, or inside it when collapsed",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
className: "LibraryPanelRef",
|
|
115
|
+
entityType: LIBRARY_PANEL_REF_TYPE,
|
|
116
|
+
kind: "library",
|
|
117
|
+
entityKind: "property",
|
|
118
|
+
description: "A library panel placed on a dashboard, with its own gridPos, id and title; names a LibraryPanel, or { uid, name } of one already in Grafana",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
className: "AlertRuleGroup",
|
|
122
|
+
entityType: ALERT_RULE_GROUP_TYPE,
|
|
123
|
+
kind: "alerting",
|
|
124
|
+
entityKind: "resource",
|
|
125
|
+
description: "A group of Grafana-managed alert and recording rules in a folder, evaluated together at one interval",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
className: "ContactPoint",
|
|
129
|
+
entityType: CONTACT_POINT_TYPE,
|
|
130
|
+
kind: "alerting",
|
|
131
|
+
entityKind: "resource",
|
|
132
|
+
description: "A contact point: the integrations (email, Slack, webhook, ...) alerts are sent to, under one name",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
className: "NotificationPolicy",
|
|
136
|
+
entityType: NOTIFICATION_POLICY_TYPE,
|
|
137
|
+
kind: "alerting",
|
|
138
|
+
entityKind: "resource",
|
|
139
|
+
description: "An organisation's notification policy tree: the root receiver and the routes that match alerts to contact points",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
className: "MuteTiming",
|
|
143
|
+
entityType: MUTE_TIMING_TYPE,
|
|
144
|
+
kind: "alerting",
|
|
145
|
+
entityKind: "resource",
|
|
146
|
+
description: "A named time interval that mutes (or, as active_time_intervals, enables) notifications",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
className: "NotificationTemplate",
|
|
150
|
+
entityType: NOTIFICATION_TEMPLATE_TYPE,
|
|
151
|
+
kind: "alerting",
|
|
152
|
+
entityKind: "resource",
|
|
153
|
+
description: "A notification template group, Go templates contact point settings can use",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
className: "AlertRule",
|
|
157
|
+
entityType: ALERT_RULE_TYPE,
|
|
158
|
+
kind: "alerting",
|
|
159
|
+
entityKind: "property",
|
|
160
|
+
description: "A Grafana-managed alert or recording rule: queries, expressions and the condition that fires it",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
className: "AlertQuery",
|
|
164
|
+
entityType: ALERT_QUERY_TYPE,
|
|
165
|
+
kind: "alerting",
|
|
166
|
+
entityKind: "property",
|
|
167
|
+
description: "A datasource query of an alert rule, with its model as Grafana stores it and its time range",
|
|
168
|
+
},
|
|
169
|
+
...EXPRESSIONS.map(([className, type, description]) => ({
|
|
170
|
+
className,
|
|
171
|
+
entityType: `${EXPRESSION_TYPE_PREFIX}${type}`,
|
|
172
|
+
kind: "expression",
|
|
173
|
+
entityKind: "property",
|
|
174
|
+
description,
|
|
175
|
+
})),
|
|
176
|
+
...panelEntries,
|
|
177
|
+
...queryEntries,
|
|
178
|
+
...VARIABLES.map(([className, type, description]) => ({
|
|
179
|
+
className,
|
|
180
|
+
entityType: `${VARIABLE_TYPE_PREFIX}${type}`,
|
|
181
|
+
kind: "variable",
|
|
182
|
+
entityKind: "property",
|
|
183
|
+
description,
|
|
184
|
+
})),
|
|
185
|
+
];
|
|
186
|
+
/**
|
|
187
|
+
* The catalog as a chant lexicon registry, keyed by class name. Every class
|
|
188
|
+
* is listed as a resource, including the property-kind panels, queries and
|
|
189
|
+
* variables: the registry drives `new …` completions, and an author writes
|
|
190
|
+
* `new TimeSeriesPanel(…)` the same way as `new Dashboard(…)`.
|
|
191
|
+
*/
|
|
192
|
+
export function lexiconRegistry() {
|
|
193
|
+
const out = {};
|
|
194
|
+
for (const e of [...BUILTIN_CATALOG].sort((a, b) => a.className.localeCompare(b.className))) {
|
|
195
|
+
out[e.className] = { resourceType: e.entityType, kind: "resource", lexicon: "grafana" };
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation generation for the grafana lexicon: the generated reference
|
|
3
|
+
* pages from the core docs pipeline, plus the authored pages in docs/pages/.
|
|
4
|
+
*/
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
|
|
8
|
+
function serviceFromType(resourceType) {
|
|
9
|
+
const parts = resourceType.split("::");
|
|
10
|
+
return parts.length >= 2 ? parts[1] : "Grafana";
|
|
11
|
+
}
|
|
12
|
+
const overview = `The grafana lexicon types what a [Grafana](https://grafana.com/docs/grafana/latest/) dashboard
|
|
13
|
+
shows: datasources, dashboards, rows, panels, queries and variables, and
|
|
14
|
+
Grafana-managed alerting. \`chant build\`
|
|
15
|
+
writes one JSON file per dashboard, the JSON Grafana imports as it is, plus the
|
|
16
|
+
provisioning files Grafana reads for dashboards and datasources. Grafana server
|
|
17
|
+
settings, users and plugins are out of scope.
|
|
18
|
+
|
|
19
|
+
\`\`\`ts
|
|
20
|
+
import { Datasource, PromQuery, TimeSeriesPanel, Dashboard } from "@intentius/chant-lexicon-grafana";
|
|
21
|
+
|
|
22
|
+
const prometheus = new Datasource({ name: "Prometheus", type: "prometheus", url: "http://prometheus:9090" });
|
|
23
|
+
const rate = new PromQuery({ expr: "sum(rate(http_requests_total[$__rate_interval]))" });
|
|
24
|
+
const requests = new TimeSeriesPanel({ title: "Requests per second", datasource: prometheus, targets: [rate] });
|
|
25
|
+
|
|
26
|
+
export const overview = new Dashboard({ title: "Overview", panels: [requests] });
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
Panels: time series, stat, gauge, table, logs, traces, heatmap, text, bar
|
|
30
|
+
chart, bar gauge, pie chart, state timeline, status history, histogram, node
|
|
31
|
+
graph, XY chart, trend, canvas, geomap, candlestick, annotations list, dashboard list, news, data grid, flame graph and alert list, plus rows.
|
|
32
|
+
Queries: Prometheus (PromQL), Tempo (TraceQL), Loki (LogQL), Elasticsearch,
|
|
33
|
+
CloudWatch, Azure Monitor, Cloud Monitoring, BigQuery, Pyroscope, and SQL for
|
|
34
|
+
PostgreSQL, MySQL and MSSQL. Panel options and query fields are generated from
|
|
35
|
+
Grafana's own schemas at a pinned version, corrected against Grafana's CUE, and
|
|
36
|
+
track Grafana 12.4 and 13.x. Every build validates the dashboards against the
|
|
37
|
+
same schemas.
|
|
38
|
+
Dashboards are written in the classic (v1) JSON model. Grafana 13's v2 model
|
|
39
|
+
(tabs, auto grids, conditional rendering) cannot be declared yet; import and
|
|
40
|
+
drift read a v2 dashboard by converting it to the classic model, and say what
|
|
41
|
+
the conversion loses.
|
|
42
|
+
\`definePanel\` and \`defineQuery\` add plugins chant doesn't ship.
|
|
43
|
+
\`chant import\` turns dashboard JSON exported from Grafana into this TypeScript,
|
|
44
|
+
and it builds back to the same dashboard (see
|
|
45
|
+
[Importing Dashboards](./importing/)). \`chant lifecycle diff --live\` reports
|
|
46
|
+
a dashboard edited in Grafana as drift, property by property, and
|
|
47
|
+
\`chant import --from <env>\` writes a running Grafana's dashboards as
|
|
48
|
+
TypeScript (see [Drift and Live Export](./observing/)). \`grafanaApply\` writes
|
|
49
|
+
a build to a running Grafana over its HTTP API, with folders and library
|
|
50
|
+
panels, and prunes the project's own dashboards and folders (see
|
|
51
|
+
[Apply over the API](./applying/)).
|
|
52
|
+
|
|
53
|
+
Grafana-managed alerting is typed too: rule groups with their queries and
|
|
54
|
+
server-side expressions (reduce, math, threshold, resample, classic
|
|
55
|
+
conditions, SQL), contact points, the notification policy tree, mute timings
|
|
56
|
+
and templates, written to Grafana's alerting provisioning file.
|
|
57
|
+
\`SloAlertRules\` turns a prometheus \`Slo\` into burn-rate alert rules. See
|
|
58
|
+
[Alerting](./alerting/).
|
|
59
|
+
|
|
60
|
+
Three composites build whole dashboards from declarations in other lexicons:
|
|
61
|
+
\`RedDashboard\` from an otel \`spanmetrics\` connector, \`SloDashboard\` from a
|
|
62
|
+
prometheus \`Slo\` and \`AgentDashboard\` from the otel GenAI preset. Metric names
|
|
63
|
+
come from the declaration, so renaming one moves the panels. See
|
|
64
|
+
[Dashboards from Declarations](./composites/).
|
|
65
|
+
|
|
66
|
+
Checks catch a query aimed at a datasource nobody declared, with
|
|
67
|
+
\`Datasource\` or \`ExternalDatasource\` (GRAF101), or of the
|
|
68
|
+
wrong type (GRAF102), a \`$variable\` the dashboard doesn't declare (GRAF103),
|
|
69
|
+
duplicate uids and ids (GRAF104), panels off the grid or overlapping (GRAF105),
|
|
70
|
+
anything Grafana's dashboard schema rejects (GRAF107), PromQL sent to a
|
|
71
|
+
Prometheus that doesn't parse (GRAF108), LogQL sent to a Loki and TraceQL
|
|
72
|
+
sent to a Tempo that don't parse (GRAF116, GRAF117), dashboard providers that load a
|
|
73
|
+
dashboard twice or not into its declared folder (GRAF109), a panel repeated over a variable that only ever
|
|
74
|
+
holds one value (GRAF110), and a unit Grafana doesn't know (GRAF115). For alerting, they catch rules whose condition or expressions name
|
|
75
|
+
no query (GRAF111), queries to undeclared datasources (GRAF112), routes to
|
|
76
|
+
undeclared contact points or mute timings (GRAF113), and uids, intervals and
|
|
77
|
+
duplicates Grafana refuses (GRAF114).
|
|
78
|
+
`;
|
|
79
|
+
const outputFormat = `The grafana lexicon writes Grafana's own files, keyed by path under the
|
|
80
|
+
directory of the \`-o\` output:
|
|
81
|
+
|
|
82
|
+
- \`dashboards/<uid>.json\`, or \`dashboards/<folder>/<uid>.json\` for a
|
|
83
|
+
dashboard with a \`folder\`: the dashboard JSON, one file per dashboard.
|
|
84
|
+
- \`provisioning/datasources/chant.yaml\`: every declared datasource.
|
|
85
|
+
- \`provisioning/dashboards/chant.yaml\`: a file provider for the dashboards
|
|
86
|
+
directory; a default one unless a \`DashboardProvider\` is declared.
|
|
87
|
+
- \`provisioning/alerting/chant.yaml\`: rule groups, contact points, the
|
|
88
|
+
notification policy tree, mute timings and templates, when the build
|
|
89
|
+
declares any.
|
|
90
|
+
|
|
91
|
+
The primary output (the \`-o\` file itself) is a JSON index of the dashboards,
|
|
92
|
+
datasources, alerting and files that were built.
|
|
93
|
+
|
|
94
|
+
- Dashboard JSON carries the pinned schema's \`schemaVersion\`, no numeric \`id\`,
|
|
95
|
+
and every field Grafana's schema requires (link defaults, an empty
|
|
96
|
+
\`annotations.list\`, \`fieldConfig.overrides\`).
|
|
97
|
+
- Panels without a position are laid out in declaration order on the 24-column
|
|
98
|
+
grid. Ids and query \`refId\`s are assigned in order unless set.
|
|
99
|
+
- A declared \`Datasource\` is written as \`{ type, uid }\` wherever it is
|
|
100
|
+
referenced, and as its uid inside another datasource's \`jsonData\`.
|
|
101
|
+
- \`chant build\` rewrites JSON files with sorted keys; Grafana does not care
|
|
102
|
+
about key order.
|
|
103
|
+
- The dashboard JSON carries no ownership marker. Grafana keeps who manages a
|
|
104
|
+
dashboard in the resource's metadata on its \`dashboard.grafana.app\` API,
|
|
105
|
+
not in the JSON: for a provisioned dashboard, the name of the provider that
|
|
106
|
+
loaded it, which is \`chant\` unless a \`DashboardProvider\` names another.
|
|
107
|
+
\`chant lifecycle diff --live\` reads that back (see
|
|
108
|
+
[Drift and Live Export](../observing/)).
|
|
109
|
+
`;
|
|
110
|
+
export async function generateDocs(opts) {
|
|
111
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
112
|
+
const config = {
|
|
113
|
+
name: "grafana",
|
|
114
|
+
displayName: "Grafana",
|
|
115
|
+
description: "Typed Grafana dashboards, panels, queries and datasources, built to dashboard JSON and provisioning files",
|
|
116
|
+
distDir: join(pkgDir, "dist"),
|
|
117
|
+
outDir: join(pkgDir, "docs"),
|
|
118
|
+
basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/grafana/",
|
|
119
|
+
overview,
|
|
120
|
+
outputFormat,
|
|
121
|
+
serviceFromType,
|
|
122
|
+
srcDir: join(pkgDir, "src"),
|
|
123
|
+
examplesDir: join(pkgDir, "examples"),
|
|
124
|
+
};
|
|
125
|
+
const result = docsPipeline(config);
|
|
126
|
+
writeDocsSite(config, result);
|
|
127
|
+
if (opts?.verbose) {
|
|
128
|
+
console.error(`Generated ${result.pages.size} documentation pages`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grafana lexicon's generate step, offline and deterministic.
|
|
3
|
+
*
|
|
4
|
+
* 1. `src/schema/<name>.gen.ts` from each vendored schema in `src/spec/schemas/`
|
|
5
|
+
* (see `src/pin.ts`). These are committed, and a test fails when they
|
|
6
|
+
* drift from what this step would write.
|
|
7
|
+
* 2. `src/spec/schemas.gen.ts`, the same schemas with the overlay applied, as
|
|
8
|
+
* JSON text. Validation (GRAF107, the `/validation` subpath) reads them
|
|
9
|
+
* from there rather than from the files, so it needs no filesystem access
|
|
10
|
+
* and works bundled. Committed and checked the same way.
|
|
11
|
+
* 3. `src/generated/lexicon-grafana.json`, the registry of entity classes
|
|
12
|
+
* the package exports, derived from the catalog so the packaged
|
|
13
|
+
* `dist/meta.json` cannot disagree with the classes.
|
|
14
|
+
*/
|
|
15
|
+
import { dirname, join } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
18
|
+
import { SCHEMA_NAMES } from "../pin.js";
|
|
19
|
+
import { digestMismatches, loadSchema } from "../spec/schemas.js";
|
|
20
|
+
import { schemaModule } from "./schema-types.js";
|
|
21
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
22
|
+
/** The dashboard schema's own default `schemaVersion`, the one the serializer emits. */
|
|
23
|
+
function dashboardExtras(schema) {
|
|
24
|
+
const defs = schema.definitions;
|
|
25
|
+
const v = defs.Dashboard?.properties?.schemaVersion?.default;
|
|
26
|
+
if (typeof v !== "number")
|
|
27
|
+
throw new Error("grafana: dashboard schema has no default schemaVersion");
|
|
28
|
+
return ["/** The dashboard `schemaVersion` the pinned schema defaults to, and the one chant emits. */", `export const DASHBOARD_SCHEMA_VERSION = ${v};`];
|
|
29
|
+
}
|
|
30
|
+
/** `src/spec/schemas.gen.ts`: each schema validation uses, overlay applied, as one line of JSON text. */
|
|
31
|
+
export function schemaDataModule() {
|
|
32
|
+
const lines = [
|
|
33
|
+
"// Code generated by chant generate. DO NOT EDIT.",
|
|
34
|
+
"// The vendored Grafana schemas (src/spec/schemas/, see src/pin.ts) with the",
|
|
35
|
+
"// correction overlay (src/spec/overlay/) applied, as JSON text, for",
|
|
36
|
+
"// src/schema-validate.ts. A module rather than a file read, so validation",
|
|
37
|
+
"// needs no filesystem access and survives bundling; text rather than object",
|
|
38
|
+
"// literals, so loading it costs little until a schema is parsed.",
|
|
39
|
+
"",
|
|
40
|
+
'import type { SchemaName } from "../pin.js";',
|
|
41
|
+
"",
|
|
42
|
+
"export const SCHEMA_JSON: Readonly<Record<SchemaName, string>> = {",
|
|
43
|
+
];
|
|
44
|
+
for (const name of SCHEMA_NAMES) {
|
|
45
|
+
lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(JSON.stringify(loadSchema(name)))},`);
|
|
46
|
+
}
|
|
47
|
+
lines.push("};", "");
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
/** Every generated schema module, keyed by path relative to the package root. */
|
|
51
|
+
export function schemaModules() {
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const name of SCHEMA_NAMES) {
|
|
54
|
+
const schema = loadSchema(name);
|
|
55
|
+
const extra = name === "dashboard" ? dashboardExtras(schema) : [];
|
|
56
|
+
out[`src/schema/${name}.gen.ts`] = schemaModule(name, schema, extra).source;
|
|
57
|
+
}
|
|
58
|
+
out["src/spec/schemas.gen.ts"] = schemaDataModule();
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** The packaged types entry re-exports the compiled declarations. */
|
|
62
|
+
const TYPES_DTS = `// The grafana lexicon's entity classes are hand-written in src/, with panel
|
|
63
|
+
// and query types generated into src/schema/; this entry re-exports them.
|
|
64
|
+
export * from "../index.js";
|
|
65
|
+
`;
|
|
66
|
+
export async function generate(opts) {
|
|
67
|
+
const bad = digestMismatches();
|
|
68
|
+
if (bad.length > 0) {
|
|
69
|
+
throw new Error(`grafana: vendored schemas do not match GRAFANA_SCHEMA_PIN: ${bad.map((b) => `${b.name} (${b.actual.slice(0, 12)}…)`).join(", ")}. ` +
|
|
70
|
+
"Run `just fetch-schemas` and update src/pin.ts.");
|
|
71
|
+
}
|
|
72
|
+
const modules = schemaModules();
|
|
73
|
+
for (const [rel, source] of Object.entries(modules)) {
|
|
74
|
+
mkdirSync(dirname(join(pkgDir, rel)), { recursive: true });
|
|
75
|
+
writeFileSync(join(pkgDir, rel), source);
|
|
76
|
+
}
|
|
77
|
+
const { BUILTIN_CATALOG, lexiconRegistry } = await import("../catalog.js");
|
|
78
|
+
const lexiconJSON = `${JSON.stringify(lexiconRegistry(), null, 2)}\n`;
|
|
79
|
+
if (opts?.verbose) {
|
|
80
|
+
console.error(`grafana: ${Object.keys(modules).length} schema modules, ${BUILTIN_CATALOG.length} entity classes`);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
lexiconJSON,
|
|
84
|
+
typesDTS: TYPES_DTS,
|
|
85
|
+
indexTS: "",
|
|
86
|
+
resources: BUILTIN_CATALOG.length,
|
|
87
|
+
properties: 0,
|
|
88
|
+
enums: 0,
|
|
89
|
+
warnings: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Write the registry to src/generated/lexicon-grafana.json, for tools that read it from there. */
|
|
93
|
+
export function writeGeneratedFiles(result) {
|
|
94
|
+
const dir = join(pkgDir, "src", "generated");
|
|
95
|
+
mkdirSync(dir, { recursive: true });
|
|
96
|
+
writeFileSync(join(dir, "lexicon-grafana.json"), result.lexiconJSON);
|
|
97
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grafana lexicon packaging, delegating to core's packagePipeline.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
7
|
+
import { packagePipeline, collectSkills, } from "@intentius/chant/codegen/package";
|
|
8
|
+
import { generate } from "./generate.js";
|
|
9
|
+
import { grafanaSkills } from "../skill-defs.js";
|
|
10
|
+
// src/codegen/package.ts -> src/
|
|
11
|
+
const srcDir = dirname(moduleSourceDir(import.meta.url));
|
|
12
|
+
export async function packageLexicon(opts = {}) {
|
|
13
|
+
const pkgJson = JSON.parse(readFileSync(join(srcDir, "..", "package.json"), "utf-8"));
|
|
14
|
+
return packagePipeline({
|
|
15
|
+
generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
|
|
16
|
+
buildManifest: () => ({
|
|
17
|
+
name: "grafana",
|
|
18
|
+
version: pkgJson.version ?? "0.0.1",
|
|
19
|
+
chantVersion: ">=0.1.0",
|
|
20
|
+
namespace: "Grafana",
|
|
21
|
+
intrinsics: [],
|
|
22
|
+
pseudoParameters: {},
|
|
23
|
+
}),
|
|
24
|
+
srcDir,
|
|
25
|
+
collectSkills: () => collectSkills(grafanaSkills()),
|
|
26
|
+
version: pkgJson.version ?? "0.0.1",
|
|
27
|
+
}, opts);
|
|
28
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema (draft-07, the subset cog emits) to TypeScript declarations.
|
|
3
|
+
*
|
|
4
|
+
* Each vendored schema, with its correction overlay applied, becomes one
|
|
5
|
+
* module under `src/schema/`: one exported type per entry in `definitions`,
|
|
6
|
+
* with the schema's descriptions as doc comments. `required` is kept, so the types say what the schema says; the
|
|
7
|
+
* entity props wrap panel options in `DeepPartial` where Grafana fills the
|
|
8
|
+
* rest in on import.
|
|
9
|
+
*/
|
|
10
|
+
const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
11
|
+
/**
|
|
12
|
+
* Keys written quoted even though they are identifiers. The repo's egress
|
|
13
|
+
* scan (test/egress-catalogue.ts) reads a bare `fetch` as a call to the
|
|
14
|
+
* global, and the dashboard schema has an action option named `fetch`.
|
|
15
|
+
*/
|
|
16
|
+
const QUOTED_KEYS = new Set(["fetch"]);
|
|
17
|
+
/** `dataquery` -> `Dataquery`: every exported type starts upper-case. */
|
|
18
|
+
export function typeName(defName) {
|
|
19
|
+
return defName.charAt(0).toUpperCase() + defName.slice(1);
|
|
20
|
+
}
|
|
21
|
+
function refName(ref) {
|
|
22
|
+
const m = /^#\/definitions\/(.+)$/.exec(ref);
|
|
23
|
+
if (!m)
|
|
24
|
+
throw new Error(`schema-types: only local #/definitions refs are supported, got "${ref}"`);
|
|
25
|
+
return typeName(m[1]);
|
|
26
|
+
}
|
|
27
|
+
function literal(value) {
|
|
28
|
+
return JSON.stringify(value);
|
|
29
|
+
}
|
|
30
|
+
function paren(t) {
|
|
31
|
+
return /[|&]/.test(t) && !/^\{[\s\S]*\}$/.test(t) ? `(${t})` : t;
|
|
32
|
+
}
|
|
33
|
+
function union(parts) {
|
|
34
|
+
const uniq = [...new Set(parts)];
|
|
35
|
+
if (uniq.includes("unknown"))
|
|
36
|
+
return "unknown";
|
|
37
|
+
return uniq.length === 0 ? "never" : uniq.join(" | ");
|
|
38
|
+
}
|
|
39
|
+
function docComment(node, indent) {
|
|
40
|
+
const lines = [];
|
|
41
|
+
const text = typeof node.description === "string" ? node.description.trim() : "";
|
|
42
|
+
const deprecated = node.deprecated === true || typeof node["x-deprecation-message"] === "string";
|
|
43
|
+
if (!text && !deprecated)
|
|
44
|
+
return lines;
|
|
45
|
+
lines.push(`${indent}/**`);
|
|
46
|
+
for (const raw of text ? text.replace(/\*\//g, "*\\/").split("\n") : []) {
|
|
47
|
+
const l = raw.trimEnd();
|
|
48
|
+
lines.push(`${indent} *${l ? ` ${l}` : ""}`);
|
|
49
|
+
}
|
|
50
|
+
if (deprecated) {
|
|
51
|
+
const msg = typeof node["x-deprecation-message"] === "string" ? ` ${String(node["x-deprecation-message"])}` : "";
|
|
52
|
+
lines.push(`${indent} * @deprecated${msg.replace(/\*\//g, "*\\/")}`);
|
|
53
|
+
}
|
|
54
|
+
lines.push(`${indent} */`);
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
|
57
|
+
function objectType(node, indent) {
|
|
58
|
+
const props = node.properties ?? {};
|
|
59
|
+
const required = new Set(node.required ?? []);
|
|
60
|
+
const inner = `${indent} `;
|
|
61
|
+
const lines = ["{"];
|
|
62
|
+
for (const [key, child] of Object.entries(props)) {
|
|
63
|
+
lines.push(...docComment(child, inner));
|
|
64
|
+
const k = IDENT.test(key) && !QUOTED_KEYS.has(key) ? key : JSON.stringify(key);
|
|
65
|
+
lines.push(`${inner}${k}${required.has(key) ? "" : "?"}: ${tsType(child, inner)};`);
|
|
66
|
+
}
|
|
67
|
+
const ap = node.additionalProperties;
|
|
68
|
+
if (ap !== undefined && ap !== false) {
|
|
69
|
+
const t = ap === true || Object.keys(ap).length === 0 ? "unknown" : tsType(ap, inner);
|
|
70
|
+
lines.push(`${inner}[key: string]: ${Object.keys(props).length > 0 ? "unknown" : t};`);
|
|
71
|
+
}
|
|
72
|
+
lines.push(`${indent}}`);
|
|
73
|
+
return lines.length === 2 ? "Record<string, never>" : lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A branch that only constrains which keys are present (`{ required: [...] }`),
|
|
77
|
+
* like the overlay's "a panel has a `type` or a `libraryPanel`". It says
|
|
78
|
+
* nothing about the shape, so the type ignores it and validation enforces it.
|
|
79
|
+
*/
|
|
80
|
+
function constraintOnly(branches) {
|
|
81
|
+
return Array.isArray(branches) && branches.every((b) => Object.keys(b).every((k) => k === "required"));
|
|
82
|
+
}
|
|
83
|
+
/** The TypeScript type for one schema node. */
|
|
84
|
+
export function tsType(node, indent = "") {
|
|
85
|
+
if (node === undefined || node === true)
|
|
86
|
+
return "unknown";
|
|
87
|
+
if (node === false)
|
|
88
|
+
return "never";
|
|
89
|
+
if (node.$ref)
|
|
90
|
+
return refName(node.$ref);
|
|
91
|
+
if (node.const !== undefined)
|
|
92
|
+
return literal(node.const);
|
|
93
|
+
if (Array.isArray(node.enum))
|
|
94
|
+
return union(node.enum.map(literal));
|
|
95
|
+
if (node.oneOf && !constraintOnly(node.oneOf))
|
|
96
|
+
return union(node.oneOf.map((n) => paren(tsType(n, indent))));
|
|
97
|
+
if (node.anyOf && !constraintOnly(node.anyOf))
|
|
98
|
+
return union(node.anyOf.map((n) => paren(tsType(n, indent))));
|
|
99
|
+
if (node.allOf)
|
|
100
|
+
return node.allOf.map((n) => paren(tsType(n, indent))).join(" & ");
|
|
101
|
+
const types = Array.isArray(node.type) ? node.type : node.type ? [node.type] : [];
|
|
102
|
+
if (types.length === 0)
|
|
103
|
+
return node.properties ? objectType(node, indent) : "unknown";
|
|
104
|
+
return union(types.map((t) => {
|
|
105
|
+
switch (t) {
|
|
106
|
+
case "string":
|
|
107
|
+
return "string";
|
|
108
|
+
case "number":
|
|
109
|
+
case "integer":
|
|
110
|
+
return "number";
|
|
111
|
+
case "boolean":
|
|
112
|
+
return "boolean";
|
|
113
|
+
case "null":
|
|
114
|
+
return "null";
|
|
115
|
+
case "array": {
|
|
116
|
+
if (Array.isArray(node.items))
|
|
117
|
+
return `[${node.items.map((n) => tsType(n, indent)).join(", ")}]`;
|
|
118
|
+
return `${paren(tsType(node.items, indent))}[]`;
|
|
119
|
+
}
|
|
120
|
+
case "object": {
|
|
121
|
+
if (node.properties)
|
|
122
|
+
return objectType(node, indent);
|
|
123
|
+
const ap = node.additionalProperties;
|
|
124
|
+
if (ap === false)
|
|
125
|
+
return "Record<string, never>";
|
|
126
|
+
if (ap === undefined || ap === true || Object.keys(ap).length === 0)
|
|
127
|
+
return "Record<string, unknown>";
|
|
128
|
+
return `Record<string, ${tsType(ap, indent)}>`;
|
|
129
|
+
}
|
|
130
|
+
default:
|
|
131
|
+
return "unknown";
|
|
132
|
+
}
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
/** One schema file to one TypeScript module. */
|
|
136
|
+
export function schemaModule(name, schema, extra = []) {
|
|
137
|
+
const defs = (schema.definitions ?? {});
|
|
138
|
+
const out = [
|
|
139
|
+
"// Code generated by chant generate from the vendored Grafana schema",
|
|
140
|
+
`// src/spec/schemas/${name}.jsonschema.json (see src/pin.ts) and its correction`,
|
|
141
|
+
"// overlay in src/spec/overlay/ (see src/spec/overlay.ts). DO NOT EDIT.",
|
|
142
|
+
"/* eslint-disable */",
|
|
143
|
+
"",
|
|
144
|
+
];
|
|
145
|
+
const types = [];
|
|
146
|
+
for (const [defName, def] of Object.entries(defs)) {
|
|
147
|
+
const tn = typeName(defName);
|
|
148
|
+
types.push(tn);
|
|
149
|
+
out.push(...docComment(def, ""));
|
|
150
|
+
const t = tsType(def, "");
|
|
151
|
+
if (t.startsWith("{"))
|
|
152
|
+
out.push(`export interface ${tn} ${t}`);
|
|
153
|
+
else
|
|
154
|
+
out.push(`export type ${tn} = ${t};`);
|
|
155
|
+
out.push("");
|
|
156
|
+
}
|
|
157
|
+
out.push(...extra);
|
|
158
|
+
return { source: `${out.join("\n").replace(/\n+$/, "")}\n`, types };
|
|
159
|
+
}
|