From 28bea6920a9398e2222cab6050e60c8bf587e6f3 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 2 Oct 2022 14:26:06 +0200 Subject: [PATCH 1/2] feat(preserve): [#24] Nexus3 repository. --- .dockerignore | 3 +- .github/workflows/go.yml | 10 +- .gitignore | 14 +- README.md | 51 +- cmd/yaam/main.go | 10 +- cmd/yaam/main_test.go | 515 +++--------------- configs/.golangci.yml | 64 +++ docs/config/MAVEN.md | 2 +- docs/config/NPM.md | 2 +- docs/other/BACKGROUND.md | 17 + internal/{ => app/yaam}/api/api.go | 0 internal/{ => app/yaam}/api/api_test.go | 0 internal/app/yaam/artifact/artifact.go | 23 + internal/app/yaam/artifact/artifact_test.go | 38 ++ internal/{ => app/yaam}/artifact/generic.go | 0 .../yaam/artifact/generic_test.go} | 0 internal/{ => app/yaam}/artifact/interface.go | 0 internal/{ => app/yaam}/artifact/maven.go | 7 +- internal/app/yaam/artifact/maven_test.go | 1 + internal/app/yaam/artifact/npm.go | 264 +++++++++ internal/app/yaam/artifact/npm_test.go | 61 +++ internal/artifact/artifact.go | 37 -- internal/artifact/artifact_test.go | 49 -- internal/artifact/npm.go | 215 -------- internal/pkg/artifact/artifact.go | 63 ++- internal/pkg/artifact/artifact_test.go | 13 + internal/pkg/artifact/validate.go | 17 +- internal/pkg/artifact/validate_test.go | 43 +- internal/pkg/file/file.go | 28 +- internal/pkg/file/file_test.go | 1 + internal/pkg/project/project.go | 31 +- internal/pkg/project/project_test.go | 16 + internal/pkg/yaamtest/config.go | 75 +++ internal/pkg/yaamtest/config_test.go | 1 + internal/pkg/yaamtest/generic.go | 81 +++ internal/pkg/yaamtest/generic_test.go | 1 + internal/pkg/yaamtest/maven.go | 162 ++++++ internal/pkg/yaamtest/maven_test.go | 1 + internal/pkg/yaamtest/npm.go | 42 ++ internal/pkg/yaamtest/npm_test.go | 1 + internal/pkg/yaamtest/status.go | 36 ++ internal/pkg/yaamtest/status_test.go | 1 + test/README.md | 3 - test/{ => testdata}/gradle/README.md | 0 test/{ => testdata}/gradle/demo/.gitignore | 0 .../demo/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 test/{ => testdata}/gradle/demo/gradlew | 0 test/{ => testdata}/gradle/demo/gradlew.bat | 0 .../com/example/demo/DemoApplication.java | 0 .../src/main/resources/application.properties | 0 .../example/demo/DemoApplicationTests.java | 0 test/{ => testdata}/npm/README.md | 0 test/{ => testdata}/npm/demo/.gitignore | 0 test/{ => testdata}/npm/demo/README.md | 0 test/{ => testdata}/npm/demo/package.json | 0 .../npm/demo/public/favicon.ico | Bin .../{ => testdata}/npm/demo/public/index.html | 0 .../npm/demo/public/logo192.png | Bin .../npm/demo/public/logo512.png | Bin .../npm/demo/public/manifest.json | 0 .../{ => testdata}/npm/demo/public/robots.txt | 0 test/{ => testdata}/npm/demo/src/App.css | 0 test/{ => testdata}/npm/demo/src/App.js | 0 test/{ => testdata}/npm/demo/src/App.test.js | 0 test/{ => testdata}/npm/demo/src/index.css | 0 test/{ => testdata}/npm/demo/src/index.js | 0 test/{ => testdata}/npm/demo/src/logo.svg | 0 .../npm/demo/src/reportWebVitals.js | 0 .../{ => testdata}/npm/demo/src/setupTests.js | 0 test/testdata/npm/y18n.tmp | 1 + test/testdata/npm/y18n/-/y18n-5.0.8.tgz | Bin 0 -> 6157 bytes 72 files changed, 1136 insertions(+), 864 deletions(-) create mode 100644 configs/.golangci.yml rename internal/{ => app/yaam}/api/api.go (100%) rename internal/{ => app/yaam}/api/api_test.go (100%) create mode 100644 internal/app/yaam/artifact/artifact.go create mode 100644 internal/app/yaam/artifact/artifact_test.go rename internal/{ => app/yaam}/artifact/generic.go (100%) rename internal/{artifact/maven_test.go => app/yaam/artifact/generic_test.go} (100%) rename internal/{ => app/yaam}/artifact/interface.go (100%) rename internal/{ => app/yaam}/artifact/maven.go (91%) create mode 100644 internal/app/yaam/artifact/maven_test.go create mode 100644 internal/app/yaam/artifact/npm.go create mode 100644 internal/app/yaam/artifact/npm_test.go delete mode 100644 internal/artifact/artifact.go delete mode 100644 internal/artifact/artifact_test.go delete mode 100644 internal/artifact/npm.go create mode 100644 internal/pkg/file/file_test.go create mode 100644 internal/pkg/project/project_test.go create mode 100644 internal/pkg/yaamtest/config.go create mode 100644 internal/pkg/yaamtest/config_test.go create mode 100644 internal/pkg/yaamtest/generic.go create mode 100644 internal/pkg/yaamtest/generic_test.go create mode 100644 internal/pkg/yaamtest/maven.go create mode 100644 internal/pkg/yaamtest/maven_test.go create mode 100644 internal/pkg/yaamtest/npm.go create mode 100644 internal/pkg/yaamtest/npm_test.go create mode 100644 internal/pkg/yaamtest/status.go create mode 100644 internal/pkg/yaamtest/status_test.go delete mode 100644 test/README.md rename test/{ => testdata}/gradle/README.md (100%) rename test/{ => testdata}/gradle/demo/.gitignore (100%) rename test/{ => testdata}/gradle/demo/gradle/wrapper/gradle-wrapper.jar (100%) rename test/{ => testdata}/gradle/demo/gradle/wrapper/gradle-wrapper.properties (100%) rename test/{ => testdata}/gradle/demo/gradlew (100%) rename test/{ => testdata}/gradle/demo/gradlew.bat (100%) rename test/{ => testdata}/gradle/demo/src/main/java/com/example/demo/DemoApplication.java (100%) rename test/{ => testdata}/gradle/demo/src/main/resources/application.properties (100%) rename test/{ => testdata}/gradle/demo/src/test/java/com/example/demo/DemoApplicationTests.java (100%) rename test/{ => testdata}/npm/README.md (100%) rename test/{ => testdata}/npm/demo/.gitignore (100%) rename test/{ => testdata}/npm/demo/README.md (100%) rename test/{ => testdata}/npm/demo/package.json (100%) rename test/{ => testdata}/npm/demo/public/favicon.ico (100%) rename test/{ => testdata}/npm/demo/public/index.html (100%) rename test/{ => testdata}/npm/demo/public/logo192.png (100%) rename test/{ => testdata}/npm/demo/public/logo512.png (100%) rename test/{ => testdata}/npm/demo/public/manifest.json (100%) rename test/{ => testdata}/npm/demo/public/robots.txt (100%) rename test/{ => testdata}/npm/demo/src/App.css (100%) rename test/{ => testdata}/npm/demo/src/App.js (100%) rename test/{ => testdata}/npm/demo/src/App.test.js (100%) rename test/{ => testdata}/npm/demo/src/index.css (100%) rename test/{ => testdata}/npm/demo/src/index.js (100%) rename test/{ => testdata}/npm/demo/src/logo.svg (100%) rename test/{ => testdata}/npm/demo/src/reportWebVitals.js (100%) rename test/{ => testdata}/npm/demo/src/setupTests.js (100%) create mode 100644 test/testdata/npm/y18n.tmp create mode 100644 test/testdata/npm/y18n/-/y18n-5.0.8.tgz diff --git a/.dockerignore b/.dockerignore index c857885..f439335 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1 @@ -test/gradle/demo -test/npm/demo +test/testdata diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bdd95e8..87d23bf 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -4,7 +4,6 @@ name: Go jobs: build: runs-on: ubuntu-20.04 - timeout-minutes: 25 steps: - uses: actions/checkout@v2 - name: Set up Go @@ -19,8 +18,9 @@ jobs: with: node-version: 14 - name: Unit tests + timeout-minutes: 20 run: | - go test -short -cover -v -coverprofile=coverage.txt \ + go test -timeout=20m -short -cover -v -coverprofile=coverage.txt \ -covermode=atomic ./... - uses: codecov/codecov-action@v1 with: @@ -33,9 +33,9 @@ jobs: args: > -Dsonar.organization=030-github -Dsonar.projectKey=030_yaam - -Dsonar.exclusions=internal/goswagger/**,test/gradle/demo/**,test/npm/demo/** + -Dsonar.exclusions=test/testdata/** -Dsonar.sources=. - -Dsonar.coverage.exclusions=**/*_test.go,internal/goswagger/**/*,test/gradle/demo/**,test/npm/demo/** + -Dsonar.coverage.exclusions=**/*_test.go,test/testdata/** -Dsonar.verbose=true -Dsonar.go.coverage.reportPaths="coverage.txt" env: @@ -57,4 +57,4 @@ jobs: README.md -s /data/configs/.markdownlint.rb docker run --rm -v $(pwd):/app -w /app -e GOFLAGS=-buildvcs=false \ golangci/golangci-lint:v1.49.0-alpine golangci-lint run -v \ - --timeout 2m30s + --timeout 2m30s --config configs/.golangci.yml diff --git a/.gitignore b/.gitignore index 8b70038..dafe271 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,11 @@ dip cmd/yaam/yaam -test/gradle/demo/.gradle -test/gradle/demo/build -test/gradle/demo/build.gradle -test/gradle/demo/settings.gradle +test/testdata/gradle/demo/.gradle +test/testdata/gradle/demo/build +test/testdata/gradle/demo/build.gradle +test/testdata/gradle/demo/settings.gradle -test/npm/demo/node_modules -test/npm/demo/.npmrc -test/npm/demo/package-lock.json +test/testdata/npm/demo/node_modules +test/testdata/npm/demo/.npmrc +test/testdata/npm/demo/package-lock.json diff --git a/README.md b/README.md index 0949e16..cd5fdf3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,56 @@ of artifact types. Yet Another Artifact Manager (YAAM): preserves NPM and Maven packages from public repositories and unifies Maven repositories. -## Configuration +## Quickstart + +Configure YAAM by creating a `~/.yaam/config.yml` with the following content: + +```bash +--- +caches: + maven: + 3rdparty-maven: + url: https://repo.maven.apache.org/maven2/ + 3rdparty-maven-gradle-plugins: + url: https://plugins.gradle.org/m2/ + 3rdparty-maven-spring: + url: https://repo.spring.io/release/ + other-nexus-repo-releases: + url: https://some-nexus/repository/some-repo/ + user: some-user + pass: some-pass + npm: + 3rdparty-npm: + url: https://registry.npmjs.org/ +groups: + maven: + hello: + - maven/releases + - maven/3rdparty-maven + - maven/3rdparty-maven-gradle-plugins + - maven/3rdparty-maven-spring +publications: + generic: + - something + maven: + - releases + npm: + - 3rdparty-npm +``` + +Start YAAM: + +```bash +mkdir ~/.yaam/repositories +sudo chown 9999 -R ~/.yaam/repositories +docker run -e YAAM_USER=hello -e YAAM_PASS=world --rm --name=yaam \ +-it -v /home/${USER}/.yaam:/opt/yaam/.yaam -p 25213:25213 yaam +``` + +Configure the URLs in the build.gradle and settings.gradle and run +./gradlew clean + +Configure a .npmrc in an NPM project and run `npm i -d`. ### General diff --git a/cmd/yaam/main.go b/cmd/yaam/main.go index fa1ebb7..a40b80d 100644 --- a/cmd/yaam/main.go +++ b/cmd/yaam/main.go @@ -7,8 +7,8 @@ import ( "os" "time" - "github.com/030/yaam/internal/api" - "github.com/030/yaam/internal/artifact" + "github.com/030/yaam/internal/app/yaam/api" + "github.com/030/yaam/internal/app/yaam/artifact" "github.com/030/yaam/internal/pkg/project" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" @@ -128,7 +128,7 @@ func npmArtifact(w http.ResponseWriter, r *http.Request) { return } - n := artifact.Maven{RequestBody: r.Body, RequestURI: r.RequestURI, ResponseWriter: w} + n := artifact.Npm{RequestBody: r.Body, RequestURI: r.RequestURI, ResponseWriter: w} if r.Method == "POST" { var p artifact.Publisher = n if err := p.Publish(); err != nil { @@ -191,6 +191,10 @@ func main() { Handler: r, // Pass our instance of gorilla/mux in. } + if err := project.Config(); err != nil { + log.Fatal(err) + } + log.Infof("Starting YAAM version: '%s' on localhost on port: '%d'...", Version, project.Port) if err := srv.ListenAndServe(); err != nil { log.Fatal(err) diff --git a/cmd/yaam/main_test.go b/cmd/yaam/main_test.go index 6cdc7f6..cc7f131 100644 --- a/cmd/yaam/main_test.go +++ b/cmd/yaam/main_test.go @@ -2,396 +2,131 @@ package main import ( "bytes" - "errors" "fmt" "io" - "net/http" + "log" "os" - "os/exec" - "path/filepath" - "strings" "testing" - "time" "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/pkg/yaamtest" "github.com/tj/assert" ) const ( - allowedReposGeneric = `allowedRepos: - - something` - allowedReposMaven = `allowedRepos: - - releases` - allowedReposNpm = `allowedRepos: - - 3rdparty-npm` - gradleHomeDemoProject = "../../test/gradle/demo" - npmHomeDemoProject = "../../test/npm/demo" - - caches = `mavenReposAndUrls: - 3rdparty-maven: https://repo.maven.apache.org/maven2/ - 3rdparty-maven-gradle-plugins: https://plugins.gradle.org/m2/ - 3rdparty-maven-spring: https://repo.spring.io/release/ - 3rdparty-npm: https://registry.npmjs.org/` - groups = `groups: - hello: - - maven/releases - - maven/3rdparty-maven - - maven/3rdparty-maven-gradle-plugins - - maven/3rdparty-maven-spring` - cmdExitErrMsg = "%v, err: '%v'" - mavenReleasesRepo = "maven/releases" - testDir = "/tmp/yaam" - testDirGradle = testDir + "/gradle" - testDirNpm = testDir + "/npm" - testDirIso = testDir + "/ubuntu.iso" - testDirIsoDownloaded = testDir + "/downloaded-ubuntu.iso" -) - -var ( - mavenRepos = []string{"maven/3rdparty-maven", "maven/3rdparty-maven-gradle-plugins", "maven/3rdparty-maven-spring", mavenReleasesRepo} - npmrc = `registry=` + project.Url + `/npm/3rdparty-npm/ - always-auth=true - _auth=aGVsbG86d29ybGQ=` + genericUri = "/generic/something/some.iso" + genericUriFail = "/generic/something2/some.iso" ) -func testConfigHelper() error { - os.Setenv("YAAM_HOME", filepath.Join("/tmp", "yaam", "test"+time.Now().Format("20060102150405111"))) - dir := filepath.Join(os.Getenv("YAAM_HOME"), "conf") - reposDir := filepath.Join(dir, "repositories") - if err := os.MkdirAll(reposDir, os.ModePerm); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(dir, "caches.yaml"), []byte(caches), 0600); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(dir, "groups.yaml"), []byte(groups), 0600); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(reposDir, "generic.yaml"), []byte(allowedReposGeneric), 0600); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(reposDir, "maven.yaml"), []byte(allowedReposMaven), 0600); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(reposDir, "npm.yaml"), []byte(allowedReposNpm), 0600); err != nil { - return err - } - - os.Setenv("YAAM_DEBUG", "true") - os.Setenv("YAAM_USER", "hello") - os.Setenv("YAAM_PASS", "world") - - return nil -} - -func testNpmConfigHelper() (int, error) { - os.Setenv("YAAM_PASS", "world") - - if err := os.RemoveAll(filepath.Join(npmHomeDemoProject, "node_modules")); err != nil { - return 1, err - } - packageLockJson := filepath.Join(npmHomeDemoProject, "package-lock.json") - if _, err := os.Stat(packageLockJson); err == nil { - if err := os.Remove(packageLockJson); err != nil { - return 1, err - } - } - - npmrcWithCacheLocation := npmrc + ` -cache=` + testDirNpm + `/cache` + time.Now().Format("20060102150405111") + `` - if err := os.WriteFile(filepath.Join(npmHomeDemoProject, ".npmrc"), []byte(npmrcWithCacheLocation), 0600); err != nil { - return 1, err - } - - cmd := exec.Command("bash", "-c", "npm cache clean --force && npm i") - cmd.Dir = npmHomeDemoProject - co, err := cmd.CombinedOutput() - if err != nil { - return cmd.ProcessState.ExitCode(), fmt.Errorf(cmdExitErrMsg, string(co), err) - } - - return 0, nil -} - func init() { - if err := testConfigHelper(); err != nil { - panic(err) + if err := yaamtest.Config(); err != nil { + log.Fatal(err) } go main() } -func testMainGradleFile(content []byte, name string) error { - if err := os.WriteFile(filepath.Join(gradleHomeDemoProject, name+".gradle"), content, 0600); err != nil { - return err - } - return nil -} - -func testMainGradleCleanBuildHelper(pass string) (int, error) { - os.Setenv("YAAM_PASS", pass) - - os.Setenv("GRADLE_USER_HOME", testDirGradle+time.Now().Format("20060102150405111")) - cmd := exec.Command("bash", "-c", "./gradlew clean build --no-daemon") - cmd.Dir = gradleHomeDemoProject - co, err := cmd.CombinedOutput() +func TestStatus(t *testing.T) { + b, err := yaamtest.StatusHelper("GET", "/status", nil, 10) if err != nil { - return cmd.ProcessState.ExitCode(), fmt.Errorf(cmdExitErrMsg, string(co), err) + t.Error(err) } - return 0, nil + assert.NoError(t, err) + assert.Equal(t, "ok", b) } -func testMainGradlePublishHelper(repo string) (int, error) { - if err := testGradleBuildFileHelper(mavenRepos, repo); err != nil { - return 1, err - } - if err := testGradleSettingsFileHelper(mavenRepos); err != nil { - return 1, err - } - - exitCode, err := testMainGradleCleanBuildHelper("world") - if err != nil { - return exitCode, err - } - - cmd := exec.Command("bash", "-c", "./gradlew publish --no-daemon") - cmd.Dir = gradleHomeDemoProject - co, err := cmd.CombinedOutput() +// NPM: Preserve NPM artifacts by running `npm i` in a demo project. +func TestMainNpmBuild(t *testing.T) { + exitCode, err := yaamtest.NpmConfig() if err != nil { - return cmd.ProcessState.ExitCode(), fmt.Errorf(cmdExitErrMsg, string(co), err) + t.Error(err) + return } - return 0, nil + assert.NoError(t, err) + assert.Equal(t, 0, exitCode) } -func testGradleMavenRepositoriesFileHelper(repos []string) string { - var sb strings.Builder - for _, repo := range repos { - content := ` - maven { - allowInsecureProtocol true - url '` + project.Url + `/` + repo + `/' - authentication { - basic(BasicAuthentication) - } - credentials { - username "hello" - password "world" - } - }` - sb.WriteString(content) +/* +Maven: preserve artifacts by using the unify option that ensures that multiple +Maven repositories are grouped and only and endpoint has to be accessed and +subsequently publish a Maven artifact. +- GradleCleanBuildGroup +- GradleCleanBuildGroupFail +- GradlePublish +- GradlePublishFail +*/ +func TestGradleCleanBuildGroup(t *testing.T) { + repos := []string{"maven/groups/hello"} + if err := yaamtest.GradleBuildFile(repos, yaamtest.MavenReleasesRepo); err != nil { + t.Error(err) } - return sb.String() -} - -func testGradlePublishingFileHelper(repo string) string { - repos := []string{repo} - content := ` -publishing { - publications { - mavenJava(MavenPublication) { - versionMapping { - usage('java-api') { - fromResolutionOf('runtimeClasspath') - } - usage('java-runtime') { - fromResolutionResult() - } - } - } - } - - repositories {` + - testGradleMavenRepositoriesFileHelper(repos) + ` - } -} -` - return content -} - -func testGradleBuildFileHelper(repos []string, repoPublish string) error { - content := ` -plugins { - id 'org.springframework.boot' version '2.7.3' - id 'io.spring.dependency-management' version '1.0.13.RELEASE' - id 'java' - id 'maven-publish' -} - -group = 'com.example' -version = '0.0.1-SNAPSHOT' -sourceCompatibility = '17' - -repositories {` + - testGradleMavenRepositoriesFileHelper(repos) + ` -} - -` + testGradlePublishingFileHelper(repoPublish) + ` - -dependencies { - implementation 'org.springframework.boot:spring-boot-starter' - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} - -tasks.named('test') { - useJUnitPlatform() -} -` - - if err := testMainGradleFile([]byte(content), "build"); err != nil { - return err + if err := yaamtest.GradleSettingsFile(repos); err != nil { + t.Error(err) } - return nil -} - -func testGradleSettingsFileHelper(repos []string) error { - content := ` -pluginManagement { - repositories {` + - testGradleMavenRepositoriesFileHelper(repos) + ` - } -} -rootProject.name = 'demo' -` + exitCode, err := yaamtest.GradleCleanBuild("world") - if err := testMainGradleFile([]byte(content), "settings"); err != nil { - return err - } - return nil + assert.NoError(t, err) + assert.Equal(t, 0, exitCode) } -func testStatusHelper(method, pass, uri string, body io.Reader, timeout time.Duration) (string, error) { - client := &http.Client{ - Timeout: time.Second * timeout, - } - req, err := http.NewRequest(method, project.Url+uri, body) - if err != nil { - return "", err +func TestGradleCleanBuildGroupFail(t *testing.T) { + repos := []string{"maven/groups/helloworld"} + if err := yaamtest.GradleBuildFile(repos, yaamtest.MavenReleasesRepo); err != nil { + t.Error(err) } - req.SetBasicAuth("hello", pass) - resp, err := client.Do(req) - if err != nil { - return "", err + if err := yaamtest.GradleSettingsFile(yaamtest.MavenRepos); err != nil { + t.Error(err) } - defer func() { - if err := resp.Body.Close(); err != nil { - panic(err) - } - }() - b, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } + exitCode, err := yaamtest.GradleCleanBuild("world") - return string(b), nil + assert.Regexp(t, `Could not GET '`+project.Url+`/maven/groups/helloworld/.*'. Received status code 500 from server: Internal Server Error`, err) + assert.Equal(t, 1, exitCode) } -func testGenericArtifactHelper() error { - if _, err := os.Stat(testDirIso); errors.Is(err, os.ErrNotExist) { - resp, err := http.Get("https://releases.ubuntu.com/22.04.1/ubuntu-22.04.1-desktop-amd64.iso") - if err != nil { - return err - } - defer func() { - if err := resp.Body.Close(); err != nil { - panic(err) - } - }() +func TestGradlePublish(t *testing.T) { + exitCode, err := yaamtest.GradlePublish(yaamtest.MavenReleasesRepo) - f, err := os.Create(testDirIso) - if err != nil { - return err - } - defer func() { - if err := f.Close(); err != nil { - panic(err) - } - }() - _, err = io.Copy(f, resp.Body) - if err != nil { - return err - } - if err := f.Sync(); err != nil { - return err - } - } - - return nil + assert.NoError(t, err) + assert.Equal(t, 0, exitCode) } -func testGenericArtifactReqHelper(method, uri string, body io.Reader) (*http.Response, error) { - req, err := http.NewRequest(method, project.Url+uri, body) - if err != nil { - return nil, err - } - req.SetBasicAuth("hello", "world") +func TestGradlePublishFail(t *testing.T) { + exitCode, err := yaamtest.GradlePublish("maven/releases-non-existent") - client := &http.Client{ - Timeout: time.Second * 120, - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - return resp, nil -} - -func testGenericArtifactWriteOnDiskHelper(resp *http.Response) (int64, error) { - f, err := os.Create(testDirIsoDownloaded) - if err != nil { - return 0, err - } - defer func() { - if err := f.Close(); err != nil { - panic(err) - } - }() - w, err := io.Copy(f, resp.Body) - if err != nil { - return 0, err - } - if err := f.Sync(); err != nil { - return 0, err - } - return w, err + assert.Regexp(t, `Could not PUT '`+project.Url+`/maven/releases-non-existent/com/example/demo/0.0.1-SNAPSHOT/maven-metadata.xml'. Received status code 500 from server: Internal Server Error`, err) + assert.Equal(t, 1, exitCode) } -func TestGenericArtifact(t *testing.T) { - // Upload - if err := testGenericArtifactHelper(); err != nil { +/* +Generic: upload an Ubuntu.iso and download it. +- Upload +- UploadFail +- Download +- DownloadFail +*/ +func TestGenericArtifactUpload(t *testing.T) { + if err := yaamtest.GenericArtifact(); err != nil { t.Error(err) } - uri := "/generic/something/some.iso" - b, err := os.ReadFile(testDirIso) + + b, err := os.ReadFile(yaamtest.DirIso) if err != nil { t.Error(err) } r := bytes.NewReader(b) - _, err = testGenericArtifactReqHelper("POST", uri, r) + _, err = yaamtest.GenericArtifactReq("POST", genericUri, r) if err != nil { t.Error(err) } assert.NoError(t, err) - - // Download - resp, err := testGenericArtifactReqHelper("GET", uri, nil) - if err != nil { - t.Error(err) - } - w, err := testGenericArtifactWriteOnDiskHelper(resp) - if err != nil { - t.Error(err) - } - assert.Equal(t, "written: 3826831360", fmt.Sprintf("written: %d", w)) } -func TestGenericArtifactFail(t *testing.T) { - // Upload - uri := "/generic/something2/some.iso" - resp, err := testGenericArtifactReqHelper("POST", uri, nil) +func TestGenericArtifactUploadFail(t *testing.T) { + resp, err := yaamtest.GenericArtifactReq("POST", genericUriFail, nil) if err != nil { t.Error(err) } @@ -403,126 +138,24 @@ func TestGenericArtifactFail(t *testing.T) { assert.Equal(t, "check the server logs\n", bodyString) assert.Equal(t, 500, resp.StatusCode) assert.NoError(t, err) - - // Download - resp, err = testGenericArtifactReqHelper("GET", uri, nil) - if err != nil { - t.Error(err) - } - assert.Equal(t, 404, resp.StatusCode) } -func TestStatus(t *testing.T) { - b, err := testStatusHelper("GET", "world", "/status", nil, 10) +func TestGenericArtifactDownload(t *testing.T) { + resp, err := yaamtest.GenericArtifactReq("GET", genericUri, nil) if err != nil { t.Error(err) } - - assert.NoError(t, err) - assert.Equal(t, "ok", b) -} - -func TestMainNpmBuild(t *testing.T) { - exitCode, err := testNpmConfigHelper() + w, err := yaamtest.GenericArtifactWriteOnDisk(resp) if err != nil { t.Error(err) - return } - - assert.NoError(t, err) - assert.Equal(t, 0, exitCode) + assert.Equal(t, "written: 3826831360", fmt.Sprintf("written: %d", w)) } -func TestMainGradleCleanBuild(t *testing.T) { - if err := testGradleBuildFileHelper(mavenRepos, mavenReleasesRepo); err != nil { - t.Error(err) - } - if err := testGradleSettingsFileHelper(mavenRepos); err != nil { - t.Error(err) - } - - exitCode, err := testMainGradleCleanBuildHelper("world") +func TestGenericArtifactDownloadFail(t *testing.T) { + resp, err := yaamtest.GenericArtifactReq("GET", genericUriFail, nil) if err != nil { t.Error(err) } - - assert.NoError(t, err) - assert.Equal(t, 0, exitCode) -} - -func TestMainGradleCleanBuildFail(t *testing.T) { - if err := testConfigHelper(); err != nil { - t.Error(err) - } - - if err := testGradleBuildFileHelper(mavenRepos, mavenReleasesRepo); err != nil { - t.Error(err) - } - if err := testGradleSettingsFileHelper(mavenRepos); err != nil { - t.Error(err) - } - - exitCode, err := testMainGradleCleanBuildHelper("incorrectPass") - - assert.Regexp(t, "was not found in any of the following sources", err) - assert.Equal(t, 1, exitCode) -} - -func TestMainGradleCleanBuildNonMavenFail(t *testing.T) { - repos := []string{"3rdparty-maven", "3rdparty-maven-gradle-plugins", "3rdparty-maven-spring", "releases"} - if err := testGradleBuildFileHelper(repos, "releases"); err != nil { - t.Error(err) - } - if err := testGradleSettingsFileHelper(repos); err != nil { - t.Error(err) - } - - exitCode, err := testMainGradleCleanBuildHelper("world") - - assert.Regexp(t, "was not found in any of the following sources", err) - assert.Equal(t, 1, exitCode) -} - -func TestMainGradlePublish(t *testing.T) { - exitCode, err := testMainGradlePublishHelper(mavenReleasesRepo) - - assert.NoError(t, err) - assert.Equal(t, 0, exitCode) -} - -func TestMainGradlePublishFail(t *testing.T) { - exitCode, err := testMainGradlePublishHelper("maven/releases-non-existent") - - assert.Regexp(t, `Could not PUT '`+project.Url+`/maven/releases-non-existent/com/example/demo/0.0.1-SNAPSHOT/maven-metadata.xml'. Received status code 500 from server: Internal Server Error`, err) - assert.Equal(t, 1, exitCode) -} - -func TestMainGradleCleanBuildGroup(t *testing.T) { - repos := []string{"maven/groups/hello"} - if err := testGradleBuildFileHelper(repos, mavenReleasesRepo); err != nil { - t.Error(err) - } - if err := testGradleSettingsFileHelper(repos); err != nil { - t.Error(err) - } - - exitCode, err := testMainGradleCleanBuildHelper("world") - - assert.NoError(t, err) - assert.Equal(t, 0, exitCode) -} - -func TestMainGradleCleanBuildGroupFail(t *testing.T) { - repos := []string{"maven/groups/helloworld"} - if err := testGradleBuildFileHelper(repos, mavenReleasesRepo); err != nil { - t.Error(err) - } - if err := testGradleSettingsFileHelper(mavenRepos); err != nil { - t.Error(err) - } - - exitCode, err := testMainGradleCleanBuildHelper("world") - - assert.Regexp(t, `Could not GET '`+project.Url+`/maven/groups/helloworld/.*'. Received status code 500 from server: Internal Server Error`, err) - assert.Equal(t, 1, exitCode) + assert.Equal(t, 404, resp.StatusCode) } diff --git a/configs/.golangci.yml b/configs/.golangci.yml new file mode 100644 index 0000000..cb466c8 --- /dev/null +++ b/configs/.golangci.yml @@ -0,0 +1,64 @@ +--- +issues: + exclude-rules: + - linters: + - gochecknoinits + path: cmd/yaam/main_test.go + text: "don't use `init` function" + - linters: + - gochecknoinits + path: internal/app/yaam/artifact/artifact_test.go + text: "don't use `init` function" + - linters: + - gochecknoinits + path: internal/pkg/artifact/artifact_test.go + text: "don't use `init` function" + - linters: + - gochecknoinits + path: internal/pkg/artifact/validate_test.go + text: "don't use `init` function" +linters: + enable-all: true + disable: + - bodyclose + - cyclop + - durationcheck + - errorlint + - exhaustruct + - forbidigo + - forcetypeassert + - gochecknoglobals + - goconst + - gocritic + - godot + - goerr113 + - gofumpt + - golint + - gomnd + - lll + - nestif + - nlreturn + - noctx + - nonamedreturns + - paralleltest + - revive + - stylecheck + - testpackage + - unused + - usestdlibvars + - varnamelen + - whitespace + - wrapcheck + - wsl + # + # The following linters have been deprecated + # + - maligned + - ifshort + - structcheck + - exhaustivestruct + - scopelint + - deadcode + - nosnakecase + - interfacer + - varcheck diff --git a/docs/config/MAVEN.md b/docs/config/MAVEN.md index f3a90b1..d63c1bc 100644 --- a/docs/config/MAVEN.md +++ b/docs/config/MAVEN.md @@ -4,7 +4,7 @@ ```bash --- -mavenReposAndUrls: +reposAndUrls: 3rdparty-maven: https://repo.maven.apache.org/maven2/ 3rdparty-maven-gradle-plugins: https://plugins.gradle.org/m2/ 3rdparty-maven-spring: https://repo.spring.io/release/ diff --git a/docs/config/NPM.md b/docs/config/NPM.md index 944280a..ed51b34 100644 --- a/docs/config/NPM.md +++ b/docs/config/NPM.md @@ -4,7 +4,7 @@ ```bash --- -mavenReposAndUrls: +reposAndUrls: 3rdparty-npm: https://registry.npmjs.org/ ``` diff --git a/docs/other/BACKGROUND.md b/docs/other/BACKGROUND.md index fc588f9..c77e891 100644 --- a/docs/other/BACKGROUND.md +++ b/docs/other/BACKGROUND.md @@ -1,5 +1,22 @@ # Background +## Architecture + +### Preserve + +#### Maven + +- Read conf maven.yaml. +- Get name and publicURL. +- If call to name then do the actual call to the public maven repo. +- Download the maven artifact to disk. + +#### NPM + +- Download the json files as .tmp. +- Replace the public URL with YAAM in .tmp files. +- Download the files via YAAM. + ## Rationale for port 25213 Y is the 25th letter in the alphabet, two times 'a' equals 2 and 13 represents diff --git a/internal/api/api.go b/internal/app/yaam/api/api.go similarity index 100% rename from internal/api/api.go rename to internal/app/yaam/api/api.go diff --git a/internal/api/api_test.go b/internal/app/yaam/api/api_test.go similarity index 100% rename from internal/api/api_test.go rename to internal/app/yaam/api/api_test.go diff --git a/internal/app/yaam/artifact/artifact.go b/internal/app/yaam/artifact/artifact.go new file mode 100644 index 0000000..4848dbb --- /dev/null +++ b/internal/app/yaam/artifact/artifact.go @@ -0,0 +1,23 @@ +package artifact + +import ( + "fmt" + + "github.com/spf13/viper" +) + +type artefact struct { + path, url string +} + +func allowedRepos(name string) ([]string, error) { + groups := viper.GetStringMapStringSlice("groups.maven") + var repos []string + if values, ok := groups[name]; ok { + repos = values + } else { + return nil, fmt.Errorf("group: '%s' not found in config file", name) + } + + return repos, nil +} diff --git a/internal/app/yaam/artifact/artifact_test.go b/internal/app/yaam/artifact/artifact_test.go new file mode 100644 index 0000000..1cf2906 --- /dev/null +++ b/internal/app/yaam/artifact/artifact_test.go @@ -0,0 +1,38 @@ +package artifact + +import ( + "log" + "testing" + + "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/pkg/yaamtest" + "github.com/tj/assert" +) + +func init() { + if err := yaamtest.Config(); err != nil { + log.Fatal(err) + } + + if err := project.Config(); err != nil { + log.Fatal(err) + } +} + +func TestAllowedRepos(t *testing.T) { + expRepos := []string{"maven/releases", "maven/3rdparty-maven", "maven/3rdparty-maven-gradle-plugins", "maven/3rdparty-maven-spring"} + actRrepos, err := allowedRepos("hello") + if err != nil { + t.Error(err) + } + + assert.Equal(t, expRepos, actRrepos) +} + +func TestAllowedReposFail(t *testing.T) { + expRepos := []string(nil) + actRrepos, err := allowedRepos("world") + + assert.Equal(t, expRepos, actRrepos) + assert.EqualError(t, err, "group: 'world' not found in config file") +} diff --git a/internal/artifact/generic.go b/internal/app/yaam/artifact/generic.go similarity index 100% rename from internal/artifact/generic.go rename to internal/app/yaam/artifact/generic.go diff --git a/internal/artifact/maven_test.go b/internal/app/yaam/artifact/generic_test.go similarity index 100% rename from internal/artifact/maven_test.go rename to internal/app/yaam/artifact/generic_test.go diff --git a/internal/artifact/interface.go b/internal/app/yaam/artifact/interface.go similarity index 100% rename from internal/artifact/interface.go rename to internal/app/yaam/artifact/interface.go diff --git a/internal/artifact/maven.go b/internal/app/yaam/artifact/maven.go similarity index 91% rename from internal/artifact/maven.go rename to internal/app/yaam/artifact/maven.go index bd796da..066a7b8 100644 --- a/internal/artifact/maven.go +++ b/internal/app/yaam/artifact/maven.go @@ -41,8 +41,9 @@ func maven(url string, repoInConfigFile artifact.PublicRepository) (artefact, er } func (m Maven) downloadAgainIfInvalid(a artefact, resp *http.Response) error { + log.Debug(resp.StatusCode) if resp.StatusCode == http.StatusOK { - if err := file.CreateIfDoesNotExistOrEmpty(a.url, a.path, resp.Body); err != nil { + if err := file.CreateIfDoesNotExistInvalidOrEmpty(a.url, a.path, resp.Body, false); err != nil { return err } } @@ -63,7 +64,7 @@ func (m Maven) Preserve(urlStrings ...string) error { } log.Debugf("urlString: '%s'", urlString) - repoInConfigFile, err := artifact.RepoInConfigFile(m.ResponseWriter, urlString) + repoInConfigFile, err := artifact.RepoInConfigFile(m.ResponseWriter, urlString, "maven") if err != nil { return err } @@ -74,7 +75,7 @@ func (m Maven) Preserve(urlStrings ...string) error { return err } - resp, err := file.DownloadWithRetries(a.url) + resp, err := file.DownloadWithRetries(a.url, repoInConfigFile.User, repoInConfigFile.Pass) if err != nil { return err } diff --git a/internal/app/yaam/artifact/maven_test.go b/internal/app/yaam/artifact/maven_test.go new file mode 100644 index 0000000..c146142 --- /dev/null +++ b/internal/app/yaam/artifact/maven_test.go @@ -0,0 +1 @@ +package artifact diff --git a/internal/app/yaam/artifact/npm.go b/internal/app/yaam/artifact/npm.go new file mode 100644 index 0000000..e15b211 --- /dev/null +++ b/internal/app/yaam/artifact/npm.go @@ -0,0 +1,264 @@ +package artifact + +import ( + "crypto/sha1" // #nosec + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" + "time" + + "github.com/030/yaam/internal/pkg/artifact" + "github.com/030/yaam/internal/pkg/file" + "github.com/030/yaam/internal/pkg/project" + log "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" +) + +type Npm struct { + ResponseWriter http.ResponseWriter + RequestBody io.ReadCloser + RequestURI string +} + +func replaceUrlPublicNpmWithYaamHost(f string) error { + if filepath.Ext(f) == ".tmp" { + input, err := os.ReadFile(filepath.Clean(f)) + if err != nil { + return err + } + + host := os.Getenv("YAAM_HOST") + if host == "" { + host = project.HostAndPort + } + output := strings.Replace(string(input), "https://registry.npmjs.org", "http://"+host+"/npm/3rdparty-npm", -1) + + var re = regexp.MustCompile(`(/@[a-z]+)(/)`) + s := re.ReplaceAllString(output, `$1%2f`) + err = os.WriteFile(f, []byte(s), 0600) + if err != nil { + return err + } + + b, err := os.ReadFile(filepath.Clean(f)) + if err != nil { + return err + } + if !json.Valid(b) { + return fmt.Errorf("json for file: '%s' is invalid", f) + } + } + return nil +} + +func firstMatch(f, regex string) (string, error) { + re := regexp.MustCompile(regex) + match := re.FindStringSubmatch(f) + matchLength := len(match) + log.Debugf("regex: '%s', match: '%v' matchLength: '%d' for file: '%s'", regex, match, matchLength, f) + if matchLength <= 1 { + return "", fmt.Errorf("no match was found for: '%s' with regex: '%s'", f, regex) + } + m := match[1] + log.Debugf("firstMatch: '%s'", m) + + return m, nil +} + +func pathTmp(f string) (string, error) { + path, err := firstMatch(f, `^(/.*/[0-9a-z-\./@_]+)/-.*$`) + if err != nil { + return "", err + } + return filepath.Join(path + ".tmp"), nil +} + +func versionShasum(f string) (string, error) { + version, err := firstMatch(f, `-([0-9]+\.[0-9]+\.[0-9]+(-.+)?)\.tgz$`) + if err != nil { + return "", err + } + + pt, err := pathTmp(f) + if err != nil { + return "", err + } + + b, err := os.ReadFile(filepath.Clean(pt)) + if err != nil { + return "", err + } + + version = strings.Replace(version, ".", `\.`, -1) + value := gjson.GetBytes(b, `versions.`+version+`.dist.shasum`) + + return value.String(), nil +} + +func compareChecksumOnDiskWithExpectedSha(expChecksum, pathTmp string) (bool, error) { + checksumValid := true + f, err := os.Open(filepath.Clean(pathTmp)) + if err != nil { + return checksumValid, err + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + + /* #nosec */ + h := sha1.New() + if _, err := io.Copy(h, f); err != nil { + return checksumValid, err + } + fmt.Printf("%x", h.Sum(nil)) + checksum := fmt.Sprintf("%x", h.Sum(nil)) + if checksum != expChecksum { + log.Errorf("file: '%s' checksum on disk: '%s' does not match expected checksum: '%s'", pathTmp, checksum, expChecksum) + checksumValid = false + time.Sleep(file.RetryDuration) + } + + return checksumValid, nil +} + +func checksum(f string) (bool, error) { + checksumValid := true + _, fileExists := file.Exists(f) + if !fileExists && filepath.Ext(f) == ".tgz" { + pt, err := pathTmp(f) + if err != nil { + return checksumValid, err + } + + vs, err := versionShasum(f) + if err != nil { + return checksumValid, err + } + + checksumValid, err := compareChecksumOnDiskWithExpectedSha(vs, pt) + if err != nil { + return checksumValid, err + } + } + + return checksumValid, nil +} + +func (n Npm) downloadAgainIfInvalid(a artefact, resp *http.Response) error { + checksumValid, err := checksum(a.path) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusOK || filepath.Ext(a.path) == ".tmp" { + if err := file.CreateIfDoesNotExistInvalidOrEmpty(a.url, a.path, resp.Body, false); err != nil { + return err + } + } + + if file.EmptyFile(a.path) || !checksumValid { + if err := n.Preserve(); err != nil { + return err + } + } + + if filepath.Ext(a.path) == ".tmp" { + b, err := os.ReadFile(filepath.Clean(a.path)) + if err != nil { + return err + } + + if !json.Valid(b) { + log.Errorf("json file: '%s' is invalid", a.path) + if err := file.CreateIfDoesNotExistInvalidOrEmpty(a.url, a.path, resp.Body, true); err != nil { + return err + } + } + } + + return nil +} + +func (n Npm) Preserve(urlStrings ...string) error { + urlString := n.RequestURI + if len(urlStrings) > 0 { + urlString = urlStrings[0] + } + + repoInConfigFile, err := artifact.RepoInConfigFile(n.ResponseWriter, urlString, "npm") + if err != nil { + return err + } + + if !reflect.ValueOf(repoInConfigFile).IsZero() { + h, err := project.RepositoriesHome() + if err != nil { + return err + } + dir := strings.Replace(urlString, "%2f", "/", -1) + log.Debugf("extension found: '%s', file: '%s'", filepath.Ext(dir), dir) + if filepath.Ext(dir) != ".tgz" { + log.Debugf("file: '%s' does not have an extension", dir) + dir = dir + ".tmp" + } + if err := artifact.Dir(dir); err != nil { + return err + } + + log.Debugf("downloadUrl before entering downloadUrl method: '%s', regex: '%s'", urlString, repoInConfigFile.Regex) + du, err := artifact.DownloadUrl(repoInConfigFile.Url, repoInConfigFile.Regex, urlString) + if err != nil { + return err + } + completeFile := filepath.Join(h, dir) + + a := artefact{path: completeFile, url: du} + resp, err := file.DownloadWithRetries(a.url) + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + panic(err) + } + }() + if err := n.downloadAgainIfInvalid(a, resp); err != nil { + return err + } + + if err := replaceUrlPublicNpmWithYaamHost(completeFile); err != nil { + return err + } + } + + return nil +} + +func (n Npm) Publish() error { + if err := artifact.StoreOnDisk(n.RequestURI, n.RequestBody); err != nil { + return err + } + + return nil +} + +func (n Npm) Read() error { + reqUrlString := strings.Replace(n.RequestURI, "%2f", "/", -1) + if filepath.Ext(reqUrlString) != ".tgz" { + log.Debugf("file: '%s' does not have an extension", reqUrlString) + reqUrlString = reqUrlString + ".tmp" + } + if err := artifact.ReadFromDisk(n.ResponseWriter, reqUrlString); err != nil { + return fmt.Errorf(file.CannotReadErrMsg, err) + } + + return nil +} diff --git a/internal/app/yaam/artifact/npm_test.go b/internal/app/yaam/artifact/npm_test.go new file mode 100644 index 0000000..0920b87 --- /dev/null +++ b/internal/app/yaam/artifact/npm_test.go @@ -0,0 +1,61 @@ +package artifact + +import ( + "path/filepath" + "runtime" + "testing" + + "github.com/030/yaam/internal/pkg/yaamtest" + "github.com/tj/assert" +) + +func TestFirstMatch(t *testing.T) { + exp := `world` + act, err := firstMatch("helloworld", `(`+exp+`)`) + if err != nil { + t.Error(err) + } + + assert.Equal(t, exp, act) +} + +func TestFirstMatchFail(t *testing.T) { + _, err := firstMatch("hello", "world") + assert.EqualError(t, err, "no match was found for: 'hello' with regex: 'world'") +} + +func TestPathTmp(t *testing.T) { + exp := `/hello/world.tmp` + act, err := pathTmp("/hello/world/-dfsf") + if err != nil { + t.Error(err) + } + + assert.Equal(t, exp, act) +} + +func TestPathTmpFail(t *testing.T) { + _, err := pathTmp("helloworld") + assert.EqualError(t, err, "no match was found for: 'helloworld' with regex: '^(/.*/[0-9a-z-\\./@_]+)/-.*$'") +} + +func TestVersionShasum(t *testing.T) { + _, filename, _, ok := runtime.Caller(0) + if !ok { + t.Error("err") + } + dirname := filepath.Dir(filename) + + exp := "7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + act, err := versionShasum(filepath.Join(dirname, "../..", yaamtest.Testdata, "npm/y18n/-/y18n-5.0.8.tgz")) + if err != nil { + t.Error(err) + } + + assert.Equal(t, exp, act) +} + +func TestVersionShasumFail(t *testing.T) { + _, err := versionShasum("does/not/exist") + assert.EqualError(t, err, "no match was found for: 'does/not/exist' with regex: '-([0-9]+\\.[0-9]+\\.[0-9]+(-.+)?)\\.tgz$'") +} diff --git a/internal/artifact/artifact.go b/internal/artifact/artifact.go deleted file mode 100644 index 5ea8334..0000000 --- a/internal/artifact/artifact.go +++ /dev/null @@ -1,37 +0,0 @@ -package artifact - -import ( - "fmt" - "path/filepath" - - "github.com/030/yaam/internal/pkg/project" - "github.com/spf13/viper" -) - -type artefact struct { - path, url string -} - -func allowedRepos(name string) ([]string, error) { - h, err := project.Home() - if err != nil { - return []string{}, err - } - - viper.SetConfigName("groups") - viper.SetConfigType("yaml") - viper.AddConfigPath(filepath.Join(h, "conf")) - if err := viper.ReadInConfig(); err != nil { - return []string{}, err - } - - groups := viper.GetStringMapStringSlice("groups") - var repos []string - if values, ok := groups[name]; ok { - repos = values - } else { - return []string{}, fmt.Errorf("group: '%s' not found in config file", name) - } - - return repos, nil -} diff --git a/internal/artifact/artifact_test.go b/internal/artifact/artifact_test.go deleted file mode 100644 index 2e85109..0000000 --- a/internal/artifact/artifact_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package artifact - -import ( - "os" - "path/filepath" - "testing" - "time" - - "github.com/tj/assert" -) - -const ( - groups = `groups: - hello: - - maven/releases - - maven/3rdparty-maven - - maven/3rdparty-maven-gradle-plugins - - maven/3rdparty-maven-spring` -) - -func init() { - os.Setenv("YAAM_HOME", filepath.Join("/tmp", "yaam", "test"+time.Now().Format("20060102150405111"))) - dir := filepath.Join(os.Getenv("YAAM_HOME"), "conf") - reposDir := filepath.Join(dir, "repositories") - if err := os.MkdirAll(reposDir, os.ModePerm); err != nil { - panic(err) - } - if err := os.WriteFile(filepath.Join(dir, "groups.yaml"), []byte(groups), 0600); err != nil { - panic(err) - } -} - -func TestAllowedRepos(t *testing.T) { - expRepos := []string{"maven/releases", "maven/3rdparty-maven", "maven/3rdparty-maven-gradle-plugins", "maven/3rdparty-maven-spring"} - actRrepos, err := allowedRepos("hello") - if err != nil { - t.Error(err) - } - - assert.Equal(t, expRepos, actRrepos) -} - -func TestAllowedReposFail(t *testing.T) { - expRepos := []string{} - actRrepos, err := allowedRepos("world") - - assert.Equal(t, expRepos, actRrepos) - assert.EqualError(t, err, "group: 'world' not found in config file") -} diff --git a/internal/artifact/npm.go b/internal/artifact/npm.go deleted file mode 100644 index 828c9bb..0000000 --- a/internal/artifact/npm.go +++ /dev/null @@ -1,215 +0,0 @@ -package artifact - -import ( - "crypto/sha1" // #nosec - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "reflect" - "regexp" - "strings" - "time" - - "github.com/030/yaam/internal/pkg/artifact" - "github.com/030/yaam/internal/pkg/file" - "github.com/030/yaam/internal/pkg/project" - log "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" -) - -type Npm struct { - ResponseWriter http.ResponseWriter - RequestBody io.ReadCloser - RequestURI string -} - -func replaceUrlPublicNpmWithYaamHost(f, url string) error { - if filepath.Ext(f) == ".tmp" { - input, err := os.ReadFile(filepath.Clean(f)) - if err != nil { - return err - } - - host := os.Getenv("YAAM_HOST") - if host == "" { - host = project.HostAndPort - } - output := strings.Replace(string(input), "https://registry.npmjs.org", "http://"+host+"/npm/3rdparty-npm", -1) - - var re = regexp.MustCompile(`(/@[a-z]+)(/)`) - s := re.ReplaceAllString(output, `$1%2f`) - err = os.WriteFile(f, []byte(s), 0600) - if err != nil { - return err - } - - b, err := os.ReadFile(filepath.Clean(f)) - if err != nil { - return err - } - if !json.Valid(b) { - return fmt.Errorf("json for file: '%s' is invalid", f) - } - } - return nil -} - -func npm(url string, repoInConfigFile artifact.PublicRepository) (artefact, error) { - h, err := project.RepositoriesHome() - if err != nil { - return artefact{}, err - } - dir := strings.Replace(url, "%2f", "/", -1) - log.Debugf("extension found: '%s', file: '%s'", filepath.Ext(dir), dir) - if filepath.Ext(dir) != ".tgz" { - log.Debugf("file: '%s' does not have an extension", dir) - dir = dir + ".tmp" - } - if err := artifact.Dir(dir); err != nil { - return artefact{}, err - } - - log.Debugf("downloadUrl before entering downloadUrl method: '%s', regex: '%s'", url, repoInConfigFile.Regex) - du, err := artifact.DownloadUrl(repoInConfigFile.Url, repoInConfigFile.Regex, url) - if err != nil { - return artefact{}, err - } - completeFile := filepath.Join(h, dir) - log.Debugf("completeFile: '%s', downloadUrl: '%s'", completeFile, du) - - if err := replaceUrlPublicNpmWithYaamHost(completeFile, url); err != nil { - return artefact{}, err - } - - return artefact{path: completeFile, url: du}, err -} - -func checksum(f string) (bool, error) { - checksumValid := true - _, fileExists := file.Exists(f) - if !fileExists && filepath.Ext(f) == ".tgz" { - re := regexp.MustCompile(`-([0-9]+\.[0-9]+\.[0-9]).tgz$`) - match := re.FindStringSubmatch(f) - log.Debugf("match version length: '%d' for file: '%s'", len(match), f) - version := match[1] - fmt.Println(version) - - re = regexp.MustCompile(`^(/.*/[0-9a-z-/@]+)/-.*$`) - match = re.FindStringSubmatch(f) - log.Debugf("match tmp dir length: '%d' for file: '%s'", len(match), f) - blah := match[1] - fmt.Println(blah) - - blahFile := filepath.Join(blah + ".tmp") - b, err := os.ReadFile(filepath.Clean(blahFile)) - if err != nil { - return checksumValid, err - } - - version = strings.Replace(version, ".", `\.`, -1) - value := gjson.GetBytes(b, `versions.`+version+`.dist.shasum`) - println(value.String()) - - f2, err := os.Open(filepath.Clean(blahFile)) - if err != nil { - return checksumValid, err - } - defer func() { - if err := f2.Close(); err != nil { - panic(err) - } - }() - - /* #nosec */ - h := sha1.New() - if _, err := io.Copy(h, f2); err != nil { - return checksumValid, err - } - - fmt.Printf("%x", h.Sum(nil)) - checksum := fmt.Sprintf("%x", h.Sum(nil)) - if checksum != value.String() { - log.Errorf("file: '%s' checksum on disk: '%s' does not match expected checksum: '%s'", f, checksum, value.String()) - checksumValid = false - log.Warnf(file.WaitMsg, file.RetryDuration) - time.Sleep(file.RetryDuration) - } - } - - return checksumValid, nil -} - -func (n Npm) downloadAgainIfInvalid(a artefact, resp *http.Response) error { - checksumValid, err := checksum(a.path) - if err != nil { - return err - } - - if resp.StatusCode == http.StatusOK || !checksumValid || filepath.Ext(a.path) == ".tmp" { - if err := file.CreateIfDoesNotExistOrEmpty(a.url, a.path, resp.Body); err != nil { - return err - } - } - - if file.EmptyFile(a.path) { - if err := n.Preserve(); err != nil { - return err - } - } - - return nil -} - -func (n Npm) Preserve() error { - repoInConfigFile, err := artifact.RepoInConfigFile(n.ResponseWriter, n.RequestURI) - if err != nil { - return err - } - - if !reflect.ValueOf(repoInConfigFile).IsZero() { - a, err := npm(n.RequestURI, repoInConfigFile) - if err != nil { - return err - } - - resp, err := file.DownloadWithRetries(a.url) - if err != nil { - return err - } - defer func() { - if err := resp.Body.Close(); err != nil { - panic(err) - } - }() - - if err := n.downloadAgainIfInvalid(a, resp); err != nil { - return err - } - } - - return nil -} - -func (n Npm) Publish() error { - if err := artifact.StoreOnDisk(n.RequestURI, n.RequestBody); err != nil { - return err - } - - return nil -} - -func (n Npm) Read() error { - reqUrlString := strings.Replace(n.RequestURI, "%2f", "/", -1) - if filepath.Ext(reqUrlString) != ".tgz" { - log.Debugf("file: '%s' does not have an extension", reqUrlString) - reqUrlString = reqUrlString + ".tmp" - } - if err := artifact.ReadFromDisk(n.ResponseWriter, reqUrlString); err != nil { - return fmt.Errorf(file.CannotReadErrMsg, err) - } - - return nil -} diff --git a/internal/pkg/artifact/artifact.go b/internal/pkg/artifact/artifact.go index 8d3c90a..8069cea 100644 --- a/internal/pkg/artifact/artifact.go +++ b/internal/pkg/artifact/artifact.go @@ -107,31 +107,37 @@ func ReadFromDisk(w http.ResponseWriter, reqURL string) error { // attempted to download a file, it will look up the name in the config file // and find the public URLs so it can download the file from the public maven // repository and cache it on disk. -func RepoInConfigFile(w http.ResponseWriter, urlString string) (PublicRepository, error) { - yh, err := project.Home() - if err != nil { - return PublicRepository{}, err - } - - viper.SetConfigName("caches") - viper.SetConfigType("yaml") - viper.AddConfigPath(filepath.Join(yh, "conf")) - if err := viper.ReadInConfig(); err != nil { - return PublicRepository{}, err +func RepoInConfigFile(w http.ResponseWriter, urlString, artifactType string) (PublicRepository, error) { + reposAndElements := viper.GetStringMap("caches." + artifactType) + if len(reposAndElements) == 0 { + return PublicRepository{}, fmt.Errorf("caches: '%s' not found in config file", artifactType) } - reposAndUrls := viper.GetStringMapString("mavenReposAndUrls") - for repo, url := range reposAndUrls { + for repo, elements := range reposAndElements { + url, ok := elements.(map[string]interface{})["url"] + if ok { + log.Debugf("url: '%s'", url) + } + user, ok := elements.(map[string]interface{})["user"] + if ok { + log.Debugf("user: '%s'", user) + } + pass, ok := elements.(map[string]interface{})["pass"] + if ok { + log.Debug("pass: **********") + } - // if err := pr.cache(n.RequestURL); err != nil { - // return err - // } - // reqURLString := reqURL.String() log.Debugf("trying to cache artifact from: '%s'...", urlString) - rr := repoRegex(repo) + rr := repoRegex(repo, artifactType) log.Debugf("repoRegex: '%s'", rr) - pr := PublicRepository{Name: repo, Regex: rr, Url: url} + + pr := PublicRepository{Name: repo, Regex: rr, Url: url.(string)} + if user != nil && pass != nil { + pr.User = user.(string) + pr.Pass = pass.(string) + } + riu, err := repoInUrl(rr, urlString) if err != nil { return PublicRepository{}, err @@ -139,9 +145,6 @@ func RepoInConfigFile(w http.ResponseWriter, urlString string) (PublicRepository log.Debugf("repoInUrl: '%t'", riu) if riu { - // if err := pr.createDirAndStoreOnDisk(rr, reqURLString); err != nil { - // return err - // } return pr, nil } } @@ -150,11 +153,11 @@ func RepoInConfigFile(w http.ResponseWriter, urlString string) (PublicRepository } type PublicRepository struct { - Name, Regex, Url string + Name, Regex, Url, User, Pass string } -func repoRegex(repo string) string { - return `^/(maven|npm)/` + repo + `/(.*)$` +func repoRegex(repo, repoType string) string { + return `^/` + repoType + `/` + repo + `/(.*)$` } func repoInUrl(repoRegex, url string) (bool, error) { @@ -170,14 +173,14 @@ func repoInUrl(repoRegex, url string) (bool, error) { func DownloadUrl(publicRepoUrl, regex, url string) (string, error) { log.Debugf("check whether url: '%s' matches regex: '%s'. Params -> publicRepoUrl: '%s', regex: '%s' and url: '%s'", url, regex, publicRepoUrl, regex, url) - re := regexp.MustCompile(regex) - match := re.FindStringSubmatch(url) - log.Debugf("number of matching elements: %d", len(match)) - if len(match) != 3 { + r := regexp.MustCompile(regex) + match := r.FindStringSubmatch(url) + log.Debugf("number of matching elements: %d. Content: '%v'", len(match), match) + if len(match) != 2 { return "", fmt.Errorf("should be 3! publicRepoUrl: '%s', regex: '%s', url: '%s'", publicRepoUrl, regex, url) } - u := re.ReplaceAllString(url, publicRepoUrl+`$2`) + u := r.ReplaceAllString(url, publicRepoUrl+`$1`) return u, nil } diff --git a/internal/pkg/artifact/artifact_test.go b/internal/pkg/artifact/artifact_test.go index 04dc07e..713da19 100644 --- a/internal/pkg/artifact/artifact_test.go +++ b/internal/pkg/artifact/artifact_test.go @@ -6,9 +6,22 @@ import ( "strings" "testing" + "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/pkg/yaamtest" + log "github.com/sirupsen/logrus" "github.com/tj/assert" ) +func init() { + if err := yaamtest.Config(); err != nil { + log.Fatal(err) + } + + if err := project.Config(); err != nil { + log.Fatal(err) + } +} + func TestStoreOnDisk(t *testing.T) { s := strings.NewReader("Hola mundo!") rc := io.NopCloser(s) diff --git a/internal/pkg/artifact/validate.go b/internal/pkg/artifact/validate.go index ea423fd..40f8156 100644 --- a/internal/pkg/artifact/validate.go +++ b/internal/pkg/artifact/validate.go @@ -5,7 +5,6 @@ import ( "path/filepath" "regexp" - "github.com/030/yaam/internal/pkg/project" log "github.com/sirupsen/logrus" "github.com/spf13/viper" "golang.org/x/exp/slices" @@ -35,20 +34,8 @@ func validate(requestURI string) error { return nil } -func allowedRepo(name, repoType string) error { - h, err := project.Home() - if err != nil { - return err - } - - viper.SetConfigName(repoType) - viper.SetConfigType("yaml") - viper.AddConfigPath(filepath.Join(h, "conf", "repositories")) - if err := viper.ReadInConfig(); err != nil { - return err - } - - repos := viper.GetStringSlice("allowedRepos") +func allowedRepo(name, artifactType string) error { + repos := viper.GetStringSlice("publications." + artifactType) if !slices.Contains(repos, name) { return fmt.Errorf("repository: '%s' is not allowed. Allowed repos: '%v'", name, repos) } diff --git a/internal/pkg/artifact/validate_test.go b/internal/pkg/artifact/validate_test.go index 361e34e..981e8d5 100644 --- a/internal/pkg/artifact/validate_test.go +++ b/internal/pkg/artifact/validate_test.go @@ -1,43 +1,22 @@ package artifact import ( - "os" "path/filepath" "testing" - "time" + "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/pkg/yaamtest" + log "github.com/sirupsen/logrus" "github.com/tj/assert" ) -const ( - allowedReposMaven = `allowedRepos: - - releases` - allowedReposNpm = `allowedRepos: - - 3rdparty-npm` -) - -func ConfigHelper() error { - os.Setenv("YAAM_HOME", filepath.Join("/tmp", "yaam", "test"+time.Now().Format("20060102150405111"))) - dir := filepath.Join(os.Getenv("YAAM_HOME"), "conf", "repositories") - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(dir, "maven.yaml"), []byte(allowedReposMaven), 0600); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(dir, "npm.yaml"), []byte(allowedReposNpm), 0600); err != nil { - return err +func init() { + if err := yaamtest.Config(); err != nil { + log.Fatal(err) } - os.Setenv("YAAM_DEBUG", "true") - os.Setenv("YAAM_USER", "hello") - - return nil -} - -func init() { - if err := ConfigHelper(); err != nil { - panic(err) + if err := project.Config(); err != nil { + log.Fatal(err) } } @@ -52,8 +31,6 @@ func TestValidate(t *testing.T) { } func TestValidateFail(t *testing.T) { - expErr := `Config File "something" Not Found in "\[/tmp/yaam/test[0-9]+/conf/repositories\]"` - actErr := validate(filepath.Join("/something", "releases", "world", "hola.mundo")) - - assert.Regexp(t, expErr, actErr) + err := validate(filepath.Join("/something", "releases", "world")) + assert.Regexp(t, err, "requestURI: '/something/releases/world' should start with a: '/' and contain an extension") } diff --git a/internal/pkg/file/file.go b/internal/pkg/file/file.go index aaca4e1..47c9714 100644 --- a/internal/pkg/file/file.go +++ b/internal/pkg/file/file.go @@ -12,28 +12,34 @@ import ( ) const ( - RetryDuration = 30 * time.Second + RetryDuration = 5 * time.Second CannotReadErrMsg = "cannot read artifact from disk. Error: '%v'. Perhaps it resides in another repository?" WaitMsg = "wait: '%v' before retrying" ) -func DownloadWithRetries(url string) (*http.Response, error) { - retryClient := retryablehttp.NewClient() +func DownloadWithRetries(url string, auth ...string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + if len(auth) > 0 { + req.SetBasicAuth(auth[0], auth[1]) + } + retryClient := retryablehttp.NewClient() retryClient.Logger = nil - retryClient.RetryMax = 30 - retryClient.RetryWaitMin = 30 * time.Second + retryClient.RetryMax = 5 + retryClient.RetryWaitMin = 10 * time.Second retryClient.RetryWaitMax = 60 * time.Second standardClient := retryClient.StandardClient() - log.Debugf("downloadURL: '%s'", url) /* #nosec */ - r, err := standardClient.Get(url) + resp, err := standardClient.Do(req) if err != nil { return nil, err } - return r, nil + return resp, nil } func Exists(f string) (int64, bool) { @@ -45,10 +51,10 @@ func Exists(f string) (int64, bool) { return fi.Size(), true } -func CreateIfDoesNotExistOrEmpty(url, f string, body io.ReadCloser) error { +func CreateIfDoesNotExistInvalidOrEmpty(url, f string, body io.ReadCloser, invalid bool) error { var written int64 fileSize, fileExists := Exists(f) - if !fileExists || fileSize == 0 { + if !fileExists || fileSize == 0 || invalid { dst, err := os.Create(filepath.Clean(f)) if err != nil { return err @@ -67,7 +73,7 @@ func CreateIfDoesNotExistOrEmpty(url, f string, body io.ReadCloser) error { return err } } - log.Debugf("downloaded: '%s' to: '%s'. Wrote: '%d' bytes", url, f, written) + log.Infof("downloaded: '%s' to: '%s'. Wrote: '%d' bytes", url, f, written) return nil } diff --git a/internal/pkg/file/file_test.go b/internal/pkg/file/file_test.go new file mode 100644 index 0000000..b691ba5 --- /dev/null +++ b/internal/pkg/file/file_test.go @@ -0,0 +1 @@ +package file diff --git a/internal/pkg/project/project.go b/internal/pkg/project/project.go index e5deaec..5945f6e 100644 --- a/internal/pkg/project/project.go +++ b/internal/pkg/project/project.go @@ -7,6 +7,7 @@ import ( "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" + "github.com/spf13/viper" ) const ( @@ -22,19 +23,36 @@ var ( Url = Scheme + "://" + HostAndPort ) +func Config() error { + h, err := Home() + if err != nil { + return err + } + + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath(h) + if err := viper.ReadInConfig(); err != nil { + return err + } + log.Infof("config file used: '%s'", viper.ConfigFileUsed()) + + return nil +} + func Home() (string, error) { h, err := homedir.Dir() if err != nil { return "", err } - yh := filepath.Join(h, hiddenFolderName) + h = filepath.Join(h, hiddenFolderName) if os.Getenv("YAAM_HOME") != "" { - yh = os.Getenv("YAAM_HOME") + h = os.Getenv("YAAM_HOME") } - log.Debugf("yaam home: '%s'", yh) + log.Debugf("yaam home: '%s'", h) - return yh, nil + return h, nil } func RepositoriesHome() (string, error) { @@ -42,8 +60,7 @@ func RepositoriesHome() (string, error) { if err != nil { return "", err } + h = filepath.Join(h, "repositories") - rh := filepath.Join(h, "repositories") - - return rh, nil + return h, nil } diff --git a/internal/pkg/project/project_test.go b/internal/pkg/project/project_test.go new file mode 100644 index 0000000..b39aab1 --- /dev/null +++ b/internal/pkg/project/project_test.go @@ -0,0 +1,16 @@ +package project + +import ( + "regexp" + "testing" + + "github.com/tj/assert" +) + +func TestRepositoriesHome(t *testing.T) { + h, err := RepositoriesHome() + if err != nil { + t.Error(err) + } + assert.Regexp(t, regexp.MustCompile("/home/[a-z]+/.yaam/repositories"), h) +} diff --git a/internal/pkg/yaamtest/config.go b/internal/pkg/yaamtest/config.go new file mode 100644 index 0000000..5859eda --- /dev/null +++ b/internal/pkg/yaamtest/config.go @@ -0,0 +1,75 @@ +package yaamtest + +import ( + "os" + "path/filepath" + "time" +) + +const ( + Testdata = "../../test/testdata" + gradleHomeDemoProject = Testdata + "/gradle/demo" + npmHomeDemoProject = Testdata + "/npm/demo" + + conf = `--- +caches: + maven: + 3rdparty-maven: + url: https://repo.maven.apache.org/maven2/ + 3rdparty-maven-gradle-plugins: + url: https://plugins.gradle.org/m2/ + 3rdparty-maven-spring: + url: https://repo.spring.io/release/ + other-nexus-repo-releases: + url: x + user: y + pass: z + npm: + 3rdparty-npm: + url: https://registry.npmjs.org/ +groups: + maven: + hello: + - maven/releases + - maven/3rdparty-maven + - maven/3rdparty-maven-gradle-plugins + - maven/3rdparty-maven-spring +publications: + generic: + - something + maven: + - releases + npm: + - 3rdparty-npm` + cmdExitErrMsg = "%v, err: '%v'" + + dir = "/tmp/yaam" + DirIso = dir + "/ubuntu.iso" + DirIsoDownloaded = dir + "/downloaded-ubuntu.iso" + + testDirGradle = dir + "/gradle" + testDirNpm = dir + "/npm" +) + +func Config() error { + m := make(map[string]string) + m["HOME"] = filepath.Join(dir, "test"+time.Now().Format("20060102150405111")) + m["DEBUG"] = "true" + m["USER"] = "hello" + m["PASS"] = "world" + for k, v := range m { + if err := os.Setenv("YAAM_"+k, v); err != nil { + return err + } + } + + dir := os.Getenv("YAAM_HOME") + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + if err := os.WriteFile(filepath.Join(dir, "config.yml"), []byte(conf), 0600); err != nil { + return err + } + + return nil +} diff --git a/internal/pkg/yaamtest/config_test.go b/internal/pkg/yaamtest/config_test.go new file mode 100644 index 0000000..e05d8cb --- /dev/null +++ b/internal/pkg/yaamtest/config_test.go @@ -0,0 +1 @@ +package yaamtest diff --git a/internal/pkg/yaamtest/generic.go b/internal/pkg/yaamtest/generic.go new file mode 100644 index 0000000..837e07f --- /dev/null +++ b/internal/pkg/yaamtest/generic.go @@ -0,0 +1,81 @@ +package yaamtest + +import ( + "errors" + "io" + "net/http" + "os" + "time" + + "github.com/030/yaam/internal/pkg/project" +) + +func GenericArtifact() error { + if _, err := os.Stat(DirIso); errors.Is(err, os.ErrNotExist) { + resp, err := http.Get("https://releases.ubuntu.com/22.04.1/ubuntu-22.04.1-desktop-amd64.iso") + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + panic(err) + } + }() + + f, err := os.Create(DirIso) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + _, err = io.Copy(f, resp.Body) + if err != nil { + return err + } + if err := f.Sync(); err != nil { + return err + } + } + + return nil +} + +func GenericArtifactReq(method, uri string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest(method, project.Url+uri, body) + if err != nil { + return nil, err + } + req.SetBasicAuth("hello", "world") + + client := &http.Client{ + Timeout: time.Second * 120, + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} + +func GenericArtifactWriteOnDisk(resp *http.Response) (int64, error) { + f, err := os.Create(DirIsoDownloaded) + if err != nil { + return 0, err + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + w, err := io.Copy(f, resp.Body) + if err != nil { + return 0, err + } + if err := f.Sync(); err != nil { + return 0, err + } + return w, err +} diff --git a/internal/pkg/yaamtest/generic_test.go b/internal/pkg/yaamtest/generic_test.go new file mode 100644 index 0000000..e05d8cb --- /dev/null +++ b/internal/pkg/yaamtest/generic_test.go @@ -0,0 +1 @@ +package yaamtest diff --git a/internal/pkg/yaamtest/maven.go b/internal/pkg/yaamtest/maven.go new file mode 100644 index 0000000..481cbc3 --- /dev/null +++ b/internal/pkg/yaamtest/maven.go @@ -0,0 +1,162 @@ +package yaamtest + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/030/yaam/internal/pkg/project" +) + +var ( + MavenReleasesRepo = "maven/releases" + MavenRepos = []string{"maven/3rdparty-maven", "maven/3rdparty-maven-gradle-plugins", "maven/3rdparty-maven-spring", MavenReleasesRepo} +) + +func gradleFile(content []byte, name string) error { + if err := os.WriteFile(filepath.Join(gradleHomeDemoProject, name+".gradle"), content, 0600); err != nil { + return err + } + return nil +} + +func GradleMavenRepositoriesFile(repos []string) string { + var sb strings.Builder + for _, repo := range repos { + content := ` + maven { + allowInsecureProtocol true + url '` + project.Url + `/` + repo + `/' + authentication { + basic(BasicAuthentication) + } + credentials { + username "hello" + password "world" + } + }` + sb.WriteString(content) + } + return sb.String() +} + +func GradlePublishingFile(repo string) string { + repos := []string{repo} + content := ` +publishing { + publications { + mavenJava(MavenPublication) { + versionMapping { + usage('java-api') { + fromResolutionOf('runtimeClasspath') + } + usage('java-runtime') { + fromResolutionResult() + } + } + } + } + + repositories {` + + GradleMavenRepositoriesFile(repos) + ` + } +} +` + return content +} + +func GradlePublish(repo string) (int, error) { + if err := GradleBuildFile(MavenRepos, repo); err != nil { + return 1, err + } + if err := GradleSettingsFile(MavenRepos); err != nil { + return 1, err + } + + exitCode, err := GradleCleanBuild("world") + if err != nil { + return exitCode, err + } + + cmd := exec.Command("bash", "-c", "./gradlew publish --no-daemon") + cmd.Dir = gradleHomeDemoProject + co, err := cmd.CombinedOutput() + if err != nil { + return cmd.ProcessState.ExitCode(), fmt.Errorf(cmdExitErrMsg, string(co), err) + } + + return 0, nil +} + +func GradleSettingsFile(repos []string) error { + content := ` +pluginManagement { + repositories {` + + GradleMavenRepositoriesFile(repos) + ` + } +} + +rootProject.name = 'demo' +` + + if err := gradleFile([]byte(content), "settings"); err != nil { + return err + } + return nil +} + +func GradleCleanBuild(pass string) (int, error) { + if err := os.Setenv("YAAM_PASS", pass); err != nil { + return 1, err + } + + if err := os.Setenv("GRADLE_USER_HOME", testDirGradle+time.Now().Format("20060102150405111")); err != nil { + return 1, err + } + cmd := exec.Command("bash", "-c", "./gradlew clean build --no-daemon") + cmd.Dir = gradleHomeDemoProject + co, err := cmd.CombinedOutput() + if err != nil { + return cmd.ProcessState.ExitCode(), fmt.Errorf(cmdExitErrMsg, string(co), err) + } + + return 0, nil +} + +func GradleBuildFile(repos []string, repoPublish string) error { + content := ` +plugins { + id 'org.springframework.boot' version '2.7.3' + id 'io.spring.dependency-management' version '1.0.13.RELEASE' + id 'java' + id 'maven-publish' +} + +group = 'com.example' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '17' + +repositories {` + + GradleMavenRepositoriesFile(repos) + ` +} + +` + GradlePublishingFile(repoPublish) + ` + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} +` + + if err := gradleFile([]byte(content), "build"); err != nil { + return err + } + return nil +} diff --git a/internal/pkg/yaamtest/maven_test.go b/internal/pkg/yaamtest/maven_test.go new file mode 100644 index 0000000..e05d8cb --- /dev/null +++ b/internal/pkg/yaamtest/maven_test.go @@ -0,0 +1 @@ +package yaamtest diff --git a/internal/pkg/yaamtest/npm.go b/internal/pkg/yaamtest/npm.go new file mode 100644 index 0000000..ec70e7e --- /dev/null +++ b/internal/pkg/yaamtest/npm.go @@ -0,0 +1,42 @@ +package yaamtest + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/030/yaam/internal/pkg/project" +) + +var npmrc = `registry=` + project.Url + `/npm/3rdparty-npm/ +always-auth=true +_auth=aGVsbG86d29ybGQ=` + +func NpmConfig() (int, error) { + if err := os.RemoveAll(filepath.Join(npmHomeDemoProject, "node_modules")); err != nil { + return 1, err + } + packageLockJson := filepath.Join(npmHomeDemoProject, "package-lock.json") + if _, err := os.Stat(packageLockJson); err == nil { + if err := os.Remove(packageLockJson); err != nil { + return 1, err + } + } + + npmrcWithCacheLocation := npmrc + ` +cache=` + testDirNpm + `/cache` + time.Now().Format("20060102150405111") + `` + if err := os.WriteFile(filepath.Join(npmHomeDemoProject, ".npmrc"), []byte(npmrcWithCacheLocation), 0600); err != nil { + return 1, err + } + + cmd := exec.Command("bash", "-c", "npm cache clean --force && npm i") + cmd.Dir = npmHomeDemoProject + co, err := cmd.CombinedOutput() + if err != nil { + return cmd.ProcessState.ExitCode(), fmt.Errorf(cmdExitErrMsg, string(co), err) + } + + return 0, nil +} diff --git a/internal/pkg/yaamtest/npm_test.go b/internal/pkg/yaamtest/npm_test.go new file mode 100644 index 0000000..e05d8cb --- /dev/null +++ b/internal/pkg/yaamtest/npm_test.go @@ -0,0 +1 @@ +package yaamtest diff --git a/internal/pkg/yaamtest/status.go b/internal/pkg/yaamtest/status.go new file mode 100644 index 0000000..bc5b503 --- /dev/null +++ b/internal/pkg/yaamtest/status.go @@ -0,0 +1,36 @@ +package yaamtest + +import ( + "io" + "net/http" + "time" + + "github.com/030/yaam/internal/pkg/project" +) + +func StatusHelper(method, uri string, body io.Reader, timeout time.Duration) (string, error) { + client := &http.Client{ + Timeout: time.Second * timeout, + } + req, err := http.NewRequest(method, project.Url+uri, body) + if err != nil { + return "", err + } + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer func() { + if err := resp.Body.Close(); err != nil { + panic(err) + } + }() + + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(b), nil +} diff --git a/internal/pkg/yaamtest/status_test.go b/internal/pkg/yaamtest/status_test.go new file mode 100644 index 0000000..e05d8cb --- /dev/null +++ b/internal/pkg/yaamtest/status_test.go @@ -0,0 +1 @@ +package yaamtest diff --git a/test/README.md b/test/README.md deleted file mode 100644 index f2ede34..0000000 --- a/test/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test - -## Gradle diff --git a/test/gradle/README.md b/test/testdata/gradle/README.md similarity index 100% rename from test/gradle/README.md rename to test/testdata/gradle/README.md diff --git a/test/gradle/demo/.gitignore b/test/testdata/gradle/demo/.gitignore similarity index 100% rename from test/gradle/demo/.gitignore rename to test/testdata/gradle/demo/.gitignore diff --git a/test/gradle/demo/gradle/wrapper/gradle-wrapper.jar b/test/testdata/gradle/demo/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from test/gradle/demo/gradle/wrapper/gradle-wrapper.jar rename to test/testdata/gradle/demo/gradle/wrapper/gradle-wrapper.jar diff --git a/test/gradle/demo/gradle/wrapper/gradle-wrapper.properties b/test/testdata/gradle/demo/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from test/gradle/demo/gradle/wrapper/gradle-wrapper.properties rename to test/testdata/gradle/demo/gradle/wrapper/gradle-wrapper.properties diff --git a/test/gradle/demo/gradlew b/test/testdata/gradle/demo/gradlew similarity index 100% rename from test/gradle/demo/gradlew rename to test/testdata/gradle/demo/gradlew diff --git a/test/gradle/demo/gradlew.bat b/test/testdata/gradle/demo/gradlew.bat similarity index 100% rename from test/gradle/demo/gradlew.bat rename to test/testdata/gradle/demo/gradlew.bat diff --git a/test/gradle/demo/src/main/java/com/example/demo/DemoApplication.java b/test/testdata/gradle/demo/src/main/java/com/example/demo/DemoApplication.java similarity index 100% rename from test/gradle/demo/src/main/java/com/example/demo/DemoApplication.java rename to test/testdata/gradle/demo/src/main/java/com/example/demo/DemoApplication.java diff --git a/test/gradle/demo/src/main/resources/application.properties b/test/testdata/gradle/demo/src/main/resources/application.properties similarity index 100% rename from test/gradle/demo/src/main/resources/application.properties rename to test/testdata/gradle/demo/src/main/resources/application.properties diff --git a/test/gradle/demo/src/test/java/com/example/demo/DemoApplicationTests.java b/test/testdata/gradle/demo/src/test/java/com/example/demo/DemoApplicationTests.java similarity index 100% rename from test/gradle/demo/src/test/java/com/example/demo/DemoApplicationTests.java rename to test/testdata/gradle/demo/src/test/java/com/example/demo/DemoApplicationTests.java diff --git a/test/npm/README.md b/test/testdata/npm/README.md similarity index 100% rename from test/npm/README.md rename to test/testdata/npm/README.md diff --git a/test/npm/demo/.gitignore b/test/testdata/npm/demo/.gitignore similarity index 100% rename from test/npm/demo/.gitignore rename to test/testdata/npm/demo/.gitignore diff --git a/test/npm/demo/README.md b/test/testdata/npm/demo/README.md similarity index 100% rename from test/npm/demo/README.md rename to test/testdata/npm/demo/README.md diff --git a/test/npm/demo/package.json b/test/testdata/npm/demo/package.json similarity index 100% rename from test/npm/demo/package.json rename to test/testdata/npm/demo/package.json diff --git a/test/npm/demo/public/favicon.ico b/test/testdata/npm/demo/public/favicon.ico similarity index 100% rename from test/npm/demo/public/favicon.ico rename to test/testdata/npm/demo/public/favicon.ico diff --git a/test/npm/demo/public/index.html b/test/testdata/npm/demo/public/index.html similarity index 100% rename from test/npm/demo/public/index.html rename to test/testdata/npm/demo/public/index.html diff --git a/test/npm/demo/public/logo192.png b/test/testdata/npm/demo/public/logo192.png similarity index 100% rename from test/npm/demo/public/logo192.png rename to test/testdata/npm/demo/public/logo192.png diff --git a/test/npm/demo/public/logo512.png b/test/testdata/npm/demo/public/logo512.png similarity index 100% rename from test/npm/demo/public/logo512.png rename to test/testdata/npm/demo/public/logo512.png diff --git a/test/npm/demo/public/manifest.json b/test/testdata/npm/demo/public/manifest.json similarity index 100% rename from test/npm/demo/public/manifest.json rename to test/testdata/npm/demo/public/manifest.json diff --git a/test/npm/demo/public/robots.txt b/test/testdata/npm/demo/public/robots.txt similarity index 100% rename from test/npm/demo/public/robots.txt rename to test/testdata/npm/demo/public/robots.txt diff --git a/test/npm/demo/src/App.css b/test/testdata/npm/demo/src/App.css similarity index 100% rename from test/npm/demo/src/App.css rename to test/testdata/npm/demo/src/App.css diff --git a/test/npm/demo/src/App.js b/test/testdata/npm/demo/src/App.js similarity index 100% rename from test/npm/demo/src/App.js rename to test/testdata/npm/demo/src/App.js diff --git a/test/npm/demo/src/App.test.js b/test/testdata/npm/demo/src/App.test.js similarity index 100% rename from test/npm/demo/src/App.test.js rename to test/testdata/npm/demo/src/App.test.js diff --git a/test/npm/demo/src/index.css b/test/testdata/npm/demo/src/index.css similarity index 100% rename from test/npm/demo/src/index.css rename to test/testdata/npm/demo/src/index.css diff --git a/test/npm/demo/src/index.js b/test/testdata/npm/demo/src/index.js similarity index 100% rename from test/npm/demo/src/index.js rename to test/testdata/npm/demo/src/index.js diff --git a/test/npm/demo/src/logo.svg b/test/testdata/npm/demo/src/logo.svg similarity index 100% rename from test/npm/demo/src/logo.svg rename to test/testdata/npm/demo/src/logo.svg diff --git a/test/npm/demo/src/reportWebVitals.js b/test/testdata/npm/demo/src/reportWebVitals.js similarity index 100% rename from test/npm/demo/src/reportWebVitals.js rename to test/testdata/npm/demo/src/reportWebVitals.js diff --git a/test/npm/demo/src/setupTests.js b/test/testdata/npm/demo/src/setupTests.js similarity index 100% rename from test/npm/demo/src/setupTests.js rename to test/testdata/npm/demo/src/setupTests.js diff --git a/test/testdata/npm/y18n.tmp b/test/testdata/npm/y18n.tmp new file mode 100644 index 0000000..b4f074e --- /dev/null +++ b/test/testdata/npm/y18n.tmp @@ -0,0 +1 @@ +{"_id":"y18n","_rev":"37-9e422faa8b8e8bd0e2dccf5d2735d3a8","name":"y18n","description":"the bare-bones internationalization library used by yargs","dist-tags":{"latest":"5.0.8","next":"4.0.0","engines-test":"6.0.0-alpha.0","latest-4":"4.0.3","latest-3":"3.2.2"},"versions":{"1.0.0":{"name":"y18n","version":"1.0.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha"},"repository":{"type":"git","url":"git@github.com:bcoe/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/bcoe/y18n/issues"},"homepage":"https://github.com/bcoe/y18n","devDependencies":{"chai":"^3.2.0","mocha":"^2.2.5","nyc":"^3.0.1","rimraf":"^2.4.2","standard":"^4.5.4"},"gitHead":"da3facd10d36b380d02dfa14db367f09ff124869","_id":"y18n@1.0.0","_shasum":"408ca633ce1cb2e8e1e288acb8c7007dcb28a588","_from":".","_npmVersion":"2.7.5","_nodeVersion":"0.10.36","_npmUser":{"name":"bcoe","email":"ben@npmjs.com"},"dist":{"shasum":"408ca633ce1cb2e8e1e288acb8c7007dcb28a588","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-1.0.0.tgz","integrity":"sha512-p0rJluph22q+Hh6y2e2CFrTIYy1oaxLD4rudaQbU6o575Fd8hGkH0EXXGHfUfIOt3t4kkcZlNM2G0izf6w4zhA==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDzRwrlebnb6DKBJsGriXp1W+6vsGpPy0jR0mqTWyGgcAIgOZh3msEePmGyEgdlBrFsbVk59G4kXnx8Q/WaWtgMMYc="}]},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"}],"directories":{}},"1.1.0":{"name":"y18n","version":"1.1.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/bcoe/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/bcoe/y18n/issues"},"homepage":"https://github.com/bcoe/y18n","devDependencies":{"chai":"^3.2.0","coveralls":"^2.11.3","mocha":"^2.2.5","nyc":"^3.0.1","rimraf":"^2.4.2","standard":"^4.5.4"},"gitHead":"84becd79cc173a7dbfd97decbbc50aec85a52694","_id":"y18n@1.1.0","_shasum":"ba6bce6ebc50bcdb66bebadcd8f2c6fa5f5d6e4d","_from":".","_npmVersion":"2.13.2","_nodeVersion":"2.0.2","_npmUser":{"name":"bcoe","email":"ben@npmjs.com"},"dist":{"shasum":"ba6bce6ebc50bcdb66bebadcd8f2c6fa5f5d6e4d","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-1.1.0.tgz","integrity":"sha512-ZYi7XjwE80QtQwOyYLIlbW7Y8TMaG31E5SXoMqBsrk3HICl4Edgvh4bfGDioWGh9qDS91tTEN2kUNh/O3+B7jQ==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBixLyZR2MJElBOKMdHzyFziHVOYOvWUz/46F9orO78GAiEAuEbF51997/jG3JbMgiyrdfVteVjw9DlbgBdWcZ34COo="}]},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"}],"directories":{}},"2.0.0":{"name":"y18n","version":"2.0.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/bcoe/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/bcoe/y18n/issues"},"homepage":"https://github.com/bcoe/y18n","devDependencies":{"chai":"^3.2.0","coveralls":"^2.11.3","mocha":"^2.2.5","nyc":"^3.0.1","rimraf":"^2.4.2","standard":"^4.5.4"},"gitHead":"17c6b1146fc008e4e95645105f1733210733e786","_id":"y18n@2.0.0","_shasum":"c4bc87a9add7962f72d208f8d04660abd774e4de","_from":".","_npmVersion":"2.13.2","_nodeVersion":"2.0.2","_npmUser":{"name":"bcoe","email":"ben@npmjs.com"},"dist":{"shasum":"c4bc87a9add7962f72d208f8d04660abd774e4de","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-2.0.0.tgz","integrity":"sha512-pq9/O2K/Rdv5NPmMGwG17e5grO3gWSNrkzMY0XzvfIubvskV+uDOkHUlAA/AsrsxpWZ2rK9JsQmTULe7nWsDPw==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFsBhZSW+2Xpr9QJS6vWKTvPUbBChADmL2bSCcxdcMSVAiBusp+99+6whTw/3kO0D4ITpB/Ti9ufq/2k3tarnt12Cw=="}]},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"}],"directories":{}},"3.0.0":{"name":"y18n","version":"3.0.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/bcoe/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/bcoe/y18n/issues"},"homepage":"https://github.com/bcoe/y18n","devDependencies":{"chai":"^3.2.0","coveralls":"^2.11.3","mocha":"^2.2.5","nyc":"^3.0.1","rimraf":"^2.4.2","standard":"^4.5.4"},"gitHead":"58bf38fb645839f75f0ccfae16ef819fe0df2986","_id":"y18n@3.0.0","_shasum":"848c0149c64003945d62287d6a0a3b91d7fdf6f5","_from":".","_npmVersion":"2.13.2","_nodeVersion":"2.0.2","_npmUser":{"name":"bcoe","email":"ben@npmjs.com"},"dist":{"shasum":"848c0149c64003945d62287d6a0a3b91d7fdf6f5","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-3.0.0.tgz","integrity":"sha512-RoMA9LODvhCev/HdfjZMupROmZ2nLIACelUi7jJz2cG/BJw94kgL4+pYyObQlH5dQokks9KRClbNlXRz4ffqUw==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCWH2nTxft5LBLbzuoCAbJaZ7SWHFL8EQYl/ZWGYisVlgIgZNjIDKvjTAo6gz6ryJ/vDD6FWC/jaJTFpkmUtOWqiJ0="}]},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"}],"directories":{}},"3.1.0":{"name":"y18n","version":"3.1.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/bcoe/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/bcoe/y18n/issues"},"homepage":"https://github.com/bcoe/y18n","devDependencies":{"chai":"^3.2.0","coveralls":"^2.11.4","mocha":"^2.2.5","nyc":"^3.1.0","rimraf":"^2.4.2","standard":"^5.1.0"},"gitHead":"a75460b306db0097aed0c7e12362bb222e8de90b","_id":"y18n@3.1.0","_shasum":"dfcf19493e8dd9763a3d4bd0fa673de37cf8e860","_from":".","_npmVersion":"2.13.1","_nodeVersion":"0.12.7","_npmUser":{"name":"abg","email":"andrewbgoode@gmail.com"},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"},{"name":"abg","email":"andrewbgoode@gmail.com"}],"dist":{"shasum":"dfcf19493e8dd9763a3d4bd0fa673de37cf8e860","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-3.1.0.tgz","integrity":"sha512-sp7msmfHa8Y6fTvDUOWQ4tYkyRNDg5QfZLRz4OA3Vvdz4QJmg7+psnYfNvB3zKIk4tBPgn3u3rfn91qRIIPMog==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHyTK/F/291Bh51Obw9UEEDcK9hxiERciXs35sD7ySrBAiAe0/jqcQGFG7kltUwETwbjIKatVbJ/Gie1r+O36IGEIg=="}]},"directories":{}},"3.2.0":{"name":"y18n","version":"3.2.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/bcoe/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/bcoe/y18n/issues"},"homepage":"https://github.com/bcoe/y18n","devDependencies":{"chai":"^3.3.0","coveralls":"^2.11.4","mocha":"^2.3.3","nyc":"^3.2.2","rimraf":"^2.4.3","standard":"^5.3.1"},"gitHead":"a92184823afa8cccef4c8100a0b79338f85ab089","_id":"y18n@3.2.0","_shasum":"3bec64c93b730d924a6148c765757932433e34c8","_from":".","_npmVersion":"2.14.2","_nodeVersion":"0.12.7","_npmUser":{"name":"abg","email":"andrewbgoode@gmail.com"},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"},{"name":"abg","email":"andrewbgoode@gmail.com"}],"dist":{"shasum":"3bec64c93b730d924a6148c765757932433e34c8","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-3.2.0.tgz","integrity":"sha512-AwfXWS7HU/c65gWiqN1lvIUclNoXFParUa+ebDRF2MWcH8ZJZd9g9YtMjQblxzpBpLmMKHuU0yqZuRmBefgckQ==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCMMUKsD46qI5fkqEcDlvAQuW9GV9898OmUoEYNeLmxIQIgWRlV4xaZrCvaHwPHRV8eX/wm64+bM29sabOMw7HHRRo="}]},"directories":{}},"3.2.1":{"name":"y18n","version":"3.2.1","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/yargs/y18n.git"},"files":["index.js"],"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/yargs/y18n/issues"},"homepage":"https://github.com/yargs/y18n","devDependencies":{"chai":"^3.4.1","coveralls":"^2.11.6","mocha":"^2.3.4","nyc":"^6.1.1","rimraf":"^2.5.0","standard":"^5.4.1"},"gitHead":"34d6ad7bfeac67721ccbcf3bbcc761f33d787c90","_id":"y18n@3.2.1","_shasum":"6d15fba884c08679c0d77e88e7759e811e07fa41","_from":".","_npmVersion":"3.3.0","_nodeVersion":"3.2.0","_npmUser":{"name":"bcoe","email":"ben@npmjs.com"},"dist":{"shasum":"6d15fba884c08679c0d77e88e7759e811e07fa41","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-3.2.1.tgz","integrity":"sha512-Vd1yWKYGMtzFB6bAuTI7/POwJnwQStQXOe1PW1GmjUZgkaKYGc6/Pl3IDGFgplEklF65niuwBHeS5yve4+U01Q==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHdpGnO72/nxWF1WBXdJh1Siy/p71Zlj+cjF03rVImLBAiEAp3Kc9UkwBJiBUgNTHsLXbYjQuVYaChhF3DUKWJlkuN0="}]},"maintainers":[{"name":"bcoe","email":"ben@npmjs.com"},{"name":"nexdrew","email":"andrew@npmjs.com"}],"_npmOperationalInternal":{"host":"packages-13-west.internal.npmjs.com","tmp":"tmp/y18n-3.2.1.tgz_1458191070611_0.9606689948122948"},"directories":{}},"4.0.0":{"name":"y18n","version":"4.0.0","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls","release":"standard-version"},"repository":{"type":"git","url":"git+ssh://git@github.com/yargs/y18n.git"},"files":["index.js"],"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/yargs/y18n/issues"},"homepage":"https://github.com/yargs/y18n","devDependencies":{"chai":"^4.0.1","coveralls":"^3.0.0","mocha":"^4.0.1","nyc":"^11.0.1","rimraf":"^2.5.0","standard":"^10.0.0-beta.0","standard-version":"^4.2.0"},"gitHead":"45d2568800d6c57be045e76dc4984b2ef3432233","_id":"y18n@4.0.0","_npmVersion":"5.4.2","_nodeVersion":"8.6.0","_npmUser":{"name":"nexdrew","email":"andrewbgoode@gmail.com"},"dist":{"integrity":"sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==","shasum":"95ef94f85ecc81d007c264e190a120f0a3c8566b","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-4.0.0.tgz","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAG+hRS2Nlz+U3BYbNCOKiKTWMCNTDAWg5KEIqsQStxAAiA6lcUIFprodpgsZ/Q7OQSlK1woi/Cw9C4ItGQumY1njQ=="}]},"maintainers":[{"email":"andrewbgoode@gmail.com","name":"nexdrew"},{"email":"ben@npmjs.com","name":"bcoe"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n-4.0.0.tgz_1507662196433_0.6819839081726968"},"directories":{}},"5.0.0":{"name":"y18n","version":"5.0.0","description":"the bare-bones internationalization library used by yargs","exports":{"import":"./index.mjs","require":"./build/index.cjs"},"type":"module","module":"./build/lib/index.js","types":"./build/index.cjs.d.ts","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"index.js","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^2.0.2","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"335133842bfa398ba34d53ae17135d2604bf3e68","_id":"y18n@5.0.0","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-kmhFIXV4iTEaUF09bNkcjbwp0XKUKF5SGmKHusIj+oQOvZyfy3Z+UFkjJWbKhPiHVNExkEuw2UmCDBGELch4jA==","shasum":"8e056f5aa98abd2bcfa2cf2c3557c5d24ac472ae","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.0.tgz","fileCount":9,"unpackedSize":20481,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUvl0CRA9TVsSAnZWagAAq08P/2j00Ww2HPHYfHimXaq2\nC1ALZRCtELgrQPln5l1V0XCvht9/VoxUJajrvu9ff2ARCWzDe2zJZ+5TwONN\nO0dVwfG+idNkarLfSH2iduGDUmAfNsWzeWvbZ3UMQuDffRlG/vTN3IKavLkT\nHEGORY+J4oX9xJcDwvG203JXK1/mL6dyuO0Y2sCW58PgiJVqUgokTHwTCyEq\n/QNqFrRRsudvdp2YvRbVeQO8ez0+LcHlPr2R51EDO4K5ecEtr8vbNAtiXPQu\nGxM9fx4UAx454VwmXhtp8wMeTPQgoy6AdceYjyukooFXneQekrNoHALvUthB\n9rsRyL8t16OjltILG0WZ4nkAKquY5Se4HuuA4YaTwIddPs23Ia+oIhPdvbU6\n927zIuIuIc0jQJ93DLXvYS3YO+bz+1KgSTJEaHvAZIWJujjAclyzxLlFakoc\nukZml0RW12DkaXr+8CqJdvL/hy7to6YUiKd5hG2btql09vUqo0uGjZsSVvP+\nrEM0bBQDVq+NQku35R8uhNjQXG4twsywVAuJzIl8NI6Cn2ZPCJabcHFR8T8W\nm7FSGjhQEbQKnIusZIxAaeJq+A6utAlrmhX3sBhVFvB5QLk7ZEoZVKH2j2aj\nEhyYUIrgt6TD3VSNmD9avq7J6AO6j2cx0/N4CDY4uVQqMJmGffV0KkWgVT2r\nfU4f\r\n=Aj11\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC409Ch+vFVlCAN7SPzrmb/Vezs8xBkbIkngajNZBV/MQIhAPWGMQYhId/5ABPK6f6mB4vCtY/L97Td4CEd4JRouS3/"}]},"maintainers":[{"email":"ben@npmjs.com","name":"bcoe"},{"email":"andrewbgoode@gmail.com","name":"nexdrew"}],"_npmUser":{"name":"bcoe","email":"bencoe@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.0_1599273332047_0.682491923761469"},"_hasShrinkwrap":false},"5.0.1":{"name":"y18n","version":"5.0.1","description":"the bare-bones internationalization library used by yargs","exports":{"import":"./index.mjs","require":"./build/index.cjs"},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^2.0.2","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"786ffdc6cdabc15fd8e50240f662ea11e67ef5cc","_id":"y18n@5.0.1","_nodeVersion":"14.9.0","_npmVersion":"6.14.8","dist":{"integrity":"sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg==","shasum":"1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.1.tgz","fileCount":9,"unpackedSize":20729,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVCYICRA9TVsSAnZWagAABYwP/iojZVX1+4wla/SyTgEu\nc642hp7iVvg3eGo3J4yQ4/Pigiy4z1z9/dVXkQ/Evmm+C6I5UZcgMC2A8L4E\n7mkLeldGJFnGz80rPR3hZfxatTqUGFoN1Rr4x1gD4YwcG0koOkDbgsO5U3xy\nks7APhSIAHHG/Oj12Ldrj2d7HZpHzFgCsU/KsXc72APBlHblq0E6LDPa+Ioj\nis9SE7Xis+voKZ7A/Tuvk90T+mYBSFXaMR13cpzdo1AuUEmY5NV815PxWrq4\nXcPGtbcJ73k0d3a28AeyMbV5W3E2l72/TD7tJY0RpcBUO1G8gc4uTVb1LPQt\nlNJLOFrKi3cyBsdHbONXmrTRtGSwwkZr/6skYQ++3iX6WHxDLiRyQcWfJWQY\n5kATU1cHF0f7xPaqW/vyIt+QYeADAeS+a1557P9B9PWeOOj7MHwdDt7lIAHF\nrsAhYAryVZWH7bKavzUMIUZJ8ZeULg6gXcFOU2u2UJ07yy4f+p35U4W3uOFX\nkp//danQq003zVdNh3c5g2PwiJw/Mm3ZHs3VA21/5A8DZrhE6KsMmIclEWwy\nlOfjHYOEfn3xH+USqg1ipl6O2qpLOsMAy0Jw4UsdqB8AEyfKHrGUBvsPeeXK\n7YN1K3R4nhC57CTLtJkf4RVRdXjrUtPBZAXft2eJrunlTapQNAJPjP5kljMJ\ne3j8\r\n=1L39\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC/ugVZaCjUDky3n9hvpPVSJo9eRysgh5r1mE1reXMCTAIhAPb+ye2jgbMvFKNA+TX0idtkUSJgLDnsWgRkjWIAE+s8"}]},"maintainers":[{"email":"ben@npmjs.com","name":"bcoe"},{"email":"andrewbgoode@gmail.com","name":"nexdrew"}],"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.1_1599350279463_0.9581207231521827"},"_hasShrinkwrap":false},"6.0.0-alpha.0":{"name":"y18n","version":"6.0.0-alpha.0","description":"the bare-bones internationalization library used by yargs","exports":{"import":"./index.mjs","require":"./build/index.cjs"},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^2.0.2","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=12"},"standardx":{"ignore":["build"]},"readme":"# y18n\n\n[![NPM version][npm-image]][npm-url]\n[![js-standard-style][standard-image]][standard-url]\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\nThe bare-bones internationalization library used by yargs.\n\nInspired by [i18n](https://www.npmjs.com/package/i18n).\n\n## Examples\n\n_simple string translation:_\n\n```js\nconst __ = require('y18n')().__;\n\nconsole.log(__('my awesome string %s', 'foo'));\n```\n\noutput:\n\n`my awesome string foo`\n\n_using tagged template literals_\n\n```js\nconst __ = require('y18n')().__;\n\nconst str = 'foo';\n\nconsole.log(__`my awesome string ${str}`);\n```\n\noutput:\n\n`my awesome string foo`\n\n_pluralization support:_\n\n```js\nconst __n = require('y18n')().__n;\n\nconsole.log(__n('one fish %s', '%d fishes %s', 2, 'foo'));\n```\n\noutput:\n\n`2 fishes foo`\n\n## Deno Example\n\nAs of `v5` `y18n` supports [Deno](https://github.com/denoland/deno):\n\n```typescript\nimport y18n from \"https://deno.land/x/y18n/deno.ts\";\n\nconst __ = y18n({\n locale: 'pirate',\n directory: './test/locales'\n}).__\n\nconsole.info(__`Hi, ${'Ben'} ${'Coe'}!`)\n```\n\nYou will need to run with `--allow-read` to load alternative locales.\n\n## JSON Language Files\n\nThe JSON language files should be stored in a `./locales` folder.\nFile names correspond to locales, e.g., `en.json`, `pirate.json`.\n\nWhen strings are observed for the first time they will be\nadded to the JSON file corresponding to the current locale.\n\n## Methods\n\n### require('y18n')(config)\n\nCreate an instance of y18n with the config provided, options include:\n\n* `directory`: the locale directory, default `./locales`.\n* `updateFiles`: should newly observed strings be updated in file, default `true`.\n* `locale`: what locale should be used.\n* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)\n be allowed if a file matching the locale does not exist (e.g. `en_US.json`),\n default `true`.\n\n### y18n.\\_\\_(str, arg, arg, arg)\n\nPrint a localized string, `%s` will be replaced with `arg`s.\n\nThis function can also be used as a tag for a template literal. You can use it\nlike this: __`hello ${'world'}`. This will be equivalent to\n`__('hello %s', 'world')`.\n\n### y18n.\\_\\_n(singularString, pluralString, count, arg, arg, arg)\n\nPrint a localized string with appropriate pluralization. If `%d` is provided\nin the string, the `count` will replace this placeholder.\n\n### y18n.setLocale(str)\n\nSet the current locale being used.\n\n### y18n.getLocale()\n\nWhat locale is currently being used?\n\n### y18n.updateLocale(obj)\n\nUpdate the current locale with the key value pairs in `obj`.\n\n## Supported Node.js Versions\n\nLibraries in this ecosystem make a best effort to track\n[Node.js' release schedule](https://nodejs.org/en/about/releases/). Here's [a\npost on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).\n\n## License\n\nISC\n\n[npm-url]: https://npmjs.org/package/y18n\n[npm-image]: https://img.shields.io/npm/v/y18n.svg\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n[standard-url]: https://github.com/feross/standard\n","readmeFilename":"README.md","gitHead":"9c8a079a2917b71dd55086233f4272da521fc600","_id":"y18n@6.0.0-alpha.0","_nodeVersion":"12.18.3","_npmVersion":"6.14.6","dist":{"integrity":"sha512-J9CO+Qo98a30YwPMgXt1IetZS4823Y+KzBEWHPQYaO2sWcwtvVascTF0eNdUgU0Me3Efl36PnCygmVPdzqQmJg==","shasum":"eccd8e2176c2bf786dee53c4881cd88e3e4cbfc5","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-6.0.0-alpha.0.tgz","fileCount":9,"unpackedSize":20737,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXBQ2CRA9TVsSAnZWagAAM+4P/16Qogev2yixoICuoNHr\n7M6kQHMOigj5oh4YCavKfO3zm8bpQNEdu21bT2SXPYxMSgVzR8hZsJi0p56D\n68MiFTkXJLWKyjmEIrXprtLYDYOSm8BkKsFG4tiV1tgoYLryWIN8F8vzYD11\nRtB8P3Wu9INr+hbwF9XNUH0JyZfeEtmrabTodZT+/PnHV7pcnnBR45zuFuiM\nHDd4cPR5/2Ngn2+UAYF8ItIchCStN1uOFLBoq86wD98tciM2sGlu1ji7ueO3\nu1TBKXqHLjVLMPvd19ZdlAoAQwclvRk0TA2YBgqjhBc4MnnyanijwVQVKccd\nfQvPtptiSo9tgy9Z/kvXnUrsKPZ3RYus5rtyKNWI9g5jtI5R1QwYCtkTI9e7\n5+hszJIN9GJ0R49E3otwHlpVgppmZ+PEL/xfcBaAovJxHDxZbkD44EAjsBRA\nCs5A/Z9vBFJe7eytdaYPeZMEwbGPNvzZuCNfb3TpK8ZUG/TLxSsLozsVgexU\n3I5424QpbrDZ9LXYg7Ies495tc30yBhdw5TxA0sxgWzPRxaCM7kKgJlN0y3D\nxYPByUAu6f1seQ57BzQ62evhPswBSp0lyH8c1rHA3D7ufNoU1oWRb4Fj7eGu\nK1B1x/83WGMgVuJ5yimeqUDnhrKSHt7aU2sjKrwyAF7W9O1MqDuWz+fpLmIF\n5Zmv\r\n=4f0P\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCFzpI0NrxcPps5KHCsYn923+7lHs/T1OqQTYvQ3HRsagIgQr8hOuwwjwbo7FqAKcWwjyGi2et8NpeEF94qX8ydZyw="}]},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_6.0.0-alpha.0_1599870005446_0.2345770096577866"},"_hasShrinkwrap":false},"5.0.2":{"name":"y18n","version":"5.0.2","description":"the bare-bones internationalization library used by yargs","exports":{"import":"./index.mjs","require":"./build/index.cjs"},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^2.0.2","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"29e7571ece1cfa8fd5d197c284e384a2fbea79b2","_id":"y18n@5.0.2","_nodeVersion":"14.11.0","_npmVersion":"6.14.8","dist":{"integrity":"sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA==","shasum":"48218df5da2731b4403115c39a1af709c873f829","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.2.tgz","fileCount":9,"unpackedSize":21022,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfdh6fCRA9TVsSAnZWagAAsaAQAI9MDo11h0ZWwN9UGjFq\nZOFJyRuR7Er8m5VlrEs4Or5EoKnHm0kFR/baZrWWB0pSgJgQuNyqW7MXaYH8\nqFY8J5wr6pO/I6pyP91Ok6qH5gHfYhzgaH3FMFB9pP46HAVD6y+PW8qX628d\nmhN6Mo1LaLBSrudHpn4R4RRfY40dLaGECZFglZBkd5zSBSJXa81V4sJcuPB2\nCZGZMWpoCE7LBA645sCmchUEPnYyn0qLYInd4rfXtBy6DfrVCRJ+1Kf7l8Gq\nAk0/Yf/iMyzKMURxFt9vHfCfOovdXF+QQjJVNA9MgTUZ8aRjlPRkP73bsexG\nFEBBKsoE3sx9bIFEetQiUycCEqxNKp3oDJ9+Y208rKRK7mWiTQxXT9SS9p4Z\n2M/3KL8zjEJFysH6syIMv+Va52zwb6W2XnVNwUCjX09Fk6XJgqyu3nH/t6vJ\niDvPg0ZzhBMd8EOsOJ9rJfRpdlatipf7vRRfQsU/Tw+K2sZccxMNV1+B+0Hu\nIUlXtiIohIYz9qvcko9sZoDtr6Vm52NGCEaAgmn0gitBlwO5zVxGf7gS9qQx\nkRLLLcRehfPlVSvAg5R41BxIQN2yNtL2T2MnApoJ4W4+Ekq8UaFwFaVvQqN1\ngkXAbUKGH+YIxDVea9OlH4EegJdA9KK8+8+cn4v+Vy25LEJeq5+nx7wngdQi\nOUA/\r\n=hKDI\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDnixjnn1HpIII7inPp6TtNFjRQM5j2atdBPRvqic7xLgIhAIauZem9YcLJNKtVV5pBDNj00pTbaTXBU3VKv4kcphGB"}]},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.2_1601576606909_0.7193058628251092"},"_hasShrinkwrap":false},"5.0.3":{"name":"y18n","version":"5.0.3","description":"the bare-bones internationalization library used by yargs","exports":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"],"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"d3584d2f1b1d98bb8a7bfd4c8c92614d1684376a","_id":"y18n@5.0.3","_nodeVersion":"14.13.1","_npmVersion":"6.14.8","dist":{"integrity":"sha512-JeFbcHQ/7hVmMBXW6UB6Tg7apStHd/ztGz1JN78y3pFi/q0Ht1eA6PVkvw56gm7UA8fcJR/ziRlYEDMGoju0yQ==","shasum":"978115b82befe2b5c762bf55980b7b01a4a2d5d9","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.3.tgz","fileCount":9,"unpackedSize":21371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiPzQCRA9TVsSAnZWagAALcMQAJxWfGfIsRzQwm7umsq4\n9yw7cUQUOGn4ltyWJ1UUCZgd3wFw1VcmWYrx7P5Y9efAQbf5djIpFT6izBOO\n04fZb/ULw8z1VHFA1FE5TiQZU6JwbeflfNS1r+p+aNT/RnCm9GY+h01LglCQ\nb92YwoQPnBgq0NavI4DKmNyRKosb58jVCVRPj6aY8Clb7mffdd43L+71RR9L\neH+1mQ9buKCe6hFeAseIR/wIyjlmNpKSA6TdfT8URVphEBHDZHLpwTJj/6vr\nxro1ngcI+7YWSdAPiSGMna+/ilzKrK8R1tWjb/t/gr7x279/3c6xEfftgp/B\njV/U/6TRcvvwj6d409QBLxrPksZkmmfVGb2rPyrcYi6ZwZiMCNRhMea8cUwK\nvyp539i6SC/JQlJMRZL2fqfZ90iV2cwmEXPk5Cj9sCellLPuqwqudl3aca6n\nFoj+CCt4KaBpUbLOkoipqqpsxAeWes9/zO6CtbKm0uuCTa9Nd4HENYdNf+Id\nb6bWO9C3K5zO6ISSQakDPA6Ni+wQMRF3u2exRh/Of5W2UKt3+oHoTOhyh5pr\nJ0X/r1RA1bP21gH/txZZaUWEFNdFmaLOUYEjP4aob85rHOqf9YJV/pUWOtPX\n6tSfYEyIG0qQQZon2aTxsgIhiWvQjlEeKWSJRzVihMMsFI7OuEXfNVo9/0rS\nRiE9\r\n=KOEc\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFtzrTzqZXKNq1HTENkeaOR4/Kjyf80KAlN+1fppOgGTAiBmLgrIYkIHGOz2ZNHuWmp6bK0fMTVn50ekreTf9qxYsg=="}]},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.3_1602813135466_0.6790940796421885"},"_hasShrinkwrap":false},"5.0.4":{"name":"y18n","version":"5.0.4","description":"the bare-bones internationalization library used by yargs","exports":{".":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"]},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"f91f68f5b7ab163d57900b4b25e4b5d0dc5581d0","_id":"y18n@5.0.4","_nodeVersion":"14.13.1","_npmVersion":"6.14.8","dist":{"integrity":"sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ==","shasum":"0ab2db89dd5873b5ec4682d8e703e833373ea897","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.4.tgz","fileCount":9,"unpackedSize":21740,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfib/8CRA9TVsSAnZWagAA4XgP/07A3GZJ+GGFaBSlSrY2\nstsFs83Xd3S4QBLgHHFlSkeoAaTgilHqvgiQEhMPzlEzUkjwHIRa0dI18fv1\nxaNTmUC/sOJeRHpSUfGH9ZnIWBP3RHlczX6brrTL+S1P/umJAnkJCrcVJ4cU\nzTVC5zRaISPolwxWpm2yY+qEsGpwWR9TU+Ig44HPqDrQX9JYlCrBnRu/uplP\nyOHTsY2001iGmPFeA+cUNrB66z+vWKo9g9UleZrCPobtNyTct7MaDJzyoi/b\nPCsrBY6Qzof8fWqs0wjKe+0oL3OSri1d61dVJQ0ga7hyZqsqZ8VhH3YO8oNE\nYHigUi94pndxW/Voul/8LyIub5BELMUr5xhfW9txuMNqP6ewO3FG3mqTDwRH\n/gXdIPEGI/c+5XtI5B5/MYdDFmc0obNJF76u+hzGM7iz0eGKDeU/h3N8n9G1\njS5kfSRv4PTqb8M9LgfFlbn3tEqTVHicTTw/2z6cgN03OGxXcnZ7UTqZTQFl\nwaIMGMVnmKjSKWN0pqnWBM4AWVjyxXe71bYbRdvP2UewWELGGrkKj/pZpgIJ\nRqqi80pvjY+xapty6nj21dp/FdoHQZGEZIkwR3ajkA0IROfk21ES8FV+niIU\nhyd9UIG6RKrBXmdUBUKAv3wHAI9C23C2O9AEbxN0V31aXVsAiUrpUxjhQQl/\nwNqM\r\n=lPcN\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICd45z4UCqGaTKOYgwSz1pw9COdS+/c233jCqZGYpUT9AiBpCdvs+szdZIfc/mKQfvzUWVMxlZwhByPsa0GE1TgElQ=="}]},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.4_1602863099464_0.5549476304057792"},"_hasShrinkwrap":false},"5.0.5":{"name":"y18n","version":"5.0.5","description":"the bare-bones internationalization library used by yargs","exports":{".":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"]},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"e343b70fbe8245c0a02a5615eee1bbd6bcaac1d6","_id":"y18n@5.0.5","_nodeVersion":"14.13.1","_npmVersion":"6.14.8","dist":{"integrity":"sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==","shasum":"8769ec08d03b1ea2df2500acef561743bbb9ab18","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.5.tgz","fileCount":9,"unpackedSize":22062,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJflZdYCRA9TVsSAnZWagAAtyQP/jnPo33XLRtlucweLctK\nj+t+12c1vEa5tFZuotgZjQPu3tyNAeneh8YAdOCMV+f1YjRqe7SJxI4qlKED\n+iKET603NeVA3ogz3bYyjLf3bip2dJ/DJ1n88a3sOn1YNK7HnoIF7/sbRmCF\ny8iskRhiZQjNSYc+i0Ozx/n/7DTF7nxKqdIEe16xC+tybjP3044RvhckNI5T\nZkom5HwFbYHOarvcyEZk5I3AeBhcJmSrPc5I4MUDj7Mn7gsQUlh88VLe1lU6\ngL+xkMm5bkhoA3O/HVa8b0ZDpW0U2UVZJGCsJZ2uUy1A4NaBysCOuEr5qngi\nMlosNORPQX1KbDjLLZx1KVswPLoSiBGngbUNSWGHkFI03rrfFLDHh5G+lRbq\nPcY5rQCBdkK8jojAUOi/blzSVKLoAXKHQpbiSFgWcn4VNdMuK/rrdHmrJyMz\nkOaXhMNXOgn6DA9nlq/vOn59dgAKpwlmf2E15iQQ2CP1sWk7Qmk1wcY6dRDV\nR/djfuO44IOa6svqauAgQ49lFH243dCoCgXi03aZryz0HhZaP1Lw6FItI05q\ntFwsGIrfT5TnOxrPGL2qMcHrDrGusnJBPV+4XglGe8cMW8Ap59nC1B1dhIpK\nAgyU+amJdLvS1KQUiuumIqXNdWYEDlVvibzCmXuK4/ExdCdGqKxgZ9CTKlOw\nuOqO\r\n=hOib\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIA3soP+S7MThcC0sLV+KT/w7ssjDTtlhfDkDwAj0K35eAiEA8k8CtjMOlyTRaObrtHkuBUea1gXj0+F6cgCnyn3ga4U="}]},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.5_1603639128070_0.21918731679310355"},"_hasShrinkwrap":false},"4.0.1":{"name":"y18n","version":"4.0.1","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls","release":"standard-version"},"repository":{"type":"git","url":"git+ssh://git@github.com/yargs/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/yargs/y18n/issues"},"homepage":"https://github.com/yargs/y18n","devDependencies":{"chai":"^4.0.1","coveralls":"^3.0.0","mocha":"^4.0.1","nyc":"^11.0.1","rimraf":"^2.5.0","standard":"^10.0.0-beta.0","standard-version":"^4.2.0"},"readme":"# y18n\n\n[![Build Status][travis-image]][travis-url]\n[![Coverage Status][coveralls-image]][coveralls-url]\n[![NPM version][npm-image]][npm-url]\n[![js-standard-style][standard-image]][standard-url]\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\nThe bare-bones internationalization library used by yargs.\n\nInspired by [i18n](https://www.npmjs.com/package/i18n).\n\n## Examples\n\n_simple string translation:_\n\n```js\nvar __ = require('y18n').__\n\nconsole.log(__('my awesome string %s', 'foo'))\n```\n\noutput:\n\n`my awesome string foo`\n\n_using tagged template literals_\n\n```js\nvar __ = require('y18n').__\nvar str = 'foo'\n\nconsole.log(__`my awesome string ${str}`)\n```\n\noutput:\n\n`my awesome string foo`\n\n_pluralization support:_\n\n```js\nvar __n = require('y18n').__n\n\nconsole.log(__n('one fish %s', '%d fishes %s', 2, 'foo'))\n```\n\noutput:\n\n`2 fishes foo`\n\n## JSON Language Files\n\nThe JSON language files should be stored in a `./locales` folder.\nFile names correspond to locales, e.g., `en.json`, `pirate.json`.\n\nWhen strings are observed for the first time they will be\nadded to the JSON file corresponding to the current locale.\n\n## Methods\n\n### require('y18n')(config)\n\nCreate an instance of y18n with the config provided, options include:\n\n* `directory`: the locale directory, default `./locales`.\n* `updateFiles`: should newly observed strings be updated in file, default `true`.\n* `locale`: what locale should be used.\n* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)\n be allowed if a file matching the locale does not exist (e.g. `en_US.json`),\n default `true`.\n\n### y18n.\\_\\_(str, arg, arg, arg)\n\nPrint a localized string, `%s` will be replaced with `arg`s.\n\nThis function can also be used as a tag for a template literal. You can use it\nlike this: __`hello ${'world'}`. This will be equivalent to\n`__('hello %s', 'world')`.\n\n### y18n.\\_\\_n(singularString, pluralString, count, arg, arg, arg)\n\nPrint a localized string with appropriate pluralization. If `%d` is provided\nin the string, the `count` will replace this placeholder.\n\n### y18n.setLocale(str)\n\nSet the current locale being used.\n\n### y18n.getLocale()\n\nWhat locale is currently being used?\n\n### y18n.updateLocale(obj)\n\nUpdate the current locale with the key value pairs in `obj`.\n\n## License\n\nISC\n\n[travis-url]: https://travis-ci.org/yargs/y18n\n[travis-image]: https://img.shields.io/travis/yargs/y18n.svg\n[coveralls-url]: https://coveralls.io/github/yargs/y18n\n[coveralls-image]: https://img.shields.io/coveralls/yargs/y18n.svg\n[npm-url]: https://npmjs.org/package/y18n\n[npm-image]: https://img.shields.io/npm/v/y18n.svg\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n[standard-url]: https://github.com/feross/standard\n","readmeFilename":"README.md","gitHead":"8dc75802f3aa944bf9a827213969d64834621215","_id":"y18n@4.0.1","_nodeVersion":"14.14.0","_npmVersion":"6.14.8","dist":{"integrity":"sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==","shasum":"8db2b83c31c5d75099bb890b23f3094891e247d4","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-4.0.1.tgz","fileCount":5,"unpackedSize":10681,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxYOnCRA9TVsSAnZWagAA5P8P/iI/sSK4+8fUc1ICCECl\nA2mO6qvLQL5986og3flimqfqExSQTdBeRgkQQBVRF3jgFPTS9yELqnP0WFGK\nyn87WKheRLCCTl0kWVGIsdV4VJwRdGe0AspfCU0jeNDAP1ywZvzMPFlxS+Pd\nt3Re8izZvrv9w5kmZFQdlZfusUvdBbS/f6MrS01kNkP0whgaAsFlhsEx/7TT\n+A3I5Rn/DHhmEc2yQx1ur19XQqlz4I5S4lJVPM1Or9kzS7w+UkxygY15A0Vy\n15d4Y91tOVpYlQ8BJA0Tiv5KonZXvnEdE5llOPHKFWrtAn+/IJHSze4lXcja\nUJ3Ah3hJgXNphR4uRSvLwBRJK8weRxGSq4rjAsmVqh+IYzl6DQqLxrcob3K0\nS2Ya9yj/+SBuYcXJPp98V1tOh53lW6sFrNcR4KvEsZhQauZoCDGB5w8qce5N\npyKDWpl+PPs4AIcyHbA7UsoernUefzj4zaISn4LCM0f+XiIf6UbtjX4S0b4c\nPLCngNnixH880sC13rqscvRiScpNGeGbX29y70H/S+sWFWX9t90P8VcxVA94\nxfckt2qTrEXnzm0eBNljLctzimomO/pkCsHDMB9LoLgGzTZy3pFmBJH+qGq2\nV1tpvtcSSK77Hw38yWBB6x0r/VqoVqpt34wE6LnJ8JFpUmgptQpdaVVrgrSr\nJv5I\r\n=EkkS\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCzSxGX2xT88JC38tkOY3FMEOQmn8VoRrVWPeBhis/TMgIgc4QveU6amA7jzOVwZFIu0xRnkHJHvmCNxFvxH7q7ey0="}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_4.0.1_1606779814627_0.7420427256466908"},"_hasShrinkwrap":false},"3.2.2":{"name":"y18n","version":"3.2.2","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls"},"repository":{"type":"git","url":"git+ssh://git@github.com/yargs/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/yargs/y18n/issues"},"homepage":"https://github.com/yargs/y18n","devDependencies":{"chai":"^3.4.1","coveralls":"^2.11.6","mocha":"^3.0.0","nyc":"^10.0.0","rimraf":"^2.5.0","standard":"^10.0.0-beta.0"},"readme":"# y18n\n\n[![Build Status][travis-image]][travis-url]\n[![Coverage Status][coveralls-image]][coveralls-url]\n[![NPM version][npm-image]][npm-url]\n[![js-standard-style][standard-image]][standard-url]\n\nThe bare-bones internationalization library used by yargs.\n\nInspired by [i18n](https://www.npmjs.com/package/i18n).\n\n## Examples\n\n_simple string translation:_\n\n```js\nvar __ = require('y18n').__\n\nconsole.log(__('my awesome string %s', 'foo'))\n```\n\noutput:\n\n`my awesome string foo`\n\n_pluralization support:_\n\n```js\nvar __n = require('y18n').__n\n\nconsole.log(__n('one fish %s', '%d fishes %s', 2, 'foo'))\n```\n\noutput:\n\n`2 fishes foo`\n\n## JSON Language Files\n\nThe JSON language files should be stored in a `./locales` folder.\nFile names correspond to locales, e.g., `en.json`, `pirate.json`.\n\nWhen strings are observed for the first time they will be\nadded to the JSON file corresponding to the current locale.\n\n## Methods\n\n### require('y18n')(config)\n\nCreate an instance of y18n with the config provided, options include:\n\n* `directory`: the locale directory, default `./locales`.\n* `updateFiles`: should newly observed strings be updated in file, default `true`.\n* `locale`: what locale should be used.\n* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)\n be allowed if a file matching the locale does not exist (e.g. `en_US.json`),\n default `true`.\n\n### y18n.\\_\\_(str, arg, arg, arg)\n\nPrint a localized string, `%s` will be replaced with `arg`s.\n\n### y18n.\\_\\_n(singularString, pluralString, count, arg, arg, arg)\n\nPrint a localized string with appropriate pluralization. If `%d` is provided\nin the string, the `count` will replace this placeholder.\n\n### y18n.setLocale(str)\n\nSet the current locale being used.\n\n### y18n.getLocale()\n\nWhat locale is currently being used?\n\n### y18n.updateLocale(obj)\n\nUpdate the current locale with the key value pairs in `obj`.\n\n## License\n\nISC\n\n[travis-url]: https://travis-ci.org/yargs/y18n\n[travis-image]: https://img.shields.io/travis/yargs/y18n.svg\n[coveralls-url]: https://coveralls.io/github/yargs/y18n\n[coveralls-image]: https://img.shields.io/coveralls/yargs/y18n.svg\n[npm-url]: https://npmjs.org/package/y18n\n[npm-image]: https://img.shields.io/npm/v/y18n.svg\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n[standard-url]: https://github.com/feross/standard\n","readmeFilename":"README.md","gitHead":"90401eea9062ad498f4f792e3fff8008c4c193a3","_id":"y18n@3.2.2","_nodeVersion":"14.14.0","_npmVersion":"6.14.8","dist":{"integrity":"sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==","shasum":"85c901bd6470ce71fc4bb723ad209b70f7f28696","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-3.2.2.tgz","fileCount":5,"unpackedSize":9006,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf85scCRA9TVsSAnZWagAAJiEP/1w7LDZGx4QGYybO80Oi\nhb/vkfnYaBrMQCL2ApR3Vc1UaHSQh2gso6pR4C5i7SkdY3L0NpBlRotuqAOZ\nfWv5x2fDJJeqmBZFyBaklaTRTUzOoVVdanKx9E3ZGkne1H/x7ZqRmPSV+DDv\nCq3rT+z890kgSDmEl35D8d97imrmfxJdVK4izAAIlpakbtTQkLHIGfeja7iu\nCDxz0kFWjNx3jEe2jKHyEKwMKYvU57RQl0fdYFMa1LTKsYt5rynSMhOenu4p\n2kB9Har39UsBrZQVFemXjeMyLt2F343cngebFo7XxVQyI9m0cFtqeynI517r\nJFxeMFeOnfy8msT+wHn7sLlKrtRPX6pJuEBCIJanHdajjehxXAo0zcuJYeMU\n3uJnjyl1I5DzvvczoyCE0aJeexb6x/4RUvRusnk1AhJuXTm1m0FXDOaIHAHq\nNR08NWiliNkIwaPj27YyqPnC94k4brhA68lAby5SSV/hpRypwIC9n38/Jhuj\nh+C1KK/cuPjWvxWE4TNkKf8D/3Iv+7lacD5RcppVgRntK4V9rs8WQEXsxonm\nxK6k/rvWqLMCWpP9rC3Gy76LDhIQwaH5222NpMGWid1qMMf4mKGqN9igisRo\nNOH+zbfCgUINjzWaVEh6nKq0HyvAFJ0MJofrV+wpOBRiAzDWPRvht2MA1P6H\nkeCM\r\n=BVy7\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAV9u4ild2q71rpr9c2FUv9D3Heu7Zh3DkdkQiIN9NNpAiAGCPz32t9O5n+7KaO9wUbhi7RhQb5MPwzcjhkm9wysVA=="}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_3.2.2_1609800476248_0.15203860144039116"},"_hasShrinkwrap":false},"5.0.6":{"name":"y18n","version":"5.0.6","description":"the bare-bones internationalization library used by yargs","exports":{".":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"]},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx **/*.ts **/*.cjs **/*.mjs","fix":"standardx --fix **/*.ts **/*.cjs **/*.mjs","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^7.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"0884534e044d1cc1239b021232dc6f358a71fd3e","_id":"y18n@5.0.6","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-PlVX4Y0lDTN6E2V4ES2tEdyvXkeKzxa8c/vo0pxPr/TqbztddTP0yn7zZylIyiAuxerqj0Q5GhpJ1YJCP8LaZQ==","shasum":"8236b05cfc5af6a409f41326a4847c68989bb04f","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.6.tgz","fileCount":9,"unpackedSize":22909,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgamcoCRA9TVsSAnZWagAAJKAP/2sX4bd3dx2c0oN1D9uh\nmsCLv1Rcsn1+3xDFMoakFzAKcuXhYyeM6rRUu1dh9NUYm7lCSaWce4OJn4y4\nddv+lz+nSPkYeWH/lasYe5PUFbIsVg6NJLjlHK2JyXi5KZDQXUDio5XDV+tO\nPEc9n4a1qJhzsLOb5kUyQBZmi937Airjqa7Rpn5mPxbrAWILEyLNBf12bf8l\nV5s2Whu+cc/3GXAM59j+EBcm9JZFD2UeY6HT9JxAe32+g5uXuK2w+kS7EdVx\nuRr4QBbPmWw0Csx3lj7PPKn2kJOgjWD0BVjpvECi4K8i0XCgWp4xNZbY/Fj6\nWjha+JHlqGQQNLMzm4gGypqmitCfH8GBdpm2Y9TbKN6yZdEaSOtQTwSBdZH1\nZEa8oeh9N6L4sOrkMFrYK/g0tWpX6/AlmbZVVbPxiDEBCSDmaejDErmSJwGK\n/5iRjs7bE92c5axpoPeha38gWnrbmEb6f9M2lQbWZ90vWVA7mCtmb+aMavvb\nlCOxIsARS41Zr2UHgOhHSq+G3a7aHXKq2MzrE2OSbHEY0qoAu/AjF6CZi/f+\ndlrbKuoiJnEKHJeJDapedbMMR9K7OZpjnDL+LU2F1pLIZdHuu7JuTZFgJnaE\ny8JkpXxj84kSp+4xJvsW9bVGNoQIpGBCCRpcMbK6hh6PNoqswFldpflSl9yP\nLp2u\r\n=0hFW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDYfh/DyFqhrzmkSbv/4KLXLqsfNG6S/w+loCrmCHNmEAIhAKvcxSCH/jycMOQipLF2VF22bdCweS+1Gq7PpL6UNcIC"}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.6_1617585960208_0.7906647956829878"},"_hasShrinkwrap":false},"4.0.2":{"name":"y18n","version":"4.0.2","description":"the bare-bones internationalization library used by yargs","exports":{".":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"]},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx '**/*.ts' '**/*.cjs' '**/*.mjs'","fix":"standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^5.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"readme":"# y18n\n\n[![NPM version][npm-image]][npm-url]\n[![js-standard-style][standard-image]][standard-url]\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\nThe bare-bones internationalization library used by yargs.\n\nInspired by [i18n](https://www.npmjs.com/package/i18n).\n\n## Examples\n\n_simple string translation:_\n\n```js\nconst __ = require('y18n')().__;\n\nconsole.log(__('my awesome string %s', 'foo'));\n```\n\noutput:\n\n`my awesome string foo`\n\n_using tagged template literals_\n\n```js\nconst __ = require('y18n')().__;\n\nconst str = 'foo';\n\nconsole.log(__`my awesome string ${str}`);\n```\n\noutput:\n\n`my awesome string foo`\n\n_pluralization support:_\n\n```js\nconst __n = require('y18n')().__n;\n\nconsole.log(__n('one fish %s', '%d fishes %s', 2, 'foo'));\n```\n\noutput:\n\n`2 fishes foo`\n\n## Deno Example\n\nAs of `v5` `y18n` supports [Deno](https://github.com/denoland/deno):\n\n```typescript\nimport y18n from \"https://deno.land/x/y18n/deno.ts\";\n\nconst __ = y18n({\n locale: 'pirate',\n directory: './test/locales'\n}).__\n\nconsole.info(__`Hi, ${'Ben'} ${'Coe'}!`)\n```\n\nYou will need to run with `--allow-read` to load alternative locales.\n\n## JSON Language Files\n\nThe JSON language files should be stored in a `./locales` folder.\nFile names correspond to locales, e.g., `en.json`, `pirate.json`.\n\nWhen strings are observed for the first time they will be\nadded to the JSON file corresponding to the current locale.\n\n## Methods\n\n### require('y18n')(config)\n\nCreate an instance of y18n with the config provided, options include:\n\n* `directory`: the locale directory, default `./locales`.\n* `updateFiles`: should newly observed strings be updated in file, default `true`.\n* `locale`: what locale should be used.\n* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)\n be allowed if a file matching the locale does not exist (e.g. `en_US.json`),\n default `true`.\n\n### y18n.\\_\\_(str, arg, arg, arg)\n\nPrint a localized string, `%s` will be replaced with `arg`s.\n\nThis function can also be used as a tag for a template literal. You can use it\nlike this: __`hello ${'world'}`. This will be equivalent to\n`__('hello %s', 'world')`.\n\n### y18n.\\_\\_n(singularString, pluralString, count, arg, arg, arg)\n\nPrint a localized string with appropriate pluralization. If `%d` is provided\nin the string, the `count` will replace this placeholder.\n\n### y18n.setLocale(str)\n\nSet the current locale being used.\n\n### y18n.getLocale()\n\nWhat locale is currently being used?\n\n### y18n.updateLocale(obj)\n\nUpdate the current locale with the key value pairs in `obj`.\n\n## Supported Node.js Versions\n\nLibraries in this ecosystem make a best effort to track\n[Node.js' release schedule](https://nodejs.org/en/about/releases/). Here's [a\npost on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).\n\n## License\n\nISC\n\n[npm-url]: https://npmjs.org/package/y18n\n[npm-image]: https://img.shields.io/npm/v/y18n.svg\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n[standard-url]: https://github.com/feross/standard\n","readmeFilename":"README.md","gitHead":"bcfdd05cf8405593cd3261e25fde4976efd8672f","_id":"y18n@4.0.2","_nodeVersion":"10.24.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-DnBDwcL54b5xWMM/7RfFg4xs5amYxq2ot49aUfLjQSAracXkGvlZq0txzqr3Pa6Q0ayuCxBcwTzrPUScKY0O8w==","shasum":"c504495ba9b59230dd60226d1dd89c3c0a1b745e","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-4.0.2.tgz","fileCount":9,"unpackedSize":22085,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbQ6gCRA9TVsSAnZWagAA9gMQAIB0Hp9P+JtCe+iYpU4n\neIVvRlgeZvPz/sX32aXQO70RE8oKJal4i3lECi9iA2gWeb1MFHC0iiwQWxmM\ne73ERJ1PPWOEuaozP9I+HGkx+y8IaLeMl0/Hk75hDTkWlY8IY6awoDwVj7/c\nY3d/A/+442k+oOM2HdN8WSN7dbjRiwMp3/0JxqQY+o1njWbUBy77cExXfDOM\nbkwLxPO/El/QzfctyDj2YKHpvdNcBOzaDRKN6vH5V6slV8TfjX+yNyewqjQz\nvS1pONXgRb7Yff8qatzfW1Uts85cQ1kCv7Opt47wcbNOBmcq2vKhysuTyp4P\nHsx7AKV9NudVTsa2vkd4R0w7IfQgdn8V31mENkPEFsUdb7IAGU8TaJJUAl1k\nFrEzU9KAiO6xok1EMq01WLUWny1ATAL6hVJc/gimIKk0ob2FOYff3WdC4iHE\nLantYcxGvpXpK8ks0OEeKtQw1tSwk+zn6n7NAqzrCM4JEm++raM0qn3IqUf0\ntzSeCLGyAUvG2O7kwWHSOYXx9CguvWZCXudHE/hizyU7+ciSL+7pX8bLrsG2\nikcAOGJXjzWD9zCfqnwy3XIc+Vkub7FQITeeF+gaAdjcgTHLv0pjrhpWC+ie\nvOrB+0bbu1c44c3GBuxIwhSeUiRtXYCPDT4VgBJknuowgKH7t3GcEw0J+Sgi\nXpV1\r\n=q3dj\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHNRSpQmiqU2CH1vd8F3M5fbFSaK3PWHPEqw0ItgRrv3AiEAqXJON/ZV+luaoLjsSNd4VerwEeUSTEjVLZcbR9mpbT8="}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_4.0.2_1617759904116_0.11983796019170101"},"_hasShrinkwrap":false},"5.0.7":{"name":"y18n","version":"5.0.7","description":"the bare-bones internationalization library used by yargs","exports":{".":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"]},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx **/*.ts **/*.cjs **/*.mjs","fix":"standardx --fix **/*.ts **/*.cjs **/*.mjs","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^7.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"76718909722fd1765f85ba4d314f1b2ebd8b1239","_id":"y18n@5.0.7","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-oOhslryvNcA1lB9WYr+M6TMyLkLg81Dgmyb48ZDU0lvR+5bmNDTMz7iobM1QXooaLhbbrcHrlNaABhI6Vo6StQ==","shasum":"0c514aba53fc40e2db911aeb8b51566a3374efe7","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.7.tgz","fileCount":9,"unpackedSize":23196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbQ8DCRA9TVsSAnZWagAArsoP/j0uE5XVd3c+XVNb98Pn\nNq2Mx1a/fGh30Lj/mPaOwm2HG6BywRWGhYmO6xmwVTJf/ZYKhk+V050RxLoR\n9kRI8O5KHRT0iPHiHgoEz+4i65/b336SgcSLWIJDHEK/YYLOsjHxZU5BGHo5\n24c89FyaM6pK8Uq2azckQxtWrE54bzRiWe41gQCCZ6RoRnCG8GzUM+aH1GlB\n/uaSRL5AaS0mj5Ew19Evb28MSMw1lG4Ub5BmNpwAawmuntukeriP8nDSklEo\nzcjzC+lfQy42Q0NVyfoC0mdUeGwg7H7z5PrpJ4uj0MRFjD34lFAXgcVFlvyZ\neK8WRa2XrldC9CRaoCPe5LDK+rP3aa0yMFhdDMMXVdqPyjgkXlx47ysaGC7D\n60+T8DfGAx8ZDmhxqn8EIPwOmid1dZg9aVJpi22VzKnFEA7janqaqcs88T2q\nAYr9uXWHDLnzAhm/z3rlAXjJNfVlRBAkWGN9FU0QdaIFUj71EIoXCDHbNALg\n8bFJDNEjzC4SHaIDhe5t5ZaCqDKFT13kowz1ISQ+6jln6yO68g1YtbmK3xCw\nlRZQC0xplDt2CLaa7fDqHJG4wi4cr+A9J9F4+tR3vqEN7cWGTs5DqL5FZbLj\nsmlJ02uS9JtVAgUwnF61NWaiCl3UUHf2PftU0Qbi/7hocbz9GyS91hyX6A1n\nEH5k\r\n=E5K+\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCeslun2xOuyVVDbxuvBix42mRH0+soZm77UUQuc6KhQAIhAMzf50oOIs6UzP0UUObiW/draOZ2RguW4/++G9lmb5Pc"}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.7_1617760002805_0.7333351141980393"},"_hasShrinkwrap":false},"4.0.3":{"name":"y18n","version":"4.0.3","description":"the bare-bones internationalization library used by yargs","main":"index.js","scripts":{"pretest":"standard","test":"nyc mocha","coverage":"nyc report --reporter=text-lcov | coveralls","release":"standard-version"},"repository":{"type":"git","url":"git+ssh://git@github.com/yargs/y18n.git"},"keywords":["i18n","internationalization","yargs"],"author":{"name":"Ben Coe","email":"ben@npmjs.com"},"license":"ISC","bugs":{"url":"https://github.com/yargs/y18n/issues"},"homepage":"https://github.com/yargs/y18n","devDependencies":{"chai":"^4.0.1","coveralls":"^3.0.0","mocha":"^4.0.1","nyc":"^11.0.1","rimraf":"^2.5.0","standard":"^10.0.0-beta.0","standard-version":"^4.2.0"},"readme":"# y18n\n\n[![Build Status][travis-image]][travis-url]\n[![Coverage Status][coveralls-image]][coveralls-url]\n[![NPM version][npm-image]][npm-url]\n[![js-standard-style][standard-image]][standard-url]\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\nThe bare-bones internationalization library used by yargs.\n\nInspired by [i18n](https://www.npmjs.com/package/i18n).\n\n## Examples\n\n_simple string translation:_\n\n```js\nvar __ = require('y18n').__\n\nconsole.log(__('my awesome string %s', 'foo'))\n```\n\noutput:\n\n`my awesome string foo`\n\n_using tagged template literals_\n\n```js\nvar __ = require('y18n').__\nvar str = 'foo'\n\nconsole.log(__`my awesome string ${str}`)\n```\n\noutput:\n\n`my awesome string foo`\n\n_pluralization support:_\n\n```js\nvar __n = require('y18n').__n\n\nconsole.log(__n('one fish %s', '%d fishes %s', 2, 'foo'))\n```\n\noutput:\n\n`2 fishes foo`\n\n## JSON Language Files\n\nThe JSON language files should be stored in a `./locales` folder.\nFile names correspond to locales, e.g., `en.json`, `pirate.json`.\n\nWhen strings are observed for the first time they will be\nadded to the JSON file corresponding to the current locale.\n\n## Methods\n\n### require('y18n')(config)\n\nCreate an instance of y18n with the config provided, options include:\n\n* `directory`: the locale directory, default `./locales`.\n* `updateFiles`: should newly observed strings be updated in file, default `true`.\n* `locale`: what locale should be used.\n* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)\n be allowed if a file matching the locale does not exist (e.g. `en_US.json`),\n default `true`.\n\n### y18n.\\_\\_(str, arg, arg, arg)\n\nPrint a localized string, `%s` will be replaced with `arg`s.\n\nThis function can also be used as a tag for a template literal. You can use it\nlike this: __`hello ${'world'}`. This will be equivalent to\n`__('hello %s', 'world')`.\n\n### y18n.\\_\\_n(singularString, pluralString, count, arg, arg, arg)\n\nPrint a localized string with appropriate pluralization. If `%d` is provided\nin the string, the `count` will replace this placeholder.\n\n### y18n.setLocale(str)\n\nSet the current locale being used.\n\n### y18n.getLocale()\n\nWhat locale is currently being used?\n\n### y18n.updateLocale(obj)\n\nUpdate the current locale with the key value pairs in `obj`.\n\n## License\n\nISC\n\n[travis-url]: https://travis-ci.org/yargs/y18n\n[travis-image]: https://img.shields.io/travis/yargs/y18n.svg\n[coveralls-url]: https://coveralls.io/github/yargs/y18n\n[coveralls-image]: https://img.shields.io/coveralls/yargs/y18n.svg\n[npm-url]: https://npmjs.org/package/y18n\n[npm-image]: https://img.shields.io/npm/v/y18n.svg\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n[standard-url]: https://github.com/feross/standard\n","readmeFilename":"README.md","gitHead":"0aa97c508ea31efadd2a27f98fed6873eefc963e","_id":"y18n@4.0.3","_nodeVersion":"12.21.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==","shasum":"b5f259c82cd6e336921efd7bfd8bf560de9eeedf","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-4.0.3.tgz","fileCount":5,"unpackedSize":10991,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbfRkCRA9TVsSAnZWagAA14wP/iqe8XL9SWtCbOGAoOAZ\nkbCD5JlnBrfniybyuX+UaPG136jHEgEYRfaH4KGH6tWtpLmCcL0haK8ZsACk\nqLQP/G5w4b+QWpLAX2wBzQRMxOeQAyIfFuuwoRoyoAv+8oyFezX0/QAIRxu6\ng5O8c0pWtQRMN7hqV2RsbAL9CokxDuUEFjephKLvo9Fn9X4A3TuBuSzfvUbo\n5CuIeKRnsI7Ucp+nzrZ93PaqBRJhibsXSx44SL3oCfm44+vZCZyvBYuhYr5F\ngR46eCldtVEj9NEiR+/7jYPHi2Ijq7F8Ll9s3hqVujfAq9S+aIZ1lAktJB3M\nOVACLqQFd9ZGyd3Jlx0//SiwFA/U5UbHm4YmuqjpFOcvwJBwboi9ERdDXnb4\nyvup++XQWk0+OYEKgohtCevwkffHp9Tyz48nz5KEGwhlo/T1J7k0RFnKdA/Y\nwbSzh+JfcQKmuVF136L30hD+iP6oQgYPBwMFTpIuz7dlSkob32FMO9aAY0IQ\nOyDeF5exM5DTLOhq0dcZ4SUKK/aJ0u7td9qaaZWxctPZv8RgCRDkKPRPVKIr\neHfzvbCPSvbJbeY7ogCqowSt7OSMGcd6MS+zEUU5r6OX3ePX4oZPeAlwVAyo\nQ8WNDkD+1D061NCuDyatdyyr62qx1bNZwsEoS7p+y6EeEEFcyK0yWTkb1Ii0\nrCkB\r\n=siRq\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDnkHHWjnL+X5hHbt7TnnNsW9bcnUjymQjk6IXCEasnKwIhAK4hlGypSf7vIAiZFz8Hv0h+v3IxRqN4WzjTi6whxXyY"}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_4.0.3_1617818723912_0.7126177310246005"},"_hasShrinkwrap":false},"5.0.8":{"name":"y18n","version":"5.0.8","description":"the bare-bones internationalization library used by yargs","exports":{".":[{"import":"./index.mjs","require":"./build/index.cjs"},"./build/index.cjs"]},"type":"module","module":"./build/lib/index.js","keywords":["i18n","internationalization","yargs"],"homepage":"https://github.com/yargs/y18n","bugs":{"url":"https://github.com/yargs/y18n/issues"},"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"license":"ISC","author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"main":"./build/index.cjs","scripts":{"check":"standardx **/*.ts **/*.cjs **/*.mjs","fix":"standardx --fix **/*.ts **/*.cjs **/*.mjs","pretest":"rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs","test":"c8 --reporter=text --reporter=html mocha test/*.cjs","test:esm":"c8 --reporter=text --reporter=html mocha test/esm/*.mjs","posttest":"npm run check","coverage":"c8 report --check-coverage","precompile":"rimraf build","compile":"tsc","postcompile":"npm run build:cjs","build:cjs":"rollup -c","prepare":"npm run compile"},"devDependencies":{"@types/node":"^14.6.4","@wessberg/rollup-plugin-ts":"^1.3.1","c8":"^7.3.0","chai":"^4.0.1","cross-env":"^7.0.2","gts":"^3.0.0","mocha":"^8.0.0","rimraf":"^3.0.2","rollup":"^2.26.10","standardx":"^7.0.0","ts-transform-default-export":"^1.0.2","typescript":"^4.0.0"},"engines":{"node":">=10"},"standardx":{"ignore":["build"]},"gitHead":"e2900d1cdf313663bba012f20737385a42c66b87","_id":"y18n@5.0.8","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==","shasum":"7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55","tarball":"http://localhost:25213/npm/3rdparty-npm/y18n/-/y18n-5.0.8.tgz","fileCount":9,"unpackedSize":23435,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbgCeCRA9TVsSAnZWagAAe28P/1xPOIpeRSlxJ9fnToSn\n7oM9pOZJa9eijf47kkR2D+Djvh6Wt0QFZNOOF9aHFtTtu1OZKvAnsos9tXiM\n12HsrfHxjSaO1P7uvpYt6CLRfzoFXCOakeImNyIEPTg+K107WhkdP1/9JZ9X\nq/cSQfwyzwEuSoMNWA9u6dAKwwvWKojDz/64eEvsoiLP/e/VANGBYHMtipNt\niLMrZkaRLGKzxlv/g2/E+bAbUaG6Qk3f0VEqOjMMLyoGGDqF0pitoXUpGjeg\nw4u8F0KVuxoI54QOkyJzVO6yoN0knXFZHVIX2ZGRaS7kQyM8uHF4KzNuTnDs\n+FQCJfPAVT5w3qPF7i+NDEy2WS7waGkA4K7Drqto325sFLluguNXSpTsiRTT\nzlEEEDtAclNsBZbEt4WJNjS/7AzhZUDMBS7hf1LbwHLjnK4C8HYV7uSiPESG\nzRyysFcqAe19yQo+N1gK/RL+ObuU/raOpK9WLTwZGc2+yCBy7+4CDGca02dO\nn08B0DTKV2ufTRyDY4uUxxO405Dkk5tWHfFBZYe5vC1Li796MDyOTkzGSwtE\nlqBEo1qqyXKw+erstSkV/Delp1klcFBYiRNspLQOTEtf2NsRlG8Wf6Ess4XB\nLLWS4lnHGl3ZZuxznOg3uEvBsEh/uV+sx2pB30Odw6geVJ7xl2Epm52iE65y\ndl5S\r\n=nxJd\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGbHwqJFZjnz9AWpeEVMOweV2g8TS4PkQz2LPe9gUugEAiBPhTbPW09lSJKJ92lpEuiLudAZLZIMKiExkZkcVjyDjw=="}]},"_npmUser":{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"},"directories":{},"maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/y18n_5.0.8_1617821853833_0.037626956652615284"},"_hasShrinkwrap":false}},"readme":"# y18n\n\n[![NPM version][npm-image]][npm-url]\n[![js-standard-style][standard-image]][standard-url]\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\nThe bare-bones internationalization library used by yargs.\n\nInspired by [i18n](https://www.npmjs.com/package/i18n).\n\n## Examples\n\n_simple string translation:_\n\n```js\nconst __ = require('y18n')().__;\n\nconsole.log(__('my awesome string %s', 'foo'));\n```\n\noutput:\n\n`my awesome string foo`\n\n_using tagged template literals_\n\n```js\nconst __ = require('y18n')().__;\n\nconst str = 'foo';\n\nconsole.log(__`my awesome string ${str}`);\n```\n\noutput:\n\n`my awesome string foo`\n\n_pluralization support:_\n\n```js\nconst __n = require('y18n')().__n;\n\nconsole.log(__n('one fish %s', '%d fishes %s', 2, 'foo'));\n```\n\noutput:\n\n`2 fishes foo`\n\n## Deno Example\n\nAs of `v5` `y18n` supports [Deno](https://github.com/denoland/deno):\n\n```typescript\nimport y18n from \"https://deno.land/x/y18n/deno.ts\";\n\nconst __ = y18n({\n locale: 'pirate',\n directory: './test/locales'\n}).__\n\nconsole.info(__`Hi, ${'Ben'} ${'Coe'}!`)\n```\n\nYou will need to run with `--allow-read` to load alternative locales.\n\n## JSON Language Files\n\nThe JSON language files should be stored in a `./locales` folder.\nFile names correspond to locales, e.g., `en.json`, `pirate.json`.\n\nWhen strings are observed for the first time they will be\nadded to the JSON file corresponding to the current locale.\n\n## Methods\n\n### require('y18n')(config)\n\nCreate an instance of y18n with the config provided, options include:\n\n* `directory`: the locale directory, default `./locales`.\n* `updateFiles`: should newly observed strings be updated in file, default `true`.\n* `locale`: what locale should be used.\n* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)\n be allowed if a file matching the locale does not exist (e.g. `en_US.json`),\n default `true`.\n\n### y18n.\\_\\_(str, arg, arg, arg)\n\nPrint a localized string, `%s` will be replaced with `arg`s.\n\nThis function can also be used as a tag for a template literal. You can use it\nlike this: __`hello ${'world'}`. This will be equivalent to\n`__('hello %s', 'world')`.\n\n### y18n.\\_\\_n(singularString, pluralString, count, arg, arg, arg)\n\nPrint a localized string with appropriate pluralization. If `%d` is provided\nin the string, the `count` will replace this placeholder.\n\n### y18n.setLocale(str)\n\nSet the current locale being used.\n\n### y18n.getLocale()\n\nWhat locale is currently being used?\n\n### y18n.updateLocale(obj)\n\nUpdate the current locale with the key value pairs in `obj`.\n\n## Supported Node.js Versions\n\nLibraries in this ecosystem make a best effort to track\n[Node.js' release schedule](https://nodejs.org/en/about/releases/). Here's [a\npost on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).\n\n## License\n\nISC\n\n[npm-url]: https://npmjs.org/package/y18n\n[npm-image]: https://img.shields.io/npm/v/y18n.svg\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n[standard-url]: https://github.com/feross/standard\n","maintainers":[{"name":"bcoe","email":"bencoe@gmail.com"},{"name":"nexdrew","email":"andrewbgoode@gmail.com"},{"name":"oss-bot","email":"bencoe+oss-bot@gmail.com"}],"time":{"modified":"2022-06-29T06:29:54.567Z","created":"2015-07-27T07:19:00.467Z","1.0.0":"2015-07-27T07:19:00.467Z","1.1.0":"2015-07-28T04:15:40.553Z","2.0.0":"2015-07-28T05:07:08.737Z","3.0.0":"2015-07-29T07:35:44.735Z","3.1.0":"2015-08-18T22:01:52.657Z","3.2.0":"2015-09-21T20:58:01.658Z","3.2.1":"2016-03-17T05:04:31.363Z","4.0.0":"2017-10-10T19:03:17.301Z","5.0.0":"2020-09-05T02:35:32.173Z","5.0.1":"2020-09-05T23:57:59.585Z","6.0.0-alpha.0":"2020-09-12T00:20:05.591Z","5.0.2":"2020-10-01T18:23:27.000Z","5.0.3":"2020-10-16T01:52:15.606Z","5.0.4":"2020-10-16T15:44:59.622Z","5.0.5":"2020-10-25T15:18:48.201Z","4.0.1":"2020-11-30T23:43:34.811Z","3.2.2":"2021-01-04T22:47:56.362Z","5.0.6":"2021-04-05T01:26:00.411Z","4.0.2":"2021-04-07T01:45:04.213Z","5.0.7":"2021-04-07T01:46:43.112Z","4.0.3":"2021-04-07T18:05:24.075Z","5.0.8":"2021-04-07T18:57:33.969Z"},"homepage":"https://github.com/yargs/y18n","keywords":["i18n","internationalization","yargs"],"repository":{"type":"git","url":"git+https://github.com/yargs/y18n.git"},"author":{"name":"Ben Coe","email":"bencoe@gmail.com"},"bugs":{"url":"https://github.com/yargs/y18n/issues"},"license":"ISC","readmeFilename":"README.md","users":{"slurm":true,"alberh":true,"terrychan":true,"arttse":true,"metaa":true,"zeligzhou":true}} \ No newline at end of file diff --git a/test/testdata/npm/y18n/-/y18n-5.0.8.tgz b/test/testdata/npm/y18n/-/y18n-5.0.8.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0647d781cb572e51e2644d584cc23d197fc405cf GIT binary patch literal 6157 zcmV+o81m;IiwFP!000001MNNAcH20T{midGJCl}_ktkAk*^{1D93}CtV`nU_te8@iSx9yi~6#!l(-#V8*IY&I*5lK`P3Wcgd0VvQ*YJQ+I);v7e zA0JJ|KYWc#)3jcvLpHsKOK-Q^P)NNMS;oQA51Df1L~{b~@YjzcVlzene$E1B ztjH{&Uc{_ASqA>4V=)V;(Fp!wgc|-OjLO>ATU%m>f*Vo0H>T51x;olbz87o_EOT=s7{4(c9Bk$0y|Z!DRn% zbntpYMu&$4uueutrw8K+d3SL7N;w(79Gw9CF`xjkjPTL^;oIj2M=v?n!Rt4N2T(;0 z`}hTUJwDlg1&^a=2ZslzzhTA7i-Xgn@noU`0djOq#=nk_PRZmI63OxOY)lRhM$Zn% zw#o*Q1xnfwe92|{b9v-|LAMKA3@fe|%)8i8Wd^?c{buv0Rm|%^^Z%+~Xm=gpVM`J+| z#m!{^T=4qGC(!uUBTn^&QV_qY{Mn}UU0h4)|Bcvjt)}By?4xSF55H&~xB7p*tM~h5 z|KDn9t*!t67FPuvIzh*6MwLB9xdd-%hvX><*zd6uuxiB)E44lTK8_qWUxLp`*^+`! z&z0dbmMJcaNH}*Edy47OFeJa}gCp`uA#j<#2NlOA3^P@K8HF|Z9CS3)Add2vanLZidOQ2<}L|asuFeIOl8|IbSuwlG1z%)<&L+Z_9 zu+S1Wn*cU3wE?co8_3#$Avf@-3z`tywp!%Hq~;u}KQ`Wj1XVL&(9EhAyKb$-^;O_R z>>n|U5&Z02lwR|Hr_-vcsx+7ty4kTwRkjL>;sx`f@Ju@=KVnl*E4^7I506*CqB!tG zh0}=6X3RQ-Y6I$4^EB#2)F)o#IK58qxB{Z`1GmVT3W{n+)f_BpuW3NB9eXBfAgKa7 z-&5>%Fs_{+$==6LN$;wGMeP>TkYUAbrIu5O?`T$bw6V$0+gG6%oBtCqnCaeC8$^ zW&@4;@p1-a&dESe?bFgY5y z#!KqPV9%x>yB0C$&_YHG1|cm6C}VO1AR-h6m<6-c4Z>#L?`R|M5V%@wCSECzyCqjv zUjr2l*v-3_bs*Cyyt>|K3(SSaeky5Vbt9-WK=kS=Hj+jv#M$nuntM|xXSenkC^7!3 zV1g!%s@yNsivGf@=5;{WD#Af;kAi@%RG1({KH8ZYx)AI`U2e_=3`{?YwO7Le9{U zd<4G}y=eS`M~-~9#%aa(SOwUu_+Y?6<$RNhe{H9iu>Dz{FD3GntUD2Cpp?E zJ?W~Vd)WHXZ{$Z&v|#Auu?77=O*Z#kiv_&Za_OT^sAZ+LmPd?yu_BUqJHkFLp(j95 zjTH~n6VF_7T(TP=Iz#tcw9$XVE0xaxkL_yZu~n%Ry@SA2mvK0+COqXDcE4dk61mau zhb$7lrz*N3ZwwG* z$rw8u+U7JozVhA#K3GDu5@Q_T){#!FOd(}ugS!BD^b~;0Z;MubT~bdu1{b62C5&2O z5>^-7R5mVDauXR2j~zb-ZmQWpi0|vf+3fLwv>e9H9P(Ef^VU84U!QW)duzhWhE6eo zh(VWa$79yo93n@m@;E;SED2*>y+uz?rE23-UD#5h(r4$Ta0CGKS}C|Gc&;ly_^_tt z*;Gmx90%Gkrsm*epuoquP3j3sWwJ1Qmts_lOl>G%3&5s)p;ZjX<)8>zOvdVrEtrZG zCKDDDlm1wEej-I-arCmg%Fp6?dHSMUdFmE8Sl-Sv7#<<;iXOab7;f@3ZJVbh=-8E?^{hdeLcL0e`|{#wo?orH_LG?_~h+m987>DG_IA@S+Deg_ZOY7gxARwF%m*}4=hf_ zI(jBd#DMoWF~wzdv4}1JWW5<9)9EgU$JoVyAis(awR*1HO98x%5NTvC;nL@g(6bWs z>{jS}6fP0+uQuYid1&z7_=qmy{f6R$n?0 zbC~o=)G5HkBxg520jtaI;3~e$_em0k7ezuVc8OG-r$6T-!L{)xQNs}82^wW8r#fMI z&T$`Co{*#93(J_GoO_B=&@{PClkzCh3z}ONI&g$^7QPijN5VAK5BSavBJFMWX#ec1 zxc}=qM$>#Bs$X^k`1brCt&Q8I<^5mG3EJ-ee*Mc?ph1#P!o(qho${M&Vh8>L$6=8Y z@E#}05_Zk4CZJ3CvVj9t*hDyicpoRi>%G;72;YGbG>zS8>p;H9wQm1Ohkv;`ZqNVd z=>5)`{lml7{=dbw&CkeaZ}T&@`5D{%jBS3#cfPN<&Cl58XKeE`w)q)bKl*uobeo^C z&Ckda+2&_#^E1*O+~#L&^E0;j8Qc7f8$y+Be#SOGW1F8*R*dg4KSS<171A`eNg7`y zN#oDn``BKa_Ww5T;(ksYckKUn`+9l*uiNfv+x@?9ai#nJg`_8vWEa)5w5K#Dz|Q3Y ze3Cg&X(2xpo-3AvM_SGghOh1A-<6iNAqz{mk$m1&A=TQ=X4RkF@$yRSU;gr; z`%BkxyZyJ@-L>yO=-qB_YyaQkLap8L=z{I+k{ukSchGWnE?I!z(ZQEpRZ|Dzt;Iq! zaQId~mf-S$K?Bw>e2<06CN-Ag#FyU?7*PTWmKKL%?Q?}IE`plO1>8xDen*AdGifWC zHSGvrf)ys&NQ?J^ganXZoe&VE51H&puS+@P{9Pa3Y3aK;b@J3%WQ|c%&IcCspzOYPete=@V4<1sV;%@N-$O~){QOkURg!Ot1axMe&($CqLS)sM zci&Dq*RLf0!>`c293LLPR2SBluH*Ljuiw?y<3D}-{jYCxJtF&Jv;WYaDay!oaUqf-$Bv%dq;Yzu0^1W?{>m0G~CQ>ZOgfZN569(Flk>q)GLqq~Y>m^X& zAyGrr=lZi|8MnswX8~d1L}V5_7IV=@sEYFF(Iaxk1JsQE)zy`{IqJb-0Y-}EC1Uhd zRpmE>8mYFlmfp}h4Xs~O1l4EpjJ$9@vQSZ;K-d5Shrj#e2{K|b{PHElaxg5qjE3R^ zm(Kx!RL>0EZ0TKkPi~++O^Ly*zTPny>kir)>-Q|yW-u}8Y8}gLnH`H7wOX#Z{RcO< zmo&Hk@aE>*X^D0|(p&m{co2bR6QFBQ@0r!MTVUu95Rb%QJ(F2>%dlyy-EFZUW2V*t zv2@$^kZL`nRS-|_!Q$yA;_2nZ)4f4Fau*3D6n=1)#mywg=6gwUBZOBm`99fCQYv;{YM@GQ}jtDWNj)E!*nhQoUE#rc&*4sdnr^*BU&;LM4V}P|A8%%j^$^ zL(1C2j@_r$us_hcmfaZ)idxrxuvA-#RNH@4stx$3mrC>onca)ARAy~1GidCvrLzaf zOk(Jo-nRz>8|266uy0w64!RxNX2X`%8?b(-AhXtkWu_-GYvp97>32B&BzY2}{LJUW z6aWH!A*6e$M=B(Z3#mP5cdX$uhni1HJGQcfnE^O zy+);ikhDZddLkrkc$4eLwG1++V1zD4JusOOUp@t?3?JgdhQfvEEie+}A;u;#I(>r< zyThJg8I}#Pb$2)%K;Y0}{l49{m@cCQA(H#j7b5B8MWSsK$^ZS&|B`1X_04)-qTG9>urZ_mHt3x9QTHFwOp_+q$&ONC;4z+iKF>G**P$X4J3))5+OkNdxl z3gs{v1Qm2${|Zg@NbE(C!_x@~%{jw0IZ}Nz7)lLz!6@b}B_j_BTz;CY z!iX)D&yva+h~)uI1Q~KGHBHTI-8Y)%Rw^+1t&Y}j^^HM?QClCj`t}f-skfLp(CjYj zv=b}kOCjG^+|aimFfgfRwRJEw&9sd6pw;fMVc#~v6LmZA#)SAiF}hCC=99+`@NvrxXH9iQiomV10x!rlJV?Nc)`;{QDB|!U0g|7b%es3!xnU5 z`yNd}F-hoDm-E*6obpxnzv;F%Y z-{gA4Q*#vM?8mdCH?K*uT65+t7Y%0tnsF}fMS`@S zTjwGB+yukSQk}&NlU$i=g{tE>4QkgD~3K$4KHW4j7)wpNtk)4 z`oRoL{Pd6RQ>uz`;Dt+=F7S6}xSv-XH$XJ+L+)e}LqeDuUz#H0j}#mnI>2e@;9WB1 z;7jc;uXuN=C>IwO??d^Kgy}T@8GY=VO08N`r&IYY79Vr}{8@E6tyUH*La$ipFH*vf z!%Cf0Y~SZ8q(E0u{5V?1(Js`o4i10-F%`+OH#p_+fefZ64Umg9rZ+J0mrrnWeenQ( z#i^*I=cEf>7o>Lsquv@pIbLewUq!0=Aheogem)8QAQ#0 zZE|thy&xCJ#6`kzNX`&)p4c6T0%K^4!R&*lH(?S49)JrV zWLf&2CAi^`b;8t{S|=CG<6FQNa4Q5R9)UTYbuQFY%ze_6i5Q{b@{R2TApXc%@YS4E zvW~9EkVqUDUyOa6wb|36QsKRp`u_U1TJqer;% z1J|lt^Ot{XauAhpCK5(bb72mH>K*yY1rBfmTXM8%!Pqg-i;)&X98_E728YrU3Ox0-($p?%#8`YbPufSAYEAW^kpK+2U z1;>U)dI89yd&oo#aUj)XapG4ISc0263&qQ*pUsPxYqlghkZ@vHK_r^bW=iy&6-MHv4B-z;H3)Yo)FOdt2uaL z-ZS0@!HttFPf;(*C@+}h#0x$+L-QM0Q3GOO(|29QZK#QBgAx1>4Q&X|(!gO76eMV* zi!~{t59V{hdj?yI zvH_%d*%X?0IaA6({Ec`bI!F73sGLs#viX3)|194uV605E$aXH!u^A@Aq&vRf?>zc@ zz0zq*l9}@r>bN8Szt!86|Iy#h|G&j0rxIcQJAQYa1*s@^el>7CN49e7w?sF51dt!D f!cVzw`0n4 Date: Sun, 16 Oct 2022 17:09:33 +0200 Subject: [PATCH 2/2] feat(preserve): [#7] Preserve Apt. --- .github/workflows/dip.yml | 2 +- .github/workflows/release.yml | 2 +- README.md | 210 +++++++++++++++--- cmd/yaam/main.go | 30 ++- cmd/yaam/main_test.go | 24 +- configs/.golangci.yml | 4 +- deployments/k8s-openshift/deploy.yml | 84 ++++--- docs/CHANGELOG.md | 11 +- docs/config/BASE.md | 6 - docs/config/GENERIC.md | 11 - docs/config/MAVEN.md | 108 --------- docs/config/NPM.md | 17 -- docs/{unify => other}/MAVEN.md | 0 docs/preserve/MAVEN.md | 7 - docs/preserve/NPM.md | 17 -- docs/publish/GENERIC.md | 31 --- docs/publish/MAVEN.md | 7 - docs/start/DOCKER.md | 14 -- docs/start/K8SOPENSHIFT.md | 2 - internal/app/yaam/artifact/apt.go | 78 +++++++ internal/app/yaam/artifact/apt_test.go | 1 + internal/app/yaam/artifact/artifact.go | 193 ++++++++++++++++ internal/app/yaam/artifact/artifact_test.go | 42 +++- internal/app/yaam/artifact/generic.go | 7 +- internal/app/yaam/artifact/maven.go | 19 +- internal/app/yaam/artifact/npm.go | 15 +- internal/app/yaam/artifact/npm_test.go | 4 +- .../{pkg => app/yaam}/artifact/validate.go | 0 .../yaam}/artifact/validate_test.go | 4 +- internal/{pkg => app/yaam}/file/file.go | 5 +- internal/app/yaam/file/file_test.go | 40 ++++ internal/{pkg => app/yaam}/project/project.go | 0 .../{pkg => app/yaam}/project/project_test.go | 8 + internal/{pkg => app/yaam}/yaamtest/config.go | 10 +- .../{pkg => app/yaam}/yaamtest/config_test.go | 0 .../{pkg => app/yaam}/yaamtest/generic.go | 2 +- .../yaam}/yaamtest/generic_test.go | 0 internal/{pkg => app/yaam}/yaamtest/maven.go | 2 +- internal/app/yaam/yaamtest/maven_test.go | 12 + internal/{pkg => app/yaam}/yaamtest/npm.go | 2 +- .../{pkg => app/yaam}/yaamtest/npm_test.go | 0 internal/{pkg => app/yaam}/yaamtest/status.go | 4 +- .../{pkg => app/yaam}/yaamtest/status_test.go | 0 internal/pkg/artifact/artifact.go | 201 ----------------- internal/pkg/artifact/artifact_test.go | 59 ----- internal/pkg/file/file_test.go | 1 - internal/pkg/yaamtest/maven_test.go | 1 - test/testdata/empty-file.txt | 0 48 files changed, 695 insertions(+), 602 deletions(-) delete mode 100644 docs/config/BASE.md delete mode 100644 docs/config/GENERIC.md delete mode 100644 docs/config/MAVEN.md delete mode 100644 docs/config/NPM.md rename docs/{unify => other}/MAVEN.md (100%) delete mode 100644 docs/preserve/MAVEN.md delete mode 100644 docs/preserve/NPM.md delete mode 100644 docs/publish/GENERIC.md delete mode 100644 docs/publish/MAVEN.md delete mode 100644 docs/start/DOCKER.md create mode 100644 internal/app/yaam/artifact/apt.go create mode 100644 internal/app/yaam/artifact/apt_test.go rename internal/{pkg => app/yaam}/artifact/validate.go (100%) rename internal/{pkg => app/yaam}/artifact/validate_test.go (87%) rename internal/{pkg => app/yaam}/file/file.go (97%) create mode 100644 internal/app/yaam/file/file_test.go rename internal/{pkg => app/yaam}/project/project.go (100%) rename internal/{pkg => app/yaam}/project/project_test.go (63%) rename internal/{pkg => app/yaam}/yaamtest/config.go (83%) rename internal/{pkg => app/yaam}/yaamtest/config_test.go (100%) rename internal/{pkg => app/yaam}/yaamtest/generic.go (96%) rename internal/{pkg => app/yaam}/yaamtest/generic_test.go (100%) rename internal/{pkg => app/yaam}/yaamtest/maven.go (98%) create mode 100644 internal/app/yaam/yaamtest/maven_test.go rename internal/{pkg => app/yaam}/yaamtest/npm.go (95%) rename internal/{pkg => app/yaam}/yaamtest/npm_test.go (100%) rename internal/{pkg => app/yaam}/yaamtest/status.go (76%) rename internal/{pkg => app/yaam}/yaamtest/status_test.go (100%) delete mode 100644 internal/pkg/artifact/artifact.go delete mode 100644 internal/pkg/artifact/artifact_test.go delete mode 100644 internal/pkg/file/file_test.go delete mode 100644 internal/pkg/yaamtest/maven_test.go create mode 100644 test/testdata/empty-file.txt diff --git a/.github/workflows/dip.yml b/.github/workflows/dip.yml index 79fcd49..bda7161 100644 --- a/.github/workflows/dip.yml +++ b/.github/workflows/dip.yml @@ -47,7 +47,7 @@ jobs: chmod +x /tmp/git-chglog /tmp/git-chglog -o docs/CHANGELOG.md --config configs/chglog/config.yml --next-tag ${tag} echo "Checking README.md..." - sed -i "s|\(yaam:\).*|\1${tag}|" docs/start/DOCKER.md + sed -i "s|\(yaam:\).*|\1${tag}|" README.md echo "Checking k8s-openshift deployment..." sed -i "s|\(yaam:\).*|\1${tag}|" deployments/k8s-openshift/deploy.yml - uses: EndBug/add-and-commit@v9 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index baec699..7288aea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: version=$(echo "${{ github.ref }}" | sed -e "s|.*\/\(.*\)$|\1|") echo "Version: ${version}" echo "Checking README.md..." - grep "yaam:${version}" docs/start/DOCKER.md + grep "yaam:${version}" README.md echo "Checking k8s-openshift deployment..." grep "yaam:${version}" deployments/k8s-openshift/deploy.yml - name: Create release diff --git a/README.md b/README.md index cd5fdf3..e7700e3 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,28 @@ of artifact types. Yet Another Artifact Manager (YAAM): - has no UI. - does not have a database. - scales horizontally. -- supports downloading and publication of Generic, Maven and NPM artefacts, - preserves NPM and Maven packages from public repositories and unifies Maven - repositories. +- supports downloading and publication of Apt, Generic, Maven and NPM + artefacts, preserves NPM and Maven packages from public repositories and + unifies Maven repositories. ## Quickstart +Create a directory and change the permissions to ensure that YAAM can store +artifacts: + +```bash +mkdir ~/.yaam/repositories +sudo chown 9999 -R ~/.yaam/repositories +``` + Configure YAAM by creating a `~/.yaam/config.yml` with the following content: ```bash --- caches: + apt: + 3rdparty-ubuntu-nl-archive: + url: http://nl.archive.ubuntu.com/ubuntu/ maven: 3rdparty-maven: url: https://repo.maven.apache.org/maven2/ @@ -96,49 +107,190 @@ publications: Start YAAM: ```bash -mkdir ~/.yaam/repositories -sudo chown 9999 -R ~/.yaam/repositories -docker run -e YAAM_USER=hello -e YAAM_PASS=world --rm --name=yaam \ --it -v /home/${USER}/.yaam:/opt/yaam/.yaam -p 25213:25213 yaam +docker run \ + -e YAAM_DEBUG=false \ + -e YAAM_USER=hello \ + -e YAAM_PASS=world \ + --rm \ + --name=yaam \ + -it \ + -v /home/${USER}/.yaam:/opt/yaam/.yaam \ + -p 25213:25213 utrecht/yaam:v0.5.0 ``` -Configure the URLs in the build.gradle and settings.gradle and run -./gradlew clean +Once YAAM has been started, configure a project to ensure that artifacts will +be downloaded from this artifact manager. -Configure a .npmrc in an NPM project and run `npm i -d`. +### Apt -### General +sudo vim /etc/apt/auth.conf.d/hello.conf -- [Base.](docs/config/BASE.md) +```bash +machine http://localhost +login hello +password world +``` -### Artifact types +sudo vim /etc/apt/sources.list -- [Generic.](docs/config/GENERIC.md) -- [Maven.](docs/config/MAVEN.md) -- [NPM.](docs/config/NPM.md) +```bash +deb http://localhost:25213/apt/3rdparty-ubuntu-nl-archive/ focal main restricted +``` -## Start +Preserve the artifacts: -- [Binary.](docs/start/BINARY.md) -- [Docker.](docs/start/DOCKER.md) -- [K8s/OpenShift.](docs/start/K8SOPENSHIFT.md) +```bash +sudo apt-get update +``` + +### Generic + +Upload: + +```bash +curl -X POST -u hello:world \ +http://yaam.some-domain/generic/something/world4.iso \ +--data-binary @/home/${USER}/Downloads/ubuntu-22.04.1-desktop-amd64.iso +``` + +Troubleshooting: + +```bash +413 Request Entity Too Large +``` + +add: + +```bash +data: + proxy-body-size: 5G +``` + +and restart the controller pod. -## Capabilities +Verify in the `/etc/nginx/nginx.conf` file that the `client_max_body_size` has +been increased to 5G. -### Publish +Download: -- [Generic.](docs/publish/GENERIC.md) -- [Maven.](docs/publish/MAVEN.md) +```bash +curl -u hello:world http://yaam.some-domain/generic/something/world6.iso \ +-o /tmp/world6.iso +``` + +### Gradle + +Adjust the `build.gradle` and/or `settings.gradle`: + +```bash +repositories { + maven { + allowInsecureProtocol true + url 'http://localhost:25213/maven/releases/' + authentication { + basic(BasicAuthentication) + } + credentials { + username "hello" + password "world" + } + } + maven { + allowInsecureProtocol true + url 'http://localhost:25213/maven/3rdparty-maven/' + authentication { + basic(BasicAuthentication) + } + credentials { + username "hello" + password "world" + } + } + maven { + allowInsecureProtocol true + url 'http://localhost:25213/maven/3rdparty-maven-gradle-plugins/' + authentication { + basic(BasicAuthentication) + } + credentials { + username "hello" + password "world" + } + } +} +``` + +Publish: -### Preserve +```bash +publishing { + publications { + mavenJava(MavenPublication) { + versionMapping { + usage('java-api') { + fromResolutionOf('runtimeClasspath') + } + usage('java-runtime') { + fromResolutionResult() + } + } + } + } + + repositories { + maven { + allowInsecureProtocol true + url 'http://localhost:25213/maven/releases/' + authentication { + basic(BasicAuthentication) + } + credentials { + username "hello" + password "world" + } + } + } +} +``` + +Preserve the artifacts: -- [Maven.](docs/preserve/MAVEN.md) -- [NPM.](docs/preserve/NPM.md) +```bash +./gradlew clean +``` -### Unify +or publish them: -- [Maven.](docs/unify/MAVEN.md) +```bash +./gradlew publish +``` + +### NPM + +Create a `.npmrc` file in the directory of a particular NPM project: + +```bash +registry=http://localhost:25213/npm/3rdparty-npm/ +always-auth=true +_auth=aGVsbG86d29ybGQ= +cache=/tmp/some-yaam-repo/npm/cache20220914120431999 +``` + +Note: the `_auth` key should be populated with the output of: +`echo -n 'someuser:somepass' | openssl base64`. + +```bash +npm i -d +``` + +## Run + +Next to docker, one could also use a binary or K8s or OpenShift to run YAAM: + +- [Binary.](docs/start/BINARY.md) +- [K8s/OpenShift.](docs/start/K8SOPENSHIFT.md) ## Other - [Background.](docs/other/BACKGROUND.md) +- [Maven unify.](docs/other/MAVEN.md) diff --git a/cmd/yaam/main.go b/cmd/yaam/main.go index a40b80d..822d6de 100644 --- a/cmd/yaam/main.go +++ b/cmd/yaam/main.go @@ -9,7 +9,7 @@ import ( "github.com/030/yaam/internal/app/yaam/api" "github.com/030/yaam/internal/app/yaam/artifact" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/project" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) @@ -87,6 +87,33 @@ func mavenGroup(w http.ResponseWriter, r *http.Request) { } } +func aptArtifact(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := r.Body.Close(); err != nil { + panic(err) + } + }() + + if err := api.Validation(r.Method, r, w); err != nil { + httpInternalServerErrorReadTheLogs(w, err) + return + } + + a := artifact.Apt{RequestBody: r.Body, RequestURI: r.RequestURI, ResponseWriter: w} + + var ap artifact.Preserver = a + if err := ap.Preserve(); err != nil { + httpNotFoundReadTheLogs(w, err) + return + } + + var ar artifact.Reader = a + if err := ar.Read(); err != nil { + httpNotFoundReadTheLogs(w, err) + return + } +} + func genericArtifact(w http.ResponseWriter, r *http.Request) { defer func() { if err := r.Body.Close(); err != nil { @@ -176,6 +203,7 @@ func main() { } r := mux.NewRouter() + r.HandleFunc("/apt/{repo}/{artifact:.*}", aptArtifact) r.HandleFunc("/generic/{repo}/{artifact:.*}", genericArtifact) r.HandleFunc("/maven/groups/{name}/{artifact:.*}", mavenGroup) r.HandleFunc("/maven/{repo}/{artifact:.*}", mavenArtifact) diff --git a/cmd/yaam/main_test.go b/cmd/yaam/main_test.go index cc7f131..1d25829 100644 --- a/cmd/yaam/main_test.go +++ b/cmd/yaam/main_test.go @@ -8,12 +8,13 @@ import ( "os" "testing" - "github.com/030/yaam/internal/pkg/project" - "github.com/030/yaam/internal/pkg/yaamtest" + "github.com/030/yaam/internal/app/yaam/project" + "github.com/030/yaam/internal/app/yaam/yaamtest" "github.com/tj/assert" ) const ( + aptUri = "/apt/3rdparty-ubuntu-nl-archive/some.iso" genericUri = "/generic/something/some.iso" genericUriFail = "/generic/something2/some.iso" ) @@ -27,7 +28,7 @@ func init() { } func TestStatus(t *testing.T) { - b, err := yaamtest.StatusHelper("GET", "/status", nil, 10) + b, err := yaamtest.Status("GET", "/status", nil, 10) if err != nil { t.Error(err) } @@ -48,6 +49,23 @@ func TestMainNpmBuild(t *testing.T) { assert.Equal(t, 0, exitCode) } +// Apt +func TestApt(t *testing.T) { + resp, _ := yaamtest.GenericArtifactReq("GET", aptUri, nil) + defer func() { + if err := resp.Body.Close(); err != nil { + panic(err) + } + }() + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + t.Error(err) + } + bodyString := string(bodyBytes) + + assert.Equal(t, "check the server logs\n", bodyString) +} + /* Maven: preserve artifacts by using the unify option that ensures that multiple Maven repositories are grouped and only and endpoint has to be accessed and diff --git a/configs/.golangci.yml b/configs/.golangci.yml index cb466c8..ef2be53 100644 --- a/configs/.golangci.yml +++ b/configs/.golangci.yml @@ -11,11 +11,11 @@ issues: text: "don't use `init` function" - linters: - gochecknoinits - path: internal/pkg/artifact/artifact_test.go + path: internal/app/yaam/artifact/artifact_test.go text: "don't use `init` function" - linters: - gochecknoinits - path: internal/pkg/artifact/validate_test.go + path: internal/app/yaam/artifact/validate_test.go text: "don't use `init` function" linters: enable-all: true diff --git a/deployments/k8s-openshift/deploy.yml b/deployments/k8s-openshift/deploy.yml index 9e71430..abe519b 100644 --- a/deployments/k8s-openshift/deploy.yml +++ b/deployments/k8s-openshift/deploy.yml @@ -9,34 +9,41 @@ kind: ConfigMap metadata: name: conf data: - caches.yaml: |- - mavenReposAndUrls: - 3rdparty-maven: https://repo.maven.apache.org/maven2/ - 3rdparty-maven-gradle-plugins: https://plugins.gradle.org/m2/ - 3rdparty-maven-spring: https://repo.spring.io/release/ - 3rdparty-npm: https://registry.npmjs.org/ - groups.yaml: |- + config.yml: |- + --- + caches: + apt: + 3rdparty-ubuntu-nl-archive: + url: http://nl.archive.ubuntu.com/ubuntu/ + maven: + 3rdparty-maven: + url: https://repo.maven.apache.org/maven2/ + 3rdparty-maven-gradle-plugins: + url: https://plugins.gradle.org/m2/ + 3rdparty-maven-spring: + url: https://repo.spring.io/release/ + other-nexus-repo-releases: + url: https://some-nexus/repository/some-repo/ + user: some-user + pass: some-pass + npm: + 3rdparty-npm: + url: https://registry.npmjs.org/ groups: - hello: - - maven/releases - - maven/3rdparty-maven - - maven/3rdparty-maven-gradle-plugins - - maven/3rdparty-maven-spring ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: conf-repos -data: - generic.yaml: |- - allowedRepos: - - something - maven.yaml: |- - allowedRepos: - - releases - npm.yaml: |- - allowedRepos: - - 3rdparty-npm + maven: + hello: + - maven/releases + - maven/3rdparty-maven + - maven/3rdparty-maven-gradle-plugins + - maven/3rdparty-maven-spring + - maven/other-nexus-repo-releases + publications: + generic: + - something + maven: + - releases + npm: + - 3rdparty-npm --- apiVersion: v1 data: @@ -92,12 +99,6 @@ spec: averageUtilization: 90 type: Utilization type: Resource - - resource: - name: memory - target: - averageUtilization: 80 - type: Utilization - type: Resource minReplicas: 2 scaleTargetRef: apiVersion: apps/v1 @@ -138,7 +139,7 @@ spec: secretKeyRef: name: creds key: pass - image: utrecht/yaam:v0.4.2 + image: utrecht/yaam:v0.5.0 livenessProbe: httpGet: path: /status @@ -149,28 +150,23 @@ spec: port: 25213 resources: limits: - cpu: 240m - memory: 24Mi + cpu: 480m + memory: 30Mi requests: - cpu: 2m - memory: 8Mi + cpu: 96m + memory: 5Mi ports: - containerPort: 25213 name: yaam volumeMounts: - name: conf - mountPath: /opt/yaam/.yaam/conf - - name: conf-repos - mountPath: /opt/yaam/.yaam/conf/repositories + mountPath: /opt/yaam/.yaam - name: repositories mountPath: /opt/yaam/.yaam/repositories volumes: - name: conf configMap: name: conf - - name: conf-repos - configMap: - name: conf-repos - name: repositories persistentVolumeClaim: claimName: repositories diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 33c3b26..7b627bd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,10 +2,18 @@ ## [Unreleased] + +## [v0.5.0] - 2022-10-16 +### Feat +- **preserve:** [[#7](https://github.com/030/yaam/issues/7)] Preserve Apt. +- **preserve:** [[#24](https://github.com/030/yaam/issues/24)] Nexus3 repository. + + ## [v0.4.2] - 2022-10-01 ### Build - **deps:** Update versions. +- **deps:** Update versions. - **deps:** Add auto updater that creates a PR. @@ -44,7 +52,8 @@ ## v0.2.1 - 2022-08-23 -[Unreleased]: https://github.com/030/yaam/compare/v0.4.2...HEAD +[Unreleased]: https://github.com/030/yaam/compare/v0.5.0...HEAD +[v0.5.0]: https://github.com/030/yaam/compare/v0.4.2...v0.5.0 [v0.4.2]: https://github.com/030/yaam/compare/v0.4.1...v0.4.2 [v0.4.1]: https://github.com/030/yaam/compare/v0.4.0...v0.4.1 [v0.4.0]: https://github.com/030/yaam/compare/v0.3.0...v0.4.0 diff --git a/docs/config/BASE.md b/docs/config/BASE.md deleted file mode 100644 index c87c8c2..0000000 --- a/docs/config/BASE.md +++ /dev/null @@ -1,6 +0,0 @@ -# Config - -```bash -mkdir -p ~/.yaam/conf -chown 9999 -R ~/.yaam/ -``` diff --git a/docs/config/GENERIC.md b/docs/config/GENERIC.md deleted file mode 100644 index d152b8c..0000000 --- a/docs/config/GENERIC.md +++ /dev/null @@ -1,11 +0,0 @@ -# generic - -## Configuration - -~/.yaam/conf/repositories/generic.yaml - -```bash ---- -allowedRepos: - - something -``` diff --git a/docs/config/MAVEN.md b/docs/config/MAVEN.md deleted file mode 100644 index d63c1bc..0000000 --- a/docs/config/MAVEN.md +++ /dev/null @@ -1,108 +0,0 @@ -# Maven - -~/.yaam/conf/caches.yaml - -```bash ---- -reposAndUrls: - 3rdparty-maven: https://repo.maven.apache.org/maven2/ - 3rdparty-maven-gradle-plugins: https://plugins.gradle.org/m2/ - 3rdparty-maven-spring: https://repo.spring.io/release/ -``` - -~/.yaam/conf/repositories/maven.yaml - -```bash ---- -allowedRepos: - - releases -``` - -~/.yaam/conf/groups.yaml - -```bash ---- -groups: - hello: - - maven/releases - - maven/3rdparty-maven - - maven/3rdparty-maven-gradle-plugins - - maven/3rdparty-maven-spring -``` - -## Gradle - -### Preserve - -Adjust the `build.gradle` and/or `settings.gradle`: - -```bash -repositories { - maven { - allowInsecureProtocol true - url 'http://localhost:25213/maven/releases/' - authentication { - basic(BasicAuthentication) - } - credentials { - username "hello" - password "world" - } - } - maven { - allowInsecureProtocol true - url 'http://localhost:25213/maven/3rdparty-maven/' - authentication { - basic(BasicAuthentication) - } - credentials { - username "hello" - password "world" - } - } - maven { - allowInsecureProtocol true - url 'http://localhost:25213/maven/3rdparty-maven-gradle-plugins/' - authentication { - basic(BasicAuthentication) - } - credentials { - username "hello" - password "world" - } - } -} -``` - -### publish - -```bash -publishing { - publications { - mavenJava(MavenPublication) { - versionMapping { - usage('java-api') { - fromResolutionOf('runtimeClasspath') - } - usage('java-runtime') { - fromResolutionResult() - } - } - } - } - - repositories { - maven { - allowInsecureProtocol true - url 'http://localhost:25213/maven/releases/' - authentication { - basic(BasicAuthentication) - } - credentials { - username "hello" - password "world" - } - } - } -} -``` diff --git a/docs/config/NPM.md b/docs/config/NPM.md deleted file mode 100644 index ed51b34..0000000 --- a/docs/config/NPM.md +++ /dev/null @@ -1,17 +0,0 @@ -# NPM - -~/.yaam/conf/caches.yaml - -```bash ---- -reposAndUrls: - 3rdparty-npm: https://registry.npmjs.org/ -``` - -~/.yaam/conf/repositories/npm.yaml - -```bash ---- -allowedRepos: - - npm-releases -``` diff --git a/docs/unify/MAVEN.md b/docs/other/MAVEN.md similarity index 100% rename from docs/unify/MAVEN.md rename to docs/other/MAVEN.md diff --git a/docs/preserve/MAVEN.md b/docs/preserve/MAVEN.md deleted file mode 100644 index 3a2105c..0000000 --- a/docs/preserve/MAVEN.md +++ /dev/null @@ -1,7 +0,0 @@ -# Maven - -## Gradle - -```bash -./gradlew clean build -``` diff --git a/docs/preserve/NPM.md b/docs/preserve/NPM.md deleted file mode 100644 index 564afe9..0000000 --- a/docs/preserve/NPM.md +++ /dev/null @@ -1,17 +0,0 @@ -# NPM - -Create a `.npmrc` file in the directory of a particular NPM project: - -```bash -registry=http://localhost:25213/npm/3rdparty-npm/ -always-auth=true -_auth=aGVsbG86d29ybGQ= -cache=/tmp/some-yaam-repo/npm/cache20220914120431999 -``` - -Note: the `_auth` key should be populated with the output of: -`echo -n 'someuser:somepass' | openssl base64`. - -```bash -npm i -``` diff --git a/docs/publish/GENERIC.md b/docs/publish/GENERIC.md deleted file mode 100644 index 92cdd63..0000000 --- a/docs/publish/GENERIC.md +++ /dev/null @@ -1,31 +0,0 @@ -# generic - -## Upload - -```bash -curl -X POST -u hello:world http://yaam.some-domain/generic/something/world4.iso --data-binary @/home/${USER}/Downloads/ubuntu-22.04.1-desktop-amd64.iso -``` - -### Troubleshooting - -```bash -413 Request Entity Too Large -``` - -add: - -```bash -data: - proxy-body-size: 5G -``` - -and restart the controller pod. - -Verify in the `/etc/nginx/nginx.conf` file that the `client_max_body_size` has -been increased to 5G. - -## Download - -```bash -curl -u hello:world http://yaam.some-domain/generic/something/world6.iso -o /tmp/world6.iso -``` diff --git a/docs/publish/MAVEN.md b/docs/publish/MAVEN.md deleted file mode 100644 index 7040963..0000000 --- a/docs/publish/MAVEN.md +++ /dev/null @@ -1,7 +0,0 @@ -# Maven - -## Gradle - -```bash -./gradle publish -``` diff --git a/docs/start/DOCKER.md b/docs/start/DOCKER.md deleted file mode 100644 index c20b98f..0000000 --- a/docs/start/DOCKER.md +++ /dev/null @@ -1,14 +0,0 @@ -# docker - -[![dockeri.co](https://dockeri.co/image/utrecht/yaam)](https://hub.docker.com/r/utrecht/yaam) - -```bash -docker run \ - -v /home/${USER}/.yaam/conf:/opt/yaam/.yaam/conf \ - -v /home/${USER}/.yaam/repositories:/opt/yaam/.yaam/repositories \ - -e YAAM_DEBUG=true \ - -e YAAM_USER=hello \ - -e YAAM_PASS=world \ - -p 25213:25213 \ - -it utrecht/yaam:v0.4.2 -``` diff --git a/docs/start/K8SOPENSHIFT.md b/docs/start/K8SOPENSHIFT.md index 34ea199..b87e09a 100644 --- a/docs/start/K8SOPENSHIFT.md +++ b/docs/start/K8SOPENSHIFT.md @@ -1,7 +1,5 @@ # K8s and OpenShift -## Deploy - ```bash kubectl create -f deployments/k8s-openshift/deploy.yml -n yaam ``` diff --git a/internal/app/yaam/artifact/apt.go b/internal/app/yaam/artifact/apt.go new file mode 100644 index 0000000..121251a --- /dev/null +++ b/internal/app/yaam/artifact/apt.go @@ -0,0 +1,78 @@ +package artifact + +import ( + "fmt" + "io" + "net/http" + "reflect" + + "github.com/030/yaam/internal/app/yaam/file" + log "github.com/sirupsen/logrus" +) + +type Apt struct { + ResponseWriter http.ResponseWriter + RequestBody io.ReadCloser + RequestURI string +} + +func (a Apt) downloadAgainIfInvalid(atf artefact, resp *http.Response) error { + log.Debug(resp.StatusCode) + if resp.StatusCode == http.StatusOK { + if err := file.CreateIfDoesNotExistInvalidOrEmpty(atf.url, atf.path, resp.Body, false); err != nil { + return err + } + } + + if file.EmptyFile(atf.path) { + if err := a.Preserve(); err != nil { + return err + } + } + + return nil +} + +func (a Apt) Preserve(urlStrings ...string) error { + urlString := a.RequestURI + if len(urlStrings) > 0 { + urlString = urlStrings[0] + } + log.Debugf("urlString: '%s'", urlString) + + repoInConfigFile, err := RepoInConfigFile(a.ResponseWriter, urlString, "apt") + if err != nil { + return err + } + + if !reflect.ValueOf(repoInConfigFile).IsZero() { + atf, err := maven(urlString, repoInConfigFile) + if err != nil { + return err + } + + resp, err := file.DownloadWithRetries(atf.url, repoInConfigFile.User, repoInConfigFile.Pass) + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + panic(err) + } + }() + + if err := a.downloadAgainIfInvalid(atf, resp); err != nil { + return err + } + } + + return nil +} + +func (a Apt) Read() error { + if err := ReadFromDisk(a.ResponseWriter, a.RequestURI); err != nil { + return fmt.Errorf(file.CannotReadErrMsg, err) + } + + return nil +} diff --git a/internal/app/yaam/artifact/apt_test.go b/internal/app/yaam/artifact/apt_test.go new file mode 100644 index 0000000..c146142 --- /dev/null +++ b/internal/app/yaam/artifact/apt_test.go @@ -0,0 +1 @@ +package artifact diff --git a/internal/app/yaam/artifact/artifact.go b/internal/app/yaam/artifact/artifact.go index 4848dbb..12ec5d8 100644 --- a/internal/app/yaam/artifact/artifact.go +++ b/internal/app/yaam/artifact/artifact.go @@ -2,7 +2,15 @@ package artifact import ( "fmt" + "io" + "net/http" + "os" + "path/filepath" + "regexp" + "github.com/030/yaam/internal/app/yaam/file" + "github.com/030/yaam/internal/app/yaam/project" + log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -21,3 +29,188 @@ func allowedRepos(name string) ([]string, error) { return repos, nil } +func createHomeAndReturnPath(requestURI string) (string, error) { + h, err := project.RepositoriesHome() + if err != nil { + return "", err + } + + artifactPath := filepath.Join(h, requestURI) + artifactHome := filepath.Dir(artifactPath) + if err := os.MkdirAll(artifactHome, os.ModePerm); err != nil { + return "", err + } + + return artifactPath, nil +} + +func createIfDoesNotExist(path string, requestBody io.ReadCloser) error { + if _, fileExists := file.Exists(path); !fileExists { + dst, err := os.Create(filepath.Clean(path)) + if err != nil { + return err + } + + defer func() { + if err := dst.Close(); err != nil { + panic(err) + } + }() + + w, err := io.Copy(dst, requestBody) + if err != nil { + log.Error(err) + } + log.Debugf("file: '%s' created and it contains: '%d' bytes", path, w) + if err := dst.Sync(); err != nil { + return err + } + } else { + log.Debugf("file: '%s' exists already", path) + } + return nil +} + +func StoreOnDisk(requestURI string, requestBody io.ReadCloser) error { + if err := validate(requestURI); err != nil { + return err + } + + path, err := createHomeAndReturnPath(requestURI) + if err != nil { + return err + } + + if err := createIfDoesNotExist(path, requestBody); err != nil { + return err + } + + return nil +} + +func FilepathOnDisk(url string) (string, error) { + h, err := project.RepositoriesHome() + if err != nil { + return "", err + } + + f := filepath.Join(h, url) + log.Debugf("constructed filepath: '%s' after concatenating home: '%s' to url: '%s'", f, h, url) + return f, nil +} + +func ReadFromDisk(w http.ResponseWriter, reqURL string) error { + f, err := FilepathOnDisk(reqURL) + if err != nil { + return err + } + + log.Debugf("reading file: '%s' from disk...", f) + b, err := os.ReadFile(filepath.Clean(f)) + if err != nil { + return err + } + if _, err := io.WriteString(w, string(b)); err != nil { + return err + } + + return nil +} + +// ReadRepositoriesAndUrlsFromConfigFileAndCacheArtifact reads a repositories +// yaml file that contains repositories and their URLs. If a request is +// attempted to download a file, it will look up the name in the config file +// and find the public URLs so it can download the file from the public maven +// repository and cache it on disk. +func RepoInConfigFile(w http.ResponseWriter, urlString, artifactType string) (PublicRepository, error) { + reposAndElements := viper.GetStringMap("caches." + artifactType) + if len(reposAndElements) == 0 { + return PublicRepository{}, fmt.Errorf("caches: '%s' not found in config file", artifactType) + } + + for repo, elements := range reposAndElements { + url, ok := elements.(map[string]interface{})["url"] + if ok { + log.Debugf("url: '%s'", url) + } + user, ok := elements.(map[string]interface{})["user"] + if ok { + log.Debugf("user: '%s'", user) + } + pass, ok := elements.(map[string]interface{})["pass"] + if ok { + log.Debug("pass: **********") + } + + log.Debugf("trying to cache artifact from: '%s'...", urlString) + + rr := repoRegex(repo, artifactType) + log.Debugf("repoRegex: '%s'", rr) + + pr := PublicRepository{Name: repo, Regex: rr, Url: url.(string)} + if user != nil && pass != nil { + pr.User = user.(string) + pr.Pass = pass.(string) + } + + riu, err := repoInUrl(rr, urlString) + if err != nil { + return PublicRepository{}, err + } + log.Debugf("repoInUrl: '%t'", riu) + + if riu { + return pr, nil + } + } + + return PublicRepository{}, nil +} + +type PublicRepository struct { + Name, Regex, Url, User, Pass string +} + +func repoRegex(repo, repoType string) string { + return `^/` + repoType + `/` + repo + `/(.*)$` +} + +func repoInUrl(repoRegex, url string) (bool, error) { + log.Debugf("check whether url: '%s' contains repo according to regex: '%s'", url, repoRegex) + match, err := regexp.MatchString(repoRegex, url) + if err != nil { + return false, err + } + log.Debugf("outcome regex check: '%t'", match) + + return match, nil +} + +func DownloadUrl(publicRepoUrl, regex, url string) (string, error) { + log.Debugf("check whether url: '%s' matches regex: '%s'. Params -> publicRepoUrl: '%s', regex: '%s' and url: '%s'", url, regex, publicRepoUrl, regex, url) + r := regexp.MustCompile(regex) + match := r.FindStringSubmatch(url) + log.Debugf("number of matching elements: %d. Content: '%v'", len(match), match) + if len(match) != 2 { + return "", fmt.Errorf("should be 3! publicRepoUrl: '%s', regex: '%s', url: '%s'", publicRepoUrl, regex, url) + } + + u := r.ReplaceAllString(url, publicRepoUrl+`$1`) + + return u, nil +} + +func Dir(path string) error { + h, err := project.RepositoriesHome() + if err != nil { + return err + } + + dir := filepath.Join(h, filepath.Dir(path)) + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + return nil +} diff --git a/internal/app/yaam/artifact/artifact_test.go b/internal/app/yaam/artifact/artifact_test.go index 1cf2906..18aaee0 100644 --- a/internal/app/yaam/artifact/artifact_test.go +++ b/internal/app/yaam/artifact/artifact_test.go @@ -1,11 +1,14 @@ package artifact import ( + "io" "log" + "path/filepath" + "strings" "testing" - "github.com/030/yaam/internal/pkg/project" - "github.com/030/yaam/internal/pkg/yaamtest" + "github.com/030/yaam/internal/app/yaam/project" + "github.com/030/yaam/internal/app/yaam/yaamtest" "github.com/tj/assert" ) @@ -18,6 +21,41 @@ func init() { log.Fatal(err) } } +func TestStoreOnDisk(t *testing.T) { + s := strings.NewReader("Hola mundo!") + rc := io.NopCloser(s) + + err := StoreOnDisk(filepath.Join("/maven/releases/world", "hola.mundo"), rc) + if err != nil { + t.Error(err) + } + + assert.NoError(t, err) +} + +func TestStoreOnDiskFail(t *testing.T) { + err := StoreOnDisk(filepath.Join("/maven/releases-not-allowed/world", "hola.mundo"), nil) + + assert.EqualError(t, err, "repository: 'releases-not-allowed' is not allowed. Allowed repos: '[releases]'") +} + +const testUrl = "/hello/world" + +func TestRepoInUrlTrue(t *testing.T) { + match, err := repoInUrl("hello", testUrl) + if err != nil { + t.Error(err) + } + assert.Equal(t, true, match) +} + +func TestRepoInUrlFalse(t *testing.T) { + match, err := repoInUrl("hello123", testUrl) + if err != nil { + t.Error(err) + } + assert.Equal(t, false, match) +} func TestAllowedRepos(t *testing.T) { expRepos := []string{"maven/releases", "maven/3rdparty-maven", "maven/3rdparty-maven-gradle-plugins", "maven/3rdparty-maven-spring"} diff --git a/internal/app/yaam/artifact/generic.go b/internal/app/yaam/artifact/generic.go index c4ee5f4..b5a1dda 100644 --- a/internal/app/yaam/artifact/generic.go +++ b/internal/app/yaam/artifact/generic.go @@ -7,8 +7,7 @@ import ( "path/filepath" "strconv" - "github.com/030/yaam/internal/pkg/artifact" - "github.com/030/yaam/internal/pkg/file" + "github.com/030/yaam/internal/app/yaam/file" log "github.com/sirupsen/logrus" ) @@ -21,7 +20,7 @@ type Generic struct { } func (g Generic) Publish() error { - if err := artifact.StoreOnDisk(g.RequestURI, g.RequestBody); err != nil { + if err := StoreOnDisk(g.RequestURI, g.RequestBody); err != nil { return err } @@ -29,7 +28,7 @@ func (g Generic) Publish() error { } func (g Generic) Read() error { - f, err := artifact.FilepathOnDisk(g.RequestURI) + f, err := FilepathOnDisk(g.RequestURI) if err != nil { return err } diff --git a/internal/app/yaam/artifact/maven.go b/internal/app/yaam/artifact/maven.go index 066a7b8..30554f3 100644 --- a/internal/app/yaam/artifact/maven.go +++ b/internal/app/yaam/artifact/maven.go @@ -7,9 +7,8 @@ import ( "path/filepath" "reflect" - "github.com/030/yaam/internal/pkg/artifact" - "github.com/030/yaam/internal/pkg/file" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/file" + "github.com/030/yaam/internal/app/yaam/project" log "github.com/sirupsen/logrus" ) @@ -19,17 +18,17 @@ type Maven struct { RequestURI string } -func maven(url string, repoInConfigFile artifact.PublicRepository) (artefact, error) { +func maven(url string, repoInConfigFile PublicRepository) (artefact, error) { h, err := project.RepositoriesHome() if err != nil { return artefact{}, err } - if err := artifact.Dir(url); err != nil { + if err := Dir(url); err != nil { return artefact{}, err } - du, err := artifact.DownloadUrl(repoInConfigFile.Url, repoInConfigFile.Regex, url) + du, err := DownloadUrl(repoInConfigFile.Url, repoInConfigFile.Regex, url) if err != nil { return artefact{}, err } @@ -64,7 +63,7 @@ func (m Maven) Preserve(urlStrings ...string) error { } log.Debugf("urlString: '%s'", urlString) - repoInConfigFile, err := artifact.RepoInConfigFile(m.ResponseWriter, urlString, "maven") + repoInConfigFile, err := RepoInConfigFile(m.ResponseWriter, urlString, "maven") if err != nil { return err } @@ -94,7 +93,7 @@ func (m Maven) Preserve(urlStrings ...string) error { } func (m Maven) Publish() error { - if err := artifact.StoreOnDisk(m.RequestURI, m.RequestBody); err != nil { + if err := StoreOnDisk(m.RequestURI, m.RequestBody); err != nil { return err } @@ -102,7 +101,7 @@ func (m Maven) Publish() error { } func (m Maven) Read() error { - if err := artifact.ReadFromDisk(m.ResponseWriter, m.RequestURI); err != nil { + if err := ReadFromDisk(m.ResponseWriter, m.RequestURI); err != nil { return fmt.Errorf(file.CannotReadErrMsg, err) } @@ -131,7 +130,7 @@ func (m Maven) Unify(name string) error { } if _, fileExists := file.Exists(filepath.Join(h, urlString)); fileExists { - if err := artifact.ReadFromDisk(m.ResponseWriter, urlString); err != nil { + if err := ReadFromDisk(m.ResponseWriter, urlString); err != nil { log.Warnf(file.CannotReadErrMsg, err) } return nil diff --git a/internal/app/yaam/artifact/npm.go b/internal/app/yaam/artifact/npm.go index e15b211..648949d 100644 --- a/internal/app/yaam/artifact/npm.go +++ b/internal/app/yaam/artifact/npm.go @@ -13,9 +13,8 @@ import ( "strings" "time" - "github.com/030/yaam/internal/pkg/artifact" - "github.com/030/yaam/internal/pkg/file" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/file" + "github.com/030/yaam/internal/app/yaam/project" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) @@ -193,7 +192,7 @@ func (n Npm) Preserve(urlStrings ...string) error { urlString = urlStrings[0] } - repoInConfigFile, err := artifact.RepoInConfigFile(n.ResponseWriter, urlString, "npm") + repoInConfigFile, err := RepoInConfigFile(n.ResponseWriter, urlString, "npm") if err != nil { return err } @@ -209,12 +208,12 @@ func (n Npm) Preserve(urlStrings ...string) error { log.Debugf("file: '%s' does not have an extension", dir) dir = dir + ".tmp" } - if err := artifact.Dir(dir); err != nil { + if err := Dir(dir); err != nil { return err } log.Debugf("downloadUrl before entering downloadUrl method: '%s', regex: '%s'", urlString, repoInConfigFile.Regex) - du, err := artifact.DownloadUrl(repoInConfigFile.Url, repoInConfigFile.Regex, urlString) + du, err := DownloadUrl(repoInConfigFile.Url, repoInConfigFile.Regex, urlString) if err != nil { return err } @@ -243,7 +242,7 @@ func (n Npm) Preserve(urlStrings ...string) error { } func (n Npm) Publish() error { - if err := artifact.StoreOnDisk(n.RequestURI, n.RequestBody); err != nil { + if err := StoreOnDisk(n.RequestURI, n.RequestBody); err != nil { return err } @@ -256,7 +255,7 @@ func (n Npm) Read() error { log.Debugf("file: '%s' does not have an extension", reqUrlString) reqUrlString = reqUrlString + ".tmp" } - if err := artifact.ReadFromDisk(n.ResponseWriter, reqUrlString); err != nil { + if err := ReadFromDisk(n.ResponseWriter, reqUrlString); err != nil { return fmt.Errorf(file.CannotReadErrMsg, err) } diff --git a/internal/app/yaam/artifact/npm_test.go b/internal/app/yaam/artifact/npm_test.go index 0920b87..bf6b1d9 100644 --- a/internal/app/yaam/artifact/npm_test.go +++ b/internal/app/yaam/artifact/npm_test.go @@ -5,7 +5,7 @@ import ( "runtime" "testing" - "github.com/030/yaam/internal/pkg/yaamtest" + "github.com/030/yaam/internal/app/yaam/yaamtest" "github.com/tj/assert" ) @@ -47,7 +47,7 @@ func TestVersionShasum(t *testing.T) { dirname := filepath.Dir(filename) exp := "7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - act, err := versionShasum(filepath.Join(dirname, "../..", yaamtest.Testdata, "npm/y18n/-/y18n-5.0.8.tgz")) + act, err := versionShasum(filepath.Join(dirname, "../../../..", yaamtest.Testdata, "npm/y18n/-/y18n-5.0.8.tgz")) if err != nil { t.Error(err) } diff --git a/internal/pkg/artifact/validate.go b/internal/app/yaam/artifact/validate.go similarity index 100% rename from internal/pkg/artifact/validate.go rename to internal/app/yaam/artifact/validate.go diff --git a/internal/pkg/artifact/validate_test.go b/internal/app/yaam/artifact/validate_test.go similarity index 87% rename from internal/pkg/artifact/validate_test.go rename to internal/app/yaam/artifact/validate_test.go index 981e8d5..e2349e4 100644 --- a/internal/pkg/artifact/validate_test.go +++ b/internal/app/yaam/artifact/validate_test.go @@ -4,8 +4,8 @@ import ( "path/filepath" "testing" - "github.com/030/yaam/internal/pkg/project" - "github.com/030/yaam/internal/pkg/yaamtest" + "github.com/030/yaam/internal/app/yaam/project" + "github.com/030/yaam/internal/app/yaam/yaamtest" log "github.com/sirupsen/logrus" "github.com/tj/assert" ) diff --git a/internal/pkg/file/file.go b/internal/app/yaam/file/file.go similarity index 97% rename from internal/pkg/file/file.go rename to internal/app/yaam/file/file.go index 47c9714..7733a22 100644 --- a/internal/pkg/file/file.go +++ b/internal/app/yaam/file/file.go @@ -79,16 +79,17 @@ func CreateIfDoesNotExistInvalidOrEmpty(url, f string, body io.ReadCloser, inval } func EmptyFile(f string) (emptyFile bool) { + emptyFile = false fileSize, fileExists := Exists(f) if !fileExists { - return false + return emptyFile } if fileSize == 0 { log.Errorf("file: '%s' size is 0", f) log.Warnf(WaitMsg, RetryDuration) time.Sleep(RetryDuration) - return true + emptyFile = true } return emptyFile diff --git a/internal/app/yaam/file/file_test.go b/internal/app/yaam/file/file_test.go new file mode 100644 index 0000000..ffdd231 --- /dev/null +++ b/internal/app/yaam/file/file_test.go @@ -0,0 +1,40 @@ +package file + +import ( + "io" + "path/filepath" + "strings" + "testing" + + "github.com/030/yaam/internal/app/yaam/yaamtest" + "github.com/tj/assert" +) + +func TestExists(t *testing.T) { + _, exists := Exists("does-not-exist") + assert.Equal(t, false, exists) + +} + +func TestEmptyFile(t *testing.T) { + empty := EmptyFile(filepath.Join("../..", yaamtest.TestdataCwd, "empty-file.txt")) + assert.Equal(t, true, empty) +} + +func TestEmptyFileFail(t *testing.T) { + empty := EmptyFile("does-not-exist") + assert.Equal(t, false, empty) +} + +func TestCreateIfDoesNotExistInvalidOrEmpty(t *testing.T) { + s := strings.NewReader("Hola mundo!") + rc := io.NopCloser(s) + + err := CreateIfDoesNotExistInvalidOrEmpty("", "/tmp/yaam/testi.txt", rc, true) + assert.NoError(t, err) +} + +func TestCreateIfDoesNotExistInvalidOrEmptyFail(t *testing.T) { + err := CreateIfDoesNotExistInvalidOrEmpty("", "/tmp2/file-does-not-exist", nil, true) + assert.EqualError(t, err, "open /tmp2/file-does-not-exist: no such file or directory") +} diff --git a/internal/pkg/project/project.go b/internal/app/yaam/project/project.go similarity index 100% rename from internal/pkg/project/project.go rename to internal/app/yaam/project/project.go diff --git a/internal/pkg/project/project_test.go b/internal/app/yaam/project/project_test.go similarity index 63% rename from internal/pkg/project/project_test.go rename to internal/app/yaam/project/project_test.go index b39aab1..98c90bb 100644 --- a/internal/pkg/project/project_test.go +++ b/internal/app/yaam/project/project_test.go @@ -7,6 +7,14 @@ import ( "github.com/tj/assert" ) +func TestHome(t *testing.T) { + h, err := Home() + if err != nil { + t.Error(err) + } + assert.Regexp(t, regexp.MustCompile("/home/[a-z]+/.yaam"), h) +} + func TestRepositoriesHome(t *testing.T) { h, err := RepositoriesHome() if err != nil { diff --git a/internal/pkg/yaamtest/config.go b/internal/app/yaam/yaamtest/config.go similarity index 83% rename from internal/pkg/yaamtest/config.go rename to internal/app/yaam/yaamtest/config.go index 5859eda..80536b6 100644 --- a/internal/pkg/yaamtest/config.go +++ b/internal/app/yaam/yaamtest/config.go @@ -7,12 +7,16 @@ import ( ) const ( - Testdata = "../../test/testdata" - gradleHomeDemoProject = Testdata + "/gradle/demo" - npmHomeDemoProject = Testdata + "/npm/demo" + Testdata = "test/testdata" + TestdataCwd = "../../" + Testdata + gradleHomeDemoProject = TestdataCwd + "/gradle/demo" + npmHomeDemoProject = TestdataCwd + "/npm/demo" conf = `--- caches: + apt: + 3rdparty-ubuntu-nl-archive: + url: http://nl.archive.ubuntu.com/ubuntu/ maven: 3rdparty-maven: url: https://repo.maven.apache.org/maven2/ diff --git a/internal/pkg/yaamtest/config_test.go b/internal/app/yaam/yaamtest/config_test.go similarity index 100% rename from internal/pkg/yaamtest/config_test.go rename to internal/app/yaam/yaamtest/config_test.go diff --git a/internal/pkg/yaamtest/generic.go b/internal/app/yaam/yaamtest/generic.go similarity index 96% rename from internal/pkg/yaamtest/generic.go rename to internal/app/yaam/yaamtest/generic.go index 837e07f..5d1d3ad 100644 --- a/internal/pkg/yaamtest/generic.go +++ b/internal/app/yaam/yaamtest/generic.go @@ -7,7 +7,7 @@ import ( "os" "time" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/project" ) func GenericArtifact() error { diff --git a/internal/pkg/yaamtest/generic_test.go b/internal/app/yaam/yaamtest/generic_test.go similarity index 100% rename from internal/pkg/yaamtest/generic_test.go rename to internal/app/yaam/yaamtest/generic_test.go diff --git a/internal/pkg/yaamtest/maven.go b/internal/app/yaam/yaamtest/maven.go similarity index 98% rename from internal/pkg/yaamtest/maven.go rename to internal/app/yaam/yaamtest/maven.go index 481cbc3..5619e4f 100644 --- a/internal/pkg/yaamtest/maven.go +++ b/internal/app/yaam/yaamtest/maven.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/project" ) var ( diff --git a/internal/app/yaam/yaamtest/maven_test.go b/internal/app/yaam/yaamtest/maven_test.go new file mode 100644 index 0000000..94231c5 --- /dev/null +++ b/internal/app/yaam/yaamtest/maven_test.go @@ -0,0 +1,12 @@ +package yaamtest + +import ( + "testing" + + "github.com/tj/assert" +) + +func TestGradleFile(t *testing.T) { + err := gradleFile([]byte("hello"), "world") + assert.EqualError(t, err, "open ../../test/testdata/gradle/demo/world.gradle: no such file or directory") +} diff --git a/internal/pkg/yaamtest/npm.go b/internal/app/yaam/yaamtest/npm.go similarity index 95% rename from internal/pkg/yaamtest/npm.go rename to internal/app/yaam/yaamtest/npm.go index ec70e7e..8287c7a 100644 --- a/internal/pkg/yaamtest/npm.go +++ b/internal/app/yaam/yaamtest/npm.go @@ -7,7 +7,7 @@ import ( "path/filepath" "time" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/project" ) var npmrc = `registry=` + project.Url + `/npm/3rdparty-npm/ diff --git a/internal/pkg/yaamtest/npm_test.go b/internal/app/yaam/yaamtest/npm_test.go similarity index 100% rename from internal/pkg/yaamtest/npm_test.go rename to internal/app/yaam/yaamtest/npm_test.go diff --git a/internal/pkg/yaamtest/status.go b/internal/app/yaam/yaamtest/status.go similarity index 76% rename from internal/pkg/yaamtest/status.go rename to internal/app/yaam/yaamtest/status.go index bc5b503..6d54785 100644 --- a/internal/pkg/yaamtest/status.go +++ b/internal/app/yaam/yaamtest/status.go @@ -5,10 +5,10 @@ import ( "net/http" "time" - "github.com/030/yaam/internal/pkg/project" + "github.com/030/yaam/internal/app/yaam/project" ) -func StatusHelper(method, uri string, body io.Reader, timeout time.Duration) (string, error) { +func Status(method, uri string, body io.Reader, timeout time.Duration) (string, error) { client := &http.Client{ Timeout: time.Second * timeout, } diff --git a/internal/pkg/yaamtest/status_test.go b/internal/app/yaam/yaamtest/status_test.go similarity index 100% rename from internal/pkg/yaamtest/status_test.go rename to internal/app/yaam/yaamtest/status_test.go diff --git a/internal/pkg/artifact/artifact.go b/internal/pkg/artifact/artifact.go deleted file mode 100644 index 8069cea..0000000 --- a/internal/pkg/artifact/artifact.go +++ /dev/null @@ -1,201 +0,0 @@ -package artifact - -import ( - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - - "github.com/030/yaam/internal/pkg/file" - "github.com/030/yaam/internal/pkg/project" - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" -) - -func createHomeAndReturnPath(requestURI string) (string, error) { - h, err := project.RepositoriesHome() - if err != nil { - return "", err - } - - artifactPath := filepath.Join(h, requestURI) - artifactHome := filepath.Dir(artifactPath) - if err := os.MkdirAll(artifactHome, os.ModePerm); err != nil { - return "", err - } - - return artifactPath, nil -} - -func createIfDoesNotExist(path string, requestBody io.ReadCloser) error { - if _, fileExists := file.Exists(path); !fileExists { - dst, err := os.Create(filepath.Clean(path)) - if err != nil { - return err - } - - defer func() { - if err := dst.Close(); err != nil { - panic(err) - } - }() - - w, err := io.Copy(dst, requestBody) - if err != nil { - log.Error(err) - } - log.Debugf("file: '%s' created and it contains: '%d' bytes", path, w) - if err := dst.Sync(); err != nil { - return err - } - } else { - log.Debugf("file: '%s' exists already", path) - } - return nil -} - -func StoreOnDisk(requestURI string, requestBody io.ReadCloser) error { - if err := validate(requestURI); err != nil { - return err - } - - path, err := createHomeAndReturnPath(requestURI) - if err != nil { - return err - } - - if err := createIfDoesNotExist(path, requestBody); err != nil { - return err - } - - return nil -} - -func FilepathOnDisk(url string) (string, error) { - h, err := project.RepositoriesHome() - if err != nil { - return "", err - } - - f := filepath.Join(h, url) - log.Debugf("constructed filepath: '%s' after concatenating home: '%s' to url: '%s'", f, h, url) - return f, nil -} - -func ReadFromDisk(w http.ResponseWriter, reqURL string) error { - f, err := FilepathOnDisk(reqURL) - if err != nil { - return err - } - - log.Debugf("reading file: '%s' from disk...", f) - b, err := os.ReadFile(filepath.Clean(f)) - if err != nil { - return err - } - if _, err := io.WriteString(w, string(b)); err != nil { - return err - } - - return nil -} - -// ReadRepositoriesAndUrlsFromConfigFileAndCacheArtifact reads a repositories -// yaml file that contains repositories and their URLs. If a request is -// attempted to download a file, it will look up the name in the config file -// and find the public URLs so it can download the file from the public maven -// repository and cache it on disk. -func RepoInConfigFile(w http.ResponseWriter, urlString, artifactType string) (PublicRepository, error) { - reposAndElements := viper.GetStringMap("caches." + artifactType) - if len(reposAndElements) == 0 { - return PublicRepository{}, fmt.Errorf("caches: '%s' not found in config file", artifactType) - } - - for repo, elements := range reposAndElements { - url, ok := elements.(map[string]interface{})["url"] - if ok { - log.Debugf("url: '%s'", url) - } - user, ok := elements.(map[string]interface{})["user"] - if ok { - log.Debugf("user: '%s'", user) - } - pass, ok := elements.(map[string]interface{})["pass"] - if ok { - log.Debug("pass: **********") - } - - log.Debugf("trying to cache artifact from: '%s'...", urlString) - - rr := repoRegex(repo, artifactType) - log.Debugf("repoRegex: '%s'", rr) - - pr := PublicRepository{Name: repo, Regex: rr, Url: url.(string)} - if user != nil && pass != nil { - pr.User = user.(string) - pr.Pass = pass.(string) - } - - riu, err := repoInUrl(rr, urlString) - if err != nil { - return PublicRepository{}, err - } - log.Debugf("repoInUrl: '%t'", riu) - - if riu { - return pr, nil - } - } - - return PublicRepository{}, nil -} - -type PublicRepository struct { - Name, Regex, Url, User, Pass string -} - -func repoRegex(repo, repoType string) string { - return `^/` + repoType + `/` + repo + `/(.*)$` -} - -func repoInUrl(repoRegex, url string) (bool, error) { - log.Debugf("check whether url: '%s' contains repo according to regex: '%s'", url, repoRegex) - match, err := regexp.MatchString(repoRegex, url) - if err != nil { - return false, err - } - log.Debugf("outcome regex check: '%t'", match) - - return match, nil -} - -func DownloadUrl(publicRepoUrl, regex, url string) (string, error) { - log.Debugf("check whether url: '%s' matches regex: '%s'. Params -> publicRepoUrl: '%s', regex: '%s' and url: '%s'", url, regex, publicRepoUrl, regex, url) - r := regexp.MustCompile(regex) - match := r.FindStringSubmatch(url) - log.Debugf("number of matching elements: %d. Content: '%v'", len(match), match) - if len(match) != 2 { - return "", fmt.Errorf("should be 3! publicRepoUrl: '%s', regex: '%s', url: '%s'", publicRepoUrl, regex, url) - } - - u := r.ReplaceAllString(url, publicRepoUrl+`$1`) - - return u, nil -} - -func Dir(path string) error { - h, err := project.RepositoriesHome() - if err != nil { - return err - } - - dir := filepath.Join(h, filepath.Dir(path)) - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - - return nil -} diff --git a/internal/pkg/artifact/artifact_test.go b/internal/pkg/artifact/artifact_test.go deleted file mode 100644 index 713da19..0000000 --- a/internal/pkg/artifact/artifact_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package artifact - -import ( - "io" - "path/filepath" - "strings" - "testing" - - "github.com/030/yaam/internal/pkg/project" - "github.com/030/yaam/internal/pkg/yaamtest" - log "github.com/sirupsen/logrus" - "github.com/tj/assert" -) - -func init() { - if err := yaamtest.Config(); err != nil { - log.Fatal(err) - } - - if err := project.Config(); err != nil { - log.Fatal(err) - } -} - -func TestStoreOnDisk(t *testing.T) { - s := strings.NewReader("Hola mundo!") - rc := io.NopCloser(s) - - err := StoreOnDisk(filepath.Join("/maven/releases/world", "hola.mundo"), rc) - if err != nil { - t.Error(err) - } - - assert.NoError(t, err) -} - -func TestStoreOnDiskFail(t *testing.T) { - err := StoreOnDisk(filepath.Join("/maven/releases-not-allowed/world", "hola.mundo"), nil) - - assert.EqualError(t, err, "repository: 'releases-not-allowed' is not allowed. Allowed repos: '[releases]'") -} - -const testUrl = "/hello/world" - -func TestRepoInUrlTrue(t *testing.T) { - match, err := repoInUrl("hello", testUrl) - if err != nil { - t.Error(err) - } - assert.Equal(t, true, match) -} - -func TestRepoInUrlFalse(t *testing.T) { - match, err := repoInUrl("hello123", testUrl) - if err != nil { - t.Error(err) - } - assert.Equal(t, false, match) -} diff --git a/internal/pkg/file/file_test.go b/internal/pkg/file/file_test.go deleted file mode 100644 index b691ba5..0000000 --- a/internal/pkg/file/file_test.go +++ /dev/null @@ -1 +0,0 @@ -package file diff --git a/internal/pkg/yaamtest/maven_test.go b/internal/pkg/yaamtest/maven_test.go deleted file mode 100644 index e05d8cb..0000000 --- a/internal/pkg/yaamtest/maven_test.go +++ /dev/null @@ -1 +0,0 @@ -package yaamtest diff --git a/test/testdata/empty-file.txt b/test/testdata/empty-file.txt new file mode 100644 index 0000000..e69de29