vitepress 1.0.0-beta.1 → 1.0.0-beta.2

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.
@@ -47,7 +47,4 @@ const { hasSidebar } = useSidebar()
47
47
  font-weight: 500;
48
48
  color: var(--vp-c-text-2);
49
49
  }
50
-
51
- .message { order: 2; }
52
- .copyright { order: 1; }
53
50
  </style>
@@ -1,8 +1,12 @@
1
1
  <script lang="ts" setup>
2
+ import { useWindowScroll } from '@vueuse/core'
3
+ import { computed, shallowRef } from 'vue'
4
+ import { onContentUpdated } from 'vitepress'
2
5
  import { useData } from '../composables/data'
6
+ import { getHeaders, type MenuItem } from '../composables/outline'
3
7
  import { useSidebar } from '../composables/sidebar'
4
- import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
5
8
  import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
9
+ import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
6
10
 
7
11
  defineProps<{
8
12
  open: boolean
@@ -14,10 +18,32 @@ defineEmits<{
14
18
 
15
19
  const { theme, frontmatter } = useData()
16
20
  const { hasSidebar } = useSidebar()
21
+ const { y } = useWindowScroll()
22
+
23
+ const headers = shallowRef<MenuItem[]>([])
24
+
25
+ onContentUpdated(() => {
26
+ headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
27
+ })
28
+
29
+ const empty = computed(() => {
30
+ return headers.value.length === 0 && !hasSidebar.value
31
+ })
32
+
33
+ const classes = computed(() => {
34
+ return {
35
+ VPLocalNav: true,
36
+ fixed: empty.value,
37
+ 'reached-top': y.value >= 64
38
+ }
39
+ })
17
40
  </script>
18
41
 
19
42
  <template>
20
- <div class="VPLocalNav" v-if="frontmatter.layout !== 'home'">
43
+ <div
44
+ v-if="frontmatter.layout !== 'home' && (!empty || y >= 64)"
45
+ :class="classes"
46
+ >
21
47
  <button
22
48
  v-if="hasSidebar"
23
49
  class="menu"
@@ -31,7 +57,7 @@ const { hasSidebar } = useSidebar()
31
57
  </span>
32
58
  </button>
33
59
 
34
- <VPLocalNavOutlineDropdown />
60
+ <VPLocalNavOutlineDropdown :headers="headers" />
35
61
  </div>
36
62
  </template>
37
63
 
@@ -45,11 +71,19 @@ const { hasSidebar } = useSidebar()
45
71
  display: flex;
46
72
  justify-content: space-between;
47
73
  align-items: center;
74
+ border-top: 1px solid var(--vp-c-gutter);
48
75
  border-bottom: 1px solid var(--vp-c-gutter);
49
76
  padding-top: var(--vp-layout-top-height, 0px);
50
77
  width: 100%;
51
78
  background-color: var(--vp-local-nav-bg-color);
52
- transition: border-color 0.5s, background-color 0.5s;
79
+ }
80
+
81
+ .VPLocalNav.fixed {
82
+ position: fixed;
83
+ }
84
+
85
+ .VPLocalNav.reached-top {
86
+ border-top-color: transparent;
53
87
  }
54
88
 
55
89
  @media (min-width: 960px) {
@@ -1,12 +1,16 @@
1
1
  <script setup lang="ts">
2
- import { ref, nextTick, shallowRef } from 'vue'
2
+ import { onContentUpdated } from 'vitepress'
3
+ import { nextTick, ref } from 'vue'
3
4
  import { useData } from '../composables/data'
4
- import { getHeaders, resolveTitle, type MenuItem } from '../composables/outline'
5
+ import { resolveTitle, type MenuItem } from '../composables/outline'
5
6
  import VPDocOutlineItem from './VPDocOutlineItem.vue'
6
- import { onContentUpdated } from 'vitepress'
7
7
  import VPIconChevronRight from './icons/VPIconChevronRight.vue'
8
8
 
9
- const { frontmatter, theme } = useData()
9
+ defineProps<{
10
+ headers: MenuItem[]
11
+ }>()
12
+
13
+ const { theme } = useData()
10
14
  const open = ref(false)
11
15
  const vh = ref(0)
12
16
  const items = ref<HTMLDivElement>()
@@ -36,14 +40,6 @@ function scrollToTop() {
36
40
  open.value = false
37
41
  window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
38
42
  }
39
-
40
- const headers = shallowRef<MenuItem[]>([])
41
-
42
- onContentUpdated(() => {
43
- headers.value = getHeaders(
44
- frontmatter.value.outline ?? theme.value.outline
45
- )
46
- })
47
43
  </script>
48
44
 
49
45
  <template>
@@ -119,8 +119,6 @@ watchEffect(() => {
119
119
 
120
120
  const results: Ref<(SearchResult & Result)[]> = shallowRef([])
121
121
 
122
- const headingRegex = /<h(\d*).*?>.*?<a.*? href="#(.*?)".*?>.*?<\/a><\/h\1>/gi
123
-
124
122
  const enableNoResults = ref(false)
125
123
 
126
124
  watch(filterText, () => {
@@ -155,8 +153,13 @@ debouncedWatch(
155
153
  if (canceled) return
156
154
  const c = new Map<string, Map<string, string>>()
157
155
  for (const { id, mod } of mods) {
156
+ const mapId = id.slice(0, id.indexOf('#'))
157
+ let map = c.get(mapId)
158
+ if (map) continue
159
+ map = new Map()
160
+ c.set(mapId, map)
158
161
  const comp = mod.default ?? mod
159
- if (comp?.render) {
162
+ if (comp?.render || comp?.setup) {
160
163
  const app = createApp(comp)
161
164
  // Silence warnings about missing components
162
165
  app.config.warnHandler = () => {}
@@ -175,20 +178,17 @@ debouncedWatch(
175
178
  })
176
179
  const div = document.createElement('div')
177
180
  app.mount(div)
178
- const sections = div.innerHTML.split(headingRegex)
181
+ const headings = div.querySelectorAll('h1, h2, h3, h4, h5, h6')
182
+ headings.forEach((el) => {
183
+ const href = el.querySelector('a')?.getAttribute('href')
184
+ const anchor = href?.startsWith('#') && href.slice(1)
185
+ if (!anchor) return
186
+ let html = ''
187
+ while ((el = el.nextElementSibling!) && !/^h[1-6]$/i.test(el.tagName))
188
+ html += el.outerHTML
189
+ map!.set(anchor, html)
190
+ })
179
191
  app.unmount()
180
- sections.shift()
181
- const mapId = id.slice(0, id.indexOf('#'))
182
- let map = c.get(mapId)
183
- if (!map) {
184
- map = new Map()
185
- c.set(mapId, map)
186
- }
187
- for (let i = 0; i < sections.length; i += 3) {
188
- const anchor = sections[i + 1]
189
- const html = sections[i + 2]
190
- map.set(anchor, html)
191
- }
192
192
  }
193
193
  if (canceled) return
194
194
  }
@@ -241,10 +241,12 @@ async function fetchExcerpt(id: string) {
241
241
  /* Search input focus */
242
242
 
243
243
  const searchInput = ref<HTMLInputElement>()
244
-
245
- function focusSearchInput() {
244
+ const disableReset = computed(() => {
245
+ return filterText.value?.length <= 0
246
+ })
247
+ function focusSearchInput(select = true) {
246
248
  searchInput.value?.focus()
247
- searchInput.value?.select()
249
+ select && searchInput.value?.select()
248
250
  }
249
251
 
250
252
  onMounted(() => {
@@ -259,11 +261,11 @@ function onSearchBarClick(event: PointerEvent) {
259
261
 
260
262
  /* Search keyboard selection */
261
263
 
262
- const selectedIndex = ref(0)
264
+ const selectedIndex = ref(-1)
263
265
  const disableMouseOver = ref(false)
264
266
 
265
- watch(results, () => {
266
- selectedIndex.value = 0
267
+ watch(results, (r) => {
268
+ selectedIndex.value = r.length ? 0 : -1
267
269
  scrollToSelectedResult()
268
270
  })
269
271
 
@@ -360,6 +362,11 @@ onBeforeUnmount(() => {
360
362
  isLocked.value = false
361
363
  })
362
364
 
365
+ function resetSearch() {
366
+ filterText.value = ''
367
+ nextTick().then(() => focusSearchInput(false))
368
+ }
369
+
363
370
  function formMarkRegex(terms: Set<string>) {
364
371
  return new RegExp(
365
372
  [...terms]
@@ -377,34 +384,44 @@ function formMarkRegex(terms: Set<string>) {
377
384
 
378
385
  <template>
379
386
  <Teleport to="body">
380
- <div ref="el" class="VPLocalSearchBox" aria-modal="true">
387
+ <div
388
+ ref="el"
389
+ role="button"
390
+ :aria-owns="results?.length ? 'localsearch-list' : undefined"
391
+ aria-expanded="true"
392
+ aria-haspopup="listbox"
393
+ aria-labelledby="localsearch-label"
394
+ class="VPLocalSearchBox"
395
+ >
381
396
  <div class="backdrop" @click="$emit('close')" />
382
397
 
383
398
  <div class="shell">
384
- <div class="search-bar" @pointerup="onSearchBarClick($event)">
385
- <svg
386
- class="search-icon"
387
- width="18"
388
- height="18"
389
- viewBox="0 0 24 24"
390
- aria-hidden="true"
391
- >
392
- <g
393
- fill="none"
394
- stroke="currentColor"
395
- stroke-linecap="round"
396
- stroke-linejoin="round"
397
- stroke-width="2"
399
+ <form class="search-bar" @pointerup="onSearchBarClick($event)" @submit.prevent="">
400
+ <label :title="placeholder" id="localsearch-label" for="localsearch-input">
401
+ <svg
402
+ class="search-icon"
403
+ width="18"
404
+ height="18"
405
+ viewBox="0 0 24 24"
406
+ aria-hidden="true"
398
407
  >
399
- <circle cx="11" cy="11" r="8" />
400
- <path d="m21 21l-4.35-4.35" />
401
- </g>
402
- </svg>
408
+ <g
409
+ fill="none"
410
+ stroke="currentColor"
411
+ stroke-linecap="round"
412
+ stroke-linejoin="round"
413
+ stroke-width="2"
414
+ >
415
+ <circle cx="11" cy="11" r="8" />
416
+ <path d="m21 21l-4.35-4.35" />
417
+ </g>
418
+ </svg>
419
+ </label>
403
420
  <div class="search-actions before">
404
421
  <button
405
422
  class="back-button"
406
423
  :title="$t('modal.backButtonTitle')"
407
- @click="$emit('close')"
424
+ @click="selectedIndex > -1 && $emit('close')"
408
425
  >
409
426
  <svg
410
427
  width="18"
@@ -427,6 +444,8 @@ function formMarkRegex(terms: Set<string>) {
427
444
  ref="searchInput"
428
445
  v-model="filterText"
429
446
  :placeholder="placeholder"
447
+ id="localsearch-input"
448
+ aria-labelledby="localsearch-label"
430
449
  class="search-input"
431
450
  />
432
451
  <div class="search-actions">
@@ -435,7 +454,7 @@ function formMarkRegex(terms: Set<string>) {
435
454
  class="toggle-layout-button"
436
455
  :class="{ 'detailed-list': showDetailedList }"
437
456
  :title="$t('modal.displayDetails')"
438
- @click="showDetailedList = !showDetailedList"
457
+ @click="selectedIndex > -1 && (showDetailedList = !showDetailedList)"
439
458
  >
440
459
  <svg
441
460
  width="18"
@@ -456,8 +475,10 @@ function formMarkRegex(terms: Set<string>) {
456
475
 
457
476
  <button
458
477
  class="clear-button"
478
+ type="reset"
479
+ :disabled="disableReset"
459
480
  :title="$t('modal.resetButtonTitle')"
460
- @click="filterText = ''"
481
+ @click="resetSearch"
461
482
  >
462
483
  <svg
463
484
  width="18"
@@ -476,65 +497,72 @@ function formMarkRegex(terms: Set<string>) {
476
497
  </svg>
477
498
  </button>
478
499
  </div>
479
- </div>
500
+ </form>
480
501
 
481
- <div
502
+ <ul
482
503
  ref="resultsEl"
504
+ :id="results?.length ? 'localsearch-list' : undefined"
505
+ :role="results?.length ? 'listbox' : undefined"
506
+ :aria-labelledby="results?.length ? 'localsearch-label' : undefined"
483
507
  class="results"
484
508
  @mousemove="disableMouseOver = false"
485
509
  >
486
- <a
510
+ <li
487
511
  v-for="(p, index) in results"
488
512
  :key="p.id"
489
- :href="p.id"
490
- class="result"
491
- :class="{
492
- selected: selectedIndex === index
493
- }"
494
- :aria-label="[...p.titles, p.title].join(' > ')"
495
- @mouseenter="!disableMouseOver && (selectedIndex = index)"
496
- @focusin="selectedIndex = index"
497
- @click="$emit('close')"
513
+ role="option"
514
+ :aria-selected="selectedIndex === index ? 'true' : 'false'"
498
515
  >
499
- <div>
500
- <div class="titles">
501
- <span class="title-icon">#</span>
502
- <span v-for="(t, index) in p.titles" :key="index" class="title">
503
- <span class="text" v-html="t" />
504
- <svg width="18" height="18" viewBox="0 0 24 24">
505
- <path
506
- fill="none"
507
- stroke="currentColor"
508
- stroke-linecap="round"
509
- stroke-linejoin="round"
510
- stroke-width="2"
511
- d="m9 18l6-6l-6-6"
512
- />
513
- </svg>
514
- </span>
515
- <span class="title main">
516
- <span class="text" v-html="p.title" />
517
- </span>
518
- </div>
516
+ <a
517
+ :href="p.id"
518
+ class="result"
519
+ :class="{
520
+ selected: selectedIndex === index
521
+ }"
522
+ :aria-label="[...p.titles, p.title].join(' > ')"
523
+ @mouseenter="!disableMouseOver && (selectedIndex = index)"
524
+ @focusin="selectedIndex = index"
525
+ @click="$emit('close')"
526
+ >
527
+ <div>
528
+ <div class="titles">
529
+ <span class="title-icon">#</span>
530
+ <span v-for="(t, index) in p.titles" :key="index" class="title">
531
+ <span class="text" v-html="t" />
532
+ <svg width="18" height="18" viewBox="0 0 24 24">
533
+ <path
534
+ fill="none"
535
+ stroke="currentColor"
536
+ stroke-linecap="round"
537
+ stroke-linejoin="round"
538
+ stroke-width="2"
539
+ d="m9 18l6-6l-6-6"
540
+ />
541
+ </svg>
542
+ </span>
543
+ <span class="title main">
544
+ <span class="text" v-html="p.title" />
545
+ </span>
546
+ </div>
519
547
 
520
- <div v-if="showDetailedList" class="excerpt-wrapper">
521
- <div v-if="p.text" class="excerpt" inert>
522
- <div class="vp-doc" v-html="p.text" />
548
+ <div v-if="showDetailedList" class="excerpt-wrapper">
549
+ <div v-if="p.text" class="excerpt" inert>
550
+ <div class="vp-doc" v-html="p.text" />
551
+ </div>
552
+ <div class="excerpt-gradient-bottom" />
553
+ <div class="excerpt-gradient-top" />
523
554
  </div>
524
- <div class="excerpt-gradient-bottom" />
525
- <div class="excerpt-gradient-top" />
526
555
  </div>
527
- </div>
528
- </a>
529
-
530
- <div
556
+ </a>
557
+ </li>
558
+ <li
531
559
  v-if="filterText && !results.length && enableNoResults"
532
560
  class="no-results"
533
561
  >
534
562
  {{ $t('modal.noResultsText') }} "<strong>{{ filterText }}</strong
535
563
  >"
536
- </div>
537
- </div>
564
+ </li>
565
+ </ul>
538
566
 
539
567
  <div class="search-keyboard-shortcuts">
540
568
  <span>
@@ -692,11 +720,15 @@ function formMarkRegex(terms: Set<string>) {
692
720
  padding: 8px;
693
721
  }
694
722
 
695
- .search-actions button:hover,
723
+ .search-actions button:not([disabled]):hover,
696
724
  .toggle-layout-button.detailed-list {
697
725
  color: var(--vp-c-brand);
698
726
  }
699
727
 
728
+ .search-actions button.clear-button:disabled {
729
+ opacity: 0.37;
730
+ }
731
+
700
732
  .search-keyboard-shortcuts {
701
733
  font-size: 0.8rem;
702
734
  opacity: 75%;
@@ -62,15 +62,10 @@ const classes = computed(() => ({
62
62
  border-bottom: 1px solid transparent;
63
63
  padding: 0 8px 0 24px;
64
64
  height: var(--vp-nav-height);
65
- transition: border-color 0.5s, background-color 0.5s;
66
65
  pointer-events: none;
67
66
  white-space: nowrap;
68
67
  }
69
68
 
70
- .VPNavBar.has-sidebar {
71
- border-bottom-color: var(--vp-c-gutter);
72
- }
73
-
74
69
  @media (min-width: 768px) {
75
70
  .VPNavBar {
76
71
  padding: 0 32px;
@@ -79,7 +74,6 @@ const classes = computed(() => ({
79
74
 
80
75
  @media (min-width: 960px) {
81
76
  .VPNavBar.has-sidebar {
82
- border-bottom-color: transparent;
83
77
  padding: 0;
84
78
  }
85
79
 
@@ -59,10 +59,6 @@ function focusOnTargetAnchor({ target }: Event) {
59
59
  clip-path: none;
60
60
  }
61
61
 
62
- .dark .VPSkipLink {
63
- color: var(--vp-c-green);
64
- }
65
-
66
62
  @media (min-width: 1280px) {
67
63
  .VPSkipLink {
68
64
  top: 14px;
@@ -6,6 +6,7 @@ import { icons } from '../support/socialIcons'
6
6
  const props = defineProps<{
7
7
  icon: DefaultTheme.SocialLinkIcon
8
8
  link: string
9
+ ariaLabel?: string
9
10
  }>()
10
11
 
11
12
  const svg = computed(() => {
@@ -18,7 +19,7 @@ const svg = computed(() => {
18
19
  <a
19
20
  class="VPSocialLink"
20
21
  :href="link"
21
- :aria-label="typeof icon === 'string' ? icon : ''"
22
+ :aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')"
22
23
  target="_blank"
23
24
  rel="noopener"
24
25
  v-html="svg"
@@ -10,10 +10,11 @@ defineProps<{
10
10
  <template>
11
11
  <div class="VPSocialLinks">
12
12
  <VPSocialLink
13
- v-for="{ link, icon } in links"
13
+ v-for="{ link, icon, ariaLabel } in links"
14
14
  :key="link"
15
15
  :icon="icon"
16
16
  :link="link"
17
+ :ariaLabel="ariaLabel"
17
18
  />
18
19
  </div>
19
20
  </template>
@@ -1,14 +1,14 @@
1
1
  <script lang="ts" setup>
2
2
  import { ref, onMounted, watch } from 'vue'
3
3
  import { useData } from '../composables/data'
4
- import { APPEARANCE_KEY } from '../../shared'
4
+ import { inBrowser, APPEARANCE_KEY } from '../../shared'
5
5
  import VPSwitch from './VPSwitch.vue'
6
6
  import VPIconSun from './icons/VPIconSun.vue'
7
7
  import VPIconMoon from './icons/VPIconMoon.vue'
8
8
 
9
9
  const { site, isDark } = useData()
10
10
  const checked = ref(false)
11
- const toggle = typeof localStorage !== 'undefined' ? useAppearance() : () => {}
11
+ const toggle = inBrowser ? useAppearance() : () => {}
12
12
 
13
13
  onMounted(() => {
14
14
  checked.value = document.documentElement.classList.contains('dark')
@@ -1,5 +1,5 @@
1
1
  import { onMounted, onUnmounted, onUpdated } from 'vue';
2
- import { useAside } from '../composables/aside';
2
+ import { useAside } from './aside';
3
3
  import { throttleAndDebounce } from '../support/utils';
4
4
  // magic number to avoid repeated retrieval
5
5
  const PAGE_OFFSET = 71;
@@ -10,8 +10,12 @@ export function usePrevNext() {
10
10
  const index = candidates.findIndex((link) => {
11
11
  return isActive(page.value.relativePath, link.link);
12
12
  });
13
+ const hidePrev = (theme.value.docFooter?.prev === false && !frontmatter.value.prev) ||
14
+ frontmatter.value.prev === false;
15
+ const hideNext = (theme.value.docFooter?.next === false && !frontmatter.value.next) ||
16
+ frontmatter.value.next === false;
13
17
  return {
14
- prev: frontmatter.value.prev === false
18
+ prev: hidePrev
15
19
  ? undefined
16
20
  : {
17
21
  text: (typeof frontmatter.value.prev === 'string'
@@ -23,7 +27,7 @@ export function usePrevNext() {
23
27
  ? frontmatter.value.prev.link
24
28
  : undefined) ?? candidates[index - 1]?.link
25
29
  },
26
- next: frontmatter.value.next === false
30
+ next: hideNext
27
31
  ? undefined
28
32
  : {
29
33
  text: (typeof frontmatter.value.next === 'string'
@@ -56,7 +56,7 @@
56
56
  right: 8px;
57
57
  bottom: -1px;
58
58
  left: 8px;
59
- z-index: 10;
59
+ z-index: 1;
60
60
  height: 1px;
61
61
  content: '';
62
62
  background-color: transparent;
package/dist/node/cli.js CHANGED
@@ -1,30 +1,32 @@
1
- import { n as getDefaultExportFromCjs, o as c, j as build, s as serve, k as init, i as createServer } from './serve-d5608de4.js';
1
+ import { o as getDefaultExportFromCjs, p as c, q as clearCache, k as build, s as serve, l as init, j as createServer } from './serve-9050e258.js';
2
2
  import { createLogger } from 'vite';
3
3
  import 'url';
4
- import 'fs';
5
4
  import 'path';
6
5
  import 'dns';
6
+ import 'module';
7
7
  import 'node:process';
8
8
  import 'assert';
9
9
  import 'events';
10
10
  import 'node:readline';
11
11
  import 'stream';
12
12
  import 'buffer';
13
+ import 'node:path';
14
+ import 'node:url';
15
+ import 'node:fs';
16
+ import 'util';
17
+ import 'fs';
18
+ import 'fs/promises';
19
+ import 'string_decoder';
20
+ import 'os';
13
21
  import 'crypto';
14
22
  import 'shiki';
15
23
  import 'child_process';
16
- import 'node:path';
17
24
  import 'minisearch';
18
- import 'module';
19
- import 'node:url';
20
- import 'node:fs';
21
25
  import 'tty';
22
- import 'util';
23
26
  import 'net';
24
27
  import 'zlib';
25
28
  import 'http';
26
29
  import 'querystring';
27
- import 'os';
28
30
  import 'node:tty';
29
31
  import 'constants';
30
32
 
@@ -292,9 +294,9 @@ var minimist = function (args, opts) {
292
294
 
293
295
  var minimist$1 = /*@__PURE__*/getDefaultExportFromCjs(minimist);
294
296
 
295
- var version = "1.0.0-beta.1";
297
+ var version = "1.0.0-beta.2";
296
298
 
297
- function bindShortcuts(server) {
299
+ function bindShortcuts(server, createDevServer) {
298
300
  if (!server.httpServer || !process.stdin.isTTY || process.env.CI) {
299
301
  return;
300
302
  }
@@ -324,7 +326,7 @@ function bindShortcuts(server) {
324
326
  if (!shortcut)
325
327
  return;
326
328
  actionRunning = true;
327
- await shortcut.action(server);
329
+ await shortcut.action(server, createDevServer);
328
330
  actionRunning = false;
329
331
  };
330
332
  process.stdin.setRawMode(true);
@@ -334,6 +336,20 @@ function bindShortcuts(server) {
334
336
  });
335
337
  }
336
338
  const SHORTCUTS = [
339
+ {
340
+ key: "r",
341
+ description: "restart the server",
342
+ async action(server, createDevServer) {
343
+ server.config.logger.info(c.green(`restarting server...
344
+ `), {
345
+ clear: true,
346
+ timestamp: true
347
+ });
348
+ clearCache();
349
+ await server.close();
350
+ await createDevServer();
351
+ }
352
+ },
337
353
  {
338
354
  key: "u",
339
355
  description: "show server url",
@@ -391,7 +407,7 @@ if (!command || command === "dev") {
391
407
  await server.listen();
392
408
  logVersion(server.config.logger);
393
409
  server.printUrls();
394
- bindShortcuts(server);
410
+ bindShortcuts(server, createDevServer);
395
411
  };
396
412
  createDevServer().catch((err) => {
397
413
  createLogger().error(