feat(blog): add file-based blog with dynamic slugs, MDX content and layout shell

- Introduced blog routing using Next.js App Router
- Implemented dynamic [slug] pages for blog posts
- Added MDX-based content loading via lib/posts
- Integrated shared TopBar layout with navigation
- Established clear content, lib and component separation
This commit is contained in:
PascalSchattenburg
2026-01-22 14:14:15 +01:00
parent b717952234
commit d147843c76
10412 changed files with 2475583 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
import type { TraceEvent } from '../types';
import type { Reporter } from './types';
declare class MultiReporter implements Reporter {
private reporters;
constructor(reporters: Reporter[]);
flushAll(opts?: {
end: boolean;
}): Promise<void>;
report(event: TraceEvent): void;
}
export declare const reporter: MultiReporter;
export {};

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "reporter", {
enumerable: true,
get: function() {
return reporter;
}
});
const _totelemetry = /*#__PURE__*/ _interop_require_default(require("./to-telemetry"));
const _tojson = /*#__PURE__*/ _interop_require_default(require("./to-json"));
const _tojsonbuild = /*#__PURE__*/ _interop_require_default(require("./to-json-build"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
class MultiReporter {
constructor(reporters){
this.reporters = [];
this.reporters = reporters;
}
async flushAll(opts) {
await Promise.all(this.reporters.map((reporter)=>reporter.flushAll(opts)));
}
report(event) {
this.reporters.forEach((reporter)=>reporter.report(event));
}
}
const reporter = new MultiReporter([
_tojson.default,
_tojsonbuild.default,
_totelemetry.default
]);
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/trace/report/index.ts"],"sourcesContent":["import type { TraceEvent } from '../types'\nimport reportToTelemetry from './to-telemetry'\nimport reportToJson from './to-json'\nimport reportToJsonBuild from './to-json-build'\nimport type { Reporter } from './types'\n\nclass MultiReporter implements Reporter {\n private reporters: Reporter[] = []\n\n constructor(reporters: Reporter[]) {\n this.reporters = reporters\n }\n\n async flushAll(opts?: { end: boolean }) {\n await Promise.all(this.reporters.map((reporter) => reporter.flushAll(opts)))\n }\n\n report(event: TraceEvent) {\n this.reporters.forEach((reporter) => reporter.report(event))\n }\n}\n\n// JSON is always reported to allow for diagnostics\nexport const reporter = new MultiReporter([\n reportToJson,\n reportToJsonBuild,\n reportToTelemetry,\n])\n"],"names":["reporter","MultiReporter","constructor","reporters","flushAll","opts","Promise","all","map","report","event","forEach","reportToJson","reportToJsonBuild","reportToTelemetry"],"mappings":";;;;+BAuBaA;;;eAAAA;;;oEAtBiB;+DACL;oEACK;;;;;;AAG9B,MAAMC;IAGJC,YAAYC,SAAqB,CAAE;aAF3BA,YAAwB,EAAE;QAGhC,IAAI,CAACA,SAAS,GAAGA;IACnB;IAEA,MAAMC,SAASC,IAAuB,EAAE;QACtC,MAAMC,QAAQC,GAAG,CAAC,IAAI,CAACJ,SAAS,CAACK,GAAG,CAAC,CAACR,WAAaA,SAASI,QAAQ,CAACC;IACvE;IAEAI,OAAOC,KAAiB,EAAE;QACxB,IAAI,CAACP,SAAS,CAACQ,OAAO,CAAC,CAACX,WAAaA,SAASS,MAAM,CAACC;IACvD;AACF;AAGO,MAAMV,WAAW,IAAIC,cAAc;IACxCW,eAAY;IACZC,oBAAiB;IACjBC,oBAAiB;CAClB","ignoreList":[0]}

View File

@@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
const _promises = require("fs/promises");
const _ = require(".");
const _shared = require("../shared");
const _path = require("path");
const _os = require("os");
const TRACE_EVENT = {
name: 'test-span',
duration: 321,
timestamp: Date.now(),
id: 127,
startTime: Date.now()
};
const WEBPACK_INVALIDATED_EVENT = {
name: 'webpack-invalidated',
duration: 100,
timestamp: Date.now(),
id: 112,
startTime: Date.now()
};
describe('Trace Reporter', ()=>{
describe('JSON reporter', ()=>{
it('should write the trace events to JSON file', async ()=>{
const tmpDir = await (0, _promises.mkdtemp)((0, _path.join)((0, _os.tmpdir)(), 'json-reporter'));
(0, _shared.setGlobal)('distDir', tmpDir);
(0, _shared.setGlobal)('phase', 'anything');
_.reporter.report(TRACE_EVENT);
await _.reporter.flushAll();
const traceFilename = (0, _path.join)(tmpDir, 'trace');
const traces = JSON.parse(await (0, _promises.readFile)(traceFilename, 'utf-8'));
expect(traces.length).toEqual(1);
expect(traces[0].name).toEqual('test-span');
expect(traces[0].id).toEqual(127);
expect(traces[0].duration).toEqual(321);
expect(traces[0].traceId).toBeDefined();
});
});
describe('Telemetry reporter', ()=>{
it('should record telemetry event', async ()=>{
const recordMock = jest.fn();
const telemetryMock = {
record: recordMock
};
(0, _shared.setGlobal)('telemetry', telemetryMock);
// This should be ignored.
_.reporter.report(TRACE_EVENT);
expect(recordMock).toHaveBeenCalledTimes(0);
_.reporter.report(WEBPACK_INVALIDATED_EVENT);
expect(recordMock).toHaveBeenCalledTimes(1);
expect(recordMock).toHaveBeenCalledWith({
eventName: 'WEBPACK_INVALIDATED',
payload: {
durationInMicroseconds: 100
}
});
});
});
});
//# sourceMappingURL=index.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/trace/report/index.test.ts"],"sourcesContent":["import { mkdtemp, readFile } from 'fs/promises'\nimport { reporter } from '.'\nimport { setGlobal } from '../shared'\nimport { join } from 'path'\nimport { tmpdir } from 'os'\n\nconst TRACE_EVENT = {\n name: 'test-span',\n duration: 321,\n timestamp: Date.now(),\n id: 127,\n startTime: Date.now(),\n}\nconst WEBPACK_INVALIDATED_EVENT = {\n name: 'webpack-invalidated',\n duration: 100,\n timestamp: Date.now(),\n id: 112,\n startTime: Date.now(),\n}\n\ndescribe('Trace Reporter', () => {\n describe('JSON reporter', () => {\n it('should write the trace events to JSON file', async () => {\n const tmpDir = await mkdtemp(join(tmpdir(), 'json-reporter'))\n setGlobal('distDir', tmpDir)\n setGlobal('phase', 'anything')\n reporter.report(TRACE_EVENT)\n await reporter.flushAll()\n const traceFilename = join(tmpDir, 'trace')\n const traces = JSON.parse(await readFile(traceFilename, 'utf-8'))\n expect(traces.length).toEqual(1)\n expect(traces[0].name).toEqual('test-span')\n expect(traces[0].id).toEqual(127)\n expect(traces[0].duration).toEqual(321)\n expect(traces[0].traceId).toBeDefined()\n })\n })\n\n describe('Telemetry reporter', () => {\n it('should record telemetry event', async () => {\n const recordMock = jest.fn()\n const telemetryMock = {\n record: recordMock,\n }\n setGlobal('telemetry', telemetryMock)\n // This should be ignored.\n reporter.report(TRACE_EVENT)\n expect(recordMock).toHaveBeenCalledTimes(0)\n reporter.report(WEBPACK_INVALIDATED_EVENT)\n expect(recordMock).toHaveBeenCalledTimes(1)\n expect(recordMock).toHaveBeenCalledWith({\n eventName: 'WEBPACK_INVALIDATED',\n payload: {\n durationInMicroseconds: 100,\n },\n })\n })\n })\n})\n"],"names":["TRACE_EVENT","name","duration","timestamp","Date","now","id","startTime","WEBPACK_INVALIDATED_EVENT","describe","it","tmpDir","mkdtemp","join","tmpdir","setGlobal","reporter","report","flushAll","traceFilename","traces","JSON","parse","readFile","expect","length","toEqual","traceId","toBeDefined","recordMock","jest","fn","telemetryMock","record","toHaveBeenCalledTimes","toHaveBeenCalledWith","eventName","payload","durationInMicroseconds"],"mappings":";;;;0BAAkC;kBACT;wBACC;sBACL;oBACE;AAEvB,MAAMA,cAAc;IAClBC,MAAM;IACNC,UAAU;IACVC,WAAWC,KAAKC,GAAG;IACnBC,IAAI;IACJC,WAAWH,KAAKC,GAAG;AACrB;AACA,MAAMG,4BAA4B;IAChCP,MAAM;IACNC,UAAU;IACVC,WAAWC,KAAKC,GAAG;IACnBC,IAAI;IACJC,WAAWH,KAAKC,GAAG;AACrB;AAEAI,SAAS,kBAAkB;IACzBA,SAAS,iBAAiB;QACxBC,GAAG,8CAA8C;YAC/C,MAAMC,SAAS,MAAMC,IAAAA,iBAAO,EAACC,IAAAA,UAAI,EAACC,IAAAA,UAAM,KAAI;YAC5CC,IAAAA,iBAAS,EAAC,WAAWJ;YACrBI,IAAAA,iBAAS,EAAC,SAAS;YACnBC,UAAQ,CAACC,MAAM,CAACjB;YAChB,MAAMgB,UAAQ,CAACE,QAAQ;YACvB,MAAMC,gBAAgBN,IAAAA,UAAI,EAACF,QAAQ;YACnC,MAAMS,SAASC,KAAKC,KAAK,CAAC,MAAMC,IAAAA,kBAAQ,EAACJ,eAAe;YACxDK,OAAOJ,OAAOK,MAAM,EAAEC,OAAO,CAAC;YAC9BF,OAAOJ,MAAM,CAAC,EAAE,CAACnB,IAAI,EAAEyB,OAAO,CAAC;YAC/BF,OAAOJ,MAAM,CAAC,EAAE,CAACd,EAAE,EAAEoB,OAAO,CAAC;YAC7BF,OAAOJ,MAAM,CAAC,EAAE,CAAClB,QAAQ,EAAEwB,OAAO,CAAC;YACnCF,OAAOJ,MAAM,CAAC,EAAE,CAACO,OAAO,EAAEC,WAAW;QACvC;IACF;IAEAnB,SAAS,sBAAsB;QAC7BC,GAAG,iCAAiC;YAClC,MAAMmB,aAAaC,KAAKC,EAAE;YAC1B,MAAMC,gBAAgB;gBACpBC,QAAQJ;YACV;YACAd,IAAAA,iBAAS,EAAC,aAAaiB;YACvB,0BAA0B;YAC1BhB,UAAQ,CAACC,MAAM,CAACjB;YAChBwB,OAAOK,YAAYK,qBAAqB,CAAC;YACzClB,UAAQ,CAACC,MAAM,CAACT;YAChBgB,OAAOK,YAAYK,qBAAqB,CAAC;YACzCV,OAAOK,YAAYM,oBAAoB,CAAC;gBACtCC,WAAW;gBACXC,SAAS;oBACPC,wBAAwB;gBAC1B;YACF;QACF;IACF;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,9 @@
import type { TraceEvent } from '../types';
declare function reportToJsonBuild(event: TraceEvent): void;
declare const _default: {
flushAll: (opts?: {
end: boolean;
}) => Promise<void | undefined> | undefined;
report: typeof reportToJsonBuild;
};
export default _default;

View File

@@ -0,0 +1,137 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _shared = require("../shared");
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _constants = require("../../shared/lib/constants");
const _tojson = require("./to-json");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
let writeStream;
let batch;
const writeStreamOptions = {
flags: 'a',
encoding: 'utf8'
};
class RotatingWriteStream {
constructor(file, sizeLimit){
this.file = file;
this.size = 0;
this.sizeLimit = sizeLimit;
this.createWriteStream();
}
createWriteStream() {
this.writeStream = _fs.default.createWriteStream(this.file, writeStreamOptions);
}
// Recreate the file
async rotate() {
await this.end();
try {
_fs.default.unlinkSync(this.file);
} catch (err) {
// It's fine if the file does not exist yet
if (err.code !== 'ENOENT') {
throw err;
}
}
this.size = 0;
this.createWriteStream();
this.rotatePromise = undefined;
}
async write(data) {
if (this.rotatePromise) await this.rotatePromise;
this.size += data.length;
if (this.size > this.sizeLimit) {
await (this.rotatePromise = this.rotate());
}
if (!this.writeStream.write(data, 'utf8')) {
if (this.drainPromise === undefined) {
this.drainPromise = new Promise((resolve, _reject)=>{
this.writeStream.once('drain', ()=>{
this.drainPromise = undefined;
resolve();
});
});
}
await this.drainPromise;
}
}
end() {
return new Promise((resolve)=>{
this.writeStream.end(resolve);
});
}
}
const allowlistedEvents = new Set([
'next-build',
'run-turbopack',
'run-webpack',
'run-typescript',
'run-eslint',
'static-check',
'collect-build-traces',
'static-generation',
'output-export-full-static-export',
'adapter-handle-build-complete',
'output-standalone',
'telemetry-flush'
]);
function reportToJsonBuild(event) {
if (!allowlistedEvents.has(event.name)) {
return;
}
const distDir = _shared.traceGlobals.get('distDir');
const phase = _shared.traceGlobals.get('phase');
if (!distDir || !phase) {
return;
}
// Only report in production builds
if (phase !== _constants.PHASE_PRODUCTION_BUILD) {
return;
}
if (!batch) {
batch = (0, _tojson.batcher)(async (events)=>{
if (!writeStream) {
await _fs.default.promises.mkdir(distDir, {
recursive: true
});
const file = _path.default.join(distDir, 'trace-build');
writeStream = new RotatingWriteStream(file, // Development is limited to 50MB, production is unlimited
phase === _constants.PHASE_DEVELOPMENT_SERVER ? 52428800 : Infinity);
}
const eventsJson = JSON.stringify(events);
try {
await writeStream.write(eventsJson + '\n');
} catch (err) {
console.log(err);
}
});
}
batch.report({
...event,
traceId: _shared.traceId
});
}
const _default = {
flushAll: (opts)=>batch ? batch.flushAll().then(()=>{
const phase = _shared.traceGlobals.get('phase');
// Only end writeStream when manually flushing in production
if ((opts == null ? void 0 : opts.end) || phase !== _constants.PHASE_DEVELOPMENT_SERVER) {
return writeStream.end();
}
}) : undefined,
report: reportToJsonBuild
};
//# sourceMappingURL=to-json-build.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
import type { TraceEvent } from '../types';
export declare function batcher(reportEvents: (evts: TraceEvent[]) => Promise<void>): {
flushAll: () => Promise<void>;
report: (event: TraceEvent) => void;
};
declare function reportToJson(event: TraceEvent): void;
declare const _default: {
flushAll: (opts?: {
end: boolean;
}) => Promise<void | undefined> | undefined;
report: typeof reportToJson;
};
export default _default;

View File

@@ -0,0 +1,151 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
batcher: null,
default: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
batcher: function() {
return batcher;
},
default: function() {
return _default;
}
});
const _shared = require("../shared");
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _constants = require("../../shared/lib/constants");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function batcher(reportEvents) {
const events = [];
// Promise queue to ensure events are always sent on flushAll
const queue = new Set();
return {
flushAll: async ()=>{
await Promise.all(queue);
if (events.length > 0) {
await reportEvents(events);
events.length = 0;
}
},
report: (event)=>{
events.push(event);
if (events.length > 100) {
const evts = events.slice();
events.length = 0;
const report = reportEvents(evts);
queue.add(report);
report.then(()=>queue.delete(report));
}
}
};
}
let writeStream;
let batch;
const writeStreamOptions = {
flags: 'a',
encoding: 'utf8'
};
class RotatingWriteStream {
constructor(file, sizeLimit){
this.file = file;
this.size = 0;
this.sizeLimit = sizeLimit;
this.createWriteStream();
}
createWriteStream() {
this.writeStream = _fs.default.createWriteStream(this.file, writeStreamOptions);
}
// Recreate the file
async rotate() {
await this.end();
try {
_fs.default.unlinkSync(this.file);
} catch (err) {
// It's fine if the file does not exist yet
if (err.code !== 'ENOENT') {
throw err;
}
}
this.size = 0;
this.createWriteStream();
this.rotatePromise = undefined;
}
async write(data) {
if (this.rotatePromise) await this.rotatePromise;
this.size += data.length;
if (this.size > this.sizeLimit) {
await (this.rotatePromise = this.rotate());
}
if (!this.writeStream.write(data, 'utf8')) {
if (this.drainPromise === undefined) {
this.drainPromise = new Promise((resolve, _reject)=>{
this.writeStream.once('drain', ()=>{
this.drainPromise = undefined;
resolve();
});
});
}
await this.drainPromise;
}
}
end() {
return new Promise((resolve)=>{
this.writeStream.end(resolve);
});
}
}
function reportToJson(event) {
const distDir = _shared.traceGlobals.get('distDir');
const phase = _shared.traceGlobals.get('phase');
if (!distDir || !phase) {
return;
}
if (!batch) {
batch = batcher(async (events)=>{
if (!writeStream) {
await _fs.default.promises.mkdir(distDir, {
recursive: true
});
const file = _path.default.join(distDir, 'trace');
writeStream = new RotatingWriteStream(file, // Development is limited to 50MB, production is unlimited
phase === _constants.PHASE_DEVELOPMENT_SERVER ? 52428800 : Infinity);
}
const eventsJson = JSON.stringify(events);
try {
await writeStream.write(eventsJson + '\n');
} catch (err) {
console.log(err);
}
});
}
batch.report({
...event,
traceId: _shared.traceId
});
}
const _default = {
flushAll: (opts)=>batch ? batch.flushAll().then(()=>{
const phase = _shared.traceGlobals.get('phase');
// Only end writeStream when manually flushing in production
if ((opts == null ? void 0 : opts.end) || phase !== _constants.PHASE_DEVELOPMENT_SERVER) {
return writeStream.end();
}
}) : undefined,
report: reportToJson
};
//# sourceMappingURL=to-json.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
import type { TraceEvent } from '../types';
declare const _default: {
flushAll: () => void;
report: ({ name, duration }: TraceEvent) => void;
};
export default _default;

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _shared = require("../shared");
const TRACE_EVENT_ACCESSLIST = new Map(Object.entries({
'webpack-invalidated': 'WEBPACK_INVALIDATED'
}));
const reportToTelemetry = ({ name, duration })=>{
const eventName = TRACE_EVENT_ACCESSLIST.get(name);
if (!eventName) {
return;
}
const telemetry = _shared.traceGlobals.get('telemetry');
if (!telemetry) {
return;
}
telemetry.record({
eventName,
payload: {
durationInMicroseconds: duration
}
});
};
const _default = {
flushAll: ()=>{},
report: reportToTelemetry
};
//# sourceMappingURL=to-telemetry.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/trace/report/to-telemetry.ts"],"sourcesContent":["import type { Telemetry } from '../../telemetry/storage'\nimport { traceGlobals } from '../shared'\nimport type { TraceEvent } from '../types'\n\nconst TRACE_EVENT_ACCESSLIST = new Map(\n Object.entries({\n 'webpack-invalidated': 'WEBPACK_INVALIDATED',\n })\n)\n\nconst reportToTelemetry = ({ name, duration }: TraceEvent) => {\n const eventName = TRACE_EVENT_ACCESSLIST.get(name)\n if (!eventName) {\n return\n }\n const telemetry: Telemetry | undefined = traceGlobals.get('telemetry')\n if (!telemetry) {\n return\n }\n\n telemetry.record({\n eventName,\n payload: {\n durationInMicroseconds: duration,\n },\n })\n}\n\nexport default {\n flushAll: () => {},\n report: reportToTelemetry,\n}\n"],"names":["TRACE_EVENT_ACCESSLIST","Map","Object","entries","reportToTelemetry","name","duration","eventName","get","telemetry","traceGlobals","record","payload","durationInMicroseconds","flushAll","report"],"mappings":";;;;+BA4BA;;;eAAA;;;wBA3B6B;AAG7B,MAAMA,yBAAyB,IAAIC,IACjCC,OAAOC,OAAO,CAAC;IACb,uBAAuB;AACzB;AAGF,MAAMC,oBAAoB,CAAC,EAAEC,IAAI,EAAEC,QAAQ,EAAc;IACvD,MAAMC,YAAYP,uBAAuBQ,GAAG,CAACH;IAC7C,IAAI,CAACE,WAAW;QACd;IACF;IACA,MAAME,YAAmCC,oBAAY,CAACF,GAAG,CAAC;IAC1D,IAAI,CAACC,WAAW;QACd;IACF;IAEAA,UAAUE,MAAM,CAAC;QACfJ;QACAK,SAAS;YACPC,wBAAwBP;QAC1B;IACF;AACF;MAEA,WAAe;IACbQ,UAAU,KAAO;IACjBC,QAAQX;AACV","ignoreList":[0]}

View File

@@ -0,0 +1,7 @@
import type { TraceEvent } from '../types';
export type Reporter = {
flushAll: (opts?: {
end: boolean;
}) => Promise<void> | void;
report: (event: TraceEvent) => void;
};

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","ignoreList":[]}