Skip to content

Commit 4346ab8

Browse files
committed
Implement auth and transfer commands
1 parent 1dba7dc commit 4346ab8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+956
-401
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ Where to go from here? There are a few places you should check out:
130130
- [Querying package sets](#querying-package-sets)
131131
- [Upgrading packages and the package set](#upgrading-packages-and-the-package-set)
132132
- [Custom package sets](#custom-package-sets)
133+
- [Graph the project modules and dependencies](#graph-the-project-modules-and-dependencies)
133134
- [Monorepo support](#monorepo-support)
134135
- [Polyrepo support](#polyrepo-support)
135136
- [Test dependencies](#test-dependencies)
@@ -142,6 +143,9 @@ Where to go from here? There are a few places you should check out:
142143
- [Generate documentation for my project](#generate-documentation-for-my-project)
143144
- [Alternate backends](#alternate-backends)
144145
- [Publish my library](#publish-my-library)
146+
- [Publish many packages together](#publish-many-packages-together)
147+
- [Authenticated commands](#authenticated-commands)
148+
- [Transfer my package to a new owner](#transfer-my-package-to-a-new-owner)
145149
- [Know which `purs` commands are run under the hood](#know-which-purs-commands-are-run-under-the-hood)
146150
- [Install autocompletions for `bash`](#install-autocompletions-for-bash)
147151
- [Install autocompletions for `zsh`](#install-autocompletions-for-zsh)
@@ -151,10 +155,12 @@ Where to go from here? There are a few places you should check out:
151155
- [The workspace](#the-workspace)
152156
- [The configuration file](#the-configuration-file)
153157
- [The lock file](#the-lock-file)
158+
- [File System Paths used in Spago](#file-system-paths-used-in-spago)
154159
- [FAQ](#faq)
155160
- [Why can't `spago` also install my npm dependencies?](#why-cant-spago-also-install-my-npm-dependencies)
156-
- [Differences from legacy spago](#differences-from-legacy-spago)
157-
- [Watch mode](#watch-mode)
161+
- [Differences from legacy spago](#differences-from-legacy-spago)
162+
- [Watch mode](#watch-mode)
163+
- [`sources` in the configuration file](#sources-in-the-configuration-file)
158164

159165
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
160166

@@ -1092,6 +1098,29 @@ workspace:
10921098
> [!NOTE]\
10931099
> This only works when the package you add to `extraPackages` has been published to the registry. Adding a git dependency will produce an error, as publishing to the Registry only admits build plans that only contain packages coming from the Registry.
10941100

1101+
### Authenticated commands
1102+
1103+
The Registry does not need authentication when publishing new versions of a package, but it does need it when issuing
1104+
operations that modify existing packages, [such as location transfer or unpublishing](registry-dev-auth).
1105+
1106+
This authentication happens through SSH keys: by having your public key in a published version, the Registry can then
1107+
authenticate requests that are signed with your private key.
1108+
1109+
Authentication and operations that use it are automated by Spago, through the `spago auth` command: if you'd like to
1110+
be able to perform authenticated operations you need a SSH keypair, and run `spago auth` passing those keys in.
1111+
This will populate the `package.publish.owners` field in the `spago.yaml` - commit that and publish a new version,
1112+
and from that moment you'll be able to perform authenticated operations on this package.
1113+
1114+
#### Transfer my package to a new owner
1115+
1116+
If you are the owner of a package and you want to transfer it to another user, you'd need to inform the Registry
1117+
about the new location of the repository, so that the new owner will be able to publish new versions of the package.
1118+
1119+
The transfer procedure is automated by Spago commands, and goes as follows:
1120+
* Add your (or the new owner's) SSH public key to the `spago.yaml` through `spago auth` if they are not there already (see previous section)
1121+
* Transfer the repository to the new owner using the hosting platform's transfer mechanism (e.g. GitHub's transfer feature)
1122+
* Depending whose key is present in the `owners` field, either you or the new owner will update the `publish.location` field in the `spago.yaml`, and call `spago registry transfer` to initiate the transfer. If all goes well you'll now be able to publish a new version from the new location.
1123+
10951124
### Know which `purs` commands are run under the hood
10961125

10971126
The `-v` flag will print out all the `purs` commands that `spago` invokes during its operations,
@@ -1621,3 +1650,4 @@ and similarly for the `test` folder, using that for the test sources.
16211650
[watchexec]: https://github.com/watchexec/watchexec#quick-start
16221651
[purescript-langugage-server]: https://github.com/nwolverson/purescript-language-server
16231652
[ide-purescript]: https://marketplace.visualstudio.com/items?itemName=nwolverson.ide-purescript
1653+
[registry-dev-auth]: https://github.com/purescript/registry-dev/blob/master/SPEC.md#52-authentication

bin/src/Flags.purs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,19 @@ depsOnly =
335335
( O.long "deps-only"
336336
<> O.help "Build depedencies only"
337337
)
338+
339+
publicKeyPath :: Parser FilePath
340+
publicKeyPath =
341+
O.strOption
342+
( O.short 'i'
343+
<> O.metavar "PUBLIC_KEY_PATH"
344+
<> O.help "Select the path of the public key to use for authenticating operations of the package"
345+
)
346+
347+
privateKeyPath :: Parser FilePath
348+
privateKeyPath =
349+
O.strOption
350+
( O.short 'i'
351+
<> O.metavar "PRIVATE_KEY_PATH"
352+
<> O.help "The path of the private key to use for signing the operation"
353+
)

bin/src/Main.purs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Optparse as Optparse
2323
import Record as Record
2424
import Registry.PackageName as PackageName
2525
import Spago.Bin.Flags as Flags
26+
import Spago.Command.Auth as Auth
2627
import Spago.Command.Build as Build
2728
import Spago.Command.Bundle as Bundle
2829
import Spago.Command.Docs as Docs
@@ -33,7 +34,7 @@ import Spago.Command.Init as Init
3334
import Spago.Command.Ls (LsPathsArgs, LsDepsArgs, LsPackagesArgs)
3435
import Spago.Command.Ls as Ls
3536
import Spago.Command.Publish as Publish
36-
import Spago.Command.Registry (RegistryInfoArgs, RegistrySearchArgs, RegistryPackageSetsArgs)
37+
import Spago.Command.Registry (RegistryInfoArgs, RegistryPackageSetsArgs, RegistrySearchArgs, RegistryTransferArgs)
3738
import Spago.Command.Registry as RegistryCmd
3839
import Spago.Command.Repl as Repl
3940
import Spago.Command.Run as Run
@@ -203,13 +204,15 @@ data Command a
203204
| RegistryInfo RegistryInfoArgs
204205
| RegistryPackageSets RegistryPackageSetsArgs
205206
| RegistrySearch RegistrySearchArgs
207+
| RegistryTransfer RegistryTransferArgs
206208
| Repl ReplArgs
207209
| Run RunArgs
208210
| Sources SourcesArgs
209211
| Test TestArgs
210212
| Upgrade UpgradeArgs
211213
| GraphModules GraphModulesArgs
212214
| GraphPackages GraphPackagesArgs
215+
| Auth Auth.AuthArgs
213216

214217
commandParser :: forall (a :: Row Type). String -> Parser (Command a) -> String -> Mod CommandFields (SpagoCmd a)
215218
commandParser command_ parser_ description_ =
@@ -222,29 +225,22 @@ commandParser command_ parser_ description_ =
222225
argParser :: Parser (SpagoCmd ())
223226
argParser =
224227
O.hsubparser $ Foldable.fold
225-
[ commandParser "init" (Init <$> initArgsParser) "Initialise a new project"
226-
, commandParser "fetch" (Fetch <$> fetchArgsParser) "Downloads all of the project's dependencies"
227-
, commandParser "install" (Install <$> installArgsParser) "Compile the project's dependencies"
228-
, commandParser "uninstall" (Uninstall <$> uninstallArgsParser) "Remove dependencies from a package"
228+
[ commandParser "auth" (Auth <$> authArgsParser) "Authenticate as the owner of a package, to allow transfer and unpiblish operations"
229229
, commandParser "build" (Build <$> buildArgsParser) "Compile the project"
230-
, commandParser "run" (Run <$> runArgsParser) "Run the project"
231-
, commandParser "test" (Test <$> testArgsParser) "Test the project"
232230
, commandParser "bundle" (Bundle <$> bundleArgsParser) "Bundle the project in a single file"
233-
, commandParser "sources" (Sources <$> sourcesArgsParser) "List all the source paths (globs) for the dependencies of the project"
234-
, commandParser "repl" (Repl <$> replArgsParser) "Start a REPL"
235-
, commandParser "publish" (Publish <$> publishArgsParser) "Publish a package"
236-
, commandParser "upgrade" (Upgrade <$> upgradeArgsParser) "Upgrade to the latest package set, or to the latest versions of Registry packages"
237231
, commandParser "docs" (Docs <$> docsArgsParser) "Generate docs for the project and its dependencies"
238-
, O.command "registry"
232+
, commandParser "fetch" (Fetch <$> fetchArgsParser) "Downloads all of the project's dependencies"
233+
, O.command "graph"
239234
( O.info
240235
( O.hsubparser $ Foldable.fold
241-
[ commandParser "search" (RegistrySearch <$> registrySearchArgsParser) "Search for package names in the Registry"
242-
, commandParser "info" (RegistryInfo <$> registryInfoArgsParser) "Query the Registry for information about packages and versions"
243-
, commandParser "package-sets" (RegistryPackageSets <$> registryPackageSetsArgsParser) "List the available package sets"
236+
[ commandParser "modules" (GraphModules <$> graphModulesArgsParser) "Generate a graph of the project's modules"
237+
, commandParser "packages" (GraphPackages <$> graphPackagesArgsParser) "Generate a graph of the project's dependencies"
244238
]
245239
)
246-
(O.progDesc "Commands to interact with the Registry")
240+
(O.progDesc "Generate a graph of modules or dependencies")
247241
)
242+
, commandParser "init" (Init <$> initArgsParser) "Initialise a new project"
243+
, commandParser "install" (Install <$> installArgsParser) "Compile the project's dependencies"
248244
, O.command "ls"
249245
( O.info
250246
( O.hsubparser $ Foldable.fold
@@ -255,26 +251,26 @@ argParser =
255251
)
256252
(O.progDesc "List packages or dependencies")
257253
)
258-
, O.command "graph"
254+
, commandParser "publish" (Publish <$> publishArgsParser) "Publish a package"
255+
, O.command "registry"
259256
( O.info
260257
( O.hsubparser $ Foldable.fold
261-
[ commandParser "modules" (GraphModules <$> graphModulesArgsParser) "Generate a graph of the project's modules"
262-
, commandParser "packages" (GraphPackages <$> graphPackagesArgsParser) "Generate a graph of the project's dependencies"
258+
[ commandParser "search" (RegistrySearch <$> registrySearchArgsParser) "Search for package names in the Registry"
259+
, commandParser "info" (RegistryInfo <$> registryInfoArgsParser) "Query the Registry for information about packages and versions"
260+
, commandParser "package-sets" (RegistryPackageSets <$> registryPackageSetsArgsParser) "List the available package sets"
261+
, commandParser "transfer" (RegistryTransfer <$> registryTransferArgsParser) "Transfer a package you own to a different remote location"
263262
]
264263
)
265-
(O.progDesc "Generate a graph of modules or dependencies")
264+
(O.progDesc "Commands to interact with the Registry")
266265
)
266+
, commandParser "repl" (Repl <$> replArgsParser) "Start a REPL"
267+
, commandParser "run" (Run <$> runArgsParser) "Run the project"
268+
, commandParser "sources" (Sources <$> sourcesArgsParser) "List all the source paths (globs) for the dependencies of the project"
269+
, commandParser "test" (Test <$> testArgsParser) "Test the project"
270+
, commandParser "uninstall" (Uninstall <$> uninstallArgsParser) "Remove dependencies from a package"
271+
, commandParser "upgrade" (Upgrade <$> upgradeArgsParser) "Upgrade to the latest package set, or to the latest versions of Registry packages"
267272
]
268273

269-
{-
270-
271-
TODO: add flag for overriding the cache location
272-
273-
buildOptions = BuildOptions <$> watch <*> clearScreen <*> allowIgnored <*> sourcePaths <*> srcMapFlag <*> noInstall
274-
<*> pursArgs <*> depsOnly <*> beforeCommands <*> thenCommands <*> elseCommands
275-
276-
-}
277-
278274
-- https://stackoverflow.com/questions/45395369/how-to-get-console-log-line-numbers-shown-in-nodejs
279275
-- TODO: veryVerbose = CLI.switch "very-verbose" 'V' "Enable more verbosity: timestamps and source locations"
280276

@@ -463,6 +459,12 @@ registryPackageSetsArgsParser =
463459
, latest: Flags.latest
464460
}
465461

462+
registryTransferArgsParser :: Parser RegistryTransferArgs
463+
registryTransferArgsParser =
464+
Optparse.fromRecord
465+
{ privateKeyPath: Flags.privateKeyPath
466+
}
467+
466468
graphModulesArgsParser :: Parser GraphModulesArgs
467469
graphModulesArgsParser = Optparse.fromRecord
468470
{ dot: Flags.dot
@@ -496,6 +498,11 @@ lsDepsArgsParser = Optparse.fromRecord
496498
, pure: Flags.pureLockfile
497499
}
498500

501+
authArgsParser :: Parser Auth.AuthArgs
502+
authArgsParser = Optparse.fromRecord
503+
{ keyPath: Flags.publicKeyPath
504+
}
505+
499506
data Cmd a = Cmd'SpagoCmd (SpagoCmd a) | Cmd'VersionCmd Boolean
500507

501508
parseArgs :: Effect (Cmd ())
@@ -545,8 +552,10 @@ main = do
545552
Init args@{ useSolver } -> do
546553
-- Fetch the registry here so we can select the right package set later
547554
env <- mkRegistryEnv offline
555+
548556
setVersion <- parseSetVersion args.setVersion
549557
void $ runSpago env $ Init.run { mode: args.mode, setVersion, useSolver }
558+
550559
Fetch args -> do
551560
{ env, fetchOpts } <- mkFetchEnv (Record.merge { isRepl: false, migrateConfig, offline } args)
552561
void $ runSpago env (Fetch.run fetchOpts)
@@ -559,8 +568,11 @@ main = do
559568
RegistryPackageSets args -> do
560569
env <- mkRegistryEnv offline
561570
void $ runSpago env (RegistryCmd.packageSets args)
571+
RegistryTransfer args -> do
572+
{ env } <- mkFetchEnv { packages: mempty, selectedPackage: Nothing, pure: false, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig: false, offline }
573+
void $ runSpago env (RegistryCmd.transfer args)
562574
Install args -> do
563-
{ env, fetchOpts } <- mkFetchEnv (Record.merge args { isRepl: false, migrateConfig, offline })
575+
{ env, fetchOpts } <- mkFetchEnv (Record.merge { isRepl: false, migrateConfig, offline } args)
564576
dependencies <- runSpago env (Fetch.run fetchOpts)
565577
let
566578
buildArgs = Record.merge
@@ -670,7 +682,10 @@ main = do
670682
Upgrade args -> do
671683
setVersion <- parseSetVersion args.setVersion
672684
{ env } <- mkFetchEnv { packages: mempty, selectedPackage: Nothing, pure: false, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline }
673-
runSpago env $ Upgrade.run { setVersion }
685+
runSpago env (Upgrade.run { setVersion })
686+
Auth args -> do
687+
{ env } <- mkFetchEnv { packages: mempty, selectedPackage: Nothing, pure: false, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline }
688+
runSpago env $ Auth.run args
674689
-- TODO: add selected to graph commands
675690
GraphModules args -> do
676691
{ env, fetchOpts } <- mkFetchEnv { packages: mempty, selectedPackage: Nothing, pure: false, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline }

core/spago.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package:
1717
- exceptions
1818
- filterable
1919
- foldable-traversable
20+
- foreign
2021
- functions
2122
- identity
2223
- integers
@@ -39,3 +40,4 @@ package:
3940
- stringutils
4041
- transformers
4142
- tuples
43+
- unsafe-coerce

core/src/Config.purs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import Registry.Internal.Codec as Reg.Internal.Codec
5555
import Registry.Internal.Parsing as Reg.Internal.Parsing
5656
import Registry.License as License
5757
import Registry.Location as Location
58+
import Registry.Owner (Owner)
59+
import Registry.Owner as Owner
5860
import Registry.PackageName as PackageName
5961
import Registry.Range as Range
6062
import Registry.Sha256 as Sha256
@@ -100,6 +102,7 @@ type PublishConfig =
100102
, location :: Maybe Location
101103
, include :: Maybe (Array FilePath)
102104
, exclude :: Maybe (Array FilePath)
105+
, owners :: Maybe (Array Owner)
103106
}
104107

105108
publishConfigCodec :: CJ.Codec PublishConfig
@@ -109,6 +112,7 @@ publishConfigCodec = CJ.named "PublishConfig" $ CJS.objectStrict
109112
$ CJS.recordPropOptional @"location" publishLocationCodec
110113
$ CJS.recordPropOptional @"include" (CJ.array CJ.string)
111114
$ CJS.recordPropOptional @"exclude" (CJ.array CJ.string)
115+
$ CJS.recordPropOptional @"owners" (CJ.array Owner.codec)
112116
$ CJS.record
113117

114118
-- This codec duplicates `Location.codec` from the Registry library, but with

core/src/FS.purs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ module Spago.FS
77
, ensureFileSync
88
, exists
99
, getInBetweenPaths
10-
, isLink
1110
, ls
1211
, mkdirp
1312
, moveSync
1413
, readJsonFile
1514
, readTextFile
1615
, readYamlDocFile
1716
, readYamlFile
18-
, stat
1917
, writeFile
2018
, writeJsonFile
2119
, writeTextFile
@@ -33,8 +31,6 @@ import Effect.Uncurried (EffectFn2, runEffectFn2)
3331
import Node.FS.Aff as FS.Aff
3432
import Node.FS.Perms (Perms)
3533
import Node.FS.Perms as Perms
36-
import Node.FS.Stats (Stats)
37-
import Node.FS.Stats as Stats
3834
import Node.FS.Sync as FS.Sync
3935
import Spago.Json as Json
4036
import Spago.Yaml as Yaml
@@ -116,14 +112,6 @@ readYamlDocFile codec path = do
116112
result <- Aff.attempt $ FS.Aff.readTextFile UTF8 path
117113
pure (lmap Aff.message result >>= Yaml.parseYamlDoc codec >>> lmap CJ.DecodeError.print)
118114

119-
stat :: forall m. MonadAff m => FilePath -> m (Either Error Stats)
120-
stat path = liftAff $ try (FS.Aff.stat path)
121-
122-
isLink :: forall m. MonadEffect m => FilePath -> m Boolean
123-
isLink path = liftEffect $ try (FS.Sync.lstat path) >>= case _ of
124-
Left _err -> pure true -- TODO: we should bubble this up instead
125-
Right stats -> pure $ Stats.isSymbolicLink stats
126-
127115
foreign import getInBetweenPathsImpl :: EffectFn2 FilePath FilePath (Array FilePath)
128116

129117
getInBetweenPaths :: forall m. MonadEffect m => FilePath -> FilePath -> m (Array FilePath)

core/src/Json.purs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import Data.Array as Array
88
import Data.Bifunctor (lmap)
99
import Data.Codec.JSON as CJ
1010
import Data.Either (Either)
11+
import Foreign (Foreign)
12+
import JSON (JSON)
1113
import JSON as JSON
1214
import JSON.Path as JSON.Path
15+
import Unsafe.Coerce (unsafeCoerce)
1316

1417
-- | Print a type as a formatted JSON string
1518
printJson :: forall a. CJ.Codec a -> a -> String
@@ -31,3 +34,7 @@ printConfigError (DecodeError { path, message, causes }) =
3134
false -> Array.foldMap printConfigError causes
3235
-- If there are none, then we have reached a leaf, and can print the actual error
3336
true -> [ JSON.Path.print path <> ": " <> message ]
37+
38+
-- | Decode a Foreign into JSON
39+
unsafeFromForeign :: Foreign -> JSON
40+
unsafeFromForeign = unsafeCoerce

docs-search/index/src/Docs/Search/IndexBuilder.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { globSync } from "glob";
1+
import { globSync } from 'glob';
22
import path from "node:path";
33
import { fileURLToPath } from "node:url";
44

55
export function getDocsSearchAppPath() {
66
const fileName = fileURLToPath(import.meta.url);
77
const absoluteDir = path.dirname(fileName);
88
const basename = path.basename(absoluteDir);
9-
9+
1010
// unbundled dev build
1111
if (basename == "Docs.Search.IndexBuilder") {
1212
return path.join(absoluteDir, "..", "..", "bin", "docs-search-app.js");
1313
}
14-
// bundled production build
14+
// bundled production build
1515
if (basename === "bin") {
1616
return path.join(absoluteDir, "docs-search-app.js");
1717
}

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)