@storybook/addon-svelte-csf
Advanced tools
Comparing version 4.2.0--canary.186.460fc9f.0 to 4.2.0--canary.186.977d08b.0
@@ -13,14 +13,9 @@ /** | ||
import { preprocess } from 'svelte/compiler'; | ||
import { transformStoriesCode } from './post-transform'; | ||
import { codemodLegacyNodes } from '#compiler/pre-transform/index'; | ||
import { transformStoriesCode } from '#compiler/post-transform/index'; | ||
import { getSvelteAST } from '#parser/ast'; | ||
import { extractCompiledASTNodes } from '#parser/extract/compiled/nodes'; | ||
import { extractSvelteASTNodes } from '#parser/extract/svelte/nodes'; | ||
import { extractLegacyNodes } from '#compiler/pre-transform/extractor'; | ||
import { codemodLegacyNodes } from '#compiler/pre-transform/index'; | ||
export async function preTransformPlugin() { | ||
const [{ createFilter }, { loadSvelteConfig }] = await Promise.all([ | ||
import('vite'), | ||
import('@sveltejs/vite-plugin-svelte'), | ||
]); | ||
const svelteConfig = await loadSvelteConfig(); | ||
const [{ createFilter }] = await Promise.all([import('vite')]); | ||
const include = /\.stories\.svelte$/; | ||
@@ -34,21 +29,10 @@ const filter = createFilter(include); | ||
return undefined; | ||
// @ts-expect-error FIXME: `this.originalCode` exists at runtime in the development mode only. | ||
// Need to research if its documented somewhere | ||
let legacyCode = this.originalCode ?? code; | ||
if (svelteConfig?.preprocess) { | ||
const processed = await preprocess(legacyCode, svelteConfig.preprocess, { | ||
filename: id, | ||
}); | ||
legacyCode = processed.code; | ||
} | ||
let magicLegacyCode = new MagicString(legacyCode); | ||
const svelteAST = getSvelteAST({ code: legacyCode, filename: id }); | ||
const legacyNodes = await extractLegacyNodes(svelteAST); | ||
await codemodLegacyNodes({ | ||
code: magicLegacyCode, | ||
legacyNodes, | ||
}); | ||
const { print } = await import('svelte-ast-print'); | ||
const svelteAST = getSvelteAST({ code, filename: id }); | ||
const transformedSvelteAST = await codemodLegacyNodes(svelteAST); | ||
let magicCode = new MagicString(code); | ||
magicCode.overwrite(0, code.length - 1, print(transformedSvelteAST)); | ||
return { | ||
code: magicLegacyCode.toString(), | ||
map: magicLegacyCode.generateMap({ hires: true, source: id }), | ||
code: magicCode.toString(), | ||
map: magicCode.generateMap({ hires: true, source: id }), | ||
}; | ||
@@ -55,0 +39,0 @@ }, |
import { logger } from '@storybook/client-logger'; | ||
import { createASTObjectExpression, createASTProperty, findASTPropertyIndex, findPropertyDescriptionIndex, findPropertyDocsIndex, findPropertyParametersIndex, getDescriptionPropertyValue, getDocsPropertyValue, getParametersPropertyValue, } from '../shared/description'; | ||
import { findASTPropertyIndex, findPropertyDescriptionIndex, findPropertyDocsIndex, findPropertyParametersIndex, getDescriptionPropertyValue, getDocsPropertyValue, getParametersPropertyValue, } from '#compiler/post-transform/shared/description'; | ||
import { createASTProperty, createASTObjectExpression } from '#parser/ast'; | ||
import { getDefineMetaFirstArgumentObjectExpression } from '#parser/extract/svelte/define-meta'; | ||
@@ -4,0 +5,0 @@ /** |
import type { ObjectExpression, Property } from 'estree'; | ||
import type { Component } from 'svelte/compiler'; | ||
/** | ||
* Create ESTree compliant AST node for {@link Property} | ||
*/ | ||
export declare function createASTProperty(name: string, value: Property['value']): Property; | ||
/** | ||
* Create ESTree compliant AST node for {@link ObjectExpression} with optional array of properties. | ||
* By default it will create an enpty object. | ||
*/ | ||
export declare function createASTObjectExpression(properties?: ObjectExpression['properties']): ObjectExpression; | ||
interface FindPropertyOptions { | ||
@@ -13,0 +4,0 @@ name: string; |
import { logger } from '@storybook/client-logger'; | ||
import dedent from 'dedent'; | ||
import { createASTObjectExpression } from '#parser/ast'; | ||
/** | ||
* Create ESTree compliant AST node for {@link Property} | ||
*/ | ||
export function createASTProperty(name, value) { | ||
return { | ||
type: 'Property', | ||
kind: 'init', | ||
computed: false, | ||
method: false, | ||
shorthand: false, | ||
key: { | ||
type: 'Identifier', | ||
name, | ||
}, | ||
value, | ||
}; | ||
} | ||
/** | ||
* Create ESTree compliant AST node for {@link ObjectExpression} with optional array of properties. | ||
* By default it will create an enpty object. | ||
*/ | ||
export function createASTObjectExpression(properties = []) { | ||
return { | ||
type: 'ObjectExpression', | ||
properties, | ||
}; | ||
} | ||
/** | ||
* In order to be able to access AST node - {@link Property} - from the {@link ObjectExpression}, | ||
@@ -32,0 +6,0 @@ * we need to have its index based on the property name, so the key must be an identifier. |
import { logger } from '@storybook/client-logger'; | ||
import dedent from 'dedent'; | ||
import { createASTObjectExpression, createASTProperty, findASTPropertyIndex, findPropertyDescriptionIndex, findPropertyDocsIndex, findPropertyParametersIndex, getParametersPropertyValue, getDocsPropertyValue, getDescriptionPropertyValue, } from '#compiler/post-transform/shared/description'; | ||
import { findASTPropertyIndex, findPropertyDescriptionIndex, findPropertyDocsIndex, findPropertyParametersIndex, getParametersPropertyValue, getDocsPropertyValue, getDescriptionPropertyValue, } from '#compiler/post-transform/shared/description'; | ||
import { createASTObjectExpression, createASTProperty } from '#parser/ast'; | ||
import { getStoryPropsObjectExpression } from '#parser/extract/compiled/story'; | ||
@@ -5,0 +6,0 @@ /** |
@@ -1,4 +0,5 @@ | ||
import { createASTObjectExpression, createASTProperty, findPropertyParametersIndex, getParametersPropertyValue, } from '#compiler/post-transform/shared/description'; | ||
import { findPropertyParametersIndex, getParametersPropertyValue, } from '#compiler/post-transform/shared/description'; | ||
import { getStoryPropsObjectExpression } from '#parser/extract/compiled/story'; | ||
import { getStoryChildrenRawCode } from '#parser/analyse/story/children'; | ||
import { createASTObjectExpression, createASTProperty } from '#parser/ast'; | ||
/** | ||
@@ -5,0 +6,0 @@ * Insert addon's internal object `__svelteCsf` |
@@ -51,6 +51,6 @@ import { getStringValueFromAttribute } from '#parser/analyse/story/attributes'; | ||
let parameters = []; | ||
if (letDirectiveArgs) { | ||
if (letDirectiveArgs || letDirectiveContext) { | ||
parameters.push({ | ||
type: 'Identifier', | ||
name: 'args', | ||
name: letDirectiveArgs ? 'args' : '_args', | ||
}); | ||
@@ -72,3 +72,2 @@ } | ||
body: fragment, | ||
// NOTE: Those are useless, but I want TypeScript to 🤫 | ||
start, | ||
@@ -75,0 +74,0 @@ parent, |
@@ -1,8 +0,2 @@ | ||
import type { extractLegacyNodes } from '#compiler/pre-transform/extractor'; | ||
import type MagicString from 'magic-string'; | ||
interface Params { | ||
code: MagicString; | ||
legacyNodes: Awaited<ReturnType<typeof extractLegacyNodes>>; | ||
} | ||
export declare function codemodLegacyNodes(params: Params): Promise<void>; | ||
export {}; | ||
import type { Root } from 'svelte/compiler'; | ||
export declare function codemodLegacyNodes(ast: Root): Promise<Root>; |
@@ -1,12 +0,148 @@ | ||
import { transformTemplateToSnippet } from './codemods/template-to-snippet'; | ||
export async function codemodLegacyNodes(params) { | ||
const { print } = await import('svelte-ast-print'); | ||
const { legacyNodes, code } = params; | ||
const { componentsTemplate } = legacyNodes; | ||
// TODO: use toReversed() when Storybook ends support for v18 | ||
for (const template of [...componentsTemplate].reverse()) { | ||
const { start, end } = template; | ||
const snippetBlock = transformTemplateToSnippet(template); | ||
code.update(start, end, print(snippetBlock)); | ||
import pkg from '@storybook/addon-svelte-csf/package.json' with { type: 'json' }; | ||
import { transformComponentMetaToDefineMeta } from '#compiler/pre-transform/codemods/component-meta-to-define-meta'; | ||
import { transformExportMetaToDefineMeta } from '#compiler/pre-transform/codemods/export-const-to-define-meta'; | ||
import { transformImportDeclaration } from '#compiler/pre-transform/codemods/import-declaration'; | ||
import { transformLegacyStory } from '#compiler/pre-transform/codemods/legacy-story'; | ||
import { transformTemplateToSnippet } from '#compiler/pre-transform/codemods/template-to-snippet'; | ||
export async function codemodLegacyNodes(ast) { | ||
const { walk } = await import('zimmerframe'); | ||
const state = { | ||
componentIdentifierName: {}, | ||
}; | ||
let transformedAst = walk(ast, state, { | ||
_(_node, context) { | ||
const { next, state } = context; | ||
next(state); | ||
}, | ||
Root(node, context) { | ||
const { fragment, module, ...rest } = node; | ||
const { state, visit } = context; | ||
if (!module) { | ||
// TODO: Add description & document - invalid syntax in both modern and legacy | ||
throw new Error(); | ||
} | ||
const transformedModule = visit(module, state); | ||
const transformedFragment = visit(fragment, state); | ||
return { | ||
...rest, | ||
fragment: transformedFragment, | ||
module: transformedModule, | ||
}; | ||
}, | ||
Script(node, context) { | ||
const { next, state } = context; | ||
if (node.context === 'module') { | ||
next(state); | ||
} | ||
}, | ||
Program(_node, context) { | ||
const { next, state } = context; | ||
next(state); | ||
}, | ||
ImportDeclaration(node, context) { | ||
const { source, specifiers } = node; | ||
const { state } = context; | ||
const { value } = source; | ||
if (value === pkg.name) { | ||
state.componentIdentifierName = getComponentsIdentifiersNames(specifiers); | ||
return transformImportDeclaration(node); | ||
} | ||
}, | ||
ExportNamedDeclaration(node, _context) { | ||
const { declaration } = node; | ||
if (declaration && | ||
declaration.type === 'VariableDeclaration' && | ||
declaration.declarations[0].type === 'VariableDeclarator' && | ||
declaration.declarations[0].id.type === 'Identifier' && | ||
declaration.declarations[0].id.name === 'meta') { | ||
return transformExportMetaToDefineMeta(node); | ||
} | ||
}, | ||
Fragment(_node, context) { | ||
const { next, state } = context; | ||
next(state); | ||
}, | ||
Comment(node, context) { | ||
const { state } = context; | ||
state.componentMetaHtmlComment = node; | ||
}, | ||
Component(node, context) { | ||
const { name } = node; | ||
const { state } = context; | ||
const { componentIdentifierName, componentMetaHtmlComment } = state; | ||
if (name === componentIdentifierName?.Meta) { | ||
state.defineMetaFromMeta = transformComponentMetaToDefineMeta({ | ||
component: node, | ||
comment: componentMetaHtmlComment, | ||
}); | ||
} | ||
if (name === componentIdentifierName?.Story) { | ||
state.componentMetaHtmlComment = undefined; | ||
return transformLegacyStory(node); | ||
} | ||
if (name === componentIdentifierName?.Template) { | ||
state.componentMetaHtmlComment = undefined; | ||
return transformTemplateToSnippet(node); | ||
} | ||
}, | ||
}); | ||
const { componentIdentifierName, defineMetaFromMeta } = state; | ||
// Clean-up | ||
// TODO: To optimize it (stop walking on AST again)... | ||
// it might be possible to use `visit(path(-1))` for some cases in the previous AST walk, | ||
// but I haven't managed to get it to work properly | ||
transformedAst = walk(transformedAst, null, { | ||
Script(node, context) { | ||
const { context: scriptContext } = node; | ||
if (scriptContext === 'module') { | ||
const { next, state } = context; | ||
next(state); | ||
} | ||
}, | ||
Program(node, _context) { | ||
let { body, ...rest } = node; | ||
if (defineMetaFromMeta) { | ||
body.push(defineMetaFromMeta); | ||
return { ...rest, body }; | ||
} | ||
}, | ||
Fragment(node, context) { | ||
const { stop } = context; | ||
if (defineMetaFromMeta) { | ||
let { nodes, ...rest } = node; | ||
const componentMetaIndex = nodes.findIndex((node) => node.type === 'Component' && node.name === componentIdentifierName.Meta); | ||
if (componentMetaIndex !== -1) { | ||
// NOTE: Removes Meta component AST | ||
nodes.splice(componentMetaIndex, 1); | ||
if (nodes[componentMetaIndex - 1]?.type === 'Comment') { | ||
// NOTE: Removes leading Ccomment | ||
nodes.splice(componentMetaIndex - 1, 1); | ||
} | ||
if (nodes[componentMetaIndex - 1]?.type === 'Text' && | ||
nodes[componentMetaIndex - 2]?.type === 'Comment') { | ||
// NOTE: Remove leading Comment - sometimes there might a new line and indent - aka `\n ` | ||
nodes.splice(componentMetaIndex - 2, 2); | ||
} | ||
} | ||
return { ...rest, nodes }; | ||
} | ||
stop(); | ||
}, | ||
}); | ||
return transformedAst; | ||
} | ||
function getComponentsIdentifiersNames(specifiers) { | ||
let results = {}; | ||
for (const specifier of specifiers) { | ||
if (specifier.imported.name === 'Meta') { | ||
results.Meta = specifier.local.name; | ||
} | ||
if (specifier.imported.name === 'Story') { | ||
results.Story = specifier.local.name; | ||
} | ||
if (specifier.imported.name === 'Template') { | ||
results.Template = specifier.local.name; | ||
} | ||
} | ||
return results; | ||
} |
import type { Args as BaseArgs } from '@storybook/types'; | ||
import type { EmptyObject } from 'type-fest'; | ||
import type { Meta as MetaType, StoryCmp, StoryContext as BaseStoryContext, StoryAnnotations, Cmp } from '#types'; | ||
import TemplateComponent from './runtime/Template.svelte'; | ||
import LegacyStoryComponent from './runtime/LegacyStory.svelte'; | ||
import LegacyTemplateComponent from './runtime/LegacyTemplate.svelte'; | ||
export { setTemplate } from './runtime/contexts/template.svelte'; | ||
@@ -17,2 +18,3 @@ export declare function defineMeta<const TOverrideArgs extends BaseArgs = EmptyObject, const TCmp extends Cmp = Cmp>(meta: MetaType<TCmp>): { | ||
export declare const Meta: () => never; | ||
export { | ||
/** | ||
@@ -22,4 +24,3 @@ * @deprecated Use `Story` component returned from `defineMeta` instead | ||
*/ | ||
export declare const Story: () => never; | ||
export { | ||
LegacyStoryComponent as Story, | ||
/** | ||
@@ -29,2 +30,2 @@ * @deprecated Use snippets instead | ||
*/ | ||
TemplateComponent as Template, }; | ||
LegacyTemplateComponent as Template, }; |
import dedent from 'dedent'; | ||
import StoryComponent from './runtime/Story.svelte'; | ||
import TemplateComponent from './runtime/Template.svelte'; | ||
import LegacyStoryComponent from './runtime/LegacyStory.svelte'; | ||
import LegacyTemplateComponent from './runtime/LegacyTemplate.svelte'; | ||
export { setTemplate } from './runtime/contexts/template.svelte'; | ||
@@ -21,2 +22,3 @@ export function defineMeta(meta) { | ||
}; | ||
export { | ||
/** | ||
@@ -26,9 +28,3 @@ * @deprecated Use `Story` component returned from `defineMeta` instead | ||
*/ | ||
export const Story = () => { | ||
throw new Error(dedent ` | ||
The Story component can not be imported anymore, but must be desctructured from the defineMeta() call. | ||
For more details, see: https://github.com/storybookjs/addon-svelte-csf/blob/main/MIGRATION.md#export-meta-removed-in-favor-of-definemeta | ||
`); | ||
}; | ||
export { | ||
LegacyStoryComponent as Story, | ||
/** | ||
@@ -38,2 +34,2 @@ * @deprecated Use snippets instead | ||
*/ | ||
TemplateComponent as Template, }; | ||
LegacyTemplateComponent as Template, }; |
@@ -1,2 +0,3 @@ | ||
import { type Root } from 'svelte/compiler'; | ||
import type { ArrayExpression, ObjectExpression, Property } from 'estree'; | ||
import { type Attribute, type ExpressionTag, type Root } from 'svelte/compiler'; | ||
interface GetSvelteASTOptions { | ||
@@ -7,2 +8,26 @@ code: string; | ||
export declare function getSvelteAST(options: GetSvelteASTOptions): Root; | ||
/** | ||
* Create Svelte compliant AST node for {@link Attibute} with optional value. | ||
* By default it will create an shorthand attribute. | ||
*/ | ||
export declare function createASTAttribute(name: string, value?: Attribute['value']): Attribute; | ||
/** | ||
* Create Svelte compliant AST node for {@link Attibute} with optional value. | ||
* By default it will create an shorthand attribute. | ||
*/ | ||
export declare function createASTExpressionTag(expression: ExpressionTag['expression']): ExpressionTag; | ||
/** | ||
* Create ESTree compliant AST node for {@link Property} | ||
*/ | ||
export declare function createASTProperty(name: string, value: Property['value']): Property; | ||
/** | ||
* Create ESTree compliant AST node for {@link ArrayExpression} with optional array of elements. | ||
* By default it will create an empty array. | ||
*/ | ||
export declare function createASTArrayExpression(elements?: ArrayExpression['elements']): ArrayExpression; | ||
/** | ||
* Create ESTree compliant AST node for {@link ObjectExpression} with optional array of properties. | ||
* By default it will create an empty object. | ||
*/ | ||
export declare function createASTObjectExpression(properties?: ObjectExpression['properties']): ObjectExpression; | ||
export {}; |
@@ -10,1 +10,75 @@ import { compile } from 'svelte/compiler'; | ||
} | ||
/** | ||
* Create Svelte compliant AST node for {@link Attibute} with optional value. | ||
* By default it will create an shorthand attribute. | ||
*/ | ||
export function createASTAttribute(name, value = true) { | ||
return { | ||
type: 'Attribute', | ||
name, | ||
value, | ||
// NOTE: Those are useless at this point, but I needed TypeScript to 🤫 | ||
parent: null, | ||
metadata: { | ||
delegated: null, | ||
dynamic: false, | ||
}, | ||
start: 0, | ||
end: 0, | ||
}; | ||
} | ||
/** | ||
* Create Svelte compliant AST node for {@link Attibute} with optional value. | ||
* By default it will create an shorthand attribute. | ||
*/ | ||
export function createASTExpressionTag(expression) { | ||
return { | ||
type: 'ExpressionTag', | ||
expression, | ||
// NOTE: Those are useless at this point, but I needed TypeScript to 🤫 | ||
metadata: { | ||
contains_call_expression: false, | ||
dynamic: false, | ||
}, | ||
parent: null, | ||
start: 0, | ||
end: 0, | ||
}; | ||
} | ||
/** | ||
* Create ESTree compliant AST node for {@link Property} | ||
*/ | ||
export function createASTProperty(name, value) { | ||
return { | ||
type: 'Property', | ||
kind: 'init', | ||
computed: false, | ||
method: false, | ||
shorthand: false, | ||
key: { | ||
type: 'Identifier', | ||
name, | ||
}, | ||
value, | ||
}; | ||
} | ||
/** | ||
* Create ESTree compliant AST node for {@link ArrayExpression} with optional array of elements. | ||
* By default it will create an empty array. | ||
*/ | ||
export function createASTArrayExpression(elements = []) { | ||
return { | ||
type: 'ArrayExpression', | ||
elements, | ||
}; | ||
} | ||
/** | ||
* Create ESTree compliant AST node for {@link ObjectExpression} with optional array of properties. | ||
* By default it will create an empty object. | ||
*/ | ||
export function createASTObjectExpression(properties = []) { | ||
return { | ||
type: 'ObjectExpression', | ||
properties, | ||
}; | ||
} |
import type { StorybookConfig } from '@storybook/svelte-vite'; | ||
import type { Options } from '@storybook/types'; | ||
export interface StorybookAddonSvelteCsFOptions extends Options { | ||
/** | ||
* **Do you want to enable support for legacy code?** | ||
* | ||
* It will add overhead to the runtime, because it will trigger a pre-transform plugin, | ||
* which will run codemods to transform legacy syntax into modern. | ||
* | ||
* @default false | ||
*/ | ||
supportLegacy?: boolean; | ||
} | ||
export declare const viteFinal: StorybookConfig['viteFinal']; | ||
export declare const experimental_indexers: StorybookConfig['experimental_indexers']; |
import { postTransformPlugin, preTransformPlugin } from '#compiler/plugins'; | ||
import { indexer } from '#indexer/index'; | ||
export const viteFinal = async (config, options) => { | ||
let { plugins = [], ...restConfig } = config; | ||
const { supportLegacy = false } = options; | ||
if (supportLegacy) { | ||
plugins.unshift(preTransformPlugin()); | ||
} | ||
plugins.push(postTransformPlugin()); | ||
return { | ||
...config, | ||
plugins: [ | ||
/** TODO: Is this the place for `options.supportLegacy`? */ | ||
preTransformPlugin(), | ||
...(config.plugins ?? []), | ||
postTransformPlugin(), | ||
], | ||
...restConfig, | ||
plugins, | ||
}; | ||
@@ -13,0 +14,0 @@ }; |
@@ -27,5 +27,5 @@ import type { Args, ComponentAnnotations as BaseComponentAnnotations, StoryAnnotations as BaseStoryAnnotations, StoryContext as BaseStoryContext, WebRenderer } from '@storybook/types'; | ||
type InferArgs<TCmpOrArgs extends CmpOrArgs> = MapSnippetsToAcceptPrimitives<TCmpOrArgs extends Cmp ? ComponentProps<TCmpOrArgs> : TCmpOrArgs>; | ||
export type StoryContext<TCmp extends Cmp, TMeta extends Meta<TCmp>> = BaseStoryContext<SvelteRenderer<TCmp>, Simplify<TCmp extends Cmp ? ComponentProps<TCmp> : TCmp>>; | ||
export type StoryCmp<TOverrideArgs extends Args, TCmp extends Cmp, TMeta extends Meta<TCmp>> = typeof Story<TOverrideArgs, TCmp, TMeta>; | ||
export type StoryContext<TCmp extends Cmp = Cmp, TMeta extends Meta<TCmp> = Meta<TCmp>> = BaseStoryContext<SvelteRenderer<TCmp>, Simplify<TCmp extends Cmp ? ComponentProps<TCmp> : TCmp>>; | ||
export type StoryCmp<TOverrideArgs extends Args = Args, TCmp extends Cmp = Cmp, TMeta extends Meta<TCmp> = Meta<TCmp>> = typeof Story<TOverrideArgs, TCmp, TMeta>; | ||
export type StoryAnnotations<TCmp extends Cmp, TMeta extends Meta<TCmp>> = BaseStoryAnnotations<SvelteRenderer<TCmp>, InferArgs<TCmp>, Simplify<SetOptional<InferArgs<TCmp>, keyof InferArgs<TCmp>>>>; | ||
export {}; |
{ | ||
"name": "@storybook/addon-svelte-csf", | ||
"version": "4.2.0--canary.186.460fc9f.0", | ||
"version": "4.2.0--canary.186.977d08b.0", | ||
"description": "Allows to write stories in Svelte syntax", | ||
@@ -18,2 +18,5 @@ "keywords": [ | ||
"imports": { | ||
"#tests/*": { | ||
"development": "./tests/*.ts" | ||
}, | ||
"#*": { | ||
@@ -102,3 +105,3 @@ "development": "./src/*.ts", | ||
"svelte": "5.0.0-next.155", | ||
"svelte-ast-print": "^0.2.0", | ||
"svelte-ast-print": "^0.2.1", | ||
"svelte-check": "^3.8.4", | ||
@@ -105,0 +108,0 @@ "tslib": "^2.6.3", |
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
201099
125
4680
10