@intentius/chant-lexicon-prometheus 0.96.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 +58 -0
- package/dist/alertmanager.d.ts +81 -0
- package/dist/alertmanager.d.ts.map +1 -0
- package/dist/build.d.ts +33 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +9 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +19 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +7 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/catalog.d.ts +3 -0
- package/dist/composites/catalog.d.ts.map +1 -0
- package/dist/composites/index.d.ts +6 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/slo.d.ts +205 -0
- package/dist/composites/slo.d.ts.map +1 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/duration.d.ts +16 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +13 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +33 -0
- package/dist/lint/audit-catalog.d.ts +13 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/post-synth/prom-helpers.d.ts +33 -0
- package/dist/lint/post-synth/prom-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/prom101.d.ts +8 -0
- package/dist/lint/post-synth/prom101.d.ts.map +1 -0
- package/dist/lint/post-synth/prom102.d.ts +8 -0
- package/dist/lint/post-synth/prom102.d.ts.map +1 -0
- package/dist/lint/post-synth/prom103.d.ts +8 -0
- package/dist/lint/post-synth/prom103.d.ts.map +1 -0
- package/dist/lint/post-synth/prom104.d.ts +8 -0
- package/dist/lint/post-synth/prom104.d.ts.map +1 -0
- package/dist/lint/post-synth/prom105.d.ts +8 -0
- package/dist/lint/post-synth/prom105.d.ts.map +1 -0
- package/dist/lint/post-synth/prom106.d.ts +8 -0
- package/dist/lint/post-synth/prom106.d.ts.map +1 -0
- package/dist/lint/post-synth/prom107.d.ts +8 -0
- package/dist/lint/post-synth/prom107.d.ts.map +1 -0
- package/dist/lint/post-synth/prom201.d.ts +8 -0
- package/dist/lint/post-synth/prom201.d.ts.map +1 -0
- package/dist/lint/post-synth/prom202.d.ts +8 -0
- package/dist/lint/post-synth/prom202.d.ts.map +1 -0
- package/dist/lint/post-synth/prom203.d.ts +8 -0
- package/dist/lint/post-synth/prom203.d.ts.map +1 -0
- package/dist/lint/post-synth/prom204.d.ts +8 -0
- package/dist/lint/post-synth/prom204.d.ts.map +1 -0
- package/dist/lint/post-synth/prom205.d.ts +8 -0
- package/dist/lint/post-synth/prom205.d.ts.map +1 -0
- package/dist/lint/post-synth/prom206.d.ts +8 -0
- package/dist/lint/post-synth/prom206.d.ts.map +1 -0
- package/dist/lint/post-synth/prom207.d.ts +8 -0
- package/dist/lint/post-synth/prom207.d.ts.map +1 -0
- package/dist/lint/post-synth/prom208.d.ts +8 -0
- package/dist/lint/post-synth/prom208.d.ts.map +1 -0
- package/dist/lint/post-synth/prom209.d.ts +8 -0
- package/dist/lint/post-synth/prom209.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +7 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/literal-credential.d.ts +18 -0
- package/dist/lint/rules/literal-credential.d.ts.map +1 -0
- package/dist/lint/rules/prom-ast.d.ts +14 -0
- package/dist/lint/rules/prom-ast.d.ts.map +1 -0
- package/dist/lint/rules/promql-literal.d.ts +11 -0
- package/dist/lint/rules/promql-literal.d.ts.map +1 -0
- package/dist/lint/rules/slo-literal.d.ts +13 -0
- package/dist/lint/rules/slo-literal.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +4 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/matchers.d.ts +29 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/meta.json +32 -0
- package/dist/model.d.ts +262 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/okf/index.md +34 -0
- package/dist/okf/rules/PROM001.md +16 -0
- package/dist/okf/rules/PROM002.md +15 -0
- package/dist/okf/rules/PROM003.md +11 -0
- package/dist/okf/rules/PROM101.md +11 -0
- package/dist/okf/rules/PROM102.md +11 -0
- package/dist/okf/rules/PROM103.md +11 -0
- package/dist/okf/rules/PROM104.md +11 -0
- package/dist/okf/rules/PROM105.md +11 -0
- package/dist/okf/rules/PROM106.md +11 -0
- package/dist/okf/rules/PROM107.md +11 -0
- package/dist/okf/rules/PROM201.md +11 -0
- package/dist/okf/rules/PROM202.md +11 -0
- package/dist/okf/rules/PROM203.md +11 -0
- package/dist/okf/rules/PROM204.md +11 -0
- package/dist/okf/rules/PROM205.md +11 -0
- package/dist/okf/rules/PROM206.md +11 -0
- package/dist/okf/rules/PROM207.md +11 -0
- package/dist/okf/rules/PROM208.md +11 -0
- package/dist/okf/rules/PROM209.md +11 -0
- package/dist/okf/types/AlertmanagerSettings.md +13 -0
- package/dist/okf/types/InhibitRule.md +9 -0
- package/dist/okf/types/Receiver.md +13 -0
- package/dist/okf/types/Route.md +9 -0
- package/dist/okf/types/RuleGroup.md +13 -0
- package/dist/okf/types/TimeInterval.md +9 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/pin.d.ts +20 -0
- package/dist/pin.d.ts.map +1 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/promql.d.ts +25 -0
- package/dist/promql.d.ts.map +1 -0
- package/dist/rule-eval.d.ts +67 -0
- package/dist/rule-eval.d.ts.map +1 -0
- package/dist/rules/literal-credential.ts +84 -0
- package/dist/rules/prom-ast.ts +30 -0
- package/dist/rules/prom-helpers.ts +89 -0
- package/dist/rules/prom101.ts +17 -0
- package/dist/rules/prom102.ts +17 -0
- package/dist/rules/prom103.ts +17 -0
- package/dist/rules/prom104.ts +17 -0
- package/dist/rules/prom105.ts +17 -0
- package/dist/rules/prom106.ts +17 -0
- package/dist/rules/prom107.ts +17 -0
- package/dist/rules/prom201.ts +17 -0
- package/dist/rules/prom202.ts +17 -0
- package/dist/rules/prom203.ts +17 -0
- package/dist/rules/prom204.ts +17 -0
- package/dist/rules/prom205.ts +17 -0
- package/dist/rules/prom206.ts +17 -0
- package/dist/rules/prom207.ts +17 -0
- package/dist/rules/prom208.ts +17 -0
- package/dist/rules/prom209.ts +17 -0
- package/dist/rules/promql-literal.ts +55 -0
- package/dist/rules/slo-literal.ts +81 -0
- package/dist/rules.d.ts +59 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/serializer.d.ts +27 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skill-defs.d.ts +3 -0
- package/dist/skill-defs.d.ts.map +1 -0
- package/dist/skills/chant-prometheus-alertmanager.md +58 -0
- package/dist/skills/chant-prometheus-kubernetes.md +48 -0
- package/dist/skills/chant-prometheus.md +91 -0
- package/dist/tools.d.ts +30 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-config.d.ts +35 -0
- package/dist/validate-config.d.ts.map +1 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/alertmanager.ts +143 -0
- package/src/build.ts +199 -0
- package/src/catalog.ts +65 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +104 -0
- package/src/codegen/generate-cli.ts +8 -0
- package/src/codegen/generate.ts +49 -0
- package/src/codegen/package.ts +41 -0
- package/src/composites/catalog.test.ts +37 -0
- package/src/composites/catalog.ts +65 -0
- package/src/composites/composites.test.ts +256 -0
- package/src/composites/index.ts +23 -0
- package/src/composites/slo-burn.test.ts +289 -0
- package/src/composites/slo.ts +471 -0
- package/src/detect.ts +12 -0
- package/src/duration.ts +51 -0
- package/src/generated/lexicon-prometheus.json +32 -0
- package/src/index.ts +97 -0
- package/src/init-templates.test.ts +29 -0
- package/src/init-templates.ts +76 -0
- package/src/lint/audit-catalog.ts +123 -0
- package/src/lint/post-synth/index.ts +37 -0
- package/src/lint/post-synth/post-synth.test.ts +256 -0
- package/src/lint/post-synth/prom-helpers.ts +89 -0
- package/src/lint/post-synth/prom101.ts +17 -0
- package/src/lint/post-synth/prom102.ts +17 -0
- package/src/lint/post-synth/prom103.ts +17 -0
- package/src/lint/post-synth/prom104.ts +17 -0
- package/src/lint/post-synth/prom105.ts +17 -0
- package/src/lint/post-synth/prom106.ts +17 -0
- package/src/lint/post-synth/prom107.ts +17 -0
- package/src/lint/post-synth/prom201.ts +17 -0
- package/src/lint/post-synth/prom202.ts +17 -0
- package/src/lint/post-synth/prom203.ts +17 -0
- package/src/lint/post-synth/prom204.ts +17 -0
- package/src/lint/post-synth/prom205.ts +17 -0
- package/src/lint/post-synth/prom206.ts +17 -0
- package/src/lint/post-synth/prom207.ts +17 -0
- package/src/lint/post-synth/prom208.ts +17 -0
- package/src/lint/post-synth/prom209.ts +17 -0
- package/src/lint/rules/index.ts +11 -0
- package/src/lint/rules/literal-credential.ts +84 -0
- package/src/lint/rules/prom-ast.ts +30 -0
- package/src/lint/rules/promql-literal.ts +55 -0
- package/src/lint/rules/rules.test.ts +114 -0
- package/src/lint/rules/slo-literal.ts +81 -0
- package/src/lsp/completions.test.ts +23 -0
- package/src/lsp/completions.ts +15 -0
- package/src/lsp/hover.test.ts +27 -0
- package/src/lsp/hover.ts +26 -0
- package/src/matchers.ts +132 -0
- package/src/model.test.ts +104 -0
- package/src/model.ts +290 -0
- package/src/package-cli.ts +17 -0
- package/src/pin.ts +13 -0
- package/src/plugin.test.ts +73 -0
- package/src/plugin.ts +129 -0
- package/src/promql.ts +46 -0
- package/src/rule-eval.ts +397 -0
- package/src/rules.ts +127 -0
- package/src/serializer.test.ts +179 -0
- package/src/serializer.ts +55 -0
- package/src/skill-defs.ts +43 -0
- package/src/skills/chant-prometheus-alertmanager.md +58 -0
- package/src/skills/chant-prometheus-kubernetes.md +48 -0
- package/src/skills/chant-prometheus.md +91 -0
- package/src/tools.test.ts +119 -0
- package/src/tools.ts +70 -0
- package/src/validate-cli.ts +7 -0
- package/src/validate-config.ts +400 -0
- package/src/validate.ts +66 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alertmanager entities: `Route`, `Receiver`, `InhibitRule`, `TimeInterval`
|
|
3
|
+
* and `AlertmanagerSettings`, which together serialize to `alertmanager.yml`.
|
|
4
|
+
*
|
|
5
|
+
* A route names its receiver and its time intervals either by the declared
|
|
6
|
+
* entity, which keeps the reference checked by TypeScript, or by name string
|
|
7
|
+
* for one declared somewhere chant can't see. PROM201 and PROM204 catch a
|
|
8
|
+
* string that names nothing declared.
|
|
9
|
+
*
|
|
10
|
+
* The routing tree is built from `Route`s: the root is the one route no other
|
|
11
|
+
* route lists as a child. Child routes may be `Route` entities or plain
|
|
12
|
+
* objects.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createResource } from "@intentius/chant/runtime";
|
|
16
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
17
|
+
import type {
|
|
18
|
+
AlertmanagerGlobalConfig,
|
|
19
|
+
InhibitRuleConfig,
|
|
20
|
+
ReceiverConfig,
|
|
21
|
+
RouteConfig,
|
|
22
|
+
TimeIntervalConfig,
|
|
23
|
+
} from "./model";
|
|
24
|
+
|
|
25
|
+
export const RECEIVER_TYPE = "Prometheus::Alertmanager::Receiver";
|
|
26
|
+
export const ROUTE_TYPE = "Prometheus::Alertmanager::Route";
|
|
27
|
+
export const INHIBIT_RULE_TYPE = "Prometheus::Alertmanager::InhibitRule";
|
|
28
|
+
export const TIME_INTERVAL_TYPE = "Prometheus::Alertmanager::TimeInterval";
|
|
29
|
+
export const SETTINGS_TYPE = "Prometheus::Alertmanager::Settings";
|
|
30
|
+
|
|
31
|
+
type Ctor<P, E> = new (props: P) => E;
|
|
32
|
+
|
|
33
|
+
function entityClass<P, E>(entityType: string, className: string, extra?: (self: object, props: P) => void): Ctor<P, E> {
|
|
34
|
+
const Base = createResource(entityType, "prometheus", {}) as unknown as (this: object, props: Record<string, unknown>) => void;
|
|
35
|
+
const Cls = function (this: object, props: P) {
|
|
36
|
+
Base.call(this, (props ?? {}) as unknown as Record<string, unknown>);
|
|
37
|
+
extra?.(this, props);
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(Cls, "name", { value: className });
|
|
40
|
+
return Cls as unknown as Ctor<P, E>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Receiver ────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export type ReceiverProps = ReceiverConfig;
|
|
46
|
+
|
|
47
|
+
export interface ReceiverEntity extends Declarable {
|
|
48
|
+
readonly props: ReceiverProps;
|
|
49
|
+
readonly receiverName: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A notification receiver: webhook, email, Slack and PagerDuty integrations.
|
|
54
|
+
*
|
|
55
|
+
* Credentials (Slack `api_url`, PagerDuty `routing_key`, SMTP passwords)
|
|
56
|
+
* belong in the `*_file` fields; Alertmanager does not expand environment
|
|
57
|
+
* variables in its config, and PROM001 flags a literal.
|
|
58
|
+
*/
|
|
59
|
+
export const Receiver = entityClass<ReceiverProps, ReceiverEntity>(RECEIVER_TYPE, "Receiver", (self, props) => {
|
|
60
|
+
Object.defineProperty(self, "receiverName", { value: props?.name, enumerable: false });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ── TimeInterval ────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export type TimeIntervalProps = TimeIntervalConfig;
|
|
66
|
+
|
|
67
|
+
export interface TimeIntervalEntity extends Declarable {
|
|
68
|
+
readonly props: TimeIntervalProps;
|
|
69
|
+
readonly intervalName: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A named set of time periods a route can mute or activate on. */
|
|
73
|
+
export const TimeInterval = entityClass<TimeIntervalProps, TimeIntervalEntity>(TIME_INTERVAL_TYPE, "TimeInterval", (self, props) => {
|
|
74
|
+
Object.defineProperty(self, "intervalName", { value: props?.name, enumerable: false });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ── Route ───────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
export type ReceiverRef = ReceiverEntity | string;
|
|
80
|
+
export type TimeIntervalRef = TimeIntervalEntity | string;
|
|
81
|
+
|
|
82
|
+
export interface RouteProps extends Omit<RouteConfig, "receiver" | "routes" | "mute_time_intervals" | "active_time_intervals"> {
|
|
83
|
+
/** The receiver alerts matching this route go to. Required on the root route. */
|
|
84
|
+
receiver?: ReceiverRef;
|
|
85
|
+
/** Child routes, tried in order. */
|
|
86
|
+
routes?: Array<RouteEntity | RouteProps>;
|
|
87
|
+
mute_time_intervals?: TimeIntervalRef[];
|
|
88
|
+
active_time_intervals?: TimeIntervalRef[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface RouteEntity extends Declarable {
|
|
92
|
+
readonly props: RouteProps;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A node in the routing tree. Declare one root route; nest the rest under `routes`. */
|
|
96
|
+
export const Route = entityClass<RouteProps, RouteEntity>(ROUTE_TYPE, "Route");
|
|
97
|
+
|
|
98
|
+
// ── InhibitRule ─────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export type InhibitRuleProps = InhibitRuleConfig;
|
|
101
|
+
|
|
102
|
+
export interface InhibitRuleEntity extends Declarable {
|
|
103
|
+
readonly props: InhibitRuleProps;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Mutes alerts matching `target_matchers` while an alert matching `source_matchers` fires. */
|
|
107
|
+
export const InhibitRule = entityClass<InhibitRuleProps, InhibitRuleEntity>(INHIBIT_RULE_TYPE, "InhibitRule");
|
|
108
|
+
|
|
109
|
+
// ── AlertmanagerSettings ────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
export interface AlertmanagerSettingsProps {
|
|
112
|
+
global?: AlertmanagerGlobalConfig;
|
|
113
|
+
/** Paths of notification template files. */
|
|
114
|
+
templates?: string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface AlertmanagerSettingsEntity extends Declarable {
|
|
118
|
+
readonly props: AlertmanagerSettingsProps;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The `global:` block and `templates:` list. Optional; declare at most one. */
|
|
122
|
+
export const AlertmanagerSettings = entityClass<AlertmanagerSettingsProps, AlertmanagerSettingsEntity>(
|
|
123
|
+
SETTINGS_TYPE,
|
|
124
|
+
"AlertmanagerSettings",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// ── Guards ──────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
function isType<T>(type: string) {
|
|
130
|
+
return (value: unknown): value is T =>
|
|
131
|
+
typeof value === "object" && value !== null && (value as Declarable).entityType === type;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const isReceiver = isType<ReceiverEntity>(RECEIVER_TYPE);
|
|
135
|
+
export const isRoute = isType<RouteEntity>(ROUTE_TYPE);
|
|
136
|
+
export const isInhibitRule = isType<InhibitRuleEntity>(INHIBIT_RULE_TYPE);
|
|
137
|
+
export const isTimeInterval = isType<TimeIntervalEntity>(TIME_INTERVAL_TYPE);
|
|
138
|
+
export const isAlertmanagerSettings = isType<AlertmanagerSettingsEntity>(SETTINGS_TYPE);
|
|
139
|
+
|
|
140
|
+
/** True for any of the Alertmanager entity kinds. */
|
|
141
|
+
export function isAlertmanagerEntity(value: unknown): boolean {
|
|
142
|
+
return isReceiver(value) || isRoute(value) || isInhibitRule(value) || isTimeInterval(value) || isAlertmanagerSettings(value);
|
|
143
|
+
}
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared entities -> `RuleFileConfig` / `AlertmanagerConfig` -> YAML.
|
|
3
|
+
*
|
|
4
|
+
* The serializer runs these over a build's entities, and a composite in
|
|
5
|
+
* another lexicon (the k8s `PrometheusRule` path, a ConfigMap holding
|
|
6
|
+
* `alertmanager.yml`) runs them over the entities it has. Both get the same
|
|
7
|
+
* config and the same text.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
11
|
+
import { dump } from "js-yaml";
|
|
12
|
+
import {
|
|
13
|
+
isAlertmanagerSettings,
|
|
14
|
+
isInhibitRule,
|
|
15
|
+
isReceiver,
|
|
16
|
+
isRoute,
|
|
17
|
+
isTimeInterval,
|
|
18
|
+
type AlertmanagerSettingsEntity,
|
|
19
|
+
type ReceiverEntity,
|
|
20
|
+
type RouteEntity,
|
|
21
|
+
type RouteProps,
|
|
22
|
+
type TimeIntervalEntity,
|
|
23
|
+
} from "./alertmanager";
|
|
24
|
+
import type {
|
|
25
|
+
AlertmanagerConfig,
|
|
26
|
+
InhibitRuleConfig,
|
|
27
|
+
ReceiverConfig,
|
|
28
|
+
RouteConfig,
|
|
29
|
+
RuleFileConfig,
|
|
30
|
+
TimeIntervalConfig,
|
|
31
|
+
} from "./model";
|
|
32
|
+
import { isRuleGroup, ruleGroupConfig, type RuleGroupEntity } from "./rules";
|
|
33
|
+
|
|
34
|
+
function byName(a: unknown, b: unknown): number {
|
|
35
|
+
return String(a ?? "").localeCompare(String(b ?? ""));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function entityList(entities: Iterable<Declarable> | Map<string, Declarable>): Declarable[] {
|
|
39
|
+
return entities instanceof Map ? [...entities.values()] : [...entities];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A value with `undefined` keys dropped and entities left as they are. */
|
|
43
|
+
function plain(value: unknown): unknown {
|
|
44
|
+
if (value === undefined) return undefined;
|
|
45
|
+
if (Array.isArray(value)) return value.map(plain);
|
|
46
|
+
if (typeof value === "object" && value !== null) {
|
|
47
|
+
const toJSON = (value as { toJSON?: () => unknown }).toJSON;
|
|
48
|
+
if (typeof toJSON === "function") return plain(toJSON.call(value));
|
|
49
|
+
const out: Record<string, unknown> = {};
|
|
50
|
+
for (const [k, v] of Object.entries(value)) {
|
|
51
|
+
if (v !== undefined) out[k] = plain(v);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Rule file ───────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
export interface BuiltRuleFile {
|
|
61
|
+
config: RuleFileConfig;
|
|
62
|
+
groups: RuleGroupEntity[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The rule file for a set of declared entities. Entities that aren't `RuleGroup`s are ignored. */
|
|
66
|
+
export function buildRuleFile(entities: Iterable<Declarable> | Map<string, Declarable>): BuiltRuleFile {
|
|
67
|
+
const groups: RuleGroupEntity[] = [];
|
|
68
|
+
for (const e of entityList(entities)) {
|
|
69
|
+
if (isRuleGroup(e) && !groups.includes(e)) groups.push(e);
|
|
70
|
+
}
|
|
71
|
+
// Sorted by name: Prometheus evaluates groups independently, so their order
|
|
72
|
+
// carries no meaning, and sorting makes the file the same however the
|
|
73
|
+
// groups were collected (a build's entity map, or a list passed by hand).
|
|
74
|
+
groups.sort((a, b) => byName(a.props.name, b.props.name));
|
|
75
|
+
return { config: { groups: groups.map((g) => ruleGroupConfig(g)) }, groups };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── alertmanager.yml ────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export interface BuiltAlertmanager {
|
|
81
|
+
config: AlertmanagerConfig;
|
|
82
|
+
/** Non-fatal problems, e.g. more than one root route. */
|
|
83
|
+
warnings: string[];
|
|
84
|
+
/** Every Alertmanager entity that took part, including ones reached only by reference. */
|
|
85
|
+
count: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function receiverName(ref: unknown): string {
|
|
89
|
+
return isReceiver(ref) ? ref.props.name : String(ref);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function intervalName(ref: unknown): string {
|
|
93
|
+
return isTimeInterval(ref) ? ref.props.name : String(ref);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function routeProps(route: RouteEntity | RouteProps): RouteProps {
|
|
97
|
+
return isRoute(route) ? route.props : route;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function routeConfig(route: RouteEntity | RouteProps): RouteConfig {
|
|
101
|
+
const p = routeProps(route);
|
|
102
|
+
const out: RouteConfig = {};
|
|
103
|
+
if (p.receiver !== undefined) out.receiver = receiverName(p.receiver);
|
|
104
|
+
if (p.group_by !== undefined) out.group_by = [...p.group_by];
|
|
105
|
+
if (p.continue !== undefined) out.continue = p.continue;
|
|
106
|
+
if (p.matchers !== undefined) out.matchers = [...p.matchers];
|
|
107
|
+
if (p.group_wait !== undefined) out.group_wait = p.group_wait;
|
|
108
|
+
if (p.group_interval !== undefined) out.group_interval = p.group_interval;
|
|
109
|
+
if (p.repeat_interval !== undefined) out.repeat_interval = p.repeat_interval;
|
|
110
|
+
if (p.mute_time_intervals !== undefined) out.mute_time_intervals = p.mute_time_intervals.map(intervalName);
|
|
111
|
+
if (p.active_time_intervals !== undefined) out.active_time_intervals = p.active_time_intervals.map(intervalName);
|
|
112
|
+
if (p.routes !== undefined) out.routes = p.routes.map(routeConfig);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The `alertmanager.yml` for a set of declared entities. Entities that aren't Alertmanager ones are ignored. */
|
|
117
|
+
export function buildAlertmanagerConfig(entities: Iterable<Declarable> | Map<string, Declarable>): BuiltAlertmanager {
|
|
118
|
+
const all = entityList(entities);
|
|
119
|
+
const warnings: string[] = [];
|
|
120
|
+
const receivers: ReceiverEntity[] = [];
|
|
121
|
+
const intervals: TimeIntervalEntity[] = [];
|
|
122
|
+
const routes: RouteEntity[] = [];
|
|
123
|
+
const inhibits: InhibitRuleConfig[] = [];
|
|
124
|
+
let settings: AlertmanagerSettingsEntity | undefined;
|
|
125
|
+
let count = 0;
|
|
126
|
+
|
|
127
|
+
const addReceiver = (r: ReceiverEntity) => {
|
|
128
|
+
if (!receivers.includes(r)) receivers.push(r);
|
|
129
|
+
};
|
|
130
|
+
const addInterval = (t: TimeIntervalEntity) => {
|
|
131
|
+
if (!intervals.includes(t)) intervals.push(t);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
for (const e of all) {
|
|
135
|
+
if (isReceiver(e)) addReceiver(e);
|
|
136
|
+
else if (isTimeInterval(e)) addInterval(e);
|
|
137
|
+
else if (isRoute(e)) routes.push(e);
|
|
138
|
+
else if (isInhibitRule(e)) inhibits.push(plain(e.props) as InhibitRuleConfig);
|
|
139
|
+
else if (isAlertmanagerSettings(e)) {
|
|
140
|
+
if (settings) warnings.push("prometheus: more than one AlertmanagerSettings is declared; the first one is used");
|
|
141
|
+
else settings = e;
|
|
142
|
+
} else continue;
|
|
143
|
+
count++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Receivers and time intervals a route references by entity are part of the
|
|
147
|
+
// config even when not declared on their own, and child Route entities are
|
|
148
|
+
// not roots.
|
|
149
|
+
const children = new Set<RouteEntity>();
|
|
150
|
+
const walk = (route: RouteEntity | RouteProps) => {
|
|
151
|
+
const p = routeProps(route);
|
|
152
|
+
if (isReceiver(p.receiver)) addReceiver(p.receiver);
|
|
153
|
+
for (const t of [...(p.mute_time_intervals ?? []), ...(p.active_time_intervals ?? [])]) {
|
|
154
|
+
if (isTimeInterval(t)) addInterval(t);
|
|
155
|
+
}
|
|
156
|
+
for (const child of p.routes ?? []) {
|
|
157
|
+
if (isRoute(child)) children.add(child);
|
|
158
|
+
walk(child);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
for (const r of routes) walk(r);
|
|
162
|
+
const roots = routes.filter((r) => !children.has(r));
|
|
163
|
+
if (roots.length > 1) {
|
|
164
|
+
warnings.push(
|
|
165
|
+
`prometheus: ${roots.length} root Routes are declared; Alertmanager has one routing tree, so the first is used. Nest the others under its routes.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const config: AlertmanagerConfig = {};
|
|
170
|
+
if (settings?.props.global) config.global = plain(settings.props.global) as AlertmanagerConfig["global"];
|
|
171
|
+
if (settings?.props.templates) config.templates = [...settings.props.templates];
|
|
172
|
+
if (roots.length > 0) config.route = routeConfig(roots[0]);
|
|
173
|
+
if (inhibits.length > 0) config.inhibit_rules = inhibits;
|
|
174
|
+
// Receivers and time intervals are looked up by name, so their order carries
|
|
175
|
+
// no meaning; sorted, the file is the same however they were collected.
|
|
176
|
+
receivers.sort((a, b) => byName(a.props.name, b.props.name));
|
|
177
|
+
intervals.sort((a, b) => byName(a.props.name, b.props.name));
|
|
178
|
+
if (receivers.length > 0) config.receivers = receivers.map((r) => plain(r.props) as ReceiverConfig);
|
|
179
|
+
if (intervals.length > 0) config.time_intervals = intervals.map((t) => plain(t.props) as TimeIntervalConfig);
|
|
180
|
+
|
|
181
|
+
return { config, warnings, count };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── YAML ────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
/** Print a rule file or Alertmanager config as YAML, keys in the order given. */
|
|
187
|
+
export function emitYaml(value: unknown): string {
|
|
188
|
+
return dump(value, { lineWidth: -1, noRefs: true, quotingType: '"', sortKeys: false });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The rule file YAML for a set of declared entities, exactly as the serializer emits it. */
|
|
192
|
+
export function ruleFileYaml(entities: Iterable<Declarable> | Map<string, Declarable>): string {
|
|
193
|
+
return emitYaml(buildRuleFile(entities).config);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The `alertmanager.yml` text for a set of declared entities, exactly as the serializer emits it. */
|
|
197
|
+
export function alertmanagerYaml(entities: Iterable<Declarable> | Map<string, Declarable>): string {
|
|
198
|
+
return emitYaml(buildAlertmanagerConfig(entities).config);
|
|
199
|
+
}
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The entity catalog: every class this package exports, keyed by class name.
|
|
3
|
+
* It feeds the packaged registry (`dist/meta.json`), LSP completions and
|
|
4
|
+
* hover, and the docs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
8
|
+
import { RULE_GROUP_TYPE } from "./rules";
|
|
9
|
+
import { INHIBIT_RULE_TYPE, RECEIVER_TYPE, ROUTE_TYPE, SETTINGS_TYPE, TIME_INTERVAL_TYPE } from "./alertmanager";
|
|
10
|
+
|
|
11
|
+
export interface CatalogEntry {
|
|
12
|
+
className: string;
|
|
13
|
+
entityType: string;
|
|
14
|
+
/** The file the entity serializes into. */
|
|
15
|
+
file: "rule file" | "alertmanager.yml";
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CATALOG: CatalogEntry[] = [
|
|
20
|
+
{
|
|
21
|
+
className: "RuleGroup",
|
|
22
|
+
entityType: RULE_GROUP_TYPE,
|
|
23
|
+
file: "rule file",
|
|
24
|
+
description: "A group of recording and alerting rules, evaluated together on one interval",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
className: "Route",
|
|
28
|
+
entityType: ROUTE_TYPE,
|
|
29
|
+
file: "alertmanager.yml",
|
|
30
|
+
description: "A node in Alertmanager's routing tree: matchers, grouping and timing, and the receiver alerts go to",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
className: "Receiver",
|
|
34
|
+
entityType: RECEIVER_TYPE,
|
|
35
|
+
file: "alertmanager.yml",
|
|
36
|
+
description: "A notification receiver with webhook, email, Slack and PagerDuty integrations",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
className: "InhibitRule",
|
|
40
|
+
entityType: INHIBIT_RULE_TYPE,
|
|
41
|
+
file: "alertmanager.yml",
|
|
42
|
+
description: "Mutes alerts matching target_matchers while an alert matching source_matchers fires",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
className: "TimeInterval",
|
|
46
|
+
entityType: TIME_INTERVAL_TYPE,
|
|
47
|
+
file: "alertmanager.yml",
|
|
48
|
+
description: "A named set of time periods a route can mute or activate on",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
className: "AlertmanagerSettings",
|
|
52
|
+
entityType: SETTINGS_TYPE,
|
|
53
|
+
file: "alertmanager.yml",
|
|
54
|
+
description: "The global block (SMTP, Slack and PagerDuty defaults, resolve_timeout) and the templates list",
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/** The catalog as a chant lexicon registry, keyed by class name. */
|
|
59
|
+
export function lexiconRegistry(): Record<string, LexiconEntry> {
|
|
60
|
+
const out: Record<string, LexiconEntry> = {};
|
|
61
|
+
for (const e of [...CATALOG].sort((a, b) => a.className.localeCompare(b.className))) {
|
|
62
|
+
out[e.className] = { resourceType: e.entityType, kind: "resource", lexicon: "prometheus" };
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation generation for the prometheus lexicon: the generated
|
|
3
|
+
* reference pages from the core docs pipeline, plus the authored pages in
|
|
4
|
+
* docs/pages/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
|
|
10
|
+
|
|
11
|
+
function serviceFromType(resourceType: string): string {
|
|
12
|
+
const parts = resourceType.split("::");
|
|
13
|
+
return parts.length >= 2 ? parts[1] : "Prometheus";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const overview = `The prometheus lexicon types [Prometheus](https://prometheus.io/) recording and
|
|
17
|
+
alerting rules and [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/)
|
|
18
|
+
routing. \`chant build\` emits the rule file Prometheus loads through
|
|
19
|
+
\`rule_files:\`, and the \`alertmanager.yml\` Alertmanager reads.
|
|
20
|
+
|
|
21
|
+
\`\`\`ts
|
|
22
|
+
import { RuleGroup, Route, Receiver } from "@intentius/chant-lexicon-prometheus";
|
|
23
|
+
|
|
24
|
+
export const api = new RuleGroup({
|
|
25
|
+
name: "api",
|
|
26
|
+
rules: [
|
|
27
|
+
{ record: "job:http_errors:ratio5m", expr: 'sum by (job) (rate(http_requests_total{code=~"5.."}[5m])) / sum by (job) (rate(http_requests_total[5m]))' },
|
|
28
|
+
{ alert: "ApiErrors", expr: "job:http_errors:ratio5m > 0.05", for: "10m", labels: { severity: "page" }, annotations: { summary: "5xx ratio above 5%" } },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const oncall = new Receiver({ name: "oncall", pagerduty_configs: [{ routing_key_file: "/etc/alertmanager/secrets/pd-key" }] });
|
|
33
|
+
export const fallback = new Receiver({ name: "fallback", webhook_configs: [{ url: "http://alert-sink:8080/" }] });
|
|
34
|
+
export const root = new Route({ receiver: fallback, group_by: ["alertname", "job"], routes: [{ matchers: ['severity="page"'], receiver: oncall }] });
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
The same \`RuleGroup\` renders into a Prometheus Operator \`PrometheusRule\`
|
|
38
|
+
through the k8s lexicon, so rules are declared once whichever way Prometheus
|
|
39
|
+
loads them.
|
|
40
|
+
|
|
41
|
+
The \`Slo\` composite builds a service level objective to a \`RuleGroup\`:
|
|
42
|
+
error ratios per window, the error budget left, and the SRE Workbook's
|
|
43
|
+
multiwindow multi-burn-rate alerts. See [SLOs](./slos/).
|
|
44
|
+
|
|
45
|
+
Checks run at build time: PromQL syntax in every \`expr\` (PROM104), unique
|
|
46
|
+
group names (PROM101), valid durations (PROM103), routes that name
|
|
47
|
+
declared receivers (PROM201), and every alert severity routed by some route
|
|
48
|
+
(PROM202). \`promtool check rules\` and \`amtool check-config\` are wrapped as
|
|
49
|
+
helpers for tests and CI, and run when installed.
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const outputFormat = `The prometheus lexicon serializes two files:
|
|
53
|
+
|
|
54
|
+
- **The rule file**, from every \`RuleGroup\` in the build: \`groups:\`, each
|
|
55
|
+
with its \`name\`, optional \`interval\`, \`query_offset\`, \`limit\` and
|
|
56
|
+
\`labels\`, and its \`rules\`. This is the file \`rule_files:\` points at and
|
|
57
|
+
\`promtool check rules\` reads.
|
|
58
|
+
- **\`alertmanager.yml\`**, from the \`AlertmanagerSettings\`, \`Route\`,
|
|
59
|
+
\`InhibitRule\`, \`Receiver\` and \`TimeInterval\` entities, in that section
|
|
60
|
+
order: \`global\`, \`templates\`, \`route\`, \`inhibit_rules\`, \`receivers\`,
|
|
61
|
+
\`time_intervals\`.
|
|
62
|
+
|
|
63
|
+
The rule file is the primary output when the build declares any rule
|
|
64
|
+
groups, and \`alertmanager.yml\` is written beside it (\`-o dist/rules.yml\`
|
|
65
|
+
also writes \`dist/alertmanager.yml\`). A build with only Alertmanager
|
|
66
|
+
entities emits \`alertmanager.yml\` as its primary output.
|
|
67
|
+
|
|
68
|
+
- Rules inside a group and child routes keep the order they are written in,
|
|
69
|
+
which is the order Prometheus evaluates and Alertmanager matches them.
|
|
70
|
+
Groups, receivers and time intervals, whose order carries no meaning, are
|
|
71
|
+
sorted by name, so the files are the same however they were built. Keys
|
|
72
|
+
inside a rule come out in the order Prometheus documents them (\`record\`
|
|
73
|
+
or \`alert\`, \`expr\`, \`for\`, \`keep_firing_for\`, \`labels\`,
|
|
74
|
+
\`annotations\`).
|
|
75
|
+
- A route references its receiver and time intervals by name in the output,
|
|
76
|
+
whether the declaration used the entity or a string.
|
|
77
|
+
- The root route is the one \`Route\` no other route lists as a child.
|
|
78
|
+
- Neither file has a metadata channel, so no ownership marker is stamped.
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
export async function generateDocs(opts?: { verbose?: boolean }): Promise<void> {
|
|
82
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
83
|
+
|
|
84
|
+
const config: DocsConfig = {
|
|
85
|
+
name: "prometheus",
|
|
86
|
+
displayName: "Prometheus",
|
|
87
|
+
description: "Typed Prometheus rule groups and Alertmanager routing",
|
|
88
|
+
distDir: join(pkgDir, "dist"),
|
|
89
|
+
outDir: join(pkgDir, "docs"),
|
|
90
|
+
basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/prometheus/",
|
|
91
|
+
overview,
|
|
92
|
+
outputFormat,
|
|
93
|
+
serviceFromType,
|
|
94
|
+
srcDir: join(pkgDir, "src"),
|
|
95
|
+
examplesDir: join(pkgDir, "examples"),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const result = docsPipeline(config);
|
|
99
|
+
writeDocsSite(config, result);
|
|
100
|
+
|
|
101
|
+
if (opts?.verbose) {
|
|
102
|
+
console.error(`Generated ${result.pages.size} documentation pages`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prometheus lexicon's "generation" step.
|
|
3
|
+
*
|
|
4
|
+
* There is no upstream schema to fetch: Prometheus and Alertmanager define
|
|
5
|
+
* their config files as Go structs and publish no machine-readable schema for
|
|
6
|
+
* them. The types are written by hand against the releases in
|
|
7
|
+
* `PROMETHEUS_PIN` (src/pin.ts), and this step derives the registry from the
|
|
8
|
+
* entity catalog, so the packaged `dist/meta.json` can never disagree with
|
|
9
|
+
* the classes the package exports.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
15
|
+
import type { GenerateResult } from "@intentius/chant/codegen/generate";
|
|
16
|
+
import { CATALOG, lexiconRegistry } from "../catalog";
|
|
17
|
+
|
|
18
|
+
export type { GenerateResult };
|
|
19
|
+
|
|
20
|
+
/** The packaged types entry re-exports the compiled declarations, which carry every config type. */
|
|
21
|
+
const TYPES_DTS = `// The prometheus lexicon's entity classes and config types are hand-written in
|
|
22
|
+
// src/ and compiled to dist/index.d.ts; this entry re-exports them.
|
|
23
|
+
export * from "../index";
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
export async function generate(opts?: { verbose?: boolean; force?: boolean }): Promise<GenerateResult> {
|
|
27
|
+
const registry = lexiconRegistry();
|
|
28
|
+
const lexiconJSON = `${JSON.stringify(registry, null, 2)}\n`;
|
|
29
|
+
if (opts?.verbose) {
|
|
30
|
+
console.error(`prometheus: ${CATALOG.length} entities (RuleGroup and the Alertmanager kinds)`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
lexiconJSON,
|
|
34
|
+
typesDTS: TYPES_DTS,
|
|
35
|
+
indexTS: "",
|
|
36
|
+
resources: CATALOG.length,
|
|
37
|
+
properties: 0,
|
|
38
|
+
enums: 0,
|
|
39
|
+
warnings: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Write the registry to src/generated/lexicon-prometheus.json, for tools that read it from there. */
|
|
44
|
+
export function writeGeneratedFiles(result: GenerateResult): void {
|
|
45
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
46
|
+
const dir = join(pkgDir, "src", "generated");
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
writeFileSync(join(dir, "lexicon-prometheus.json"), result.lexiconJSON);
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prometheus lexicon packaging, delegating to core's packagePipeline.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import {
|
|
9
|
+
packagePipeline,
|
|
10
|
+
collectSkills,
|
|
11
|
+
type PackageOptions,
|
|
12
|
+
type PackageResult,
|
|
13
|
+
} from "@intentius/chant/codegen/package";
|
|
14
|
+
import { generate } from "./generate";
|
|
15
|
+
import { prometheusSkills } from "../skill-defs";
|
|
16
|
+
|
|
17
|
+
export type { PackageOptions, PackageResult };
|
|
18
|
+
|
|
19
|
+
// src/codegen/package.ts -> src/
|
|
20
|
+
const srcDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
21
|
+
|
|
22
|
+
export async function packageLexicon(opts: PackageOptions = {}): Promise<PackageResult> {
|
|
23
|
+
const pkgJson = JSON.parse(readFileSync(join(srcDir, "..", "package.json"), "utf-8"));
|
|
24
|
+
return packagePipeline(
|
|
25
|
+
{
|
|
26
|
+
generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
|
|
27
|
+
buildManifest: () => ({
|
|
28
|
+
name: "prometheus",
|
|
29
|
+
version: pkgJson.version ?? "0.0.1",
|
|
30
|
+
chantVersion: ">=0.1.0",
|
|
31
|
+
namespace: "Prometheus",
|
|
32
|
+
intrinsics: [],
|
|
33
|
+
pseudoParameters: {},
|
|
34
|
+
}),
|
|
35
|
+
srcDir,
|
|
36
|
+
collectSkills: () => collectSkills(prometheusSkills()),
|
|
37
|
+
version: pkgJson.version ?? "0.0.1",
|
|
38
|
+
},
|
|
39
|
+
opts,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The composite catalog matches what this lexicon exports (#2662).
|
|
3
|
+
*
|
|
4
|
+
* `catalog.ts` is generated by `npm run generate:composite-catalogs` and is
|
|
5
|
+
* what `chant serve mcp` lists.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "vitest";
|
|
9
|
+
import { isCompositeDefinition } from "@intentius/chant";
|
|
10
|
+
import * as composites from "./index";
|
|
11
|
+
import { compositeCatalog } from "./catalog";
|
|
12
|
+
import { prometheusPlugin } from "../plugin";
|
|
13
|
+
|
|
14
|
+
const exported = Object.entries(composites)
|
|
15
|
+
.filter(([, value]) => isCompositeDefinition(value))
|
|
16
|
+
.map(([name]) => name)
|
|
17
|
+
.sort();
|
|
18
|
+
const catalogued = compositeCatalog.map((entry) => entry.name).sort();
|
|
19
|
+
|
|
20
|
+
describe("the prometheus composite catalog", () => {
|
|
21
|
+
test("every exported composite has an entry, and every entry names an exported composite", () => {
|
|
22
|
+
expect(exported).toEqual(["Slo"]);
|
|
23
|
+
expect(catalogued, "regenerate with `npm run generate:composite-catalogs -- prometheus`").toEqual(exported);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("every entry says what it is and what it bundles", () => {
|
|
27
|
+
for (const entry of compositeCatalog) {
|
|
28
|
+
expect(entry.lexicon, entry.name).toBe("prometheus");
|
|
29
|
+
expect(entry.description.length, entry.name).toBeGreaterThan(0);
|
|
30
|
+
expect(entry.bundles, entry.name).toEqual(["RuleGroup"]);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("the plugin contributes it", () => {
|
|
35
|
+
expect(prometheusPlugin.composites?.()).toBe(compositeCatalog);
|
|
36
|
+
});
|
|
37
|
+
});
|