Prepare the Nuxt app to move from nuxt/ to the repository root - #5871
ZJvandeWeg wants to merge 9 commits into
Conversation
Add content/handbook/ as the newest handbook prefix and skip a rename that only moves a page between two prefixes. Without this, the commit that moves the Nuxt app to the repository root would list every handbook page as renamed in that week's Recent Changes, and the history under nuxt/content/handbook would drop out of the log's pathspec.
The Nuxt modules took the repository root to be the parent of the Nuxt root, and seven tests reached it with a fixed '../..' from nuxt/lib/. Both stop being true once the app moves to the repository root. nuxt/lib/repo-root.mjs finds the closest directory holding package-lock.json instead, which is correct on either side of the move and, for a worktree nested under .claude/worktrees/, resolves to the worktree rather than the checkout around it.
…o ESM lib/search-index.js is the only file in the root lib/, which would collide with nuxt/lib/ once the app moves to the repository root, so it joins the other plain-JS helpers there. It and the two CommonJS scripts, index-algolia and watch_blueprints, become ES modules, since the merged package.json will declare "type": "module" for the whole repository.
tailwind.config.js moves into nuxt/, beside nuxt/assets/css/style.css, the only file that loads it, so its @config path stays the same when nuxt/ moves to the repository root. It becomes an ES module and postcss.config.js becomes postcss.config.cjs, ahead of the merged package.json's "type": "module": postcss-cli 9 loads its config with require(). The compiled stylesheet is byte-for-byte unchanged.
nuxt/package.json only existed so Nuxt and Eleventy could keep separate dependencies. With Eleventy gone it splits one application's dependencies over two files and routes every Nuxt command through --workspace=nuxt. Its dependencies move into the root package.json, along with its "type": "module", and the scripts call nuxt directly with nuxt/ as the root directory. Script names are unchanged, since FlowFuse/github-actions-workflows' build_website workflow and netlify.toml call them by name. Every package resolves to the same version as before except markdown-it: the copy nuxt/server/utils/integrations-enrich.ts imports goes from 14.3.0 to 14.3.2, and the 15.0.0 copy that satisfied the markdown-it plugins' open peer ranges is no longer installed.
A second .gitignore under nuxt/ would collide with the root one when the app moves up. Its rules are now root rules under nuxt/, grouped with the matching ones already there. The build-output rules are anchored at nuxt/ rather than matching at any depth; nothing nested ever used them, and the set of ignored files in a built checkout is otherwise identical.
It spelled the guides path out as an escaped regex, nuxt\/content-guides, which a later rewrite of the nuxt/ prefix cannot match. Comparing against a string built from GUIDES_SOURCE follows the constant wherever the guides live. A comment in the sitemap route that named the nuxt/ workspace now says the same thing without naming a layout.
style.css reached two packages' CSS through ../../../node_modules/, a path that only holds while the stylesheet sits exactly three levels below the repository root. Importing by package name lets Tailwind resolve node_modules itself, from wherever the file is. Neither package declares exports, so the deep paths resolve, and the compiled stylesheet is byte-for-byte unchanged.
A guide's updated timestamp came from the newest commit touching its path, so moving the guides tree would stamp all 149 guide pages with the date of the move. It now follows the file back through renames and skips a rename that left it unchanged, the same rule nuxt/lib/git-lastmod.mjs applies to the sitemap. A move that also edited the file still dates it. For a guide that never moved, the result is the same commit as before.
✅ Deploy Preview for flowfuse-website ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
I’m in the process of reviewing it, but I’d like to test a few things first. I’ll likely finish it up tomorrow. |
|
|
||
| /** True when a rename status only moves a page from one handbook prefix to another. */ | ||
| export function isRelocation (status, fromPath, toPath) { | ||
| if (!status.startsWith('R')) return false |
There was a problem hiding this comment.
Shouldn't this check for R100 specifically, like guides-sync.mjs's -M100%?
| if (!status.startsWith('R')) return false | |
| if (status !== 'R100') return false |
|
|
||
| test('updated is the last change to a guide, not the commit that moved it', () => { | ||
| const { root, contentDocsDir, publicDocsDir, cleanup } = scratch() | ||
| const git = (date, ...args) => execFileSync('git', args, { |
There was a problem hiding this comment.
Missing -c commit.gpgsign=false, breaks on any machine/CI with global signing on. See git-lastmod.test.mjs's git helper for the pattern.
| const git = (date, ...args) => execFileSync('git', args, { | |
| const git = (date, ...args) => execFileSync('git', ['-c', 'commit.gpgsign=false', ...args], |
| // moving the guides tree does not stamp every guide with the date of the move. A move | ||
| // that also edited the file is not detected as a rename at 100%, so it shows up as an | ||
| // add and dates the file, as an edit should. | ||
| const updated = gitOutput(repoRoot, [ |
There was a problem hiding this comment.
nuxt/lib/git-lastmod.mjs's getGitLastmod() already solves this exact problem with one memoized whole-repo walk, could this reuse it instead of a per-file git log --follow?
| "generate": "nuxt generate", | ||
| "preview": "nuxt preview" |
There was a problem hiding this comment.
generate/preview weren't migrated to the root package.json but dev/build were (as dev:nuxt/prod:nuxt). Was that intentional?
| 'log', '--no-merges', '-M', '--name-status', '--date=short', | ||
| `--pretty=format:${SEP}%H${SEP}%ad${SEP}%s`, | ||
| '--', 'nuxt/content/handbook', 'src/handbook' | ||
| '--', ...HANDBOOK_PREFIXES.map(prefix => prefix.slice(0, -1)) |
There was a problem hiding this comment.
Does git actually need the trailing slash stripped here?
| '--', ...HANDBOOK_PREFIXES.map(prefix => prefix.slice(0, -1)) | |
| '--', ...HANDBOOK_PREFIXES |
| } = require("../lib/search-index"); | ||
| } from "../nuxt/lib/search-index.mjs"; | ||
|
|
||
| const site = JSON.parse(await fs.readFile(new URL("../nuxt/data/site.json", import.meta.url), "utf8")); |
There was a problem hiding this comment.
How about import site from "../nuxt/data/site.json" with { type: "json" } up with the other imports, instead of a manual readFile+JSON.parse + top-level await?
|
@ZJvandeWeg I've added my comments inline. |

Description
The Nuxt app lives in
nuxt/only because it had to share the repository with Eleventy. With Eleventy gone (#5795), the plan is to move it to the repository root. This PR does everything that can land before that move, so the move itself is a pure rename plus a mechanical prefix rewrite that is quick to review.Nothing here changes what the site serves. Each commit stands on its own:
content/handbook/as the newest handbook prefix, and skips a rename that only moves a page between prefixes. Without this, the move commit would list every handbook page as "Renamed" on Recent Changes.../from nuxt/: the two sync modules took the repo root to bedirname(nuxt.options.rootDir), and seven tests used a fixed'../..'.nuxt/lib/repo-root.mjsnow finds the closest directory holdingpackage-lock.json, which works on either side of the move and resolves a.claude/worktrees/*worktree to itself.lib/held one file and would collide withnuxt/lib/.index-algoliaandwatch_blueprintsbecome.mjs.tailwind.config.jsmoves intonuxt/, andpostcss.config.jsbecomes.cjsbecause postcss-cli 9require()s it.package.jsonwith"type": "module", and no--workspace=nuxt. Script names are unchanged, becausenetlify.tomlandFlowFuse/github-actions-workflows'build_websitecall them by name.GUIDES_SOURCE: it was an escaped regex (nuxt\/content-guides) that a prefix rewrite can't see.style.cssreachednode_modulesthrough../../../, which only holds at exactly that depth.updatedtimestamp came fromgit log -1 -- <path>, so movingcontent-guides/would stamp all 149 guide pages with the move date. It now follows renames and skips a pure rename. A new test on a scratch git repository pins this. On a full clone of main, all 149 pages get the same date as before.Verification
npm test: 287 pass (283 on main, plus the new tests).style.cssis byte-for-byte identical to main's.build:nuxt:skip-imageson this branch and on main, with the same docs and blueprints sources. Both produce the same 6,671 files. After normalising build IDs, timestamps, chunk hashes and the checkout path, every page, payload and sitemap entry matches except things that also vary between two builds of main: the shuffled customer-logo strips, random payload keys, and Shiki class names (token colours checked identical).markdown-itthatnuxt/server/utils/integrations-enrich.tsimports, which goes from 14.3.0 to 14.3.2 (patch). The 15.0.0 copy that only satisfied the markdown-it plugins' open peer ranges is gone.npm run dev:nuxtstarts from the repo root. Request handling in my environment fails withspawn EBADFexactly as it does on main, so please load a page locally before merging.The follow-up
The move itself:
git mvevery entry ofnuxt/to the root, drop thenuxt/prefix, and make six small edits the prefix rewrite can't express (nuxt dev nuxt→nuxt dev,join(repoRoot, 'nuxt')→repoRootin three scripts, Studio'srootDir: 'nuxt', two sentences in README/CLAUDE.md). I've done a trial run on top of this branch. It changes 4,611 files, all but 15 of them pure or near-pure renames. Tests pass, and the build matches this branch's except for the expected edit-link and StudiorootDirchanges.Before it can merge:
FlowFuse/github-actions-workflows'build_website.ymlnameswebsite/nuxt/for the link check,dist/, and the og-image caches. It needs to resolve the app directory instead, like it already does forsrc/. That change is ready and waiting for approval to open.FlowFuse/flowfuse's docs workflow runs its own copy of the build with--sources website/src, which already breaks now thatsrc/is gone. Preview the website on documentation pull requests flowfuse#8070 replaces it with the shared workflow.netlify.toml.Out of scope here, for separate PRs: production still builds with
SKIP_IMAGES, which turns Nuxt Image's Netlify provider off. The image-compression workflow'signorePaths: 'nuxt/**'also covers every image, so it compresses nothing.Related Issue(s)
Follows #5795.
Checklist