-
Notifications
You must be signed in to change notification settings - Fork 95
fix(bin): Don't crash only-include-used-icons when public/dsfr exists without an index.html #506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c279bd8
fa0dbe1
7ea46f8
aa428f3
38dc8c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -116,7 +116,13 @@ type CommandContext = { | |||||||||||||
| spaParams: | ||||||||||||||
| | { | ||||||||||||||
| dsfrDirPath_static: string; | ||||||||||||||
| htmlFilePath: string; | ||||||||||||||
| // Undefined whenever public/dsfr exists but no index.html was found to add | ||||||||||||||
| // a cache busting query parameter to: a project that used to be a Vite/CRA | ||||||||||||||
| // app and lost its index.html, or a public/dsfr that was committed or | ||||||||||||||
| // restored by other means. Not reachable through copy-static-assets, which | ||||||||||||||
| // resolves index.html through the same two paths as this script and asserts | ||||||||||||||
| // "Can't locate your index.html file." before creating anything. | ||||||||||||||
| htmlFilePath: string | undefined; | ||||||||||||||
| } | ||||||||||||||
| | undefined; | ||||||||||||||
| isSilent: boolean; | ||||||||||||||
|
|
@@ -401,8 +407,6 @@ async function getCommandContext(args: string[]): Promise<CommandContext> { | |||||||||||||
| return undefined; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| assert(htmlFilePath !== undefined); | ||||||||||||||
|
|
||||||||||||||
| return { | ||||||||||||||
| dsfrDirPath_static, | ||||||||||||||
| htmlFilePath | ||||||||||||||
|
|
@@ -475,7 +479,12 @@ export async function main(args: string[]) { | |||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| return { "usedIconClassNames": Array.from(setUsedIconClassNames) }; | ||||||||||||||
| // NOTE: The set is filled from a Promise.all over the source files, so its | ||||||||||||||
| // insertion order follows I/O completion order and varies between runs. Sorting | ||||||||||||||
| // makes the generated stylesheet byte stable, which is what the `hasChanged` | ||||||||||||||
| // comparison below relies on. Rule order carries no meaning here: every rule | ||||||||||||||
| // targets a distinct `.fr-icon-*::before` / `.ri-*::before` selector. | ||||||||||||||
| return { "usedIconClassNames": Array.from(setUsedIconClassNames).sort() }; | ||||||||||||||
| })(); | ||||||||||||||
|
|
||||||||||||||
| if (usedIconClassNames.length > 300) { | ||||||||||||||
|
|
@@ -529,22 +538,50 @@ export async function main(args: string[]) { | |||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| if (!hasChanged) { | ||||||||||||||
| log?.("No change since last run"); | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // NOTE: Deliberately outside of the `hasChanged` guard below. These three writes are | ||||||||||||||
| // idempotent, and inside the guard a stale or hand reverted output could never be | ||||||||||||||
| // repaired as long as icons.min.css itself did not change. Nothing else writes them: | ||||||||||||||
| // copy-dsfr-to-public builds its keep list from the url() of dsfr.min.css, so the | ||||||||||||||
| // icons and the hash query parameter are this script's responsibility alone. | ||||||||||||||
| await Promise.all([ | ||||||||||||||
| (async function addHashQueryParameterInIndexHtml() { | ||||||||||||||
| const htmlFilePath = commandContext.spaParams?.htmlFilePath; | ||||||||||||||
|
|
||||||||||||||
| if (htmlFilePath === undefined) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const html = (await readFile(htmlFilePath)).toString("utf8"); | ||||||||||||||
|
|
||||||||||||||
| const { modifiedHtml } = modifyHtmlHrefs({ | ||||||||||||||
| "html": html, | ||||||||||||||
| "getModifiedHref": href => { | ||||||||||||||
| if (!href.includes(iconsMinCssRelativePath.replace(/\\/g, "/"))) { | ||||||||||||||
| return href; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const [urlWithoutQuery] = href.split("?"); | ||||||||||||||
|
|
||||||||||||||
| return `${urlWithoutQuery}?hash=${fnv1aHashToHex( | ||||||||||||||
| rawIconCssCodeBuffer.toString("utf8") | ||||||||||||||
| )}`; | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| if (modifiedHtml === html) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| await writeFile(htmlFilePath, Buffer.from(modifiedHtml, "utf8")); | ||||||||||||||
| })(), | ||||||||||||||
| (async function generateUsedRemixiconFiles() { | ||||||||||||||
| await Promise.all( | ||||||||||||||
| [commandContext.dsfrDirPath, commandContext.spaParams?.dsfrDirPath_static] | ||||||||||||||
| .filter(exclude(undefined)) | ||||||||||||||
| .map(async dsfrDistDirPath => { | ||||||||||||||
| const remixiconDirPath = pathJoin(dsfrDistDirPath, "icons", "remixicon"); | ||||||||||||||
|
|
||||||||||||||
| if (!fs.existsSync(remixiconDirPath)) { | ||||||||||||||
| fs.mkdirSync(remixiconDirPath); | ||||||||||||||
| } | ||||||||||||||
| fs.mkdirSync(remixiconDirPath, { "recursive": true }); | ||||||||||||||
|
|
||||||||||||||
| await Promise.all( | ||||||||||||||
| usedIcons | ||||||||||||||
|
|
@@ -583,56 +620,35 @@ export async function main(args: string[]) { | |||||||||||||
| ) | ||||||||||||||
| .map(([srcFilePath, destFilePath]) => cp(srcFilePath, destFilePath)) | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This copy and its sibling Unrelated nit two functions up, since it is not in the diff and I cannot anchor it:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both confirmed, fixed in fa0dbe1. The guard. My own NOTE argued for it and I moved only half of it. The nit. What your repro turned up. It took several attempts to reproduce, because it only fails about half the time, and the reason is a second bug. Verified on a fixture (
|
||||||||||||||
| ); | ||||||||||||||
| })(), | ||||||||||||||
| (async function addHashQueryParameterInIndexHtml() { | ||||||||||||||
| if (commandContext.spaParams === undefined) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
| })() | ||||||||||||||
| ]); | ||||||||||||||
|
|
||||||||||||||
| const html = (await readFile(commandContext.spaParams.htmlFilePath)).toString("utf8"); | ||||||||||||||
| if (!hasChanged) { | ||||||||||||||
| log?.("No change since last run"); | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const { modifiedHtml } = modifyHtmlHrefs({ | ||||||||||||||
| "html": html, | ||||||||||||||
| "getModifiedHref": href => { | ||||||||||||||
| if (!href.includes(iconsMinCssRelativePath.replace(/\\/g, "/"))) { | ||||||||||||||
| return href; | ||||||||||||||
| await (async function clearCache() { | ||||||||||||||
| await Promise.all( | ||||||||||||||
| [ | ||||||||||||||
| pathJoin(".next", "cache"), | ||||||||||||||
| pathJoin(".vite"), | ||||||||||||||
| pathJoin(".cache", "storybook"), | ||||||||||||||
| pathJoin(".cache", "babel-loader"), | ||||||||||||||
| pathJoin(".cache", "default-development") | ||||||||||||||
| ] | ||||||||||||||
| .map(relativeDirPath => | ||||||||||||||
| pathJoin(commandContext.projectDirPath, "node_modules", relativeDirPath) | ||||||||||||||
| ) | ||||||||||||||
| .map(async dirPath => { | ||||||||||||||
| if (!(await existsAsync(dirPath))) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const [urlWithoutQuery] = href.split("?"); | ||||||||||||||
|
|
||||||||||||||
| return `${urlWithoutQuery}?hash=${fnv1aHashToHex( | ||||||||||||||
| rawIconCssCodeBuffer.toString("utf8") | ||||||||||||||
| )}`; | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| await writeFile( | ||||||||||||||
| commandContext.spaParams.htmlFilePath, | ||||||||||||||
| Buffer.from(modifiedHtml, "utf8") | ||||||||||||||
| ); | ||||||||||||||
| })(), | ||||||||||||||
| (async function clearCache() { | ||||||||||||||
| await Promise.all( | ||||||||||||||
| [ | ||||||||||||||
| pathJoin(".next", "cache"), | ||||||||||||||
| pathJoin(".vite"), | ||||||||||||||
| pathJoin(".cache", "storybook"), | ||||||||||||||
| pathJoin(".cache", "babel-loader"), | ||||||||||||||
| pathJoin(".cache", "default-development") | ||||||||||||||
| ] | ||||||||||||||
| .map(relativeDirPath => | ||||||||||||||
| pathJoin(commandContext.projectDirPath, "node_modules", relativeDirPath) | ||||||||||||||
| ) | ||||||||||||||
| .map(async dirPath => { | ||||||||||||||
| if (!(await existsAsync(dirPath))) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| await rm(dirPath, { "recursive": true, "force": true }); | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
| })() | ||||||||||||||
| ]); | ||||||||||||||
| await rm(dirPath, { "recursive": true, "force": true }); | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
| })(); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (require.main === module) { | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong, and it is my fault for starting it. Next.js has no
public/dsfrin the documented setup:copy-dsfr-to-public.ts:7says "not in Next.js for example", and this repo's own integration apps confirm it,next-appdirandnext-pagesdirrunonly-include-used-iconsalone whileviteandcraruncopy-dsfr-to-public && only-include-used-icons. A canonical Next.js fixture exits 0 onmaintoo, so there is no crash there. My original repro createdpublic/dsfrby hand, which is not the Next.js path, and you took my word for it in #505. Your PR body is accurate as written (monorepo, ex-SPA), it is just this comment and the "unusable in Next.js today" line. Accurate framing: any project with apublic/dsfrand noindex.html, which includes a Next.js app that opted intocopy-static-assets.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, and it's worth spelling out the evidence since it also settles part of my #505 write-up:
test/integration/next-appdir/package.jsonandnext-pagesdir/package.jsonrunonly-include-used-iconsalone, whileviteandcraruncopy-dsfr-to-publicfirst. So in the documented Next.js setup there is nopublic/dsfr,dsfrDirPath_staticisundefined,spaParamsisundefined, and the assertion is never reached. No crash there, as you say.One correction to the framing you propose, though: it does not include a Next.js app that opted into
copy-static-assets. That command asserts"Can't locate your index.html file."atcopy-dsfr-to-public.ts:60, before themkdirSync(dsfrDirPath)at:95, so a project with noindex.htmlnever gets apublic/dsfrout of it either. Confirmed on a fixture: it exits non-zero andpublic/stays empty. That is the same point I made in #505 point 6 and then contradicted in this comment.Reworded in fa0dbe1, then corrected in f0297d7 once I checked that
copy-static-assetsclaim, down to the states that actually reach this branch:The same comment had been copied into #505; corrected there too (a84376f).