create-ngmd 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/index.mjs +165 -0
- package/package.json +49 -0
- package/template/README.md +26 -0
- package/template/angular.json +54 -0
- package/template/index.html +47 -0
- package/template/link-guard.plugin.ts +190 -0
- package/template/package.json +76 -0
- package/template/page-meta.plugin.ts +132 -0
- package/template/public/analog.svg +1 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/favicon.svg +46 -0
- package/template/public/logo-mark.svg +46 -0
- package/template/public/logo.svg +53 -0
- package/template/public/vite.svg +1 -0
- package/template/sitemap.plugin.ts +148 -0
- package/template/src/app/app.config.server.ts +10 -0
- package/template/src/app/app.config.ts +59 -0
- package/template/src/app/app.spec.ts +20 -0
- package/template/src/app/app.ts +260 -0
- package/template/src/app/components/breadcrumb.ts +76 -0
- package/template/src/app/components/code-copy.ts +90 -0
- package/template/src/app/components/code-group.ts +70 -0
- package/template/src/app/components/command-palette.ts +299 -0
- package/template/src/app/components/external-links.ts +55 -0
- package/template/src/app/components/heading-anchors.ts +95 -0
- package/template/src/app/components/hlm-card.ts +31 -0
- package/template/src/app/components/media-enhancer.ts +95 -0
- package/template/src/app/components/page-footer.ts +107 -0
- package/template/src/app/components/sidebar.ts +65 -0
- package/template/src/app/components/toc.ts +131 -0
- package/template/src/app/layout-mode.service.ts +10 -0
- package/template/src/app/pages/[...not-found].page.ts +47 -0
- package/template/src/app/pages/index.page.ts +28 -0
- package/template/src/app/pages/welcome.page.ts +18 -0
- package/template/src/app/theme.ts +54 -0
- package/template/src/app/title-strategy.ts +48 -0
- package/template/src/app/ui/alert.ts +46 -0
- package/template/src/app/ui/callout.ts +57 -0
- package/template/src/app/ui/card.ts +41 -0
- package/template/src/app/ui/code-block.ts +76 -0
- package/template/src/app/ui/hero.ts +42 -0
- package/template/src/app/ui/image.ts +26 -0
- package/template/src/app/ui/index.ts +45 -0
- package/template/src/app/ui/pill.ts +42 -0
- package/template/src/app/ui/tabs.ts +76 -0
- package/template/src/app/ui/video.ts +40 -0
- package/template/src/app/ui/workflow.ts +51 -0
- package/template/src/content/welcome.md +19 -0
- package/template/src/main.server.ts +7 -0
- package/template/src/main.ts +6 -0
- package/template/src/marked-extensions/index.ts +47 -0
- package/template/src/marked-extensions/ngmd-code-group.ts +126 -0
- package/template/src/marked-extensions/ngmd-code-highlight.ts +102 -0
- package/template/src/marked-extensions/ngmd-code-import.ts +118 -0
- package/template/src/marked-extensions/ngmd-image.ts +41 -0
- package/template/src/marked-extensions/ngmd-keywords.ts +75 -0
- package/template/src/marked-extensions/ngmd-video.ts +48 -0
- package/template/src/marked-extensions/shiki-shared.ts +42 -0
- package/template/src/ngmd.config.ts +98 -0
- package/template/src/server/routes/api/v1/hello.ts +3 -0
- package/template/src/styles.css +347 -0
- package/template/src/test-setup.ts +6 -0
- package/template/src/vite-env.d.ts +9 -0
- package/template/tsconfig.app.json +14 -0
- package/template/tsconfig.json +34 -0
- package/template/tsconfig.spec.json +11 -0
- package/template/vite.config.ts +78 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
/* View Transitions API tuning. Angular's withViewTransitions() crossfades
|
|
4
|
+
* route changes via these pseudo-elements. Shorten the default ~250ms to
|
|
5
|
+
* 150ms so it stays snappy. */
|
|
6
|
+
::view-transition-old(root),
|
|
7
|
+
::view-transition-new(root) {
|
|
8
|
+
animation-duration: 150ms;
|
|
9
|
+
animation-timing-function: ease;
|
|
10
|
+
}
|
|
11
|
+
@plugin '@tailwindcss/typography';
|
|
12
|
+
|
|
13
|
+
@variant dark (&:where(.dark, .dark *));
|
|
14
|
+
|
|
15
|
+
@layer base {
|
|
16
|
+
:root {
|
|
17
|
+
/* Surface */
|
|
18
|
+
--bg: #ffffff;
|
|
19
|
+
--bg-muted: #f4f4f5;
|
|
20
|
+
--fg: #0a0a0a;
|
|
21
|
+
--muted: #71717a;
|
|
22
|
+
--border: #e4e4e7;
|
|
23
|
+
--border-strong: #d4d4d8;
|
|
24
|
+
|
|
25
|
+
/* Brand (used by accent-aware components) */
|
|
26
|
+
--primary: #18181b;
|
|
27
|
+
--primary-fg: #fafafa;
|
|
28
|
+
--accent: #d946ef;
|
|
29
|
+
--accent-fg: #ffffff;
|
|
30
|
+
--accent-soft: rgba(217, 70, 239, 0.12);
|
|
31
|
+
--accent-gradient: linear-gradient(
|
|
32
|
+
to right,
|
|
33
|
+
#f43f5e 0%,
|
|
34
|
+
#d946ef 50%,
|
|
35
|
+
#a855f7 100%
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/* Geometry */
|
|
39
|
+
--radius-sm: 0.25rem;
|
|
40
|
+
--radius: 0.5rem;
|
|
41
|
+
--radius-lg: 0.75rem;
|
|
42
|
+
--radius-xl: 1rem;
|
|
43
|
+
|
|
44
|
+
/* Typography */
|
|
45
|
+
--font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
|
46
|
+
sans-serif;
|
|
47
|
+
--font-display: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
48
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
49
|
+
|
|
50
|
+
font-family: var(--font-sans);
|
|
51
|
+
color-scheme: light;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.dark {
|
|
55
|
+
--bg: #0a0a0a;
|
|
56
|
+
--bg-muted: #18181b;
|
|
57
|
+
--fg: #fafafa;
|
|
58
|
+
--muted: #a1a1aa;
|
|
59
|
+
--border: #27272a;
|
|
60
|
+
--border-strong: #3f3f46;
|
|
61
|
+
|
|
62
|
+
--primary: #fafafa;
|
|
63
|
+
--primary-fg: #18181b;
|
|
64
|
+
--accent: #e879f9;
|
|
65
|
+
--accent-fg: #18181b;
|
|
66
|
+
--accent-soft: rgba(232, 121, 249, 0.15);
|
|
67
|
+
|
|
68
|
+
color-scheme: dark;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
html,
|
|
72
|
+
body {
|
|
73
|
+
margin: 0;
|
|
74
|
+
background: var(--bg);
|
|
75
|
+
color: var(--fg);
|
|
76
|
+
min-height: 100vh;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
a {
|
|
80
|
+
text-decoration: inherit;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@layer components {
|
|
85
|
+
analog-markdown,
|
|
86
|
+
analog-markdown-route {
|
|
87
|
+
display: block;
|
|
88
|
+
color: var(--fg);
|
|
89
|
+
line-height: 1.7;
|
|
90
|
+
word-wrap: break-word;
|
|
91
|
+
overflow-wrap: break-word;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
analog-markdown-route {
|
|
95
|
+
max-width: 48rem;
|
|
96
|
+
margin-inline: auto;
|
|
97
|
+
padding: 2rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
analog-markdown h1,
|
|
101
|
+
analog-markdown-route h1 {
|
|
102
|
+
font-size: 2.25rem;
|
|
103
|
+
font-weight: 700;
|
|
104
|
+
line-height: 1.2;
|
|
105
|
+
margin-block: 0 1rem;
|
|
106
|
+
letter-spacing: -0.02em;
|
|
107
|
+
scroll-margin-top: 6rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
analog-markdown h2,
|
|
111
|
+
analog-markdown-route h2 {
|
|
112
|
+
font-size: 1.5rem;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
line-height: 1.3;
|
|
115
|
+
margin-block: 2.5rem 1rem;
|
|
116
|
+
padding-block-end: 0.5rem;
|
|
117
|
+
border-block-end: 1px solid var(--border);
|
|
118
|
+
letter-spacing: -0.01em;
|
|
119
|
+
scroll-margin-top: 6rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
analog-markdown h3,
|
|
123
|
+
analog-markdown-route h3 {
|
|
124
|
+
font-size: 1.25rem;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
margin-block: 2rem 0.75rem;
|
|
127
|
+
scroll-margin-top: 6rem;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
analog-markdown p,
|
|
131
|
+
analog-markdown-route p {
|
|
132
|
+
margin-block: 1rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
analog-markdown ul,
|
|
136
|
+
analog-markdown-route ul,
|
|
137
|
+
analog-markdown ol,
|
|
138
|
+
analog-markdown-route ol {
|
|
139
|
+
margin-block: 1rem;
|
|
140
|
+
padding-inline-start: 1.5rem;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
analog-markdown ul,
|
|
144
|
+
analog-markdown-route ul {
|
|
145
|
+
list-style: disc;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
analog-markdown ol,
|
|
149
|
+
analog-markdown-route ol {
|
|
150
|
+
list-style: decimal;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
analog-markdown li,
|
|
154
|
+
analog-markdown-route li {
|
|
155
|
+
margin-block: 0.25rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
analog-markdown a,
|
|
159
|
+
analog-markdown-route a {
|
|
160
|
+
color: inherit;
|
|
161
|
+
text-decoration: underline;
|
|
162
|
+
text-underline-offset: 3px;
|
|
163
|
+
transition: color 0.15s ease;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
analog-markdown a:hover,
|
|
167
|
+
analog-markdown-route a:hover {
|
|
168
|
+
color: var(--accent);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
analog-markdown a:focus-visible,
|
|
172
|
+
analog-markdown-route a:focus-visible {
|
|
173
|
+
outline: 2px solid var(--accent);
|
|
174
|
+
outline-offset: 2px;
|
|
175
|
+
border-radius: 2px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
analog-markdown code:not(pre code),
|
|
179
|
+
analog-markdown-route code:not(pre code) {
|
|
180
|
+
background: color-mix(in srgb, var(--border) 60%, transparent);
|
|
181
|
+
padding: 0.15em 0.4em;
|
|
182
|
+
border-radius: 0.25rem;
|
|
183
|
+
font-size: 0.875em;
|
|
184
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
analog-markdown pre,
|
|
188
|
+
analog-markdown-route pre {
|
|
189
|
+
margin-block: 1.5rem;
|
|
190
|
+
padding: 1rem 1.25rem;
|
|
191
|
+
border-radius: 0.5rem;
|
|
192
|
+
overflow-x: auto;
|
|
193
|
+
font-size: 0.875rem;
|
|
194
|
+
line-height: 1.6;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
analog-markdown pre code,
|
|
198
|
+
analog-markdown-route pre code {
|
|
199
|
+
background: transparent;
|
|
200
|
+
padding: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
analog-markdown blockquote,
|
|
204
|
+
analog-markdown-route blockquote {
|
|
205
|
+
border-inline-start: 3px solid var(--border);
|
|
206
|
+
padding-inline-start: 1rem;
|
|
207
|
+
margin-inline: 0;
|
|
208
|
+
color: var(--muted);
|
|
209
|
+
font-style: italic;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.ngmd-video {
|
|
213
|
+
position: relative;
|
|
214
|
+
width: 100%;
|
|
215
|
+
aspect-ratio: 16 / 9;
|
|
216
|
+
margin-block: 1.5rem;
|
|
217
|
+
border-radius: var(--radius-lg);
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
background: var(--bg-muted);
|
|
220
|
+
}
|
|
221
|
+
.ngmd-video iframe {
|
|
222
|
+
position: absolute;
|
|
223
|
+
inset: 0;
|
|
224
|
+
width: 100%;
|
|
225
|
+
height: 100%;
|
|
226
|
+
border: 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ngmd-image {
|
|
230
|
+
margin-block: 1.5rem;
|
|
231
|
+
margin-inline: 0;
|
|
232
|
+
}
|
|
233
|
+
.ngmd-image img {
|
|
234
|
+
width: 100%;
|
|
235
|
+
height: auto;
|
|
236
|
+
border-radius: var(--radius);
|
|
237
|
+
border: 1px solid var(--border);
|
|
238
|
+
}
|
|
239
|
+
.ngmd-image figcaption {
|
|
240
|
+
margin-top: 0.5rem;
|
|
241
|
+
text-align: center;
|
|
242
|
+
font-size: 0.875rem;
|
|
243
|
+
color: var(--muted);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.ngmd-code-group {
|
|
247
|
+
margin-block: 1.5rem;
|
|
248
|
+
border: 1px solid var(--border);
|
|
249
|
+
border-radius: var(--radius-lg);
|
|
250
|
+
overflow: hidden;
|
|
251
|
+
background: var(--bg);
|
|
252
|
+
}
|
|
253
|
+
.ngmd-code-group__tabs {
|
|
254
|
+
display: flex;
|
|
255
|
+
flex-wrap: wrap;
|
|
256
|
+
border-bottom: 1px solid var(--border);
|
|
257
|
+
background: var(--bg-muted);
|
|
258
|
+
}
|
|
259
|
+
.ngmd-code-group__tab {
|
|
260
|
+
appearance: none;
|
|
261
|
+
background: transparent;
|
|
262
|
+
border: 0;
|
|
263
|
+
border-bottom: 2px solid transparent;
|
|
264
|
+
padding: 0.625rem 1rem;
|
|
265
|
+
font-size: 0.875rem;
|
|
266
|
+
font-weight: 500;
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
color: var(--muted);
|
|
269
|
+
font-family: var(--font-mono);
|
|
270
|
+
transition: color 0.15s ease, border-color 0.15s ease;
|
|
271
|
+
}
|
|
272
|
+
.ngmd-code-group__tab:hover {
|
|
273
|
+
color: var(--fg);
|
|
274
|
+
}
|
|
275
|
+
.ngmd-code-group__tab[data-active="true"] {
|
|
276
|
+
color: var(--accent);
|
|
277
|
+
border-bottom-color: var(--accent);
|
|
278
|
+
}
|
|
279
|
+
.ngmd-code-group__panel {
|
|
280
|
+
display: none;
|
|
281
|
+
}
|
|
282
|
+
.ngmd-code-group__panel[data-active="true"] {
|
|
283
|
+
display: block;
|
|
284
|
+
}
|
|
285
|
+
.ngmd-code-group__panel pre {
|
|
286
|
+
margin: 0;
|
|
287
|
+
border-radius: 0;
|
|
288
|
+
border: 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Shiki dual-theme: github-light by default, github-dark under `.dark`.
|
|
292
|
+
* Shiki emits both --shiki-light and --shiki-dark CSS vars on every span
|
|
293
|
+
* but never picks a default color, so we have to wire both branches. */
|
|
294
|
+
/* Scoped to `.shiki-themes` (only present on dual-theme output, never on
|
|
295
|
+
* single-theme build-time output) so we don't override the inline colours
|
|
296
|
+
* shiki emits for the build-time `.md` code blocks. */
|
|
297
|
+
.shiki-themes,
|
|
298
|
+
.shiki-themes span {
|
|
299
|
+
color: var(--shiki-light);
|
|
300
|
+
background-color: var(--shiki-light-bg);
|
|
301
|
+
}
|
|
302
|
+
.dark .shiki-themes,
|
|
303
|
+
.dark .shiki-themes span {
|
|
304
|
+
color: var(--shiki-dark) !important;
|
|
305
|
+
background-color: var(--shiki-dark-bg) !important;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Highlighted lines (from `{1,3-5}` meta on a fenced block).
|
|
309
|
+
* Shiki wraps each line in `<span class="line">`; the ngmd-code-highlight
|
|
310
|
+
* marked extension adds `highlighted` to lines in the spec. We render a
|
|
311
|
+
* full-bleed tinted background plus a left-edge accent stripe. */
|
|
312
|
+
.shiki .line.highlighted {
|
|
313
|
+
display: inline-block;
|
|
314
|
+
width: 100%;
|
|
315
|
+
background-color: rgba(217, 70, 239, 0.12);
|
|
316
|
+
box-shadow: inset 2px 0 var(--accent);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* `<div class="ngmd-code-import">` wraps a file= imported code block with
|
|
320
|
+
* a header bar that links the file path to its GitHub source. */
|
|
321
|
+
.ngmd-code-import {
|
|
322
|
+
margin-block: 1.5rem;
|
|
323
|
+
border: 1px solid var(--border);
|
|
324
|
+
border-radius: var(--radius-lg);
|
|
325
|
+
overflow: hidden;
|
|
326
|
+
background: var(--bg);
|
|
327
|
+
}
|
|
328
|
+
.ngmd-code-import__header {
|
|
329
|
+
display: block;
|
|
330
|
+
padding: 0.5rem 1rem;
|
|
331
|
+
border-bottom: 1px solid var(--border);
|
|
332
|
+
background: var(--bg-muted);
|
|
333
|
+
font-family: var(--font-mono);
|
|
334
|
+
font-size: 0.8125rem;
|
|
335
|
+
color: var(--muted);
|
|
336
|
+
text-decoration: none;
|
|
337
|
+
transition: color 0.15s ease;
|
|
338
|
+
}
|
|
339
|
+
.ngmd-code-import__header:hover {
|
|
340
|
+
color: var(--accent);
|
|
341
|
+
}
|
|
342
|
+
.ngmd-code-import pre {
|
|
343
|
+
margin: 0;
|
|
344
|
+
border: 0;
|
|
345
|
+
border-radius: 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
2
|
+
{
|
|
3
|
+
"extends": "./tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "./out-tsc/app",
|
|
6
|
+
"types": []
|
|
7
|
+
},
|
|
8
|
+
"files": ["src/main.ts", "src/main.server.ts"],
|
|
9
|
+
"include": [
|
|
10
|
+
"src/**/*.d.ts",
|
|
11
|
+
"src/app/pages/**/*.page.ts",
|
|
12
|
+
"src/server/middleware/**/*.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compileOnSave": false,
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": "./",
|
|
5
|
+
"outDir": "./dist/out-tsc",
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noImplicitOverride": true,
|
|
9
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
10
|
+
"noImplicitReturns": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"declaration": false,
|
|
14
|
+
"downlevelIteration": true,
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"moduleResolution": "bundler",
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"importHelpers": true,
|
|
19
|
+
"target": "ES2022",
|
|
20
|
+
"module": "ES2022",
|
|
21
|
+
"lib": ["ES2022", "dom"],
|
|
22
|
+
"useDefineForClassFields": false,
|
|
23
|
+
"skipLibCheck": true
|
|
24
|
+
},
|
|
25
|
+
"angularCompilerOptions": {
|
|
26
|
+
"enableI18nLegacyMessageIdFormat": false,
|
|
27
|
+
"strictInjectionParameters": true,
|
|
28
|
+
"strictInputAccessModifiers": true,
|
|
29
|
+
"strictTemplates": true
|
|
30
|
+
},
|
|
31
|
+
"references": [
|
|
32
|
+
{ "path": "tsconfig.spec.json" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
2
|
+
{
|
|
3
|
+
"extends": "./tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "./out-tsc/spec",
|
|
6
|
+
"target": "es2022",
|
|
7
|
+
"types": ["node", "vitest/globals"]
|
|
8
|
+
},
|
|
9
|
+
"files": ["src/test-setup.ts"],
|
|
10
|
+
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/// <reference types="vitest" />
|
|
2
|
+
|
|
3
|
+
import { defineConfig, type Plugin } from 'vite';
|
|
4
|
+
import analog from '@analogjs/platform';
|
|
5
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { getBuildExtensions } from './src/marked-extensions';
|
|
8
|
+
import { pageMetaPlugin } from './page-meta.plugin';
|
|
9
|
+
import { internalLinkGuard } from './link-guard.plugin';
|
|
10
|
+
import { sitemapPlugin } from './sitemap.plugin';
|
|
11
|
+
import config from './src/ngmd.config';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build-time guard: errors when a markdown file in `src/content/` contains
|
|
15
|
+
* a raw HTML `<a href="http(s)://...">` without `target="_blank"`. Raw HTML
|
|
16
|
+
* anchors bypass the marked link renderer (which would add target=_blank
|
|
17
|
+
* automatically), so this catches external links that would silently open
|
|
18
|
+
* in the same tab.
|
|
19
|
+
*
|
|
20
|
+
* Lifted from the adev docs pipeline pattern.
|
|
21
|
+
*/
|
|
22
|
+
function externalLinkGuard(): Plugin {
|
|
23
|
+
return {
|
|
24
|
+
name: 'ngmd-external-link-guard',
|
|
25
|
+
enforce: 'pre',
|
|
26
|
+
transform(_code, id) {
|
|
27
|
+
if (!id.endsWith('.md')) return null;
|
|
28
|
+
const content = readFileSync(id.split('?')[0], 'utf8');
|
|
29
|
+
const anchorRe = /<a\b[^>]*href=["']https?:\/\/[^"']+["'][^>]*>/g;
|
|
30
|
+
const matches = content.match(anchorRe) ?? [];
|
|
31
|
+
for (const m of matches) {
|
|
32
|
+
if (!/target=["']_blank["']/.test(m)) {
|
|
33
|
+
this.error(
|
|
34
|
+
`[ngmd] External anchor in ${id} is missing target="_blank":\n ${m}\n` +
|
|
35
|
+
`Add target="_blank" rel="noopener noreferrer" so external links open in a new tab.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default defineConfig(async () => ({
|
|
45
|
+
build: {
|
|
46
|
+
target: ['es2020'],
|
|
47
|
+
},
|
|
48
|
+
resolve: {
|
|
49
|
+
mainFields: ['module'],
|
|
50
|
+
},
|
|
51
|
+
plugins: [
|
|
52
|
+
externalLinkGuard(),
|
|
53
|
+
internalLinkGuard(),
|
|
54
|
+
pageMetaPlugin({ repoUrl: config.site.githubUrl, branch: 'main' }),
|
|
55
|
+
sitemapPlugin({ siteUrl: config.site.url }),
|
|
56
|
+
analog({
|
|
57
|
+
content: {
|
|
58
|
+
highlighter: 'shiki',
|
|
59
|
+
markedOptions: {
|
|
60
|
+
extensions: await getBuildExtensions(),
|
|
61
|
+
},
|
|
62
|
+
shikiOptions: {
|
|
63
|
+
highlighter: {
|
|
64
|
+
additionalLangs: ['bash', 'md', 'json'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
tailwindcss(),
|
|
70
|
+
],
|
|
71
|
+
test: {
|
|
72
|
+
globals: true,
|
|
73
|
+
environment: 'jsdom',
|
|
74
|
+
setupFiles: ['src/test-setup.ts'],
|
|
75
|
+
include: ['**/*.spec.ts'],
|
|
76
|
+
reporters: ['default'],
|
|
77
|
+
},
|
|
78
|
+
}));
|