Skip to content

Commit 188e61d

Browse files
authored
New 'spago init --subpackage foo' option to initialize a sub-project within current workspace (#1279)
1 parent 1f9ecda commit 188e61d

13 files changed

+242
-63
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Other improvements:
3434
Spago on different platforms.
3535
- when encountering a mistyped option for a command, Spago will show help for
3636
that command, not root help.
37+
- a new `spago init --subpackage foo` option to initialize a sub-project in the
38+
current workspace.
3739

3840
## [0.21.0] - 2023-05-04
3941

bin/src/Flags.purs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Data.List as List
77
import Options.Applicative (FlagFields, Mod, Parser)
88
import Options.Applicative as O
99
import Options.Applicative.Types as OT
10+
import Spago.Command.Init as Init
1011
import Spago.Core.Config as Core
1112

1213
flagMaybe (a Type). a -> Mod FlagFields (Maybe a) -> Parser (Maybe a)
@@ -178,8 +179,8 @@ transitive =
178179
<> O.help "Include transitive dependencies"
179180
)
180181

181-
pure :: Parser Boolean
182-
pure =
182+
pureLockfile :: Parser Boolean
183+
pureLockfile =
183184
O.switch
184185
( O.long "pure"
185186
<> O.help "Use the package information from the current lockfile, even if it is out of date"
@@ -301,6 +302,19 @@ maybePackageName =
301302
<> O.help "Optional package name to be used for the new project"
302303
)
303304

305+
subpackageName :: Parser String
306+
subpackageName =
307+
O.strOption
308+
( O.long "subpackage"
309+
<> O.help "Name of a subpackage to initialize within the current workspace"
310+
)
311+
312+
initMode :: Parser Init.InitMode
313+
initMode =
314+
(Init.InitSubpackage <$> { packageName: _ } <$> subpackageName)
315+
<|> (Init.InitWorkspace <$> { packageName: _ } <$> maybePackageName)
316+
<|> pure (Init.InitWorkspace { packageName: Nothing })
317+
304318
ensureRanges :: Parser Boolean
305319
ensureRanges =
306320
O.switch

bin/src/Main.purs

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@ import Data.Foldable as Foldable
1212
import Data.List as List
1313
import Data.Maybe as Maybe
1414
import Data.Set as Set
15-
import Data.String as String
1615
import Effect.Aff as Aff
1716
import Effect.Aff.AVar as AVar
1817
import Effect.Now as Now
19-
import Node.Path as Path
2018
import Node.Process as Process
2119
import Options.Applicative (CommandFields, Mod, Parser, ParserPrefs(..))
2220
import Options.Applicative as O
@@ -53,12 +51,10 @@ import Spago.Generated.BuildInfo as BuildInfo
5351
import Spago.Git as Git
5452
import Spago.Json as Json
5553
import Spago.Log (LogVerbosity(..))
56-
import Spago.Log as Log
5754
import Spago.Paths as Paths
5855
import Spago.Purs as Purs
5956
import Spago.Registry as Registry
6057
import Spago.Repl as SpagoRepl
61-
import Unsafe.Coerce as UnsafeCoerce
6258

6359
type GlobalArgs =
6460
{ noColor :: Boolean
@@ -70,7 +66,7 @@ type GlobalArgs =
7066

7167
type InitArgs =
7268
{ setVersion :: Maybe String
73-
, name :: Maybe String
69+
, mode :: Init.InitMode
7470
, useSolver :: Boolean
7571
}
7672

@@ -296,7 +292,7 @@ initArgsParser :: Parser InitArgs
296292
initArgsParser =
297293
Optparse.fromRecord
298294
{ setVersion: Flags.maybeSetVersion
299-
, name: Flags.maybePackageName
295+
, mode: Flags.initMode
300296
, useSolver: Flags.useSolver
301297
}
302298

@@ -307,7 +303,7 @@ fetchArgsParser =
307303
, selectedPackage: Flags.selectedPackage
308304
, ensureRanges: Flags.ensureRanges
309305
, testDeps: Flags.testDeps
310-
, pure: Flags.pure
306+
, pure: Flags.pureLockfile
311307
}
312308

313309
sourcesArgsParser :: Parser SourcesArgs
@@ -328,7 +324,7 @@ installArgsParser =
328324
, pedanticPackages: Flags.pedanticPackages
329325
, ensureRanges: Flags.ensureRanges
330326
, testDeps: Flags.testDeps
331-
, pure: Flags.pure
327+
, pure: Flags.pureLockfile
332328
}
333329

334330
uninstallArgsParser :: Parser UninstallArgs
@@ -350,7 +346,7 @@ buildArgsParser = Optparse.fromRecord
350346
, jsonErrors: Flags.jsonErrors
351347
, strict: Flags.strict
352348
, statVerbosity: Flags.statVerbosity
353-
, pure: Flags.pure
349+
, pure: Flags.pureLockfile
354350
}
355351

356352
replArgsParser :: Parser ReplArgs
@@ -372,7 +368,7 @@ runArgsParser = Optparse.fromRecord
372368
, ensureRanges: Flags.ensureRanges
373369
, strict: Flags.strict
374370
, statVerbosity: Flags.statVerbosity
375-
, pure: Flags.pure
371+
, pure: Flags.pureLockfile
376372
}
377373

378374
upgradeArgsParser :: Parser UpgradeArgs
@@ -391,7 +387,7 @@ testArgsParser = Optparse.fromRecord
391387
, pedanticPackages: Flags.pedanticPackages
392388
, strict: Flags.strict
393389
, statVerbosity: Flags.statVerbosity
394-
, pure: Flags.pure
390+
, pure: Flags.pureLockfile
395391
}
396392

397393
bundleArgsParser :: Parser BundleArgs
@@ -413,7 +409,7 @@ bundleArgsParser =
413409
, ensureRanges: Flags.ensureRanges
414410
, strict: Flags.strict
415411
, statVerbosity: Flags.statVerbosity
416-
, pure: Flags.pure
412+
, pure: Flags.pureLockfile
417413
}
418414

419415
publishArgsParser :: Parser PublishArgs
@@ -489,15 +485,15 @@ lsPathsArgsParser = Optparse.fromRecord
489485
lsPackagesArgsParser :: Parser LsPackagesArgs
490486
lsPackagesArgsParser = Optparse.fromRecord
491487
{ json: Flags.json
492-
, pure: Flags.pure
488+
, pure: Flags.pureLockfile
493489
}
494490

495491
lsDepsArgsParser :: Parser LsDepsArgs
496492
lsDepsArgsParser = Optparse.fromRecord
497493
{ json: Flags.json
498494
, transitive: Flags.transitive
499495
, selectedPackage: Flags.selectedPackage
500-
, pure: Flags.pure
496+
, pure: Flags.pureLockfile
501497
}
502498

503499
data Cmd a = Cmd'SpagoCmd (SpagoCmd a) | Cmd'VersionCmd Boolean
@@ -547,24 +543,10 @@ main = do
547543
}
548544
void $ runSpago env (Sources.run { json: args.json })
549545
Init args@{ useSolver } -> do
550-
-- Figure out the package name from the current dir
551-
let candidateName = fromMaybe (String.take 150 $ Path.basename Paths.cwd) args.name
552-
logDebug [ show Paths.cwd, show candidateName ]
553-
packageName <- case PackageName.parse (PackageName.stripPureScriptPrefix candidateName) of
554-
Left err -> die
555-
[ toDoc "Could not figure out a name for the new package. Error:"
556-
, Log.break
557-
, Log.indent2 $ toDoc err
558-
]
559-
Right p -> pure p
560-
setVersion <- parseSetVersion args.setVersion
561-
logDebug [ "Got packageName and setVersion:", PackageName.print packageName, unsafeStringify setVersion ]
562-
let initOpts = { packageName, setVersion, useSolver }
563546
-- Fetch the registry here so we can select the right package set later
564547
env <- mkRegistryEnv offline
565-
void $ runSpago env $ Init.run initOpts
566-
logInfo "Set up a new Spago project."
567-
logInfo "Try running `spago run`"
548+
setVersion <- parseSetVersion args.setVersion
549+
void $ runSpago env $ Init.run { mode: args.mode, setVersion, useSolver }
568550
Fetch args -> do
569551
{ env, fetchOpts } <- mkFetchEnv (Record.merge { isRepl: false, migrateConfig, offline } args)
570552
void $ runSpago env (Fetch.run fetchOpts)
@@ -620,7 +602,7 @@ main = do
620602
env <- mkRegistryEnv offline
621603
void $ runSpago env $ Init.run
622604
{ setVersion: Nothing
623-
, packageName: UnsafeCoerce.unsafeCoerce "repl"
605+
, mode: Init.InitWorkspace { packageName: Just "repl" }
624606
, useSolver: true
625607
}
626608
pure $ List.fromFoldable [ "effect", "console" ] -- TODO newPackages

src/Spago/Command/Init.purs

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Spago.Command.Init
22
( DefaultConfigOptions(..)
33
, DefaultConfigPackageOptions
44
, DefaultConfigWorkspaceOptions
5+
, InitMode(..)
56
, InitOptions
67
, defaultConfig
78
, defaultConfig'
@@ -14,65 +15,79 @@ module Spago.Command.Init
1415
import Spago.Prelude
1516

1617
import Data.Map as Map
18+
import Data.String as String
1719
import Node.Path as Path
1820
import Registry.PackageName as PackageName
1921
import Registry.Version as Version
2022
import Spago.Config (Dependencies(..), SetAddress(..), Config)
2123
import Spago.Config as Config
2224
import Spago.FS as FS
25+
import Spago.Log as Log
26+
import Spago.Paths as Paths
2327
import Spago.Registry (RegistryEnv)
2428
import Spago.Registry as Registry
2529

30+
data InitMode
31+
= InitWorkspace { packageName :: Maybe String }
32+
| InitSubpackage { packageName :: String }
33+
2634
type InitOptions =
2735
-- TODO: we should allow the `--package-set` flag to alternatively pass in a URL
2836
{ setVersion :: Maybe Version
29-
, packageName :: PackageName
37+
, mode :: InitMode
3038
, useSolver :: Boolean
3139
}
3240

3341
-- TODO run git init? Is that desirable?
3442

3543
run :: a. InitOptions -> Spago (RegistryEnv a) Config
3644
run opts = do
37-
logInfo "Initializing a new project..."
38-
3945
-- Use the specified version of the package set (if specified).
4046
-- Otherwise, get the latest version of the package set for the given compiler
4147
packageSetVersion <- Registry.findPackageSet opts.setVersion
4248

49+
packageName <- getPackageName
50+
withWorkspace <- getWithWorkspace packageSetVersion
51+
projectDir <- getProjectDir packageName
52+
4353
{ purs } <- ask
54+
logInfo "Initializing a new project..."
4455
logInfo $ "Found PureScript " <> Version.print purs.version <> ", will use package set " <> Version.print packageSetVersion
4556

46-
-- Write config
4757
let
48-
config = defaultConfig
49-
{ name: opts.packageName
50-
, withWorkspace: Just
51-
{ setVersion: case opts.useSolver of
52-
true -> Nothing
53-
false -> Just packageSetVersion
54-
}
55-
, testModuleName: "Test.Main"
56-
}
57-
let configPath = "spago.yaml"
58+
mainModuleName = "Main"
59+
testModuleName = "Test.Main"
60+
srcDir = Path.concat [ projectDir, "src" ]
61+
testDir = Path.concat [ projectDir, "test" ]
62+
configPath = Path.concat [ projectDir, "spago.yaml" ]
63+
config = defaultConfig { name: packageName, withWorkspace, testModuleName }
64+
65+
-- Write config
5866
(FS.exists configPath) >>= case _ of
5967
true -> logInfo $ foundExistingProject configPath
6068
false -> liftAff $ FS.writeYamlFile Config.configCodec configPath config
6169

6270
-- If these directories (or files) exist, we skip copying "sample sources"
6371
-- Because you might want to just init a project with your own source files,
6472
-- or just migrate a psc-package project
65-
let mainModuleName = "Main"
66-
whenDirNotExists "src" do
67-
copyIfNotExists ("src" <> Path.sep <> mainModuleName <> ".purs") (srcMainTemplate mainModuleName)
73+
whenDirNotExists srcDir do
74+
copyIfNotExists (Path.concat [ srcDir, mainModuleName <> ".purs" ]) (srcMainTemplate mainModuleName)
6875

69-
whenDirNotExists "test" $ do
70-
FS.mkdirp (Path.concat [ "test", "Test" ])
71-
copyIfNotExists (Path.concat [ "test", "Test", "Main.purs" ]) (testMainTemplate "Test.Main")
76+
whenDirNotExists testDir $ do
77+
FS.mkdirp (Path.concat [ testDir, "Test" ])
78+
copyIfNotExists (Path.concat [ testDir, "Test", "Main.purs" ]) (testMainTemplate testModuleName)
7279

73-
copyIfNotExists ".gitignore" gitignoreTemplate
80+
case opts.mode of
81+
InitWorkspace _ -> do
82+
copyIfNotExists ".gitignore" gitignoreTemplate
83+
copyIfNotExists pursReplFile.name pursReplFile.content
84+
InitSubpackage _ ->
85+
pure unit
7486

75-
copyIfNotExists pursReplFile.name pursReplFile.content
87+
logInfo "Set up a new Spago project."
88+
case opts.mode of
89+
InitWorkspace _ -> logInfo "Try running `spago run`"
90+
InitSubpackage _ -> logInfo $ "Try running `spago run -p " <> PackageName.print packageName <> "`"
7691

7792
pure config
7893

@@ -87,6 +102,46 @@ run opts = do
87102
true -> logInfo $ foundExistingFile dest
88103
false -> FS.writeTextFile dest srcTemplate
89104

105+
getPackageName :: Spago (RegistryEnv a) PackageName
106+
getPackageName = do
107+
let
108+
candidateName = case opts.mode of
109+
InitWorkspace { packageName: Nothing } -> String.take 150 $ Path.basename Paths.cwd
110+
InitWorkspace { packageName: Just n } -> n
111+
InitSubpackage { packageName: n } -> n
112+
logDebug [ show Paths.cwd, show candidateName ]
113+
pname <- case PackageName.parse (PackageName.stripPureScriptPrefix candidateName) of
114+
Left err -> die
115+
[ toDoc "Could not figure out a name for the new package. Error:"
116+
, Log.break
117+
, Log.indent2 $ toDoc err
118+
]
119+
Right p -> pure p
120+
logDebug [ "Got packageName and setVersion:", PackageName.print pname, unsafeStringify opts.setVersion ]
121+
pure pname
122+
123+
getWithWorkspace :: Version -> Spago (RegistryEnv a) (Maybe { setVersion :: Maybe Version })
124+
getWithWorkspace setVersion = case opts.mode of
125+
InitWorkspace _ ->
126+
pure $ Just
127+
{ setVersion: case opts.useSolver of
128+
true -> Nothing
129+
false -> Just setVersion
130+
}
131+
InitSubpackage _ -> do
132+
when (isJust opts.setVersion || opts.useSolver) do
133+
logWarn "The --package-set and --use-solver flags are ignored when initializing a subpackage"
134+
pure Nothing
135+
136+
getProjectDir :: PackageName -> Spago (RegistryEnv a) FilePath
137+
getProjectDir packageName = case opts.mode of
138+
InitWorkspace _ ->
139+
pure "."
140+
InitSubpackage _ -> do
141+
let dirPath = PackageName.print packageName
142+
unlessM (FS.exists dirPath) $ FS.mkdirp dirPath
143+
pure dirPath
144+
90145
-- TEMPLATES -------------------------------------------------------------------
91146

92147
type TemplateConfig =
@@ -234,10 +289,10 @@ pursReplFile = { name: ".purs-repl", content: "import Prelude\n" }
234289
-- ERROR TEXTS -----------------------------------------------------------------
235290

236291
foundExistingProject :: FilePath -> String
237-
foundExistingProject path = "Found a " <> show path <> " file, skipping copy."
292+
foundExistingProject path = "Found a \"" <> path <> "\" file, skipping copy."
238293

239294
foundExistingDirectory :: FilePath -> String
240-
foundExistingDirectory dir = "Found existing directory " <> show dir <> ", skipping copy of sample sources"
295+
foundExistingDirectory dir = "Found existing directory \"" <> dir <> "\", skipping copy of sample sources"
241296

242297
foundExistingFile :: FilePath -> String
243-
foundExistingFile file = "Found existing file " <> show file <> ", not overwriting it"
298+
foundExistingFile file = "Found existing file \"" <> file <> "\", not overwriting it"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Invalid option `--subpackage'
2+
3+
Usage: index.dev.js init [--migrate] [--monochrome|--no-color] [--offline] [-q|--quiet] [-v|--verbose] ([--subpackage ARG] | [--name ARG]) [--package-set ARG] [--use-solver]
4+
Initialise a new project
5+
6+
Available options:
7+
--migrate Migrate the spago.yaml file to the latest format
8+
--monochrome,--no-color Force logging without ANSI color escape sequences
9+
--offline Do not attempt to use the network. Warning: this will
10+
fail if you don't have the necessary dependencies
11+
already cached
12+
-q,--quiet Suppress all spago logging
13+
-v,--verbose Enable additional debug logging, e.g. printing `purs`
14+
commands
15+
--subpackage ARG Name of a subpackage to initialize within the current
16+
workspace
17+
--name ARG Optional package name to be used for the new project
18+
--package-set ARG Optional package set version to be used instead of
19+
the latest one
20+
--use-solver Use the solver instead of package sets
21+
-h,--help Show this help text
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Initializing a new project...
2+
Found PureScript a.b.c, will use package set x.y.z
3+
Found existing directory "subdir/src", skipping copy of sample sources
4+
Set up a new Spago project.
5+
Try running `spago run -p subdir`

0 commit comments

Comments
 (0)