Skip to content

Commit

Permalink
Merge branch 'main' into shadows-updated-0806
Browse files Browse the repository at this point in the history
  • Loading branch information
KenAJoh authored Jun 22, 2023
2 parents 672feca + 6cb6c52 commit a8bb170
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:
node-version: 16.13.0
cache: yarn

- uses: denoland/setup-deno@v1
with:
deno-version: v1.x

- name: Restore cache
uses: actions/cache@v3
with:
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"storybook:aksel": "concurrently \"yarn watch:tw\" \"cross-env STORYBOOK_STORIES=all storybook dev -p 6006\"",
"dev": "yarn workspace aksel.nav.no dev",
"docgen": "yarn workspaces foreach -p run docgen",
"changelog": "deno run --allow-read --allow-write --no-config scripts/deno/createMainChangelog.ts",
"chromatic": "npx chromatic --project-token x3xqdfgkujg --build-script-name build:storybook",
"build:storybook": "storybook build",
"test": "yarn workspaces foreach -p run test",
"lint": "yarn eslint . && yarn stylelint @navikt/**/*.css",
"lint:css": "yarn stylelint @navikt/**/*.css",
"changeset": "changeset",
"version": "changeset version",
"version": "changeset version && yarn changelog",
"release": "yarn boot && yarn docgen && changeset publish"
},
"workspaces": [
Expand Down
233 changes: 233 additions & 0 deletions scripts/deno/createMainChangelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { readFileSync, writeFileSync } from "node:fs";
import {
List,
ListItem,
Root,
Text,
Link,
Paragraph,
PhrasingContent,
} from "npm:@types/mdast";
import { Node } from "npm:@types/unist";
import { heading, root, text } from "npm:mdast-builder";
import remarkParse from "npm:remark-parse";
import remarkStringify from "npm:remark-stringify";
import { unified } from "npm:unified";
import { EXIT, SKIP, CONTINUE, visit } from "npm:unist-util-visit";
import { visitParents } from "npm:unist-util-visit-parents";
import { getChangelogs } from "./utils.ts";

/**
* Small diagram of the process:
original
markdown files
|
V
custom JSON
representation
of changelog
|
V
final markdown
*/

type PackageName = string;
type Version = string;
type VersionEntry = Record<PackageName, Node[]>;
type Changelog = Record<Version, VersionEntry>;

const upsertEntry = (
changelog: Changelog,
{
lastSeenPackage,
lastSeenVersion,
}: {
lastSeenPackage: string;
lastSeenVersion: string;
},
value: Node
) => {
changelog[lastSeenVersion] ??= {};

const raw_ast_nodes = changelog[lastSeenVersion][lastSeenPackage];

changelog[lastSeenVersion][lastSeenPackage] = raw_ast_nodes
? [...raw_ast_nodes, value]
: [value];

const updatedVersion = changelog[lastSeenVersion] || [];
changelog[lastSeenVersion] = updatedVersion;
};

const PROrCommitHashLink = (node: Root): Link | null => {
let infoLink: Link | null = null;
visit(node, (someChildNode) => {
if (someChildNode.type === "link") {
infoLink = structuredClone(someChildNode);
return EXIT;
}
});

return infoLink;
};

const processNode = (node: Root) => {
const infoLink = PROrCommitHashLink(node) || text("");
visitParents(node, (childNode, ancestors) => {
if (childNode.type === "text" && childNode.value.startsWith("! -")) {
const parent = ancestors.findLast(
(ancestor) => ancestor.type === "paragraph"
) as Paragraph;
visit(parent, (node, index, parent) => {
if (
node.type === "link" ||
(node.type === "text" && node.value === " Thanks ")
) {
if (parent && index !== null) {
parent.children.splice(index, 1);
return [CONTINUE, index];
}
}
if (node.type === "text" && node.value.startsWith("! -")) {
node.value = node.value.replace(/! (- )+/, "");
return;
}
});
parent.children.push(text(" ") as PhrasingContent);
parent.children.push(infoLink as PhrasingContent);
}
});
};

const parseMarkdownFiles = async (filePaths: string[]): Promise<Changelog> => {
const changelog: Changelog = {};

for (const filePath of filePaths) {
const fileContent = readFileSync(filePath, { encoding: "utf-8" });
const fileAST = await unified().use(remarkParse).parse(fileContent);

///////////////////
// filtering passes
///////////////////

// filter out all the 'Updated dependencies' nodes (at their relevant parent)
visitParents(fileAST, "paragraph", (node, ancestors) => {
if (
node.children[0].type === "text" &&
node.children[0].value.startsWith("Updated dependencies")
) {
const listIndex = ancestors.findLastIndex(
(ancestor) => ancestor.type === "list"
);
const parent = ancestors[listIndex] as List;

// we traversed up from child match to parent
// so we need to traverse down from parent to child
// again to find index of child within parent... yeah 😂
const indexWithinList = parent.children.findIndex((child: ListItem) => {
return child.children.some((grandchild) => {
let found = false;
visit(grandchild, (node) => {
if (
node.type === "text" &&
node.value.startsWith("Updated dependencies")
) {
found = true;
return EXIT;
}
});
return found;
});
});
if (parent && indexWithinList !== -1) {
parent.children.splice(indexWithinList, 1);
return [SKIP, indexWithinList];
}
}
});

// filter all empty list nodes
visit(fileAST, (node, index, parent) => {
if (node.type === "list" && node.children.length === 0) {
if (parent && index !== null) {
parent.children.splice(index, 1);
return [SKIP, index];
}
}
});

///////////////////
// upsert into custom JS object
///////////////////

let lastSeenPackage = "";
let lastSeenVersion = "";

visit(fileAST, (node) => {
if (node.type === "root") {
return;
}
if (node.type === "heading" && node.depth === 1) {
const childNode = node.children[0] as Text | undefined;
if (childNode && childNode.type === "text") {
const packageName = childNode.value;
if (packageName.startsWith("@navikt/")) {
lastSeenPackage = packageName;
}
}
} else if (node.type === "heading" && node.depth === 2) {
const childNode = node.children[0] as Text | undefined;
if (childNode && childNode.type === "text") {
const version = childNode.value;
if (version.match(/^\d+\.\d+\.\d+$/)) {
lastSeenVersion = version;
}
}
} else if (node.type === "heading" && node.depth === 3) {
const childNode = node.children[0] as Text | undefined;
if (childNode && childNode.type === "text") {
// ignore semver heading ('Major', 'Minor', 'Patch')
}
} else {
upsertEntry(
changelog,
{ lastSeenPackage, lastSeenVersion },
structuredClone(node)
);
}
return SKIP;
});
}
return changelog;
};

const createMainChangelog = async (changelog: Changelog): Promise<string> => {
const headings = [];
Object.entries(changelog).forEach(([version, versionEntry]) => {
headings.push(heading(2, [text(version)]));
for (const [packageName, changes] of Object.entries(versionEntry)) {
headings.push(heading(3, [text(packageName)]));
for (const change of changes) {
processNode(change as Root);
headings.push(change);
}
}
});

headings.unshift(heading(1, [text("Changelog")]));

const changelog_node_tree = root(headings) as Root;
const processed = await unified()
.use(remarkStringify, { bullet: "-" })
.stringify(changelog_node_tree);

return processed;
};

const changelogFiles = getChangelogs("./@navikt");
console.log("processing the following markdown files:", changelogFiles);
const changelogJSON = await parseMarkdownFiles(changelogFiles);
const changelog = await createMainChangelog(changelogJSON);
writeFileSync("CHANGELOG.md", changelog);
console.log("wrote to CHANGELOG.md");
23 changes: 23 additions & 0 deletions scripts/deno/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { readdirSync, statSync } from "node:fs";

export const getChangelogs = (path: string) => {
const changelogs: string[] = [];
const walkFiles = (dirPath: string) => {
const files = readdirSync(dirPath);
files.forEach((file) => {
const filePath = `${dirPath}/${file}`;
if (
statSync(filePath).isDirectory() &&
!file.startsWith("node_modules")
) {
walkFiles(filePath);
} else {
if (file.match(/^CHANGELOG\.md$/)) {
changelogs.push(filePath);
}
}
});
};
walkFiles(path);
return changelogs;
};

0 comments on commit a8bb170

Please sign in to comment.