@publish.os/nextjs 0.0.1-alpha.91 → 0.0.1-alpha.92

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.
Files changed (153) hide show
  1. package/compiler-assets/nextjs/defaults/group/index.tsx +16 -11
  2. package/compiler-assets/nextjs/defaults/group/layout.tsx +6 -2
  3. package/compiler-assets/nextjs/defaults/group/page.tsx +7 -6
  4. package/compiler-assets/nextjs/defaults/project/next.config.mjs +2 -1
  5. package/compiler-assets/nextjs/defaults/root/index.tsx +16 -11
  6. package/compiler-assets/nextjs/defaults/root/layout.tsx +7 -3
  7. package/compiler-assets/nextjs/shims/agent-user-agents.ts +65 -0
  8. package/compiler-assets/nextjs/shims/document.tsx +9 -1
  9. package/compiler-assets/nextjs/shims/generated-leaf-page.tsx +3 -0
  10. package/compiler-assets/nextjs/shims/proxy.ts +156 -54
  11. package/dist/{build-operation-5QMLXF7N.js → build-operation-CJDUL4OW.js} +5 -5
  12. package/dist/{chunk-GBPLER5H.js → chunk-3T4HVDSC.js} +2 -2
  13. package/dist/{chunk-UYGCOWRF.js → chunk-DX7PXFCM.js} +860 -50
  14. package/dist/{chunk-F3AGNDF4.js → chunk-DZ3VWEV5.js} +860 -575
  15. package/dist/{chunk-OXOFRXEI.js → chunk-NHSLTARU.js} +1284 -1164
  16. package/dist/{chunk-ORIUWJBQ.js → chunk-SBMDONTV.js} +3 -2
  17. package/dist/compiler.js +42 -23
  18. package/dist/components/blocks/agent-actions.d.ts +7 -7
  19. package/dist/components/blocks/agent-actions.js +1 -32
  20. package/dist/components/blocks/toc.d.ts +7 -7
  21. package/dist/components/ui/button.d.ts +1 -2
  22. package/dist/{compose-CVBJRAF6.js → compose-44GJSMXD.js} +2 -2
  23. package/dist/config/createNextConfig.js +2 -0
  24. package/dist/{dev-operation-OSHHWRWK.js → dev-operation-WNECBHZ5.js} +17 -7
  25. package/dist/{ide-support-CU6Z5PVR.js → ide-support-TG74L4FF.js} +3 -3
  26. package/dist/index.d.ts +143 -126
  27. package/dist/index.js +25 -0
  28. package/dist/kernel/components/blocks/AgentActions.d.ts +7 -7
  29. package/dist/kernel/components/blocks/AgentActions.js +1 -32
  30. package/dist/kernel/components/blocks/agent-actions.d.ts +1 -2
  31. package/dist/kernel/components/blocks/agent-actions.js +1 -32
  32. package/dist/kernel/components/blocks/animated-icon-swap.d.ts +2 -2
  33. package/dist/kernel/components/blocks/color-palette.d.ts +2 -2
  34. package/dist/kernel/components/blocks/logo-block.d.ts +2 -2
  35. package/dist/kernel/components/blocks/nav-breadcrumb.d.ts +6 -5
  36. package/dist/kernel/components/blocks/nav-breadcrumb.js +19 -13
  37. package/dist/kernel/components/blocks/navbar.d.ts +5 -4
  38. package/dist/kernel/components/blocks/navbar.js +62 -42
  39. package/dist/kernel/components/blocks/navigation-progress.d.ts +2 -2
  40. package/dist/kernel/components/blocks/next-prev.d.ts +4 -4
  41. package/dist/kernel/components/blocks/next-prev.js +29 -6
  42. package/dist/kernel/components/blocks/page-list.d.ts +4 -4
  43. package/dist/kernel/components/blocks/page-list.js +30 -7
  44. package/dist/kernel/components/blocks/search.d.ts +9 -15
  45. package/dist/kernel/components/blocks/sidebar-node.d.ts +3 -5
  46. package/dist/kernel/components/blocks/sidebar-node.js +12 -2
  47. package/dist/kernel/components/blocks/sidebar.d.ts +6 -7
  48. package/dist/kernel/components/blocks/sidebar.js +132 -26
  49. package/dist/kernel/components/blocks/theme-toggle.d.ts +3 -4
  50. package/dist/kernel/components/blocks/toc-client.d.ts +7 -7
  51. package/dist/kernel/components/blocks/toc.d.ts +3 -3
  52. package/dist/kernel/components/mdx/components/blockquote.d.ts +2 -2
  53. package/dist/kernel/components/mdx/components/callout.d.ts +3 -3
  54. package/dist/kernel/components/mdx/components/code-block.d.ts +2 -2
  55. package/dist/kernel/components/mdx/components/copy-code-button.d.ts +2 -2
  56. package/dist/kernel/components/mdx/components/figure-caption.d.ts +2 -2
  57. package/dist/kernel/components/mdx/components/figure.d.ts +3 -3
  58. package/dist/kernel/components/mdx/components/footnotes.d.ts +2 -2
  59. package/dist/kernel/components/mdx/components/heading-copy-link.d.ts +2 -2
  60. package/dist/kernel/components/mdx/components/heading.d.ts +2 -2
  61. package/dist/kernel/components/mdx/components/hr.d.ts +4 -4
  62. package/dist/kernel/components/mdx/components/image-zoom.d.ts +3 -3
  63. package/dist/kernel/components/mdx/components/image-zoom.js +3 -2
  64. package/dist/kernel/components/mdx/components/image.d.ts +2 -2
  65. package/dist/kernel/components/mdx/components/image.js +1 -1
  66. package/dist/kernel/components/mdx/components/inline-code.d.ts +2 -2
  67. package/dist/kernel/components/mdx/components/kbd.d.ts +3 -3
  68. package/dist/kernel/components/mdx/components/link.d.ts +3 -3
  69. package/dist/kernel/components/mdx/components/paragraph.d.ts +2 -2
  70. package/dist/kernel/components/mdx/components/strikethrough.d.ts +2 -2
  71. package/dist/kernel/components/mdx/components/table.d.ts +7 -7
  72. package/dist/kernel/components/mdx/components/task-list.d.ts +3 -3
  73. package/dist/kernel/components/mdx/components/video-player.d.ts +2 -2
  74. package/dist/kernel/components/mdx/components/video.d.ts +2 -3
  75. package/dist/kernel/components/mdx/error-boundary.d.ts +2 -3
  76. package/dist/kernel/components/mdx/error-fallback.d.ts +2 -2
  77. package/dist/kernel/components/mdx/mdx.d.ts +42 -11
  78. package/dist/kernel/components/mdx/mdx.js +1 -1
  79. package/dist/kernel/components/next-image.d.ts +3 -3
  80. package/dist/kernel/components/ui/accordion.d.ts +7 -7
  81. package/dist/kernel/components/ui/breadcrumb-trail.d.ts +2 -3
  82. package/dist/kernel/components/ui/breadcrumb.d.ts +13 -13
  83. package/dist/kernel/components/ui/button-group.d.ts +5 -5
  84. package/dist/kernel/components/ui/button.d.ts +3 -3
  85. package/dist/kernel/components/ui/card.d.ts +7 -7
  86. package/dist/kernel/components/ui/dialog.d.ts +10 -10
  87. package/dist/kernel/components/ui/drawer.d.ts +12 -12
  88. package/dist/kernel/components/ui/kbd.d.ts +3 -3
  89. package/dist/kernel/components/ui/link.d.ts +3 -3
  90. package/dist/kernel/components/ui/menu.d.ts +22 -22
  91. package/dist/kernel/components/ui/mod-key.d.ts +2 -3
  92. package/dist/kernel/components/ui/navigation-menu.d.ts +15 -15
  93. package/dist/kernel/components/ui/popover.d.ts +11 -11
  94. package/dist/kernel/components/ui/progress.d.ts +7 -7
  95. package/dist/kernel/components/ui/scroll-area.d.ts +1 -2
  96. package/dist/kernel/components/ui/select.d.ts +16 -16
  97. package/dist/kernel/components/ui/separator.d.ts +3 -3
  98. package/dist/kernel/components/ui/table.d.ts +7 -7
  99. package/dist/kernel/components/ui/tabs.d.ts +12 -12
  100. package/dist/kernel/components/ui/tag.d.ts +3 -3
  101. package/dist/kernel/components/ui/text.d.ts +4 -4
  102. package/dist/kernel/components/ui/toast.d.ts +11 -11
  103. package/dist/kernel/components/ui/tooltip.d.ts +9 -9
  104. package/dist/kernel/defaults.registry.d.ts +2 -2
  105. package/dist/kernel/defaults.registry.js +2 -2
  106. package/dist/kernel/routes/agent/markdown.d.ts +4 -0
  107. package/dist/kernel/routes/agent/markdown.js +11 -7
  108. package/dist/kernel/routes/layouts/group.d.ts +1 -1
  109. package/dist/kernel/routes/metadata/feed.d.ts +1 -2
  110. package/dist/kernel/routes/metadata/feed.js +34 -29
  111. package/dist/kernel/routes/metadata/llms.d.ts +3 -31
  112. package/dist/kernel/routes/metadata/llms.js +2 -200
  113. package/dist/kernel/routes/metadata/robots.d.ts +5 -3
  114. package/dist/kernel/routes/metadata/robots.js +4 -25
  115. package/dist/kernel/routes/metadata/sitemap.d.ts +15 -1
  116. package/dist/kernel/routes/metadata/sitemap.js +200 -112
  117. package/dist/kernel/routes/not-found.js +1 -1
  118. package/dist/kernel/routes/pages/group.d.ts +3 -3
  119. package/dist/kernel/routes/pages/group.js +10 -4
  120. package/dist/kernel/routes/pages/leaf.d.ts +9 -4
  121. package/dist/kernel/routes/pages/leaf.js +16 -8
  122. package/dist/kernel/routes/pages/root.d.ts +3 -3
  123. package/dist/kernel/routes/pages/root.js +20 -5
  124. package/dist/keyboard-shortcuts.d.ts +2 -2
  125. package/dist/manifest-types-CcVPH_jh.d.ts +83 -0
  126. package/dist/resolvers/manifest-resolver.d.ts +2 -2
  127. package/dist/resolvers/manifest-resolver.js +65 -35
  128. package/dist/search.d.ts +2 -9
  129. package/dist/search.js +2 -7
  130. package/dist/sidebar-types-CMrMM0Nj.d.ts +105 -0
  131. package/dist/theme-scope.d.ts +2 -2
  132. package/dist/types-BbqmOzLC.d.ts +168 -0
  133. package/dist/types-TjH1ek0X.d.ts +22 -0
  134. package/dist/types.d.ts +116 -91
  135. package/dist/{validate-local-source-SV44DBQT.js → validate-local-source-EN2GW4FR.js} +3 -3
  136. package/dist/{validate-publish-os-source-NZJF3TB2.js → validate-publish-os-source-OEWQDB7B.js} +2 -2
  137. package/init/seed/minimal/publish.os/blog/index.tsx +25 -22
  138. package/init/seed/minimal/publish.os/blog/page.tsx +31 -16
  139. package/init/seed/minimal/publish.os/components/blog/copy-link.tsx +1 -1
  140. package/init/seed/minimal/publish.os/components/blog/frontmatter.ts +41 -0
  141. package/init/seed/minimal/publish.os/publish.os.config.json +13 -2
  142. package/init/seed/minimal/seed.json +1 -0
  143. package/init/seed/scratch/publish.os/publish.os.config.json +1 -1
  144. package/package.json +4 -4
  145. package/release/app/phosphor/package.json +1 -1
  146. package/release/app/phosphor/pnpm-lock.yaml.template +53 -127
  147. package/release/compiler/pnpm-lock.yaml.template +49 -123
  148. package/release/release.json +4 -4
  149. package/dist/sidebar-types-DKpR27II.d.ts +0 -25
  150. package/dist/types-CJWRG05y.d.ts +0 -76
  151. package/dist/types-CUtrppxt.d.ts +0 -69
  152. package/dist/types-J2w_puPF.d.ts +0 -128
  153. package/dist/{button-BdCScEKi.d.ts → button-D6Xp2HBm.d.ts} +1 -1
@@ -4,14 +4,16 @@ import {
4
4
  assertPublishOsSourceBoundary
5
5
  } from "./chunk-7NY22B5U.js";
6
6
  import {
7
+ FrontmatterParseError,
7
8
  MdxIconImportError,
8
9
  ROOT_CONTENT_GROUP_KEY,
9
10
  ROOT_CONTENT_GROUP_TITLE,
10
- UnsupportedFrontmatterLanguageError,
11
11
  assertSourceDependency,
12
+ authoredDate,
12
13
  buildFaviconsAndLogos,
13
14
  buildMediaAssets,
14
15
  collectBindingNames,
16
+ convertMdxToMarkdown,
15
17
  groupedMdxExports,
16
18
  joinUrlPath,
17
19
  mdxComponentExportName,
@@ -23,7 +25,7 @@ import {
23
25
  replacedMdxSourceFiles,
24
26
  supportedNextjsSourceImports,
25
27
  validateMdxIconImports
26
- } from "./chunk-UYGCOWRF.js";
28
+ } from "./chunk-DX7PXFCM.js";
27
29
  import {
28
30
  isFrameworkNamespacePath
29
31
  } from "./chunk-EFO6AWXN.js";
@@ -48,7 +50,15 @@ import {
48
50
 
49
51
  // src/lib/compiler/validation/validate-publish-os-source.ts
50
52
  import { readdir as readdir3, readFile as readFile5, stat as stat3 } from "fs/promises";
51
- import { basename, dirname, extname as extname3, relative as relative4, resolve as resolve7, sep } from "path";
53
+ import {
54
+ basename as basename2,
55
+ dirname,
56
+ extname as extname4,
57
+ parse,
58
+ relative as relative4,
59
+ resolve as resolve7,
60
+ sep
61
+ } from "path";
52
62
  import ts2 from "typescript";
53
63
 
54
64
  // src/lib/compiler/source/diagnostics/diagnostic-store.ts
@@ -57,7 +67,7 @@ var diagnosticSchema = z.object({
57
67
  code: z.string(),
58
68
  level: z.union([z.literal("error"), z.literal("warning")]),
59
69
  message: z.string(),
60
- details: z.record(z.unknown()).optional()
70
+ details: z.record(z.string(), z.unknown()).optional()
61
71
  }), DiagnosticStore = class {
62
72
  diagnostics = [];
63
73
  add(diagnostic) {
@@ -95,8 +105,13 @@ import { resolve } from "path";
95
105
 
96
106
  // src/lib/compiler/source/config/format-config-issues.ts
97
107
  function issueMessages(issue) {
98
- return issue.code === "invalid_union" ? issue.unionErrors.flatMap(
99
- (error) => error.issues.flatMap(issueMessages)
108
+ return issue.code === "invalid_union" ? issue.errors.flatMap(
109
+ (issues) => issues.flatMap(
110
+ (nestedIssue) => issueMessages({
111
+ ...nestedIssue,
112
+ path: [...issue.path, ...nestedIssue.path]
113
+ })
114
+ )
100
115
  ) : issue.code === "unrecognized_keys" ? issue.keys.map((key) => {
101
116
  let path = [...issue.path, key].join(".");
102
117
  return issue.path.join(".") === "media.assets" && [
@@ -115,60 +130,105 @@ function formatConfigIssues(error) {
115
130
  // src/lib/compiler/source/config/publish-os-config-schema.ts
116
131
  import { z as z4 } from "zod";
117
132
 
118
- // src/lib/compiler/source/config/og-metadata-schema.ts
133
+ // src/lib/compiler/source/config/metadata-schema.ts
119
134
  import { z as z2 } from "zod";
120
- var ogImageSchema = z2.union([
121
- z2.string().min(1),
122
- z2.object({ src: z2.string().min(1), alt: z2.string().optional() }).strict()
123
- ]), ogIndexMetadataSchema = z2.object({
135
+ var imagePathSchema = z2.string().min(1).refine((value) => {
136
+ if (value.startsWith("/assets/") && value.length > 8)
137
+ return !0;
138
+ try {
139
+ return new URL(value).protocol === "https:";
140
+ } catch {
141
+ return !1;
142
+ }
143
+ }, "Use a nonempty /assets/ path or an absolute HTTPS URL."), imageSchema = z2.union([
144
+ imagePathSchema,
145
+ z2.object({ src: imagePathSchema, alt: z2.string().optional() }).strict()
146
+ ]), socialFields = {
124
147
  title: z2.string().optional(),
125
148
  description: z2.string().optional(),
126
- image: ogImageSchema.optional(),
127
- scope: z2.enum(["page", "subtree"]).optional()
128
- }).strict().refine((value) => value.scope === void 0 || value.image !== void 0, {
129
- message: "Set og.image when you set og.scope.",
130
- path: ["scope"]
131
- });
149
+ image: imageSchema.optional()
150
+ }, pageMetadataSchema = z2.object({
151
+ openGraph: z2.object(socialFields).strict().optional(),
152
+ robots: z2.object({ index: z2.boolean().optional(), follow: z2.boolean().optional() }).strict().optional()
153
+ }).strict(), indexMetadataSchema = z2.object({
154
+ openGraph: z2.object({
155
+ ...socialFields,
156
+ scope: z2.enum(["page", "subtree"]).optional()
157
+ }).strict().refine(
158
+ (value) => value.scope === void 0 || value.image !== void 0,
159
+ {
160
+ message: "Set metadata.openGraph.image when setting scope.",
161
+ path: ["scope"]
162
+ }
163
+ ).optional()
164
+ }).strict();
132
165
 
133
166
  // src/lib/compiler/source/config/publish-os-group-schema.ts
134
167
  import { z as z3 } from "zod";
135
- var treeNodeSchema = z3.lazy(
136
- () => z3.union([
137
- z3.object({
138
- type: z3.literal("section"),
139
- id: z3.string(),
140
- title: z3.string(),
141
- children: z3.array(treeNodeSchema)
142
- }),
168
+ var navigationSortSchema = z3.object({
169
+ source: z3.literal("frontmatter"),
170
+ field: z3.string().min(1),
171
+ type: z3.enum(["date", "number"]),
172
+ direction: z3.enum(["asc", "desc"]).default("asc")
173
+ }).strict(), navigationEntrySchema = z3.lazy(
174
+ () => z3.discriminatedUnion("type", [
143
175
  z3.object({
144
176
  type: z3.literal("page"),
145
- file: z3.string(),
146
- title: z3.string().optional(),
147
- slug: z3.string().optional()
148
- }),
177
+ file: z3.string().min(1).optional(),
178
+ group: z3.string().min(1).optional(),
179
+ label: z3.string().min(1).optional(),
180
+ items: z3.array(navigationEntrySchema).optional(),
181
+ navigation: z3.lazy(() => navigationConfigSchema).optional()
182
+ }).strict(),
183
+ z3.object({
184
+ type: z3.literal("folder"),
185
+ group: z3.string().min(1).optional(),
186
+ label: z3.string().min(1),
187
+ items: z3.array(navigationEntrySchema).optional(),
188
+ navigation: z3.lazy(() => navigationConfigSchema).optional()
189
+ }).strict(),
190
+ z3.object({
191
+ type: z3.literal("section"),
192
+ label: z3.string().min(1),
193
+ items: z3.array(navigationEntrySchema)
194
+ }).strict(),
149
195
  z3.object({
150
196
  type: z3.literal("link"),
151
- id: z3.string(),
152
- title: z3.string(),
153
- url: z3.string()
154
- }),
197
+ label: z3.string().min(1),
198
+ url: z3.string().min(1)
199
+ }).strict(),
155
200
  z3.object({
156
- type: z3.literal("folder"),
157
- id: z3.string(),
158
- title: z3.string(),
159
- group: z3.string().optional(),
160
- children: z3.array(treeNodeSchema).optional()
161
- })
162
- ])
163
- ), groupListSchema = z3.object({
164
- source: z3.enum(["content", "ids"]),
165
- ids: z3.array(z3.string()).optional(),
166
- sort: z3.object({
167
- by: z3.string(),
168
- direction: z3.enum(["asc", "desc"]).optional()
169
- }).optional(),
170
- prevNext: z3.boolean().optional()
171
- }).strict(), outputScopeSchema = z3.enum(["site", "group", "self"]), outputStatusSchema = z3.enum(["published", "draft", "archived"]), outputDeclarationSchema = z3.object({
201
+ type: z3.literal("view"),
202
+ id: z3.string().min(1).optional(),
203
+ label: z3.string().min(1).optional(),
204
+ items: z3.array(navigationEntrySchema)
205
+ }).strict(),
206
+ z3.object({
207
+ type: z3.literal("switch-view"),
208
+ label: z3.string().min(1),
209
+ target: z3.union([
210
+ z3.string().min(1),
211
+ z3.object({ view: z3.string().min(1) }).strict()
212
+ ])
213
+ }).strict()
214
+ ]).superRefine((entry, context) => {
215
+ entry.type === "page" && entry.file && entry.group && context.addIssue({
216
+ code: "custom",
217
+ path: ["group"],
218
+ message: 'A page navigation entry cannot set both "file" and "group". Use "file" for a page or "group" for a group placement.'
219
+ });
220
+ })
221
+ ), navigationConfigSchema = z3.object({
222
+ items: z3.array(navigationEntrySchema),
223
+ sort: z3.unknown().optional()
224
+ }).strict().transform(({ items, sort }) => {
225
+ if (sort === void 0) return { items };
226
+ let parsedSort = navigationSortSchema.safeParse(sort);
227
+ return parsedSort.success ? { items, sort: parsedSort.data } : {
228
+ items,
229
+ sortWarning: 'Invalid navigation sort config. Use source "frontmatter", a field, type "date" or "number", and direction "asc" or "desc". Filename order is used.'
230
+ };
231
+ }), outputScopeSchema = z3.enum(["site", "group", "self"]), outputDeclarationSchema = z3.object({
172
232
  path: z3.string().min(1),
173
233
  scope: outputScopeSchema.optional(),
174
234
  description: z3.string().optional(),
@@ -179,7 +239,6 @@ var treeNodeSchema = z3.lazy(
179
239
  }), outputDefaultsDeclarationSchema = z3.object({
180
240
  scope: outputScopeSchema.optional(),
181
241
  limit: z3.number().int().positive().optional(),
182
- status: z3.array(outputStatusSchema).optional(),
183
242
  includeDescriptions: z3.boolean().optional()
184
243
  }).strict(), feedsOutputSchema = z3.object({
185
244
  rss: outputDeclarationSchema.optional(),
@@ -257,32 +316,25 @@ var httpsOriginSchema = z3.string().transform((value) => normalizeHttpsOrigin(va
257
316
  ]), fontConfigSchema = z3.record(
258
317
  z3.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
259
318
  fontRoleSchema
260
- ), groupConfigDocumentSchema = z3.lazy(
261
- () => z3.object({
262
- origin: httpsOriginSchema.optional(),
263
- og: ogIndexMetadataSchema.optional(),
264
- title: z3.string().optional(),
265
- slug: z3.string().optional(),
266
- path: z3.string().regex(/^\//).optional(),
267
- mode: z3.enum(["tree", "list"]).optional(),
268
- theme: groupThemeSchema.optional(),
269
- fonts: fontConfigSchema.optional(),
270
- meta: z3.record(z3.unknown()).optional(),
271
- tree: z3.array(treeNodeSchema).optional(),
272
- list: groupListSchema.optional(),
273
- outputs: outputsSchema.optional(),
274
- outputDefaults: outputDefaultsSchema.optional()
275
- }).strict()
276
- );
319
+ ), groupConfigDocumentSchema = z3.object({
320
+ origin: httpsOriginSchema.optional(),
321
+ metadata: indexMetadataSchema.optional(),
322
+ title: z3.string().optional(),
323
+ slug: z3.string().optional(),
324
+ path: z3.string().regex(/^\//).optional(),
325
+ theme: groupThemeSchema.optional(),
326
+ fonts: fontConfigSchema.optional(),
327
+ meta: z3.record(z3.string(), z3.unknown()).optional(),
328
+ navigation: navigationConfigSchema.optional(),
329
+ outputs: outputsSchema.optional(),
330
+ outputDefaults: outputDefaultsSchema.optional()
331
+ }).strict();
277
332
 
278
333
  // src/lib/compiler/source/config/publish-os-config-schema.ts
279
334
  var siteSchema = z4.object({
280
335
  name: z4.string().optional(),
281
336
  description: z4.string().optional()
282
- }).strict(), runtimePackageMessage = 'Set runtime to "@publish.os/nextjs@<exact-version>". Replace <exact-version> with the exact framework version for this source.', runtimeSchema = z4.string({
283
- required_error: runtimePackageMessage,
284
- invalid_type_error: runtimePackageMessage
285
- }).refine((value) => runtimePackageVersion(value) !== void 0, {
337
+ }).strict(), runtimePackageMessage = 'Set runtime to "@publish.os/nextjs@<exact-version>". Replace <exact-version> with the exact framework version for this source.', runtimeSchema = z4.string({ error: runtimePackageMessage }).refine((value) => runtimePackageVersion(value) !== void 0, {
286
338
  message: runtimePackageMessage
287
339
  }), rootThemeSchema = z4.object({
288
340
  defaultMode: z4.enum(["system", "light", "dark"]).optional(),
@@ -293,7 +345,6 @@ var siteSchema = z4.object({
293
345
  enabled: z4.boolean().optional(),
294
346
  markdownRoutes: z4.boolean().optional(),
295
347
  markdownAccept: z4.boolean().optional(),
296
- plainTextAccept: z4.boolean().optional(),
297
348
  agentDetection: z4.boolean().optional(),
298
349
  discoveryHeaders: z4.boolean().optional(),
299
350
  blockInternalMarkdownRoute: z4.boolean().optional()
@@ -307,11 +358,11 @@ var siteSchema = z4.object({
307
358
  theme: rootThemeSchema.optional(),
308
359
  fonts: fontConfigSchema.optional(),
309
360
  media: mediaConfigSchema.optional(),
310
- og: ogIndexMetadataSchema.optional(),
311
- meta: z4.record(z4.unknown()).optional(),
361
+ metadata: indexMetadataSchema.optional(),
362
+ meta: z4.record(z4.string(), z4.unknown()).optional(),
312
363
  outputs: outputsSchema.optional(),
313
364
  outputDefaults: outputDefaultsSchema.optional(),
314
- groups: z4.record(groupConfigDocumentSchema).default({})
365
+ groups: z4.record(z4.string(), groupConfigDocumentSchema).default({})
315
366
  }).strict();
316
367
 
317
368
  // src/lib/compiler/source/diagnostics/json-source-location.ts
@@ -600,9 +651,17 @@ async function discoverGroups(options) {
600
651
  parent: options.parentKey
601
652
  }
602
653
  });
603
- if (siblingKeys.set(normalized.localKey, entry.name), !await isDiscoverableGroupFolder(folderPath, options.absoluteBase))
604
- continue;
605
- let key = options.parentKey ? `${options.parentKey}/${normalized.localKey}` : normalized.localKey, group = {
654
+ siblingKeys.set(normalized.localKey, entry.name);
655
+ let key = options.parentKey ? `${options.parentKey}/${normalized.localKey}` : normalized.localKey, isDiscoverable = await isDiscoverableGroupFolder(
656
+ folderPath,
657
+ options.absoluteBase
658
+ ), children = await discoverGroups({
659
+ absoluteBase: options.absoluteBase,
660
+ folderPath,
661
+ parentKey: key
662
+ });
663
+ if (!isDiscoverable && children.length === 0) continue;
664
+ let group = {
606
665
  key,
607
666
  localKey: normalized.localKey,
608
667
  localFolderName: entry.name,
@@ -611,13 +670,7 @@ async function discoverGroups(options) {
611
670
  layoutBoundary: normalized.layoutBoundary,
612
671
  hasContent: await pathIsDirectory(join(folderPath, "content"))
613
672
  };
614
- groups.push(group), groups.push(
615
- ...await discoverGroups({
616
- absoluteBase: options.absoluteBase,
617
- folderPath,
618
- parentKey: key
619
- })
620
- );
673
+ groups.push(group, ...children);
621
674
  }
622
675
  return groups;
623
676
  }
@@ -717,7 +770,20 @@ async function collectGroup(options) {
717
770
  localConfigPath,
718
771
  key,
719
772
  absoluteBase
720
- ), merged = mergeGroupConfig(entry, localConfig), slug = normalizeGroupSlug(merged.slug ?? localKey, key), path = merged.path ? normalizeGroupPath(merged.path, key) : joinUrlPath(parentPath, slug), contentPath = join(folderPath, "content");
773
+ ), merged = mergeGroupConfig(entry, localConfig), slug = normalizeGroupSlug(merged.slug ?? localKey, key), path = merged.path ? normalizeGroupPath(merged.path, key) : joinUrlPath(parentPath, slug);
774
+ if (parentKey && (parentPath === "/" ? path === "/" : path === parentPath || !path.startsWith(`${parentPath}/`)))
775
+ throw new AppError2({
776
+ code: ERROR_CODES.CONFIG_GROUP_INVALID_SCHEMA,
777
+ message: `Nested group ${key} must use a path beneath its parent ${parentKey} (${parentPath}); received ${path}`,
778
+ details: {
779
+ group: key,
780
+ parentGroup: parentKey,
781
+ path,
782
+ parentPath,
783
+ correction: `Set the path for ${key} to a path beneath ${parentPath}, or move the group to a top-level source folder.`
784
+ }
785
+ });
786
+ let contentPath = join(folderPath, "content");
721
787
  discovered.hasContent && await ensureDirectoryExists(contentPath, key, "content folder");
722
788
  let existingPathOwner = paths.get(path);
723
789
  if (existingPathOwner && !(path === "/" && existingPathOwner === ROOT_CONTENT_GROUP_KEY))
@@ -728,6 +794,7 @@ async function collectGroup(options) {
728
794
  });
729
795
  paths.set(path, key), groups[key] = {
730
796
  ...merged,
797
+ ...localConfig?.navigation ? { localNavigation: localConfig.navigation } : {},
731
798
  key,
732
799
  slug,
733
800
  path,
@@ -798,24 +865,55 @@ async function resolvePublishOsConfig(baseDir) {
798
865
 
799
866
  // src/lib/compiler/source/content/scan-publish-os-content.ts
800
867
  import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
801
- import { extname, join as join2, relative as relative2 } from "path";
802
- var RESERVED_FIELDS = /* @__PURE__ */ new Set([
803
- "title",
804
- "slug",
805
- "description",
806
- "og",
807
- "status",
808
- "publishedAt",
809
- "updatedAt",
810
- "authors",
811
- "tags"
812
- ]), UNSUPPORTED_FIELDS = /* @__PURE__ */ new Set(["author", "meta", "metadata"]);
868
+ import { extname as extname2, join as join2, relative as relative2 } from "path";
869
+
870
+ // src/lib/compiler/source/content/page-defaults.ts
871
+ import { basename, extname } from "path";
872
+ import remarkMdx from "remark-mdx";
873
+ import remarkParse from "remark-parse";
874
+ import { unified } from "unified";
875
+ var parser = unified().use(remarkParse).use(remarkMdx);
876
+ function defaultTitle(path) {
877
+ return basename(path, extname(path)).replace(/[-_]+/g, " ").replace(
878
+ /(^|\s)(\p{L})/gu,
879
+ (_, space, letter) => space + letter.toLocaleUpperCase()
880
+ );
881
+ }
882
+ function staticText(node) {
883
+ return node.type === "text" ? node.value ?? "" : ["emphasis", "strong", "delete", "link", "linkReference"].includes(
884
+ node.type
885
+ ) ? (node.children ?? []).map((child) => staticText(child)).join("") : "";
886
+ }
887
+ function firstProse(node) {
888
+ if (node.type === "paragraph")
889
+ return (node.children ?? []).map((child) => staticText(child)).join("").replace(/\s+/g, " ").trim() || void 0;
890
+ if (["root", "blockquote", "list", "listItem"].includes(node.type))
891
+ for (let child of node.children ?? []) {
892
+ let prose = firstProse(child);
893
+ if (prose) return prose;
894
+ }
895
+ }
896
+ function defaultDescription(body) {
897
+ let prose = firstProse(parser.parse(body));
898
+ if (!prose) return "";
899
+ let sentence = [
900
+ ...new Intl.Segmenter(void 0, { granularity: "sentence" }).segment(
901
+ prose
902
+ )
903
+ ][0]?.segment.trim() ?? prose, chars = [
904
+ ...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(
905
+ sentence
906
+ )
907
+ ].map(({ segment }) => segment);
908
+ if (chars.length <= 160) return sentence;
909
+ let prefix = chars.slice(0, 159).join(""), boundary = /\s/u.test(chars[159] ?? "") ? -1 : prefix.search(/\s+\S*$/u);
910
+ return `${(boundary > 0 ? prefix.slice(0, boundary) : prefix).trimEnd()}\u2026`;
911
+ }
912
+
913
+ // src/lib/compiler/source/content/scan-publish-os-content.ts
813
914
  function fileSlug(relativePath) {
814
915
  return relativePath.replace(/\\/g, "/").replace(/\.mdx$/i, "");
815
916
  }
816
- function asString(value) {
817
- return typeof value == "string" && value.length > 0 ? value : void 0;
818
- }
819
917
  function normalizeContentSlug(value, absolutePath) {
820
918
  let result = normalizeSlugPath(value);
821
919
  if (result.errors.length > 0)
@@ -830,287 +928,153 @@ function normalizeContentSlug(value, absolutePath) {
830
928
  });
831
929
  return result.value;
832
930
  }
833
- function asDateString(value) {
834
- if (typeof value == "string" && value.length > 0) return value;
835
- if (value instanceof Date && !Number.isNaN(value.getTime()))
836
- return value.toISOString().slice(0, 10);
837
- }
838
- function isIsoDate(value) {
839
- return !Number.isNaN(Date.parse(value));
840
- }
841
931
  function warning(code, message, details) {
842
932
  return { code, level: "warning", message, details };
843
933
  }
844
- function treeSlugs(nodes) {
845
- function visit(tree) {
846
- let slugs = /* @__PURE__ */ new Map();
847
- for (let node of tree)
848
- if (node.type === "page" && (slugs.has(node.file) || slugs.set(node.file, asString(node.slug))), "children" in node && node.children)
849
- for (let [fileID, slug] of visit(node.children))
850
- slug && !slugs.has(fileID) && slugs.set(fileID, slug);
851
- return slugs;
852
- }
853
- return visit(nodes ?? []);
854
- }
855
- function normalizeAuthorship(authors, absolutePath) {
856
- let warnings = [];
857
- if (authors === void 0)
858
- return { authors: [], warnings };
859
- if (!Array.isArray(authors))
860
- return warnings.push(
861
- warning(
862
- "CONTENT_AUTHORS_INVALID",
863
- `Ignoring invalid authors for ${absolutePath}`,
864
- {
865
- path: absolutePath
866
- }
867
- )
868
- ), { authors: [], warnings };
869
- let normalized = [];
870
- for (let author of authors) {
871
- if (typeof author == "string" && author.length > 0) {
872
- normalized.push({ name: author });
873
- continue;
874
- }
875
- if (author && typeof author == "object" && typeof author.name == "string") {
876
- let value = author;
877
- normalized.push({
878
- id: asString(value.id),
879
- name: value.name,
880
- handle: asString(value.handle),
881
- avatar: asString(value.avatar),
882
- url: asString(value.url)
883
- });
884
- continue;
885
- }
886
- warnings.push(
887
- warning(
888
- "CONTENT_AUTHOR_INVALID",
889
- `Ignoring invalid author entry for ${absolutePath}`,
890
- {
891
- path: absolutePath
892
- }
893
- )
894
- );
895
- }
896
- return { authors: normalized, warnings };
897
- }
898
- function normalizeTags(tags, absolutePath) {
899
- return tags === void 0 ? { tags: [], warnings: [] } : Array.isArray(tags) ? {
900
- tags: tags.filter((tag) => typeof tag == "string"),
901
- warnings: []
902
- } : {
903
- tags: [],
904
- warnings: [
905
- warning(
906
- "CONTENT_TAGS_INVALID",
907
- `Ignoring invalid tags for ${absolutePath}`,
908
- {
909
- path: absolutePath
910
- }
911
- )
912
- ]
913
- };
914
- }
915
- function normalizeOgMetadata(value, absolutePath) {
916
- if (value === void 0) return;
917
- if (!value || typeof value != "object" || Array.isArray(value))
918
- throw new AppError2({
919
- code: "CONTENT_FRONTMATTER_OG_INVALID",
920
- message: `Invalid og frontmatter for ${absolutePath}; expected an object with title, description, or image`,
921
- details: { path: absolutePath, field: "og" }
922
- });
923
- let source = value, allowedFields = /* @__PURE__ */ new Set(["title", "description", "image"]);
924
- for (let field of Object.keys(source))
925
- if (!allowedFields.has(field))
926
- throw new AppError2({
927
- code: "CONTENT_FRONTMATTER_OG_INVALID",
928
- message: `Invalid og frontmatter field "${field}" for ${absolutePath}`,
929
- details: { path: absolutePath, field: `og.${field}` }
930
- });
931
- let og = {};
932
- if (source.title !== void 0) {
933
- if (typeof source.title != "string" || source.title.length === 0)
934
- throw new AppError2({
935
- code: "CONTENT_FRONTMATTER_OG_INVALID",
936
- message: `Invalid og.title for ${absolutePath}; expected a non-empty string`,
937
- details: { path: absolutePath, field: "og.title" }
938
- });
939
- og.title = source.title;
940
- }
941
- if (source.description !== void 0) {
942
- if (typeof source.description != "string" || source.description.length === 0)
943
- throw new AppError2({
944
- code: "CONTENT_FRONTMATTER_OG_INVALID",
945
- message: `Invalid og.description for ${absolutePath}; expected a non-empty string`,
946
- details: { path: absolutePath, field: "og.description" }
947
- });
948
- og.description = source.description;
949
- }
950
- if (source.image !== void 0)
951
- if (typeof source.image == "string" && source.image.length > 0)
952
- og.image = source.image;
953
- else if (source.image && typeof source.image == "object" && !Array.isArray(source.image)) {
954
- let image = source.image, allowedImageFields = /* @__PURE__ */ new Set(["src", "alt"]);
955
- for (let field of Object.keys(image))
956
- if (!allowedImageFields.has(field))
957
- throw new AppError2({
958
- code: "CONTENT_FRONTMATTER_OG_INVALID",
959
- message: `Invalid og.image field "${field}" for ${absolutePath}`,
960
- details: { path: absolutePath, field: `og.image.${field}` }
961
- });
962
- if (typeof image.src != "string" || image.src.length === 0)
963
- throw new AppError2({
964
- code: "CONTENT_FRONTMATTER_OG_INVALID",
965
- message: `Invalid og.image.src for ${absolutePath}; expected a non-empty string`,
966
- details: { path: absolutePath, field: "og.image.src" }
967
- });
968
- if (image.alt !== void 0 && typeof image.alt != "string")
969
- throw new AppError2({
970
- code: "CONTENT_FRONTMATTER_OG_INVALID",
971
- message: `Invalid og.image.alt for ${absolutePath}; expected a string`,
972
- details: { path: absolutePath, field: "og.image.alt" }
973
- });
974
- og.image = {
975
- src: image.src,
976
- ...typeof image.alt == "string" ? { alt: image.alt } : {}
977
- };
978
- } else
979
- throw new AppError2({
980
- code: "CONTENT_FRONTMATTER_OG_INVALID",
981
- message: `Invalid og.image for ${absolutePath}; expected a non-empty string or object with src`,
982
- details: { path: absolutePath, field: "og.image" }
983
- });
984
- return Object.keys(og).length > 0 ? og : void 0;
985
- }
986
- function customMeta(frontmatter, absolutePath) {
987
- let meta = {}, warnings = [];
988
- for (let [key, value] of Object.entries(frontmatter))
989
- if (!RESERVED_FIELDS.has(key)) {
990
- if (UNSUPPORTED_FIELDS.has(key)) {
934
+ async function readContentFile(group, absolutePath) {
935
+ let raw = await readFile3(absolutePath, "utf-8");
936
+ try {
937
+ let parsed = parseSourceFrontmatter(raw), frontmatter = parsed.data, relativePath = relative2(group.contentPath, absolutePath), fileID = normalizeContentSlug(fileSlug(relativePath), absolutePath), warnings = [];
938
+ if (parsed.content.includes("<")) {
939
+ let unknown = new Set(
940
+ convertMdxToMarkdown({ source: parsed.content }).diagnostics.filter(
941
+ (diagnostic) => diagnostic.code === "mdx-component-unknown"
942
+ ).map((diagnostic) => diagnostic.componentName).filter((name) => !!name)
943
+ );
944
+ for (let name of unknown)
991
945
  warnings.push(
992
946
  warning(
993
- "CONTENT_FRONTMATTER_FIELD_UNSUPPORTED",
994
- `Ignoring unsupported frontmatter field "${key}" for ${absolutePath}`,
995
- { path: absolutePath, field: key }
947
+ "CONTENT_COMPONENT_NOT_CONVERTED",
948
+ `Custom component <${name}> renders in HTML, but its runtime output is not included in generated Markdown or search. Authored children remain available.`,
949
+ { path: absolutePath }
996
950
  )
997
951
  );
998
- continue;
999
- }
1000
- meta[key] = value;
1001
952
  }
1002
- return { meta, warnings };
1003
- }
1004
- async function readContentFile(group, absolutePath, configSlugs) {
1005
- let raw = await readFile3(absolutePath, "utf-8");
1006
- try {
1007
- let parsed = parseSourceFrontmatter(raw), frontmatter = parsed.data, relativePath = relative2(group.contentPath, absolutePath), fileID = normalizeContentSlug(fileSlug(relativePath), absolutePath), title = asString(frontmatter.title);
1008
- if (frontmatter.id !== void 0)
1009
- throw new AppError2({
1010
- code: ERROR_CODES.CONTENT_FRONTMATTER_ID_INVALID,
1011
- message: `Frontmatter field "id" is not supported for ${absolutePath}; page identity is derived from fileID`,
1012
- details: { path: absolutePath, field: "id" }
1013
- });
953
+ for (let field of ["title", "description"])
954
+ if (frontmatter[field] !== void 0 && typeof frontmatter[field] != "string")
955
+ throw new AppError2({
956
+ code: "CONTENT_FRONTMATTER_TYPE_INVALID",
957
+ message: `Invalid frontmatter field "${field}" in ${absolutePath}; expected a string. Remove it to use the default or provide text.`,
958
+ details: { path: absolutePath, field }
959
+ });
960
+ let title = frontmatter.title?.trim() || defaultTitle(relativePath), description = frontmatter.description?.trim() || defaultDescription(parsed.content);
961
+ for (let field of ["title", "description"])
962
+ frontmatter[field]?.trim() || warnings.push(
963
+ warning(
964
+ "CONTENT_FRONTMATTER_DEFAULT",
965
+ `Missing or blank ${field} in ${absolutePath}; add ${field} to frontmatter.`,
966
+ { path: absolutePath, field }
967
+ )
968
+ );
1014
969
  if (!fileID)
1015
970
  throw new AppError2({
1016
971
  code: ERROR_CODES.CONTENT_FRONTMATTER_ID_INVALID,
1017
972
  message: `Could not derive fileID for ${absolutePath}`,
1018
973
  details: { path: absolutePath }
1019
974
  });
1020
- if (!title)
975
+ if (frontmatter.slug !== void 0 && (typeof frontmatter.slug != "string" || !frontmatter.slug.trim() || /[\\/]/.test(frontmatter.slug)))
1021
976
  throw new AppError2({
1022
- code: ERROR_CODES.CONTENT_FRONTMATTER_REQUIRED,
1023
- message: `Missing required frontmatter field "title" for ${absolutePath}`,
1024
- details: { path: absolutePath, field: "title" }
977
+ code: ERROR_CODES.CONTENT_SLUG_INVALID,
978
+ message: `Invalid slug in ${absolutePath}; use one nonempty path segment, or remove slug to use the filename.`,
979
+ details: {
980
+ path: absolutePath,
981
+ field: "slug",
982
+ correction: "Remove slug or provide one nonempty path segment."
983
+ }
1025
984
  });
1026
- let warnings = [], frontmatterSlug = asString(frontmatter.slug), slugSource = configSlugs.get(fileID) ?? frontmatterSlug ?? fileID, slugResult = normalizeSlugPath(slugSource);
1027
- if (slugResult.errors.length > 0)
985
+ let sourceSegments = fileID.split("/"), slugSource = frontmatter.slug ?? sourceSegments.at(-1) ?? "", slugResult = normalizeSlugPath(slugSource);
986
+ if (slugResult.errors.length > 0 || !slugResult.value || slugResult.value.includes("/"))
1028
987
  throw new AppError2({
1029
988
  code: ERROR_CODES.CONTENT_SLUG_INVALID,
1030
- message: `Invalid content slug "${slugSource}" for ${absolutePath}`,
989
+ message: `Invalid slug "${slugSource}" in ${absolutePath}; remove it or provide one valid path segment.`,
1031
990
  details: {
1032
991
  errors: slugResult.errors,
1033
992
  path: absolutePath,
1034
- slug: slugSource
993
+ field: "slug",
994
+ correction: "Remove slug or provide one valid path segment."
1035
995
  }
1036
996
  });
1037
- let slug = slugResult.value;
1038
- slugResult.changed && warnings.push(
997
+ let slug = slugResult.value, routeSlug = [...sourceSegments.slice(0, -1), slug].join("/");
998
+ if (slugResult.changed && warnings.push(
1039
999
  warning(
1040
1000
  "CONTENT_SLUG_NORMALIZED",
1041
1001
  `Normalized slug "${slugSource}" to "${slug}" for ${absolutePath}`,
1042
1002
  { path: absolutePath, from: slugSource, to: slug }
1043
1003
  )
1044
- );
1045
- let status = "published";
1046
- frontmatter.status === "published" || frontmatter.status === "draft" || frontmatter.status === "archived" ? status = frontmatter.status : frontmatter.status !== void 0 && warnings.push(
1004
+ ), frontmatter.status !== void 0 && typeof frontmatter.status != "string")
1005
+ throw new AppError2({
1006
+ code: "CONTENT_FRONTMATTER_TYPE_INVALID",
1007
+ message: `Invalid frontmatter field "status" in ${absolutePath}; expected a string.`,
1008
+ details: {
1009
+ path: absolutePath,
1010
+ field: "status",
1011
+ correction: 'Set status to "draft" or "published", or remove it.'
1012
+ }
1013
+ });
1014
+ let normalizedStatus = frontmatter.status?.trim().toLowerCase(), status = "published";
1015
+ normalizedStatus === "published" || normalizedStatus === "draft" ? status = normalizedStatus : frontmatter.status !== void 0 && warnings.push(
1047
1016
  warning(
1048
1017
  "CONTENT_STATUS_INVALID",
1049
- `Invalid status "${String(frontmatter.status)}" for ${absolutePath}; defaulting to published`,
1050
- { path: absolutePath, status: frontmatter.status }
1051
- )
1052
- );
1053
- let rawPublishedAt = asDateString(frontmatter.publishedAt), publishedAt = rawPublishedAt && isIsoDate(rawPublishedAt) ? rawPublishedAt : void 0;
1054
- rawPublishedAt && !publishedAt && warnings.push(
1055
- warning(
1056
- "CONTENT_DATE_INVALID",
1057
- `Invalid publishedAt for ${absolutePath}`,
1018
+ `Status "${String(frontmatter.status)}" is not recognized; this page will be published.`,
1058
1019
  {
1059
1020
  path: absolutePath,
1060
- field: "publishedAt",
1061
- value: rawPublishedAt
1021
+ propertyPath: "status",
1022
+ correction: 'Set status to "draft" to keep the page unpublished, or "published" to publish it.'
1062
1023
  }
1063
1024
  )
1064
1025
  );
1065
- let rawUpdatedAt = asDateString(frontmatter.updatedAt), updatedAt = rawUpdatedAt && isIsoDate(rawUpdatedAt) ? rawUpdatedAt : void 0;
1066
- rawUpdatedAt && !updatedAt && warnings.push(
1067
- warning(
1068
- "CONTENT_DATE_INVALID",
1069
- `Invalid updatedAt for ${absolutePath}`,
1070
- {
1026
+ let metadataResult = pageMetadataSchema.safeParse(
1027
+ frontmatter.metadata === void 0 ? {} : frontmatter.metadata
1028
+ );
1029
+ if (!metadataResult.success) {
1030
+ let issue = metadataResult.error.issues[0], key = issue?.code === "unrecognized_keys" ? issue.keys[0] : void 0, field = [
1031
+ "metadata",
1032
+ ...issue?.path ?? [],
1033
+ ...key ? [key] : []
1034
+ ].join(".");
1035
+ throw new AppError2({
1036
+ code: "CONTENT_FRONTMATTER_TYPE_INVALID",
1037
+ message: `Invalid ${field} in ${absolutePath}. ${formatConfigIssues(metadataResult.error)}. Correct or remove this field.`,
1038
+ details: {
1071
1039
  path: absolutePath,
1072
- field: "updatedAt",
1073
- value: rawUpdatedAt
1040
+ field,
1041
+ correction: `Correct or remove ${field}.`
1074
1042
  }
1075
- )
1076
- );
1077
- let authors = normalizeAuthorship(frontmatter.authors, absolutePath);
1078
- warnings.push(...authors.warnings);
1079
- let tags = normalizeTags(frontmatter.tags, absolutePath);
1080
- warnings.push(...tags.warnings);
1081
- let og = normalizeOgMetadata(frontmatter.og, absolutePath), meta = customMeta(frontmatter, absolutePath);
1082
- return warnings.push(...meta.warnings), {
1043
+ });
1044
+ }
1045
+ let resolved = {
1046
+ ...frontmatter,
1047
+ title,
1048
+ description,
1049
+ slug,
1050
+ status,
1051
+ metadata: metadataResult.data
1052
+ };
1053
+ return {
1083
1054
  fileID,
1084
1055
  group: group.key,
1085
- slug,
1086
- title,
1087
- description: asString(frontmatter.description),
1088
- og,
1056
+ slug: routeSlug,
1057
+ frontmatter: resolved,
1089
1058
  status,
1090
- publishedAt,
1091
- updatedAt,
1092
- authors: authors.authors,
1093
- tags: tags.tags,
1094
- meta: meta.meta,
1095
1059
  absolutePath,
1096
1060
  relativePath,
1097
- body: parsed.content.trim(),
1061
+ body: parsed.content,
1098
1062
  warnings
1099
1063
  };
1100
1064
  } catch (error) {
1101
- throw error instanceof AppError2 ? error : error instanceof UnsupportedFrontmatterLanguageError ? new AppError2({
1102
- code: ERROR_CODES.CONTENT_FRONTMATTER_LANGUAGE_UNSUPPORTED,
1103
- message: `Unsupported frontmatter language "${error.language}" for ${absolutePath}. Use YAML or JSON.`,
1104
- details: { path: absolutePath, language: error.language }
1105
- }) : new AppError2({
1065
+ throw error instanceof AppError2 ? error : new AppError2({
1106
1066
  code: ERROR_CODES.CONTENT_FILE_PARSE_FAILED,
1107
- message: `Failed to parse frontmatter for ${absolutePath}`,
1108
- details: { error }
1067
+ message: `Failed to parse page ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`,
1068
+ details: {
1069
+ path: absolutePath,
1070
+ ...error instanceof FrontmatterParseError ? { line: error.line, column: error.column, field: error.field } : {},
1071
+ error
1072
+ }
1109
1073
  });
1110
1074
  }
1111
1075
  }
1112
1076
  async function walkContent(group) {
1113
- let files = [], stack = [group.contentPath], configSlugs = treeSlugs(group.tree);
1077
+ let files = [], stack = [group.contentPath];
1114
1078
  for (; stack.length > 0; ) {
1115
1079
  let current = stack.pop();
1116
1080
  if (!current) continue;
@@ -1128,15 +1092,15 @@ async function walkContent(group) {
1128
1092
  stack.push(absolute);
1129
1093
  continue;
1130
1094
  }
1131
- extname(entry).toLowerCase() === ".mdx" && files.push(await readContentFile(group, absolute, configSlugs));
1095
+ extname2(entry).toLowerCase() === ".mdx" && files.push(await readContentFile(group, absolute));
1132
1096
  }
1133
1097
  }
1134
1098
  return files;
1135
1099
  }
1136
- function buildRoutes(groups, files) {
1100
+ function buildRoutes(groups, files, includeDrafts = !1) {
1137
1101
  let routes = [];
1138
1102
  for (let file of files) {
1139
- if (file.status === "draft")
1103
+ if (file.status === "draft" && !includeDrafts)
1140
1104
  continue;
1141
1105
  let group = groups[file.group];
1142
1106
  if (!group)
@@ -1154,50 +1118,75 @@ function buildRoutes(groups, files) {
1154
1118
  async function scanContent(config) {
1155
1119
  let files = (await Promise.all(
1156
1120
  Object.values(config.groups).map((group) => walkContent(group))
1157
- )).flat(), warnings = files.flatMap((file) => file.warnings), fileIDs = /* @__PURE__ */ new Set();
1121
+ )).flat(), warnings = files.flatMap((file) => file.warnings), fileIDs = /* @__PURE__ */ new Map();
1158
1122
  for (let file of files) {
1159
- let scopedFileID = `${file.group}:${file.fileID}`;
1160
- if (fileIDs.has(scopedFileID))
1123
+ let scopedFileID = `${file.group}:${file.fileID}`, previous = fileIDs.get(scopedFileID), sourceRoot = config.groups[file.group]?.__meta.basePath ?? "";
1124
+ if (previous)
1161
1125
  throw new AppError2({
1162
1126
  code: ERROR_CODES.CONTENT_DUPLICATE_ID,
1163
- message: `Duplicate content fileID "${file.fileID}" in group ${file.group}`,
1164
- details: { fileID: file.fileID, group: file.group }
1127
+ message: `Duplicate page identity "${file.fileID}" in group "${file.group}": ${relative2(sourceRoot, previous.absolutePath)} and ${relative2(sourceRoot, file.absolutePath)}. Rename one source file.`,
1128
+ details: {
1129
+ path: file.absolutePath,
1130
+ existingPath: previous.absolutePath,
1131
+ fileID: file.fileID,
1132
+ group: file.group,
1133
+ correction: "Rename one source file."
1134
+ }
1165
1135
  });
1166
- fileIDs.add(scopedFileID);
1136
+ fileIDs.set(scopedFileID, file);
1167
1137
  }
1168
- let routes = buildRoutes(config.groups, files), routePaths = /* @__PURE__ */ new Set(), groupPaths = new Map(
1169
- Object.values(config.groups).map((group) => [group.path, group.key])
1170
- );
1171
- for (let route of routes) {
1172
- if (routePaths.has(route.path))
1173
- throw new AppError2({
1174
- code: ERROR_CODES.CONTENT_DUPLICATE_ROUTE,
1175
- message: `Duplicate route "${route.path}"`,
1176
- details: { route: route.path }
1177
- });
1178
- let groupPathOwner = groupPaths.get(route.path);
1179
- if (groupPathOwner)
1138
+ let candidates = buildRoutes(config.groups, files, !0), routes = buildRoutes(config.groups, files), owners = /* @__PURE__ */ new Map();
1139
+ for (let group of Object.values(config.groups))
1140
+ owners.set(group.path, {
1141
+ source: relative2(
1142
+ group.__meta.basePath,
1143
+ group.__meta.filePath ?? group.__meta.basePath
1144
+ ),
1145
+ kind: `group ${group.key}`
1146
+ });
1147
+ for (let route of candidates) {
1148
+ let file = fileIDs.get(`${route.group}:${route.fileID}`);
1149
+ if (!file) continue;
1150
+ let sourceRoot = config.groups[route.group]?.__meta.basePath ?? "", sourcePath = relative2(sourceRoot, file.absolutePath), paths = [[route.path, "page"]];
1151
+ config.agent?.enabled !== !1 && config.agent?.markdownRoutes !== !1 && paths.push([`${route.path}.md`, "Markdown"]);
1152
+ let normalized = normalizeSlugPath(route.path.replace(/^\//, ""));
1153
+ if (normalized.errors.length)
1180
1154
  throw new AppError2({
1181
- code: ERROR_CODES.CONTENT_DUPLICATE_ROUTE,
1182
- message: `Content route "${route.path}" collides with group "${groupPathOwner}"`,
1155
+ code: ERROR_CODES.CONTENT_SLUG_INVALID,
1156
+ message: `Invalid route "${route.path}" for ${file.absolutePath}. Shorten the path.`,
1183
1157
  details: {
1158
+ path: file.absolutePath,
1184
1159
  route: route.path,
1185
- group: route.group,
1186
- fileID: route.fileID,
1187
- existingGroup: groupPathOwner
1160
+ errors: normalized.errors,
1161
+ correction: "Shorten the path."
1188
1162
  }
1189
1163
  });
1190
- routePaths.add(route.path);
1164
+ for (let [path, kind] of paths) {
1165
+ let previous = owners.get(path);
1166
+ if (previous)
1167
+ throw new AppError2({
1168
+ code: ERROR_CODES.CONTENT_DUPLICATE_ROUTE,
1169
+ message: `URL "${path}" is used by ${previous.kind} ${previous.source} and ${kind} ${sourcePath}. Change a page slug or group path.`,
1170
+ details: {
1171
+ path: file.absolutePath,
1172
+ existingPath: previous.source,
1173
+ route: path,
1174
+ correction: "Change a page slug or group path."
1175
+ }
1176
+ });
1177
+ owners.set(path, { source: sourcePath, kind });
1178
+ }
1191
1179
  }
1192
1180
  let meta = config.meta;
1193
1181
  return {
1194
1182
  site: {
1195
1183
  name: config.site?.name ?? meta?.title ?? "Untitled",
1196
1184
  description: config.site?.description ?? meta?.description,
1197
- ...config.og ? { og: config.og } : {}
1185
+ ...config.metadata ? { metadata: config.metadata } : {}
1198
1186
  },
1199
1187
  files,
1200
1188
  routes,
1189
+ candidateRoutes: candidates,
1201
1190
  warnings
1202
1191
  };
1203
1192
  }
@@ -1216,13 +1205,26 @@ function appErrorToDiagnostic(error) {
1216
1205
  import { readFile as readFile4 } from "fs/promises";
1217
1206
  import { relative as relative3, resolve as resolve4 } from "path";
1218
1207
  async function missingPageReferences(options) {
1219
- let diagnostics = [], configSource = /* @__PURE__ */ new Map(), routes = new Set(
1220
- options.content.routes.map((route) => `${route.group}\0${route.fileID}`)
1208
+ let diagnostics = [], configSource = /* @__PURE__ */ new Map(), files = new Map(
1209
+ options.content.files.map(
1210
+ (file) => [
1211
+ relative3(options.sourceRoot, file.absolutePath).replace(/\\/g, "/"),
1212
+ file
1213
+ ]
1214
+ )
1215
+ ), groupContentRoots = Object.values(options.config.groups).map(
1216
+ (group) => ({
1217
+ group,
1218
+ prefix: `${group.__meta.relativeContentPath.replace(/\\/g, "/")}/`
1219
+ })
1221
1220
  ), rootConfig = resolve4(options.sourceRoot, "publish.os.config.json");
1222
- async function locationFor(groupKey, groupFile, path) {
1221
+ function groupForSourcePath(path) {
1222
+ return groupContentRoots.filter(({ prefix }) => path.startsWith(prefix)).sort((a, b) => b.prefix.length - a.prefix.length)[0]?.group;
1223
+ }
1224
+ async function locationFor(groupFile, path, rootPath) {
1223
1225
  for (let [file, sourcePath] of [
1224
1226
  [groupFile, path],
1225
- [rootConfig, ["groups", groupKey, ...path]]
1227
+ [rootConfig, rootPath]
1226
1228
  ]) {
1227
1229
  let raw = configSource.get(file);
1228
1230
  raw === void 0 && (raw = await readFile4(file, "utf8"), configSource.set(file, raw));
@@ -1235,227 +1237,491 @@ async function missingPageReferences(options) {
1235
1237
  }
1236
1238
  }
1237
1239
  for (let group of Object.values(options.config.groups)) {
1238
- async function visit(nodes, path) {
1240
+ let hasLocalNavigation = !!group.localNavigation, basePath = hasLocalNavigation ? ["navigation", "items"] : ["groups", group.key, "navigation", "items"], rootBasePath = hasLocalNavigation ? ["groups", group.key, ...basePath] : basePath;
1241
+ async function visit(nodes, path, rootPath, contentGroup = group) {
1239
1242
  for (let [index, node] of nodes.entries()) {
1240
- let nodePath = [...path, index];
1241
- if (node.type === "page" && !routes.has(`${group.key}\0${node.file}`)) {
1242
- let propertyPath = `groups.${group.key}.${nodePath.join(".")}.file`, location = await locationFor(group.key, group.__meta.filePath, [
1243
- ...nodePath,
1244
- "file"
1245
- ]), expected = `publish.os/${group.__meta.relativeContentPath.replace(/\\/g, "/")}/${node.file}.mdx`;
1246
- diagnostics.push({
1247
- code: "CONTENT_TREE_PAGE_MISSING",
1248
- level: "warning",
1249
- message: `Referenced page "${node.file}" was not found. Expected ${expected}.`,
1250
- details: {
1251
- ...location,
1252
- propertyPath,
1253
- correction: "Add that page, or change or remove the tree entry."
1254
- }
1255
- });
1243
+ let nodePath = [...path, index], rootNodePath = [...rootPath, index], targetGroupKey = node.type === "folder" || node.type === "page" ? node.group : void 0, nestedContentGroup = targetGroupKey ? options.config.groups[targetGroupKey] ?? contentGroup : contentGroup;
1244
+ if (node.type === "page" && node.file) {
1245
+ let expected = node.file.replace(/\\/g, "/"), propertyPath = `${nodePath.join(".")}.file`, location = await locationFor(
1246
+ group.__meta.filePath,
1247
+ [...nodePath, "file"],
1248
+ [...rootNodePath, "file"]
1249
+ ), expectedPrefix = `${contentGroup.__meta.relativeContentPath.replace(/\\/g, "/")}/`, referencedFile = files.get(expected), targetGroupKey2 = (referencedFile ? options.config.groups[referencedFile.group] : groupForSourcePath(expected))?.key ?? referencedFile?.group, isForeign = targetGroupKey2 !== void 0 && targetGroupKey2 !== contentGroup.key;
1250
+ if (isForeign || !referencedFile) {
1251
+ let code = isForeign ? "NAVIGATION_PAGE_FOREIGN" : "NAVIGATION_PAGE_MISSING", message = isForeign ? `Referenced page "${node.file}" belongs to group "${targetGroupKey2}", not "${contentGroup.key}".` : `Referenced page "${node.file}" was not found.`;
1252
+ diagnostics.push({
1253
+ code,
1254
+ level: "error",
1255
+ message,
1256
+ details: {
1257
+ ...location,
1258
+ propertyPath,
1259
+ group: contentGroup.key,
1260
+ target: node.file,
1261
+ ...targetGroupKey2 ? { owner: targetGroupKey2 } : {},
1262
+ correction: `Use a source-root-relative MDX path inside ${expectedPrefix}, such as ${expectedPrefix}<file>.mdx.`
1263
+ }
1264
+ });
1265
+ }
1256
1266
  }
1257
- "children" in node && node.children && await visit(node.children, [...nodePath, "children"]);
1267
+ "items" in node && node.items && await visit(
1268
+ node.items,
1269
+ [...nodePath, "items"],
1270
+ [...rootNodePath, "items"],
1271
+ nestedContentGroup
1272
+ ), (node.type === "folder" || node.type === "page") && node.navigation && await visit(
1273
+ node.navigation.items,
1274
+ [...nodePath, "navigation", "items"],
1275
+ [...rootNodePath, "navigation", "items"],
1276
+ nestedContentGroup
1277
+ );
1258
1278
  }
1259
1279
  }
1260
- await visit(group.tree ?? [], ["tree"]);
1280
+ let navigation = group.localNavigation ?? group.navigation;
1281
+ await visit(navigation?.items ?? [], basePath, rootBasePath);
1261
1282
  }
1262
1283
  return diagnostics;
1263
1284
  }
1264
1285
 
1265
1286
  // src/lib/compiler/source/manifest/create-publish-os-manifest.ts
1266
1287
  import { createHash } from "crypto";
1288
+
1289
+ // src/lib/compiler/source/manifest/sort-navigation-entries.ts
1290
+ function sortKey(value, type) {
1291
+ if (value === void 0) return { reason: "missing" };
1292
+ if (type === "number")
1293
+ return typeof value == "number" && Number.isFinite(value) ? { value } : { reason: "invalid" };
1294
+ let date = authoredDate(value);
1295
+ return date ? { value: Date.parse(date) } : { reason: "invalid" };
1296
+ }
1297
+ function filenameOrder(left, right) {
1298
+ return left.localeCompare(right);
1299
+ }
1300
+ function sortNavigationEntries({
1301
+ entries,
1302
+ explicitCount = 0,
1303
+ field,
1304
+ type,
1305
+ direction
1306
+ }) {
1307
+ if (!Number.isInteger(explicitCount) || explicitCount < 0 || explicitCount > entries.length)
1308
+ throw new RangeError(
1309
+ "explicitCount must be between zero and the entry count."
1310
+ );
1311
+ let automatic = entries.slice(explicitCount), pageEntries = automatic.flatMap(
1312
+ (candidate, index) => candidate.kind === "page" ? [{ candidate, index, key: sortKey(candidate.frontmatter[field], type) }] : []
1313
+ ), warnings = pageEntries.flatMap(
1314
+ ({ candidate, key }) => key.reason ? [
1315
+ {
1316
+ code: key.reason === "missing" ? "NAVIGATION_SORT_VALUE_MISSING" : "NAVIGATION_SORT_VALUE_INVALID",
1317
+ field,
1318
+ file: candidate.sourcePath
1319
+ }
1320
+ ] : []
1321
+ ), directionFactor = direction === "asc" ? 1 : -1;
1322
+ pageEntries.sort((left, right) => {
1323
+ let leftValue = left.key.value, rightValue = right.key.value, leftValid = leftValue !== void 0;
1324
+ return leftValid !== (rightValue !== void 0) ? leftValid ? -1 : 1 : leftValue !== void 0 && rightValue !== void 0 && leftValue !== rightValue ? (leftValue - rightValue) * directionFactor : filenameOrder(
1325
+ left.candidate.sourcePath,
1326
+ right.candidate.sourcePath
1327
+ ) || left.index - right.index;
1328
+ });
1329
+ let nextPage = 0, sortedAutomatic = automatic.map(
1330
+ (candidate) => candidate.kind === "page" ? pageEntries[nextPage++]?.candidate : candidate
1331
+ );
1332
+ return {
1333
+ entries: [
1334
+ ...entries.slice(0, explicitCount).map((candidate) => candidate.entry),
1335
+ ...sortedAutomatic.flatMap(
1336
+ (candidate) => candidate ? [candidate.entry] : []
1337
+ )
1338
+ ],
1339
+ warnings
1340
+ };
1341
+ }
1342
+
1343
+ // src/lib/compiler/source/manifest/create-publish-os-manifest.ts
1267
1344
  function stableStringify(value) {
1268
1345
  return Array.isArray(value) ? `[${value.map((item) => stableStringify(item)).join(",")}]` : value && typeof value == "object" ? `{${Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(
1269
1346
  ([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`
1270
1347
  ).join(",")}}` : JSON.stringify(value);
1271
1348
  }
1272
- function resolveGroupReference(config, currentGroup, reference) {
1273
- let relativeKey = `${currentGroup}/${reference}`;
1274
- if (config.groups[relativeKey]) return relativeKey;
1275
- if (config.groups[reference]) return reference;
1276
- }
1277
- function normalizeTreeNode(node, group, pagesByFileID, routesByFileID, options) {
1278
- if (node.type === "page") {
1279
- let page = pagesByFileID.get(node.file), route = routesByFileID.get(node.file);
1280
- return !page || !route ? null : {
1281
- type: "page",
1282
- fileID: node.file,
1283
- title: node.title ?? page.title,
1284
- path: route.path,
1285
- group,
1286
- description: page.description,
1287
- og: page.og,
1288
- status: page.status,
1289
- publishedAt: page.publishedAt,
1290
- updatedAt: page.updatedAt,
1291
- authors: page.authors,
1292
- tags: page.tags,
1293
- meta: page.meta
1294
- };
1295
- }
1296
- if (node.type === "link")
1297
- return {
1298
- type: "link",
1299
- id: node.id,
1300
- title: node.title,
1301
- href: node.url
1302
- };
1303
- let children = node.children?.map(
1304
- (child) => normalizeTreeNode(child, group, pagesByFileID, routesByFileID, options)
1305
- ).filter((child) => child !== null) ?? [];
1306
- if (node.type === "folder") {
1307
- let referencedGroup = resolveGroupReference(
1308
- options.config,
1309
- group,
1310
- node.group ?? node.id
1311
- ), referencedChildren = children.length > 0 || !referencedGroup ? children : options.childTreeForGroup(referencedGroup);
1312
- return {
1313
- type: "folder",
1314
- id: node.id,
1315
- title: node.title,
1316
- path: referencedGroup ? options.config.groups[referencedGroup]?.path : void 0,
1317
- group: referencedGroup ?? group,
1318
- children: referencedChildren
1319
- };
1320
- }
1321
- return {
1322
- type: node.type,
1323
- id: node.id,
1324
- title: node.title,
1325
- group,
1326
- children
1327
- };
1349
+ function nodeId(group, source, type) {
1350
+ return createHash("sha256").update(`${group}\0${type}\0${source}`).digest("hex").slice(0, 16);
1328
1351
  }
1329
- function defaultTree(group, pages, routesByFileID) {
1330
- return pages.flatMap((page) => {
1331
- let route = routesByFileID.get(page.fileID);
1332
- return route ? {
1333
- type: "page",
1334
- fileID: page.fileID,
1335
- title: page.title,
1336
- path: route.path,
1352
+ function navigationError(message, group, target, source) {
1353
+ return new AppError2({
1354
+ code: ERROR_CODES.CONFIG_GROUP_INVALID_SCHEMA,
1355
+ message,
1356
+ details: {
1337
1357
  group,
1338
- description: page.description,
1339
- og: page.og,
1340
- status: page.status,
1341
- publishedAt: page.publishedAt,
1342
- updatedAt: page.updatedAt,
1343
- authors: page.authors,
1344
- tags: page.tags,
1345
- meta: page.meta
1346
- } : [];
1347
- }).sort((a, b) => a.path.localeCompare(b.path));
1348
- }
1349
- function configuredPageIds(nodes) {
1350
- let ids = /* @__PURE__ */ new Set();
1351
- for (let node of nodes ?? []) {
1352
- if (node.type === "page") {
1353
- ids.add(node.file);
1354
- continue;
1358
+ target,
1359
+ ...source ? { propertyPath: source } : {},
1360
+ correction: "Correct the navigation entry and save the group config."
1361
+ }
1362
+ });
1363
+ }
1364
+ function defaultItems(options) {
1365
+ let remaining = new Map(options.pages.map((page) => [page.fileID, page])), sortWarningReported = !1;
1366
+ function folderItems(folder) {
1367
+ let names = /* @__PURE__ */ new Set();
1368
+ for (let fileID of remaining.keys()) {
1369
+ if (folder && !fileID.startsWith(`${folder}/`)) continue;
1370
+ let name = fileID.slice(folder ? folder.length + 1 : 0);
1371
+ names.add(name.split("/")[0] ?? name);
1372
+ }
1373
+ let items2 = [];
1374
+ for (let name of [...names].sort((a, b) => a.localeCompare(b))) {
1375
+ let prefix = folder ? `${folder}/${name}` : name, file = remaining.get(prefix);
1376
+ if (file) {
1377
+ remaining.delete(prefix);
1378
+ let route = options.routes.get(file.fileID);
1379
+ if (!route) continue;
1380
+ items2.push({
1381
+ id: nodeId(options.group, file.fileID, "page"),
1382
+ type: "page",
1383
+ label: file.frontmatter.title,
1384
+ href: route.path,
1385
+ page: {
1386
+ fileID: file.fileID,
1387
+ group: file.group,
1388
+ path: file.path,
1389
+ frontmatter: file.frontmatter
1390
+ }
1391
+ });
1392
+ continue;
1393
+ }
1394
+ let children = folderItems(prefix);
1395
+ children.length && items2.push({
1396
+ id: nodeId(options.group, prefix, "folder"),
1397
+ type: "folder",
1398
+ label: name,
1399
+ children
1400
+ });
1355
1401
  }
1356
- if ("children" in node)
1357
- for (let id of configuredPageIds(node.children))
1358
- ids.add(id);
1402
+ return sortItems(items2);
1403
+ }
1404
+ let items = folderItems("");
1405
+ for (let child of options.childGroups)
1406
+ items.push(options.makeGroup(child, "page"));
1407
+ return items;
1408
+ function sortItems(items2) {
1409
+ let sort = options.sort;
1410
+ if (!sort)
1411
+ return options.sortWarning && !sortWarningReported && (sortWarningReported = !0, options.onWarning?.({
1412
+ code: "NAVIGATION_SORT_CONFIG_INVALID",
1413
+ level: "warning",
1414
+ message: `Group "${options.group}": ${options.sortWarning}`,
1415
+ details: {
1416
+ group: options.group,
1417
+ propertyPath: "navigation.sort"
1418
+ }
1419
+ })), items2;
1420
+ let result = sortNavigationEntries({
1421
+ entries: items2.map(
1422
+ (entry) => entry.type === "page" ? {
1423
+ kind: "page",
1424
+ entry,
1425
+ sourcePath: `${options.contentPath.replace(/\\/g, "/")}/${entry.page.fileID}.mdx`,
1426
+ frontmatter: entry.page.frontmatter
1427
+ } : { kind: "other", entry }
1428
+ ),
1429
+ field: sort.field,
1430
+ type: sort.type,
1431
+ direction: sort.direction
1432
+ });
1433
+ for (let warning2 of result.warnings)
1434
+ options.onWarning?.({
1435
+ code: warning2.code,
1436
+ level: "warning",
1437
+ message: warning2.code === "NAVIGATION_SORT_VALUE_MISSING" ? `Navigation sort field "${warning2.field}" is missing from ${warning2.file}; the page sorts after valid values.` : `Navigation sort field "${warning2.field}" is invalid in ${warning2.file}; the page sorts after valid values.`,
1438
+ details: {
1439
+ group: options.group,
1440
+ field: warning2.field,
1441
+ file: warning2.file
1442
+ }
1443
+ });
1444
+ return result.entries;
1359
1445
  }
1360
- return ids;
1361
1446
  }
1362
- function createManifest(config, content) {
1447
+ function createManifest(config, content, onNavigationWarning) {
1363
1448
  let routesByGroup = /* @__PURE__ */ new Map();
1364
1449
  for (let route of content.routes) {
1365
1450
  let groupRoutes = routesByGroup.get(route.group);
1366
1451
  groupRoutes || (groupRoutes = /* @__PURE__ */ new Map(), routesByGroup.set(route.group, groupRoutes)), groupRoutes.has(route.fileID) || groupRoutes.set(route.fileID, route);
1367
1452
  }
1368
- let pages = [], pagesByGroup = /* @__PURE__ */ new Map(), pagesByFileIDByGroup = /* @__PURE__ */ new Map();
1453
+ let pages = [], pagesByGroup = /* @__PURE__ */ new Map(), sourcePathToPage = /* @__PURE__ */ new Map();
1369
1454
  for (let file of content.files) {
1370
1455
  let route = routesByGroup.get(file.group)?.get(file.fileID);
1371
1456
  if (!route) continue;
1372
1457
  let page = {
1373
1458
  fileID: file.fileID,
1374
1459
  group: file.group,
1375
- slug: route.slug,
1376
1460
  path: route.path,
1377
- title: file.title,
1378
- description: file.description,
1379
- og: file.og,
1380
- status: file.status,
1381
- publishedAt: file.publishedAt,
1382
- updatedAt: file.updatedAt,
1383
- authors: file.authors,
1384
- tags: file.tags,
1385
- meta: file.meta,
1461
+ frontmatter: file.frontmatter,
1386
1462
  body: file.body
1387
1463
  };
1388
1464
  pages.push(page);
1389
1465
  let groupPages = pagesByGroup.get(page.group);
1390
1466
  groupPages ? groupPages.push(page) : pagesByGroup.set(page.group, [page]);
1391
- let pagesByFileID = pagesByFileIDByGroup.get(page.group);
1392
- pagesByFileID || (pagesByFileID = /* @__PURE__ */ new Map(), pagesByFileIDByGroup.set(page.group, pagesByFileID)), pagesByFileID.has(page.fileID) || pagesByFileID.set(page.fileID, page);
1467
+ let group = config.groups[file.group];
1468
+ if (!group) continue;
1469
+ let sourcePath = `${group.__meta.relativeContentPath.replace(/\\/g, "/")}/${file.relativePath.replace(/\\/g, "/")}`;
1470
+ sourcePathToPage.set(sourcePath, page);
1393
1471
  }
1394
- let configuredPageIDsByGroup = /* @__PURE__ */ new Map();
1395
- for (let group of Object.values(config.groups))
1396
- configuredPageIDsByGroup.set(
1397
- group.key,
1398
- configuredPageIds(group.tree)
1399
- );
1400
- let treeCache = /* @__PURE__ */ new Map();
1401
- function groupTree(groupKey, stack = /* @__PURE__ */ new Set()) {
1402
- let cached = treeCache.get(groupKey);
1403
- if (cached) return cached;
1404
- if (stack.has(groupKey)) return [];
1472
+ let groupEntries = Object.values(config.groups), childrenByGroup = /* @__PURE__ */ new Map();
1473
+ for (let group of groupEntries) {
1474
+ if (!group.parentKey) continue;
1475
+ let children = childrenByGroup.get(group.parentKey) ?? [];
1476
+ children.push(group.key), childrenByGroup.set(group.parentKey, children);
1477
+ }
1478
+ for (let children of childrenByGroup.values())
1479
+ children.sort((a, b) => a.localeCompare(b));
1480
+ function groupNode(groupKey, presentation, label, fallback, stack, placementPath) {
1481
+ if (stack.has(groupKey))
1482
+ throw navigationError(
1483
+ `Recursive group placement for "${groupKey}".`,
1484
+ groupKey,
1485
+ groupKey
1486
+ );
1487
+ let group = config.groups[groupKey];
1488
+ if (!group)
1489
+ throw navigationError(
1490
+ `Navigation references missing group "${groupKey}".`,
1491
+ groupKey,
1492
+ groupKey
1493
+ );
1494
+ let children = buildGroupItems(
1495
+ groupKey,
1496
+ group.localNavigation ?? fallback ?? group.navigation,
1497
+ /* @__PURE__ */ new Set([...stack, groupKey]),
1498
+ fallback
1499
+ ), route = group.path;
1500
+ return {
1501
+ id: nodeId(groupKey, placementPath, "group"),
1502
+ type: "group",
1503
+ label: label ?? group.title ?? groupKey,
1504
+ groupKey,
1505
+ route,
1506
+ presentation,
1507
+ ...presentation === "page" ? { href: route } : {},
1508
+ children
1509
+ };
1510
+ }
1511
+ function buildGroupItems(groupKey, navigation, stack = /* @__PURE__ */ new Set(), inheritedFallback) {
1405
1512
  let group = config.groups[groupKey];
1406
1513
  if (!group) return [];
1407
- stack.add(groupKey);
1408
- let groupPages = pagesByGroup.get(group.key) ?? [], pagesByFileID = pagesByFileIDByGroup.get(group.key) ?? /* @__PURE__ */ new Map(), routesByFileID = routesByGroup.get(group.key) ?? /* @__PURE__ */ new Map(), configTree = group.tree, configuredPageIDs = configuredPageIDsByGroup.get(group.key), tree2 = configTree ? [
1409
- ...configTree.map(
1410
- (node) => normalizeTreeNode(
1411
- node,
1412
- group.key,
1413
- pagesByFileID,
1414
- routesByFileID,
1415
- {
1416
- childTreeForGroup: (childGroup) => groupTree(childGroup, new Set(stack)),
1417
- config
1418
- }
1419
- )
1420
- ).filter((node) => node !== null),
1421
- ...defaultTree(
1422
- group.key,
1423
- groupPages.filter((page) => !configuredPageIDs?.has(page.fileID)),
1424
- routesByFileID
1514
+ let groupPages = pagesByGroup.get(groupKey) ?? [], routesByFileID = routesByGroup.get(groupKey) ?? /* @__PURE__ */ new Map(), groupContentPath = group?.__meta.relativeContentPath ?? "content", placedPages = /* @__PURE__ */ new Set(), placedGroups = /* @__PURE__ */ new Set();
1515
+ function normalizeEntry(entry, source) {
1516
+ let id = nodeId(groupKey, source, entry.type);
1517
+ if (entry.type === "page") {
1518
+ if (entry.group) {
1519
+ let target = entry.group, targetGroup = config.groups[target];
1520
+ if (!targetGroup)
1521
+ throw navigationError(
1522
+ `Navigation references missing group "${target}".`,
1523
+ groupKey,
1524
+ target
1525
+ );
1526
+ if (targetGroup.parentKey !== groupKey)
1527
+ throw navigationError(
1528
+ `Group "${target}" must be a child of "${groupKey}".`,
1529
+ groupKey,
1530
+ target
1531
+ );
1532
+ if (placedGroups.has(target))
1533
+ throw navigationError(
1534
+ `Group "${target}" appears more than once in navigation.`,
1535
+ groupKey,
1536
+ target
1537
+ );
1538
+ return placedGroups.add(target), groupNode(
1539
+ target,
1540
+ "page",
1541
+ entry.label,
1542
+ entry.navigation ?? inheritedFallback,
1543
+ stack,
1544
+ source
1545
+ );
1546
+ }
1547
+ let sourcePath = entry.file ?? "", page = sourcePathToPage.get(sourcePath), expectedPrefix = `${groupContentPath.replace(/\\/g, "/")}/`;
1548
+ if (!sourcePath.startsWith(expectedPrefix))
1549
+ throw navigationError(
1550
+ `Page reference "${entry.file ?? ""}" does not belong to "${groupKey}".`,
1551
+ groupKey,
1552
+ entry.file ?? ""
1553
+ );
1554
+ if (!page || page.group !== groupKey) return null;
1555
+ if (placedPages.has(page.fileID))
1556
+ throw navigationError(
1557
+ `Page "${entry.file}" appears more than once in navigation.`,
1558
+ groupKey,
1559
+ entry.file ?? ""
1560
+ );
1561
+ placedPages.add(page.fileID);
1562
+ let route = routesByFileID.get(page.fileID);
1563
+ if (!route)
1564
+ throw navigationError(
1565
+ `Page route is missing for "${entry.file}".`,
1566
+ groupKey,
1567
+ entry.file ?? ""
1568
+ );
1569
+ let children = entry.items?.map((child, index) => normalizeEntry(child, `${source}.${index}`)).filter((child) => child !== null);
1570
+ return {
1571
+ id,
1572
+ type: "page",
1573
+ label: entry.label ?? page.frontmatter.title,
1574
+ href: route.path,
1575
+ page: {
1576
+ fileID: page.fileID,
1577
+ group: page.group,
1578
+ path: page.path,
1579
+ frontmatter: page.frontmatter
1580
+ },
1581
+ ...children?.length ? { children } : {}
1582
+ };
1583
+ }
1584
+ if (entry.type === "folder" && entry.group) {
1585
+ let target = entry.group, targetGroup = config.groups[target];
1586
+ if (!targetGroup)
1587
+ throw navigationError(
1588
+ `Navigation references missing group "${target}".`,
1589
+ groupKey,
1590
+ target
1591
+ );
1592
+ if (targetGroup.parentKey !== groupKey)
1593
+ throw navigationError(
1594
+ `Group "${target}" must be a child of "${groupKey}".`,
1595
+ groupKey,
1596
+ target
1597
+ );
1598
+ if (placedGroups.has(target))
1599
+ throw navigationError(
1600
+ `Group "${target}" appears more than once in navigation.`,
1601
+ groupKey,
1602
+ target
1603
+ );
1604
+ return placedGroups.add(target), groupNode(
1605
+ target,
1606
+ "folder",
1607
+ entry.label,
1608
+ entry.navigation ?? inheritedFallback,
1609
+ stack,
1610
+ source
1611
+ );
1612
+ }
1613
+ if (entry.type === "section" || entry.type === "folder") {
1614
+ let children = entry.items?.map((child, index) => normalizeEntry(child, `${source}.${index}`)).filter((child) => child !== null) ?? [];
1615
+ return { id, type: entry.type, label: entry.label, children };
1616
+ }
1617
+ return entry.type === "link" ? { id, type: "link", label: entry.label, href: entry.url } : entry.type === "view" ? {
1618
+ id,
1619
+ type: "view",
1620
+ ...entry.id ? { viewId: entry.id } : {},
1621
+ ...entry.label ? { label: entry.label } : {},
1622
+ items: entry.items.map((child, index) => normalizeEntry(child, `${source}.${index}`)).filter((child) => child !== null)
1623
+ } : {
1624
+ id,
1625
+ type: "switch-view",
1626
+ label: entry.label,
1627
+ target: entry.target
1628
+ };
1629
+ }
1630
+ let items = navigation?.items.map(
1631
+ (entry, index) => normalizeEntry(entry, `navigation.items.${index}`)
1632
+ ).filter((entry) => entry !== null) ?? [], automatic = defaultItems({
1633
+ group: groupKey,
1634
+ pages: groupPages.filter((page) => !placedPages.has(page.fileID)),
1635
+ routes: routesByFileID,
1636
+ childGroups: (childrenByGroup.get(groupKey) ?? []).filter(
1637
+ (key) => !placedGroups.has(key)
1638
+ ),
1639
+ contentPath: groupContentPath,
1640
+ sort: navigation?.sort,
1641
+ sortWarning: navigation?.sortWarning,
1642
+ onWarning: onNavigationWarning,
1643
+ makeGroup: (key, presentation, label) => groupNode(
1644
+ key,
1645
+ presentation,
1646
+ label,
1647
+ inheritedFallback,
1648
+ stack,
1649
+ `default:${key}`
1650
+ )
1651
+ });
1652
+ return items.push(...automatic), items;
1653
+ }
1654
+ let rootKeys = groupEntries.filter((group) => !group.parentKey).map((group) => group.key).sort((a, b) => a.localeCompare(b)), navigationByRoot = /* @__PURE__ */ new Map();
1655
+ for (let rootGroup of rootKeys)
1656
+ navigationByRoot.set(rootGroup, {
1657
+ rootGroup,
1658
+ items: buildGroupItems(
1659
+ rootGroup,
1660
+ config.groups[rootGroup]?.localNavigation ?? config.groups[rootGroup]?.navigation
1425
1661
  )
1426
- ] : defaultTree(group.key, groupPages, routesByFileID);
1427
- return stack.delete(groupKey), treeCache.set(groupKey, tree2), tree2;
1662
+ });
1663
+ for (let [rootGroup, navigation] of navigationByRoot) {
1664
+ let inspect2 = function(nodes, path) {
1665
+ for (let [index, node] of nodes.entries()) {
1666
+ let source = `${path}.${index}`;
1667
+ if (node.type === "view" && node.viewId) {
1668
+ if (views.has(node.viewId))
1669
+ throw navigationError(
1670
+ `View ID "${node.viewId}" is duplicated.`,
1671
+ rootGroup,
1672
+ node.viewId,
1673
+ source
1674
+ );
1675
+ views.set(node.viewId, source);
1676
+ }
1677
+ node.type === "switch-view" && switches.push({ target: node.target, source }), "children" in node && node.children && inspect2(node.children, source), node.type === "view" && inspect2(node.items, source);
1678
+ }
1679
+ };
1680
+ var inspect = inspect2;
1681
+ let views = /* @__PURE__ */ new Map(), switches = [];
1682
+ inspect2(navigation.items, "navigation.items");
1683
+ for (let control of switches) {
1684
+ let viewTarget = typeof control.target == "string" ? void 0 : control.target.view, groupTarget = typeof control.target == "string" ? control.target : void 0;
1685
+ if (viewTarget && !views.has(viewTarget))
1686
+ throw navigationError(
1687
+ `Switch-view target "${viewTarget}" does not exist.`,
1688
+ rootGroup,
1689
+ viewTarget,
1690
+ control.source
1691
+ );
1692
+ if (groupTarget && !config.groups[groupTarget] && !views.has(groupTarget))
1693
+ throw navigationError(
1694
+ `Switch-view target "${groupTarget}" is neither a group nor a named view.`,
1695
+ rootGroup,
1696
+ groupTarget,
1697
+ control.source
1698
+ );
1699
+ }
1428
1700
  }
1429
1701
  let groups = Object.fromEntries(
1430
- Object.values(config.groups).map((group) => {
1431
- let groupPages = pagesByGroup.get(group.key) ?? [], groupData = {
1432
- key: group.key,
1433
- title: group.title ?? group.key,
1434
- path: group.path,
1435
- origin: group.origin ?? (group.key === ROOT_CONTENT_GROUP_KEY ? void 0 : config.origin),
1436
- ancestry: group.ancestry,
1437
- ...group.og ? { og: group.og } : {},
1438
- meta: group.meta,
1439
- tree: groupTree(group.key),
1440
- pages: groupPages
1702
+ groupEntries.map((group) => {
1703
+ let rootGroup = group.ancestry[0] ?? group.key, navigation = navigationByRoot.get(rootGroup) ?? {
1704
+ rootGroup,
1705
+ items: []
1441
1706
  };
1442
- return [group.key, groupData];
1707
+ return [
1708
+ group.key,
1709
+ {
1710
+ key: group.key,
1711
+ title: group.title ?? group.key,
1712
+ path: group.path,
1713
+ origin: group.origin ?? (group.key === ROOT_CONTENT_GROUP_KEY ? void 0 : config.origin),
1714
+ ancestry: group.ancestry,
1715
+ ...group.metadata ? { metadata: group.metadata } : {},
1716
+ meta: group.meta,
1717
+ navigation,
1718
+ pages: pagesByGroup.get(group.key) ?? []
1719
+ }
1720
+ ];
1443
1721
  })
1444
- ), tree = Object.values(groups).filter((group) => !config.groups[group.key]?.parentKey).flatMap(
1445
- (group) => group.key === ROOT_CONTENT_GROUP_KEY ? group.tree : [
1446
- {
1447
- type: "section",
1448
- id: group.key,
1449
- title: group.title,
1450
- path: group.path,
1451
- group: group.key,
1452
- children: group.tree
1453
- }
1454
- ]
1455
1722
  ), manifest = {
1456
1723
  site: content.site,
1457
1724
  groups,
1458
- tree,
1459
1725
  routes: content.routes,
1460
1726
  pages,
1461
1727
  contentHash: ""
@@ -1466,7 +1732,8 @@ function createManifest(config, content) {
1466
1732
  // src/lib/compiler/source/prepare-publish-os-source.ts
1467
1733
  async function preparePublishOsSource({
1468
1734
  publishOsDir,
1469
- sourceIdentity
1735
+ sourceIdentity,
1736
+ includeDrafts = !1
1470
1737
  }) {
1471
1738
  try {
1472
1739
  let sourceRoot = resolve5(publishOsDir), boundary = await assertPublishOsSourceBoundary(
@@ -1481,28 +1748,35 @@ async function preparePublishOsSource({
1481
1748
  sourceRoot,
1482
1749
  config,
1483
1750
  content
1484
- });
1751
+ }), sourceDiagnostics = [
1752
+ ...boundary.nestedSources.map((file) => ({
1753
+ code: "SOURCE_NESTED_EXCLUDED",
1754
+ level: "warning",
1755
+ message: "Nested publish.os source is excluded from this site.",
1756
+ details: { file }
1757
+ })),
1758
+ ...content.warnings,
1759
+ ...missingPages,
1760
+ ...faviconsAndLogos.warnings,
1761
+ ...media.warnings.map((warning2) => ({
1762
+ ...warning2,
1763
+ level: "warning"
1764
+ }))
1765
+ ];
1766
+ if (missingPages.some((diagnostic) => diagnostic.level === "error"))
1767
+ return { diagnostics: sourceDiagnostics };
1768
+ let navigationWarnings = [], manifest = createManifest(
1769
+ config,
1770
+ includeDrafts ? { ...content, routes: content.candidateRoutes } : content,
1771
+ (warning2) => navigationWarnings.push(warning2)
1772
+ );
1485
1773
  return {
1486
- diagnostics: [
1487
- ...boundary.nestedSources.map((file) => ({
1488
- code: "SOURCE_NESTED_EXCLUDED",
1489
- level: "warning",
1490
- message: "Nested publish.os source is excluded from this site.",
1491
- details: { file }
1492
- })),
1493
- ...content.warnings,
1494
- ...missingPages,
1495
- ...faviconsAndLogos.warnings,
1496
- ...media.warnings.map((warning2) => ({
1497
- ...warning2,
1498
- level: "warning"
1499
- }))
1500
- ],
1774
+ diagnostics: [...sourceDiagnostics, ...navigationWarnings],
1501
1775
  source: {
1502
1776
  assets: { faviconsAndLogos, media },
1503
1777
  config,
1504
1778
  content,
1505
- manifest: createManifest(config, content)
1779
+ manifest
1506
1780
  }
1507
1781
  };
1508
1782
  } catch (error) {
@@ -1520,12 +1794,12 @@ async function preparePublishOsSource({
1520
1794
 
1521
1795
  // src/lib/compiler/theme/theme-token-diagnostics.ts
1522
1796
  import { readdirSync, readFileSync } from "fs";
1523
- import { extname as extname2, join as join3, resolve as resolve6 } from "path";
1797
+ import { extname as extname3, join as join3, resolve as resolve6 } from "path";
1524
1798
  import postcss from "postcss";
1525
1799
  function cssFiles(directory) {
1526
1800
  return readdirSync(directory, { withFileTypes: !0 }).flatMap((entry) => {
1527
1801
  let path = join3(directory, entry.name);
1528
- return entry.isDirectory() ? cssFiles(path) : extname2(entry.name) === ".css" ? [path] : [];
1802
+ return entry.isDirectory() ? cssFiles(path) : extname3(entry.name) === ".css" ? [path] : [];
1529
1803
  });
1530
1804
  }
1531
1805
  function readDesignTokens() {
@@ -1586,17 +1860,19 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
1586
1860
  "GroupContext",
1587
1861
  "LayoutProps",
1588
1862
  "ManifestLike",
1589
- "NavFolder",
1590
- "NavItem",
1591
- "NavLink",
1592
- "NavPage",
1593
- "NavSection",
1863
+ "NavigationContainerNode",
1864
+ "NavigationGroupNode",
1865
+ "NavigationLinkNode",
1866
+ "NavigationNode",
1867
+ "NavigationPageNode",
1868
+ "NavigationSwitchViewNode",
1869
+ "NavigationViewNode",
1594
1870
  "PageData",
1595
1871
  "PageProps",
1872
+ "ResolvedNavigation",
1596
1873
  "RootLayoutProps",
1597
1874
  "RootPageProps",
1598
- "SiteData",
1599
- "TreeNode"
1875
+ "SiteData"
1600
1876
  ]), ROUTE_EXECUTION_EXPORTS = [
1601
1877
  "runtime",
1602
1878
  "dynamic",
@@ -1646,7 +1922,7 @@ function normalizeLocalFontSourcePath(value) {
1646
1922
  return value.replace(/^\/+/, "").replace(/^\.\//, "");
1647
1923
  }
1648
1924
  function isFontFilePath(path) {
1649
- return FONT_FILE_EXTENSIONS.has(extname3(path).toLowerCase());
1925
+ return FONT_FILE_EXTENSIONS.has(extname4(path).toLowerCase());
1650
1926
  }
1651
1927
  function isValidSourceFontFilePath(path) {
1652
1928
  return path.startsWith("fonts/") && isFontFilePath(path);
@@ -1691,7 +1967,7 @@ async function validateConfiguredLocalFontSources({
1691
1967
  return sourcePaths;
1692
1968
  }
1693
1969
  function isHardRejectedPath(path) {
1694
- let name = basename(path);
1970
+ let name = basename2(path);
1695
1971
  return isReservedFrameworkPath(path) || path.split("/").some((segment) => ["packages", "scripts"].includes(segment)) || LOCKFILES.has(name) || name === "tsconfig.json" && path !== "tsconfig.json";
1696
1972
  }
1697
1973
  function isAllowedBareImport(specifier) {
@@ -1793,7 +2069,7 @@ function importDetails(options) {
1793
2069
  };
1794
2070
  }
1795
2071
  function scriptKindForPath(path) {
1796
- switch (extname3(path).toLowerCase()) {
2072
+ switch (extname4(path).toLowerCase()) {
1797
2073
  case ".tsx":
1798
2074
  return ts2.ScriptKind.TSX;
1799
2075
  case ".jsx":
@@ -2032,7 +2308,7 @@ function validateSourceFilePath({
2032
2308
  groupFolders,
2033
2309
  store
2034
2310
  }) {
2035
- if (basename(file) === "leaf.tsx" && (file === "leaf.tsx" || groupFolders.has(dirname(file))) || file.split("/").includes("(leaf)")) {
2311
+ if (basename2(file) === "leaf.tsx" && (file === "leaf.tsx" || groupFolders.has(dirname(file))) || file.split("/").includes("(leaf)")) {
2036
2312
  let suggestedPath = file.replaceAll("(leaf)", "(page)").replace(/leaf\.tsx$/, "page.tsx");
2037
2313
  return addFileError(
2038
2314
  store,
@@ -2059,7 +2335,7 @@ function validateSourceFontFile({
2059
2335
  return isValidSourceFontFilePath(file) ? (configured || addFileWarning(
2060
2336
  store,
2061
2337
  "FRAMEWORK_FONT_FILE_UNUSED",
2062
- `Font file "${basename(file)}" is not used by the current font configuration and will not be included in the site.`,
2338
+ `Font file "${basename2(file)}" is not used by the current font configuration and will not be included in the site.`,
2063
2339
  file,
2064
2340
  { fontSourcePath: `/${file}`, suggestion: fontConfigHint(file) }
2065
2341
  ), !0) : (addFileWarning(
@@ -2069,7 +2345,7 @@ function validateSourceFontFile({
2069
2345
  file,
2070
2346
  {
2071
2347
  fontSourcePath: `/${file}`,
2072
- suggestion: fontConfigHint(`fonts/${basename(file)}`)
2348
+ suggestion: fontConfigHint(`fonts/${basename2(file)}`)
2073
2349
  }
2074
2350
  ), !0);
2075
2351
  }
@@ -2110,7 +2386,7 @@ async function validateSourceFileContents({
2110
2386
  checkedCss,
2111
2387
  store
2112
2388
  }) {
2113
- let extension = extname3(file), sourceModule = SOURCE_EXTENSIONS.has(extension), validatesAttribution = ATTRIBUTION_VALIDATION_EXTENSIONS.has(extension), scansLegacyIcons = sourceModule || MARKDOWN_ICON_SCAN_EXTENSIONS.has(extension) && !file.startsWith("assets/");
2389
+ let extension = extname4(file), sourceModule = SOURCE_EXTENSIONS.has(extension), validatesAttribution = ATTRIBUTION_VALIDATION_EXTENSIONS.has(extension), scansLegacyIcons = sourceModule || MARKDOWN_ICON_SCAN_EXTENSIONS.has(extension) && !file.startsWith("assets/");
2114
2390
  if (!validatesAttribution && !scansLegacyIcons) return;
2115
2391
  let absolutePath = resolve7(publishOsDir, file), source = await readFile5(absolutePath, "utf-8");
2116
2392
  validatesAttribution && validateAttributionReferences(source, file, store), extension === ".css" && (validateThemeTokenSpelling(source, file, store), await validateCssDependencies({
@@ -2337,10 +2613,18 @@ async function validateImportSpecifier(options) {
2337
2613
  importDetails(options)
2338
2614
  );
2339
2615
  }
2340
- function toValidationDiagnostic(diagnostic) {
2616
+ function toValidationDiagnostic(diagnostic, sourceRoot) {
2617
+ let path = diagnostic.details?.file ?? diagnostic.details?.path ?? diagnostic.file, file = typeof path == "string" && isSameOrDescendant(sourceRoot, resolve7(sourceRoot, path)) ? relative4(sourceRoot, resolve7(sourceRoot, path)).split(sep).join("/") : void 0;
2341
2618
  return {
2342
2619
  ...diagnostic,
2343
- file: typeof diagnostic.details?.file == "string" ? diagnostic.details.file : diagnostic.file,
2620
+ message: sourceRoot === parse(sourceRoot).root ? diagnostic.message : diagnostic.message.replaceAll(`${sourceRoot}${sep}`, ""),
2621
+ file,
2622
+ ...typeof diagnostic.details?.field == "string" && !diagnostic.details.propertyPath ? {
2623
+ details: {
2624
+ ...diagnostic.details,
2625
+ propertyPath: diagnostic.details.field
2626
+ }
2627
+ } : {},
2344
2628
  line: typeof diagnostic.details?.line == "number" ? diagnostic.details.line : diagnostic.line,
2345
2629
  column: typeof diagnostic.details?.column == "number" ? diagnostic.details.column : diagnostic.column
2346
2630
  };
@@ -2355,7 +2639,8 @@ async function validatePublishOsSource(options) {
2355
2639
  try {
2356
2640
  let prepared = options.sourcePreparation ?? await preparePublishOsSource({
2357
2641
  publishOsDir,
2358
- sourceIdentity: options.sourceIdentity
2642
+ sourceIdentity: options.sourceIdentity,
2643
+ includeDrafts: options.includeDrafts
2359
2644
  });
2360
2645
  for (let diagnostic of prepared.diagnostics)
2361
2646
  store.add(diagnostic);
@@ -2363,7 +2648,7 @@ async function validatePublishOsSource(options) {
2363
2648
  } catch (error) {
2364
2649
  error instanceof AppError ? store.error(error.code, error.message, error.details) : store.error("FRAMEWORK_VALIDATE_UNKNOWN", String(error));
2365
2650
  }
2366
- let allDiagnostics = store.all().map(toValidationDiagnostic), errors = allDiagnostics.filter((d) => d.level === "error"), warnings = allDiagnostics.filter((d) => d.level === "warning"), valid = errors.length === 0;
2651
+ let allDiagnostics = store.all().map((diagnostic) => toValidationDiagnostic(diagnostic, publishOsDir)), errors = allDiagnostics.filter((d) => d.level === "error"), warnings = allDiagnostics.filter((d) => d.level === "warning"), valid = errors.length === 0;
2367
2652
  return {
2368
2653
  valid,
2369
2654
  errors,