diff --git a/go.mod b/go.mod index 5b70674..6ab4929 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/cloudflare/circl v1.3.8 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dusted-go/logging v1.2.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect @@ -34,10 +35,12 @@ require ( github.com/samber/mo v1.11.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7b9f3a8..4da4f93 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dusted-go/logging v1.2.1 h1:FLRmtpbpVLuTUHLbkc4DXfVIVeZlZk7YlhD+uXkg464= +github.com/dusted-go/logging v1.2.1/go.mod h1:s58+s64zE5fxSWWZfp+b8ZV0CHyKHjamITGyuY1wzGg= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -89,6 +91,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/main.go b/main.go index 270eec2..e4d6b61 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dusted-go/logging/prettylog" + "github.com/spf13/afero" "io" "log/slog" "os" @@ -26,6 +28,8 @@ var nextRepo = nrRepo{url: `https://github.com/newrelic/newrelic-node-nextjs.git var columHeaders = map[string]string{"Name": `Package Name`, "MinSupportedVersion": `Minimum Supported Version`, "LatestVersion": `Latest Supported Version`, "MinAgentVersion": `Minimum Agent Version*`} +var appFS = afero.NewOsFs() + func main() { err := run(os.Args) if err != nil { @@ -60,36 +64,56 @@ func run(args []string) error { repos = []nrRepo{agentRepo, apolloRepo, nextRepo} } - repoChan := make(chan repoIterChan) - cloneWg := &sync.WaitGroup{} - cloneRepos(repos, repoChan, cloneWg) - go func() { - cloneWg.Wait() - close(repoChan) - }() + logger.Info("cloning repositories") + cloneResults := cloneRepos(repos, logger) + logger.Info("repository cloning complete") testDirs := make([]string, 0) - data := make([]ReleaseData, 0) - for repo := range repoChan { - if repo.err != nil { - logger.Error(repo.err.Error()) + for _, cloneResult := range cloneResults { + if cloneResult.Error != nil { + logger.Error(cloneResult.Error.Error()) continue } - var repoDir = repo.repoDir - var testDir = repo.testPath - versionedTestsDir := filepath.Join(repoDir, testDir) - fmt.Println("Adding test dir") + + versionedTestsDir := filepath.Join(cloneResult.Directory, cloneResult.TestDirectory) + logger.Debug("adding test dir", "dir", versionedTestsDir) testDirs = append(testDirs, versionedTestsDir) } + logger.Info("processing data") + data := processVersionedTestDirs(testDirs, logger) + logger.Info("data processing complete") + + for _, cloneResult := range cloneResults { + os.RemoveAll(cloneResult.Directory) + } + + slices.SortFunc(data, releaseDataSorter) + prunedData := pruneData(data) + switch flags.outputFormat.String() { + default: + renderAsAscii(prunedData, os.Stdout) + case "ascii": + renderAsAscii(prunedData, os.Stdout) + case "markdown": + renderAsMarkdown(prunedData, os.Stdout) + } + + return nil +} + +// processVersionedTestDirs iterates through all versioned test directories, +// looking for versioned `package.json` files, and processes what it finds +// into release data for each found supported module. +func processVersionedTestDirs(testDirs []string, logger *slog.Logger) []ReleaseData { wg := sync.WaitGroup{} - logger.Debug("Processing data ...") + results := make([]ReleaseData, 0) for _, versionedTestsDir := range testDirs { iterChan := make(chan dirIterChan) go iterateTestDir(versionedTestsDir, iterChan) - npm := NewNpmClient() + npm := NewNpmClient(WithLogger(logger)) for result := range iterChan { if result.err != nil { logger.Error(result.err.Error()) @@ -102,71 +126,45 @@ func run(args []string) error { logger.Debug(err.Error()) continue } - return err + + // TODO: this was a hard exit. How should we handle catastrophic errors? + logger.Error(err.Error()) + continue } - // TODO: handle errors better. Probably refactor into something like the dirIter goroutine for _, info := range pkgInfos { wg.Add(1) go func(info PkgInfo) { defer wg.Done() - logger.Debug("getting detailed package info", "package", info.Name) releaseData, err := buildReleaseData(info, npm) if err != nil { logger.Error(err.Error()) return } - data = append(data, *releaseData) + results = append(results, *releaseData) }(info) } } } wg.Wait() - for repo := range repoChan { - os.RemoveAll(repo.repoDir) - } - - slices.SortFunc(data, func(a ReleaseData, b ReleaseData) int { - if a.Name == b.Name { - return 0 - } - switch a.Name > b.Name { - case true: - return 1 - default: - return -1 - } - }) - prunedData := pruneData(data) - switch flags.outputFormat.String() { - default: - renderAsAscii(prunedData, os.Stdout) - case "ascii": - renderAsAscii(prunedData, os.Stdout) - case "markdown": - renderAsMarkdown(prunedData, os.Stdout) - } - - return nil + return results } func buildLogger(verbose bool) *slog.Logger { + dest := prettylog.WithDestinationWriter(os.Stderr) + level := slog.LevelInfo + if verbose == true { - return slog.New( - // TODO: replace with https://github.com/dusted-go/logging/issues/3 - slog.NewTextHandler( - os.Stderr, - &slog.HandlerOptions{Level: slog.LevelDebug}, - ), - ) + level = slog.LevelDebug } - return slog.New( - slog.NewTextHandler( - os.Stderr, - &slog.HandlerOptions{Level: slog.LevelError}, - ), + + handler := prettylog.New( + &slog.HandlerOptions{Level: level}, + dest, ) + + return slog.New(handler) } func buildReleaseData(info PkgInfo, npm *NpmClient) (*ReleaseData, error) { @@ -252,7 +250,8 @@ func iterateTestDir(dir string, iterChan chan dirIterChan) { close(iterChan) } -func readPackageJson(pkgJsonFile *os.File) (*VersionedTestPackageJson, error) { +// readPackageJson reads a file as a versioned `package.json`. +func readPackageJson(pkgJsonFile io.Reader) (*VersionedTestPackageJson, error) { data, err := io.ReadAll(pkgJsonFile) if err != nil { return nil, err @@ -267,47 +266,70 @@ func readPackageJson(pkgJsonFile *os.File) (*VersionedTestPackageJson, error) { return &vtpj, nil } -func cloneRepos(repos []nrRepo, repoChan chan repoIterChan, wg *sync.WaitGroup) { +// cloneRepos clones multiple repositories at once but does not return until +// all repositories have been cloned. +func cloneRepos(repos []nrRepo, logger *slog.Logger) []CloneRepoResult { + wg := sync.WaitGroup{} + result := make([]CloneRepoResult, 0) for _, repo := range repos { wg.Add(1) - go cloneRepo(repo, wg, repoChan) + go func(r nrRepo) { + defer wg.Done() + cloneResult := cloneRepo(r, logger) + result = append(result, cloneResult) + }(repo) } + wg.Wait() + return result } -func cloneRepo(repo nrRepo, wg *sync.WaitGroup, repoChan chan repoIterChan) { - defer wg.Done() +// cloneRepo clones a remote repository. If a local directory is specified +// in the repo description, then cloning is skipped and only a result object +// is returned. +func cloneRepo(repo nrRepo, logger *slog.Logger) CloneRepoResult { if repo.repoDir != "" { - repoChan <- repoIterChan{ - repoDir: repo.repoDir, - testPath: repo.testPath, + return CloneRepoResult{ + Directory: repo.repoDir, + TestDirectory: repo.testPath, } - return } - repoDir, err := os.MkdirTemp("", "newrelic") + repoDir, err := afero.TempDir(appFS, "", "newrelic") if err != nil { - repoChan <- repoIterChan{ - err: fmt.Errorf("failed to create temporary directory: %w", err), + return CloneRepoResult{ + Error: fmt.Errorf("failed to create temporary directory: %w", err), } - return } - fmt.Println("Cloning repository ...") + logger.Debug("cloning repo", "url", repo.url) _, err = git.PlainClone(repoDir, false, &git.CloneOptions{ URL: repo.url, ReferenceName: plumbing.ReferenceName(repo.branch), Depth: 1, }) if err != nil { - repoChan <- repoIterChan{ - err: fmt.Errorf("failed to clone repo `%s`: %w", repo.url, err), + return CloneRepoResult{ + Error: fmt.Errorf("failed to clone repo `%s`: %w", repo.url, err), } - return } - repoChan <- repoIterChan{ - repoDir: repoDir, - testPath: repo.testPath, + return CloneRepoResult{ + Directory: repoDir, + TestDirectory: repo.testPath, + } +} + +// releaseDataSorter is a sorting function for [ReleaseData]. It is meant to +// be used by [slices.SortFunc]. +func releaseDataSorter(a ReleaseData, b ReleaseData) int { + if a.Name == b.Name { + return 0 + } + switch a.Name > b.Name { + case true: + return 1 + default: + return -1 } } @@ -352,6 +374,10 @@ func renderAsAscii(data []ReleaseData, writer io.Writer) { io.WriteString(writer, outputTable.Render()) } +// renderAsMarkdown renders the collected data as a Markdown table. This is +// intended to be used when generating output to be embedded in one of docs +// locations (or maybe to be fed into pandoc to generate a PDF in order to +// email it to a customer). func renderAsMarkdown(data []ReleaseData, writer io.Writer) { outputTable := releaseDataToTable(data) io.WriteString( @@ -376,6 +402,8 @@ func renderAsMarkdown(data []ReleaseData, writer io.Writer) { ) } +// releaseDataToTable builds the tabular data structure from the discovered +// supported modules data. func releaseDataToTable(data []ReleaseData) table.Writer { outputTable := table.NewWriter() diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..0ddd0de --- /dev/null +++ b/main_test.go @@ -0,0 +1,262 @@ +package main + +import ( + "context" + "errors" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "path" + "strings" + "testing" +) + +var nilLogger = slog.New(slog.NewTextHandler(io.Discard, nil)) + +type logCollector struct { + logs []string +} + +func (lc *logCollector) Write(p []byte) (int, error) { + lc.logs = append(lc.logs, string(p)) + return len(p), nil +} + +// errorReader is an implementation of [io.Reader] that always +// returns an error on read. +type errorReader struct{} + +func (er *errorReader) Read([]byte) (int, error) { + return 0, errors.New("boom") +} + +func Test_buildLogger(t *testing.T) { + t.Run("returns debug level logger", func(t *testing.T) { + logger := buildLogger(true) + assert.Equal(t, true, logger.Handler().Enabled(context.TODO(), slog.LevelError)) + assert.Equal(t, true, logger.Handler().Enabled(context.TODO(), slog.LevelDebug)) + }) + + t.Run("returns standard logger", func(t *testing.T) { + logger := buildLogger(false) + assert.Equal(t, true, logger.Handler().Enabled(context.TODO(), slog.LevelInfo)) + assert.Equal(t, true, logger.Handler().Enabled(context.TODO(), slog.LevelError)) + assert.Equal(t, false, logger.Handler().Enabled(context.TODO(), slog.LevelDebug)) + }) +} + +func Test_processVersionedTestDirs(t *testing.T) { + t.Run("parses a versioned test dir", func(t *testing.T) { + collector := &logCollector{} + logger := slog.New(slog.NewJSONHandler(collector, &slog.HandlerOptions{Level: slog.LevelError})) + testDirs := []string{"testdata/versioned"} + + releaseData := processVersionedTestDirs(testDirs, logger) + assert.Equal(t, 0, len(collector.logs)) + assert.Equal(t, 3, len(releaseData)) + }) +} + +func Test_readPackageJson(t *testing.T) { + t.Run("errors for bad file reader", func(t *testing.T) { + data, err := readPackageJson(&errorReader{}) + assert.Nil(t, data) + assert.ErrorContains(t, err, "boom") + }) + + t.Run("errors for bad package data", func(t *testing.T) { + file, err := os.Open(path.Join("testdata", "bad-versioned-package.json")) + require.Nil(t, err) + data, err := readPackageJson(file) + assert.Nil(t, data) + assert.ErrorContains(t, err, "cannot unmarshal object into") + }) + + t.Run("reads a good file", func(t *testing.T) { + file, err := os.Open(path.Join("testdata", "latest-version.json")) + require.Nil(t, err) + + expected := &VersionedTestPackageJson{ + Name: "latest-range", + Targets: []Target{ + {Name: "foo", MinAgentVersion: "1.0.0"}, + }, + Version: "", + Private: false, + Tests: []TestDescription{ + { + Supported: true, + Comment: "", + Engines: EnginesBlock{}, + Dependencies: DependenciesBlock{ + "foo": DependencyBlock{ + Versions: "latest", + Samples: 0, + }, + }, + Files: nil, + }, + }, + } + data, err := readPackageJson(file) + assert.Nil(t, err) + assert.Equal(t, expected, data) + }) +} + +func Test_cloneRepos(t *testing.T) { + origFS := appFS + t.Cleanup(func() { + appFS = origFS + }) + + t.Run("clones multiple repos", func(t *testing.T) { + appFS = afero.NewMemMapFs() + repos := []nrRepo{ + {url: "testdata/bare-repo.git", testPath: "a"}, + {url: "testdata/bare-repo.git", testPath: "b"}, + } + results := cloneRepos(repos, nilLogger) + assert.Equal(t, 2, len(results)) + for _, result := range results { + assert.Nil(t, result.Error) + assert.Equal(t, true, strings.ContainsAny(result.TestDirectory, "ab")) + assert.Equal(t, true, strings.Contains(result.Directory, "/newrelic")) + } + }) +} + +func Test_cloneRepo(t *testing.T) { + origFS := appFS + t.Cleanup(func() { + appFS = origFS + }) + + t.Run("returns repo info if local repo dir provided", func(t *testing.T) { + repo := nrRepo{ + repoDir: "/foo/bar", + testPath: "versioned/tests", + } + result := cloneRepo(repo, nilLogger) + assert.Nil(t, result.Error) + assert.Equal(t, result.Directory, "/foo/bar") + assert.Equal(t, result.TestDirectory, "versioned/tests") + }) + + t.Run("returns error from creating temp dir", func(t *testing.T) { + appFS = afero.NewReadOnlyFs(afero.NewMemMapFs()) + repo := nrRepo{ + url: "https://git.example.com/foo", + branch: "main", + testPath: "test/versioned", + } + result := cloneRepo(repo, nilLogger) + assert.NotNil(t, result.Error) + assert.ErrorContains(t, result.Error, "failed to create temporary directory") + }) + + t.Run("returns error for bad remote response", func(t *testing.T) { + appFS = afero.NewMemMapFs() + ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(500) + res.Write([]byte("bad")) + })) + defer ts.Close() + + repo := nrRepo{ + url: ts.URL, + branch: "main", + testPath: "test/versioned", + } + result := cloneRepo(repo, nilLogger) + assert.NotNil(t, result.Error) + assert.ErrorContains(t, result.Error, "unexpected client error") + }) + + t.Run("clones repo into temp dir", func(t *testing.T) { + appFS = afero.NewMemMapFs() + repo := nrRepo{ + url: "testdata/bare-repo.git", + branch: "main", + testPath: "test/versioned", + } + result := cloneRepo(repo, nilLogger) + assert.Nil(t, result.Error) + assert.Equal(t, true, strings.Contains(result.Directory, "/newrelic")) + assert.Equal(t, "test/versioned", result.TestDirectory) + }) +} + +func Test_releaseDataSorter(t *testing.T) { + a := ReleaseData{Name: "same"} + b := ReleaseData{Name: "same"} + assert.Equal(t, 0, releaseDataSorter(a, b)) + + a = ReleaseData{Name: "second"} + b = ReleaseData{Name: "first"} + assert.Equal(t, 1, releaseDataSorter(a, b)) + + a = ReleaseData{Name: "first"} + b = ReleaseData{Name: "second"} + assert.Equal(t, -1, releaseDataSorter(a, b)) +} + +func Test_pruneData(t *testing.T) { + // Short circuits for a single element. + input := []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + expected := []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + assert.Equal(t, expected, pruneData(input)) + + // Drops a literal duplicate. + input = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + expected = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + assert.Equal(t, expected, pruneData(input)) + + // Picks first one. + input = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + {Name: "foo", MinSupportedVersion: "2.0.0"}, + } + expected = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + assert.Equal(t, expected, pruneData(input)) + + // Picks second one. + input = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "2.0.0"}, + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + expected = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + } + assert.Equal(t, expected, pruneData(input)) + + // All-in-one. + input = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "2.0.0"}, + {Name: "foo", MinSupportedVersion: "1.0.0"}, + {Name: "bar", MinSupportedVersion: "1.0.0"}, + {Name: "baz", MinSupportedVersion: "3.0.0"}, + } + expected = []ReleaseData{ + {Name: "foo", MinSupportedVersion: "1.0.0"}, + {Name: "bar", MinSupportedVersion: "1.0.0"}, + {Name: "baz", MinSupportedVersion: "3.0.0"}, + } + assert.Equal(t, expected, pruneData(input)) +} diff --git a/npm.go b/npm.go index bf62343..320a1f9 100644 --- a/npm.go +++ b/npm.go @@ -4,12 +4,15 @@ import ( "encoding/json" "fmt" "github.com/jsumners/go-rfc3339" + "io" + "log/slog" "net/http" "strings" ) type NpmClient struct { baseUrl string + log *slog.Logger http *http.Client } @@ -34,6 +37,7 @@ func NewNpmClient(options ...NpmClientOption) *NpmClient { client := &NpmClient{ baseUrl: "https://registry.npmjs.com", http: http.DefaultClient, + log: slog.New(slog.NewTextHandler(io.Discard, nil)), } for _, opt := range options { @@ -58,9 +62,16 @@ func WithHttpClient(c *http.Client) NpmClientOption { } } +func WithLogger(logger *slog.Logger) NpmClientOption { + return func(client *NpmClient) { + client.log = logger + } +} + // GetDetailedInfo gets the full detailed information about a package from the // NPM registry. func (nc *NpmClient) GetDetailedInfo(packageName string) (*NpmDetailedPackage, error) { + nc.log.Debug("getting detailed info for " + packageName) req, err := http.NewRequest( http.MethodGet, fmt.Sprintf("%s/%s", nc.baseUrl, packageName), @@ -91,6 +102,7 @@ func (nc *NpmClient) GetDetailedInfo(packageName string) (*NpmDetailedPackage, e // GetLatest retrieves the latest version string for the given package. func (nc *NpmClient) GetLatest(packageName string) (string, error) { + nc.log.Debug("getting latest version for " + packageName) req, err := http.NewRequest( http.MethodGet, fmt.Sprintf("%s/%s/latest", nc.baseUrl, packageName), diff --git a/testdata/Readme.md b/testdata/Readme.md new file mode 100644 index 0000000..84bc2ea --- /dev/null +++ b/testdata/Readme.md @@ -0,0 +1,13 @@ +## bare-repo.git + +This is a bare Git repository used for testing repo cloning. To add/remove +files in it: + +```sh +$ git clone bare-repo.git foo-repo +$ cd foo-repo +# make changes +$ git add . +$ git commit -m 'whatever' +$ cd .. && rm -rf foo-repo +``` diff --git a/testdata/bad-versioned-package.json b/testdata/bad-versioned-package.json new file mode 100644 index 0000000..acff0c4 --- /dev/null +++ b/testdata/bad-versioned-package.json @@ -0,0 +1,7 @@ +{ + "name": "latest-range", + "targets": [{ "name": "foo", "minAgentVersion": "1.0.0" }], + "tests": { + "should-be": "an array of objects" + } +} diff --git a/testdata/bare-repo.git/HEAD b/testdata/bare-repo.git/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/testdata/bare-repo.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/testdata/bare-repo.git/config b/testdata/bare-repo.git/config new file mode 100644 index 0000000..e6da231 --- /dev/null +++ b/testdata/bare-repo.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/testdata/bare-repo.git/description b/testdata/bare-repo.git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/testdata/bare-repo.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/testdata/bare-repo.git/objects/05/27e6bd2d76b45e2933183f1b506c7ac49f5872 b/testdata/bare-repo.git/objects/05/27e6bd2d76b45e2933183f1b506c7ac49f5872 new file mode 100644 index 0000000..a8dc1c0 Binary files /dev/null and b/testdata/bare-repo.git/objects/05/27e6bd2d76b45e2933183f1b506c7ac49f5872 differ diff --git a/testdata/bare-repo.git/objects/64/b394399d7c778ba5e4837b3b5b9dd3cf208004 b/testdata/bare-repo.git/objects/64/b394399d7c778ba5e4837b3b5b9dd3cf208004 new file mode 100644 index 0000000..ba75ed2 Binary files /dev/null and b/testdata/bare-repo.git/objects/64/b394399d7c778ba5e4837b3b5b9dd3cf208004 differ diff --git a/testdata/bare-repo.git/objects/6e/bbd34220dbd45b5b95d6fe1c4d0db138aac084 b/testdata/bare-repo.git/objects/6e/bbd34220dbd45b5b95d6fe1c4d0db138aac084 new file mode 100644 index 0000000..d353c12 Binary files /dev/null and b/testdata/bare-repo.git/objects/6e/bbd34220dbd45b5b95d6fe1c4d0db138aac084 differ diff --git a/testdata/bare-repo.git/refs/heads/main b/testdata/bare-repo.git/refs/heads/main new file mode 100644 index 0000000..b626dab --- /dev/null +++ b/testdata/bare-repo.git/refs/heads/main @@ -0,0 +1 @@ +64b394399d7c778ba5e4837b3b5b9dd3cf208004 diff --git a/testdata/versioned/restify/restify-post-7/package.json b/testdata/versioned/restify/restify-post-7/package.json deleted file mode 100644 index 3c44ace..0000000 --- a/testdata/versioned/restify/restify-post-7/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "restify-tests", - "version": "0.0.0", - "private": true, - "tests": [ - { - "engines": { - "node": ">=16 < 18" - }, - "dependencies": { - "restify": ">=7.0.0", - "express": "4.16", - "restify-errors": "6.1" - }, - "files": [ - "capture-params.tap.js", - "ignoring.tap.js", - "restify.tap.js", - "rum.tap.js", - "router.tap.js", - "transaction-naming.tap.js", - "with-express.tap.js" - ] - }, - { - "engines": { - "node": ">=18" - }, - "dependencies": { - "restify": ">=10.0.0", - "express": "4.16", - "restify-errors": "6.1" - }, - "files": [ - "async-handlers.tap.js", - "capture-params.tap.js", - "ignoring.tap.js", - "restify.tap.js", - "rum.tap.js", - "router.tap.js", - "transaction-naming.tap.js", - "with-express.tap.js" - ] - } - ], - "dependencies": { - "express": "4.16", - "restify-errors": "6.1" - } -} diff --git a/testdata/versioned/restify/restify-pre-7/package.json b/testdata/versioned/restify/restify-pre-7/package.json deleted file mode 100644 index ada8c84..0000000 --- a/testdata/versioned/restify/restify-pre-7/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "restify-tests", - "version": "0.0.0", - "private": true, - "tests": [ - { - "engines": { - "node": ">=16" - }, - "dependencies": { - "restify": ">=5.0.0 <7", - "express": "4.16", - "restify-errors": "6.1" - }, - "files": [ - "capture-params.tap.js", - "ignoring.tap.js", - "restify.tap.js", - "router.tap.js", - "rum.tap.js", - "transaction-naming.tap.js" - ] - } - ], - "dependencies": { - "express": "4.16", - "restify-errors": "6.1" - } -} diff --git a/types.go b/types.go index 1126725..59265b6 100644 --- a/types.go +++ b/types.go @@ -26,6 +26,21 @@ type repoIterChan struct { err error } +// CloneRepoResult represents the status of Git repository clone operation. +type CloneRepoResult struct { + // Directory is the path on the file system that contains the cloned + // repository. + Directory string + + // TestDirectory is a string relative to Directory that contains the + // versioned tests for the repository. + TestDirectory string + + // Error indicates if there was some problem during the clone operation. + // Should be `nil` for success results. + Error error +} + // ReleaseData represents a row of information about a package. Specifically, // it's the final computed information to be rendered into documents. type ReleaseData struct {