graphql-query-rewriter
Advanced tools
Comparing version 1.0.2 to 1.0.3
@@ -44,2 +44,3 @@ import { parse, print, parseType } from 'graphql'; | ||
var ignoreKeys = new Set(['loc']); | ||
/** @hidden */ | ||
var nodesMatch = function (node1, node2) { | ||
@@ -76,2 +77,3 @@ for (var _i = 0, _a = Object.keys(node1); _i < _a.length; _i++) { | ||
* @param callback Called on each node, and returns a new rewritten node | ||
* @hidden | ||
*/ | ||
@@ -84,3 +86,3 @@ var rewriteDoc = function (doc, callback) { | ||
var node = nextNodeAndVars.node; | ||
var nextParents = [node].concat(curParents); | ||
var nextParents = curParents.concat([node]); | ||
for (var _i = 0, _a = Object.keys(node); _i < _a.length; _i++) { | ||
@@ -120,2 +122,3 @@ var key = _a[_i]; | ||
}; | ||
/** @hidden */ | ||
var extractVariableDefinitions = function (doc) { | ||
@@ -130,2 +133,3 @@ for (var _i = 0, _a = doc.definitions; _i < _a.length; _i++) { | ||
}; | ||
/** @hidden */ | ||
var replaceVariableDefinitions = function (doc, variableDefinitions) { | ||
@@ -152,2 +156,3 @@ var definitions = doc.definitions.map(function (def) { | ||
}; | ||
/** @hidden */ | ||
var rewriteResultsAtPath = function (results, path, callback) { | ||
@@ -174,2 +179,6 @@ if (path.length === 0) | ||
/** | ||
* Create a new instance of this class for each request that needs to be processed | ||
* This class handles rewriting the query and the reponse according to the rewriters passed in | ||
*/ | ||
var RewriteHandler = /** @class */ (function () { | ||
@@ -182,2 +191,7 @@ function RewriteHandler(rewriters) { | ||
} | ||
/** | ||
* Call this on a graphQL request in middleware before passing on to the real graphql processor | ||
* @param query The graphQL query | ||
* @param variables The variables map for the graphQL query | ||
*/ | ||
RewriteHandler.prototype.rewriteRequest = function (query, variables) { | ||
@@ -208,2 +222,7 @@ var _this = this; | ||
}; | ||
/** | ||
* Call this on the response returned from graphQL before passing it back to the client | ||
* This will change the output to match what the original query requires | ||
* @param response The graphQL response object | ||
*/ | ||
RewriteHandler.prototype.rewriteResponse = function (response) { | ||
@@ -225,2 +244,6 @@ if (this.hasProcessedResponse) | ||
/** | ||
* Abstract base Rewriter class | ||
* Extend this class and overwrite its methods to create a new rewriter | ||
*/ | ||
var Rewriter = /** @class */ (function () { | ||
@@ -350,2 +373,3 @@ function Rewriter(_a) { | ||
/** @hidden */ | ||
var identifyFunc = function (val) { return val; }; | ||
@@ -352,0 +376,0 @@ |
@@ -48,2 +48,3 @@ (function (global, factory) { | ||
var ignoreKeys = new Set(['loc']); | ||
/** @hidden */ | ||
var nodesMatch = function (node1, node2) { | ||
@@ -80,2 +81,3 @@ for (var _i = 0, _a = Object.keys(node1); _i < _a.length; _i++) { | ||
* @param callback Called on each node, and returns a new rewritten node | ||
* @hidden | ||
*/ | ||
@@ -88,3 +90,3 @@ var rewriteDoc = function (doc, callback) { | ||
var node = nextNodeAndVars.node; | ||
var nextParents = [node].concat(curParents); | ||
var nextParents = curParents.concat([node]); | ||
for (var _i = 0, _a = Object.keys(node); _i < _a.length; _i++) { | ||
@@ -124,2 +126,3 @@ var key = _a[_i]; | ||
}; | ||
/** @hidden */ | ||
var extractVariableDefinitions = function (doc) { | ||
@@ -134,2 +137,3 @@ for (var _i = 0, _a = doc.definitions; _i < _a.length; _i++) { | ||
}; | ||
/** @hidden */ | ||
var replaceVariableDefinitions = function (doc, variableDefinitions) { | ||
@@ -156,2 +160,3 @@ var definitions = doc.definitions.map(function (def) { | ||
}; | ||
/** @hidden */ | ||
var rewriteResultsAtPath = function (results, path, callback) { | ||
@@ -178,2 +183,6 @@ if (path.length === 0) | ||
/** | ||
* Create a new instance of this class for each request that needs to be processed | ||
* This class handles rewriting the query and the reponse according to the rewriters passed in | ||
*/ | ||
var RewriteHandler = /** @class */ (function () { | ||
@@ -186,2 +195,7 @@ function RewriteHandler(rewriters) { | ||
} | ||
/** | ||
* Call this on a graphQL request in middleware before passing on to the real graphql processor | ||
* @param query The graphQL query | ||
* @param variables The variables map for the graphQL query | ||
*/ | ||
RewriteHandler.prototype.rewriteRequest = function (query, variables) { | ||
@@ -212,2 +226,7 @@ var _this = this; | ||
}; | ||
/** | ||
* Call this on the response returned from graphQL before passing it back to the client | ||
* This will change the output to match what the original query requires | ||
* @param response The graphQL response object | ||
*/ | ||
RewriteHandler.prototype.rewriteResponse = function (response) { | ||
@@ -229,2 +248,6 @@ if (this.hasProcessedResponse) | ||
/** | ||
* Abstract base Rewriter class | ||
* Extend this class and overwrite its methods to create a new rewriter | ||
*/ | ||
var Rewriter = /** @class */ (function () { | ||
@@ -354,2 +377,3 @@ function Rewriter(_a) { | ||
/** @hidden */ | ||
var identifyFunc = function (val) { return val; }; | ||
@@ -356,0 +380,0 @@ |
@@ -15,2 +15,3 @@ "use strict"; | ||
var ignoreKeys = new Set(['loc']); | ||
/** @hidden */ | ||
exports.nodesMatch = function (node1, node2) { | ||
@@ -47,2 +48,3 @@ for (var _i = 0, _a = Object.keys(node1); _i < _a.length; _i++) { | ||
* @param callback Called on each node, and returns a new rewritten node | ||
* @hidden | ||
*/ | ||
@@ -55,3 +57,3 @@ exports.rewriteDoc = function (doc, callback) { | ||
var node = nextNodeAndVars.node; | ||
var nextParents = [node].concat(curParents); | ||
var nextParents = curParents.concat([node]); | ||
for (var _i = 0, _a = Object.keys(node); _i < _a.length; _i++) { | ||
@@ -91,2 +93,3 @@ var key = _a[_i]; | ||
}; | ||
/** @hidden */ | ||
exports.extractVariableDefinitions = function (doc) { | ||
@@ -101,2 +104,3 @@ for (var _i = 0, _a = doc.definitions; _i < _a.length; _i++) { | ||
}; | ||
/** @hidden */ | ||
exports.replaceVariableDefinitions = function (doc, variableDefinitions) { | ||
@@ -123,2 +127,3 @@ var definitions = doc.definitions.map(function (def) { | ||
}; | ||
/** @hidden */ | ||
exports.rewriteResultsAtPath = function (results, path, callback) { | ||
@@ -125,0 +130,0 @@ if (path.length === 0) |
@@ -5,2 +5,6 @@ "use strict"; | ||
var ast_1 = require("./ast"); | ||
/** | ||
* Create a new instance of this class for each request that needs to be processed | ||
* This class handles rewriting the query and the reponse according to the rewriters passed in | ||
*/ | ||
var RewriteHandler = /** @class */ (function () { | ||
@@ -13,2 +17,7 @@ function RewriteHandler(rewriters) { | ||
} | ||
/** | ||
* Call this on a graphQL request in middleware before passing on to the real graphql processor | ||
* @param query The graphQL query | ||
* @param variables The variables map for the graphQL query | ||
*/ | ||
RewriteHandler.prototype.rewriteRequest = function (query, variables) { | ||
@@ -39,2 +48,7 @@ var _this = this; | ||
}; | ||
/** | ||
* Call this on the response returned from graphQL before passing it back to the client | ||
* This will change the output to match what the original query requires | ||
* @param response The graphQL response object | ||
*/ | ||
RewriteHandler.prototype.rewriteResponse = function (response) { | ||
@@ -41,0 +55,0 @@ if (this.hasProcessedResponse) |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Abstract base Rewriter class | ||
* Extend this class and overwrite its methods to create a new rewriter | ||
*/ | ||
var Rewriter = /** @class */ (function () { | ||
@@ -4,0 +8,0 @@ function Rewriter(_a) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** @hidden */ | ||
exports.identifyFunc = function (val) { return val; }; | ||
//# sourceMappingURL=utils.js.map |
import { ASTNode, DocumentNode, VariableDefinitionNode } from 'graphql'; | ||
/** @hidden */ | ||
export declare const nodesMatch: (node1: ASTNode, node2: ASTNode) => boolean; | ||
/** @hidden */ | ||
export interface NodeAndVarDefs { | ||
@@ -11,5 +13,8 @@ node: ASTNode; | ||
* @param callback Called on each node, and returns a new rewritten node | ||
* @hidden | ||
*/ | ||
export declare const rewriteDoc: (doc: DocumentNode, callback: (nodeAndVars: NodeAndVarDefs, parents: ReadonlyArray<ASTNode>) => NodeAndVarDefs) => DocumentNode; | ||
/** @hidden */ | ||
export declare const extractVariableDefinitions: (doc: DocumentNode) => ReadonlyArray<VariableDefinitionNode>; | ||
/** @hidden */ | ||
export declare const replaceVariableDefinitions: (doc: DocumentNode, variableDefinitions: ReadonlyArray<VariableDefinitionNode>) => DocumentNode; | ||
@@ -20,6 +25,8 @@ /** | ||
export declare const extractPath: (parents: ReadonlyArray<ASTNode>) => ReadonlyArray<string>; | ||
/** @hidden */ | ||
interface ResultObj { | ||
[key: string]: any; | ||
} | ||
/** @hidden */ | ||
export declare const rewriteResultsAtPath: (results: ResultObj, path: ReadonlyArray<string>, callback: (resultsAtPath: any) => any) => ResultObj; | ||
export {}; |
import Rewriter, { Variables } from './rewriters/Rewriter'; | ||
/** | ||
* Create a new instance of this class for each request that needs to be processed | ||
* This class handles rewriting the query and the reponse according to the rewriters passed in | ||
*/ | ||
export default class RewriteHandler { | ||
@@ -8,2 +12,7 @@ private rewriters; | ||
constructor(rewriters: Rewriter[]); | ||
/** | ||
* Call this on a graphQL request in middleware before passing on to the real graphql processor | ||
* @param query The graphQL query | ||
* @param variables The variables map for the graphQL query | ||
*/ | ||
rewriteRequest(query: string, variables?: Variables): { | ||
@@ -13,3 +22,8 @@ query: string; | ||
}; | ||
/** | ||
* Call this on the response returned from graphQL before passing it back to the client | ||
* This will change the output to match what the original query requires | ||
* @param response The graphQL response object | ||
*/ | ||
rewriteResponse(response: any): any; | ||
} |
@@ -11,2 +11,6 @@ import { ASTNode } from 'graphql'; | ||
} | ||
/** | ||
* Abstract base Rewriter class | ||
* Extend this class and overwrite its methods to create a new rewriter | ||
*/ | ||
declare abstract class Rewriter { | ||
@@ -13,0 +17,0 @@ protected fieldName: string; |
@@ -0,1 +1,2 @@ | ||
/** @hidden */ | ||
export declare const identifyFunc: <T>(val: T) => T; |
{ | ||
"name": "graphql-query-rewriter", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "", | ||
@@ -15,3 +15,3 @@ "keywords": [], | ||
"type": "git", | ||
"url": "" | ||
"url": "https://github.com/ef-eng/graphql-query-rewriter" | ||
}, | ||
@@ -18,0 +18,0 @@ "license": "MIT", |
310
README.md
@@ -1,8 +0,310 @@ | ||
# Graphql Query Rewriter | ||
# GraphQL Query Rewriter | ||
[![CircleCI](https://circleci.com/gh/chanind/graphql-query-rewriter/tree/master.svg?style=shield)](https://circleci.com/gh/chanind/graphql-query-rewriter/tree/master) | ||
[![Coverage Status](https://coveralls.io/repos/github/chanind/graphql-query-rewriter/badge.svg?branch=master)](https://coveralls.io/github/chanind/graphql-query-rewriter?branch=master) | ||
[![CircleCI](https://circleci.com/gh/ef-eng/graphql-query-rewriter/tree/master.svg?style=shield)](https://circleci.com/gh/ef-eng/graphql-query-rewriter/tree/master) | ||
[![Coverage Status](https://coveralls.io/repos/github/chanind/graphql-query-rewriter/badge.svg?branch=master)](https://coveralls.io/github/ef-eng/graphql-query-rewriter?branch=master) | ||
[![npm](https://badgen.net/npm/v/graphql-query-rewriter)](https://www.npmjs.com/package/graphql-query-rewriter) | ||
[![license](https://badgen.net/npm/license/graphql-query-rewriter)](https://opensource.org/licenses/MIT) | ||
WIP! | ||
Seamlessly turn breaking GraphQL schema changes into non-breaking changes by rewriting queries in middleware. | ||
Full API docs are available at https://ef-eng.github.io/graphql-query-rewriter | ||
## The Problem | ||
GraphQL is great at enforcing a strict schema for APIs, but its lack of versioning makes it extremely difficult to make changes to GraphQL schemas without breaking existing clients. For example, take the following query: | ||
``` | ||
query getUserById($id: String!) { | ||
userById(id: $id) { | ||
... | ||
} | ||
} | ||
``` | ||
Oh no! We should have used `ID!` as the type for `userById(id)` instead of `String!`, but it's already in production! Now if we change our schema to use `ID!` instead of `String!` then our old clients will start getting the error `Variable "$id" of type "String!" used in position expecting type "ID!"`. Currently your only options are to continue using the incorrect `String!` type forever (*eeew*), or make a new query with a new name, like `userByIdNew(id: ID!)` (*gross*)! | ||
Wouldn't it be great if you could change the schema to use `ID!`, but just silently replace `String!` in old queries with `ID!` in your middleware so the old queries will continue to work just like they had been? | ||
## Rewrite it! | ||
GraphQL Query Rewriter provides a way to rewrite deprecated queries in middleware so they'll conform to your new schema without needing to sully your API with awkwardly renamed and deprecated fields like `doTheThingNew` or `doTheThingV3`. | ||
In the above example, we can set up a rewrite rule so that `userById(id: String!)` will be seamlessly rewritten to `userById(id: ID!)` using the following middleware (assuming express-graphql): | ||
```js | ||
import { FieldArgTypeRewriter } from 'graphql-query-rewriter'; | ||
import { graphqlRewriterMiddleware } from 'express-graphql-query-rewriter'; | ||
const app = express(); | ||
// set up graphqlRewriterMiddleware right before graphQL gets processed | ||
// to rewrite deprecated queries so they seamlessly work with your new schema | ||
app.use('/graphql', graphqlRewriterMiddleware({ | ||
rewriters: [ | ||
new FieldArgTypeRewriter({ | ||
fieldName: 'userById', | ||
argName: 'id', | ||
oldType: 'String!', | ||
newType: 'ID!' | ||
}), | ||
] | ||
})); | ||
app.use('/graphql', graphqlHTTP( ... )); | ||
... | ||
``` | ||
Now, when old clients send the following query: | ||
``` | ||
query getUserById($id: String!) { | ||
userById(id: $id) { | ||
... | ||
} | ||
} | ||
``` | ||
It will be rewritten before it gets processed to: | ||
``` | ||
query getUserById($id: ID!) { | ||
userById(id: $id) { | ||
... | ||
} | ||
} | ||
``` | ||
Now your schema is clean and up to date, and deprecated clients keep working! GraphQL Schema Rewriter can rewrite much more complex queries than just changing a single input type as well. | ||
## Installation | ||
Installation requires the base package `graphql-query-rewriter` and a middleware adapter for the web framework you use. Currently works with `express-graphql` and `apollo-server`. | ||
#### For express-graphql | ||
``` | ||
npm install graphql-query-rewriter express-graphql-query-rewriter | ||
``` | ||
#### For Apollo-server | ||
Apollo server works with `express-graphql-query-rewriter` via [Apollo server middleware](https://www.apollographql.com/docs/apollo-server/migration-two-dot/#adding-additional-middleware-to-apollo-server-2). | ||
``` | ||
npm install graphql-query-rewriter express-graphql express-graphql-query-rewriter | ||
``` | ||
## Usage | ||
First you need to set up an appropriate middleware for your server. | ||
#### For express-graphql | ||
With [express-graphql](https://github.com/graphql/express-graphql), you can use the `express-graphql-query-rewriter` middleware. This middleware goes directy before your `graphql` handler in express: | ||
```js | ||
import { graphqlRewriterMiddleware } from 'express-graphql-query-rewriter'; | ||
... | ||
// graphqlRewriterMiddleware should go directly before the graphQL handler | ||
app.use('/graphql', graphqlRewriterMiddleware({ | ||
rewriters: [ /* place rewriters here */] | ||
}) | ||
app.use('/graphql', graphqlHTTP( ... )); | ||
... | ||
``` | ||
#### For apollo-server | ||
Apollo-server can also use the `express-graphql-query-rewriter` middleware like below: | ||
```js | ||
const { ApolloServer, gql } = require("apollo-server-express"); | ||
const express = require("express"); | ||
const { graphqlRewriterMiddleware } = require("express-graphql-query-rewriter"); | ||
// configure ApolloServer as usual | ||
const server = new ApolloServer({ typeDefs, resolvers }); | ||
const app = express(); | ||
const path = "/graphql"; | ||
app.use( | ||
path, | ||
graphqlRewriterMiddleware({ | ||
rewriters: [ /* place rewriters here */] | ||
}) | ||
); | ||
server.applyMiddleware({ app, path, bodyParserConfig: false }); | ||
... | ||
``` | ||
Note that you need to specify `bodyParserConfig: false` in `applyMiddleware()` since `express-graphql-query-rewriter` already parses the graphQL body in order to rewrite it. | ||
### FieldArgTypeRewriter | ||
`FieldArgTypeRewriter` rewrites the type of an argument to a graphQL query or mutation. For example, to change from `Int` to `Int!` in a mutation called `doTheThing(arg1: Int)` you could add the following: | ||
```js | ||
import { FieldArgTypeRewriter } from 'graphql-query-rewriter'; | ||
// add this to the rewriters array in graphqlRewriterMiddleware(...) | ||
const rewriter = new FieldArgTypeRewriter({ | ||
fieldName: 'doTheThing', | ||
argName: 'arg1', | ||
oldType: 'Int', | ||
newType: 'Int!' | ||
}) | ||
``` | ||
Sometimes, you'll need to do some preprocessing on the variables submitted to the rewritten argument to make them into the type needed by the new schema. You can do this by passing in a `coerceVariable` function which returns a new value of the variable. For example, the following changes the value of `arg1` from `Int!` to `String!`, and also changes the value of `arg1` to a string as well: | ||
```js | ||
import { FieldArgTypeRewriter } from 'graphql-query-rewriter'; | ||
// add this to the rewriters array in graphqlRewriterMiddleware(...) | ||
const rewriter = new FieldArgTypeRewriter({ | ||
fieldName: 'doTheThing', | ||
argName: 'arg1', | ||
oldType: 'Int!', | ||
newType: 'String!' | ||
coerceVariable: (val) => `${val}`, | ||
}) | ||
``` | ||
### FieldArgNameRewriter | ||
`FieldArgNameRewriter` rewrites the name of an argument to a graphQL query or mutation. For example, to change an argument name from `userID` to `userId` in a mutation called `createUser(userID: ID!)` you could add the following: | ||
```js | ||
import { FieldArgNameRewriter } from 'graphql-query-rewriter'; | ||
// add this to the rewriters array in graphqlRewriterMiddleware(...) | ||
const rewriter = new FieldArgNameRewriter({ | ||
fieldName: 'createUser', | ||
oldArgName: 'userID', | ||
newArgName: 'userId' | ||
}) | ||
``` | ||
### FieldArgsToInputTypeRewriter | ||
`FieldArgsToInputTypeRewriter` can be used to move mutation parameters into a single input object, by default named `input`. It's a best-practice to use a single input type for mutations in GraphQL, and it's required by the [Relay GraphQL Spec](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#mutations). For example, to migrate the mutation `createUser(username: String!, password: String!)` to a mutation with a proper input type like: | ||
``` | ||
mutation createUser(input: CreateUserInput!) { ... } | ||
type CreateUserInput { | ||
username: String! | ||
password: String! | ||
} | ||
``` | ||
we can make this change with the following rewriter: | ||
```js | ||
import { FieldArgsToInputTypeRewriter } from 'graphql-query-rewriter'; | ||
// add this to the rewriters array in graphqlRewriterMiddleware(...) | ||
const rewriter = new FieldArgsToInputTypeRewriter({ | ||
fieldName: 'createUser', | ||
argNames: ['username', 'password'], | ||
inputArgName: 'input' // inputArgName can be left out to use 'input' by default | ||
}) | ||
``` | ||
For example, This would rewrite the following mutation: | ||
``` | ||
mutation createUser($username: String!, $password: String!) { | ||
createUser(username: $username, password: $password) { | ||
... | ||
} | ||
} | ||
``` | ||
and turn it into: | ||
``` | ||
mutation createUser($username: String!, $password: String!) { | ||
createUser(input: { username: $username, password: $password }) { | ||
... | ||
} | ||
} | ||
``` | ||
### NestFieldOutputsRewriter | ||
`NestFieldOutputsRewriter` can be used to move mutation outputs into a nested payload object. It's a best-practice for each mutation in GraphQL to have its own output type, and it's required by the [Relay GraphQL Spec](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#mutations). For example, to migrate the mutation `createUser(input: CreateUserInput!): User!` to a mutation with a proper output payload type like: | ||
``` | ||
mutation createUser(input: CreateUserInput!) CreateUserPayload | ||
type User { | ||
id | ||
username | ||
} | ||
type CreateUserPayload { | ||
user: User! | ||
} | ||
``` | ||
we can make this change with the following rewriter: | ||
```js | ||
import { NestFieldOutputsRewriter } from 'graphql-query-rewriter'; | ||
// add this to the rewriters array in graphqlRewriterMiddleware(...) | ||
const rewriter = new NestFieldOutputsRewriter({ | ||
fieldName: 'createUser', | ||
newOutputName: 'user', | ||
outputsToNest: ['id', 'username'] | ||
}) | ||
``` | ||
For example, This would rewrite the following mutation: | ||
``` | ||
mutation createUser(input: CreateUserInput!) { | ||
createUser(input: $input) { | ||
id | ||
username | ||
} | ||
} | ||
``` | ||
and turn it into: | ||
``` | ||
mutation createUser(input: CreateUserInput!) { | ||
createUser(input: $input) { | ||
user { | ||
id | ||
username | ||
} | ||
} | ||
} | ||
``` | ||
## Current Limitations | ||
Currently GraphQL Query Rewriter can only work with a single operation per query, and cannot properly handle aliased fields. These limitations should hopefully be fixed soon. Contributions are welcome! | ||
## License | ||
GraphQL Query Rewriter is released under a [MIT License](https://opensource.org/licenses/MIT). | ||
## Contributing | ||
Contributions are welcome! These steps will guide you through contributing to this project: | ||
- Fork the repo | ||
- Clone it and install dependencies | ||
`git clone https://github.com/ef-eng/graphql-query-rewriter` | ||
`yarn install` | ||
Make and commit your changes. Make sure the commands yarn run build and yarn run test:prod are working. | ||
Finally send a [GitHub Pull Request](https://github.com/ef-eng/graphql-query-rewriter/compare?expand=1) with a clear list of what you've done. Make sure all of your commits are atomic (one feature per commit). Please add tests for any features that you add or change. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
174070
1746
311
0