Repository navigation
fix(portal): keep the portal's styling when it is built from another drive #440
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
c1c04ef
341d2f3
b198b89
494ffaa
61d5619
26ac4d3
586f226
02e4857
6cd6664
5a67acf
7b39678
76949da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { realpathSync } from 'node:fs'; | ||
| import { normalizePath, type Plugin, type ResolvedConfig } from 'vite'; | ||
| import { withBasePath } from './src/lib/base-path'; | ||
|
|
||
| /** A page's images from the static directory, which can sit on another Windows drive than this project. */ | ||
| export function staticImages(staticDirectory: string | null): Plugin[] { | ||
| const resolve: Plugin = { | ||
| name: 'apimatic:static-images:resolve', | ||
| enforce: 'pre', | ||
| resolveId: { | ||
| // Fumadocs' import of a file on another drive: the absolute path, still behind a `./` (fuma-nama/fumadocs#3723). | ||
| filter: { id: /^\.\/[A-Za-z]:\// }, | ||
| handler(source, importer, options) { | ||
| return this.resolve(source.slice(2), importer, { ...options, skipSelf: true }); | ||
| } | ||
| } | ||
| }; | ||
| if (staticDirectory === null) return [resolve]; | ||
|
|
||
| // A Windows path names the same file in any case. | ||
| const folded = (path: string) => (process.platform === 'win32' ? path.toLowerCase() : path); | ||
| // Vite names a module by its real path, but keeps the spelling of a `subst` drive. | ||
| const directories = [staticDirectory, realpathSync.native(staticDirectory)].map((dir) => | ||
| folded(`${normalizePath(dir)}/`) | ||
| ); | ||
| let config: ResolvedConfig; | ||
| return [ | ||
| resolve, | ||
| { | ||
| name: 'apimatic:static-images:serve', | ||
| apply: 'serve', | ||
| enforce: 'pre', | ||
| configResolved(resolved) { | ||
| config = resolved; | ||
| }, | ||
| // Vite's dev server serves no file from another drive (vitejs/vite#10802), but its public directory from any. | ||
| load(id) { | ||
| const directory = directories.find((dir) => folded(id).startsWith(dir)); | ||
| // A query such as `?raw` asks for more than the address, and stays Vite's to answer. | ||
|
Contributor
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. Worth recording as a known limitation: across drives, a
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. Recorded in the plan's §10, with the 403 from a |
||
| if (directory === undefined || id.includes('?') || !config.assetsInclude(id)) return null; | ||
| const address = withBasePath(`/${id.slice(directory.length)}`, config.base); | ||
| return `export default ${JSON.stringify(address)};`; | ||
| } | ||
| } | ||
| ]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import os from 'os'; | ||
| import sinon from 'sinon'; | ||
| import { expect } from 'chai'; | ||
| import { ok } from 'neverthrow'; | ||
| import { PreparePortalProjectAction } from '../../../src/actions/portal/prepare-project'; | ||
| import { PortalAuthorizationService } from '../../../src/infrastructure/services/portal-authorization-service'; | ||
| import { DirectoryPath } from '../../../src/types/file/directoryPath'; | ||
| import { CommandMetadata } from '../../../src/types/common/command-metadata'; | ||
| import { ProjectContext } from '../../../src/types/project-context'; | ||
| import { stubPreparePortalProject } from './prepare-project-stubs'; | ||
|
|
||
| const COMMAND_METADATA: CommandMetadata = { commandName: 'portal generate', shell: 'test' }; | ||
|
|
||
| // What `portal generate` and `portal serve` both run before either does its own part. | ||
| describe('PreparePortalProjectAction', () => { | ||
| afterEach(() => { | ||
| sinon.restore(); | ||
| }); | ||
|
|
||
| it('stops before signing in when the source is on a network share', async function () { | ||
| if (process.platform !== 'win32') this.skip(); | ||
| const shared = stubPreparePortalProject(); | ||
| const authorize = sinon.stub(PortalAuthorizationService.prototype, 'authorize').resolves(ok(undefined)); | ||
| const onPrepared = sinon.stub(); | ||
| const share = new DirectoryPath('\\\\server\\share\\api'); | ||
|
|
||
| const result = await new PreparePortalProjectAction(new DirectoryPath(os.tmpdir()), COMMAND_METADATA).execute( | ||
| ProjectContext.in(share), | ||
| { onPrepared } | ||
| ); | ||
|
|
||
| expect(result.isFailed()).to.be.true; | ||
| expect(shared.prompts.networkShareUnsupported.calledOnceWith(share.join('src'))).to.be.true; | ||
| expect(authorize.called).to.be.false; | ||
| expect(onPrepared.called).to.be.false; | ||
| }); | ||
| }); |
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.
The
//server/sharebranch doesn't work. Once./is stripped,this.resolve('//localhost/D$/…')is read as a protocol-relative URL, and the SSR bundle keeps it as an import that Node can't load. With the input at\\localhost\D$\pr440\inputs\nogitand a page using a static image, the build fails during prerender:Returning the id directly made that build pass, with the images bundled:
Even then, a share input loses its static directory:
/images/logo.pngis missing from the output, and nothing reports it. I got that from a share input with no page images too.devfails earlier on a share (EPERM creating a link), so this isn't a regression. Either fix both, or fail early with a clear "network shares aren't supported" message and drop the//branch from the filter.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.
Reproduced both on
\\localhost\C$. The second one is Vite's:normalizePathturns\\localhost\C$\…into/localhost/C$/…, sopublicDirnames no folder and the copy is skipped without a word. I went with failing early.portal generateandportal servenow stop on a share before signing in. The message says to copy the folder to a local drive or map the share to a drive letter.Z:builds completely, static directory included.//branch is gone from the filter.Fixed in 586f226.