Skip to content

Commit e952cb4

Browse files
authored
Merge pull request #59 from hapijs/feat/atom
feat: add changelog Atom support
2 parents 77888bd + ce47517 commit e952cb4

27 files changed

Lines changed: 1472 additions & 478 deletions

.vitepress/theme/main.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,23 @@
6060
width: 12px;
6161
height: 12px;
6262
}
63+
64+
.atom-link {
65+
display: inline-flex;
66+
align-items: center;
67+
vertical-align: middle;
68+
color: var(--vp-c-text-2);
69+
transition: color 0.2s;
70+
text-decoration: none !important;
71+
margin-left: 12px;
72+
}
73+
74+
.atom-link:hover {
75+
color: var(--vp-c-orange) !important;
76+
}
77+
78+
.atom-icon {
79+
width: 24px;
80+
height: 24px;
81+
fill: currentColor;
82+
}

.vitepress/theme/variables.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
--cm-selection-bg-dark: #3e4451;
1717
--cm-selection-bg-light: #d7d4f0;
1818

19+
/* Atom Icon */
20+
--vp-c-orange: #f26522;
21+
1922
/* Modal & Overlays */
2023
--overlay-bg: rgba(0, 0, 0, 0.5);
2124
--modal-shadow: rgba(0, 0, 0, 0.3);

cli/generateChangelog.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import fs from 'node:fs/promises';
22
import path from 'node:path';
33

44
import { Semaphore } from 'es-toolkit';
5+
import { Feed } from 'feed';
56
import Semver from 'semver';
67

78
import { getMilestoneIssues, getMilestones } from './gh.js';
8-
import { getExisting, getModuleChangelogPath, getModuleMarkdownChangelogPath } from './paths.js';
9+
import { modules } from './modules.js';
10+
import { getExisting, getModuleChangelogPath, getModuleMarkdownChangelogPath, PUBLIC_ATOM_DIR } from './paths.js';
911

1012
import type { ChangelogItem } from './types.js';
1113

@@ -65,6 +67,54 @@ export const generateChangelog = async (moduleName: string) => {
6567
}
6668

6769
await generateModuleMarkdownChangelog(moduleName, sortedChangelog);
70+
await generateModuleAtom(moduleName, sortedChangelog);
71+
};
72+
73+
const generateModuleAtom = async (moduleName: string, sortedChangelog: ChangelogItem[]) => {
74+
const isJoi = moduleName === 'joi';
75+
const spec = modules[moduleName];
76+
const fullModuleName = spec?.package ?? moduleName;
77+
const baseLink = isJoi ? 'https://joi.dev' : `https://joi.dev/module/${moduleName}`;
78+
const changelogLink = isJoi
79+
? 'https://joi.dev/resources/changelog'
80+
: `https://joi.dev/module/${moduleName}/changelog`;
81+
82+
const feed = new Feed({
83+
favicon: 'https://joi.dev/favicon.png',
84+
id: baseLink,
85+
image: 'https://joi.dev/img/logo.png',
86+
language: 'en',
87+
link: baseLink,
88+
title: `${fullModuleName} changelog`,
89+
updated: new Date(sortedChangelog[0]?.date ?? Date.now()),
90+
});
91+
92+
const items = sortedChangelog.slice(0, 50);
93+
94+
for (const item of items) {
95+
const title = `${fullModuleName} v${item.version}`;
96+
const link = `${changelogLink}#${item.version}`;
97+
const date = new Date(item.date);
98+
99+
let content = '<ul>';
100+
for (const issue of item.issues) {
101+
content += `<li><a href="${issue.url}">[#${issue.number}]</a> ${escapeHtml(issue.title)}</li>`;
102+
}
103+
content += '</ul>';
104+
105+
feed.addItem({
106+
content,
107+
date,
108+
description: title,
109+
id: item.url,
110+
link,
111+
title,
112+
});
113+
}
114+
115+
const atomPath = path.join(PUBLIC_ATOM_DIR, `${moduleName}.atom`);
116+
await fs.mkdir(path.dirname(atomPath), { recursive: true });
117+
await fs.writeFile(atomPath, feed.atom1());
68118
};
69119

70120
const generateModuleMarkdownChangelog = async (moduleName: string, sortedChangelog: ChangelogItem[]) => {

cli/getModuleInfo.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
API_DIR,
1313
METADATA_DIR,
1414
MODULE_DIR,
15+
PACKAGE_JSON_PATH,
1516
POLICIES_GENERATED_DIR,
1617
getExisting,
1718
getModuleInfoPath,
@@ -226,6 +227,34 @@ await Promise.all(
226227
await fs.mkdir(METADATA_DIR, { recursive: true });
227228
await fs.writeFile(path.join(METADATA_DIR, 'modules.json'), JSON.stringify(sortedRepos, null, 2));
228229

230+
console.info('Updating joi dependencies...');
231+
const packageJson = JSON.parse(await fs.readFile(PACKAGE_JSON_PATH, 'utf8'));
232+
const joiMajors = Object.keys(modules.joi.compatibility);
233+
const joiRepo = repos.joi;
234+
let changed = false;
235+
236+
for (const majorStr of joiMajors) {
237+
const major = parseInt(majorStr, 10);
238+
const depName = `joi-${major}`;
239+
const latestVersion = joiRepo?.versionsArray?.find((v) => Semver.major(v) === major);
240+
241+
if (latestVersion) {
242+
const depValue = `npm:joi@${latestVersion}`;
243+
if (packageJson.dependencies[depName] !== depValue) {
244+
packageJson.dependencies[depName] = depValue;
245+
changed = true;
246+
}
247+
} else {
248+
console.warn(`Could not find latest version for joi major ${major}`);
249+
}
250+
}
251+
252+
if (changed) {
253+
await fs.writeFile(PACKAGE_JSON_PATH, `${JSON.stringify(packageJson, null, 2)}\n`);
254+
console.info('Running pnpm install...');
255+
execFileSync('pnpm', ['install'], { stdio: 'inherit' });
256+
}
257+
229258
// Generate module/index.md
230259
const moduleIndexMdPath = path.join(MODULE_DIR, 'index.md');
231260
const moduleIndexContent = `# Modules

cli/paths.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export const MARKDOWN_DIR = path.join(GENERATED_DIR, 'markdown');
88
export const POLICIES_GENERATED_DIR = path.join(MARKDOWN_DIR, 'policies');
99
export const METADATA_DIR = path.join(GENERATED_DIR, 'metadata');
1010
export const MODULES_DIR = path.join(GENERATED_DIR, 'modules');
11+
export const ROOT_DIR = path.join(import.meta.dirname, '..');
12+
export const PACKAGE_JSON_PATH = path.join(ROOT_DIR, 'package.json');
13+
export const PUBLIC_ATOM_DIR = path.join(import.meta.dirname, '../docs/public/atom');
1114

1215
export const getModuleMarkdownPath = (moduleName: string, major: string | number) =>
1316
path.join(MARKDOWN_DIR, moduleName, major.toString(), 'api.md');
@@ -22,6 +25,8 @@ export const getModuleInfoPath = (moduleName: string) => path.join(getModuleStor
2225
export const getModuleChangelogPath = (moduleName: string) =>
2326
path.join(getModuleStoragePath(moduleName), 'changelog.json');
2427

28+
export const getModuleAtomPath = (moduleName: string) => path.join(getModuleStoragePath(moduleName), 'changelog.atom');
29+
2530
export const getExisting = async <T>(filePath: string): Promise<T | undefined> => {
2631
try {
2732
const content = await fs.readFile(filePath, 'utf8');

components/ApiOutline.vue

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { onMounted, ref } from 'vue';
44
55
const outline = ref(null);
66
7-
87
const onActiveChange = (mutations) => {
98
for (const { target, type, attributeName } of mutations) {
109
if (
@@ -18,12 +17,10 @@ const onActiveChange = (mutations) => {
1817
}
1918
};
2019
21-
2220
onMounted(() => {
2321
outline.value = document.querySelector('.VPDocAsideOutline');
2422
});
2523
26-
2724
useMutationObserver(outline, onActiveChange, {
2825
attributeFilter: ['class'],
2926
subtree: true,

components/CarbonAds.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,23 @@ import { onMounted, watch } from 'vue';
44
55
const route = useRoute();
66
7-
87
const load = () => {
98
const script = document.createElement('script');
109
script.id = '_carbonads_js';
1110
script.src = `//cdn.carbonads.com/carbon.js?serve=CEAIL27W&placement=joidev`;
1211
script.async = true;
1312
14-
1513
const container = document.querySelector('#carbon-ad');
1614
if (container) {
1715
container.innerHTML = '';
1816
container.append(script);
1917
}
2018
};
2119
22-
2320
onMounted(() => {
2421
load();
2522
});
2623
27-
2824
watch(
2925
() => route.path,
3026
() => {

components/CodeMirrorEditor.vue

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,16 @@ const { errorLines, joiVersion, language, readOnly } = defineProps({
2525
readOnly: { default: false, type: Boolean },
2626
});
2727
28-
2928
const data = defineModel({ default: '', type: String });
3029
31-
3230
const { isDark } = useData();
3331
const editorContainer = ref(null);
3432
let view = null;
3533
36-
3734
const themeCompartment = new Compartment();
3835
const joiCompartment = new Compartment();
3936
const setErrorLines = StateEffect.define();
4037
41-
4238
const errorLineField = StateField.define({
4339
create() {
4440
return Decoration.none;
@@ -62,7 +58,6 @@ const errorLineField = StateField.define({
6258
},
6359
});
6460
65-
6661
const getThemeExtension = () => {
6762
const theme = isDark.value ? darcula : eclipse;
6863
const cursorTheme = EditorView.theme({
@@ -76,7 +71,6 @@ const getThemeExtension = () => {
7671
return [theme, cursorTheme];
7772
};
7873
79-
8074
const getJoiExtension = () => {
8175
if (!joiVersion) {
8276
return [];
@@ -86,13 +80,11 @@ const getJoiExtension = () => {
8680
});
8781
};
8882
89-
9083
const format = async () => {
9184
if (!view) {
9285
return;
9386
}
9487
95-
9688
const code = view.state.doc.toString();
9789
try {
9890
const [prettier, prettierPluginBabel, prettierPluginEstree] = await Promise.all([
@@ -101,7 +93,6 @@ const format = async () => {
10193
import('prettier/plugins/estree'),
10294
]);
10395
104-
10596
const formatted = await prettier.format(code, {
10697
parser: language === 'json' ? 'json' : 'babel',
10798
plugins: [
@@ -114,7 +105,6 @@ const format = async () => {
114105
trailingComma: 'all',
115106
});
116107
117-
118108
if (formatted !== code) {
119109
view.dispatch({
120110
changes: { from: 0, insert: formatted, to: view.state.doc.length },
@@ -125,10 +115,8 @@ const format = async () => {
125115
}
126116
};
127117
128-
129118
defineExpose({ format });
130119
131-
132120
onMounted(() => {
133121
const extensions = [
134122
basicSetup,
@@ -162,7 +150,6 @@ onMounted(() => {
162150
}
163151
});
164152
165-
166153
watch(data, (newValue) => {
167154
if (view && newValue !== view.state.doc.toString()) {
168155
view.dispatch({
@@ -171,7 +158,6 @@ watch(data, (newValue) => {
171158
}
172159
});
173160
174-
175161
watch(
176162
() => errorLines,
177163
(newLines) => {
@@ -184,7 +170,6 @@ watch(
184170
{ deep: true },
185171
);
186172
187-
188173
watch(isDark, () => {
189174
if (view) {
190175
view.dispatch({
@@ -193,7 +178,6 @@ watch(isDark, () => {
193178
}
194179
});
195180
196-
197181
watch(
198182
() => joiVersion,
199183
() => {
@@ -205,7 +189,6 @@ watch(
205189
},
206190
);
207191
208-
209192
onBeforeUnmount(() => {
210193
if (view) {
211194
view.destroy();

components/ModuleIndex.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const search = ref('');
5252
const sort = ref('name');
5353
const moduleInfo = ref(modulesData);
5454
55-
5655
const filteredModules = computed(() => {
5756
const searchTerm = search.value.trim().toLowerCase();
5857
const modules = Object.entries(moduleInfo.value)

components/StatusContent.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import modulesData from '../generated/metadata/modules.json' with { type: 'json'
5757
5858
const modules = ref(modulesData);
5959
60-
6160
const filteredModules = computed(() => modules.value);
6261
</script>
6362

0 commit comments

Comments
 (0)