Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a9581e2
feat(ember-vite): add static export markdown payload helpers
josemarluedke Jul 30, 2026
fbf0e98
feat(ember-vite): add llms.txt and llms-full.txt builders
josemarluedke Jul 30, 2026
7968c21
feat(ember-vite): add collectStaticExportFiles entry point
josemarluedke Jul 30, 2026
6b5c0c1
refactor(ember-vite): extract writeTextToPublic from writeJsonToPublic
josemarluedke Jul 30, 2026
a9d53d9
feat(ember-vite): add staticExport option to DocfyViteOptions
josemarluedke Jul 30, 2026
d460374
feat(ember-vite): emit static docs export during build
josemarluedke Jul 30, 2026
930684d
test(test-app-vite): enable static docs export
josemarluedke Jul 30, 2026
5676ac1
docs(ember-vite): document staticExport option
josemarluedke Jul 30, 2026
605efa6
style(ember-vite): apply prettier formatting to static-export
josemarluedke Jul 30, 2026
c7e258b
fix: fail loudly on misconfigured staticExport, add llms.txt H1 support
josemarluedke Jul 30, 2026
ae6b70f
ci: run @docfy/ember-vite tests
josemarluedke Jul 30, 2026
1d9a120
feat(ember-vite): make staticExport.siteUrl optional for root-relativ…
josemarluedke Jul 30, 2026
b7dddf7
fix(ember-vite): reject a siteUrl carrying a query string or fragment
josemarluedke Jul 30, 2026
f6e696a
feat(ember-vite): default staticExport.projectName from package.json
josemarluedke Jul 30, 2026
0f36951
chore: define pnpm workspace and install fresh
josemarluedke Jul 30, 2026
7ba9f0c
chore: run tasks with pnpm -r, keep lerna for versioning
josemarluedke Jul 30, 2026
9e786b6
chore: use pnpm script prefixes in concurrently
josemarluedke Jul 30, 2026
cd8fa79
chore: declare @eslint/js and allow required build scripts
josemarluedke Jul 30, 2026
0bbd674
fix: point tsconfig typeRoots at each package's own node_modules
josemarluedke Jul 30, 2026
424674b
ci: install and run tasks with pnpm
josemarluedke Jul 30, 2026
a3aa53e
docs: use pnpm for repo development instructions
josemarluedke Jul 30, 2026
c7bf079
fix: declare eslint-plugin-jest where eslint configs import it
josemarluedke Jul 30, 2026
e5a6626
Merge pull request #211 from josemarluedke/chore/pnpm-migration
josemarluedke Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ jobs:

steps:
- uses: actions/checkout@v4
# Must precede setup-node: `cache: pnpm` resolves the pnpm store path,
# which requires pnpm to already be on PATH. Version comes from the root
# package.json `packageManager` field.
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24.x
- name: Install yarn
run: npm install -g yarn
cache: pnpm
- name: Install dependencies
run: yarn install
run: pnpm install --frozen-lockfile
- name: Compile TypeScript
run: yarn compile
run: pnpm compile
# - name: Lint
# run: yarn lint
- name: Test
run: cd packages/core && yarn test
# run: pnpm lint
- name: Test @docfy/core
run: pnpm --filter @docfy/core test
- name: Test @docfy/ember-vite
run: pnpm --filter @docfy/ember-vite test
12 changes: 6 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
## How to install

Make sure you are using yarn (version 1)
Make sure you are using [pnpm](https://pnpm.io/) (version 11 or later)

In the root folder:

```
yarn install
pnpm install
```

Then:

```
yarn compile
pnpm compile
```

## Ember

```bash
cd packages/ember
yarn start
pnpm start
```

## Core

```bash
cd packages/core
# yarn test --watch for watch mode
yarn test
# pnpm test --watch for watch mode
pnpm test
```
95 changes: 95 additions & 0 deletions docs/ember/ember-vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ docfyVite({
// Enable HMR (optional)
hmr: true, // default: true

// Static text export for non-JS clients (optional) - see "Static Export" below
staticExport: {
enabled: true, // default: false
},

// Inline config (optional) - overrides config file
config: {
sources: [
Expand Down Expand Up @@ -168,6 +173,96 @@ import { getDocfyOutput } from '@docfy/ember/output:virtual';
const docfyData = getDocfyOutput();
```

### Static Export

A Docfy site is client-rendered, so a plain HTTP request returns the app shell rather than your
content. Crawlers, `curl`, and AI coding agents fetching a page get markup with no documentation
in it.

Enabling `staticExport` emits a text-only mirror of your docs alongside the app:

```js
docfyVite({
staticExport: {
enabled: true,
},
});
```

That writes three kinds of file into your build output:

- **`<page-url>.md`** for every page, at the same path as the live route plus a `.md` suffix. The
route `/docs/getting-started` gets `dist/docs/getting-started.md`. Index routes become
`index.md`.
- **`llms.txt`** — a compact index of every page, grouped by section, following the
[llms.txt convention](https://llmstxt.org).
- **`llms-full.txt`** — every page's content concatenated into one file.

The export is **build-only**. Nothing is emitted during `vite dev`, so your source tree stays
clean while you work.

#### Options

| Option | Type | Default | Description |
| -------------------- | --------- | -------------------- | ------------------------------------------------------------------------------------------------- |
| `enabled` | `boolean` | `false` | Master switch. The rest are no-ops unless this is `true`. |
| `markdown` | `boolean` | `true` | Emit one `.md` file per page. |
| `llmsTxt` | `boolean` | `true` | Emit `llms.txt`. |
| `llmsFullTxt` | `boolean` | `true` | Emit `llms-full.txt`. |
| `siteUrl` | `string` | — | Absolute origin for links. Omit for root-relative links; set for absolute ones. |
| `projectName` | `string` | `package.json` name | H1 heading at the top of `llms.txt`. |
| `projectDescription` | `string` | — | Short blurb after the H1, as a blockquote. |

#### Relative or absolute links

By default the links inside `llms.txt` and `llms-full.txt` are root-relative:

```
- [Getting Started](/docs/getting-started.md)
```

This is valid per the llms.txt spec and stays correct wherever the site is served — production,
deploy previews, forks, or `localhost` — with no configuration. Set `siteUrl` when you want
absolute links instead, which helps consumers that read the text detached from its origin:

```js
docfyVite({
staticExport: {
enabled: true,
siteUrl: 'https://docfy.dev',
},
});
```

#### Customizing what a page exports

Each page exports its raw markdown source with the frontmatter block stripped. When a page relies
on a custom component that only renders in the browser, the exported text would contain the
component tag rather than its content. To substitute something meaningful, set
`pluginData.staticMarkdown` from a Docfy plugin:

```js
export default {
runAfter(ctx) {
ctx.pages.forEach(page => {
page.pluginData.staticMarkdown = page.markdown.replace(
/<ApiTable @of="(\w+)" \/>/g,
(_, name) => renderMarkdownTable(name)
);
});
},
};
```

Use `runAfter` and work on markdown text rather than the AST. By that point `page.ast` has been
converted to hast and is what your live routes render from, so mutating it would change the app
itself. `page.markdown` is raw source that nothing else reads, which is why writing a derived
value into `pluginData.staticMarkdown` cannot affect the rendered site.

Set it on `page.pluginData`, not `page.meta.pluginData` — the latter is ignored by the export and
is serialized into the app's JavaScript bundle, so putting page content there would ship every
page's markdown to the browser.

## Advanced Configuration

### Multiple Sources
Expand Down
18 changes: 3 additions & 15 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
{
"version": "0.11.0",
"packages": [
"packages/*",
"test-app-vite",
"test-app-classic"
],
"npmClient": "yarn",
"command": {
"bootstrap": {
"npmClientArgs": [
"--frozen-lockfile",
"--non-interactive"
]
}
}
}
"packages": ["packages/*", "test-app-vite", "test-app-classic"],
"npmClient": "pnpm"
}
21 changes: 7 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
{
"name": "root",
"private": true,
"workspaces": [
"packages/*",
"test-app-vite",
"test-app-classic"
],
"packageManager": "pnpm@11.1.2",
"scripts": {
"lint": "lerna run lint --parallel",
"lint:fix": "lerna run lint:fix --parallel",
"lint:format": "lerna run format:check --parallel",
"lint:format:fix": "lerna run format --parallel",
"lint": "pnpm -r --parallel run lint",
"lint:fix": "pnpm -r --parallel run lint:fix",
"lint:format": "pnpm -r --parallel run format:check",
"lint:format:fix": "pnpm -r --parallel run format",
"start": "echo 'TODO'",
"compile": "lerna run compile",
"test": "lerna run --parallel test"
"compile": "pnpm -r run compile",
"test": "pnpm -r --parallel run test"
},
"devDependencies": {
"lerna": "^9.0.3"
},
"resolutions": {
"@types/unist": "2.0.3"
}
}
2 changes: 1 addition & 1 deletion packages/core/tests/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"paths": {
"*": ["../types/*"]
},
"typeRoots": ["../../../node_modules/@types", "../types"],
"typeRoots": ["../node_modules/@types", "../types"],
"types": ["jest", "node"]
},
"include": ["**/*"]
Expand Down
12 changes: 7 additions & 5 deletions packages/ember-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"prebuild": "npm-run-all compile",
"build": "ember build --environment=production",
"compile": "tsc --project src/tsconfig.json",
"lint": "yarn lint:js && yarn lint:hbs",
"lint:fix": "concurrently \"yarn:lint:*:fix\" --names \"fix:\" --prefixColors auto",
"lint": "pnpm lint:js && pnpm lint:hbs",
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto",
"lint:hbs": "ember-template-lint .",
"lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint . --cache",
Expand All @@ -33,8 +33,8 @@
"test:ember-compatibility": "echo 'ember try:each TODO'"
},
"dependencies": {
"@docfy/core": "^0.11.0",
"@docfy/ember": "^0.11.0",
"@docfy/core": "workspace:^0.11.0",
"@docfy/ember": "workspace:^0.11.0",
"broccoli-bridge": "^1.0.0",
"broccoli-file-creator": "^2.1.1",
"broccoli-funnel": "^3.0.8",
Expand All @@ -55,12 +55,14 @@
},
"devDependencies": {
"@babel/helper-define-map": "^7.18.6",
"@docfy/plugin-with-prose": "^0.11.0",
"@docfy/plugin-with-prose": "workspace:^0.11.0",
"@ember/optional-features": "^2.2.0",
"@ember/test-helpers": "^5.2.2",
"@eslint/js": "^9.32.0",
"@glimmer/component": "^2.0.0",
"@glimmer/tracking": "^1.1.2",
"@mapbox/rehype-prism": "^0.5.0",
"@types/unist": "^2.0.0",
"autoprefixer": "^10.4.21",
"broccoli-asset-rev": "^3.0.0",
"concurrently": "^9.1.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-cli/src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"paths": {
"*": ["types/*"]
},
"typeRoots": ["../../../node_modules/@types", "types"],
"typeRoots": ["../node_modules/@types", "types"],
"types": []
},
"include": ["**/*", "types/**/*"]
Expand Down
91 changes: 91 additions & 0 deletions packages/ember-vite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ interface DocfyViteOptions {
*/
hmr?: boolean;

/**
* Static text export for non-JS clients. See "Static Export" below.
*/
staticExport?: {
enabled?: boolean;
markdown?: boolean;
llmsTxt?: boolean;
llmsFullTxt?: boolean;
siteUrl?: string;
projectDescription?: string;
projectName?: string;
};

// All @docfy/core options are also supported
sources?: SourceConfig[];
plugins?: PluginList;
Expand All @@ -97,6 +110,84 @@ interface DocfyViteOptions {
}
```

## Static Export

Emit a statically-servable, text-only mirror of your docs so AI agents, crawlers, and any
non-JavaScript client can read them. Off by default, and **build-only** — nothing is emitted
during `vite dev`.

```javascript
docfyVitePlugin({
staticExport: {
enabled: true,
projectDescription: 'Docfy is a modular JavaScript tool to help build documentation sites.',
},
});
```

This writes into your build output:

- `<page-url>.md` for every page, at the same path as the live route plus a `.md` suffix
(`/docs/getting-started` → `dist/docs/getting-started.md`). Index routes become `index.md`.
- `llms.txt` — a compact, links-only index grouped by section, following the
[llms.txt convention](https://llmstxt.org).
- `llms-full.txt` — every page's content concatenated in the same order.

By default, links in `llms.txt` and `llms-full.txt` are root-relative (e.g. `/docs/about.md`),
which is valid per the llms.txt spec and works on any origin — deploy previews, forks, staging,
and local builds — with no configuration. Set `siteUrl` to emit absolute links instead, which is
useful when the text is consumed detached from its origin:

```javascript
docfyVitePlugin({
staticExport: {
enabled: true,
siteUrl: 'https://docfy.dev',
},
});
```

### Options

| Option | Type | Default | Description |
| -------------------- | --------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled` | `boolean` | `false` | Master switch. The rest of these options are no-ops unless this is `true`. |
| `markdown` | `boolean` | `true` | Emit one `.md` file per page. Only takes effect when `enabled` is `true`. |
| `llmsTxt` | `boolean` | `true` | Emit `llms.txt`. Only takes effect when `enabled` is `true`. |
| `llmsFullTxt` | `boolean` | `true` | Emit `llms-full.txt`. Only takes effect when `enabled` is `true`. |
| `siteUrl` | `string` | — | Absolute http(s) site origin for links. Optional — omit it for root-relative links; set it for absolute links. May include a path (for docs served under a subpath) but not a query string or fragment. |
| `projectName` | `string` | app's `package.json` `name` | Project name emitted as the H1 heading at the top of `llms.txt`. Defaults to the consuming app's `package.json` `name`, so the H1 the spec requires is always present. |
| `projectDescription` | `string` | — | Short blurb placed at the top of `llms.txt`, after the H1 (if any), as a blockquote. |

### Customizing a page's exported Markdown

By default each page exports its raw Markdown source with the frontmatter block stripped. To
export something different — for example replacing a custom component tag with a real Markdown
table — set `pluginData.staticMarkdown` from a Docfy plugin:

```js
export default {
runAfter(ctx) {
ctx.pages.forEach(page => {
page.pluginData.staticMarkdown = page.markdown.replace(
/<Signature @component="(\w+)" \/>/g,
(_, name) => renderSignatureTable(name)
);
});
},
};
```

Use `runAfter` and operate on Markdown text, not the AST. By that point `page.ast` has already
been converted to hast and is what the live route templates are rendered from — mutating it
would change the rendered app. `page.markdown` is raw source that nothing else reads, so writing
a derived value into `pluginData.staticMarkdown` cannot affect the SPA build.

Set `page.pluginData.staticMarkdown`, not `page.meta.pluginData.staticMarkdown` — the latter is
silently ignored by the static export **and** is serialized into the app's client JS bundle (via
the virtual Docfy output module), so putting full page Markdown there would inline every page's
content into the shipped bundle.

## Virtual Modules

The plugin provides several virtual modules that you can import in your Ember app:
Expand Down
Loading
Loading