summaryrefslogtreecommitdiff
path: root/packages/integrations/markdoc/src/utils.ts
blob: 1daf8f026ffe6ee040930dd86573bb6755585fd0 (plain) (blame)
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
import matter from 'gray-matter';
import crypto from 'node:crypto';
import path from 'node:path';
import type { ErrorPayload as ViteErrorPayload } from 'vite';

/**
 * Match YAML exception handling from Astro core errors
 * @see 'astro/src/core/errors.ts'
 */
export function parseFrontmatter(fileContents: string, filePath: string) {
	try {
		// `matter` is empty string on cache results
		// clear cache to prevent this
		(matter as any).clearCache();
		return matter(fileContents);
	} catch (e: any) {
		if (e.name === 'YAMLException') {
			const err: Error & ViteErrorPayload['err'] = e;
			err.id = filePath;
			err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
			err.message = e.reason;
			throw err;
		} else {
			throw e;
		}
	}
}

/**
 * Matches AstroError object with types like error codes stubbed out
 * @see 'astro/src/core/errors/errors.ts'
 */
export class MarkdocError extends Error {
	public errorCode: number;
	public loc: ErrorLocation | undefined;
	public title: string | undefined;
	public hint: string | undefined;
	public frame: string | undefined;

	type = 'MarkdocError';

	constructor(props: ErrorProperties, ...params: any) {
		super(...params);

		const {
			// Use default code for unknown errors in Astro core
			// We don't have a best practice for integration error codes yet
			code = 99999,
			name,
			title = 'MarkdocError',
			message,
			stack,
			location,
			hint,
			frame,
		} = props;

		this.errorCode = code;
		this.title = title;
		if (message) this.message = message;
		// Only set this if we actually have a stack passed, otherwise uses Error's
		this.stack = stack ? stack : this.stack;
		this.loc = location;
		this.hint = hint;
		this.frame = frame;
	}
}

interface ErrorLocation {
	file?: string;
	line?: number;
	column?: number;
}

interface ErrorProperties {
	code?: number;
	title?: string;
	name?: string;
	message?: string;
	location?: ErrorLocation;
	hint?: string;
	stack?: string;
	frame?: string;
}

/**
 * @see 'astro/src/core/path.ts'
 */
export function prependForwardSlash(str: string) {
	return str[0] === '/' ? str : '/' + str;
}

export function isValidUrl(str: string): boolean {
	try {
		new URL(str);
		return true;
	} catch {
		return false;
	}
}

/**
 * Identifies Astro components with propagated assets
 * @see 'packages/astro/src/content/consts.ts'
 */
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';

/**
 * @see 'packages/astro/src/content/utils.ts'
 */
export function hasContentFlag(viteId: string, flag: string): boolean {
	const flags = new URLSearchParams(viteId.split('?')[1] ?? '');
	return flags.has(flag);
}

/**
 * Create build hash for manual Rollup chunks.
 * @see 'packages/astro/src/core/build/plugins/plugin-css.ts'
 */
export function createNameHash(baseId: string, hashIds: string[]): string {
	const baseName = baseId ? path.parse(baseId).name : 'index';
	const hash = crypto.createHash('sha256');
	for (const id of hashIds) {
		hash.update(id, 'utf-8');
	}
	const h = hash.digest('hex').slice(0, 8);
	const proposedName = baseName + '.' + h;
	return proposedName;
}