aboutsummaryrefslogtreecommitdiff
path: root/scripts/docs
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-26 17:15:50 +0200
committerGitHub <noreply@github.com>2026-09-26 17:15:50 +0200
commitc34dd6b2acd432cb7acbe9de568b245853e3b8c7 (patch)
treea68818659f64e7f760a6d1f16176b0d843d97568 /scripts/docs
parent804a4aaf3ec21e681a09d3021d96b33206280b16 (diff)
fix: create a symlink on non-elevated windows in docs test (#4654)
* fix: create a symlink on non-elevated windows in docs test Windows terminals without Developer Mode or elevation cannot create file symlinks, so the versions publishing integration test failed with EPERM before it could reach the non-ordinary-file rejection. Fall back to a directory junction, which is creatable without elevation and hits the same rejection path. * fix: detect docs snapshot reparse points with lstat Classify entries with lstat instead of the readdir dirent so symlinks and Windows junctions are rejected regardless of how a runtime classifies them. This keeps the versions integration test's non-ordinary-file assertion meaningful on non-elevated Windows, where the fixture can only create a directory junction.
Diffstat (limited to 'scripts/docs')
-rw-r--r--scripts/docs/snapshot.mjs14
-rw-r--r--scripts/docs/versions.integration.test.ts17
2 files changed, 23 insertions, 8 deletions
diff --git a/scripts/docs/snapshot.mjs b/scripts/docs/snapshot.mjs
index e9eaf92c..7610715a 100644
--- a/scripts/docs/snapshot.mjs
+++ b/scripts/docs/snapshot.mjs
@@ -1,5 +1,5 @@
import { execFileSync } from 'node:child_process';
-import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
+import { lstat, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { dirname, relative, resolve } from 'node:path';
export function createGit(repoRoot) {
@@ -92,10 +92,14 @@ export async function compareGitTree(git, ref, sourceRoot, snapshotRoot) {
export async function listDocumentationPaths(root) {
const paths = [];
async function walk(directory) {
- for (const entry of await readdir(directory, { withFileTypes: true })) {
- const path = resolve(directory, entry.name);
- if (entry.isDirectory()) await walk(path);
- else if (entry.isFile()) paths.push(relative(root, path));
+ for (const name of await readdir(directory)) {
+ const path = resolve(directory, name);
+ // lstat does not follow links, so symlinks and Windows junctions stay
+ // non-ordinary here even if a runtime classifies them as directories in
+ // the readdir entry.
+ const stats = await lstat(path);
+ if (stats.isDirectory()) await walk(path);
+ else if (stats.isFile()) paths.push(relative(root, path));
else throw new Error(`${path} is not an ordinary documentation file`);
}
}
diff --git a/scripts/docs/versions.integration.test.ts b/scripts/docs/versions.integration.test.ts
index 9731d3c0..214bfef9 100644
--- a/scripts/docs/versions.integration.test.ts
+++ b/scripts/docs/versions.integration.test.ts
@@ -100,9 +100,20 @@ describe('documentation release publishing', () => {
correctedArchivedDocs,
);
const linkedPage = resolve(archivedDocsRoot, 'linked.mdx');
- await symlink('index.mdx', linkedPage);
- expect(() => runScript(root, ['check'])).toThrow();
- await rm(linkedPage);
+ if (process.platform === 'win32') {
+ // A normal Windows terminal cannot create file symlinks without Developer
+ // Mode or elevation, but a directory junction is creatable without
+ // either. Link to an empty directory outside the snapshot so the walk
+ // stays finite and only the non-ordinary-file rejection can satisfy the
+ // assertion below.
+ const linkTarget = await mkdtemp(join(tmpdir(), 'herdr-docs-link-'));
+ temporaryDirectories.push(linkTarget);
+ await symlink(linkTarget, linkedPage, 'junction');
+ } else {
+ await symlink('index.mdx', linkedPage);
+ }
+ expect(() => runScript(root, ['check'])).toThrow(/is not an ordinary documentation file/);
+ await rm(linkedPage, { recursive: true, force: true });
runScript(root, ['check']);
delete archivedManifest.versions[0].commit;