run-and-notify 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.env.example +3 -0
  2. package/LICENSE +619 -0
  3. package/README.md +132 -0
  4. package/config.example.json +54 -0
  5. package/dist/bundle/run-and-notify.mjs +516 -0
  6. package/dist/bundle/run-and-notify.mjs.map +7 -0
  7. package/examples/apps/daily-digest-failure.mjs +17 -0
  8. package/examples/apps/daily-digest-html-success.mjs +14 -0
  9. package/examples/apps/daily-digest-success.mjs +16 -0
  10. package/examples/apps/full-html.mjs +14 -0
  11. package/examples/apps/full-markdown.mjs +19 -0
  12. package/examples/apps/full-raw.mjs +4 -0
  13. package/examples/apps/structured-jsonl-success.mjs +23 -0
  14. package/examples/config-html.json +8 -0
  15. package/examples/config-jsonl.json +8 -0
  16. package/examples/config-markdown.json +8 -0
  17. package/examples/config-raw.json +8 -0
  18. package/examples/daily-digest-html/config.json +46 -0
  19. package/examples/daily-digest-html/templates/error.email.html.hbs +9 -0
  20. package/examples/daily-digest-html/templates/error.slack.blocks.json.hbs +9 -0
  21. package/examples/daily-digest-html/templates/error.subject.hbs +1 -0
  22. package/examples/daily-digest-html/templates/error.text.hbs +7 -0
  23. package/examples/daily-digest-html/templates/success.email.html.hbs +1 -0
  24. package/examples/daily-digest-html/templates/success.slack.blocks.json.hbs +9 -0
  25. package/examples/daily-digest-html/templates/success.subject.hbs +1 -0
  26. package/examples/daily-digest-html/templates/success.text.hbs +1 -0
  27. package/examples/daily-digest-markdown/config.json +46 -0
  28. package/examples/daily-digest-markdown/templates/error.email.html.hbs +9 -0
  29. package/examples/daily-digest-markdown/templates/error.slack.blocks.json.hbs +16 -0
  30. package/examples/daily-digest-markdown/templates/error.subject.hbs +1 -0
  31. package/examples/daily-digest-markdown/templates/error.text.hbs +9 -0
  32. package/examples/daily-digest-markdown/templates/success.email.html.hbs +1 -0
  33. package/examples/daily-digest-markdown/templates/success.slack.blocks.json.hbs +9 -0
  34. package/examples/daily-digest-markdown/templates/success.subject.hbs +1 -0
  35. package/examples/daily-digest-markdown/templates/success.text.hbs +1 -0
  36. package/examples/full-html/config.json +46 -0
  37. package/examples/full-html/templates/email.html.hbs +22 -0
  38. package/examples/full-html/templates/slack.blocks.json.hbs +9 -0
  39. package/examples/full-html/templates/slack.text.hbs +1 -0
  40. package/examples/full-html/templates/subject.hbs +1 -0
  41. package/examples/full-html/templates/text.hbs +12 -0
  42. package/examples/full-markdown/config.json +46 -0
  43. package/examples/full-markdown/templates/email.html.hbs +1 -0
  44. package/examples/full-markdown/templates/slack.blocks.json.hbs +9 -0
  45. package/examples/full-markdown/templates/slack.text.hbs +1 -0
  46. package/examples/full-markdown/templates/subject.hbs +1 -0
  47. package/examples/full-markdown/templates/text.hbs +18 -0
  48. package/examples/full-raw/config.json +46 -0
  49. package/examples/full-raw/templates/email.html.hbs +22 -0
  50. package/examples/full-raw/templates/slack.blocks.json.hbs +16 -0
  51. package/examples/full-raw/templates/slack.text.hbs +1 -0
  52. package/examples/full-raw/templates/subject.hbs +1 -0
  53. package/examples/full-raw/templates/text.hbs +12 -0
  54. package/examples/minimal/config.json +20 -0
  55. package/examples/structured-jsonl-html/config.json +46 -0
  56. package/examples/structured-jsonl-html/templates/error.email.html.hbs +6 -0
  57. package/examples/structured-jsonl-html/templates/error.slack.blocks.json.hbs +9 -0
  58. package/examples/structured-jsonl-html/templates/error.subject.hbs +1 -0
  59. package/examples/structured-jsonl-html/templates/error.text.hbs +4 -0
  60. package/examples/structured-jsonl-html/templates/success.email.html.hbs +16 -0
  61. package/examples/structured-jsonl-html/templates/success.slack.blocks.json.hbs +9 -0
  62. package/examples/structured-jsonl-html/templates/success.subject.hbs +1 -0
  63. package/examples/structured-jsonl-html/templates/success.text.hbs +6 -0
  64. package/examples/structured-jsonl-markdown/config.json +46 -0
  65. package/examples/structured-jsonl-markdown/templates/error.email.html.hbs +9 -0
  66. package/examples/structured-jsonl-markdown/templates/error.slack.blocks.json.hbs +9 -0
  67. package/examples/structured-jsonl-markdown/templates/error.subject.hbs +1 -0
  68. package/examples/structured-jsonl-markdown/templates/error.text.hbs +9 -0
  69. package/examples/structured-jsonl-markdown/templates/success.email.html.hbs +1 -0
  70. package/examples/structured-jsonl-markdown/templates/success.slack.blocks.json.hbs +9 -0
  71. package/examples/structured-jsonl-markdown/templates/success.subject.hbs +1 -0
  72. package/examples/structured-jsonl-markdown/templates/success.text.hbs +19 -0
  73. package/package.json +79 -0
  74. package/schemas/config.schema.json +259 -0
@@ -0,0 +1,12 @@
1
+ # Raw Command Result
2
+
3
+ - Executed: {{datetimeFromISO8601 executedAt}}
4
+ - Command: `{{shellCommand command}}`
5
+ - CWD: `{{shellToken cwd}}`
6
+ - Status: `{{status}}`
7
+
8
+ ## Output
9
+ {{stdout.raw}}
10
+
11
+ ## Errors
12
+ {{stderr.raw}}
@@ -0,0 +1,20 @@
1
+ {
2
+ "transports": {
3
+ "smtp": {
4
+ "enabled": true,
5
+ "host": "smtp.example.com",
6
+ "port": 587,
7
+ "from": "run-and-notify@example.com",
8
+ "to": ["ops@example.com"],
9
+ "auth": {
10
+ "user": "run-and-notify@example.com",
11
+ "passEnvVar": "SMTP_PASS"
12
+ }
13
+ },
14
+ "slack": {
15
+ "enabled": true,
16
+ "tokenEnvVar": "SLACK_BOT_TOKEN",
17
+ "defaultChannel": "#ops"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "locale": "en-US",
3
+ "timeoutSeconds": 0,
4
+ "showStderrIfSuccess": false,
5
+ "templatesDir": "examples/structured-jsonl-html/templates",
6
+ "stdout": {
7
+ "format": "jsonl"
8
+ },
9
+ "stderr": {
10
+ "format": "jsonl"
11
+ },
12
+ "transports": {
13
+ "smtp": {
14
+ "enabled": true,
15
+ "host": "smtp.example.com",
16
+ "port": 587,
17
+ "from": "run-and-notify@example.com",
18
+ "to": ["reports@example.com"]
19
+ },
20
+ "slack": {
21
+ "enabled": true,
22
+ "tokenEnvVar": "SLACK_BOT_TOKEN",
23
+ "defaultChannel": "#reports"
24
+ }
25
+ },
26
+ "success": {
27
+ "email": {
28
+ "subject": "success.subject.hbs",
29
+ "html": "success.email.html.hbs",
30
+ "text": "success.text.hbs"
31
+ },
32
+ "slack": {
33
+ "blocks": "success.slack.blocks.json.hbs"
34
+ }
35
+ },
36
+ "error": {
37
+ "email": {
38
+ "subject": "error.subject.hbs",
39
+ "html": "error.email.html.hbs",
40
+ "text": "error.text.hbs"
41
+ },
42
+ "slack": {
43
+ "blocks": "error.slack.blocks.json.hbs"
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,6 @@
1
+ <h1>Structured report failed</h1>
2
+ <ul>
3
+ {{#each stderr.lines}}
4
+ <li>{{#if timestamp}}{{datetimeFromISO8601 timestamp}} {{{escapeHtml msg}}}{{else}}{{{escapeHtml raw}}}{{/if}}</li>
5
+ {{/each}}
6
+ </ul>
@@ -0,0 +1,9 @@
1
+ [
2
+ {
3
+ "type": "section",
4
+ "text": {
5
+ "type": "mrkdwn",
6
+ "text": {{{jsonString (concat "*Structured report failed*" (nl) "*Status:* " status)}}}
7
+ }
8
+ }
9
+ ]
@@ -0,0 +1 @@
1
+ Structured report failed with status {{status}}
@@ -0,0 +1,4 @@
1
+ Structured report failed
2
+ {{#each stderr.lines}}
3
+ {{#if timestamp}}{{datetimeFromISO8601 timestamp}} {{msg}}{{else}}{{raw}}{{/if}}
4
+ {{/each}}
@@ -0,0 +1,16 @@
1
+ <h1>{{stdout.lines.[0].title}}</h1>
2
+ <ul>
3
+ {{#each stdout.lines.[0].items}}
4
+ <li>{{this}}</li>
5
+ {{/each}}
6
+ </ul>
7
+ <table>
8
+ {{#each stdout.lines.[0].table}}
9
+ <tr><td>{{this.[0]}}</td><td>{{this.[1]}}</td></tr>
10
+ {{/each}}
11
+ </table>
12
+ <p>Date: {{dateFromISO8601 stdout.lines.[0].date}}</p>
13
+ <p>Time: {{timeFromISO8601 stdout.lines.[0].time}}</p>
14
+ <p>Date/time: {{datetimeFromISO8601 stdout.lines.[0].datetime}}</p>
15
+ {{{markdownToHtml stdout.lines.[0].childMarkdown}}}
16
+ <pre>{{{rawToHtml stdout.lines.[0].childRaw}}}</pre>
@@ -0,0 +1,9 @@
1
+ [
2
+ {
3
+ "type": "section",
4
+ "text": {
5
+ "type": "mrkdwn",
6
+ "text": {{{jsonString (slackCodeBlock (concat stdout.lines.[0].title (nl) stdout.lines.[0].childMarkdown (nl) (datetimeFromISO8601 stdout.lines.[0].datetime)))}}}
7
+ }
8
+ }
9
+ ]
@@ -0,0 +1 @@
1
+ {{stdout.lines.[0].title}}
@@ -0,0 +1,6 @@
1
+ {{stdout.lines.[0].title}}
2
+ {{#each stdout.lines.[0].items}}
3
+ - {{this}}
4
+ {{/each}}
5
+ {{stdout.lines.[0].childMarkdown}}
6
+ {{stdout.lines.[0].childRaw}}
@@ -0,0 +1,46 @@
1
+ {
2
+ "locale": "en-US",
3
+ "timeoutSeconds": 0,
4
+ "showStderrIfSuccess": false,
5
+ "templatesDir": "examples/structured-jsonl-markdown/templates",
6
+ "stdout": {
7
+ "format": "jsonl"
8
+ },
9
+ "stderr": {
10
+ "format": "jsonl"
11
+ },
12
+ "transports": {
13
+ "smtp": {
14
+ "enabled": true,
15
+ "host": "smtp.example.com",
16
+ "port": 587,
17
+ "from": "run-and-notify@example.com",
18
+ "to": ["reports@example.com"]
19
+ },
20
+ "slack": {
21
+ "enabled": true,
22
+ "tokenEnvVar": "SLACK_BOT_TOKEN",
23
+ "defaultChannel": "#reports"
24
+ }
25
+ },
26
+ "success": {
27
+ "email": {
28
+ "subject": "success.subject.hbs",
29
+ "html": "success.email.html.hbs",
30
+ "text": "success.text.hbs"
31
+ },
32
+ "slack": {
33
+ "blocks": "success.slack.blocks.json.hbs"
34
+ }
35
+ },
36
+ "error": {
37
+ "email": {
38
+ "subject": "error.subject.hbs",
39
+ "html": "error.email.html.hbs",
40
+ "text": "error.text.hbs"
41
+ },
42
+ "slack": {
43
+ "blocks": "error.slack.blocks.json.hbs"
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,9 @@
1
+ <h1>Structured report failed</h1>
2
+ <p><strong>CWD:</strong> {{shellToken cwd}}</p>
3
+ <p><strong>Command:</strong> {{shellCommand command}}</p>
4
+ <p><strong>Status:</strong> {{status}}</p>
5
+ <ul>
6
+ {{#each stderr.lines}}
7
+ <li>{{#if timestamp}}{{datetimeFromISO8601 timestamp}} {{{escapeHtml msg}}}{{else}}{{{escapeHtml raw}}}{{/if}}</li>
8
+ {{/each}}
9
+ </ul>
@@ -0,0 +1,9 @@
1
+ [
2
+ {
3
+ "type": "section",
4
+ "text": {
5
+ "type": "mrkdwn",
6
+ "text": {{{jsonString (concat "*Structured report failed*" (nl) "*Status:* " status)}}}
7
+ }
8
+ }
9
+ ]
@@ -0,0 +1 @@
1
+ Structured report failed with status {{status}}
@@ -0,0 +1,9 @@
1
+ Structured report failed
2
+
3
+ - CWD: `{{shellToken cwd}}`
4
+ - Command: `{{shellCommand command}}`
5
+ - Status: `{{status}}`
6
+
7
+ {{#each stderr.lines}}
8
+ - {{#if timestamp}}{{datetimeFromISO8601 timestamp}} {{{escapeMarkdown msg}}}{{else}}{{{escapeMarkdown raw}}}{{/if}}
9
+ {{/each}}
@@ -0,0 +1 @@
1
+ {{{markdownToHtml (concat "# " stdout.lines.[0].title (nl) (nl) "- " stdout.lines.[0].items.[0] (nl) "- " stdout.lines.[0].items.[1] (nl) (nl) (htmlToMarkdown stdout.lines.[0].childHtml) (nl) (nl) (escapeMarkdown stdout.lines.[0].childRaw))}}}
@@ -0,0 +1,9 @@
1
+ [
2
+ {
3
+ "type": "section",
4
+ "text": {
5
+ "type": "mrkdwn",
6
+ "text": {{{jsonString (slackCodeBlock (concat stdout.lines.[0].title (nl) "- " stdout.lines.[0].items.[0] (nl) "- " stdout.lines.[0].items.[1] (nl) (datetimeFromISO8601 stdout.lines.[0].datetime)))}}}
7
+ }
8
+ }
9
+ ]
@@ -0,0 +1 @@
1
+ {{stdout.lines.[0].title}}
@@ -0,0 +1,19 @@
1
+ # {{stdout.lines.[0].title}}
2
+
3
+ {{#each stdout.lines.[0].items}}
4
+ - {{this}}
5
+ {{/each}}
6
+
7
+ | Col 1 | Col 2 |
8
+ | --- | --- |
9
+ {{#each stdout.lines.[0].table}}
10
+ | {{this.[0]}} | {{this.[1]}} |
11
+ {{/each}}
12
+
13
+ - Date: {{dateFromISO8601 stdout.lines.[0].date}}
14
+ - Time: {{timeFromISO8601 stdout.lines.[0].time}}
15
+ - Date/time: {{datetimeFromISO8601 stdout.lines.[0].datetime}}
16
+
17
+ {{htmlToMarkdown stdout.lines.[0].childHtml}}
18
+
19
+ {{escapeMarkdown stdout.lines.[0].childRaw}}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "run-and-notify",
3
+ "version": "0.1.0",
4
+ "description": "Execute a command and send the notification results via better-notify",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/barbieri/run-and-notify.git"
8
+ },
9
+ "type": "module",
10
+ "private": false,
11
+ "license": "GPL-3.0-or-later",
12
+ "keywords": [
13
+ "cli",
14
+ "notifications",
15
+ "slack",
16
+ "email",
17
+ "automation",
18
+ "monitoring",
19
+ "run-and-notify"
20
+ ],
21
+ "engines": {
22
+ "node": ">=24.15.0"
23
+ },
24
+ "files": [
25
+ "dist/bundle",
26
+ "schemas",
27
+ "examples",
28
+ "config.example.json",
29
+ ".env.example",
30
+ "LICENSE",
31
+ "README.md"
32
+ ],
33
+ "bin": {
34
+ "run-and-notify": "dist/bundle/run-and-notify.mjs"
35
+ },
36
+ "devDependencies": {
37
+ "@betternotify/core": "1.0.0-beta.8",
38
+ "@betternotify/email": "1.0.0-beta.4",
39
+ "@betternotify/slack": "1.0.0-beta.4",
40
+ "@betternotify/smtp": "1.0.0-beta.3",
41
+ "@biomejs/biome": "^2.4.15",
42
+ "@tsconfig/strictest": "^2.0.8",
43
+ "@types/lodash": "^4.17.24",
44
+ "@types/node": "^25.9.1",
45
+ "@types/turndown": "^5.0.5",
46
+ "@types/yargs": "^17.0.35",
47
+ "@vitest/coverage-v8": "3.2.4",
48
+ "ajv": "8.20.0",
49
+ "dotenv": "^17.4.2",
50
+ "esbuild": "^0.28.0",
51
+ "handlebars": "^4.7.8",
52
+ "husky": "^9.1.7",
53
+ "lodash": "^4.18.1",
54
+ "markdown-to-slack-blocks": "^1.5.0",
55
+ "marked": "^17.0.1",
56
+ "npm-run-all": "^4.1.5",
57
+ "pino": "^10.3.1",
58
+ "pino-pretty": "^13.1.3",
59
+ "tsx": "^4.20.6",
60
+ "turndown": "^7.2.2",
61
+ "typescript": "6.0.3",
62
+ "vitest": "3.2.4",
63
+ "yargs": "^18.0.0"
64
+ },
65
+ "scripts": {
66
+ "build": "tsc && node scripts/build-cli.mjs",
67
+ "build:cli": "node scripts/build-cli.mjs",
68
+ "check": "biome check --error-on-warnings .",
69
+ "check:fix": "biome check --write --error-on-warnings .",
70
+ "format": "biome format --write .",
71
+ "lint": "biome lint --error-on-warnings .",
72
+ "qa": "run-p check build test typecheck",
73
+ "run-and-notify": "node --import tsx src/run-and-notify.ts",
74
+ "run-and-notify:bundle": "node dist/bundle/run-and-notify.mjs",
75
+ "test": "vitest run",
76
+ "test:watch": "vitest",
77
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json"
78
+ }
79
+ }
@@ -0,0 +1,259 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/barbieri/run-and-notify/schemas/config.schema.json",
4
+ "title": "run-and-notify configuration",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "description": "Human-readable name of the automation used in default notification titles and subjects. Defaults to the command with arguments",
11
+ "minLength": 1
12
+ },
13
+ "locale": {
14
+ "type": "string",
15
+ "description": "BCP 47 locale used by date and time template helpers.",
16
+ "default": "en-US",
17
+ "minLength": 1
18
+ },
19
+ "cwd": {
20
+ "type": "string",
21
+ "description": "Working directory used to execute the command. CLI override: --cwd=/path."
22
+ },
23
+ "dryRun": {
24
+ "type": "boolean",
25
+ "description": "Render and log notifications without sending through configured transports. It DOES EXECUTE the target command, just does not send the notifications!",
26
+ "default": false
27
+ },
28
+ "propagateExitCode": {
29
+ "type": "boolean",
30
+ "description": "When true, the run-and-notify process exits with the target command status after notifications are delivered.",
31
+ "default": true
32
+ },
33
+ "timeoutSeconds": {
34
+ "type": "integer",
35
+ "description": "Command timeout in seconds. A value of 0 disables the timeout.",
36
+ "default": 0,
37
+ "minimum": 0
38
+ },
39
+ "showStderrIfSuccess": {
40
+ "type": "boolean",
41
+ "description": "When false, success templates should not render stderr.",
42
+ "default": false
43
+ },
44
+ "hideCommandIfSuccess": {
45
+ "type": "boolean",
46
+ "description": "When true, success templates should render only command output content and omit command metadata, visible separators, and output headings.",
47
+ "default": false
48
+ },
49
+ "templatesDir": {
50
+ "type": "string",
51
+ "description": "Directory containing Handlebars templates and partials. When omitted, built-in templates are used.",
52
+ "minLength": 1
53
+ },
54
+ "stdout": {
55
+ "type": "object",
56
+ "description": "Parsing configuration for command stdout.",
57
+ "additionalProperties": false,
58
+ "properties": {
59
+ "format": {
60
+ "type": "string",
61
+ "description": "How stdout is exposed to templates.",
62
+ "default": "raw",
63
+ "enum": ["raw", "jsonl", "markdown", "html"]
64
+ }
65
+ },
66
+ "required": ["format"]
67
+ },
68
+ "stderr": {
69
+ "type": "object",
70
+ "description": "Parsing configuration for command stderr.",
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "format": {
74
+ "type": "string",
75
+ "description": "How stderr is exposed to templates.",
76
+ "default": "raw",
77
+ "enum": ["raw", "jsonl", "markdown", "html"]
78
+ }
79
+ },
80
+ "required": ["format"]
81
+ },
82
+ "transports": {
83
+ "type": "object",
84
+ "description": "Better-Notify transport configuration.",
85
+ "default": {},
86
+ "additionalProperties": false,
87
+ "properties": {
88
+ "smtp": {
89
+ "type": "object",
90
+ "description": "SMTP email transport configuration.",
91
+ "additionalProperties": false,
92
+ "properties": {
93
+ "enabled": {
94
+ "type": "boolean",
95
+ "description": "Enable SMTP email notifications.",
96
+ "default": true
97
+ },
98
+ "host": {
99
+ "type": "string",
100
+ "description": "SMTP server host.",
101
+ "minLength": 1
102
+ },
103
+ "port": {
104
+ "type": "integer",
105
+ "description": "SMTP server port.",
106
+ "minimum": 1,
107
+ "maximum": 65535
108
+ },
109
+ "secure": {
110
+ "type": "boolean",
111
+ "description": "Use TLS from connection start instead of STARTTLS.",
112
+ "default": false
113
+ },
114
+ "from": {
115
+ "type": "string",
116
+ "description": "Email sender address.",
117
+ "minLength": 1
118
+ },
119
+ "to": {
120
+ "type": "array",
121
+ "description": "Email recipient addresses.",
122
+ "items": {
123
+ "type": "string",
124
+ "minLength": 1
125
+ },
126
+ "minItems": 1
127
+ },
128
+ "auth": {
129
+ "type": "object",
130
+ "description": "SMTP authentication configuration.",
131
+ "additionalProperties": false,
132
+ "properties": {
133
+ "user": {
134
+ "type": "string",
135
+ "description": "SMTP username."
136
+ },
137
+ "passEnvVar": {
138
+ "type": "string",
139
+ "description": "Environment variable containing the SMTP password.",
140
+ "minLength": 1
141
+ }
142
+ }
143
+ }
144
+ },
145
+ "required": ["enabled", "host", "port", "from", "to"]
146
+ },
147
+ "slack": {
148
+ "type": "object",
149
+ "description": "Better-Notify Slack transport configuration.",
150
+ "additionalProperties": false,
151
+ "properties": {
152
+ "enabled": {
153
+ "type": "boolean",
154
+ "description": "Enable Slack notifications.",
155
+ "default": true
156
+ },
157
+ "tokenEnvVar": {
158
+ "type": "string",
159
+ "description": "Environment variable containing the Slack bot token used by @betternotify/slack slackTransport().",
160
+ "minLength": 1
161
+ },
162
+ "defaultChannel": {
163
+ "type": "string",
164
+ "description": "Default Slack channel ID or name used when Slack templates do not set a destination."
165
+ }
166
+ },
167
+ "required": ["enabled", "tokenEnvVar"]
168
+ }
169
+ }
170
+ },
171
+ "success": {
172
+ "$ref": "#/definitions/notification",
173
+ "description": "Templates used when the command exits with status 0.",
174
+ "default": {
175
+ "email": {
176
+ "subject": "success.subject.hbs",
177
+ "html": "default.email.html.hbs",
178
+ "text": "default.text.hbs"
179
+ },
180
+ "slack": {
181
+ "blocks": "default.slack.blocks.json.hbs"
182
+ }
183
+ }
184
+ },
185
+ "error": {
186
+ "$ref": "#/definitions/notification",
187
+ "description": "Templates used when the command exits with a non-zero status.",
188
+ "default": {
189
+ "email": {
190
+ "subject": "error.subject.hbs",
191
+ "html": "default.email.html.hbs",
192
+ "text": "default.text.hbs"
193
+ },
194
+ "slack": {
195
+ "blocks": "default.slack.blocks.json.hbs"
196
+ }
197
+ }
198
+ }
199
+ },
200
+ "required": [
201
+ "locale",
202
+ "dryRun",
203
+ "propagateExitCode",
204
+ "timeoutSeconds",
205
+ "showStderrIfSuccess",
206
+ "hideCommandIfSuccess",
207
+ "stdout",
208
+ "stderr",
209
+ "transports",
210
+ "success",
211
+ "error"
212
+ ],
213
+ "definitions": {
214
+ "emailTemplates": {
215
+ "type": "object",
216
+ "additionalProperties": false,
217
+ "properties": {
218
+ "subject": {
219
+ "type": "string",
220
+ "description": "Handlebars template filename used for email subject."
221
+ },
222
+ "html": {
223
+ "type": "string",
224
+ "description": "Handlebars template filename used for email HTML."
225
+ },
226
+ "text": {
227
+ "type": "string",
228
+ "description": "Handlebars template filename used for plain text email."
229
+ }
230
+ }
231
+ },
232
+ "slackTemplates": {
233
+ "type": "object",
234
+ "additionalProperties": false,
235
+ "properties": {
236
+ "text": {
237
+ "type": "string",
238
+ "description": "Optional Handlebars template filename used for Slack fallback text. When omitted, fallback text is generated from command and status."
239
+ },
240
+ "blocks": {
241
+ "type": "string",
242
+ "description": "Handlebars template filename that renders a JSON array of Slack blocks."
243
+ }
244
+ }
245
+ },
246
+ "notification": {
247
+ "type": "object",
248
+ "additionalProperties": false,
249
+ "properties": {
250
+ "email": {
251
+ "$ref": "#/definitions/emailTemplates"
252
+ },
253
+ "slack": {
254
+ "$ref": "#/definitions/slackTemplates"
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }