From 46dcf38d4bcc1ad2ecd7fbcd85813ef60ec37d96 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Wed, 14 Oct 2026 10:08:04 -0400 Subject: [PATCH 1/5] Let Node tools read through the snapshot symlink --- hugo.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hugo.yaml b/hugo.yaml index de2b41585b..1d937f34ac 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -244,6 +244,19 @@ security: getenv: # cspell:disable-line # Netlify build env var - ^(BRANCH|BUILD_ID|COMMIT_REF|CONTEXT|DEPLOY_ID|NETLIFY|PULL_REQUEST|REVIEW_ID)$ + node: + permissions: + # Node tools (PostCSS, for the RTL stylesheet) may read only these paths, + # and Hugo rejects a symlink under them whose target lies elsewhere. + # Maintainers keep `public` as a symlink to a sibling snapshot repo; the + # two relative entries name it from the primary clone and from a task + # worktree (`wt/docsy-example/TASK`). Entries that don't exist are inert. + # `.` is the default and must stay: the npm workspace link under + # `node_modules/@docsy` resolves inside the project. + allowRead: + - . + - ../docsy-example.g + - ../../../docsy-example.g module: # Uncomment the next line to build and serve using local docsy clone declared in the named Hugo workspace: From c3a620aa0964117e11eaa1e0091db44172972292 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Wed, 14 Oct 2026 10:37:59 -0400 Subject: [PATCH 2/5] Home the snapshot-symlink rationale in CONTRIBUTING --- CONTRIBUTING.md | 15 +++++++++++++++ hugo.yaml | 8 +------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d71f08752..5c10a6aefd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,6 +114,19 @@ npm run local -- serve The `local` prefix runs the script against the sibling Docsy, and the server watches it, so theme edits hot-reload. +### Built-site snapshot + +To diff the built site across changes (step 7 of Docsy's [consumer-site test +procedure][]), keep `public` as a symlink to a sibling git repo, +`../docsy-example.g`, and commit its contents after each build with +`npm run _commit:public`. Hugo's Node [permission model][] lets Node tools +(PostCSS, for the RTL stylesheet) read only the paths listed under +`security.node.permissions.allowRead`, and rejects a symlink whose target lies +outside them, so `hugo.yaml` lists the snapshot repo as seen from the primary +clone and from a task worktree under `wt/docsy-example/`, beside Hugo's default +`.`, which the list replaces. Another layout adds its own entry; a path that +doesn't exist is inert. + ### Link checking For the link cache (the committed `link-cache.jsonc`, its fields, and the @@ -129,6 +142,7 @@ cache][]. What differs here: [alternate dashboard]: https://app.netlify.com/sites/goldydocs/deploys +[consumer-site test procedure]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#consumer-site-test [contribution guidelines]: https://main--docsydocs.netlify.app/docs/contributing/ [Dependency updates]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#dependency-updates [deploys]: https://app.netlify.com/sites/docsy-example/deploys @@ -139,6 +153,7 @@ cache][]. What differs here: [main ruleset]: https://github.com/docsy/docsy-example/rules/23697395 [Merge requirements]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#merge-requirements [officially supported version]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#official-hugo-version +[permission model]: https://gohugo.io/configuration/security/#nodepermissionsallowread [workflow security analysis]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#workflow-security-analysis diff --git a/hugo.yaml b/hugo.yaml index 1d937f34ac..bc746cfbf0 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -246,13 +246,7 @@ security: - ^(BRANCH|BUILD_ID|COMMIT_REF|CONTEXT|DEPLOY_ID|NETLIFY|PULL_REQUEST|REVIEW_ID)$ node: permissions: - # Node tools (PostCSS, for the RTL stylesheet) may read only these paths, - # and Hugo rejects a symlink under them whose target lies elsewhere. - # Maintainers keep `public` as a symlink to a sibling snapshot repo; the - # two relative entries name it from the primary clone and from a task - # worktree (`wt/docsy-example/TASK`). Entries that don't exist are inert. - # `.` is the default and must stay: the npm workspace link under - # `node_modules/@docsy` resolves inside the project. + # Snapshot symlink targets, see CONTRIBUTING.md § Built-site snapshot. allowRead: - . - ../docsy-example.g From c4e4d0c8150aaa5cc303ffdaee7b0f3c7b70bd87 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 15 Oct 2026 20:15:03 -0400 Subject: [PATCH 3/5] State the symlink rule and why `.` stays; label the list precisely r1.1-r1.3 --- CONTRIBUTING.md | 15 ++++++++------- hugo.yaml | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c10a6aefd..d7447a8a16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,13 +119,14 @@ watches it, so theme edits hot-reload. To diff the built site across changes (step 7 of Docsy's [consumer-site test procedure][]), keep `public` as a symlink to a sibling git repo, `../docsy-example.g`, and commit its contents after each build with -`npm run _commit:public`. Hugo's Node [permission model][] lets Node tools -(PostCSS, for the RTL stylesheet) read only the paths listed under -`security.node.permissions.allowRead`, and rejects a symlink whose target lies -outside them, so `hugo.yaml` lists the snapshot repo as seen from the primary -clone and from a task worktree under `wt/docsy-example/`, beside Hugo's default -`.`, which the list replaces. Another layout adds its own entry; a path that -doesn't exist is inert. +`npm run _commit:public`. Hugo's Node [permission model][] rejects that symlink +unless its target is listed under `security.node.permissions.allowRead` +(PostCSS, for the RTL stylesheet, is the tool that trips it here), so +`hugo.yaml` lists the snapshot repo as seen from the primary clone and from a +task worktree under `wt/docsy-example/`. The list replaces Hugo's default, so it +keeps `.`: the npm workspace link under `node_modules/@docsy` resolves into the +project and would fail the same check without it. Another layout adds its own +entry; a path that doesn't exist is inert. ### Link checking diff --git a/hugo.yaml b/hugo.yaml index bc746cfbf0..59924d11bb 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -246,7 +246,8 @@ security: - ^(BRANCH|BUILD_ID|COMMIT_REF|CONTEXT|DEPLOY_ID|NETLIFY|PULL_REQUEST|REVIEW_ID)$ node: permissions: - # Snapshot symlink targets, see CONTRIBUTING.md § Built-site snapshot. + # Project root plus the public snapshot symlink's targets; see + # CONTRIBUTING.md § Built-site snapshot. allowRead: - . - ../docsy-example.g From 28d6418ef1a7c8d1977956b9d3be31d75a2e4553 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 15 Oct 2026 21:14:13 -0400 Subject: [PATCH 4/5] Describe both snapshot layouts; note the template-user and worktree cases --- CONTRIBUTING.md | 27 ++++++++++++++++----------- hugo.yaml | 5 +++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7447a8a16..e6dfdf2f08 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,17 +116,22 @@ watches it, so theme edits hot-reload. ### Built-site snapshot -To diff the built site across changes (step 7 of Docsy's [consumer-site test -procedure][]), keep `public` as a symlink to a sibling git repo, -`../docsy-example.g`, and commit its contents after each build with -`npm run _commit:public`. Hugo's Node [permission model][] rejects that symlink -unless its target is listed under `security.node.permissions.allowRead` -(PostCSS, for the RTL stylesheet, is the tool that trips it here), so -`hugo.yaml` lists the snapshot repo as seen from the primary clone and from a -task worktree under `wt/docsy-example/`. The list replaces Hugo's default, so it -keeps `.`: the npm workspace link under `node_modules/@docsy` resolves into the -project and would fail the same check without it. Another layout adds its own -entry; a path that doesn't exist is inert. +To diff each build against the last (the A/B diff step of Docsy's [consumer-site +test procedure][]), keep the built site in a git repo and commit it after each +build with `npm run _commit:public`. Two layouts: + +- `npm run make:public` makes `public/` itself a repo, inside the project; it + needs no configuration. +- Maintainers keep `public` as a symlink to a sibling repo, + `../docsy-example.g`, so that the primary clone and its task worktrees + (`wt/docsy-example/TASK`, beside the clone) share one snapshot. Hugo's Node + [permission model][] rejects a symlink whose target isn't in + `security.node.permissions.allowRead`, so `hugo.yaml` lists the snapshot as + seen from both places, plus `.` (the npm workspace link under + `node_modules/@docsy` resolves into the project and needs it). PostCSS, run + for the RTL stylesheet, is the tool that trips the check. From a worktree, the + entry is live whether or not `public` is a symlink; an entry whose path + doesn't exist is inert, so another layout adds its own. ### Link checking diff --git a/hugo.yaml b/hugo.yaml index 59924d11bb..e0a494ab0c 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -246,8 +246,9 @@ security: - ^(BRANCH|BUILD_ID|COMMIT_REF|CONTEXT|DEPLOY_ID|NETLIFY|PULL_REQUEST|REVIEW_ID)$ node: permissions: - # Project root plus the public snapshot symlink's targets; see - # CONTRIBUTING.md § Built-site snapshot. + # Project root, and the maintainers' snapshot repo as seen from the + # primary clone and from a task worktree (CONTRIBUTING.md § Built-site + # snapshot); sites made from this template can drop the `node` block. allowRead: - . - ../docsy-example.g From bc97380ff1cc2c7428c9c1b15b45eeb995b01b0a Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Fri, 16 Oct 2026 10:43:18 -0400 Subject: [PATCH 5/5] Give the snapshot symlink target by location --- CONTRIBUTING.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6dfdf2f08..e3c7889e5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,16 +122,17 @@ build with `npm run _commit:public`. Two layouts: - `npm run make:public` makes `public/` itself a repo, inside the project; it needs no configuration. -- Maintainers keep `public` as a symlink to a sibling repo, - `../docsy-example.g`, so that the primary clone and its task worktrees - (`wt/docsy-example/TASK`, beside the clone) share one snapshot. Hugo's Node - [permission model][] rejects a symlink whose target isn't in - `security.node.permissions.allowRead`, so `hugo.yaml` lists the snapshot as - seen from both places, plus `.` (the npm workspace link under - `node_modules/@docsy` resolves into the project and needs it). PostCSS, run - for the RTL stylesheet, is the tool that trips the check. From a worktree, the - entry is live whether or not `public` is a symlink; an entry whose path - doesn't exist is inert, so another layout adds its own. +- Maintainers keep `public` as a symlink to a sibling repo, `docsy-example.g`, + so that the primary clone and its task worktrees (`wt/docsy-example/TASK`, + beside the clone) share one snapshot. Hugo's Node [permission model][] rejects + a symlink whose target isn't in `security.node.permissions.allowRead`, so + `hugo.yaml` lists the snapshot as seen from the clone (`../docsy-example.g`) + and from a worktree (`../../../docsy-example.g`), plus `.` (the npm workspace + link under `node_modules/@docsy` resolves into the project and needs it). + PostCSS, run for the RTL stylesheet, is the tool that trips the check. From a + worktree, the entry is live whether or not `public` is a symlink; an entry + whose path doesn't exist is inert. A snapshot kept elsewhere needs its own + entry. ### Link checking