feat(nx): add option validation to @nrwl/tao/generate with bonus levenshtein lookup
This commit is contained in:
parent
01c88fdb5b
commit
5a398a6870
@ -60,6 +60,7 @@
|
|||||||
"@schematics/angular": "8.3.3",
|
"@schematics/angular": "8.3.3",
|
||||||
"@testing-library/react": "9.2.0",
|
"@testing-library/react": "9.2.0",
|
||||||
"@types/express": "4.17.0",
|
"@types/express": "4.17.0",
|
||||||
|
"@types/fast-levenshtein": "^0.0.1",
|
||||||
"@types/jasmine": "~2.8.6",
|
"@types/jasmine": "~2.8.6",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/jest": "24.0.9",
|
"@types/jest": "24.0.9",
|
||||||
@ -101,6 +102,7 @@
|
|||||||
"eslint-plugin-react": "^7.14.3",
|
"eslint-plugin-react": "^7.14.3",
|
||||||
"eslint-plugin-react-hooks": "^1.7.0",
|
"eslint-plugin-react-hooks": "^1.7.0",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
|
"fast-levenshtein": "^2.0.6",
|
||||||
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
||||||
"fs-extra": "7.0.1",
|
"fs-extra": "7.0.1",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
"@angular-devkit/architect": "0.803.3",
|
"@angular-devkit/architect": "0.803.3",
|
||||||
"inquirer": "^6.3.1",
|
"inquirer": "^6.3.1",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"strip-json-comments": "2.0.1"
|
"strip-json-comments": "2.0.1",
|
||||||
|
"fast-levenshtein": "2.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,16 +19,15 @@ import {
|
|||||||
NodeWorkflow,
|
NodeWorkflow,
|
||||||
validateOptionsWithSchema
|
validateOptionsWithSchema
|
||||||
} from '@angular-devkit/schematics/tools';
|
} from '@angular-devkit/schematics/tools';
|
||||||
import { execSync } from 'child_process';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as inquirer from 'inquirer';
|
import * as inquirer from 'inquirer';
|
||||||
|
import { detectPackageManager } from '../shared/detect-package-manager';
|
||||||
import { getLogger } from '../shared/logger';
|
import { getLogger } from '../shared/logger';
|
||||||
import {
|
import {
|
||||||
coerceTypes,
|
|
||||||
convertAliases,
|
|
||||||
convertToCamelCase,
|
convertToCamelCase,
|
||||||
handleErrors,
|
handleErrors,
|
||||||
Schema
|
Schema,
|
||||||
|
validateOptions
|
||||||
} from '../shared/params';
|
} from '../shared/params';
|
||||||
import { commandName, printHelp } from '../shared/print-help';
|
import { commandName, printHelp } from '../shared/print-help';
|
||||||
import minimist = require('minimist');
|
import minimist = require('minimist');
|
||||||
@ -148,61 +147,6 @@ function createRecorder(record: any, logger: logging.Logger) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectPackageManager(
|
|
||||||
host: virtualFs.Host<any>
|
|
||||||
): Promise<string> {
|
|
||||||
const hostTree = new HostTree(host);
|
|
||||||
if (hostTree.get('workspace.json')) {
|
|
||||||
const workspaceJson: { cli: { packageManager: string } } = JSON.parse(
|
|
||||||
hostTree.read('workspace.json')!.toString()
|
|
||||||
);
|
|
||||||
if (workspaceJson.cli && workspaceJson.cli.packageManager) {
|
|
||||||
return workspaceJson.cli.packageManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await fileExists(host, 'yarn.lock')) {
|
|
||||||
return 'yarn';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await fileExists(host, 'pnpm-lock.yaml')) {
|
|
||||||
return 'pnpm';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await fileExists(host, 'package-lock.json')) {
|
|
||||||
return 'npm';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, there are no lock files, so lets check for package managers in our preferred order
|
|
||||||
if (isPackageManagerInstalled('yarn')) {
|
|
||||||
return 'yarn';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPackageManagerInstalled('pnpm')) {
|
|
||||||
return 'pnpm';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'npm';
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileExists(
|
|
||||||
host: virtualFs.Host<any>,
|
|
||||||
fileName: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
return host.exists(fileName as any).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPackageManagerInstalled(packageManager: string) {
|
|
||||||
try {
|
|
||||||
execSync(`${packageManager} --version`, {
|
|
||||||
stdio: ['ignore', 'ignore', 'ignore']
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createWorkflow(
|
async function createWorkflow(
|
||||||
fsHost: virtualFs.Host<fs.Stats>,
|
fsHost: virtualFs.Host<fs.Stats>,
|
||||||
root: string,
|
root: string,
|
||||||
@ -343,15 +287,18 @@ async function runSchematic(
|
|||||||
workflow: NodeWorkflow,
|
workflow: NodeWorkflow,
|
||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
opts: GenerateOptions,
|
opts: GenerateOptions,
|
||||||
schematic: Schematic<any, any>
|
schematic: Schematic<any, any>,
|
||||||
|
allowAdditionalArgs: boolean = false
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const flattenedSchema = await workflow.registry
|
const flattenedSchema = (await workflow.registry
|
||||||
.flatten(schematic.description.schemaJson!)
|
.flatten(schematic.description.schemaJson!)
|
||||||
.toPromise();
|
.toPromise()) as Schema;
|
||||||
|
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
printGenHelp(opts, flattenedSchema as any, logger);
|
printGenHelp(opts, flattenedSchema as any, logger);
|
||||||
} else {
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const defaults =
|
const defaults =
|
||||||
opts.schematicName === 'tao-new'
|
opts.schematicName === 'tao-new'
|
||||||
? {}
|
? {}
|
||||||
@ -362,10 +309,25 @@ async function runSchematic(
|
|||||||
);
|
);
|
||||||
const record = { loggingQueue: [] as string[], error: false };
|
const record = { loggingQueue: [] as string[], error: false };
|
||||||
workflow.reporter.subscribe(createRecorder(record, logger));
|
workflow.reporter.subscribe(createRecorder(record, logger));
|
||||||
const schematicOptions = convertAliases(
|
|
||||||
coerceTypes(opts.schematicOptions, flattenedSchema as any),
|
const schematicOptions = validateOptions(
|
||||||
flattenedSchema as any
|
opts.schematicOptions,
|
||||||
|
flattenedSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (schematicOptions['--'] && !allowAdditionalArgs) {
|
||||||
|
schematicOptions['--'].forEach(unmatched => {
|
||||||
|
const message =
|
||||||
|
`Could not match option '${unmatched.name}' to the ${opts.collectionName}:${opts.schematicName} schema.` +
|
||||||
|
(unmatched.possible.length > 0
|
||||||
|
? ` Possible matches : ${unmatched.possible.join()}`
|
||||||
|
: '');
|
||||||
|
logger.fatal(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
await workflow
|
await workflow
|
||||||
.execute({
|
.execute({
|
||||||
collection: opts.collectionName,
|
collection: opts.collectionName,
|
||||||
@ -375,13 +337,14 @@ async function runSchematic(
|
|||||||
logger
|
logger
|
||||||
})
|
})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
if (!record.error) {
|
if (!record.error) {
|
||||||
record.loggingQueue.forEach(log => logger.info(log));
|
record.loggingQueue.forEach(log => logger.info(log));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.dryRun) {
|
if (opts.dryRun) {
|
||||||
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,6 +365,7 @@ export async function generate(
|
|||||||
'generate',
|
'generate',
|
||||||
await readDefaultCollection(fsHost)
|
await readDefaultCollection(fsHost)
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflow = await createWorkflow(fsHost, root, opts);
|
const workflow = await createWorkflow(fsHost, root, opts);
|
||||||
const collection = getCollection(workflow, opts.collectionName);
|
const collection = getCollection(workflow, opts.collectionName);
|
||||||
const schematic = collection.createSchematic(opts.schematicName, true);
|
const schematic = collection.createSchematic(opts.schematicName, true);
|
||||||
@ -438,12 +402,14 @@ export async function taoNew(
|
|||||||
const workflow = await createWorkflow(fsHost, root, opts);
|
const workflow = await createWorkflow(fsHost, root, opts);
|
||||||
const collection = getCollection(workflow, opts.collectionName);
|
const collection = getCollection(workflow, opts.collectionName);
|
||||||
const schematic = collection.createSchematic('tao-new', true);
|
const schematic = collection.createSchematic('tao-new', true);
|
||||||
|
const allowAdditionalArgs = true; // tao-new is a special case, we can't yet know the schema to validate against
|
||||||
return runSchematic(
|
return runSchematic(
|
||||||
root,
|
root,
|
||||||
workflow,
|
workflow,
|
||||||
logger,
|
logger,
|
||||||
{ ...opts, schematicName: schematic.description.name },
|
{ ...opts, schematicName: schematic.description.name },
|
||||||
schematic
|
schematic,
|
||||||
|
allowAdditionalArgs
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
59
packages/tao/src/shared/detect-package-manager.ts
Normal file
59
packages/tao/src/shared/detect-package-manager.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { virtualFs } from '@angular-devkit/core';
|
||||||
|
import { HostTree } from '@angular-devkit/schematics';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
|
export async function detectPackageManager(
|
||||||
|
host: virtualFs.Host<any>
|
||||||
|
): Promise<string> {
|
||||||
|
const hostTree = new HostTree(host);
|
||||||
|
if (hostTree.get('workspace.json')) {
|
||||||
|
const workspaceJson: { cli: { packageManager: string } } = JSON.parse(
|
||||||
|
hostTree.read('workspace.json')!.toString()
|
||||||
|
);
|
||||||
|
if (workspaceJson.cli && workspaceJson.cli.packageManager) {
|
||||||
|
return workspaceJson.cli.packageManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await fileExists(host, 'yarn.lock')) {
|
||||||
|
return 'yarn';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await fileExists(host, 'pnpm-lock.yaml')) {
|
||||||
|
return 'pnpm';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await fileExists(host, 'package-lock.json')) {
|
||||||
|
return 'npm';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, there are no lock files,
|
||||||
|
// so lets check for package managers in our preferred order
|
||||||
|
if (isPackageManagerInstalled('yarn')) {
|
||||||
|
return 'yarn';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPackageManagerInstalled('pnpm')) {
|
||||||
|
return 'pnpm';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'npm';
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileExists(
|
||||||
|
host: virtualFs.Host<any>,
|
||||||
|
fileName: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
return host.exists(fileName as any).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPackageManagerInstalled(packageManager: string) {
|
||||||
|
try {
|
||||||
|
execSync(`${packageManager} --version`, {
|
||||||
|
stdio: ['ignore', 'ignore', 'ignore']
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { convertToCamelCase, convertAliases } from './params';
|
import { convertAliases, convertToCamelCase, lookupUnmatched } from './params';
|
||||||
|
|
||||||
describe('params', () => {
|
describe('params', () => {
|
||||||
describe('convertToCamelCase', () => {
|
describe('convertToCamelCase', () => {
|
||||||
@ -47,7 +47,7 @@ describe('params', () => {
|
|||||||
).toEqual({ directory: 'test' });
|
).toEqual({ directory: 'test' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter out unknown keys without alias', () => {
|
it('should filter unknown keys into the leftovers field', () => {
|
||||||
expect(
|
expect(
|
||||||
convertAliases(
|
convertAliases(
|
||||||
{ d: 'test' },
|
{ d: 'test' },
|
||||||
@ -57,7 +57,70 @@ describe('params', () => {
|
|||||||
description: ''
|
description: ''
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).toEqual({});
|
).toEqual({
|
||||||
|
'--': [
|
||||||
|
{
|
||||||
|
name: 'd',
|
||||||
|
possible: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('lookupUnmatched', () => {
|
||||||
|
it('should populate the possible array with near matches', () => {
|
||||||
|
expect(
|
||||||
|
lookupUnmatched(
|
||||||
|
{
|
||||||
|
'--': [
|
||||||
|
{
|
||||||
|
name: 'directoy',
|
||||||
|
possible: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: { directory: { type: 'string' } },
|
||||||
|
required: [],
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
'--': [
|
||||||
|
{
|
||||||
|
name: 'directoy',
|
||||||
|
possible: ['directory']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT populate the possible array with far matches', () => {
|
||||||
|
expect(
|
||||||
|
lookupUnmatched(
|
||||||
|
{
|
||||||
|
'--': [
|
||||||
|
{
|
||||||
|
name: 'directoy',
|
||||||
|
possible: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: { faraway: { type: 'string' } },
|
||||||
|
required: [],
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
'--': [
|
||||||
|
{
|
||||||
|
name: 'directoy',
|
||||||
|
possible: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { logging } from '@angular-devkit/core';
|
import { logging } from '@angular-devkit/core';
|
||||||
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
|
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
|
||||||
|
import levenshtein = require('fast-levenshtein');
|
||||||
|
|
||||||
export type Schema = {
|
export type Schema = {
|
||||||
properties: { [p: string]: any };
|
properties: { [p: string]: any };
|
||||||
@ -7,6 +8,16 @@ export type Schema = {
|
|||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Unmatched = {
|
||||||
|
name: string;
|
||||||
|
possible: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Options = {
|
||||||
|
'--'?: Unmatched[];
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
export async function handleErrors(
|
export async function handleErrors(
|
||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
isVerbose: boolean,
|
isVerbose: boolean,
|
||||||
@ -27,9 +38,7 @@ export async function handleErrors(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertToCamelCase(parsed: {
|
export function convertToCamelCase(parsed: Options): Options {
|
||||||
[k: string]: any;
|
|
||||||
}): { [k: string]: any } {
|
|
||||||
return Object.keys(parsed).reduce(
|
return Object.keys(parsed).reduce(
|
||||||
(m, c) => ({ ...m, [camelCase(c)]: parsed[c] }),
|
(m, c) => ({ ...m, [camelCase(c)]: parsed[c] }),
|
||||||
{}
|
{}
|
||||||
@ -45,7 +54,14 @@ function camelCase(input: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function coerceTypes(opts: { [k: string]: any }, schema: Schema) {
|
/**
|
||||||
|
* Coerces (and replaces) options identified as 'boolean' or 'number' in the Schema
|
||||||
|
*
|
||||||
|
* @param opts The options to check
|
||||||
|
* @param schema The schema definition with types to check against
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function coerceTypes(opts: Options, schema: Schema): Options {
|
||||||
Object.keys(opts).forEach(k => {
|
Object.keys(opts).forEach(k => {
|
||||||
if (schema.properties[k] && schema.properties[k].type == 'boolean') {
|
if (schema.properties[k] && schema.properties[k].type == 'boolean') {
|
||||||
opts[k] = opts[k] === true || opts[k] === 'true';
|
opts[k] = opts[k] === true || opts[k] === 'true';
|
||||||
@ -56,7 +72,14 @@ export function coerceTypes(opts: { [k: string]: any }, schema: Schema) {
|
|||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertAliases(opts: { [k: string]: any }, schema: Schema) {
|
/**
|
||||||
|
* Converts any options passed in with short aliases to their full names if found
|
||||||
|
* Unmatched options are added to opts['--']
|
||||||
|
*
|
||||||
|
* @param opts The options passed in by the user
|
||||||
|
* @param schema The schema definition to check against
|
||||||
|
*/
|
||||||
|
export function convertAliases(opts: Options, schema: Schema): Options {
|
||||||
return Object.keys(opts).reduce((acc, k) => {
|
return Object.keys(opts).reduce((acc, k) => {
|
||||||
if (schema.properties[k]) {
|
if (schema.properties[k]) {
|
||||||
acc[k] = opts[k];
|
acc[k] = opts[k];
|
||||||
@ -66,8 +89,55 @@ export function convertAliases(opts: { [k: string]: any }, schema: Schema) {
|
|||||||
);
|
);
|
||||||
if (found) {
|
if (found) {
|
||||||
acc[found[0]] = opts[k];
|
acc[found[0]] = opts[k];
|
||||||
|
} else {
|
||||||
|
if (!acc['--']) {
|
||||||
|
acc['--'] = [];
|
||||||
|
}
|
||||||
|
acc['--'].push({
|
||||||
|
name: k,
|
||||||
|
possible: []
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find what the user meant by unmatched commands
|
||||||
|
*
|
||||||
|
* @param opts The options passed in by the user
|
||||||
|
* @param schema The schema definition to check against
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function lookupUnmatched(opts: Options, schema: Schema): Options {
|
||||||
|
if (opts['--']) {
|
||||||
|
const props = Object.keys(schema.properties);
|
||||||
|
|
||||||
|
opts['--'].forEach(unmatched => {
|
||||||
|
unmatched.possible = props.filter(
|
||||||
|
p => levenshtein.get(p, unmatched.name) < 3
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts aliases and coerces types according to the schema
|
||||||
|
*
|
||||||
|
* @param opts The options to check
|
||||||
|
* @param schema The schema definition to validate against
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
*
|
||||||
|
* Unmatched options are added to opts['--']
|
||||||
|
* and listed along with possible schema matches
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function validateOptions(opts: Options, schema: Schema): Options {
|
||||||
|
return lookupUnmatched(
|
||||||
|
convertAliases(coerceTypes(opts, schema), schema),
|
||||||
|
schema
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1773,6 +1773,11 @@
|
|||||||
"@types/express-serve-static-core" "*"
|
"@types/express-serve-static-core" "*"
|
||||||
"@types/serve-static" "*"
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/fast-levenshtein@^0.0.1":
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.1.tgz#3a3615cf173645c8fca58d051e4e32824e4bd286"
|
||||||
|
integrity sha1-OjYVzxc2Rcj8pY0FHk4ygk5L0oY=
|
||||||
|
|
||||||
"@types/glob@^7.1.1":
|
"@types/glob@^7.1.1":
|
||||||
version "7.1.1"
|
version "7.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
||||||
@ -6796,7 +6801,7 @@ fast-json-stable-stringify@2.0.0, fast-json-stable-stringify@2.x, fast-json-stab
|
|||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||||
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
||||||
|
|
||||||
fast-levenshtein@~2.0.4:
|
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user