orga-build 0.5.1 → 0.5.3

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.
@@ -12,6 +12,7 @@ const outDir = path.join(__dirname, '.test-output')
12
12
  describe('orga-build', () => {
13
13
  before(async () => {
14
14
  await fs.mkdir(fixtureDir, { recursive: true })
15
+ await fs.mkdir(path.join(fixtureDir, 'docs'), { recursive: true })
15
16
  // Create minimal fixture
16
17
  await fs.writeFile(
17
18
  path.join(fixtureDir, 'index.org'),
@@ -20,8 +21,14 @@ describe('orga-build', () => {
20
21
  * Hello World
21
22
 
22
23
  This is a test page.
24
+
25
+ Here's [[file:./docs/index.org][index page]].
26
+
27
+ Here's [[file:more.org][another page]].
23
28
  `
24
29
  )
30
+ await fs.writeFile(path.join(fixtureDir, 'docs', 'index.org'), 'Docs index page.')
31
+ await fs.writeFile(path.join(fixtureDir, 'more.org'), 'Another page.')
25
32
  })
26
33
 
27
34
  after(async () => {
@@ -51,6 +58,8 @@ This is a test page.
51
58
  const html = await fs.readFile(indexPath, 'utf-8')
52
59
  assert.ok(html.includes('<title>Test Page</title>'), 'should have title')
53
60
  assert.ok(html.includes('Hello World'), 'should have heading content')
61
+ assert.ok(html.includes('href="/docs"'), 'should rewrite docs/index.org to /docs')
62
+ assert.ok(html.includes('href="/more"'), 'should rewrite more.org to /more')
54
63
  })
55
64
 
56
65
  test('generates assets directory', async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["build.js"],"names":[],"mappings":"AAcA;;GAEG;AACH,qEAFW,OAAO,aAAa,EAAE,MAAM,iBAqJtC;;sBA/J4C,aAAa"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["build.js"],"names":[],"mappings":"AAcA;;GAEG;AACH,qEAFW,OAAO,aAAa,EAAE,MAAM,iBAmJtC;;sBA7J4C,aAAa"}
package/lib/build.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path'
2
2
  import { createBuilder } from 'vite'
3
3
  import { fileURLToPath, pathToFileURL } from 'node:url'
4
- import { copy, emptyDir, ensureDir, exists } from './fs.js'
4
+ import { emptyDir, ensureDir, exists } from './fs.js'
5
5
  import fs from 'fs/promises'
6
6
  import { createOrgaBuildConfig, alias } from './plugin.js'
7
7
 
@@ -23,7 +23,7 @@ export async function build({
23
23
  }) {
24
24
  await emptyDir(outDir)
25
25
  const ssrOutDir = path.join(outDir, '.ssr')
26
- const clientOutDir = path.join(outDir, '.client')
26
+ const clientOutDir = outDir
27
27
 
28
28
  const { plugins, resolve } = createOrgaBuildConfig({
29
29
  root,
@@ -59,7 +59,7 @@ export async function build({
59
59
  build: {
60
60
  outDir: clientOutDir,
61
61
  cssCodeSplit: false,
62
- emptyOutDir: true,
62
+ emptyOutDir: false,
63
63
  assetsDir: 'assets',
64
64
  rollupOptions: {
65
65
  input: clientEntry,
@@ -119,8 +119,6 @@ export async function build({
119
119
  })
120
120
  )
121
121
 
122
- await copy(clientOutDir, outDir)
123
- await fs.rm(clientOutDir, { recursive: true })
124
122
  await fs.rm(ssrOutDir, { recursive: true })
125
123
 
126
124
  return
package/lib/files.d.ts CHANGED
@@ -7,11 +7,16 @@ export function setup(dir: string, { outDir }?: {
7
7
  outDir?: string | undefined;
8
8
  }): {
9
9
  pages: () => Promise<Record<string, Page>>;
10
- page: (id: string) => Promise<Page>;
10
+ page: (slug: string) => Promise<Page>;
11
11
  components: () => Promise<string | null>;
12
12
  layouts: () => Promise<Record<string, string>>;
13
13
  contentEntries: () => Promise<ContentEntry[]>;
14
14
  };
15
+ /**
16
+ * Convert a content file path (relative to content root) to the canonical page slug.
17
+ * @param {string} contentFilePath
18
+ */
19
+ export function getSlugFromContentFilePath(contentFilePath: string): string;
15
20
  export type Page = {
16
21
  dataPath: string;
17
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["files.js"],"names":[],"mappings":"AA8EA;;;;GAIG;AACH,2BAJW,MAAM,eAEd;IAAyB,MAAM;CACjC;;eAiIY,MAAM;;;;EAKlB;;cAjNa,MAAM;;;;YACN,MAAM;;;QAMN,MAAM;UACN,MAAM;UACN,MAAM;cACN,MAAM;SACN,KAAK,GAAG,KAAK,GAAG,KAAK;UACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["files.js"],"names":[],"mappings":"AA8EA;;;;GAIG;AACH,2BAJW,MAAM,eAEd;IAAyB,MAAM;CACjC;;iBAiIY,MAAM;;;;EAKlB;AAsBD;;;GAGG;AACH,4DAFW,MAAM,UAoBhB;;cA7Pa,MAAM;;;;YACN,MAAM;;;QAMN,MAAM;UACN,MAAM;UACN,MAAM;cACN,MAAM;SACN,KAAK,GAAG,KAAK,GAAG,KAAK;UACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
package/lib/files.js CHANGED
@@ -106,8 +106,8 @@ export function setup(dir, { outDir } = {}) {
106
106
  /** @type {Record<string, Page>} */
107
107
  const pages = {}
108
108
  for (const file of files) {
109
- const pageId = getPagePublicPath(file)
110
- pages[pageId] = {
109
+ const pageSlug = getSlugFromContentFilePath(file)
110
+ pages[pageSlug] = {
111
111
  dataPath: path.join(dir, file)
112
112
  }
113
113
  }
@@ -131,8 +131,8 @@ export function setup(dir, { outDir } = {}) {
131
131
 
132
132
  return layoutFiles.reduce(
133
133
  (/** @type {Record<string, string>} */ result, file) => {
134
- const layoutPath = path.dirname(getPagePublicPath(file))
135
- result[layoutPath] = path.join(dir, file)
134
+ const layoutSlug = path.dirname(getSlugFromContentFilePath(file))
135
+ result[layoutSlug] = path.join(dir, file)
136
136
  return result
137
137
  },
138
138
  /** @type {Record<string, string>} */ {}
@@ -209,10 +209,10 @@ export function setup(dir, { outDir } = {}) {
209
209
 
210
210
  return files
211
211
 
212
- /** @param {string} id */
213
- async function page(id) {
212
+ /** @param {string} slug */
213
+ async function page(slug) {
214
214
  const all = await pages()
215
- return all[id]
215
+ return all[slug]
216
216
  }
217
217
  }
218
218
 
@@ -237,16 +237,18 @@ function cache(fn) {
237
237
  }
238
238
 
239
239
  /**
240
- * @param {string} file
240
+ * Convert a content file path (relative to content root) to the canonical page slug.
241
+ * @param {string} contentFilePath
241
242
  */
242
- function getPagePublicPath(file) {
243
- let pagePublicPath = file.replace(/\.(org|md|mdx|js|jsx|ts|tsx)$/, '')
244
- pagePublicPath = pagePublicPath.replace(/index$/, '')
243
+ export function getSlugFromContentFilePath(contentFilePath) {
244
+ const normalizedFilePath = contentFilePath.replace(/\\/g, '/')
245
+ let slug = normalizedFilePath.replace(/\.(org|md|mdx|js|jsx|ts|tsx)$/, '')
246
+ slug = slug.replace(/index$/, '')
245
247
  // remove trailing slash
246
- pagePublicPath = pagePublicPath.replace(/\/$/, '')
248
+ slug = slug.replace(/\/$/, '')
247
249
  // ensure starting slash
248
- pagePublicPath = pagePublicPath.replace(/^\//, '')
249
- pagePublicPath = `/${pagePublicPath}`
250
+ slug = slug.replace(/^\//, '')
251
+ slug = `/${slug}`
250
252
 
251
253
  // turn [id] into :id
252
254
  // so that react-router can recognize it as url params
@@ -255,5 +257,5 @@ function getPagePublicPath(file) {
255
257
  // (_, paramName) => `:${paramName}`
256
258
  // )
257
259
 
258
- return pagePublicPath
260
+ return slug
259
261
  }
package/lib/orga.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * @param {Object} options
3
3
  * @param {string|string[]} options.containerClass - CSS class name(s) to wrap the rendered content
4
+ * @param {string} options.root - Root directory for content files
4
5
  */
5
- export function setupOrga({ containerClass }: {
6
+ export function setupOrga({ containerClass, root }: {
6
7
  containerClass: string | string[];
8
+ root: string;
7
9
  }): import("@orgajs/rollup").Plugin;
8
10
  //# sourceMappingURL=orga.d.ts.map
package/lib/orga.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"orga.d.ts","sourceRoot":"","sources":["orga.js"],"names":[],"mappings":"AAMA;;;GAGG;AACH,8CAFG;IAAiC,cAAc,EAAvC,MAAM,GAAC,MAAM,EAAE;CACzB,mCAaA"}
1
+ {"version":3,"file":"orga.d.ts","sourceRoot":"","sources":["orga.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,oDAHG;IAAiC,cAAc,EAAvC,MAAM,GAAC,MAAM,EAAE;IACC,IAAI,EAApB,MAAM;CAChB,mCAYA"}
package/lib/orga.js CHANGED
@@ -1,23 +1,25 @@
1
1
  /**
2
2
  * @import {Root as HastTree} from 'hast'
3
3
  */
4
+ import path from 'node:path'
4
5
  import _orga from '@orgajs/rollup'
5
6
  import { visitParents } from 'unist-util-visit-parents'
7
+ import { getSlugFromContentFilePath } from './files.js'
6
8
 
7
9
  /**
8
10
  * @param {Object} options
9
11
  * @param {string|string[]} options.containerClass - CSS class name(s) to wrap the rendered content
12
+ * @param {string} options.root - Root directory for content files
10
13
  */
11
- export function setupOrga({ containerClass }) {
14
+ export function setupOrga({ containerClass, root }) {
12
15
  return _orga({
13
- rehypePlugins: [[rehypeWrap, { className: containerClass }], image],
16
+ rehypePlugins: [
17
+ [rehypeWrap, { className: containerClass }],
18
+ [rewriteOrgFileLinks, { root }],
19
+ image
20
+ ],
14
21
  reorgRehypeOptions: {
15
- linkHref: (link) => {
16
- if (link.path.protocol === 'file') {
17
- return link.path.value.replace(/\.org$/, '')
18
- }
19
- return link.path.value
20
- }
22
+ linkHref: (link) => link.path.value
21
23
  }
22
24
  })
23
25
  }
@@ -83,6 +85,56 @@ function image() {
83
85
  }
84
86
  }
85
87
 
88
+ /**
89
+ * @param {Object} options
90
+ * @param {string} options.root
91
+ */
92
+ function rewriteOrgFileLinks({ root }) {
93
+ /**
94
+ * @param {any} tree
95
+ * @param {import('vfile').VFile} [file]
96
+ */
97
+ return function (tree, file) {
98
+ const filePath = file?.path
99
+ if (!filePath) return
100
+
101
+ visitParents(tree, { tagName: 'a' }, (node) => {
102
+ const href = node?.properties?.href
103
+ if (typeof href !== 'string') return
104
+ if (!href.endsWith('.org')) return
105
+
106
+ const targetSlug = resolveOrgHrefToContentSlug({
107
+ root,
108
+ filePath,
109
+ href
110
+ })
111
+ if (!targetSlug) return
112
+ node.properties.href = targetSlug
113
+ })
114
+ }
115
+ }
116
+
117
+ /**
118
+ * @param {Object} options
119
+ * @param {string} options.root
120
+ * @param {string} options.filePath
121
+ * @param {string} options.href
122
+ * @returns {string|null}
123
+ */
124
+ function resolveOrgHrefToContentSlug({ root, filePath, href }) {
125
+ const decodedHrefPath = decodeURI(href)
126
+ const absoluteTargetPath = decodedHrefPath.startsWith('/')
127
+ ? path.resolve(root, `.${decodedHrefPath}`)
128
+ : path.resolve(path.dirname(filePath), decodedHrefPath)
129
+
130
+ const relativeTargetPath = path.relative(root, absoluteTargetPath)
131
+ if (relativeTargetPath.startsWith('..') || path.isAbsolute(relativeTargetPath)) {
132
+ return null
133
+ }
134
+
135
+ return getSlugFromContentFilePath(relativeTargetPath)
136
+ }
137
+
86
138
  function genId(length = 8) {
87
139
  const array = new Uint8Array(length)
88
140
  crypto.getRandomValues(array)
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["plugin.js"],"names":[],"mappings":"AAoBA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,kEAHW,sBAAsB,GACpB,OAAO,MAAM,EAAE,YAAY,EAAE,CAIzC;AAED;;;;;;GAMG;AACH,uHAHW,sBAAsB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5I;IAAE,OAAO,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,KAAK,CAAA;KAAE,CAAA;CAAE,CAoBxF;AAiBD;;;;;;;;;;;;GAYG;AACH,gDAHW,MAAM,GACJ,OAAO,MAAM,EAAE,MAAM,CA8CjC;AA9HD;;GAEG;AACH;;;;EAIC;;;;;UAIa,MAAM;;;;aACN,MAAM,GAAG,SAAS;;;;qBAClB,MAAM,GAAC,MAAM,EAAE"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["plugin.js"],"names":[],"mappings":"AAoBA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,kEAHW,sBAAsB,GACpB,OAAO,MAAM,EAAE,YAAY,EAAE,CAQzC;AAED;;;;;;GAMG;AACH,uHAHW,sBAAsB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5I;IAAE,OAAO,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,KAAK,CAAA;KAAE,CAAA;CAAE,CAoBxF;AAiBD;;;;;;;;;;;;GAYG;AACH,gDAHW,MAAM,GACJ,OAAO,MAAM,EAAE,MAAM,CA8CjC;AAlID;;GAEG;AACH;;;;EAIC;;;;;UAIa,MAAM;;;;aACN,MAAM,GAAG,SAAS;;;;qBAClB,MAAM,GAAC,MAAM,EAAE"}
package/lib/plugin.js CHANGED
@@ -33,7 +33,11 @@ export const alias = {
33
33
  * @returns {import('vite').PluginOption[]}
34
34
  */
35
35
  export function orgaBuildPlugin({ root, outDir, containerClass = [] }) {
36
- return [setupOrga({ containerClass }), react(), pluginFactory({ dir: root, outDir })]
36
+ return [
37
+ setupOrga({ containerClass, root }),
38
+ react(),
39
+ pluginFactory({ dir: root, outDir })
40
+ ]
37
41
  }
38
42
 
39
43
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orga-build",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "A simple tool that builds org-mode files into a website",
5
5
  "type": "module",
6
6
  "engines": {