Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c5962d3

Browse files
committed
feat(Build): add getMissingLocalDependencies function
- Implemented a function to check for missing local dependencies in the import map. - Returns a list of missing package@version strings. - Integrated pre-build check for undeclared dependencies in the build process.
1 parent b781e73 commit c5962d3

File tree

3 files changed

+172
-54
lines changed

3 files changed

+172
-54
lines changed

src/Perla/Build.fs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,85 @@ module Build =
228228

229229
cssFiles, jsFiles
230230

231+
/// Checks for missing local dependencies referenced in the import map (from both imports and scopes).
232+
/// Returns a list of missing package@version strings.
233+
let getMissingLocalDependencies
234+
(config: PerlaConfig)
235+
(importMap: Perla.PkgManager.ImportMap)
236+
: string list =
237+
if not config.useLocalPkgs then
238+
[]
239+
else
240+
let tryExtractPkgVer(path: string) =
241+
let marker = "/node_modules/.perla/"
242+
let idx = path.IndexOf(marker)
243+
244+
if idx >= 0 then
245+
let rest = path.Substring(idx + marker.Length)
246+
247+
let parts =
248+
rest.Split([| '/' |], System.StringSplitOptions.RemoveEmptyEntries)
249+
250+
if parts.Length = 0 then
251+
None
252+
elif parts.[0].StartsWith("@") && parts.Length >= 2 then
253+
// Scoped package: join first two segments
254+
Some(parts.[0] + "/" + parts.[1])
255+
else
256+
// Unscoped package: just the first segment
257+
Some(parts.[0])
258+
else
259+
None
260+
261+
let allPkgVers =
262+
importMap.imports.Values
263+
|> Seq.append(
264+
importMap.scopes |> Seq.collect(fun kv -> kv.Value.Values)
265+
)
266+
|> Seq.choose tryExtractPkgVer
267+
|> Seq.distinct
268+
|> Seq.toList
269+
270+
let cwd = System.IO.Directory.GetCurrentDirectory()
271+
272+
let getTopLevelNodeModulesPath (cwd: string) (pkgVer: string) =
273+
if pkgVer.StartsWith("@") then
274+
// Scoped: @scope/name@version
275+
let atIdx = pkgVer.IndexOf("@", 1) // skip first char
276+
277+
if atIdx > 0 then
278+
let scope = pkgVer.Substring(0, atIdx)
279+
let nameAndVersion = pkgVer.Substring(atIdx + 1)
280+
let nameEndIdx = nameAndVersion.IndexOf("@")
281+
282+
if nameEndIdx > 0 then
283+
let name = nameAndVersion.Substring(0, nameEndIdx)
284+
System.IO.Path.Combine(cwd, "node_modules", scope, name)
285+
else
286+
System.IO.Path.Combine(cwd, "node_modules", scope)
287+
else
288+
System.IO.Path.Combine(cwd, "node_modules", pkgVer)
289+
else
290+
// Unscoped: name@version
291+
let nameEndIdx = pkgVer.IndexOf("@")
292+
293+
let name =
294+
if nameEndIdx > 0 then
295+
pkgVer.Substring(0, nameEndIdx)
296+
else
297+
pkgVer
298+
299+
System.IO.Path.Combine(cwd, "node_modules", name)
300+
301+
allPkgVers
302+
|> List.filter(fun pkgVer ->
303+
let topLevelPath = getTopLevelNodeModulesPath cwd pkgVer
304+
305+
not(
306+
System.IO.Directory.Exists(topLevelPath)
307+
|| System.IO.File.Exists(topLevelPath)
308+
))
309+
231310
module BuildService =
232311
let Create(args: BuildServiceArgs) : BuildService =
233312
{ new BuildService with

src/Perla/Build.fsi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ type BuildOptions = { enablePreview: bool }
1616

1717
[<RequireQualifiedAccess>]
1818
module Build =
19+
/// Checks for missing local dependencies referenced in the import map (from both imports and scopes).
20+
/// Returns a list of missing package@version strings.
21+
val getMissingLocalDependencies:
22+
config: PerlaConfig -> importMap: Perla.PkgManager.ImportMap -> string list
23+
1924
val EnsureBody: IHtmlDocument -> AngleSharp.Dom.IElement
2025
val EnsureHead: IHtmlDocument -> AngleSharp.Dom.IElement
2126

src/Perla/Handlers.fs

Lines changed: 88 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -600,72 +600,100 @@ module Handlers =
600600

601601
let resolutionA = ImportMaps.resolveForBuildA config importMapA
602602

603+
let missing =
604+
adaptive {
605+
let! config = config
606+
let! map = importMapA
607+
return Build.getMissingLocalDependencies config map
608+
}
609+
|> AVal.force
610+
603611
let {
604612
importMap = map
605613
externals = externals
606614
} =
607615
resolutionA |> AVal.force
608616

609-
let cssPaths, jsBundleEntrypoints, jsStandalonePaths =
610-
Build.EntryPoints document
611-
612-
// Step 9: Run esbuild or move/copy output
613-
let! esbuildOutput =
614-
container.BuildService.RunEsbuild(
615-
config,
616-
tempDir,
617-
cssPaths,
618-
jsBundleEntrypoints,
619-
externals |> Seq.map UMX.untag |> Seq.toList
617+
// Pre-build check for missing local dependencies
618+
if (config |> AVal.force).useLocalPkgs && missing.Length > 0 then
619+
container.Logger.LogError(
620+
"We've found undeclared dependencies: {pkgs}.",
621+
String.concat ", " missing
620622
)
621623

622-
container.BuildService.MoveOrCopyOutput(
623-
config,
624-
tempDir,
625-
esbuildOutput.outputDir
626-
)
624+
container.Logger.LogError
625+
"Your project is currently implicitly using these undeclared dependencies, to proceed with the build add them to your dependencies."
627626

628-
// Step 10: Write index.html
629-
let jsPaths = seq {
630-
yield! jsStandalonePaths
631-
yield! jsBundleEntrypoints
632-
}
627+
container.Logger.LogError(
628+
"{cmd}",
629+
"perla add " + String.concat " " missing
630+
)
633631

634-
do!
635-
container.BuildService.WriteIndex(
632+
return 1
633+
else
634+
let cssPaths, jsBundleEntrypoints, jsStandalonePaths =
635+
Build.EntryPoints document
636+
637+
// Step 9: Run esbuild or move/copy output
638+
let! esbuildOutput =
639+
container.BuildService.RunEsbuild(
640+
config,
641+
tempDir,
642+
cssPaths,
643+
jsBundleEntrypoints,
644+
externals |> Seq.map UMX.untag |> Seq.toList
645+
)
646+
647+
container.BuildService.MoveOrCopyOutput(
636648
config,
637-
document,
638-
map,
639-
jsPaths,
640-
cssPaths,
641-
esbuildOutput.cssFiles
649+
tempDir,
650+
esbuildOutput.outputDir
642651
)
643652

644-
// // cleanup temporary directory
645-
try
646-
Directory.Delete(UMX.untag ".tmp/perla", true) |> ignore
647-
with ex ->
648-
container.Logger.LogWarning(
649-
"Failed to delete temporary directory {dir}: {error}",
650-
UMX.untag ".tmp/perla",
651-
ex.Message
652-
)
653+
// Step 10: Write index.html
654+
let jsPaths = seq {
655+
yield! jsStandalonePaths
656+
yield! jsBundleEntrypoints
657+
}
653658

654-
// Step 11: Start preview server if requested
655-
if options.enablePreview then
656-
container.Logger.LogInformation "Starting a preview server for the build"
659+
do!
660+
container.BuildService.WriteIndex(
661+
config,
662+
document,
663+
map,
664+
jsPaths,
665+
cssPaths,
666+
esbuildOutput.cssFiles
667+
)
657668

658-
SuaveServer.startStaticServer
659-
{
660-
Logger = container.Logger
661-
VirtualFileSystem = container.VirtualFileSystem
662-
Config = config
663-
FsManager = container.FsManager
664-
FileChangedEvents = container.VirtualFileSystem.FileChanges
665-
}
666-
token
669+
// // cleanup temporary directory
670+
try
671+
Directory.Delete(UMX.untag ".tmp/perla", true) |> ignore
672+
with ex ->
673+
container.Logger.LogWarning(
674+
"Failed to delete temporary directory {dir}: {error}",
675+
UMX.untag ".tmp/perla",
676+
ex.Message
677+
)
667678

668-
return 0
679+
// Step 11: Start preview server if requested
680+
if options.enablePreview then
681+
container.Logger.LogInformation
682+
"Starting a preview server for the build"
683+
684+
SuaveServer.startStaticServer
685+
{
686+
Logger = container.Logger
687+
VirtualFileSystem = container.VirtualFileSystem
688+
Config = config
689+
FsManager = container.FsManager
690+
FileChangedEvents = container.VirtualFileSystem.FileChanges
691+
}
692+
token
693+
694+
return 0
695+
else
696+
return 0
669697
}
670698

671699
let runServe (container: AppContainer) (options: ServeOptions) = cancellableTask {
@@ -1031,7 +1059,10 @@ module Handlers =
10311059
"Generating Import Map...",
10321060
pkgManager.Install(
10331061
installSet,
1034-
[ DefaultProvider provider ],
1062+
[
1063+
DefaultProvider provider
1064+
Env(set [ ExportCondition.Browser; ExportCondition.Module ])
1065+
],
10351066
cancellationToken = token
10361067
)
10371068
)
@@ -1064,7 +1095,7 @@ module Handlers =
10641095
"Downloading Sources...",
10651096
pkgManager.GoOffline(
10661097
installResponse.map,
1067-
[ Provider config.provider ],
1098+
[ Provider config.provider; Exclude(set [ Unused ]) ],
10681099
token
10691100
)
10701101
)
@@ -1165,7 +1196,10 @@ module Handlers =
11651196
"Regenerating import map with remaining packages...",
11661197
container.PkgManager.Install(
11671198
remainingPackages,
1168-
[ DefaultProvider provider ],
1199+
[
1200+
DefaultProvider provider
1201+
Env(set [ ExportCondition.Browser; ExportCondition.Module ])
1202+
],
11691203
token
11701204
)
11711205
)
@@ -1184,7 +1218,7 @@ module Handlers =
11841218
"Consolidating local packages...",
11851219
container.PkgManager.GoOffline(
11861220
newMapResponse.map,
1187-
[ Provider config.provider ],
1221+
[ Provider config.provider; Exclude(set [ Unused ]) ],
11881222
token
11891223
)
11901224
)

0 commit comments

Comments
 (0)