Repository navigation
Expand file tree
/
Copy pathconfig.go
More file actions
498 lines (454 loc) · 17 KB
/
Copy pathconfig.go
File metadata and controls
498 lines (454 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
package core
import (
"fmt"
"os"
"github.com/Masterminds/semver/v3"
"gopkg.in/yaml.v2"
"github.com/miniscruff/changie/shared"
)
const (
configEnvVar = "CHANGIE_CONFIG_PATH"
timeFormat string = "20060102-150405"
CreateFileMode os.FileMode = 0644
CreateDirMode os.FileMode = 0755
AutoLevel = "auto"
MajorLevel = "major"
MinorLevel = "minor"
PatchLevel = "patch"
NoneLevel = "none"
EmptyLevel = ""
)
var ConfigPaths []string = []string{
".changie.yaml",
".changie.yml",
}
// GetVersions will return, in semver sorted order, all released versions
type GetVersions func(shared.ReadDirer, Config) ([]*semver.Version, error)
// Kind config allows you to customize the options depending on what kind was selected.
type KindConfig struct {
// Label is the value used in the prompt when selecting a kind.
// example: yaml
// label: Feature
Label string `yaml:",omitempty" required:"true"`
// Format will override the root kind format when building the kind header.
// example: yaml
// format: '### {{.Kind}} **Breaking Changes**'
Format string `yaml:",omitempty"`
// Change format will override the root change format when building changes specific to this kind.
// example: yaml
// changeFormat: 'Breaking: {{.Custom.Body}}
ChangeFormat string `yaml:"changeFormat,omitempty"`
// Additional choices allows adding choices per kind
AdditionalChoices []Custom `yaml:"additionalChoices,omitempty"`
// Post process options when saving a new change fragment specific to this kind.
Post []PostProcessConfig `yaml:"post,omitempty"`
// Skip global choices allows skipping the parent choices options.
SkipGlobalChoices bool `yaml:"skipGlobalChoices,omitempty" default:"false"`
// Skip body allows skipping the parent body prompt.
SkipBody bool `yaml:"skipBody,omitempty" default:"false"`
// Skip global post allows skipping the parent post processing.
SkipGlobalPost bool `yaml:"skipGlobalPost,omitempty" default:"false"`
// Auto determines what value to bump when using `batch auto` or `next auto`.
// Possible values are major, minor, patch or none and the highest one is used if
// multiple changes are found. none will not bump the version.
// Only none changes is not a valid bump and will fail to batch.
// example: yaml
// auto: minor
AutoLevel string `yaml:"auto,omitempty"`
}
// Body config allows you to customize the default body prompt
type BodyConfig struct {
// Min length specifies the minimum body length
MinLength *int64 `yaml:"minLength,omitempty" default:"no min"`
// Max length specifies the maximum body length
MaxLength *int64 `yaml:"maxLength,omitempty" default:"no max"`
// Block allows multiline text inputs for body messages
UseBlock bool `yaml:"block,omitempty" default:"false"`
}
func (b BodyConfig) CreateCustom() *Custom {
customType := CustomString
if b.UseBlock {
customType = CustomBlock
}
return &Custom{
Label: "Body",
Type: customType,
MinLength: b.MinLength,
MaxLength: b.MaxLength,
}
}
func (b BodyConfig) Validate(input string) error {
c := Custom{
Label: "Body",
Type: CustomString,
MinLength: b.MinLength,
MaxLength: b.MaxLength,
}
return c.Validate(input)
}
// Configuration options for newlines before and after different elements.
type NewlinesConfig struct {
// Add newlines after change fragment
AfterChange int `yaml:"afterChange,omitempty" default:"0"`
// Add newlines after the header file in the merged changelog
AfterChangelogHeader int `yaml:"afterChangelogHeader,omitempty" default:"0"`
// Add newlines after adding a version to the changelog
AfterChangelogVersion int `yaml:"afterChangelogVersion,omitempty" default:"0"`
// Add newlines after component
AfterComponent int `yaml:"afterComponent,omitempty" default:"0"`
// Add newlines after footer file
AfterFooterFile int `yaml:"afterFooterFile,omitempty" default:"0"`
// Add newlines after footer template
AfterFooterTemplate int `yaml:"afterFooter,omitempty" default:"0"`
// Add newlines after header file
AfterHeaderFile int `yaml:"afterHeaderFile,omitempty" default:"0"`
// Add newlines after header template
AfterHeaderTemplate int `yaml:"afterHeaderTemplate,omitempty" default:"0"`
// Add newlines after kind
AfterKind int `yaml:"afterKind,omitempty" default:"0"`
// Add newlines after version
AfterVersion int `yaml:"afterVersion,omitempty" default:"0"`
// Add newlines before change fragment
BeforeChange int `yaml:"beforeChange,omitempty" default:"0"`
// Add newlines before adding a version to the changelog
BeforeChangelogVersion int `yaml:"beforeChangelogVersion,omitempty" default:"0"`
// Add newlines before component
BeforeComponent int `yaml:"beforeComponent,omitempty" default:"0"`
// Add newlines before footer file
BeforeFooterFile int `yaml:"beforeFooterFile,omitempty" default:"0"`
// Add newlines before footer template
BeforeFooterTemplate int `yaml:"beforeFooterTemplate,omitempty" default:"0"`
// Add newlines before header file
BeforeHeaderFile int `yaml:"beforeHeaderFile,omitempty" default:"0"`
// Add newlines before header template
BeforeHeaderTemplate int `yaml:"beforeHeaderTemplate,omitempty" default:"0"`
// Add newlines before kind
BeforeKind int `yaml:"beforeKind,omitempty" default:"0"`
// Add newlines before version
BeforeVersion int `yaml:"beforeVersion,omitempty" default:"0"`
// Add newlines at the end of the version file
EndOfVersion int `yaml:"endOfVersion,omitempty" default:"0"`
}
// PostProcessConfig allows adding additional custom values to a change fragment
// after all the other inputs are complete.
// This will add additional keys to the `custom` section of the fragment.
// If the key already exists as part of a custom choice the value will be overridden.
type PostProcessConfig struct {
// Key to save the custom value with
Key string `yaml:"key"`
// Value of the custom value as a go template
Value string `yaml:"value" templateType:"Change"`
}
// ProjectConfig extends changie to support multiple changelog files for different projects
// inside one repository.
// example: yaml
// projects:
// - label: UI
// key: ui
// changelog: ui/CHANGELOG.md
// - label: Email Sender
// key: email_sender
// changelog: services/email/CHANGELOG.md
type ProjectConfig struct {
// Label is the value used in the prompt when selecting a project.
// example: yaml
// label: Frontend
Label string `yaml:"label"`
// Key is the value used for unreleased and version output paths.
// example: yaml
// key: frontend
Key string `yaml:"key"`
// ChangelogPath is the path to the changelog for this project.
// example: yaml
// changelog: src/frontend/CHANGELOG.md
ChangelogPath string `yaml:"changelog"`
// Replacements to run when merging a changelog for our project.
// example: yaml
// # nodejs package.json replacement
// replacements:
// - path: ui/package.json
// find: ' "version": ".*",'
// replace: ' "version": "{{.VersionNoPrefix}}",'
Replacements []Replacement `yaml:"replacements"`
}
// Config handles configuration for a project.
//
// Custom configuration path:
//
// By default Changie will try and load ".changie.yaml" and ".changie.yml" before running commands.
// If you want to change this path set the environment variable `CHANGIE_CONFIG_PATH` to the desired file.
//
// `export CHANGIE_CONFIG_PATH=./tools/changie.yaml`
//
// [Template Cache](#templatecache-type) handles all the template executions.
//
// When batching changes into a version, the headers and footers are placed as such:
//
// 1. Header file
// 1. Header template
// 1. All changes
// 1. Footer template
// 1. Footer file
//
// All elements are optional and will be added together if all are provided.
type Config struct {
// Directory for change files, header file and unreleased files.
// Relative to project root.
// example: yaml
// changesDir: .changes
ChangesDir string `yaml:"changesDir" required:"true"`
// Directory for all unreleased change files.
// Relative to [changesDir](#config-changesdir).
// example: yaml
// unreleasedDir: unreleased
UnreleasedDir string `yaml:"unreleasedDir" required:"true"`
// Header content included at the top of the merged changelog.
// A default header file is created when initializing that follows "Keep a Changelog".
//
// Filepath for your changelog header file.
// Relative to [changesDir](#config-changesdir).
// example: yaml
// headerPath: header.tpl.md
HeaderPath string `yaml:"headerPath"`
// Filepath for the generated changelog file.
// Relative to project root.
// ChangelogPath is not required if you are using projects.
// example: yaml
// changelogPath: CHANGELOG.md
ChangelogPath string `yaml:"changelogPath"`
// File extension for generated version files.
// This should probably match your changelog path file.
// Must not include the period.
// example: yaml
// # for markdown changelogs
// versionExt: md
VersionExt string `yaml:"versionExt" required:"true"`
// Filepath for your version header file relative to [unreleasedDir](#config-unreleaseddir).
// It is also possible to use the '--header-path' parameter when using the [batch command](/cli/changie_batch).
VersionHeaderPath string `yaml:"versionHeaderPath,omitempty"`
// Filepath for your version footer file relative to [unreleasedDir](#config-unreleaseddir).
// It is also possible to use the '--footer-path' parameter when using the [batch command](/cli/changie_batch).
VersionFooterPath string `yaml:"versionFooterPath,omitempty"`
// Customize the file name generated for new fragments.
// The default uses the component and kind only if configured for your project.
// The file is placed in the unreleased directory, so the full path is:
//
// "{{.ChangesDir}}/{{.UnreleasedDir}}/{{.FragmentFileFormat}}.yaml"
// example: yaml
// fragmentFileFormat: "{{.Kind}}-{{.Custom.Issue}}"
FragmentFileFormat string `yaml:"fragmentFileFormat,omitempty" default:"{{.Project}}-{{.Component}}-{{.Kind}}-{{.Time.Format \"20060102-150405\"}}" templateType:"Change"`
// Template used to generate version headers.
VersionFormat string `yaml:"versionFormat,omitempty" templateType:"BatchData"`
// Template used to generate component headers.
// If format is empty no header will be included.
// If components are disabled, the format is unused.
ComponentFormat string `yaml:"componentFormat,omitempty" templateType:"ComponentData"`
// Template used to generate kind headers.
// If format is empty no header will be included.
// If kinds are disabled, the format is unused.
KindFormat string `yaml:"kindFormat,omitempty" templateType:"KindData"`
// Template used to generate change lines in version files and changelog.
// Custom values are created through custom choices and can be accessible through the Custom argument.
// example: yaml
// changeFormat: '* [#{{.Custom.Issue}}](https://github.com/miniscruff/changie/issues/{{.Custom.Issue}}) {{.Body}}'
ChangeFormat string `yaml:"changeFormat" templateType:"Change"`
// Template used to generate a version header.
HeaderFormat string `yaml:"headerFormat,omitempty" templateType:"BatchData"`
// Template used to generate a version footer.
// example: yaml
// # config yaml
// custom:
// - key: Author
// type: string
// minLength: 3
// footerFormat: |
// ### Contributors
// {{- range (customs .Changes "Author" | uniq) }}
// * [{{.}}](https://github.com/{{.}})
// {{- end}}
FooterFormat string `yaml:"footerFormat,omitempty" templateType:"BatchData"`
// Options to customize the body prompt
Body BodyConfig `yaml:"body,omitempty"`
// Components are an additional layer of organization suited for projects that want to split
// change fragments by an area or tag of the project.
// An example could be splitting your changelogs by packages for a monorepo.
// If no components are listed then the component prompt will be skipped and no component header included.
// By default no components are configured.
// example: yaml
// components:
// - API
// - CLI
// - Frontend
Components []string `yaml:"components,omitempty"`
// Kinds are another optional layer of changelogs suited for specifying what type of change we are
// making.
// If configured, developers will be prompted to select a kind.
//
// The default list comes from keep a changelog and includes; added, changed, removed, deprecated, fixed, and security.
// example: yaml
// kinds:
// - label: Added
// - label: Changed
// - label: Deprecated
// - label: Removed
// - label: Fixed
// - label: Security
Kinds []KindConfig `yaml:"kinds,omitempty"`
// Custom choices allow you to ask for additional information when creating a new change fragment.
// These custom choices are included in the [change custom](#change-custom) value.
// example: yaml
// # github issue and author name
// custom:
// - key: Issue
// type: int
// minInt: 1
// - key: Author
// label: GitHub Name
// type: string
// minLength: 3
CustomChoices []Custom `yaml:"custom,omitempty"`
// Replacements to run when merging a changelog.
// example: yaml
// # nodejs package.json replacement
// replacements:
// - path: package.json
// find: ' "version": ".*",'
// replace: ' "version": "{{.VersionNoPrefix}}",'
Replacements []Replacement `yaml:"replacements,omitempty"`
// Newline options allow you to add extra lines between elements written by changie.
Newlines NewlinesConfig `yaml:"newlines,omitempty"`
// Post process options when saving a new change fragment.
// example: yaml
// # build a GitHub link from author choice
// post:
// - key: AuthorLink
// value: "https://github.com/{{.Custom.Author}}
// changeFormat: "* {{.Body}} by [{{.Custom.Author}}]({{.Custom.AuthorLink}})"
Post []PostProcessConfig `yaml:"post,omitempty"`
// Prefix of environment variables to load for templates.
// The prefix is removed from resulting key map.
// example: yaml
// # if we have an environment variable like so:
// # export CHANGIE_PROJECT=changie
// # we can use that in our templates if we set the prefix
// envPrefix: "CHANGIE_"
// versionFormat: "New release for {{.Env.PROJECT}}"
EnvPrefix string `yaml:"envPrefix,omitempty"`
// Projects allow you to specify child projects as part of a monorepo setup.
// example: yaml
// projects:
// - label: UI
// key: ui
// changelog: ui/CHANGELOG.md
// - label: Email Sender
// key: email_sender
// changelog: services/email/CHANGELOG.md
Projects []ProjectConfig `yaml:"projects,omitempty"`
// ProjectsVersionSeparator is used to determine the final version when using projects.
// The result is: project key + projectVersionSeparator + latest/next version.
// example: yaml
// projectsVersionSeparator: "_"
ProjectsVersionSeparator string `yaml:"projectsVersionSeparator,omitempty"`
cachedEnvVars map[string]string
}
func (c *Config) KindHeader(label string) string {
for _, kindConfig := range c.Kinds {
if kindConfig.Format != "" && kindConfig.Label == label {
return kindConfig.Format
}
}
return c.KindFormat
}
func (c *Config) ChangeFormatForKind(label string) string {
for _, kindConfig := range c.Kinds {
if kindConfig.ChangeFormat != "" && kindConfig.Label == label {
return kindConfig.ChangeFormat
}
}
return c.ChangeFormat
}
func (c *Config) EnvVars() map[string]string {
if c.cachedEnvVars == nil {
c.cachedEnvVars = LoadEnvVars(c, os.Environ())
}
return c.cachedEnvVars
}
// Save will save the config as a yaml file to the default path
func (c *Config) Save(wf shared.WriteFiler) error {
bs, _ := yaml.Marshal(&c)
return wf(ConfigPaths[0], bs, CreateFileMode)
}
func (c *Config) Project(labelOrKey string) (*ProjectConfig, error) {
if len(c.Projects) == 0 {
return &ProjectConfig{}, nil
}
if len(labelOrKey) == 0 {
return nil, errProjectRequired
}
for _, pc := range c.Projects {
if labelOrKey == pc.Label || labelOrKey == pc.Key {
return &pc, nil
}
}
return nil, errProjectNotFound
}
func (c *Config) ProjectLabels() []string {
projectLabels := make([]string, len(c.Projects))
for i, pc := range c.Projects {
if len(pc.Label) > 0 {
projectLabels[i] = pc.Label
} else {
projectLabels[i] = pc.Key
}
}
return projectLabels
}
// Exists returns whether or not a config already exists
func (c *Config) Exists() (bool, error) {
for _, p := range ConfigPaths {
if exists, err := FileExists(p); exists || err != nil {
return exists, err
}
}
return false, nil
}
// LoadConfig will load the config from the default path
func LoadConfig(rf shared.ReadFiler) (*Config, error) {
var (
c Config
bs []byte
err error
)
customPath := os.Getenv(configEnvVar)
if customPath != "" {
bs, err = rf(customPath)
} else {
for _, path := range ConfigPaths {
bs, err = rf(path)
if err == nil {
break
}
}
}
if err != nil {
return nil, err
}
err = yaml.Unmarshal(bs, &c)
if err != nil {
return nil, err
}
// load backward incompatible configs
if c.FragmentFileFormat == "" {
if len(c.Projects) > 0 {
c.FragmentFileFormat += "{{.Project}}-"
}
if len(c.Components) > 0 {
c.FragmentFileFormat += "{{.Component}}-"
}
if len(c.Kinds) > 0 {
c.FragmentFileFormat += "{{.Kind}}-"
}
c.FragmentFileFormat += fmt.Sprintf("{{.Time.Format \"%v\"}}", timeFormat)
}
return &c, nil
}