Skip to content

Commit

Permalink
Fix failing YAMLs (#14)
Browse files Browse the repository at this point in the history
* fix failing on invalid yaml

* chore: remove unused field
  • Loading branch information
shvgn authored Feb 8, 2022
1 parent 57f38dd commit 00fb9b8
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 372 deletions.
6 changes: 3 additions & 3 deletions src/changes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { formatYaml, formatMarkdown } from "./format"
import { PullRequest, collectChangelog } from "./parse"
import { formatMarkdown, formatYaml } from "./format"
import { collectChangelog, PullRequest } from "./parse"

export interface Inputs {
token: string
Expand All @@ -20,7 +20,7 @@ export async function collectChanges(inputs: Inputs): Promise<Outputs> {
return out
}

// Process
// We assume all PRs have the same milestone
const milestone = pulls[0].milestone.title
const changes = collectChangelog(pulls)

Expand Down
149 changes: 110 additions & 39 deletions src/format.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,80 @@
import * as yaml from "js-yaml"
import { Change, ModuleChanges, ChangesByModule } from "./parse"
import json2md, { DataObject } from "json2md"

const MARKDOWN_HEADER_TAG = "h2"
const MARKDOWN_MODULER_TAG = "h4"
const MARKDOWN_NOTE_PREFIX = "**NOTE!**"
import { Change, ChangeEntry, ChangesByModule, ModuleChanges } from "./parse"

/**
* @function formatYaml returns changes formatted in YAML
* @function formatYaml returns changes formatted in YAML with grouping by module, type, and omiiting invalid entries
* @param changes by module
* @returns
*/
export function formatYaml(body: ChangesByModule): string {
export function formatYaml(changes: ChangeEntry[]): string {
const opts = {
sortKeys: unknownFirst,
sortKeys: true,
lineWidth: 100,
forceQuotes: false,
quotingType: "'",
} as yaml.DumpOptions

// create the map from only valid entries: module -> fix/feature -> change[]
const body = changes
.filter((c) => c.valid()) //
.reduce(groupByModuleAndType, {})

return yaml.dump(body, opts)
}

function unknownFirst(a: string, b: string): number {
if (isUnknown(a) || a < b) return -1
if (isUnknown(b) || a > b) return 1
return 0
}
function groupByModuleAndType(acc: ChangesByModule, change: ChangeEntry) {
// ensure module key: { "module": {} }
acc[change.module] = acc[change.module] || ({} as ModuleChanges)
const mc = acc[change.module]
const getTypeList = (k: string) => {
mc[k] = mc[k] || []
return mc[k]
}

// ensure module change list
// e.g. for fixes: { "module": { "fixes": [] } }
let list
switch (change.type) {
case "fix":
list = getTypeList("fixes")
break
case "feature":
list = getTypeList("features")
break
default:
throw new Error("invalid type")
}

function isUnknown(s: string): boolean {
return typeof s === "string" && s.toLowerCase() === "unknown"
// add the change
list.push(
new Change({
description: change.description,
pull_request: change.pull_request,
note: change.note,
}),
)

return acc
}

const MARKDOWN_HEADER_TAG = "h2"
const MARKDOWN_MODULE_TAG = "h4"
const MARKDOWN_NOTE_PREFIX = "**NOTE!**"

/**
* @function formatMarkdown returns changes formatted in markdown
* @param changes by module
* @returns
*/
export function formatMarkdown(milestone: string, body: ChangesByModule): string {
const pairs = Object.entries(body).sort((a, b) => unknownFirst(a[0], b[0]))
export function formatMarkdown(milestone: string, changes: ChangeEntry[]): string {
const body: DataObject[] = [
{ [MARKDOWN_HEADER_TAG]: `Changelog ${milestone}` }, // title
...formatMalformedEntries(changes),
...formatEntriesByModuleAndType(changes),
]

const content: DataObject = [{ [MARKDOWN_HEADER_TAG]: `Changelog ${milestone}` }]
for (const [modName, changes] of pairs) {
content.push({ [MARKDOWN_MODULER_TAG]: `[${modName}]` })
content.push({ ul: moduleChangesMarkdown(changes) })
}

const md = json2md(content)
const md = json2md(body)

// Workaround to omit excessive empty lines
// https://github.com/IonicaBizau/json2md/issues/53
Expand All @@ -60,35 +88,78 @@ function fixLineBreaks(md: string): string {
.filter((s) => s.trim() != "")
// wrap subheaders with empty lines
.map((s) => (s.startsWith("###") ? `\n${s}\n` : s))
.map((s) => (s.startsWith("**") && s.endsWith("**") ? `\n${s}\n` : s))
.join("\n")

// add empty line to the end
return fixed + "\n"
}

function moduleChangesMarkdown(mc: ModuleChanges) {
const md: DataObject = []
if (mc.unknown) {
md.push("unknown")
md.push({ ul: mc.unknown.flatMap(changeMardown) })
function formatEntriesByModuleAndType(changes: ChangeEntry[]): DataObject[] {
const body: DataObject[] = []

const validEntries = changes
.filter((c) => c.valid()) //
.reduce(groupByModuleAndType, {})

// Collect valid change entries; sort by module name
const pairs = Object.entries(validEntries).sort((a, b) => (a[0] < b[0] ? -1 : 1))
for (const [modName, changes] of pairs) {
body.push({ [MARKDOWN_MODULE_TAG]: modName })
body.push(...moduleChangesMarkdown(changes))
}
if (mc.features) {
md.push("features")
md.push({ ul: mc.features.flatMap(changeMardown) })

return body
}

function formatMalformedEntries(changes: ChangeEntry[]): DataObject[] {
const body: DataObject[] = []

// Collect malformed on the top for easier fixing
const invalidEntries = changes
.filter((c) => !c.valid())
.sort((a, b) => (a.pull_request < b.pull_request ? -1 : 1))

if (invalidEntries.length > 0) {
body.push([{ [MARKDOWN_MODULE_TAG]: "[MALFORMED]" }])

const ul: string[] = []
for (const c of invalidEntries) {
const prNum = parsePullRequestNumberFromURL(c.pull_request)
ul.push(`[#${prNum}](${c.pull_request})`)
}
body.push({ ul: ul.sort() })
}
if (mc.fixes) {
md.push("fixes")
md.push({ ul: mc.fixes.flatMap(changeMardown) })

return body
}

function moduleChangesMarkdown(moduleChanges: ModuleChanges): DataObject[] {
const md: DataObject[] = []
if (moduleChanges.features) {
md.push({ p: "**features**" })
md.push({ ul: moduleChanges.features.flatMap(changeMardown) })
}
if (moduleChanges.fixes) {
md.push({ p: "**fixes**" })
md.push({ ul: moduleChanges.fixes.flatMap(changeMardown) })
}
// console.log("mc", JSON.stringify(md, null, 2))
return md
}

function changeMardown(c: Change): DataObject {
const detail: unknown[] = [{ link: { source: c.pull_request, title: "Pull request" } }]
function parsePullRequestNumberFromURL(prUrl: string): string {
const parts = prUrl.split("/")
return parts[parts.length - 1]
}

function changeMardown(c: Change): string {
const pr = parsePullRequestNumberFromURL(c.pull_request)
const lines = [`${c.description} [#${pr}](${pr})`]

if (c.note) {
detail.push(`${MARKDOWN_NOTE_PREFIX} ${c.note}`)
lines.push(`${MARKDOWN_NOTE_PREFIX} ${c.note}`)
}

return [c.description, { ul: detail }]
return lines.join("\n")
}
Loading

0 comments on commit 00fb9b8

Please sign in to comment.