Skip to content

Commit ec69012

Browse files
committed
fix(docs): prevent concurrent site output writes
Eleventy completion hooks could write into .11ty-vite/public while Vite renamed dist into .11ty-vite, causing ENOTEMPTY build failures. Run hooks sequentially so Vite completes before search, sitemap, and feed writers publish to the final output directory. Sitemap and feed writers use Eleventy's configured output directory. Validation: site build and 14 sitemap/feed tests passed. Signed-off-by: John Yanarella <jyanarella@nvidia.com>
1 parent 9fde4a5 commit ec69012

7 files changed

Lines changed: 78 additions & 16 deletions

File tree

‎projects/site/eleventy.config.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ const entrypoints = [
9393
* Sets up plugins, transforms, collections, and build options
9494
*/
9595
export default function (eleventyConfig) {
96+
// Vite must finish before the other output writers run.
97+
eleventyConfig.setEventEmitterMode('sequential');
9698
eleventyConfig.pathPrefix = BASE_URL;
9799

98100
// Add core 11ty plugins
@@ -166,9 +168,7 @@ export default function (eleventyConfig) {
166168

167169
// Add search plugin for documentation search functionality
168170
if (process.env.ELEVENTY_RUN_MODE === 'build') {
169-
eleventyConfig.addPlugin(searchPlugin, {
170-
outputPath: './.11ty-vite/public/.pagefind'
171-
});
171+
eleventyConfig.addPlugin(searchPlugin);
172172
}
173173

174174
// https://llmstxt.org

‎projects/site/src/_11ty/plugins/search.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { join } from 'node:path';
12
import * as pagefind from 'pagefind';
23

3-
export async function searchPlugin(eleventyConfig, pluginOptions) {
4+
export async function searchPlugin(eleventyConfig) {
45
const { index } = await pagefind.createIndex();
56

67
eleventyConfig.addTransform('search-index', async function (content) {
@@ -17,9 +18,9 @@ export async function searchPlugin(eleventyConfig, pluginOptions) {
1718
return content;
1819
});
1920

20-
eleventyConfig.on('eleventy.after', async () => {
21+
eleventyConfig.on('eleventy.after', async ({ directories }) => {
2122
const { errors } = await index.writeFiles({
22-
outputPath: pluginOptions.outputPath
23+
outputPath: join(directories.output, '.pagefind')
2324
});
2425
if (errors.length > 0) {
2526
console.error('Pagefind errors', errors);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { access, mkdtemp, rm } from 'node:fs/promises';
2+
import { tmpdir } from 'node:os';
3+
import { join } from 'node:path';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
5+
import { searchPlugin } from './search.js';
6+
7+
describe('searchPlugin', () => {
8+
let outputPath: string | undefined;
9+
10+
afterEach(async () => {
11+
if (outputPath) await rm(outputPath, { force: true, recursive: true });
12+
});
13+
14+
it('should publish the search index to the selected site output', async () => {
15+
outputPath = await mkdtemp(join(tmpdir(), 'elements-search-'));
16+
const on = vi.fn();
17+
const addTransform = vi.fn();
18+
await searchPlugin({ on, addTransform });
19+
const transform = addTransform.mock.calls[0]?.[1];
20+
const afterBuild = on.mock.calls.find(([event]) => event === 'eleventy.after')?.[1];
21+
22+
await transform.call(
23+
{ page: { url: '/docs/elements/tabs/', inputPath: 'tabs.html' } },
24+
'<html lang="en"><body><h1>Tabs</h1><p>Tab composition</p></body></html>'
25+
);
26+
await afterBuild({ directories: { output: outputPath } });
27+
28+
await expect(access(join(outputPath, '.pagefind', 'pagefind.js'))).resolves.toBeUndefined();
29+
});
30+
});

‎projects/site/src/_11ty/plugins/sitemap-xml.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { promises as fsp } from 'node:fs';
2+
import { join } from 'node:path';
23
import { getExplicitModifiedDate, normalizeContentDate } from '../utils/content-dates.js';
34
import { getReleaseVideo } from '../utils/release-video.js';
45
import { getSiteUrl } from '../utils/site-url.js';
@@ -107,8 +108,8 @@ export function renderSitemap(results = []) {
107108
}
108109

109110
export function sitemapPlugin(eleventyConfig) {
110-
eleventyConfig.on('eleventy.after', async ({ results } = {}) => {
111-
await fsp.mkdir('./.11ty-vite/public/', { recursive: true });
112-
await fsp.writeFile('./.11ty-vite/public/sitemap.xml', renderSitemap(results), 'utf-8');
111+
eleventyConfig.on('eleventy.after', async ({ results, directories }) => {
112+
await fsp.mkdir(directories.output, { recursive: true });
113+
await fsp.writeFile(join(directories.output, 'sitemap.xml'), renderSitemap(results), 'utf-8');
113114
});
114115
}

‎projects/site/src/_11ty/plugins/sitemap-xml.test.ts‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
2+
import { tmpdir } from 'node:os';
3+
import { join } from 'node:path';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
25
import { lintRules } from '../../docs/lint/rules.js';
3-
import { isSitemapPageUrl, renderSitemap } from './sitemap-xml.js';
6+
import { isSitemapPageUrl, renderSitemap, sitemapPlugin } from './sitemap-xml.js';
47

58
describe('isSitemapPageUrl', () => {
69
it('should include crawlable html pages', () => {
@@ -105,3 +108,27 @@ describe('isSitemapPageUrl', () => {
105108
expect(sitemap.match(/<video:video>/g)).toHaveLength(1);
106109
});
107110
});
111+
112+
describe('sitemapPlugin', () => {
113+
let outputPath: string | undefined;
114+
115+
afterEach(async () => {
116+
if (outputPath) await rm(outputPath, { force: true, recursive: true });
117+
});
118+
119+
it('should publish the sitemap to the completed site output', async () => {
120+
outputPath = await mkdtemp(join(tmpdir(), 'elements-sitemap-'));
121+
const on = vi.fn();
122+
sitemapPlugin({ on });
123+
const afterBuild = on.mock.calls.find(([event]) => event === 'eleventy.after')?.[1];
124+
125+
await afterBuild({
126+
directories: { output: outputPath },
127+
results: [{ url: '/docs/elements/tabs/', content: '' }]
128+
});
129+
130+
await expect(readFile(join(outputPath, 'sitemap.xml'), 'utf-8')).resolves.toContain(
131+
'<loc>https://nvidia.github.io/elements/docs/elements/tabs/</loc>'
132+
);
133+
});
134+
});

‎projects/site/src/_11ty/plugins/updates-feed.js‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import rssPlugin from '@11ty/eleventy-plugin-rss';
44
import { getContentDates, normalizeContentDate } from '../utils/content-dates.js';
55
import { ELEMENTS_SITE_ORIGIN } from '../utils/site-url.js';
66

7-
const DEFAULT_PUBLIC_OUTPUT_PATH = './.11ty-vite/public';
7+
const DEFAULT_PUBLIC_OUTPUT_PATH = './dist';
88

99
export const UPDATES_COLLECTION = 'updates';
1010
export const RSS_UPDATES_COLLECTION = 'rssUpdates';
@@ -138,7 +138,7 @@ export function getAtomUpdatesCollection(collectionApi) {
138138
);
139139
}
140140

141-
export function updatesFeedPlugin(eleventyConfig, { publicOutputPath = DEFAULT_PUBLIC_OUTPUT_PATH } = {}) {
141+
export function updatesFeedPlugin(eleventyConfig, { publicOutputPath } = {}) {
142142
eleventyConfig.addCollection(RSS_UPDATES_COLLECTION, getRssUpdatesCollection);
143143
eleventyConfig.addCollection(ATOM_UPDATES_COLLECTION, getAtomUpdatesCollection);
144144
eleventyConfig.addPlugin(rssPlugin);
@@ -152,5 +152,7 @@ export function updatesFeedPlugin(eleventyConfig, { publicOutputPath = DEFAULT_P
152152
permalink: feed.outputPath
153153
});
154154
}
155-
eleventyConfig.on('eleventy.after', ({ results } = {}) => writeUpdateFeeds(results ?? [], publicOutputPath));
155+
eleventyConfig.on('eleventy.after', ({ results, directories }) =>
156+
writeUpdateFeeds(results ?? [], publicOutputPath ?? directories.output)
157+
);
156158
}

‎projects/site/src/_11ty/plugins/updates-feed.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,15 @@ describe('updatesFeedPlugin', () => {
116116
);
117117
});
118118

119-
it('should publish generated feeds to the Vite public directory', async () => {
119+
it('should publish generated feeds to the completed site output', async () => {
120120
publicOutputPath = await mkdtemp(join(tmpdir(), 'elements-updates-feed-'));
121121
const on = vi.fn();
122122
const atom = '<feed>updates</feed>';
123-
updatesFeedPlugin({ addCollection: vi.fn(), addPlugin: vi.fn(), addTemplate: vi.fn(), on }, { publicOutputPath });
123+
updatesFeedPlugin({ addCollection: vi.fn(), addPlugin: vi.fn(), addTemplate: vi.fn(), on });
124124
const afterBuild = on.mock.calls.find(([event]) => event === 'eleventy.after')?.[1];
125125

126126
await afterBuild({
127+
directories: { output: publicOutputPath },
127128
results: [
128129
{ content: '<rss>updates</rss>', url: '/feed.xml' },
129130
{ content: atom, url: '/atom.xml' },

0 commit comments

Comments
 (0)