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/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/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/.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/.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..e7700e3 100644
--- a/README.md
+++ b/README.md
@@ -52,44 +52,245 @@ 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.
-## Configuration
+## Quickstart
-### General
+Create a directory and change the permissions to ensure that YAAM can store
+artifacts:
-- [Base.](docs/config/BASE.md)
+```bash
+mkdir ~/.yaam/repositories
+sudo chown 9999 -R ~/.yaam/repositories
+```
-### Artifact types
+Configure YAAM by creating a `~/.yaam/config.yml` with the following content:
-- [Generic.](docs/config/GENERIC.md)
-- [Maven.](docs/config/MAVEN.md)
-- [NPM.](docs/config/NPM.md)
+```bash
+---
+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:
+ 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
+Start YAAM:
-- [Binary.](docs/start/BINARY.md)
-- [Docker.](docs/start/DOCKER.md)
-- [K8s/OpenShift.](docs/start/K8SOPENSHIFT.md)
+```bash
+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
+```
+
+Once YAAM has been started, configure a project to ensure that artifacts will
+be downloaded from this artifact manager.
+
+### Apt
+
+sudo vim /etc/apt/auth.conf.d/hello.conf
+
+```bash
+machine http://localhost
+login hello
+password world
+```
+
+sudo vim /etc/apt/sources.list
+
+```bash
+deb http://localhost:25213/apt/3rdparty-ubuntu-nl-archive/ focal main restricted
+```
+
+Preserve the artifacts:
+
+```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
+```
-## Capabilities
+and restart the controller pod.
-### Publish
+Verify in the `/etc/nginx/nginx.conf` file that the `client_max_body_size` has
+been increased to 5G.
-- [Generic.](docs/publish/GENERIC.md)
-- [Maven.](docs/publish/MAVEN.md)
+Download:
-### Preserve
+```bash
+curl -u hello:world http://yaam.some-domain/generic/something/world6.iso \
+-o /tmp/world6.iso
+```
-- [Maven.](docs/preserve/MAVEN.md)
-- [NPM.](docs/preserve/NPM.md)
+### Gradle
-### Unify
+Adjust the `build.gradle` and/or `settings.gradle`:
-- [Maven.](docs/unify/MAVEN.md)
+```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"
+ }
+ }
+ }
+}
+```
+
+Preserve the artifacts:
+
+```bash
+./gradlew clean
+```
+
+or publish them:
+
+```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 fa1ebb7..822d6de 100644
--- a/cmd/yaam/main.go
+++ b/cmd/yaam/main.go
@@ -7,9 +7,9 @@ import (
"os"
"time"
- "github.com/030/yaam/internal/api"
- "github.com/030/yaam/internal/artifact"
- "github.com/030/yaam/internal/pkg/project"
+ "github.com/030/yaam/internal/app/yaam/api"
+ "github.com/030/yaam/internal/app/yaam/artifact"
+ "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 {
@@ -128,7 +155,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 {
@@ -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)
@@ -191,6 +219,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..1d25829 100644
--- a/cmd/yaam/main_test.go
+++ b/cmd/yaam/main_test.go
@@ -2,527 +2,178 @@ 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/app/yaam/project"
+ "github.com/030/yaam/internal/app/yaam/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=`
+ aptUri = "/apt/3rdparty-ubuntu-nl-archive/some.iso"
+ 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.Status("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)
- }
-
- return 0, nil
-}
-
-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)
- }
- 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
+ t.Error(err)
+ return
}
- return nil
-}
-
-func testGradleSettingsFileHelper(repos []string) error {
- content := `
-pluginManagement {
- repositories {` +
- testGradleMavenRepositoriesFileHelper(repos) + `
- }
-}
-
-rootProject.name = 'demo'
-`
- 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
- }
- req.SetBasicAuth("hello", pass)
- resp, err := client.Do(req)
- if err != nil {
- return "", err
- }
+// Apt
+func TestApt(t *testing.T) {
+ resp, _ := yaamtest.GenericArtifactReq("GET", aptUri, nil)
defer func() {
if err := resp.Body.Close(); err != nil {
panic(err)
}
}()
-
- b, err := io.ReadAll(resp.Body)
+ bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
- return "", err
- }
-
- return string(b), nil
-}
-
-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)
- }
- }()
-
- 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
-}
-
-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")
-
- client := &http.Client{
- Timeout: time.Second * 120,
- }
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
+ t.Error(err)
}
- return resp, nil
-}
+ bodyString := string(bodyBytes)
-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.Equal(t, "check the server logs\n", bodyString)
}
-func TestGenericArtifact(t *testing.T) {
- // Upload
- if err := testGenericArtifactHelper(); err != nil {
- t.Error(err)
- }
- uri := "/generic/something/some.iso"
- b, err := os.ReadFile(testDirIso)
- if err != nil {
+/*
+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)
}
- r := bytes.NewReader(b)
- _, err = testGenericArtifactReqHelper("POST", uri, r)
- if err != nil {
+ if err := yaamtest.GradleSettingsFile(repos); 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))
-}
+ exitCode, err := yaamtest.GradleCleanBuild("world")
-func TestGenericArtifactFail(t *testing.T) {
- // Upload
- uri := "/generic/something2/some.iso"
- resp, err := testGenericArtifactReqHelper("POST", uri, nil)
- if err != nil {
- t.Error(err)
- }
- b, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Error(err)
- }
- bodyString := string(b)
- assert.Equal(t, "check the server logs\n", bodyString)
- assert.Equal(t, 500, resp.StatusCode)
assert.NoError(t, err)
+ assert.Equal(t, 0, exitCode)
+}
- // Download
- resp, err = testGenericArtifactReqHelper("GET", uri, nil)
- if err != nil {
+func TestGradleCleanBuildGroupFail(t *testing.T) {
+ repos := []string{"maven/groups/helloworld"}
+ if err := yaamtest.GradleBuildFile(repos, yaamtest.MavenReleasesRepo); err != nil {
t.Error(err)
}
- assert.Equal(t, 404, resp.StatusCode)
-}
-
-func TestStatus(t *testing.T) {
- b, err := testStatusHelper("GET", "world", "/status", nil, 10)
- if err != nil {
+ if err := yaamtest.GradleSettingsFile(yaamtest.MavenRepos); err != nil {
t.Error(err)
}
- assert.NoError(t, err)
- assert.Equal(t, "ok", b)
+ exitCode, err := yaamtest.GradleCleanBuild("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)
}
-func TestMainNpmBuild(t *testing.T) {
- exitCode, err := testNpmConfigHelper()
- if err != nil {
- t.Error(err)
- return
- }
+func TestGradlePublish(t *testing.T) {
+ exitCode, err := yaamtest.GradlePublish(yaamtest.MavenReleasesRepo)
assert.NoError(t, err)
assert.Equal(t, 0, exitCode)
}
-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")
- if err != nil {
- t.Error(err)
- }
+func TestGradlePublishFail(t *testing.T) {
+ exitCode, err := yaamtest.GradlePublish("maven/releases-non-existent")
- assert.NoError(t, err)
- assert.Equal(t, 0, exitCode)
+ 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 TestMainGradleCleanBuildFail(t *testing.T) {
- if err := testConfigHelper(); 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)
}
- if err := testGradleBuildFileHelper(mavenRepos, mavenReleasesRepo); err != nil {
+ b, err := os.ReadFile(yaamtest.DirIso)
+ if err != nil {
t.Error(err)
}
- if err := testGradleSettingsFileHelper(mavenRepos); err != nil {
+ r := bytes.NewReader(b)
+ _, err = yaamtest.GenericArtifactReq("POST", genericUri, r)
+ if 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)
+ assert.NoError(t, err)
}
-func TestMainGradleCleanBuildNonMavenFail(t *testing.T) {
- repos := []string{"3rdparty-maven", "3rdparty-maven-gradle-plugins", "3rdparty-maven-spring", "releases"}
- if err := testGradleBuildFileHelper(repos, "releases"); err != nil {
+func TestGenericArtifactUploadFail(t *testing.T) {
+ resp, err := yaamtest.GenericArtifactReq("POST", genericUriFail, nil)
+ if err != nil {
t.Error(err)
}
- if err := testGradleSettingsFileHelper(repos); err != nil {
+ b, err := io.ReadAll(resp.Body)
+ if 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)
-
+ bodyString := string(b)
+ assert.Equal(t, "check the server logs\n", bodyString)
+ assert.Equal(t, 500, resp.StatusCode)
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 {
+func TestGenericArtifactDownload(t *testing.T) {
+ resp, err := yaamtest.GenericArtifactReq("GET", genericUri, nil)
+ if err != nil {
t.Error(err)
}
- if err := testGradleSettingsFileHelper(repos); err != nil {
+ w, err := yaamtest.GenericArtifactWriteOnDisk(resp)
+ if err != nil {
t.Error(err)
}
-
- exitCode, err := testMainGradleCleanBuildHelper("world")
-
- assert.NoError(t, err)
- assert.Equal(t, 0, exitCode)
+ assert.Equal(t, "written: 3826831360", fmt.Sprintf("written: %d", w))
}
-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 {
+func TestGenericArtifactDownloadFail(t *testing.T) {
+ resp, err := yaamtest.GenericArtifactReq("GET", genericUriFail, nil)
+ if 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..ef2be53
--- /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/app/yaam/artifact/artifact_test.go
+ text: "don't use `init` function"
+ - linters:
+ - gochecknoinits
+ path: internal/app/yaam/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/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 f3a90b1..0000000
--- a/docs/config/MAVEN.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# Maven
-
-~/.yaam/conf/caches.yaml
-
-```bash
----
-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/
-```
-
-~/.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 944280a..0000000
--- a/docs/config/NPM.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# NPM
-
-~/.yaam/conf/caches.yaml
-
-```bash
----
-mavenReposAndUrls:
- 3rdparty-npm: https://registry.npmjs.org/
-```
-
-~/.yaam/conf/repositories/npm.yaml
-
-```bash
----
-allowedRepos:
- - npm-releases
-```
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/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/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/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/artifact/maven_test.go b/internal/app/yaam/artifact/apt_test.go
similarity index 100%
rename from internal/artifact/maven_test.go
rename to internal/app/yaam/artifact/apt_test.go
diff --git a/internal/pkg/artifact/artifact.go b/internal/app/yaam/artifact/artifact.go
similarity index 68%
rename from internal/pkg/artifact/artifact.go
rename to internal/app/yaam/artifact/artifact.go
index 8d3c90a..12ec5d8 100644
--- a/internal/pkg/artifact/artifact.go
+++ b/internal/app/yaam/artifact/artifact.go
@@ -8,12 +8,27 @@ import (
"path/filepath"
"regexp"
- "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/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
+}
func createHomeAndReturnPath(requestURI string) (string, error) {
h, err := project.RepositoriesHome()
if err != nil {
@@ -107,31 +122,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
+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)
}
- viper.SetConfigName("caches")
- viper.SetConfigType("yaml")
- viper.AddConfigPath(filepath.Join(yh, "conf"))
- if err := viper.ReadInConfig(); err != nil {
- return PublicRepository{}, err
- }
-
- 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 +160,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 +168,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 +188,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/app/yaam/artifact/artifact_test.go
similarity index 54%
rename from internal/pkg/artifact/artifact_test.go
rename to internal/app/yaam/artifact/artifact_test.go
index 04dc07e..18aaee0 100644
--- a/internal/pkg/artifact/artifact_test.go
+++ b/internal/app/yaam/artifact/artifact_test.go
@@ -2,13 +2,25 @@ package artifact
import (
"io"
+ "log"
"path/filepath"
"strings"
"testing"
+ "github.com/030/yaam/internal/app/yaam/project"
+ "github.com/030/yaam/internal/app/yaam/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 TestStoreOnDisk(t *testing.T) {
s := strings.NewReader("Hola mundo!")
rc := io.NopCloser(s)
@@ -44,3 +56,21 @@ func TestRepoInUrlFalse(t *testing.T) {
}
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"}
+ 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 79%
rename from internal/artifact/generic.go
rename to internal/app/yaam/artifact/generic.go
index c4ee5f4..b5a1dda 100644
--- a/internal/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/generic_test.go b/internal/app/yaam/artifact/generic_test.go
new file mode 100644
index 0000000..c146142
--- /dev/null
+++ b/internal/app/yaam/artifact/generic_test.go
@@ -0,0 +1 @@
+package artifact
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 73%
rename from internal/artifact/maven.go
rename to internal/app/yaam/artifact/maven.go
index bd796da..30554f3 100644
--- a/internal/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
}
@@ -41,8 +40,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 +63,7 @@ func (m Maven) Preserve(urlStrings ...string) error {
}
log.Debugf("urlString: '%s'", urlString)
- repoInConfigFile, err := artifact.RepoInConfigFile(m.ResponseWriter, urlString)
+ repoInConfigFile, err := RepoInConfigFile(m.ResponseWriter, urlString, "maven")
if err != nil {
return err
}
@@ -74,7 +74,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
}
@@ -93,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
}
@@ -101,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)
}
@@ -130,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/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..648949d
--- /dev/null
+++ b/internal/app/yaam/artifact/npm.go
@@ -0,0 +1,263 @@
+package artifact
+
+import (
+ "crypto/sha1" // #nosec
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "strings"
+ "time"
+
+ "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"
+)
+
+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 := 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 := Dir(dir); err != nil {
+ return err
+ }
+
+ log.Debugf("downloadUrl before entering downloadUrl method: '%s', regex: '%s'", urlString, repoInConfigFile.Regex)
+ du, err := 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 := 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 := 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..bf6b1d9
--- /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/app/yaam/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/pkg/artifact/validate.go b/internal/app/yaam/artifact/validate.go
similarity index 72%
rename from internal/pkg/artifact/validate.go
rename to internal/app/yaam/artifact/validate.go
index ea423fd..40f8156 100644
--- a/internal/pkg/artifact/validate.go
+++ b/internal/app/yaam/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/app/yaam/artifact/validate_test.go b/internal/app/yaam/artifact/validate_test.go
new file mode 100644
index 0000000..e2349e4
--- /dev/null
+++ b/internal/app/yaam/artifact/validate_test.go
@@ -0,0 +1,36 @@
+package artifact
+
+import (
+ "path/filepath"
+ "testing"
+
+ "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"
+)
+
+func init() {
+ if err := yaamtest.Config(); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := project.Config(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func TestValidate(t *testing.T) {
+ expDir := filepath.Join("/maven", "releases", "world")
+ err := validate(filepath.Join(expDir, "hola.mundo"))
+ if err != nil {
+ t.Error(err)
+ }
+
+ assert.NoError(t, err)
+}
+
+func TestValidateFail(t *testing.T) {
+ 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/app/yaam/file/file.go
similarity index 65%
rename from internal/pkg/file/file.go
rename to internal/app/yaam/file/file.go
index aaca4e1..7733a22 100644
--- a/internal/pkg/file/file.go
+++ b/internal/app/yaam/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,22 +73,23 @@ 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
}
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 55%
rename from internal/pkg/project/project.go
rename to internal/app/yaam/project/project.go
index e5deaec..5945f6e 100644
--- a/internal/pkg/project/project.go
+++ b/internal/app/yaam/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/app/yaam/project/project_test.go b/internal/app/yaam/project/project_test.go
new file mode 100644
index 0000000..98c90bb
--- /dev/null
+++ b/internal/app/yaam/project/project_test.go
@@ -0,0 +1,24 @@
+package project
+
+import (
+ "regexp"
+ "testing"
+
+ "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 {
+ t.Error(err)
+ }
+ assert.Regexp(t, regexp.MustCompile("/home/[a-z]+/.yaam/repositories"), h)
+}
diff --git a/internal/app/yaam/yaamtest/config.go b/internal/app/yaam/yaamtest/config.go
new file mode 100644
index 0000000..80536b6
--- /dev/null
+++ b/internal/app/yaam/yaamtest/config.go
@@ -0,0 +1,79 @@
+package yaamtest
+
+import (
+ "os"
+ "path/filepath"
+ "time"
+)
+
+const (
+ 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/
+ 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/app/yaam/yaamtest/config_test.go b/internal/app/yaam/yaamtest/config_test.go
new file mode 100644
index 0000000..e05d8cb
--- /dev/null
+++ b/internal/app/yaam/yaamtest/config_test.go
@@ -0,0 +1 @@
+package yaamtest
diff --git a/internal/app/yaam/yaamtest/generic.go b/internal/app/yaam/yaamtest/generic.go
new file mode 100644
index 0000000..5d1d3ad
--- /dev/null
+++ b/internal/app/yaam/yaamtest/generic.go
@@ -0,0 +1,81 @@
+package yaamtest
+
+import (
+ "errors"
+ "io"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/030/yaam/internal/app/yaam/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/app/yaam/yaamtest/generic_test.go b/internal/app/yaam/yaamtest/generic_test.go
new file mode 100644
index 0000000..e05d8cb
--- /dev/null
+++ b/internal/app/yaam/yaamtest/generic_test.go
@@ -0,0 +1 @@
+package yaamtest
diff --git a/internal/app/yaam/yaamtest/maven.go b/internal/app/yaam/yaamtest/maven.go
new file mode 100644
index 0000000..5619e4f
--- /dev/null
+++ b/internal/app/yaam/yaamtest/maven.go
@@ -0,0 +1,162 @@
+package yaamtest
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/030/yaam/internal/app/yaam/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/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/app/yaam/yaamtest/npm.go b/internal/app/yaam/yaamtest/npm.go
new file mode 100644
index 0000000..8287c7a
--- /dev/null
+++ b/internal/app/yaam/yaamtest/npm.go
@@ -0,0 +1,42 @@
+package yaamtest
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+
+ "github.com/030/yaam/internal/app/yaam/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/app/yaam/yaamtest/npm_test.go b/internal/app/yaam/yaamtest/npm_test.go
new file mode 100644
index 0000000..e05d8cb
--- /dev/null
+++ b/internal/app/yaam/yaamtest/npm_test.go
@@ -0,0 +1 @@
+package yaamtest
diff --git a/internal/app/yaam/yaamtest/status.go b/internal/app/yaam/yaamtest/status.go
new file mode 100644
index 0000000..6d54785
--- /dev/null
+++ b/internal/app/yaam/yaamtest/status.go
@@ -0,0 +1,36 @@
+package yaamtest
+
+import (
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/030/yaam/internal/app/yaam/project"
+)
+
+func Status(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/app/yaam/yaamtest/status_test.go b/internal/app/yaam/yaamtest/status_test.go
new file mode 100644
index 0000000..e05d8cb
--- /dev/null
+++ b/internal/app/yaam/yaamtest/status_test.go
@@ -0,0 +1 @@
+package yaamtest
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/validate_test.go b/internal/pkg/artifact/validate_test.go
deleted file mode 100644
index 361e34e..0000000
--- a/internal/pkg/artifact/validate_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package artifact
-
-import (
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "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
- }
-
- os.Setenv("YAAM_DEBUG", "true")
- os.Setenv("YAAM_USER", "hello")
-
- return nil
-}
-
-func init() {
- if err := ConfigHelper(); err != nil {
- panic(err)
- }
-}
-
-func TestValidate(t *testing.T) {
- expDir := filepath.Join("/maven", "releases", "world")
- err := validate(filepath.Join(expDir, "hola.mundo"))
- if err != nil {
- t.Error(err)
- }
-
- assert.NoError(t, err)
-}
-
-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)
-}
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/testdata/empty-file.txt b/test/testdata/empty-file.txt
new file mode 100644
index 0000000..e69de29
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 0000000..0647d78
Binary files /dev/null and b/test/testdata/npm/y18n/-/y18n-5.0.8.tgz differ