polen 0.9.0-next.5 → 0.9.0-next.7

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 (101) hide show
  1. package/README.md +42 -0
  2. package/build/api/builder/builder.d.ts +1 -0
  3. package/build/api/builder/builder.d.ts.map +1 -1
  4. package/build/api/builder/builder.js +1 -0
  5. package/build/api/builder/builder.js.map +1 -1
  6. package/build/api/config/configurator.d.ts +21 -5
  7. package/build/api/config/configurator.d.ts.map +1 -1
  8. package/build/api/config/configurator.js +12 -0
  9. package/build/api/config/configurator.js.map +1 -1
  10. package/build/api/config-resolver/resolve.d.ts +1 -1
  11. package/build/api/config-resolver/resolve.js +1 -1
  12. package/build/api/config-resolver/vite.d.ts.map +1 -1
  13. package/build/api/config-resolver/vite.js +1 -0
  14. package/build/api/config-resolver/vite.js.map +1 -1
  15. package/build/api/schema/read.d.ts +1 -1
  16. package/build/api/singletons/markdown/markdown.d.ts.map +1 -1
  17. package/build/api/singletons/markdown/markdown.js +33 -11
  18. package/build/api/singletons/markdown/markdown.js.map +1 -1
  19. package/build/api/utils/asset-url/asset-url.d.ts +20 -0
  20. package/build/api/utils/asset-url/asset-url.d.ts.map +1 -0
  21. package/build/api/utils/asset-url/asset-url.js +35 -0
  22. package/build/api/utils/asset-url/asset-url.js.map +1 -0
  23. package/build/api/utils/asset-url/index.d.ts +2 -0
  24. package/build/api/utils/asset-url/index.d.ts.map +1 -0
  25. package/build/api/utils/asset-url/index.js +2 -0
  26. package/build/api/utils/asset-url/index.js.map +1 -0
  27. package/build/api/vite/plugins/core.d.ts.map +1 -1
  28. package/build/api/vite/plugins/core.js +10 -4
  29. package/build/api/vite/plugins/core.js.map +1 -1
  30. package/build/api/vite/plugins/pages.d.ts.map +1 -1
  31. package/build/api/vite/plugins/pages.js +27 -2
  32. package/build/api/vite/plugins/pages.js.map +1 -1
  33. package/build/cli/commands/build.js +2 -0
  34. package/build/cli/commands/build.js.map +1 -1
  35. package/build/cli/commands/dev.js +2 -0
  36. package/build/cli/commands/dev.js.map +1 -1
  37. package/build/lib/shiki/index.d.ts +2 -0
  38. package/build/lib/shiki/index.d.ts.map +1 -0
  39. package/build/lib/shiki/index.js +2 -0
  40. package/build/lib/shiki/index.js.map +1 -0
  41. package/build/lib/shiki/shiki.d.ts +26 -0
  42. package/build/lib/shiki/shiki.d.ts.map +1 -0
  43. package/build/lib/shiki/shiki.js +105 -0
  44. package/build/lib/shiki/shiki.js.map +1 -0
  45. package/build/lib/vite-virtual/identifier.d.ts +2 -2
  46. package/build/project-data.d.ts +1 -0
  47. package/build/project-data.d.ts.map +1 -1
  48. package/build/template/components/CodeBlock.d.ts +17 -0
  49. package/build/template/components/CodeBlock.d.ts.map +1 -0
  50. package/build/template/components/CodeBlock.jsx +42 -0
  51. package/build/template/components/CodeBlock.jsx.map +1 -0
  52. package/build/template/components/Link.d.ts.map +1 -1
  53. package/build/template/components/Link.jsx +2 -1
  54. package/build/template/components/Link.jsx.map +1 -1
  55. package/build/template/entry.client.jsx +3 -0
  56. package/build/template/entry.client.jsx.map +1 -1
  57. package/build/template/routes/root.d.ts.map +1 -1
  58. package/build/template/routes/root.jsx +37 -2
  59. package/build/template/routes/root.jsx.map +1 -1
  60. package/build/template/server/manifest.d.ts +1 -1
  61. package/build/template/server/manifest.d.ts.map +1 -1
  62. package/build/template/server/manifest.js +6 -5
  63. package/build/template/server/manifest.js.map +1 -1
  64. package/build/template/server/render-page.d.ts.map +1 -1
  65. package/build/template/server/render-page.jsx +2 -1
  66. package/build/template/server/render-page.jsx.map +1 -1
  67. package/build/template/server/ssg/generate.d.ts.map +1 -1
  68. package/build/template/server/ssg/generate.js +50 -7
  69. package/build/template/server/ssg/generate.js.map +1 -1
  70. package/build/template/server/view.d.ts.map +1 -1
  71. package/build/template/server/view.js +4 -1
  72. package/build/template/server/view.js.map +1 -1
  73. package/package.json +10 -1
  74. package/src/api/builder/builder.ts +2 -0
  75. package/src/api/config/configurator.ts +34 -5
  76. package/src/api/config-resolver/resolve.ts +1 -1
  77. package/src/api/config-resolver/vite.ts +1 -0
  78. package/src/api/schema/read.ts +1 -1
  79. package/src/api/singletons/markdown/markdown.test.ts +89 -0
  80. package/src/api/singletons/markdown/markdown.ts +35 -13
  81. package/src/api/utils/asset-url/asset-url.test.ts +47 -0
  82. package/src/api/utils/asset-url/asset-url.ts +38 -0
  83. package/src/api/utils/asset-url/index.ts +1 -0
  84. package/src/api/vite/plugins/core.ts +10 -4
  85. package/src/api/vite/plugins/pages.ts +27 -2
  86. package/src/cli/commands/build.ts +5 -0
  87. package/src/cli/commands/dev.ts +5 -0
  88. package/src/lib/shiki/index.ts +1 -0
  89. package/src/lib/shiki/shiki.test.ts +107 -0
  90. package/src/lib/shiki/shiki.ts +161 -0
  91. package/src/lib/vite-virtual/identifier.ts +2 -2
  92. package/src/project-data.ts +1 -0
  93. package/src/template/components/CodeBlock.tsx +73 -0
  94. package/src/template/components/Link.tsx +4 -3
  95. package/src/template/entry.client.tsx +3 -0
  96. package/src/template/routes/root.tsx +37 -2
  97. package/src/template/server/manifest.ts +6 -3
  98. package/src/template/server/render-page.tsx +2 -1
  99. package/src/template/server/ssg/generate.ts +70 -7
  100. package/src/template/server/view.ts +4 -1
  101. package/src/template/styles/code-block.css +186 -0
@@ -1 +1 @@
1
- {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../../src/template/server/ssg/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAEtC,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAA;AAC1C,OAAO,YAAY,MAAM,sCAAsC,CAAA;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAE,IAA+B,EAAE,EAAE;IAChE,MAAM,OAAO,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;QAC1C,MAAM,oBAAoB,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1D,IAAI,oBAAoB,YAAY,QAAQ,EAAE,CAAC;YAC7C,OAAO,oBAAoB,CAAA;QAC7B,CAAC;QACD,OAAO,UAAU,CAAC,oBAAoB,CAAC,CAAA;IACzC,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,cAAc,EAAE,CAAA;IAEnC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAC7B,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE;QAC/C,WAAW,EAAE,EAAE;QACf,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI;KAC5C,CAAC,CAAA;IAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;IAC5E,CAAC;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../../src/template/server/ssg/generate.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/template/server/view.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI,sCAA8B,CAAA"}
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/template/server/view.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,IAAI,sCAEf,CAAA"}
@@ -1,4 +1,7 @@
1
1
  import { createStaticHandler } from 'react-router';
2
+ import PROJECT_DATA from 'virtual:polen/project/data.jsonsuper';
2
3
  import { routes } from '../routes.jsx';
3
- export const view = createStaticHandler(routes);
4
+ export const view = createStaticHandler(routes, {
5
+ basename: PROJECT_DATA.basePath === `/` ? undefined : PROJECT_DATA.basePath.slice(0, -1), // Remove trailing slash for React Router
6
+ });
4
7
  //# sourceMappingURL=view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"view.js","sourceRoot":"","sources":["../../../src/template/server/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,MAAM,CAAC,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA"}
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../../src/template/server/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,YAAY,MAAM,sCAAsC,CAAA;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,MAAM,CAAC,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,YAAY,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yCAAyC;CACpI,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polen",
3
- "version": "0.9.0-next.5",
3
+ "version": "0.9.0-next.7",
4
4
  "type": "module",
5
5
  "description": "A framework for delightful GraphQL developer portals",
6
6
  "author": {
@@ -84,6 +84,9 @@
84
84
  "@radix-ui/themes": "^3.2.1",
85
85
  "@rolldown/pluginutils": "1.0.0-beta.12",
86
86
  "@rollup/pluginutils": "^5.1.4",
87
+ "@shikijs/rehype": "^3.6.0",
88
+ "@shikijs/transformers": "^3.6.0",
89
+ "@shikijs/twoslash": "^3.6.0",
87
90
  "@swc/core": "^1.11.31",
88
91
  "@types/jsesc": "^3.0.3",
89
92
  "@vitejs/plugin-react": "^4.5.1",
@@ -105,16 +108,21 @@
105
108
  "react": "^19.1.0",
106
109
  "react-dom": "^19.1.0",
107
110
  "react-router": "^7.6.2",
111
+ "rehype-stringify": "^10.0.1",
108
112
  "remark": "^15.0.1",
109
113
  "remark-gfm": "^4.0.1",
110
114
  "remark-html": "^16.0.1",
115
+ "remark-parse": "^11.0.0",
116
+ "remark-rehype": "^11.1.2",
111
117
  "resolve.imports": "^2.0.3",
112
118
  "rolldown": "1.0.0-beta.12",
119
+ "shiki": "^3.6.0",
113
120
  "source-map": "^0.7.4",
114
121
  "superjson": "^2.2.2",
115
122
  "tinyglobby": "^0.2.14",
116
123
  "tsx": "^4.19.4",
117
124
  "typescript": "^5.8.3",
125
+ "unified": "^11.0.5",
118
126
  "vfile": "^6.0.3",
119
127
  "vite": "npm:rolldown-vite@~6.3.18",
120
128
  "vite-plugin-inspect": "^11.1.0",
@@ -170,6 +178,7 @@
170
178
  "test:unit": "vitest",
171
179
  "dev": "pnpm build:watch:emit",
172
180
  "dev:pokemon": "pnpm polen dev --project examples/pokemon",
181
+ "build:demos": "zx ./scripts/build-demos.mjs",
173
182
  "build:clean": "pnpm tsc --build tsconfig.build.json --clean && rm -rf build",
174
183
  "build": "tsc --build tsconfig.build.json",
175
184
  "build:watch": "tsc --build tsconfig.build.json --watch",
@@ -12,6 +12,7 @@ const buildDefaults = {
12
12
  interface BuildConfigInput {
13
13
  debug?: boolean
14
14
  architecture?: Config.BuildArchitecture
15
+ base?: string
15
16
  }
16
17
 
17
18
  export const build = async (buildConfigInput: BuildConfigInput) => {
@@ -22,6 +23,7 @@ export const build = async (buildConfigInput: BuildConfigInput) => {
22
23
  overrides: {
23
24
  build: {
24
25
  architecture: buildConfig.architecture,
26
+ ...(buildConfig.base ? { base: buildConfig.base } : {}),
25
27
  },
26
28
  advanced: {
27
29
  debug: buildConfig.debug,
@@ -24,7 +24,7 @@ export interface ConfigInput {
24
24
  *
25
25
  * Relative paths will be resolved relative to this config file.
26
26
  *
27
- * @defaultValue The directory where the config file is located.
27
+ * @default The directory where the config file is located.
28
28
  */
29
29
  root?: string
30
30
  /**
@@ -32,7 +32,7 @@ export interface ConfigInput {
32
32
  *
33
33
  * Powered by [Vite Inspect](https://github.com/antfu-collective/vite-plugin-inspect).
34
34
  *
35
- * @defaultValue true
35
+ * @default true
36
36
  */
37
37
  schema?: SchemaConfigInput
38
38
  schemaAugmentations?: SchemaAugmentation.Augmentation[]
@@ -42,12 +42,27 @@ export interface ConfigInput {
42
42
  *
43
43
  * Used in the navigation bar and in the title tag.
44
44
  *
45
- * @defaultValue `My Developer Portal`
45
+ * @default `My Developer Portal`
46
46
  */
47
47
  title?: string
48
48
  }
49
49
  build?: {
50
50
  architecture?: BuildArchitecture
51
+ /**
52
+ * Base public path for the deployed site.
53
+ *
54
+ * Use this when deploying to a subdirectory (e.g., GitHub Pages project sites).
55
+ *
56
+ * Examples:
57
+ * - `/` (default) - Deploy to root
58
+ * - `/my-project/` - Deploy to subdirectory
59
+ * - `/pr-123/` - PR preview deployments
60
+ *
61
+ * Must start and end with `/`.
62
+ *
63
+ * @default `/`
64
+ */
65
+ base?: string
51
66
  }
52
67
  advanced?: {
53
68
  explorer?: boolean
@@ -64,7 +79,7 @@ export interface ConfigInput {
64
79
  * - Global CLI usage against ephemeral projects e.g. a directory with just a
65
80
  * GraphQL Schema file.
66
81
  *
67
- * @defaultValue false
82
+ * @default false
68
83
  */
69
84
  isSelfContainedMode?: boolean
70
85
  /**
@@ -90,7 +105,7 @@ export interface ConfigInput {
90
105
  *
91
106
  * - build output is NOT minified.
92
107
  *
93
- * @defaultValue false
108
+ * @default false
94
109
  */
95
110
  debug?: boolean
96
111
  /**
@@ -140,6 +155,7 @@ export interface Config {
140
155
  _input: ConfigInput
141
156
  build: {
142
157
  architecture: BuildArchitecture
158
+ base: string
143
159
  }
144
160
  watch: {
145
161
  also: string[]
@@ -193,6 +209,7 @@ const configInputDefaults: Config = {
193
209
  },
194
210
  build: {
195
211
  architecture: BuildArchitecture.enum.ssg,
212
+ base: `/`,
196
213
  },
197
214
  schema: null,
198
215
  ssr: {
@@ -231,6 +248,18 @@ export const normalizeInput = async (
231
248
  config.build.architecture = configInput.build.architecture
232
249
  }
233
250
 
251
+ if (configInput?.build?.base !== undefined) {
252
+ // Validate base path
253
+ const base = configInput.build.base
254
+ if (!base.startsWith(`/`)) {
255
+ throw new Error(`Base path must start with "/". Provided: ${base}`)
256
+ }
257
+ if (!base.endsWith(`/`)) {
258
+ throw new Error(`Base path must end with "/". Provided: ${base}`)
259
+ }
260
+ config.build.base = base
261
+ }
262
+
234
263
  if (configInput?.advanced?.debug !== undefined) {
235
264
  config.advanced.debug = configInput.advanced.debug
236
265
  }
@@ -21,7 +21,7 @@ export const fromMemory = async (
21
21
  /**
22
22
  * Refer to `baseRootDirPath` parameter on {@link normalizeInput}.
23
23
  *
24
- * @defaultValue `process.cwd()`
24
+ * @default `process.cwd()`
25
25
  */
26
26
  baseRootDirPath?: string,
27
27
  ) => {
@@ -9,6 +9,7 @@ export const toViteUserConfig = (
9
9
  config: Config,
10
10
  ): ViteUserConfigWithPolen => {
11
11
  const viteUserConfigFromPolen: Vite.UserConfig = {
12
+ base: config.build.base,
12
13
  plugins: [Main(config)],
13
14
  }
14
15
 
@@ -6,7 +6,7 @@ export type DataSourceType = `file` | `directory` | `memory` | `data`
6
6
 
7
7
  export interface Config {
8
8
  /**
9
- * @defaultValue `true`
9
+ * @default `true`
10
10
  */
11
11
  enabled?: boolean
12
12
  useDataSources?: Arr.Maybe<DataSourceType>
@@ -0,0 +1,89 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { parse } from './markdown.js'
3
+
4
+ describe(`markdown parser with syntax highlighting`, () => {
5
+ test(`parse highlights code blocks`, async () => {
6
+ const markdown = `
7
+ # Hello
8
+
9
+ \`\`\`javascript
10
+ const x = 42
11
+ console.log(x)
12
+ \`\`\`
13
+ `
14
+ const result = await parse(markdown)
15
+
16
+ expect(result).toContain(`<h1>Hello</h1>`)
17
+ expect(result).toContain(`<pre`)
18
+ expect(result).toContain(`shiki`)
19
+ expect(result).toContain(`console`)
20
+ expect(result).toContain(`42`)
21
+ })
22
+
23
+ // Note: parseSync cannot be used with async rehype plugins like Shiki
24
+ // This is a known limitation - syntax highlighting requires async processing
25
+
26
+ test(`parse supports GraphQL syntax`, async () => {
27
+ const markdown = `
28
+ \`\`\`graphql
29
+ type Query {
30
+ user(id: ID!): User
31
+ }
32
+ \`\`\`
33
+ `
34
+ const result = await parse(markdown)
35
+
36
+ expect(result).toContain(`type`)
37
+ expect(result).toContain(`Query`)
38
+ // Check that both ID and ! are present (they may be in separate spans)
39
+ expect(result).toContain(`> ID<`)
40
+ expect(result).toContain(`>!</`)
41
+ })
42
+
43
+ test(`parse handles inline code`, async () => {
44
+ const markdown = `This is \`inline code\` in a sentence.`
45
+ const result = await parse(markdown)
46
+
47
+ expect(result).toContain(`<code>inline code</code>`)
48
+ })
49
+
50
+ test(`parse supports GitHub Flavored Markdown`, async () => {
51
+ const markdown = `
52
+ | Column 1 | Column 2 |
53
+ |----------|----------|
54
+ | Cell 1 | Cell 2 |
55
+
56
+ - [x] Task 1
57
+ - [ ] Task 2
58
+ `
59
+ const result = await parse(markdown)
60
+
61
+ expect(result).toContain(`<table>`)
62
+ expect(result).toContain(`<input`)
63
+ expect(result).toContain(`checked`)
64
+ })
65
+
66
+ test(`parse handles code blocks without language`, async () => {
67
+ const markdown = `
68
+ \`\`\`
69
+ plain text without language
70
+ \`\`\`
71
+ `
72
+ const result = await parse(markdown)
73
+
74
+ expect(result).toContain(`<pre`)
75
+ expect(result).toContain(`plain text without language`)
76
+ })
77
+
78
+ test(`parse preserves theme CSS variables`, async () => {
79
+ const markdown = `
80
+ \`\`\`javascript
81
+ const theme = "light"
82
+ \`\`\`
83
+ `
84
+ const result = await parse(markdown)
85
+
86
+ expect(result).toContain(`--shiki-light`)
87
+ expect(result).toContain(`--shiki-dark`)
88
+ })
89
+ })
@@ -1,21 +1,43 @@
1
- import * as Remark from 'remark'
2
- import RemarkGfm from 'remark-gfm'
3
- import RemarkHtml from 'remark-html'
1
+ import rehypeShiki from '@shikijs/rehype'
2
+ import rehypeStringify from 'rehype-stringify'
3
+ import remarkGfm from 'remark-gfm'
4
+ import remarkParse from 'remark-parse'
5
+ import remarkRehype from 'remark-rehype'
6
+ import { unified } from 'unified'
4
7
 
5
- export const parse = async (content: string): Promise<string> => {
6
- const result = await Remark.remark()
7
- .use(RemarkGfm)
8
- .use(RemarkHtml)
9
- .process(content)
8
+ // Create a processor with Shiki for syntax highlighting (async only)
9
+ const createProcessorWithShiki = () => {
10
+ return unified()
11
+ .use(remarkParse)
12
+ .use(remarkGfm)
13
+ .use(remarkRehype)
14
+ .use(rehypeShiki, {
15
+ themes: {
16
+ light: `github-light`,
17
+ dark: `tokyo-night`,
18
+ },
19
+ defaultColor: false,
20
+ cssVariablePrefix: `--shiki-`,
21
+ })
22
+ .use(rehypeStringify)
23
+ }
24
+
25
+ // Create a processor without syntax highlighting for sync processing
26
+ const createProcessorSync = () => {
27
+ return unified()
28
+ .use(remarkParse)
29
+ .use(remarkGfm)
30
+ .use(remarkRehype)
31
+ .use(rehypeStringify)
32
+ }
10
33
 
34
+ export const parse = async (content: string): Promise<string> => {
35
+ const result = await createProcessorWithShiki().process(content)
11
36
  return String(result)
12
37
  }
13
38
 
14
39
  export const parseSync = (content: string): string => {
15
- const result = Remark.remark()
16
- .use(RemarkGfm)
17
- .use(RemarkHtml)
18
- .processSync(content)
19
-
40
+ // Note: Syntax highlighting is not available in sync mode due to @shikijs/rehype being async-only
41
+ const result = createProcessorSync().processSync(content)
20
42
  return String(result)
21
43
  }
@@ -0,0 +1,47 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { assetUrl, faviconUrl, joinPaths, pageUrl } from './asset-url.js'
3
+
4
+ describe('asset-url helpers', () => {
5
+ describe('joinPaths', () => {
6
+ test('joins base path with asset path', () => {
7
+ expect(joinPaths('/', 'assets/style.css')).toBe('/assets/style.css')
8
+ expect(joinPaths('/my-app/', 'assets/style.css')).toBe('/my-app/assets/style.css')
9
+ expect(joinPaths('/my-app/', '/assets/style.css')).toBe('/my-app/assets/style.css')
10
+ })
11
+
12
+ test('handles trailing slash in base', () => {
13
+ expect(joinPaths('/base/', 'path')).toBe('/base/path')
14
+ })
15
+
16
+ test('handles leading slash in path', () => {
17
+ expect(joinPaths('/base/', '/path')).toBe('/base/path')
18
+ })
19
+ })
20
+
21
+ describe('assetUrl', () => {
22
+ test('creates asset URLs with base path', () => {
23
+ expect(assetUrl('assets/app.js', '/')).toBe('/assets/app.js')
24
+ expect(assetUrl('assets/app.js', '/my-app/')).toBe('/my-app/assets/app.js')
25
+ expect(assetUrl('/assets/app.js', '/my-app/')).toBe('/my-app/assets/app.js')
26
+ })
27
+ })
28
+
29
+ describe('faviconUrl', () => {
30
+ test('creates favicon URLs with base path', () => {
31
+ expect(faviconUrl('logo.ico', '/')).toBe('/logo.ico')
32
+ expect(faviconUrl('logo.ico', '/my-app/')).toBe('/my-app/logo.ico')
33
+ expect(faviconUrl('/logo.ico', '/my-app/')).toBe('/my-app/logo.ico')
34
+ })
35
+ })
36
+
37
+ describe('pageUrl', () => {
38
+ test('creates page URLs with base path', () => {
39
+ expect(pageUrl('/', '/')).toBe('/')
40
+ expect(pageUrl('', '/')).toBe('/')
41
+ expect(pageUrl('/', '/my-app/')).toBe('/my-app/')
42
+ expect(pageUrl('', '/my-app/')).toBe('/my-app/')
43
+ expect(pageUrl('about', '/my-app/')).toBe('/my-app/about')
44
+ expect(pageUrl('/about', '/my-app/')).toBe('/my-app/about')
45
+ })
46
+ })
47
+ })
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Helper functions for generating asset URLs that respect the base path configuration.
3
+ */
4
+
5
+ /**
6
+ * Join a base path with an asset path, handling leading/trailing slashes correctly.
7
+ */
8
+ export const joinPaths = (base: string, path: string): string => {
9
+ // Remove leading slash from path if present
10
+ const cleanPath = path.startsWith(`/`) ? path.slice(1) : path
11
+ // Base already has trailing slash by validation
12
+ return base + cleanPath
13
+ }
14
+
15
+ /**
16
+ * Create an asset URL from a path, respecting the base configuration.
17
+ */
18
+ export const assetUrl = (path: string, base: string): string => {
19
+ return joinPaths(base, path)
20
+ }
21
+
22
+ /**
23
+ * Create a favicon URL from a path, respecting the base configuration.
24
+ */
25
+ export const faviconUrl = (path: string, base: string): string => {
26
+ return assetUrl(path, base)
27
+ }
28
+
29
+ /**
30
+ * Create a page URL from a path, respecting the base configuration.
31
+ */
32
+ export const pageUrl = (path: string, base: string): string => {
33
+ // For pages, we want to ensure no double slashes
34
+ if (path === `/` || path === ``) {
35
+ return base
36
+ }
37
+ return joinPaths(base, path)
38
+ }
@@ -0,0 +1 @@
1
+ export * from './asset-url.ts'
@@ -190,9 +190,13 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
190
190
  const schemaNavbar = navbarData.get('schema')
191
191
  schemaNavbar.length = 0 // Clear existing
192
192
  if (schema) {
193
- schemaNavbar.push({ pathExp: `reference`, title: `Reference` })
193
+ // IMPORTANT: Always ensure paths start with '/' for React Router compatibility.
194
+ // Without the leading slash, React Router treats paths as relative, which causes
195
+ // hydration mismatches between SSR (where base path is prepended) and client
196
+ // (where basename is configured). This ensures consistent behavior.
197
+ schemaNavbar.push({ pathExp: `/reference`, title: `Reference` })
194
198
  if (schema.versions.length > 1) {
195
- schemaNavbar.push({ pathExp: `changelog`, title: `Changelog` })
199
+ schemaNavbar.push({ pathExp: `/changelog`, title: `Changelog` })
196
200
  }
197
201
  }
198
202
 
@@ -203,6 +207,7 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
203
207
  const projectData: ProjectData = {
204
208
  schema,
205
209
  faviconPath: `/logo.svg`,
210
+ basePath: config.build.base,
206
211
  paths: config.paths.project,
207
212
  server: {
208
213
  static: {
@@ -210,8 +215,9 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
210
215
  // relative from CWD of process that boots n1ode server
211
216
  // can easily break! Use path relative in server??
212
217
  directory: `./` + config.paths.project.relative.build.root,
213
- // Uses Hono route syntax.
214
- route: `/` + config.paths.project.relative.build.relative.assets + `/*`,
218
+ // Uses Hono route syntax - includes base path
219
+ route: config.build.base.slice(0, -1) + `/` + config.paths.project.relative.build.relative.assets
220
+ + `/*`,
215
221
  },
216
222
  },
217
223
  }
@@ -8,6 +8,7 @@ import { Tree } from '#lib/tree/index'
8
8
  import { debug } from '#singletons/debug'
9
9
  import { superjson } from '#singletons/superjson'
10
10
  import mdx from '@mdx-js/rollup'
11
+ import rehypeShiki from '@shikijs/rehype'
11
12
  import { Path, Str } from '@wollybeard/kit'
12
13
  import remarkGfm from 'remark-gfm'
13
14
 
@@ -123,6 +124,22 @@ export const Pages = ({
123
124
  ...mdx({
124
125
  jsxImportSource: `polen/react`,
125
126
  remarkPlugins: [remarkGfm],
127
+ rehypePlugins: [
128
+ [
129
+ rehypeShiki,
130
+ {
131
+ themes: {
132
+ light: `github-light`,
133
+ dark: `tokyo-night`,
134
+ },
135
+ defaultColor: false,
136
+ cssVariablePrefix: `--shiki-`,
137
+ transformers: [
138
+ // Line numbers will be handled via CSS
139
+ ],
140
+ },
141
+ ],
142
+ ],
126
143
  }),
127
144
  },
128
145
 
@@ -240,7 +257,11 @@ export const Pages = ({
240
257
  const pathExp = FileRouter.pathToExpression([child.value.name])
241
258
  const title = Str.titlizeSlug(child.value.name)
242
259
  navbarPages.push({
243
- pathExp: pathExp.startsWith('/') ? pathExp.slice(1) : pathExp,
260
+ // IMPORTANT: Always ensure paths start with '/' for React Router compatibility.
261
+ // Without the leading slash, React Router treats paths as relative, which causes
262
+ // hydration mismatches between SSR (where base path is prepended) and client
263
+ // (where basename is configured). This ensures consistent behavior.
264
+ pathExp: pathExp.startsWith('/') ? pathExp : '/' + pathExp,
244
265
  title,
245
266
  })
246
267
  }
@@ -248,7 +269,11 @@ export const Pages = ({
248
269
  const pathExp = FileRouter.pathToExpression([child.value.name])
249
270
  const title = Str.titlizeSlug(child.value.name)
250
271
  navbarPages.push({
251
- pathExp: pathExp.startsWith('/') ? pathExp.slice(1) : pathExp,
272
+ // IMPORTANT: Always ensure paths start with '/' for React Router compatibility.
273
+ // Without the leading slash, React Router treats paths as relative, which causes
274
+ // hydration mismatches between SSR (where base path is prepended) and client
275
+ // (where basename is configured). This ensures consistent behavior.
276
+ pathExp: pathExp.startsWith('/') ? pathExp : '/' + pathExp,
252
277
  title,
253
278
  })
254
279
  }
@@ -11,6 +11,10 @@ const args = Command.create()
11
11
  `--architecture -a`,
12
12
  Api.Config.BuildArchitecture.default('ssg').describe('Which kind of application architecture to output.'),
13
13
  )
14
+ .parameter(
15
+ `--base -b`,
16
+ z.string().optional().describe('Base public path for deployment (e.g., /my-project/)'),
17
+ )
14
18
  .settings({
15
19
  parameters: {
16
20
  environment: {
@@ -33,4 +37,5 @@ const args = Command.create()
33
37
  await Api.Builder.build({
34
38
  ...(args.debug === false ? {} : { debug: args.debug }),
35
39
  ...(args.architecture === 'ssg' ? {} : { architecture: args.architecture }),
40
+ ...(args.base ? { base: args.base } : {}),
36
41
  })
@@ -14,6 +14,10 @@ const args = Command.create()
14
14
  // @ts-expect-error
15
15
  z.string().optional().describe(`The path to the project directory. Default is CWD (current working directory).`),
16
16
  )
17
+ .parameter(
18
+ `--base -b`,
19
+ z.string().optional().describe('Base public path for deployment (e.g., /my-project/)'),
20
+ )
17
21
  .settings({
18
22
  parameters: {
19
23
  environment: {
@@ -32,6 +36,7 @@ const dir = ensureOptionalAbsoluteWithCwd(args.project) as string
32
36
  const viteUserConfig = await Api.ConfigResolver.fromFile({
33
37
  dir,
34
38
  overrides: {
39
+ ...(args.base ? { build: { base: args.base } } : {}),
35
40
  advanced: {
36
41
  debug: args.debug,
37
42
  },
@@ -0,0 +1 @@
1
+ export * from './shiki.ts'