github.com/whyrusleeping/gx@v0.14.3/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  	"text/tabwriter"
    15  
    16  	"github.com/blang/semver"
    17  	cli "github.com/urfave/cli"
    18  	gx "github.com/whyrusleeping/gx/gxutil"
    19  	filter "github.com/whyrusleeping/json-filter"
    20  	progmeter "github.com/whyrusleeping/progmeter"
    21  	log "github.com/whyrusleeping/stump"
    22  )
    23  
    24  var (
    25  	cwd string
    26  	pm  *gx.PM
    27  )
    28  
    29  const PkgFileName = gx.PkgFileName
    30  
    31  func LoadPackageFile(path string) (*gx.Package, error) {
    32  	if path == PkgFileName {
    33  		root, err := gx.GetPackageRoot()
    34  		if err != nil {
    35  			return nil, err
    36  		}
    37  
    38  		path = filepath.Join(root, PkgFileName)
    39  	}
    40  
    41  	var pkg gx.Package
    42  	err := gx.LoadPackageFile(&pkg, path)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	if pkg.GxVersion == "" {
    48  		pkg.GxVersion = gx.GxVersion
    49  	}
    50  
    51  	if pkg.SubtoolRequired {
    52  		found, err := gx.IsSubtoolInstalled(pkg.Language)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  		if !found {
    57  			return nil, fmt.Errorf("package requires a subtool (gx-%s) and none was found", pkg.Language)
    58  		}
    59  	}
    60  
    61  	dephashes := make(map[string]string)
    62  	for _, dep := range pkg.Dependencies {
    63  		if pkgname := dephashes[dep.Hash]; pkgname != "" {
    64  			return nil, fmt.Errorf("have two packages with a hash of %s (%s, %s)", dep.Hash, dep.Name, pkgname)
    65  		}
    66  		dephashes[dep.Hash] = dep.Name
    67  	}
    68  
    69  	return &pkg, nil
    70  }
    71  
    72  func main() {
    73  	cfg, err := gx.LoadConfig()
    74  	if err != nil {
    75  		log.Fatal(err)
    76  	}
    77  
    78  	pm, err = gx.NewPM(cfg)
    79  	if err != nil {
    80  		log.Fatal(err)
    81  	}
    82  
    83  	app := cli.NewApp()
    84  	app.Author = "whyrusleeping"
    85  	app.Version = gx.GxVersion
    86  	app.Flags = []cli.Flag{
    87  		cli.BoolFlag{
    88  			Name:  "verbose",
    89  			Usage: "print verbose logging information",
    90  		},
    91  	}
    92  	app.Before = func(c *cli.Context) error {
    93  		log.Verbose = c.Bool("verbose")
    94  
    95  		gcwd, err := os.Getwd()
    96  		if err != nil {
    97  			return err
    98  		}
    99  		cwd = gcwd
   100  
   101  		return nil
   102  	}
   103  
   104  	app.Usage = "gx is a packaging tool that uses ipfs"
   105  
   106  	app.Commands = []cli.Command{
   107  		CleanCommand,
   108  		DepsCommand,
   109  		GetCommand,
   110  		ImportCommand,
   111  		DiffCommand,
   112  		InitCommand,
   113  		InstallCommand,
   114  		LockInstallCommand,
   115  		PublishCommand,
   116  		ReleaseCommand,
   117  		RepoCommand,
   118  		UpdateCommand,
   119  		VersionCommand,
   120  		ViewCommand,
   121  		SetCommand,
   122  		TestCommand,
   123  	}
   124  
   125  	if err := app.Run(os.Args); err != nil {
   126  		log.Fatal(err)
   127  	}
   128  }
   129  
   130  func checkLastPubVer() string {
   131  	out, err := ioutil.ReadFile(filepath.Join(cwd, ".gx", "lastpubver"))
   132  	if err != nil {
   133  		return ""
   134  	}
   135  
   136  	parts := bytes.Split(out, []byte{':'})
   137  	return string(parts[0])
   138  }
   139  
   140  var PublishCommand = cli.Command{
   141  	Name:  "publish",
   142  	Usage: "publish a package",
   143  	Description: `publish a package into ipfs using a locally running daemon.
   144  
   145  'publish' bundles up all files associated with the package (respecting
   146  .gitignore and .gxignore files), adds them to ipfs, and writes out the
   147  resulting package hash.
   148  
   149  By default, you cannot publish a package without updating the version
   150  number. This is a soft requirement and can be skipped by specifying the
   151  -f or --force flag.
   152  `,
   153  	Flags: []cli.Flag{
   154  		cli.BoolFlag{
   155  			Name:  "force,f",
   156  			Usage: "allow publishing without bumping version",
   157  		},
   158  	},
   159  	Action: func(c *cli.Context) error {
   160  		if gx.UsingGateway {
   161  			log.Log("gx cannot publish using public gateways.")
   162  			log.Log("please run an ipfs node and try again.")
   163  			return nil
   164  		}
   165  
   166  		pkg, err := LoadPackageFile(PkgFileName)
   167  		if err != nil {
   168  			return err
   169  		}
   170  
   171  		if !c.Bool("force") {
   172  			if pkg.Version == checkLastPubVer() {
   173  				log.Fatal("please update your packages version before publishing. (use -f to skip)")
   174  			}
   175  		}
   176  
   177  		_, err = doPublish(pkg)
   178  		return err
   179  	},
   180  }
   181  
   182  func doPublish(pkg *gx.Package) (string, error) {
   183  	if !pm.ShellOnline() {
   184  		return "", fmt.Errorf("ipfs daemon isn't running")
   185  	}
   186  
   187  	err := gx.TryRunHook("pre-publish", pkg.Language, pkg.SubtoolRequired)
   188  	if err != nil {
   189  		return "", err
   190  	}
   191  
   192  	hash, err := pm.PublishPackage(cwd, &pkg.PackageBase)
   193  	if err != nil {
   194  		return hash, fmt.Errorf("publishing: %s", err)
   195  	}
   196  	log.Log("package %s published with hash: %s", pkg.Name, hash)
   197  
   198  	// write out version hash
   199  	err = writeLastPub(pkg.Version, hash)
   200  	if err != nil {
   201  		return hash, err
   202  	}
   203  
   204  	err = gx.TryRunHook("post-publish", pkg.Language, pkg.SubtoolRequired, hash)
   205  	return hash, err
   206  }
   207  
   208  func writeLastPub(vers string, hash string) error {
   209  	err := os.MkdirAll(".gx", 0755)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	fi, err := os.Create(".gx/lastpubver")
   215  	if err != nil {
   216  		return fmt.Errorf("failed to create version file: %s", err)
   217  	}
   218  
   219  	defer fi.Close()
   220  
   221  	log.VLog("writing published version to .gx/lastpubver")
   222  	_, err = fmt.Fprintf(fi, "%s: %s\n", vers, hash)
   223  	if err != nil {
   224  		return fmt.Errorf("failed to write version file: %s", err)
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  var ImportCommand = cli.Command{
   231  	Name:  "import",
   232  	Usage: "import a package as a dependency",
   233  	Description: `Download packages and add them as a dependency in package.json.
   234  
   235  EXAMPLE
   236    > gx import QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH
   237    > gx import github.com/libp2p/go-libp2p
   238  
   239      In the last example, Gx will check the ".gx/lastpubver"
   240      file in the repository to find which hash to import.
   241  `,
   242  	Flags: []cli.Flag{
   243  		cli.BoolTFlag{
   244  			Name:  "global",
   245  			Usage: "download imported package to global store",
   246  		},
   247  		cli.BoolFlag{
   248  			Name:  "local",
   249  			Usage: "install packages locally (equal to --global=false)",
   250  		},
   251  	},
   252  	Action: func(c *cli.Context) error {
   253  		if len(c.Args()) == 0 {
   254  			return fmt.Errorf("import requires a package reference")
   255  		}
   256  
   257  		global := c.BoolT("global")
   258  		if c.Bool("local") {
   259  			global = false
   260  		}
   261  
   262  		pm.SetGlobal(global)
   263  
   264  		pkg, err := LoadPackageFile(PkgFileName)
   265  		if err != nil {
   266  			return err
   267  		}
   268  
   269  		depname := c.Args().First()
   270  		cdep := pkg.FindDep(depname)
   271  		if cdep != nil {
   272  			return fmt.Errorf("package %s already imported as %s", cdep.Hash, cdep.Name)
   273  		}
   274  
   275  		dephash, err := pm.ResolveDepName(depname)
   276  		if err != nil {
   277  			return err
   278  		}
   279  
   280  		ipath, err := gx.InstallPath(pkg.Language, "", global)
   281  		if err != nil {
   282  			return err
   283  		}
   284  
   285  		npkg, err := pm.InstallPackage(dephash, ipath)
   286  		if err != nil {
   287  			return fmt.Errorf("(install): %s", err)
   288  		}
   289  
   290  		pm.ProgMeter.Stop()
   291  
   292  		if pkg.FindDep(npkg.Name) != nil {
   293  			s := fmt.Sprintf("package with name %s already imported, continue?", npkg.Name)
   294  			if !yesNoPrompt(s, false) {
   295  				return nil
   296  			}
   297  			log.Log("continuing, please note some things may not work as expected")
   298  		}
   299  
   300  		ndep := &gx.Dependency{
   301  			Author:  npkg.Author,
   302  			Hash:    dephash,
   303  			Name:    npkg.Name,
   304  			Version: npkg.Version,
   305  		}
   306  
   307  		pkg.Dependencies = append(pkg.Dependencies, ndep)
   308  		err = gx.SavePackageFile(pkg, PkgFileName)
   309  		if err != nil {
   310  			return fmt.Errorf("writing pkgfile: %s", err)
   311  		}
   312  
   313  		err = gx.TryRunHook("post-import", npkg.Language, npkg.SubtoolRequired, dephash)
   314  		if err != nil {
   315  			return fmt.Errorf("running post-import: %s", err)
   316  		}
   317  
   318  		return nil
   319  	},
   320  }
   321  
   322  var InstallCommand = cli.Command{
   323  	Name:    "install",
   324  	Usage:   "install this package",
   325  	Aliases: []string{"i"},
   326  	Flags: []cli.Flag{
   327  		cli.BoolTFlag{
   328  			Name:  "global",
   329  			Usage: "install package in global namespace",
   330  		},
   331  		cli.BoolFlag{
   332  			Name:  "local",
   333  			Usage: "install packages locally (equal to --global=false)",
   334  		},
   335  		cli.BoolFlag{
   336  			Name:  "save",
   337  			Usage: "write installed packages as deps in package.json",
   338  		},
   339  		cli.BoolFlag{
   340  			Name:  "nofancy",
   341  			Usage: "write minimal output",
   342  		},
   343  	},
   344  	Action: func(c *cli.Context) error {
   345  		pkg, err := LoadPackageFile(PkgFileName)
   346  		if err != nil {
   347  			return err
   348  		}
   349  
   350  		save := c.Bool("save")
   351  
   352  		global := c.BoolT("global")
   353  		if c.Bool("local") {
   354  			global = false
   355  		}
   356  
   357  		pm.SetGlobal(global)
   358  
   359  		pm.ProgMeter = progmeter.NewProgMeter(c.Bool("nofancy"))
   360  
   361  		if len(c.Args()) == 0 {
   362  			cwd, err := os.Getwd()
   363  			if err != nil {
   364  				return err
   365  			}
   366  
   367  			err = gx.TryRunHook("req-check", pkg.Language, pkg.SubtoolRequired, cwd)
   368  			if err != nil {
   369  				return err
   370  			}
   371  
   372  			ipath, err := gx.InstallPath(pkg.Language, cwd, global)
   373  			if err != nil {
   374  				return err
   375  			}
   376  
   377  			err = pm.InstallDeps(pkg, ipath)
   378  			if err != nil {
   379  				return fmt.Errorf("install deps: %s", err)
   380  			}
   381  			return nil
   382  		}
   383  
   384  		ipath, err := gx.InstallPath(pkg.Language, "", global)
   385  		if err != nil {
   386  			return err
   387  		}
   388  
   389  		for _, p := range c.Args() {
   390  			phash, err := pm.ResolveDepName(p)
   391  			if err != nil {
   392  				return fmt.Errorf("resolving package '%s': %s", p, err)
   393  			}
   394  
   395  			if p != phash {
   396  				log.VLog("%s resolved to %s", p, phash)
   397  			}
   398  
   399  			ndep, err := pm.ImportPackage(ipath, p)
   400  			if err != nil {
   401  				return fmt.Errorf("importing package '%s': %s", p, err)
   402  			}
   403  
   404  			if save {
   405  				foundPackage := false
   406  				for _, d := range pkg.Dependencies {
   407  					if d.Hash == ndep.Hash {
   408  						foundPackage = true
   409  					}
   410  				}
   411  				if !foundPackage {
   412  					pkg.Dependencies = append(pkg.Dependencies, ndep)
   413  				}
   414  			}
   415  		}
   416  
   417  		if save {
   418  			err := gx.SavePackageFile(pkg, PkgFileName)
   419  			if err != nil {
   420  				return err
   421  			}
   422  		}
   423  
   424  		return nil
   425  	},
   426  }
   427  
   428  var GetCommand = cli.Command{
   429  	Name:  "get",
   430  	Usage: "download a package",
   431  	Flags: []cli.Flag{
   432  		cli.StringFlag{
   433  			Name:  "o",
   434  			Usage: "specify output dir name",
   435  		},
   436  	},
   437  	Action: func(c *cli.Context) error {
   438  		if !c.Args().Present() {
   439  			return fmt.Errorf("no package specified")
   440  		}
   441  
   442  		pkg, err := pm.ResolveDepName(c.Args().First())
   443  		if err != nil {
   444  			return err
   445  		}
   446  
   447  		out := c.String("o")
   448  		if out == "" {
   449  			out = filepath.Join(cwd, pkg)
   450  		}
   451  
   452  		log.Log("writing package to:", out)
   453  		_, err = pm.GetPackageTo(pkg, out)
   454  		if err != nil {
   455  			return fmt.Errorf("fetching package: %s", err)
   456  		}
   457  		return nil
   458  	},
   459  }
   460  
   461  var InitCommand = cli.Command{
   462  	Name:  "init",
   463  	Usage: "initialize a package in the current working directory",
   464  	Flags: []cli.Flag{
   465  		cli.StringFlag{
   466  			Name:  "lang",
   467  			Usage: "specify primary language of new package",
   468  		},
   469  	},
   470  	Action: func(c *cli.Context) error {
   471  		var pkgname string
   472  		if len(c.Args()) > 0 {
   473  			pkgname = c.Args().First()
   474  		} else {
   475  			pkgname = filepath.Base(cwd)
   476  		}
   477  
   478  		lang := c.String("lang")
   479  		if !c.IsSet("lang") {
   480  			lang = promptUser("what language will the project be in?")
   481  		}
   482  
   483  		log.Log("initializing package %s...", pkgname)
   484  		err := pm.InitPkg(cwd, pkgname, lang, func(p *gx.Package) {
   485  			p.Bugs.Url = promptUser("where should users go to report issues?")
   486  		})
   487  
   488  		if err != nil {
   489  			return fmt.Errorf("init error: %s", err)
   490  		}
   491  
   492  		return nil
   493  	},
   494  }
   495  
   496  var UpdateCommand = cli.Command{
   497  	Name:      "update",
   498  	Usage:     "update a package dependency",
   499  	ArgsUsage: "[oldref] [newref]",
   500  	Description: `Update a package to a specified ref.
   501  
   502  EXAMPLE:
   503     Update 'myPkg' to a given version (referencing it by package name):
   504  
   505     $ gx update myPkg QmPZ6gM12JxshKzwSyrhbEmyrsi7UaMrnoQZL6mdrzSfh1
   506  
   507     or reference it by hash:
   508  
   509     $ export OLDHASH=QmdTTcAwxWhHLruoZtowxuqua1e5GVkYzxziiYPDn4vWJb
   510     $ export NEWHASH=QmPZ6gM12JxshKzwSyrhbEmyrsi7UaMrnoQZL6mdrzSfh1
   511     $ gx update $OLDHASH $NEWHASH
   512  `,
   513  	Flags: []cli.Flag{
   514  		cli.BoolTFlag{
   515  			Name:  "global",
   516  			Usage: "install new package in global namespace",
   517  		},
   518  		cli.BoolFlag{
   519  			Name:  "local",
   520  			Usage: "install packages locally (equal to --global=false)",
   521  		},
   522  		cli.BoolFlag{
   523  			Name:  "with-deps",
   524  			Usage: "experimental feature to recursively update child deps too",
   525  		},
   526  	},
   527  	Action: func(c *cli.Context) error {
   528  		pkg, err := LoadPackageFile(PkgFileName)
   529  		if err != nil {
   530  			log.Fatal("error: ", err)
   531  		}
   532  
   533  		var existing, target string
   534  		switch len(c.Args()) {
   535  		case 0:
   536  			log.Fatal("update requires two arguments, current and target")
   537  		case 1:
   538  			target = c.Args()[0]
   539  		case 2:
   540  			existing = c.Args()[0]
   541  			target = c.Args()[1]
   542  		default:
   543  			log.Log("ignoring extra arguments: %s", c.Args()[2:])
   544  		}
   545  
   546  		trgthash, err := pm.ResolveDepName(target)
   547  		if err != nil {
   548  			return err
   549  		}
   550  
   551  		global := c.BoolT("global")
   552  		if c.Bool("local") {
   553  			global = false
   554  		}
   555  
   556  		ipath, err := gx.InstallPath(pkg.Language, cwd, global)
   557  		if err != nil {
   558  			return err
   559  		}
   560  
   561  		npkg, err := pm.InstallPackage(trgthash, ipath)
   562  		if err != nil {
   563  			log.Fatal("(installpackage) : ", err)
   564  		}
   565  
   566  		if existing == "" {
   567  			existing = npkg.Name
   568  		}
   569  
   570  		var oldhash string
   571  		olddep := pkg.FindDep(existing)
   572  		if olddep == nil {
   573  			log.Fatal("unknown package: ", existing)
   574  		}
   575  		oldhash = olddep.Hash
   576  
   577  		log.Log("updating %s to version %s (%s)", olddep.Name, npkg.Version, trgthash)
   578  
   579  		if npkg.Name != olddep.Name {
   580  			prompt := fmt.Sprintf(`Target package has a different name than new package:
   581  old: %s (%s)
   582  new: %s (%s)
   583  continue?`, olddep.Name, olddep.Hash, npkg.Name, trgthash)
   584  			if !yesNoPrompt(prompt, false) {
   585  				log.Fatal("refusing to update package with different names")
   586  			}
   587  		}
   588  
   589  		log.VLog("running pre update hook...")
   590  		err = gx.TryRunHook("pre-update", pkg.Language, pkg.SubtoolRequired, existing)
   591  		if err != nil {
   592  			return err
   593  		}
   594  
   595  		if c.Bool("with-deps") {
   596  			err := RecursiveDepUpdate(pkg, oldhash, trgthash)
   597  			if err != nil {
   598  				return err
   599  			}
   600  		} else {
   601  			log.VLog("checking for potential package naming collisions...")
   602  			err = updateCollisionCheck(pkg, olddep, trgthash, nil, make(map[string]struct{}))
   603  			if err != nil {
   604  				log.Fatal("update sanity check: ", err)
   605  			}
   606  			log.VLog("  - no collisions found for updated package")
   607  		}
   608  
   609  		olddep.Hash = trgthash
   610  		olddep.Version = npkg.Version
   611  
   612  		err = gx.SavePackageFile(pkg, PkgFileName)
   613  		if err != nil {
   614  			return fmt.Errorf("writing package file: %s", err)
   615  		}
   616  
   617  		log.VLog("running post update hook...")
   618  		err = gx.TryRunHook("post-update", pkg.Language, pkg.SubtoolRequired, oldhash, trgthash)
   619  		if err != nil {
   620  			return err
   621  		}
   622  
   623  		log.VLog("update complete!")
   624  
   625  		return nil
   626  	},
   627  }
   628  
   629  func updateCollisionCheck(ipkg *gx.Package, idep *gx.Dependency, trgt string, chain []string, skip map[string]struct{}) error {
   630  	return ipkg.ForEachDep(func(dep *gx.Dependency, pkg *gx.Package) error {
   631  		if _, ok := skip[dep.Hash]; ok {
   632  			return nil
   633  		}
   634  
   635  		if dep == idep {
   636  			return nil
   637  		}
   638  		skip[dep.Hash] = struct{}{}
   639  
   640  		if (dep.Name == idep.Name && dep.Hash != trgt) || (dep.Hash == idep.Hash && dep.Name != idep.Name) {
   641  			log.Log("dep %s also imports %s (%s)", strings.Join(chain, "/"), dep.Name, dep.Hash)
   642  			return nil
   643  		}
   644  
   645  		return updateCollisionCheck(pkg, idep, trgt, append(chain, dep.Name), skip)
   646  	})
   647  }
   648  
   649  var VersionCommand = cli.Command{
   650  	Name:  "version",
   651  	Usage: "view or modify this package's version",
   652  	Description: `view or modify this package's version
   653  
   654     run without any arguments, will print the current semver of this package.
   655  
   656     if an argument is given, it will be parsed as a semver; if that succeeds,
   657     the version will be set to that exactly. If the argument is not a semver,
   658     it should be one of three things: "major", "minor", or "patch". Passing
   659     any of those three will bump the corresponding segment of the semver up
   660     by one.
   661  
   662  EXAMPLE:
   663  
   664     > gx version
   665     0.4.0
   666  
   667     > gx version patch
   668     updated version to 0.4.1
   669  
   670     > gx version major
   671     updated version to 1.0.0
   672  
   673     > gx version 2.5.7
   674     updated version to 2.5.7
   675  `,
   676  	Action: func(c *cli.Context) (outerr error) {
   677  		pkg, err := LoadPackageFile(PkgFileName)
   678  		if err != nil {
   679  			return err
   680  		}
   681  		if !c.Args().Present() {
   682  			fmt.Println(pkg.Version)
   683  			return
   684  		}
   685  
   686  		return updateVersion(pkg, c.Args().First())
   687  	},
   688  }
   689  
   690  func updateVersion(pkg *gx.Package, nver string) (outerr error) {
   691  	if nver == "" {
   692  		return fmt.Errorf("must specify version with non-zero length")
   693  	}
   694  
   695  	defer func() {
   696  		err := gx.SavePackageFile(pkg, PkgFileName)
   697  		if err != nil {
   698  			outerr = err
   699  		}
   700  	}()
   701  
   702  	// if argument is a semver, set version to it
   703  	_, err := semver.Make(nver)
   704  	if err == nil {
   705  		pkg.Version = nver
   706  		return nil
   707  	}
   708  
   709  	v, err := semver.Make(pkg.Version)
   710  	if err != nil {
   711  		return err
   712  	}
   713  	switch nver {
   714  	case "major":
   715  		v.Major++
   716  		v.Minor = 0
   717  		v.Patch = 0
   718  		v.Pre = nil // reset prerelase info
   719  	case "minor":
   720  		v.Minor++
   721  		v.Patch = 0
   722  		v.Pre = nil
   723  	case "patch":
   724  		v.Patch++
   725  		v.Pre = nil
   726  	default:
   727  		if nver[0] == 'v' {
   728  			nver = nver[1:]
   729  		}
   730  		newver, err := semver.Make(nver)
   731  		if err != nil {
   732  			log.Error(err)
   733  			return
   734  		}
   735  		v = newver
   736  	}
   737  	log.Log("updated version to: %s", v)
   738  
   739  	pkg.Version = v.String()
   740  
   741  	return nil
   742  }
   743  
   744  var ViewCommand = cli.Command{
   745  	Name:  "view",
   746  	Usage: "view package information",
   747  	Description: `view can be used to print out information in the package.json
   748     of this package, or a dependency specified either by name or hash.
   749  
   750  EXAMPLE:
   751     > gx view language
   752     go
   753  
   754     > gx view .
   755     {
   756       "language": "go",
   757       "name": "gx",
   758       "version": "0.2.0
   759     }
   760  
   761     > gx view go-libp2p gx.dvcsimport
   762     "github.com/ipfs/go-libp2p"
   763  
   764     > gx view '.gxDependencies[0].name'
   765     go-multihash
   766  
   767     > gx view '.gxDependencies[.name=go-multiaddr].hash'
   768     QmWLfU4tstw2aNcTykDm44xbSTCYJ9pUJwfhQCKGwckcHx
   769  `,
   770  	Action: func(c *cli.Context) error {
   771  		if !c.Args().Present() {
   772  			log.Fatal("must specify at least a query")
   773  		}
   774  
   775  		var cfg map[string]interface{}
   776  		if len(c.Args()) == 2 {
   777  			pkg, err := LoadPackageFile(gx.PkgFileName)
   778  			if err != nil {
   779  				return err
   780  			}
   781  
   782  			ref := c.Args()[0]
   783  			dep := pkg.FindDep(ref)
   784  			if dep == nil {
   785  				return fmt.Errorf("no dep referenced by %s", ref)
   786  			}
   787  			err = gx.LoadPackage(&cfg, pkg.Language, dep.Hash)
   788  			if err != nil {
   789  				return err
   790  			}
   791  		} else {
   792  			root, err := gx.GetPackageRoot()
   793  			if err != nil {
   794  				return err
   795  			}
   796  
   797  			err = gx.LoadPackageFile(&cfg, filepath.Join(root, PkgFileName))
   798  			if err != nil {
   799  				return err
   800  			}
   801  		}
   802  
   803  		queryStr := c.Args()[len(c.Args())-1]
   804  		val, err := filter.Get(cfg, queryStr)
   805  		if err != nil {
   806  			return err
   807  		}
   808  
   809  		jsonPrint(val)
   810  		return nil
   811  	},
   812  }
   813  
   814  var depDotCommand = cli.Command{
   815  	Name:  "dot",
   816  	Usage: "generate dot graph of the package tree",
   817  	Action: func(c *cli.Context) error {
   818  		pkg, err := LoadPackageFile(PkgFileName)
   819  		if err != nil {
   820  			return err
   821  		}
   822  		dt, err := genDepsTree(pm, pkg)
   823  		if err != nil {
   824  			log.Fatal(err)
   825  		}
   826  
   827  		toProcess := make([]*depTreeNode, 0, 32)
   828  		rank := make(map[string]int)
   829  
   830  		name := func(d *gx.Dependency) string {
   831  			hash := d.Hash
   832  			if len(hash) > 6 {
   833  				hash = hash[0:6]
   834  			}
   835  			return fmt.Sprintf("\"%s@%s\"", d.Name, hash)
   836  		}
   837  
   838  		bfs := func(dt *depTreeNode, depth int) {
   839  			toProcess = append(toProcess, dt.children...)
   840  			rank[name(dt.this)] = depth
   841  		}
   842  		depth := 0
   843  		bfs(dt, depth)
   844  		for len(toProcess) > 0 {
   845  			depth++
   846  			current := toProcess
   847  			toProcess = make([]*depTreeNode, 0, 32)
   848  			for _, dt := range current {
   849  				bfs(dt, depth)
   850  			}
   851  		}
   852  
   853  		fmt.Println("digraph G {")
   854  		fmt.Println("rankdir = TB;")
   855  		fmt.Println("subgraph {")
   856  
   857  		toPrint := make([]*depTreeNode, 0, 32)
   858  		printed := make(map[string]struct{})
   859  		printNode := func(dt *depTreeNode) {
   860  			if _, ok := printed[dt.this.Hash]; ok {
   861  				return
   862  			}
   863  			printed[dt.this.Hash] = struct{}{}
   864  			toPrint = append(toPrint, dt.children...)
   865  			for _, c := range dt.children {
   866  				fmt.Printf("%s -> %s\n", name(dt.this), name(c.this))
   867  			}
   868  		}
   869  
   870  		printNode(dt)
   871  		for len(toPrint) > 0 {
   872  			current := toPrint
   873  			toPrint = make([]*depTreeNode, 0, 32)
   874  			for _, dt := range current {
   875  				printNode(dt)
   876  			}
   877  		}
   878  		reverseRank := make([][]string, depth+1)
   879  		for n, r := range rank {
   880  			reverseRank[r] = append(reverseRank[r], n)
   881  		}
   882  		for _, r := range reverseRank {
   883  			fmt.Printf("{rank = same;")
   884  			for _, n := range r {
   885  				fmt.Printf(" %s;", n)
   886  			}
   887  			fmt.Printf("}\n")
   888  		}
   889  		fmt.Println("}")
   890  		fmt.Println("}")
   891  		return nil
   892  
   893  	},
   894  }
   895  
   896  var depCheckCommand = cli.Command{
   897  	Name:  "check",
   898  	Usage: "perform comprehensive sanity check and note any packaging issues",
   899  	Action: func(c *cli.Context) error {
   900  		pkg, err := LoadPackageFile(PkgFileName)
   901  		if err != nil {
   902  			return err
   903  		}
   904  		success, err := check(pkg)
   905  		if err != nil {
   906  			return err
   907  		}
   908  		if success {
   909  			os.Exit(0)
   910  		} else {
   911  			os.Exit(1)
   912  		}
   913  		return nil
   914  	},
   915  }
   916  
   917  var LockInstallCommand = cli.Command{
   918  	Name:  "lock-install",
   919  	Usage: "Install deps from lockfile into vendor",
   920  	Flags: []cli.Flag{
   921  		cli.BoolFlag{
   922  			Name:  "nofancy",
   923  			Usage: "write minimal output",
   924  		},
   925  	},
   926  	Action: func(c *cli.Context) error {
   927  		cwd, err := os.Getwd()
   928  		if err != nil {
   929  			return err
   930  		}
   931  
   932  		var lck gx.LockFile
   933  		if err := gx.LoadLockFile(&lck, filepath.Join(cwd, gx.LckFileName)); err != nil {
   934  			return err
   935  		}
   936  
   937  		pm.ProgMeter = progmeter.NewProgMeter(c.Bool("nofancy"))
   938  
   939  		if err := pm.InstallLock(lck.Lock, cwd); err != nil {
   940  			return fmt.Errorf("install deps: %s", err)
   941  		}
   942  
   943  		return nil
   944  	},
   945  }
   946  
   947  var CleanCommand = cli.Command{
   948  	Name:  "clean",
   949  	Usage: "cleanup unused packages in vendor directory",
   950  	Description: `deletes any package in the 'vendor/gx' directory
   951     that is not a dependency of this package.
   952  
   953     use '--dry-run' to print packages that would be deleted without actually
   954     removing them.
   955     `,
   956  	Flags: []cli.Flag{
   957  		cli.BoolFlag{
   958  			Name:  "dry-run",
   959  			Usage: "print out things to be removed without removing them",
   960  		},
   961  	},
   962  	Action: func(c *cli.Context) error {
   963  		pkg, err := LoadPackageFile(PkgFileName)
   964  		if err != nil {
   965  			return err
   966  		}
   967  
   968  		dry := c.Bool("dry-run")
   969  
   970  		good, err := pm.EnumerateDependencies(pkg)
   971  		if err != nil {
   972  			return err
   973  		}
   974  
   975  		ipath, err := gx.InstallPath(pkg.Language, cwd, false)
   976  		if err != nil {
   977  			return err
   978  		}
   979  
   980  		vdir := filepath.Join(ipath, "gx", "ipfs")
   981  		dirinfos, err := ioutil.ReadDir(vdir)
   982  		if err != nil {
   983  			if os.IsNotExist(err) {
   984  				return nil
   985  			}
   986  			return err
   987  		}
   988  
   989  		for _, di := range dirinfos {
   990  			if !strings.HasPrefix(di.Name(), "Qm") {
   991  				continue
   992  			}
   993  			_, keep := good[di.Name()]
   994  			if !keep {
   995  				fmt.Println(di.Name())
   996  				if !dry {
   997  					err := os.RemoveAll(filepath.Join(vdir, di.Name()))
   998  					if err != nil {
   999  						return err
  1000  					}
  1001  				}
  1002  			}
  1003  		}
  1004  
  1005  		return nil
  1006  	},
  1007  }
  1008  
  1009  var DepsCommand = cli.Command{
  1010  	Name:  "deps",
  1011  	Usage: "print out package dependencies",
  1012  	Description: `prints out dependencies for this package
  1013  
  1014     Run with no flags, will print out name, hash, and version for each
  1015     package that is a direct dependency of this package.
  1016  
  1017     The '-r' option will recursively print out all dependencies directly
  1018     and indirectly required by this package.
  1019  
  1020     The '--tree' option will do the same as '-r', but will add indents
  1021     to show which packages are dependent on which other.
  1022  `,
  1023  	Flags: []cli.Flag{
  1024  		cli.BoolFlag{
  1025  			Name:  "r",
  1026  			Usage: "print deps recursively",
  1027  		},
  1028  		cli.BoolFlag{
  1029  			Name:  "q",
  1030  			Usage: "only print hashes",
  1031  		},
  1032  		cli.BoolFlag{
  1033  			Name:  "tree",
  1034  			Usage: "print deps as a tree",
  1035  		},
  1036  		cli.BoolTFlag{
  1037  			Name:  "s,sort",
  1038  			Usage: "sort output by package name",
  1039  		},
  1040  		cli.StringFlag{
  1041  			Name:  "highlight",
  1042  			Usage: "for tree printing, prune branches unrelated to arg",
  1043  		},
  1044  		cli.BoolFlag{
  1045  			Name:  "collapse",
  1046  			Usage: "for tree printing, prune branches already printed",
  1047  		},
  1048  	},
  1049  	Subcommands: []cli.Command{
  1050  		depBundleCommand,
  1051  		depFindCommand,
  1052  		depStatsCommand,
  1053  		depDupesCommand,
  1054  		depCheckCommand,
  1055  		depDotCommand,
  1056  	},
  1057  	Action: func(c *cli.Context) error {
  1058  		rec := c.Bool("r")
  1059  		quiet := c.Bool("q")
  1060  
  1061  		pkg, err := LoadPackageFile(PkgFileName)
  1062  		if err != nil {
  1063  			return err
  1064  		}
  1065  
  1066  		if c.Bool("tree") {
  1067  			dt, err := genDepsTree(pm, pkg)
  1068  			if err != nil {
  1069  				log.Fatal(err)
  1070  			}
  1071  
  1072  			dt.printFiltered(c.String("highlight"), quiet, c.Bool("collapse"))
  1073  			return nil
  1074  		}
  1075  
  1076  		var deps []string
  1077  		if rec {
  1078  			depmap, err := pm.EnumerateDependencies(pkg)
  1079  			if err != nil {
  1080  				return err
  1081  			}
  1082  
  1083  			for k := range depmap {
  1084  				deps = append(deps, k)
  1085  			}
  1086  		} else {
  1087  			for _, d := range pkg.Dependencies {
  1088  				deps = append(deps, d.Hash)
  1089  			}
  1090  		}
  1091  
  1092  		sort.Strings(deps)
  1093  
  1094  		buf := new(bytes.Buffer)
  1095  		w := tabwriter.NewWriter(buf, 12, 4, 1, ' ', 0)
  1096  		for _, d := range deps {
  1097  			if !quiet {
  1098  				var dpkg gx.Package
  1099  				err := gx.LoadPackage(&dpkg, pkg.Language, d)
  1100  				if err != nil {
  1101  					if os.IsNotExist(err) {
  1102  						return fmt.Errorf("package %s not found", d)
  1103  					}
  1104  					return err
  1105  				}
  1106  
  1107  				fmt.Fprintf(w, "%s\t%s\t%s\n", dpkg.Name, d, dpkg.Version)
  1108  			} else {
  1109  				fmt.Fprintln(w, d)
  1110  			}
  1111  		}
  1112  		w.Flush()
  1113  
  1114  		if c.Bool("sort") {
  1115  			lines := strings.Split(buf.String(), "\n")
  1116  			lines = lines[:len(lines)-1] // remove trailing newline
  1117  			sort.Strings(lines)
  1118  			for _, l := range lines {
  1119  				fmt.Println(l)
  1120  			}
  1121  		} else {
  1122  			io.Copy(os.Stdout, buf)
  1123  		}
  1124  
  1125  		return nil
  1126  	},
  1127  }
  1128  
  1129  var depFindCommand = cli.Command{
  1130  	Name:  "find",
  1131  	Usage: "print hash of a given dependency",
  1132  	Action: func(c *cli.Context) error {
  1133  
  1134  		if len(c.Args()) != 1 {
  1135  			return fmt.Errorf("must be passed exactly one argument")
  1136  		}
  1137  
  1138  		pkg, err := LoadPackageFile(PkgFileName)
  1139  		if err != nil {
  1140  			return err
  1141  		}
  1142  
  1143  		dep := c.Args()[0]
  1144  
  1145  		for _, d := range pkg.Dependencies {
  1146  			if d.Name == dep {
  1147  				fmt.Println(d.Hash)
  1148  				return nil
  1149  			}
  1150  		}
  1151  		log.Fatal("no dependency named '%s' found", dep)
  1152  
  1153  		return nil
  1154  	},
  1155  }
  1156  
  1157  var depDupesCommand = cli.Command{
  1158  	Name:  "dupes",
  1159  	Usage: "print out packages with same names, but different hashes",
  1160  	Action: func(c *cli.Context) error {
  1161  		root, err := LoadPackageFile(PkgFileName)
  1162  		if err != nil {
  1163  			return err
  1164  		}
  1165  		pkgs := []*gx.Package{root}
  1166  
  1167  		// name -> hash -> importer
  1168  		set := make(map[string]map[string][]string)
  1169  
  1170  		for len(pkgs) > 0 {
  1171  			for _, d := range pkgs[0].Dependencies {
  1172  				if set[d.Name] == nil {
  1173  					set[d.Name] = map[string][]string{}
  1174  				}
  1175  				_, load := set[d.Name][d.Hash]
  1176  				set[d.Name][d.Hash] = append(set[d.Name][d.Hash], pkgs[0].Name)
  1177  
  1178  				if load {
  1179  					continue
  1180  				}
  1181  
  1182  				var depkg gx.Package
  1183  				err := gx.LoadPackage(&depkg, pkgs[0].Language, d.Hash)
  1184  				if err != nil {
  1185  					if os.IsNotExist(err) {
  1186  						return fmt.Errorf("package %s (%s) not found", d.Name, d.Hash)
  1187  					}
  1188  					return err
  1189  				}
  1190  
  1191  				pkgs = append(pkgs, &depkg)
  1192  			}
  1193  			pkgs = pkgs[1:]
  1194  		}
  1195  
  1196  		for name, hashes := range set {
  1197  			if len(hashes) > 1 {
  1198  				mostImporters := make([]string, 0, len(hashes))
  1199  				for hash := range hashes {
  1200  					mostImporters = append(mostImporters, hash)
  1201  				}
  1202  				sort.Slice(mostImporters, func(i, j int) bool {
  1203  					return len(hashes[mostImporters[i]]) >= len(hashes[mostImporters[j]])
  1204  				})
  1205  				fmt.Printf("package %s imported with multiple hashes (mostly as %s):\n", name, mostImporters[0])
  1206  				for _, hash := range mostImporters[1:] {
  1207  					//only print a few first since it's likely where the problem is
  1208  					n := len(hashes[hash])
  1209  					if n > 3 {
  1210  						n = 3
  1211  					}
  1212  					fmt.Printf("\tas %s by %s\n", hash, strings.Join(hashes[hash][:n], ", "))
  1213  				}
  1214  
  1215  			}
  1216  		}
  1217  
  1218  		return nil
  1219  	},
  1220  }
  1221  
  1222  var depStatsCommand = cli.Command{
  1223  	Name:  "stats",
  1224  	Usage: "print out statistics about this packages dependency tree",
  1225  	Action: func(c *cli.Context) error {
  1226  		pkg, err := LoadPackageFile(PkgFileName)
  1227  		if err != nil {
  1228  			return err
  1229  		}
  1230  
  1231  		ds, err := gx.GetDepStats(pkg)
  1232  		if err != nil {
  1233  			return err
  1234  		}
  1235  
  1236  		fmt.Printf("Total Import Count: %d\n", ds.TotalCount)
  1237  		fmt.Printf("Unique Import Count: %d\n", ds.TotalUnique)
  1238  		fmt.Printf("Average Import Depth: %.2f\n", ds.AverageDepth)
  1239  
  1240  		return nil
  1241  	},
  1242  }
  1243  
  1244  var depBundleCommand = cli.Command{
  1245  	Name:  "bundle",
  1246  	Usage: "print hash of object containing all dependencies for this package",
  1247  	Action: func(c *cli.Context) error {
  1248  		pkg, err := LoadPackageFile(PkgFileName)
  1249  		if err != nil {
  1250  			return err
  1251  		}
  1252  
  1253  		obj, err := depBundleForPkg(pkg)
  1254  		if err != nil {
  1255  			return err
  1256  		}
  1257  
  1258  		fmt.Println(obj)
  1259  		return nil
  1260  	},
  1261  }
  1262  
  1263  func depBundleForPkg(pkg *gx.Package) (string, error) {
  1264  	return depBundleForPkgRec(pkg, make(map[string]bool))
  1265  }
  1266  
  1267  func depBundleForPkgRec(pkg *gx.Package, done map[string]bool) (string, error) {
  1268  	obj, err := pm.Shell().NewObject("unixfs-dir")
  1269  	if err != nil {
  1270  		return "", err
  1271  	}
  1272  
  1273  	for _, dep := range pkg.Dependencies {
  1274  		if done[dep.Hash] {
  1275  			continue
  1276  		}
  1277  
  1278  		log.Log("processing dep: ", dep.Name)
  1279  		nobj, err := pm.Shell().PatchLink(obj, dep.Name+"-"+dep.Hash, dep.Hash, false)
  1280  		if err != nil {
  1281  			return "", err
  1282  		}
  1283  
  1284  		var cpkg gx.Package
  1285  		err = gx.LoadPackage(&cpkg, pkg.Language, dep.Hash)
  1286  		if err != nil {
  1287  			return "", err
  1288  		}
  1289  
  1290  		child, err := depBundleForPkgRec(&cpkg, done)
  1291  		if err != nil {
  1292  			return "", err
  1293  		}
  1294  
  1295  		nobj, err = pm.Shell().PatchLink(nobj, dep.Name+"-"+dep.Hash+"-deps", child, false)
  1296  		if err != nil {
  1297  			return "", err
  1298  		}
  1299  
  1300  		obj = nobj
  1301  
  1302  		done[dep.Hash] = true
  1303  	}
  1304  
  1305  	return obj, nil
  1306  }
  1307  
  1308  var DiffCommand = cli.Command{
  1309  	Name:        "diff",
  1310  	Usage:       "gx diff <old> <new>",
  1311  	Description: "gx diff prints the changes between two given packages",
  1312  	Action: func(c *cli.Context) error {
  1313  		if len(c.Args()) != 2 {
  1314  			return fmt.Errorf("gx diff takes two arguments")
  1315  		}
  1316  		a := c.Args()[0]
  1317  		b := c.Args()[1]
  1318  
  1319  		diff, err := DiffPackages(a, b)
  1320  		if err != nil {
  1321  			return err
  1322  		}
  1323  
  1324  		diff.Print(true)
  1325  		diff.Cleanup()
  1326  		return nil
  1327  	},
  1328  }
  1329  
  1330  var SetCommand = cli.Command{
  1331  	Name:  "set",
  1332  	Usage: "set package information",
  1333  	Description: `set can be used to change package information.
  1334  EXAMPLE:
  1335     > gx set license MIT
  1336  `,
  1337  	Flags: []cli.Flag{
  1338  		cli.BoolFlag{
  1339  			Name:  "in-json",
  1340  			Usage: "Interpret input as json",
  1341  		},
  1342  	},
  1343  	Action: func(c *cli.Context) error {
  1344  		if c.NArg() < 2 {
  1345  			log.Fatal("must specify query and value")
  1346  		}
  1347  
  1348  		var cfg map[string]interface{}
  1349  		err := gx.LoadPackageFile(&cfg, PkgFileName)
  1350  		if err != nil {
  1351  			return err
  1352  		}
  1353  
  1354  		queryStr := c.Args().Get(0)
  1355  		valueStr := c.Args().Get(1)
  1356  		var value interface{} = valueStr
  1357  		if c.Bool("in-json") {
  1358  			err = json.Unmarshal([]byte(valueStr), &value)
  1359  			if err != nil {
  1360  				return err
  1361  			}
  1362  		}
  1363  
  1364  		err = filter.Set(cfg, queryStr, value)
  1365  		if err != nil {
  1366  			return err
  1367  		}
  1368  
  1369  		return gx.SavePackageFile(cfg, PkgFileName)
  1370  	},
  1371  }
  1372  
  1373  var ReleaseCommand = cli.Command{
  1374  	Name:        "release",
  1375  	Usage:       "perform a release of a package",
  1376  	Description: `release updates the package version, publishes the package, and runs a configured release script.`,
  1377  	Action: func(c *cli.Context) error {
  1378  		if c.NArg() < 1 {
  1379  			log.Fatal("must specify release severity (major, minor, patch)")
  1380  		}
  1381  
  1382  		if !pm.ShellOnline() {
  1383  			return fmt.Errorf("ipfs daemon isn't running")
  1384  		}
  1385  
  1386  		pkg, err := LoadPackageFile(PkgFileName)
  1387  		if err != nil {
  1388  			return err
  1389  		}
  1390  
  1391  		err = updateVersion(pkg, c.Args().First())
  1392  		if err != nil {
  1393  			return err
  1394  		}
  1395  
  1396  		fmt.Printf("publishing package...\r")
  1397  		hash, err := doPublish(pkg)
  1398  		if err != nil {
  1399  			return err
  1400  		}
  1401  
  1402  		return runRelease(pkg, hash)
  1403  	},
  1404  }
  1405  
  1406  var TestCommand = cli.Command{
  1407  	Name:            "test",
  1408  	Usage:           "run package tests",
  1409  	Description:     `Runs a pre-test setup hook, the test command itself, and then a post-test cleanup hook.`,
  1410  	SkipFlagParsing: true,
  1411  	Action: func(c *cli.Context) error {
  1412  		pkg, err := LoadPackageFile(PkgFileName)
  1413  		if err != nil {
  1414  			return err
  1415  		}
  1416  
  1417  		err = gx.TryRunHook("pre-test", pkg.Language, pkg.SubtoolRequired)
  1418  		if err != nil {
  1419  			return err
  1420  		}
  1421  
  1422  		var testErr error
  1423  		if pkg.Test != "" {
  1424  			testErr = fmt.Errorf("don't support running custom test script yet, bug whyrusleeping")
  1425  		} else {
  1426  			testErr = gx.TryRunHook("test", pkg.Language, pkg.SubtoolRequired, c.Args()...)
  1427  		}
  1428  
  1429  		err = gx.TryRunHook("post-test", pkg.Language, pkg.SubtoolRequired)
  1430  		if err != nil {
  1431  			return err
  1432  		}
  1433  
  1434  		return testErr
  1435  	},
  1436  }
  1437  
  1438  func runRelease(pkg *gx.Package, hash string) error {
  1439  	if pkg.ReleaseCmd == "" {
  1440  		return nil
  1441  	}
  1442  
  1443  	cmd := exec.Command("sh", "-c", pkg.ReleaseCmd)
  1444  	cmd.Env = append(
  1445  		os.Environ(),
  1446  		"VERSION="+pkg.Version, // deprecated.
  1447  		"GX_VERSION="+pkg.Version,
  1448  		"GX_NAME="+pkg.Name,
  1449  		"GX_LANGUAGE="+pkg.Language,
  1450  		"GX_LICENSE="+pkg.License,
  1451  		"GX_AUTHOR="+pkg.Author,
  1452  		"GX_HASH="+hash,
  1453  	)
  1454  	cmd.Stderr = os.Stderr
  1455  	cmd.Stdout = os.Stdout
  1456  	cmd.Stdin = os.Stdin
  1457  
  1458  	return cmd.Run()
  1459  }