mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
feat: add option to fetch remote licenses for pnpm-lock.yaml files (#4286)
Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com>
This commit is contained in:
parent
e923db2a94
commit
c0f32e1dba
@ -18,8 +18,9 @@ func NewPackageCataloger() pkg.Cataloger {
|
|||||||
func NewLockCataloger(cfg CatalogerConfig) pkg.Cataloger {
|
func NewLockCataloger(cfg CatalogerConfig) pkg.Cataloger {
|
||||||
yarnLockAdapter := newGenericYarnLockAdapter(cfg)
|
yarnLockAdapter := newGenericYarnLockAdapter(cfg)
|
||||||
packageLockAdapter := newGenericPackageLockAdapter(cfg)
|
packageLockAdapter := newGenericPackageLockAdapter(cfg)
|
||||||
|
pnpmLockAdapter := newGenericPnpmLockAdapter(cfg)
|
||||||
return generic.NewCataloger("javascript-lock-cataloger").
|
return generic.NewCataloger("javascript-lock-cataloger").
|
||||||
WithParserByGlobs(packageLockAdapter.parsePackageLock, "**/package-lock.json").
|
WithParserByGlobs(packageLockAdapter.parsePackageLock, "**/package-lock.json").
|
||||||
WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock").
|
WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock").
|
||||||
WithParserByGlobs(parsePnpmLock, "**/pnpm-lock.yaml")
|
WithParserByGlobs(pnpmLockAdapter.parsePnpmLock, "**/pnpm-lock.yaml")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,7 @@ func newPackageLockV1Package(ctx context.Context, cfg CatalogerConfig, resolver
|
|||||||
licenseSet = pkg.NewLicenseSet(licenses...)
|
licenseSet = pkg.NewLicenseSet(licenses...)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("unable to extract licenses from javascript yarn.lock for package %s:%s: %+v", name, version, err)
|
log.Debugf("unable to extract licenses from javascript package-lock.json for package %s:%s: %+v", name, version, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ func newPackageLockV2Package(ctx context.Context, cfg CatalogerConfig, resolver
|
|||||||
licenseSet = pkg.NewLicenseSet(licenses...)
|
licenseSet = pkg.NewLicenseSet(licenses...)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("unable to extract licenses from javascript yarn.lock for package %s:%s: %+v", name, u.Version, err)
|
log.Debugf("unable to extract licenses from javascript package-lock.json for package %s:%s: %+v", name, u.Version, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,19 @@ func newPackageLockV2Package(ctx context.Context, cfg CatalogerConfig, resolver
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPnpmPackage(ctx context.Context, resolver file.Resolver, location file.Location, name, version string) pkg.Package {
|
func newPnpmPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string) pkg.Package {
|
||||||
|
var licenseSet pkg.LicenseSet
|
||||||
|
|
||||||
|
if cfg.SearchRemoteLicenses {
|
||||||
|
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, version)
|
||||||
|
if err == nil && license != "" {
|
||||||
|
licenses := pkg.NewLicensesFromValuesWithContext(ctx, license)
|
||||||
|
licenseSet = pkg.NewLicenseSet(licenses...)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("unable to extract licenses from javascript pnpm-lock.yaml for package %s:%s: %+v", name, version, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return finalizeLockPkg(
|
return finalizeLockPkg(
|
||||||
ctx,
|
ctx,
|
||||||
resolver,
|
resolver,
|
||||||
@ -169,6 +181,7 @@ func newPnpmPackage(ctx context.Context, resolver file.Resolver, location file.L
|
|||||||
pkg.Package{
|
pkg.Package{
|
||||||
Name: name,
|
Name: name,
|
||||||
Version: version,
|
Version: version,
|
||||||
|
Licenses: licenseSet,
|
||||||
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||||
PURL: packageURL(name, version),
|
PURL: packageURL(name, version),
|
||||||
Language: pkg.JavaScript,
|
Language: pkg.JavaScript,
|
||||||
|
|||||||
@ -18,9 +18,6 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// integrity check
|
|
||||||
var _ generic.Parser = parsePnpmLock
|
|
||||||
|
|
||||||
// pnpmPackage holds the raw name and version extracted from the lockfile.
|
// pnpmPackage holds the raw name and version extracted from the lockfile.
|
||||||
type pnpmPackage struct {
|
type pnpmPackage struct {
|
||||||
Name string
|
Name string
|
||||||
@ -45,6 +42,16 @@ type pnpmV9LockYaml struct {
|
|||||||
Packages map[string]interface{} `yaml:"packages"`
|
Packages map[string]interface{} `yaml:"packages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type genericPnpmLockAdapter struct {
|
||||||
|
cfg CatalogerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenericPnpmLockAdapter(cfg CatalogerConfig) genericPnpmLockAdapter {
|
||||||
|
return genericPnpmLockAdapter{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse implements the pnpmLockfileParser interface for v6-v8 lockfiles.
|
// Parse implements the pnpmLockfileParser interface for v6-v8 lockfiles.
|
||||||
func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, error) {
|
func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, error) {
|
||||||
if err := yaml.Unmarshal(data, p); err != nil {
|
if err := yaml.Unmarshal(data, p); err != nil {
|
||||||
@ -116,7 +123,7 @@ func newPnpmLockfileParser(version float64) pnpmLockfileParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parsePnpmLock is the main parser function for pnpm-lock.yaml files.
|
// parsePnpmLock is the main parser function for pnpm-lock.yaml files.
|
||||||
func parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
data, err := io.ReadAll(reader)
|
data, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err)
|
return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err)
|
||||||
@ -142,7 +149,7 @@ func parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Envir
|
|||||||
|
|
||||||
packages := make([]pkg.Package, len(pnpmPkgs))
|
packages := make([]pkg.Package, len(pnpmPkgs))
|
||||||
for i, p := range pnpmPkgs {
|
for i, p := range pnpmPkgs {
|
||||||
packages[i] = newPnpmPackage(ctx, resolver, reader.Location, p.Name, p.Version)
|
packages[i] = newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")
|
return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
package javascript
|
package javascript
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
@ -50,7 +55,8 @@ func TestParsePnpmLock(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||||
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePnpmV6Lock(t *testing.T) {
|
func TestParsePnpmV6Lock(t *testing.T) {
|
||||||
@ -142,7 +148,8 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||||
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePnpmLockV9(t *testing.T) {
|
func TestParsePnpmLockV9(t *testing.T) {
|
||||||
@ -184,14 +191,101 @@ func TestParsePnpmLockV9(t *testing.T) {
|
|||||||
Type: pkg.NpmPkg,
|
Type: pkg.NpmPkg,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||||
// TODO: no relationships are under test
|
// TODO: no relationships are under test
|
||||||
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expected, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expected, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchPnpmForLicenses(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
fixture := "test-fixtures/pnpm-remote/pnpm-lock.yaml"
|
||||||
|
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||||
|
mux, url, teardown := setupNpmRegistry()
|
||||||
|
defer teardown()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixture string
|
||||||
|
config CatalogerConfig
|
||||||
|
requestHandlers []handlerPath
|
||||||
|
expectedPackages []pkg.Package
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "search remote licenses returns the expected licenses when search is set to true",
|
||||||
|
config: CatalogerConfig{SearchRemoteLicenses: true},
|
||||||
|
requestHandlers: []handlerPath{
|
||||||
|
{
|
||||||
|
// https://registry.npmjs.org/nanoid/3.3.4
|
||||||
|
path: "/nanoid/3.3.4",
|
||||||
|
handler: generateMockNpmRegistryHandler("test-fixtures/pnpm-remote/registry_response.json"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPackages: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "nanoid",
|
||||||
|
Version: "3.3.4",
|
||||||
|
Locations: locations,
|
||||||
|
PURL: "pkg:npm/nanoid@3.3.4",
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")),
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// set up the mock server
|
||||||
|
for _, handler := range tc.requestHandlers {
|
||||||
|
mux.HandleFunc(handler.path, handler.handler)
|
||||||
|
}
|
||||||
|
tc.config.NPMBaseURL = url
|
||||||
|
adapter := newGenericPnpmLockAdapter(tc.config)
|
||||||
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, tc.expectedPackages, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
func Test_corruptPnpmLock(t *testing.T) {
|
func Test_corruptPnpmLock(t *testing.T) {
|
||||||
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||||
pkgtest.NewCatalogTester().
|
pkgtest.NewCatalogTester().
|
||||||
FromFile(t, "test-fixtures/corrupt/pnpm-lock.yaml").
|
FromFile(t, "test-fixtures/corrupt/pnpm-lock.yaml").
|
||||||
WithError().
|
WithError().
|
||||||
TestParser(t, parsePnpmLock)
|
TestParser(t, adapter.parsePnpmLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateMockNpmRegistryHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
// Copy the file's content to the response writer
|
||||||
|
file, err := os.Open(responseFixture)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(w, file)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup sets up a test HTTP server for mocking requests to a particular registry.
|
||||||
|
// The returned url is injected into the Config so the client uses the test server.
|
||||||
|
// Tests should register handlers on mux to simulate the expected request/response structure
|
||||||
|
func setupNpmRegistry() (mux *http.ServeMux, serverURL string, teardown func()) {
|
||||||
|
// mux is the HTTP request multiplexer used with the test server.
|
||||||
|
mux = http.NewServeMux()
|
||||||
|
|
||||||
|
// We want to ensure that tests catch mistakes where the endpoint URL is
|
||||||
|
// specified as absolute rather than relative. It only makes a difference
|
||||||
|
// when there's a non-empty base URL path. So, use that. See issue #752.
|
||||||
|
apiHandler := http.NewServeMux()
|
||||||
|
apiHandler.Handle("/", mux)
|
||||||
|
// server is a test HTTP server used to provide mock API responses.
|
||||||
|
server := httptest.NewServer(apiHandler)
|
||||||
|
|
||||||
|
return mux, server.URL, server.Close
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,7 +239,7 @@ func TestSearchYarnForLicenses(t *testing.T) {
|
|||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
fixture := "test-fixtures/yarn-remote/yarn.lock"
|
fixture := "test-fixtures/yarn-remote/yarn.lock"
|
||||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||||
mux, url, teardown := setup()
|
mux, url, teardown := setupYarnRegistry()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -255,7 +255,7 @@ func TestSearchYarnForLicenses(t *testing.T) {
|
|||||||
{
|
{
|
||||||
// https://registry.yarnpkg.com/@babel/code-frame/7.10.4
|
// https://registry.yarnpkg.com/@babel/code-frame/7.10.4
|
||||||
path: "/@babel/code-frame/7.10.4",
|
path: "/@babel/code-frame/7.10.4",
|
||||||
handler: generateMockNPMHandler("test-fixtures/yarn-remote/registry_response.json"),
|
handler: generateMockYarnRegistryHandler("test-fixtures/yarn-remote/registry_response.json"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedPackages: []pkg.Package{
|
expectedPackages: []pkg.Package{
|
||||||
@ -445,7 +445,7 @@ func TestParseYarnFindPackageVersions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateMockNPMHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
|
func generateMockYarnRegistryHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
// Copy the file's content to the response writer
|
// Copy the file's content to the response writer
|
||||||
@ -464,10 +464,10 @@ func generateMockNPMHandler(responseFixture string) func(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup sets up a test HTTP server for mocking requests to maven central.
|
// setup sets up a test HTTP server for mocking requests to a particular registry.
|
||||||
// The returned url is injected into the Config so the client uses the test server.
|
// The returned url is injected into the Config so the client uses the test server.
|
||||||
// Tests should register handlers on mux to simulate the expected request/response structure
|
// Tests should register handlers on mux to simulate the expected request/response structure
|
||||||
func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
|
func setupYarnRegistry() (mux *http.ServeMux, serverURL string, teardown func()) {
|
||||||
// mux is the HTTP request multiplexer used with the test server.
|
// mux is the HTTP request multiplexer used with the test server.
|
||||||
mux = http.NewServeMux()
|
mux = http.NewServeMux()
|
||||||
|
|
||||||
|
|||||||
11
syft/pkg/cataloger/javascript/test-fixtures/pnpm-remote/pnpm-lock.yaml
generated
Normal file
11
syft/pkg/cataloger/javascript/test-fixtures/pnpm-remote/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
|
specifiers:
|
||||||
|
nanoid: ^3.3.4
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.4
|
||||||
|
|
||||||
|
packages:
|
||||||
|
/nanoid/3.3.4:
|
||||||
|
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"name": "nanoid",
|
||||||
|
"version": "3.3.4",
|
||||||
|
"keywords": [
|
||||||
|
"uuid",
|
||||||
|
"random",
|
||||||
|
"id",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Andrey Sitnik",
|
||||||
|
"email": "andrey@sitnik.ru"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"_id": "nanoid@3.3.4",
|
||||||
|
"maintainers": [
|
||||||
|
{
|
||||||
|
"name": "ai",
|
||||||
|
"email": "andrey@sitnik.ru"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/ai/nanoid#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ai/nanoid/issues"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"shasum": "730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab",
|
||||||
|
"tarball": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||||
|
"fileCount": 24,
|
||||||
|
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"sig": "MEQCIEXG2ta5bIaT6snvQFKV+m1KjuF4DaCpp186tcPo8vsRAiB2Eg9/6nKRi4lZOfwQC1fgq4EzrFjU8T+uqwGxWEQE8A==",
|
||||||
|
"keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unpackedSize": 21583,
|
||||||
|
"npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJicQqNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp6rw/+IRvv2zOtwi8goF3h1VctIQVWtTtYrobDIVC2W++jyxdbgZoP\r\n2CDj1YWjrr+eM6O6sI1Bj+bF+yoqQ+z8ojtfW3vtRPpjzUf/7Sgs4F2ANshp\r\ne3rqdaQLjpHPriHf6HmPJy3YNJ+7n5TPPGoTEGXAe4eCZdko3XidCMWZdHlf\r\nYQU9CVYiG6mjjORkWw1sYctt8exdcGFMh0QoQq7BEp04QWm04JwvHjUiAgvf\r\nmEQLrNrf9nwzjpnubAJD+1z6fKOc9vUE44MOj2PkPoOr6a+iBBBgwBf45cnj\r\ng8R2G5xzxsRRB0a8XZdp67y3WA8rIaYaUuBFtEWYp7QFoA/tp6AGmHEAhjLa\r\nQKTquG7ejBu21ZsQaxpGc/3WWLEm+7F78GF8CXpQdtg0Kg1eugRotSNnU0SO\r\nPLiyYV4Mw6kXnbVchS5Y+HmcDVEcSBMTve/f1KpmIhJueJ20RCg4MGYZWgI9\r\nNJ1KgH2h4djX4XuoXpcsKnX3oVfinHEMke8sLWXHsMAtOxDipEWgW9cE9hk0\r\n71Y6LAAPBu34pmaj73B0qZiIY7wXxoGWQOCl2STS/VyDG/K9w1T+WiYROu+8\r\nE9Gd+f4qXmdi7Jw6May86DDfauCwBP3gnrB5aeOktCjWsgrrdClN3Hv2pIAN\r\noJcjS3IURf6oeV4+Yw1B5GoJu1Y/6U75fOU=\r\n=IMnM\r\n-----END PGP SIGNATURE-----\r\n"
|
||||||
|
},
|
||||||
|
"main": "index.cjs",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"module": "index.js",
|
||||||
|
"browser": {
|
||||||
|
"./index.js": "./index.browser.js",
|
||||||
|
"./index.cjs": "./index.browser.cjs",
|
||||||
|
"./async/index.js": "./async/index.browser.js",
|
||||||
|
"./async/index.cjs": "./async/index.browser.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"import": "./index.js",
|
||||||
|
"browser": "./index.browser.js",
|
||||||
|
"default": "./index.js",
|
||||||
|
"require": "./index.cjs"
|
||||||
|
},
|
||||||
|
"./async": {
|
||||||
|
"import": "./async/index.js",
|
||||||
|
"browser": "./async/index.browser.js",
|
||||||
|
"default": "./async/index.js",
|
||||||
|
"require": "./async/index.cjs"
|
||||||
|
},
|
||||||
|
"./index.d.ts": "./index.d.ts",
|
||||||
|
"./non-secure": {
|
||||||
|
"import": "./non-secure/index.js",
|
||||||
|
"default": "./non-secure/index.js",
|
||||||
|
"require": "./non-secure/index.cjs"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
"./url-alphabet": {
|
||||||
|
"import": "./url-alphabet/index.js",
|
||||||
|
"default": "./url-alphabet/index.js",
|
||||||
|
"require": "./url-alphabet/index.cjs"
|
||||||
|
},
|
||||||
|
"./async/package.json": "./async/package.json",
|
||||||
|
"./non-secure/package.json": "./non-secure/package.json",
|
||||||
|
"./url-alphabet/package.json": "./url-alphabet/package.json"
|
||||||
|
},
|
||||||
|
"gitHead": "fc5bd0dbba830b1e6f3e572da8e2bc9ddc1b4b44",
|
||||||
|
"_npmUser": {
|
||||||
|
"name": "ai",
|
||||||
|
"email": "andrey@sitnik.ru"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"url": "git+https://github.com/ai/nanoid.git",
|
||||||
|
"type": "git"
|
||||||
|
},
|
||||||
|
"_npmVersion": "8.6.0",
|
||||||
|
"description": "A tiny (116 bytes), secure URL-friendly unique string ID generator",
|
||||||
|
"directories": {},
|
||||||
|
"sideEffects": false,
|
||||||
|
"_nodeVersion": "18.0.0",
|
||||||
|
"react-native": "index.js",
|
||||||
|
"_hasShrinkwrap": false,
|
||||||
|
"_npmOperationalInternal": {
|
||||||
|
"tmp": "tmp/nanoid_3.3.4_1651575437375_0.2288595018362154",
|
||||||
|
"host": "s3://npm-registry-packages"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user