@levino/shipyard-base 0.9.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levino/shipyard-base",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Core layouts, components, and configuration for shipyard - a composable page builder for Astro",
5
5
  "keywords": [
6
6
  "astro",
@@ -67,9 +67,11 @@
67
67
  "@astrojs/markdown-remark": "^7.2.4",
68
68
  "clsx": "^2.1.0",
69
69
  "mdast-util-directive": "^3.0.0",
70
+ "mdast-util-to-hast": "^13.2.1",
70
71
  "micromark-extension-directive": "^3.0.0",
71
72
  "ramda": "^0.32.0",
72
73
  "remark-directive": "^3.0.0",
74
+ "remark-parse": "^11.0.0",
73
75
  "tailwind-merge": "^2.2.0",
74
76
  "unified": "^11.0.0",
75
77
  "unist-util-visit": "^5.0.0"
package/src/astro.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * as components from './astro/components'
2
- export * as layouts from './astro/layouts'
1
+ export * as components from '../astro/components'
2
+ export * as layouts from '../astro/layouts'
@@ -16,6 +16,7 @@ interface ConfigUpdate {
16
16
  const baseConfig: Config = {
17
17
  title: 'Test',
18
18
  brand: 'Test',
19
+ tagline: 'Test',
19
20
  navigation: {},
20
21
  }
21
22
 
@@ -83,7 +84,7 @@ describe('markdown processor', () => {
83
84
  })
84
85
 
85
86
  expect(logger.warn).toHaveBeenCalledOnce()
86
- expect(logger.warn.mock.calls[0][0]).toContain('satteri')
87
+ expect(logger.warn.mock.calls[0]?.[0]).toContain('satteri')
87
88
  expect(update?.markdown?.processor?.name).toBe(unified().name)
88
89
  expect(update?.markdown?.processor?.options.remarkPlugins).toHaveLength(
89
90
  SHIPYARD_PLUGIN_COUNT,
@@ -1,5 +1,9 @@
1
1
  import type { Root } from 'mdast'
2
2
  import type { ContainerDirective } from 'mdast-util-directive'
3
+ // `mdast-util-to-hast` augments mdast's `Data` with `hName`/`hProperties`, which
4
+ // this plugin sets. Imported explicitly so the augmentation is present when a
5
+ // consumer compiles this file, not just via a transitive dev dependency.
6
+ import type {} from 'mdast-util-to-hast'
3
7
  import { includes, pipe, toLower } from 'ramda'
4
8
  import type { Plugin } from 'unified'
5
9
  import { SKIP, visit } from 'unist-util-visit'
@@ -67,7 +71,11 @@ export const remarkAdmonitions: Plugin<[], Root> = () => {
67
71
  }
68
72
 
69
73
  const type = toLower(directiveName) as AdmonitionType
70
- const title = node.label ?? getDefaultTitle(type)
74
+ // NOTE: container directives carry no `label` field. `:::note[Custom]`
75
+ // is parsed by mdast-util-directive into a first child paragraph with
76
+ // `data.directiveLabel === true`, so custom titles are not picked up
77
+ // here and the default title is used for every admonition.
78
+ const title = getDefaultTitle(type)
71
79
 
72
80
  // Set up the data for hast transformation
73
81
  if (!node.data) {
@@ -1,6 +1,11 @@
1
1
  import type { Root } from 'mdast'
2
2
  import { directiveFromMarkdown } from 'mdast-util-directive'
3
3
  import { directive } from 'micromark-extension-directive'
4
+ // `remark-parse` augments unified's `Data` with `micromarkExtensions` and
5
+ // `fromMarkdownExtensions`. shipyard ships raw TypeScript source, so a consumer
6
+ // compiles this file in their own program — importing the augmentation here
7
+ // keeps it available there instead of relying on a transitive dev dependency.
8
+ import type {} from 'remark-parse'
4
9
  import type { Plugin, Processor } from 'unified'
5
10
 
6
11
  /**
@@ -1,3 +1,8 @@
1
+ // `astroHTML.JSX` is a global namespace declared by Astro. It is only in scope
2
+ // for files that pull in Astro's JSX types, which `.ts` files in this package
3
+ // do not do implicitly. The reference is erased at compile time.
4
+ /// <reference types="astro/astro-jsx" />
5
+
1
6
  export interface NavigationEntry {
2
7
  label?: string
3
8
  href?: string
@@ -3,6 +3,7 @@
3
3
  * Supports Docusaurus-style features like line numbers, line highlighting, and code block titles.
4
4
  */
5
5
 
6
+ import type { ElementContent } from 'hast'
6
7
  import {
7
8
  complement,
8
9
  filter,
@@ -29,7 +30,9 @@ const parseLineRange = (rangeStr: string): number[] => {
29
30
  const parseSegment = (segment: string): number[] => {
30
31
  const trimmed = trim(segment)
31
32
  if (test(/-/, trimmed)) {
32
- const [start, end] = split('-', trimmed).map(Number)
33
+ const [startStr, endStr] = split('-', trimmed)
34
+ const start = Number(startStr)
35
+ const end = Number(endStr)
33
36
  return Array.from({ length: end - start + 1 }, (_, i) => start + i)
34
37
  }
35
38
  return [Number(trimmed)]
@@ -79,7 +82,7 @@ const extractCodeMeta = (meta: string | undefined): CodeMeta => {
79
82
  const startLineNumber = startLineMatch[1] ? Number(startLineMatch[1]) : 1
80
83
 
81
84
  return {
82
- title,
85
+ ...(isNil(title) ? {} : { title }),
83
86
  highlightLines,
84
87
  showLineNumbers,
85
88
  startLineNumber,
@@ -198,7 +201,7 @@ export const transformerMagicComments = (): ShikiTransformer => {
198
201
  const lines = node.children.filter((child) => child.type === 'element')
199
202
  lines.forEach((lineNode, index) => {
200
203
  // Extract text content from line
201
- const getText = (n: typeof lineNode): string => {
204
+ const getText = (n: ElementContent): string => {
202
205
  if (n.type === 'text') return n.value
203
206
  if (n.type === 'element' && n.children) {
204
207
  return n.children.map(getText).join('')
@@ -44,8 +44,8 @@ describe('checkLinks', () => {
44
44
  const result = checkLinks(testDir)
45
45
 
46
46
  expect(result.brokenCount).toBe(1)
47
- expect(result.brokenLinks[0].href).toBe('/non-existent')
48
- expect(result.brokenLinks[0].sourceFile).toBe('index.html')
47
+ expect(result.brokenLinks[0]?.href).toBe('/non-existent')
48
+ expect(result.brokenLinks[0]?.sourceFile).toBe('index.html')
49
49
  })
50
50
 
51
51
  test('ignores external links', () => {
@@ -163,7 +163,7 @@ describe('checkLinks', () => {
163
163
 
164
164
  const result = checkLinks(testDir)
165
165
 
166
- expect(result.brokenLinks[0].line).toBe(2)
166
+ expect(result.brokenLinks[0]?.line).toBe(2)
167
167
  })
168
168
  })
169
169
 
@@ -221,7 +221,7 @@ describe('reportBrokenLinks', () => {
221
221
  reportBrokenLinks(result, 'log', logger as any)
222
222
 
223
223
  expect(logger.info).toHaveBeenCalled()
224
- const message = logger.info.mock.calls[0][0]
224
+ const message = logger.info.mock.calls[0]?.[0]
225
225
  expect(message).toContain('Found 1 broken link')
226
226
  expect(message).toContain('/broken')
227
227
  expect(message).toContain('index.html:1')
@@ -238,7 +238,7 @@ describe('reportBrokenLinks', () => {
238
238
  reportBrokenLinks(result, 'warn', logger as any)
239
239
 
240
240
  expect(logger.warn).toHaveBeenCalled()
241
- const message = logger.warn.mock.calls[0][0]
241
+ const message = logger.warn.mock.calls[0]?.[0]
242
242
  expect(message).toContain('Found 1 broken link')
243
243
  })
244
244
 
@@ -269,7 +269,7 @@ describe('reportBrokenLinks', () => {
269
269
 
270
270
  reportBrokenLinks(result, 'warn', logger as any)
271
271
 
272
- const message = logger.warn.mock.calls[0][0]
272
+ const message = logger.warn.mock.calls[0]?.[0]
273
273
  expect(message).toContain('Found 3 broken links')
274
274
  })
275
275
  })
@@ -52,16 +52,15 @@ function extractInternalLinks(html: string): { href: string; line: number }[] {
52
52
 
53
53
  const hrefRegex = /href=["']([^"']+)["']/g
54
54
 
55
- for (let i = 0; i < lines.length; i++) {
56
- const line = lines[i]
57
-
55
+ for (const [index, line] of lines.entries()) {
58
56
  for (const match of line.matchAll(hrefRegex)) {
59
57
  const href = match[1]
60
58
 
61
59
  // Only include internal links (start with /)
62
60
  // This automatically excludes external URLs, anchors, and special protocols
63
- if (href.startsWith('/')) {
64
- links.push({ href, line: i + 1 })
61
+ // (the capture group is mandatory, so `href` is always a string here)
62
+ if (href?.startsWith('/')) {
63
+ links.push({ href, line: index + 1 })
65
64
  }
66
65
  }
67
66
  }
@@ -75,7 +74,10 @@ function extractInternalLinks(html: string): { href: string; line: number }[] {
75
74
  */
76
75
  function normalizePath(href: string): string[] {
77
76
  // Remove query string and hash, and decode URL-encoded characters
78
- const path = decodeURIComponent(href.split('?')[0].split('#')[0])
77
+ // (`split` always yields at least one element, so the defaults never apply)
78
+ const [beforeQuery = ''] = href.split('?')
79
+ const [pathname = ''] = beforeQuery.split('#')
80
+ const path = decodeURIComponent(pathname)
79
81
 
80
82
  // Generate possible file paths to check
81
83
  const paths: string[] = []