@releasekit/notes 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.
@@ -0,0 +1,177 @@
1
+ import { NotesConfig } from '@releasekit/config';
2
+ export { loadAuth, saveAuth } from '@releasekit/config';
3
+ import { ReleaseKitError } from '@releasekit/core';
4
+ export { EXIT_CODES } from '@releasekit/core';
5
+
6
+ declare function loadConfig(projectDir?: string, configFile?: string): NotesConfig;
7
+ declare function getDefaultConfig(): NotesConfig;
8
+
9
+ interface RetryOptions {
10
+ maxAttempts?: number;
11
+ initialDelay?: number;
12
+ maxDelay?: number;
13
+ backoffFactor?: number;
14
+ }
15
+
16
+ type ChangelogType = 'added' | 'changed' | 'deprecated' | 'removed' | 'fixed' | 'security';
17
+ interface ChangelogEntry {
18
+ type: ChangelogType;
19
+ description: string;
20
+ issueIds?: string[];
21
+ scope?: string;
22
+ originalType?: string;
23
+ breaking?: boolean;
24
+ }
25
+ interface PackageChangelog {
26
+ packageName: string;
27
+ version: string;
28
+ previousVersion: string | null;
29
+ revisionRange: string;
30
+ repoUrl: string | null;
31
+ date: string;
32
+ entries: ChangelogEntry[];
33
+ }
34
+ type InputSource = 'package-versioner' | 'conventional-changelog' | 'git-log' | 'manual';
35
+ interface ChangelogInput {
36
+ source: InputSource;
37
+ packages: PackageChangelog[];
38
+ metadata?: {
39
+ repoUrl?: string;
40
+ defaultBranch?: string;
41
+ };
42
+ }
43
+ interface EnhancedData {
44
+ entries: ChangelogEntry[];
45
+ summary?: string;
46
+ categories?: Record<string, ChangelogEntry[]>;
47
+ releaseNotes?: string;
48
+ }
49
+ interface TemplateContext {
50
+ packageName: string;
51
+ version: string;
52
+ previousVersion: string | null;
53
+ date: string;
54
+ repoUrl: string | null;
55
+ entries: ChangelogEntry[];
56
+ compareUrl?: string;
57
+ enhanced?: EnhancedData;
58
+ }
59
+ interface DocumentContext {
60
+ project: {
61
+ name: string;
62
+ repoUrl?: string;
63
+ };
64
+ versions: TemplateContext[];
65
+ unreleased?: TemplateContext;
66
+ compareUrls?: Record<string, string>;
67
+ }
68
+ interface LLMOptions {
69
+ timeout?: number;
70
+ maxTokens?: number;
71
+ temperature?: number;
72
+ }
73
+ interface LLMConfig {
74
+ provider: string;
75
+ model: string;
76
+ baseURL?: string;
77
+ apiKey?: string;
78
+ options?: LLMOptions;
79
+ concurrency?: number;
80
+ retry?: RetryOptions;
81
+ tasks?: {
82
+ summarize?: boolean;
83
+ enhance?: boolean;
84
+ categorize?: boolean;
85
+ releaseNotes?: boolean;
86
+ };
87
+ }
88
+ type OutputFormat = 'markdown' | 'github-release' | 'json';
89
+ interface OutputConfig {
90
+ format: OutputFormat;
91
+ file?: string;
92
+ options?: Record<string, unknown>;
93
+ }
94
+ type MonorepoMode = 'root' | 'packages' | 'both';
95
+ interface MonorepoConfig {
96
+ mode?: MonorepoMode;
97
+ rootPath?: string;
98
+ packagesPath?: string;
99
+ }
100
+ type UpdateStrategy = 'prepend' | 'regenerate';
101
+ type TemplateEngine = 'handlebars' | 'liquid' | 'ejs';
102
+ interface TemplateConfig {
103
+ path?: string;
104
+ engine?: TemplateEngine;
105
+ }
106
+ interface Config {
107
+ input?: {
108
+ source?: string;
109
+ file?: string;
110
+ };
111
+ output: OutputConfig[];
112
+ monorepo?: MonorepoConfig;
113
+ templates?: TemplateConfig;
114
+ llm?: LLMConfig;
115
+ updateStrategy?: UpdateStrategy;
116
+ }
117
+ interface CompleteOptions {
118
+ maxTokens?: number;
119
+ temperature?: number;
120
+ timeout?: number;
121
+ }
122
+
123
+ declare function createTemplateContext(pkg: PackageChangelog): TemplateContext;
124
+ declare function runPipeline(input: ChangelogInput, config: Config, dryRun: boolean): Promise<void>;
125
+ declare function processInput(inputJson: string, config: Config, dryRun: boolean): Promise<void>;
126
+
127
+ declare abstract class NotesError extends ReleaseKitError {
128
+ }
129
+
130
+ declare class InputParseError extends NotesError {
131
+ readonly code = "INPUT_PARSE_ERROR";
132
+ readonly suggestions: string[];
133
+ }
134
+ declare class TemplateError extends NotesError {
135
+ readonly code = "TEMPLATE_ERROR";
136
+ readonly suggestions: string[];
137
+ }
138
+ declare class LLMError extends NotesError {
139
+ readonly code = "LLM_ERROR";
140
+ readonly suggestions: string[];
141
+ }
142
+ declare class GitHubError extends NotesError {
143
+ readonly code = "GITHUB_ERROR";
144
+ readonly suggestions: string[];
145
+ }
146
+ declare class ConfigError extends NotesError {
147
+ readonly code = "CONFIG_ERROR";
148
+ readonly suggestions: string[];
149
+ }
150
+ declare function getExitCode(error: NotesError): number;
151
+
152
+ declare function parsePackageVersioner(json: string): ChangelogInput;
153
+ declare function parsePackageVersionerFile(filePath: string): ChangelogInput;
154
+ declare function parsePackageVersionerStdin(): Promise<ChangelogInput>;
155
+
156
+ interface MonorepoOptions {
157
+ rootPath: string;
158
+ packagesPath: string;
159
+ mode: 'root' | 'packages' | 'both';
160
+ }
161
+ declare function aggregateToRoot(contexts: TemplateContext[]): TemplateContext;
162
+
163
+ declare function writeMonorepoChangelogs(contexts: TemplateContext[], options: MonorepoOptions, config: {
164
+ updateStrategy?: 'prepend' | 'regenerate';
165
+ }, dryRun: boolean): void;
166
+ declare function detectMonorepo(cwd: string): {
167
+ isMonorepo: boolean;
168
+ packagesPath: string;
169
+ };
170
+
171
+ declare function renderJson(contexts: TemplateContext[]): string;
172
+ declare function writeJson(outputPath: string, contexts: TemplateContext[], dryRun: boolean): void;
173
+
174
+ declare function renderMarkdown(contexts: TemplateContext[]): string;
175
+ declare function writeMarkdown(outputPath: string, contexts: TemplateContext[], config: Config, dryRun: boolean): void;
176
+
177
+ export { NotesError as ChangelogCreatorError, type ChangelogEntry, type ChangelogInput, type ChangelogType, type CompleteOptions, type Config, ConfigError, type DocumentContext, type EnhancedData, GitHubError, InputParseError, type InputSource, type LLMConfig, LLMError, type LLMOptions, type MonorepoConfig, type MonorepoMode, NotesError, type OutputConfig, type OutputFormat, type PackageChangelog, type RetryOptions, type TemplateConfig, type TemplateContext, type TemplateEngine, TemplateError, type UpdateStrategy, aggregateToRoot, createTemplateContext, detectMonorepo, getDefaultConfig, getExitCode, loadConfig, parsePackageVersioner, parsePackageVersionerFile, parsePackageVersionerStdin, processInput, renderJson, renderMarkdown, runPipeline, writeJson, writeMarkdown, writeMonorepoChangelogs };
@@ -0,0 +1,177 @@
1
+ import { NotesConfig } from '@releasekit/config';
2
+ export { loadAuth, saveAuth } from '@releasekit/config';
3
+ import { ReleaseKitError } from '@releasekit/core';
4
+ export { EXIT_CODES } from '@releasekit/core';
5
+
6
+ declare function loadConfig(projectDir?: string, configFile?: string): NotesConfig;
7
+ declare function getDefaultConfig(): NotesConfig;
8
+
9
+ interface RetryOptions {
10
+ maxAttempts?: number;
11
+ initialDelay?: number;
12
+ maxDelay?: number;
13
+ backoffFactor?: number;
14
+ }
15
+
16
+ type ChangelogType = 'added' | 'changed' | 'deprecated' | 'removed' | 'fixed' | 'security';
17
+ interface ChangelogEntry {
18
+ type: ChangelogType;
19
+ description: string;
20
+ issueIds?: string[];
21
+ scope?: string;
22
+ originalType?: string;
23
+ breaking?: boolean;
24
+ }
25
+ interface PackageChangelog {
26
+ packageName: string;
27
+ version: string;
28
+ previousVersion: string | null;
29
+ revisionRange: string;
30
+ repoUrl: string | null;
31
+ date: string;
32
+ entries: ChangelogEntry[];
33
+ }
34
+ type InputSource = 'package-versioner' | 'conventional-changelog' | 'git-log' | 'manual';
35
+ interface ChangelogInput {
36
+ source: InputSource;
37
+ packages: PackageChangelog[];
38
+ metadata?: {
39
+ repoUrl?: string;
40
+ defaultBranch?: string;
41
+ };
42
+ }
43
+ interface EnhancedData {
44
+ entries: ChangelogEntry[];
45
+ summary?: string;
46
+ categories?: Record<string, ChangelogEntry[]>;
47
+ releaseNotes?: string;
48
+ }
49
+ interface TemplateContext {
50
+ packageName: string;
51
+ version: string;
52
+ previousVersion: string | null;
53
+ date: string;
54
+ repoUrl: string | null;
55
+ entries: ChangelogEntry[];
56
+ compareUrl?: string;
57
+ enhanced?: EnhancedData;
58
+ }
59
+ interface DocumentContext {
60
+ project: {
61
+ name: string;
62
+ repoUrl?: string;
63
+ };
64
+ versions: TemplateContext[];
65
+ unreleased?: TemplateContext;
66
+ compareUrls?: Record<string, string>;
67
+ }
68
+ interface LLMOptions {
69
+ timeout?: number;
70
+ maxTokens?: number;
71
+ temperature?: number;
72
+ }
73
+ interface LLMConfig {
74
+ provider: string;
75
+ model: string;
76
+ baseURL?: string;
77
+ apiKey?: string;
78
+ options?: LLMOptions;
79
+ concurrency?: number;
80
+ retry?: RetryOptions;
81
+ tasks?: {
82
+ summarize?: boolean;
83
+ enhance?: boolean;
84
+ categorize?: boolean;
85
+ releaseNotes?: boolean;
86
+ };
87
+ }
88
+ type OutputFormat = 'markdown' | 'github-release' | 'json';
89
+ interface OutputConfig {
90
+ format: OutputFormat;
91
+ file?: string;
92
+ options?: Record<string, unknown>;
93
+ }
94
+ type MonorepoMode = 'root' | 'packages' | 'both';
95
+ interface MonorepoConfig {
96
+ mode?: MonorepoMode;
97
+ rootPath?: string;
98
+ packagesPath?: string;
99
+ }
100
+ type UpdateStrategy = 'prepend' | 'regenerate';
101
+ type TemplateEngine = 'handlebars' | 'liquid' | 'ejs';
102
+ interface TemplateConfig {
103
+ path?: string;
104
+ engine?: TemplateEngine;
105
+ }
106
+ interface Config {
107
+ input?: {
108
+ source?: string;
109
+ file?: string;
110
+ };
111
+ output: OutputConfig[];
112
+ monorepo?: MonorepoConfig;
113
+ templates?: TemplateConfig;
114
+ llm?: LLMConfig;
115
+ updateStrategy?: UpdateStrategy;
116
+ }
117
+ interface CompleteOptions {
118
+ maxTokens?: number;
119
+ temperature?: number;
120
+ timeout?: number;
121
+ }
122
+
123
+ declare function createTemplateContext(pkg: PackageChangelog): TemplateContext;
124
+ declare function runPipeline(input: ChangelogInput, config: Config, dryRun: boolean): Promise<void>;
125
+ declare function processInput(inputJson: string, config: Config, dryRun: boolean): Promise<void>;
126
+
127
+ declare abstract class NotesError extends ReleaseKitError {
128
+ }
129
+
130
+ declare class InputParseError extends NotesError {
131
+ readonly code = "INPUT_PARSE_ERROR";
132
+ readonly suggestions: string[];
133
+ }
134
+ declare class TemplateError extends NotesError {
135
+ readonly code = "TEMPLATE_ERROR";
136
+ readonly suggestions: string[];
137
+ }
138
+ declare class LLMError extends NotesError {
139
+ readonly code = "LLM_ERROR";
140
+ readonly suggestions: string[];
141
+ }
142
+ declare class GitHubError extends NotesError {
143
+ readonly code = "GITHUB_ERROR";
144
+ readonly suggestions: string[];
145
+ }
146
+ declare class ConfigError extends NotesError {
147
+ readonly code = "CONFIG_ERROR";
148
+ readonly suggestions: string[];
149
+ }
150
+ declare function getExitCode(error: NotesError): number;
151
+
152
+ declare function parsePackageVersioner(json: string): ChangelogInput;
153
+ declare function parsePackageVersionerFile(filePath: string): ChangelogInput;
154
+ declare function parsePackageVersionerStdin(): Promise<ChangelogInput>;
155
+
156
+ interface MonorepoOptions {
157
+ rootPath: string;
158
+ packagesPath: string;
159
+ mode: 'root' | 'packages' | 'both';
160
+ }
161
+ declare function aggregateToRoot(contexts: TemplateContext[]): TemplateContext;
162
+
163
+ declare function writeMonorepoChangelogs(contexts: TemplateContext[], options: MonorepoOptions, config: {
164
+ updateStrategy?: 'prepend' | 'regenerate';
165
+ }, dryRun: boolean): void;
166
+ declare function detectMonorepo(cwd: string): {
167
+ isMonorepo: boolean;
168
+ packagesPath: string;
169
+ };
170
+
171
+ declare function renderJson(contexts: TemplateContext[]): string;
172
+ declare function writeJson(outputPath: string, contexts: TemplateContext[], dryRun: boolean): void;
173
+
174
+ declare function renderMarkdown(contexts: TemplateContext[]): string;
175
+ declare function writeMarkdown(outputPath: string, contexts: TemplateContext[], config: Config, dryRun: boolean): void;
176
+
177
+ export { NotesError as ChangelogCreatorError, type ChangelogEntry, type ChangelogInput, type ChangelogType, type CompleteOptions, type Config, ConfigError, type DocumentContext, type EnhancedData, GitHubError, InputParseError, type InputSource, type LLMConfig, LLMError, type LLMOptions, type MonorepoConfig, type MonorepoMode, NotesError, type OutputConfig, type OutputFormat, type PackageChangelog, type RetryOptions, type TemplateConfig, type TemplateContext, type TemplateEngine, TemplateError, type UpdateStrategy, aggregateToRoot, createTemplateContext, detectMonorepo, getDefaultConfig, getExitCode, loadConfig, parsePackageVersioner, parsePackageVersionerFile, parsePackageVersionerStdin, processInput, renderJson, renderMarkdown, runPipeline, writeJson, writeMarkdown, writeMonorepoChangelogs };
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ import {
2
+ ConfigError,
3
+ EXIT_CODES,
4
+ GitHubError,
5
+ InputParseError,
6
+ LLMError,
7
+ NotesError,
8
+ TemplateError,
9
+ aggregateToRoot,
10
+ createTemplateContext,
11
+ detectMonorepo,
12
+ getDefaultConfig,
13
+ getExitCode,
14
+ loadAuth,
15
+ loadConfig,
16
+ parsePackageVersioner,
17
+ parsePackageVersionerFile,
18
+ parsePackageVersionerStdin,
19
+ processInput,
20
+ renderJson,
21
+ renderMarkdown,
22
+ runPipeline,
23
+ saveAuth,
24
+ writeJson,
25
+ writeMarkdown,
26
+ writeMonorepoChangelogs
27
+ } from "./chunk-GN5RQW3G.js";
28
+ export {
29
+ NotesError as ChangelogCreatorError,
30
+ ConfigError,
31
+ EXIT_CODES,
32
+ GitHubError,
33
+ InputParseError,
34
+ LLMError,
35
+ NotesError,
36
+ TemplateError,
37
+ aggregateToRoot,
38
+ createTemplateContext,
39
+ detectMonorepo,
40
+ getDefaultConfig,
41
+ getExitCode,
42
+ loadAuth,
43
+ loadConfig,
44
+ parsePackageVersioner,
45
+ parsePackageVersionerFile,
46
+ parsePackageVersionerStdin,
47
+ processInput,
48
+ renderJson,
49
+ renderMarkdown,
50
+ runPipeline,
51
+ saveAuth,
52
+ writeJson,
53
+ writeMarkdown,
54
+ writeMonorepoChangelogs
55
+ };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@releasekit/notes",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for generating changelogs with LLM-powered enhancement and flexible templating",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "bin": {
22
+ "releasekit-notes": "./dist/cli.js"
23
+ },
24
+ "scripts": {
25
+ "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
26
+ "dev": "tsup src/index.ts src/cli.ts --format esm,cjs --watch --dts",
27
+ "clean": "rm -rf dist coverage .turbo",
28
+ "test": "vitest run",
29
+ "test:unit": "vitest run --coverage",
30
+ "test:coverage": "vitest run --coverage",
31
+ "lint": "biome check .",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "keywords": ["changelog", "release-notes", "llm", "template", "cli"],
35
+ "author": "Sam Maister <goosewobbler@protonmail.com>",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/goosewobbler/releasekit",
40
+ "directory": "packages/notes"
41
+ },
42
+ "files": ["dist", "templates", "README.md", "LICENSE"],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "@anthropic-ai/sdk": "^0.39.0",
48
+ "@octokit/rest": "^21.1.1",
49
+ "@releasekit/config": "workspace:*",
50
+ "@releasekit/core": "workspace:*",
51
+ "chalk": "catalog:",
52
+ "commander": "catalog:",
53
+ "ejs": "^3.1.10",
54
+ "handlebars": "^4.7.8",
55
+ "liquidjs": "^10.21.0",
56
+ "openai": "^4.87.0",
57
+ "zod": "catalog:"
58
+ },
59
+ "devDependencies": {
60
+ "@biomejs/biome": "catalog:",
61
+ "@types/ejs": "^3.1.5",
62
+ "@types/node": "catalog:",
63
+ "@vitest/coverage-v8": "catalog:",
64
+ "tsup": "catalog:",
65
+ "typescript": "catalog:",
66
+ "vitest": "catalog:"
67
+ },
68
+ "engines": {
69
+ "node": ">=18 || >=20"
70
+ }
71
+ }
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ {{#each versions}}
6
+ {{> version}}
7
+ {{/each}}
@@ -0,0 +1,2 @@
1
+ - {{#if scope}}**{{scope}}**: {{/if}}{{description}}{{#if issueIds}} ({{join issueIds ", "}}){{/if}}
2
+
@@ -0,0 +1,8 @@
1
+ ## [{{version}}] - {{date}}
2
+ {{#if compareUrl}}
3
+ [Full Changelog]({{compareUrl}})
4
+ {{/if}}
5
+
6
+ {{#each entries}}
7
+ {{> entry}}
8
+ {{/each}}
@@ -0,0 +1,20 @@
1
+ ## What's Changed
2
+
3
+ <% if (versions.length > 0) { %>
4
+ <% const version = versions[0]; %>
5
+
6
+ <% if (version.enhanced?.releaseNotes) { %>
7
+ <%= version.enhanced.releaseNotes %>
8
+ <% } else { %>
9
+ <% if (version.entries.length > 0) { %>
10
+ <% version.entries.forEach(function(entry) { %>
11
+ - <% if (entry.scope) { %>**<%= entry.scope %>**: <% } %><%= entry.description %><% if (entry.issueIds) { %> (<%= entry.issueIds.join(', ') %>)<% } %>
12
+
13
+ <% }); %>
14
+ <% } %>
15
+ <% } %>
16
+
17
+ <% if (version.compareUrl) { %>
18
+ **Full Changelog**: <%= version.compareUrl %>
19
+ <% } %>
20
+ <% } %>
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ {% for version in versions %}
9
+ {% include "version" %}
10
+ {% endfor %}
@@ -0,0 +1,2 @@
1
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
2
+
@@ -0,0 +1,54 @@
1
+ ## [{{ version.version }}]{% if version.previousVersion %}{% endif %} - {{ version.date }}
2
+ {% if version.compareUrl %}
3
+ [Full Changelog]({{ version.compareUrl }})
4
+ {% endif %}
5
+
6
+ {% assign added = version.entries | where: "type", "added" %}
7
+ {% assign changed = version.entries | where: "type", "changed" %}
8
+ {% assign deprecated = version.entries | where: "type", "deprecated" %}
9
+ {% assign removed = version.entries | where: "type", "removed" %}
10
+ {% assign fixed = version.entries | where: "type", "fixed" %}
11
+ {% assign security = version.entries | where: "type", "security" %}
12
+
13
+ {% if added.size > 0 %}
14
+ ### Added
15
+ {% for entry in added %}
16
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
17
+
18
+ {% endfor %}
19
+ {% endif %}
20
+ {% if changed.size > 0 %}
21
+ ### Changed
22
+ {% for entry in changed %}
23
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
24
+
25
+ {% endfor %}
26
+ {% endif %}
27
+ {% if deprecated.size > 0 %}
28
+ ### Deprecated
29
+ {% for entry in deprecated %}
30
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
31
+
32
+ {% endfor %}
33
+ {% endif %}
34
+ {% if removed.size > 0 %}
35
+ ### Removed
36
+ {% for entry in removed %}
37
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
38
+
39
+ {% endfor %}
40
+ {% endif %}
41
+ {% if fixed.size > 0 %}
42
+ ### Fixed
43
+ {% for entry in fixed %}
44
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
45
+
46
+ {% endfor %}
47
+ {% endif %}
48
+ {% if security.size > 0 %}
49
+ ### Security
50
+ {% for entry in security %}
51
+ - {% if entry.scope %}**{{ entry.scope }}**: {% endif %}{{ entry.description }}{% if entry.issueIds %} ({{ entry.issueIds | join: ", " }}){% endif %}
52
+
53
+ {% endfor %}
54
+ {% endif %}