@@ -3,14 +3,21 @@ module Spago.Command.Bundle where
33import Spago.Prelude
44
55import Data.Array (all , fold , take )
6+ import Data.Array.NonEmpty as NEA
7+ import Data.Map as Map
68import Data.String as Str
79import Data.String.Utils (startsWith )
810import Spago.Cmd as Cmd
11+ import Spago.Command.Build as Build
12+ import Spago.Command.Fetch as Fetch
913import Spago.Config (BundlePlatform (..), BundleType (..), Workspace , WorkspacePackage )
1014import Spago.Esbuild (Esbuild )
1115import Spago.FS as FS
1216import Spago.Generated.BuildInfo as BuildInfo
1317import Spago.Path as Path
18+ import Spago.Purs (Purs , ModuleGraph (..))
19+ import Spago.Purs as Purs
20+ import Spago.Purs.EntryPoint as EntryPoint
1421
1522type BundleEnv a =
1623 { esbuild :: Esbuild
@@ -19,6 +26,8 @@ type BundleEnv a =
1926 , bundleOptions :: BundleOptions
2027 , workspace :: Workspace
2128 , selected :: WorkspacePackage
29+ , purs :: Purs
30+ , dependencies :: Fetch.PackageTransitiveDeps
2231 | a
2332 }
2433
@@ -86,6 +95,10 @@ run = do
8695 , entrypoint
8796 ]
8897
98+ -- Check that the entry module exports a `main` function when bundling an app
99+ when (opts.type == BundleApp ) do
100+ validateMainExport opts.module
101+
89102 -- FIXME: remove this after 2024-12-01
90103 whenM (FS .exists $ rootPath </> checkWatermarkMarkerFileName)
91104 $ unless opts.force
@@ -146,3 +159,44 @@ nodeTargetPolyfill = Str.joinWith ";"
146159 , " const __dirname = __path.dirname(__url.fileURLToPath(import.meta.url))"
147160 , " const __filename=new URL(import.meta.url).pathname"
148161 ]
162+
163+ -- | Validate that the entry module declares and exports a `main` function
164+ validateMainExport :: forall a . String -> Spago (BundleEnv a ) Unit
165+ validateMainExport moduleName = do
166+ { rootPath, selected, dependencies } <- ask
167+
168+ let
169+ globs = Build .getBuildGlobs
170+ { rootPath
171+ , dependencies: Fetch .toAllDependencies dependencies
172+ , depsOnly: false
173+ , withTests: false
174+ , selected: NEA .singleton selected
175+ }
176+
177+ Purs .graph rootPath globs [] >>= case _ of
178+ Left err -> logWarn $ " Could not verify main export: " <> show err
179+ Right (ModuleGraph graph) ->
180+ case Map .lookup moduleName graph of
181+ Nothing ->
182+ die
183+ [ " Cannot bundle app: module " <> moduleName <> " was not found in the build."
184+ , " "
185+ , " Make sure the module exists and is included in your build."
186+ ]
187+ Just { path } -> do
188+ sourceCode <- FS .readTextFile (rootPath </> path)
189+ case EntryPoint .hasMainExport sourceCode of
190+ EntryPoint.MainExported -> pure unit
191+ EntryPoint.MainNotDeclared ->
192+ die
193+ [ " Cannot bundle app: module " <> moduleName <> " does not declare a `main` function."
194+ , " If you want to create a bundle without an entry point, use --bundle-type=module instead."
195+ ]
196+ EntryPoint.MainNotExported ->
197+ die
198+ [ " Cannot bundle app: module " <> moduleName <> " does not export `main`."
199+ , " Add `main` to the module's export list, remove the explicit export list, or use --bundle-type=module."
200+ ]
201+ EntryPoint.ParseError err ->
202+ logWarn $ " Could not verify main export: " <> err
0 commit comments