adding new cli helper. this cli helper is unified
This commit is contained in:
parent
f2b0e1ab7b
commit
9186a0655f
2
bin/nope
Normal file
2
bin/nope
Normal file
@ -0,0 +1,2 @@
|
||||
#! /usr/bin/env node
|
||||
require("../dist/lib/cli/nope");
|
@ -2,64 +2,70 @@
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2020-11-06 08:52:11
|
||||
* @modify date 2020-11-06 08:52:12
|
||||
* @modify date 2021-01-18 17:19:40
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
import { ClassDeclaration, MethodDeclaration, PropertyDeclaration } from "ts-morph";
|
||||
import {
|
||||
ClassDeclaration,
|
||||
MethodDeclaration,
|
||||
PropertyDeclaration
|
||||
} from "ts-morph";
|
||||
import { IDecoratorFilter } from "../types/IDecoratorFilter";
|
||||
|
||||
export function defaultDecoratorFilter(decorators: {
|
||||
class: string[],
|
||||
methods: string[],
|
||||
properties: string[]
|
||||
}, caseSensitive = false): IDecoratorFilter{
|
||||
export function defaultDecoratorFilter(
|
||||
decorators: {
|
||||
class: string[];
|
||||
methods: string[];
|
||||
properties: string[];
|
||||
},
|
||||
caseSensitive = false
|
||||
): IDecoratorFilter {
|
||||
// Update the Decorators:
|
||||
if (!caseSensitive) {
|
||||
decorators.class = decorators.class.map((item) => item.toLocaleLowerCase());
|
||||
decorators.methods = decorators.methods.map((item) =>
|
||||
item.toLocaleLowerCase()
|
||||
);
|
||||
decorators.properties = decorators.properties.map((item) =>
|
||||
item.toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Update the Decorators:
|
||||
if (!caseSensitive){
|
||||
decorators.class = decorators.class.map(item => item.toLocaleLowerCase());
|
||||
decorators.methods = decorators.methods.map(item => item.toLocaleLowerCase());
|
||||
decorators.properties = decorators.properties.map(item => item.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
return (declaration, decorator, mapping) => {
|
||||
|
||||
// Get the Name of the Decorator
|
||||
let nameOfDecorator = decorator.getName();
|
||||
// Let CaseSensitive or not
|
||||
if (!caseSensitive) {
|
||||
nameOfDecorator = nameOfDecorator.toLowerCase();
|
||||
}
|
||||
|
||||
if (declaration instanceof ClassDeclaration){
|
||||
for (const name of decorators.class){
|
||||
if (typeof mapping.aliasToOriginal[nameOfDecorator] === 'string') {
|
||||
if (name === mapping.aliasToOriginal[nameOfDecorator])
|
||||
return true;
|
||||
} else if (name === nameOfDecorator){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (declaration instanceof MethodDeclaration){
|
||||
for (const name of decorators.methods){
|
||||
if (typeof mapping.aliasToOriginal[nameOfDecorator] === 'string') {
|
||||
if (name === mapping.aliasToOriginal[nameOfDecorator])
|
||||
return true;
|
||||
} else if (name === nameOfDecorator){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (declaration instanceof PropertyDeclaration){
|
||||
for (const name of decorators.properties){
|
||||
if (typeof mapping.aliasToOriginal[nameOfDecorator] === 'string') {
|
||||
if (name === mapping.aliasToOriginal[nameOfDecorator])
|
||||
return true;
|
||||
} else if (name === nameOfDecorator){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return (declaration, decorator, mapping) => {
|
||||
// Get the Name of the Decorator
|
||||
let nameOfDecorator = decorator.getName();
|
||||
// Let CaseSensitive or not
|
||||
if (!caseSensitive) {
|
||||
nameOfDecorator = nameOfDecorator.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
if (declaration instanceof ClassDeclaration) {
|
||||
for (const name of decorators.class) {
|
||||
if (typeof mapping.aliasToOriginal[nameOfDecorator] === "string") {
|
||||
if (name === mapping.aliasToOriginal[nameOfDecorator]) return true;
|
||||
} else if (name === nameOfDecorator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (declaration instanceof MethodDeclaration) {
|
||||
for (const name of decorators.methods) {
|
||||
if (typeof mapping.aliasToOriginal[nameOfDecorator] === "string") {
|
||||
if (name === mapping.aliasToOriginal[nameOfDecorator]) return true;
|
||||
} else if (name === nameOfDecorator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (declaration instanceof PropertyDeclaration) {
|
||||
for (const name of decorators.properties) {
|
||||
if (typeof mapping.aliasToOriginal[nameOfDecorator] === "string") {
|
||||
if (name === mapping.aliasToOriginal[nameOfDecorator]) return true;
|
||||
} else if (name === nameOfDecorator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2020-11-05 11:03:02
|
||||
* @modify date 2020-11-05 11:03:04
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
import { IFunctionDeclaredInVariableInformation } from "../types/IFunctionDeclaredInVariableInformation";
|
||||
import { IFunctionFilter } from "../types/IFunctionFilter";
|
||||
|
||||
/**
|
||||
* Method to create a Default Filter for Methods.
|
||||
* @param options
|
||||
*/
|
||||
export function defaultFunctionFilter(options: {
|
||||
functionDecorator: string,
|
||||
}): IFunctionFilter {
|
||||
return (func: IFunctionDeclaredInVariableInformation) => func.declarationCode.includes(options.functionDecorator + '(');
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2020-11-05 11:02:57
|
||||
* @modify date 2020-11-05 11:02:59
|
||||
* @modify date 2021-01-18 17:19:36
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
@ -17,7 +17,11 @@ import { IModifierInformation } from "../types/IModifierInformation";
|
||||
* Default Method for filtering Methods.
|
||||
*/
|
||||
export function defaultMethodFilter(): IMethodFilter {
|
||||
return (cl: ClassDeclaration, method: (IMethodInformation & IDecoratorInformation & IModifierInformation), importMapping: IImportMapping) => {
|
||||
return method.isPublic && method.isAsync;
|
||||
}
|
||||
}
|
||||
return (
|
||||
cl: ClassDeclaration,
|
||||
method: IMethodInformation & IDecoratorInformation & IModifierInformation,
|
||||
importMapping: IImportMapping
|
||||
) => {
|
||||
return method.isPublic && method.isAsync;
|
||||
};
|
||||
}
|
||||
|
@ -6,13 +6,13 @@
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
import { ClassDeclaration } from "ts-morph"
|
||||
import { isPropOfType } from "../helpers/isPropOfType"
|
||||
import { IDecoratorInformation } from "../types/IDecoratorInformation"
|
||||
import { IImportMapping } from "../types/IImportMapping"
|
||||
import { IModifierInformation } from "../types/IModifierInformation"
|
||||
import { IPropertyFilter } from "../types/IPropertyFilter"
|
||||
import { IPropertyInformation } from "../types/IPropertyInformation"
|
||||
import { ClassDeclaration } from "ts-morph";
|
||||
import { isPropOfType } from "../helpers/isPropOfType";
|
||||
import { IDecoratorInformation } from "../types/IDecoratorInformation";
|
||||
import { IImportMapping } from "../types/IImportMapping";
|
||||
import { IModifierInformation } from "../types/IModifierInformation";
|
||||
import { IPropertyFilter } from "../types/IPropertyFilter";
|
||||
import { IPropertyInformation } from "../types/IPropertyInformation";
|
||||
|
||||
/**
|
||||
* Default Filter, to Filter Properties
|
||||
@ -25,6 +25,6 @@ export function defaultPropertyFilter(options: {
|
||||
return (cl: ClassDeclaration, property: (IPropertyInformation & IDecoratorInformation & IModifierInformation), importMapping: IImportMapping) => {
|
||||
return isPropOfType(property.declaration, options.propertyType, false) &&
|
||||
property.isPublic &&
|
||||
property.decoratorNames.includes(options.propertyDecorator)
|
||||
}
|
||||
property.decoratorNames.includes(options.propertyDecorator);
|
||||
};
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2020-11-11 13:27:58
|
||||
* @modify date 2021-01-08 08:37:04
|
||||
* @modify date 2021-01-18 18:46:10
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
@ -12,13 +12,28 @@ import { getNopeLogger } from "../logger/getLogger";
|
||||
|
||||
// Define the Main Function.
|
||||
// This function is used as cli tool.
|
||||
const main = async function () {
|
||||
export const generateDefaultConfig = async function (
|
||||
additionalArguments: {
|
||||
help: string;
|
||||
type: "string" | "number";
|
||||
name: string | string;
|
||||
defaultValue?: any;
|
||||
}[] = []
|
||||
) {
|
||||
const parser = new ArgumentParser({
|
||||
version: "1.0.0",
|
||||
addHelp: true,
|
||||
description: "Command Line interface, determines the available Packages."
|
||||
});
|
||||
|
||||
for (const arg of additionalArguments) {
|
||||
parser.addArgument(arg.name, {
|
||||
help: arg.help,
|
||||
defaultValue: arg.defaultValue,
|
||||
type: arg.type
|
||||
});
|
||||
}
|
||||
|
||||
parser.addArgument(["-f", "--file"], {
|
||||
help: "File containing containing the package definitions.",
|
||||
defaultValue: "./nopeconfig.json",
|
||||
@ -62,4 +77,6 @@ const main = async function () {
|
||||
}
|
||||
};
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
if (require.main === module) {
|
||||
generateDefaultConfig().catch((e) => console.error(e));
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2020-11-11 13:27:58
|
||||
* @modify date 2020-11-12 13:49:17
|
||||
* @modify date 2021-01-18 18:48:59
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
@ -13,66 +13,77 @@ import { getNopeLogger } from "../logger/getLogger";
|
||||
|
||||
// Define the Main Function.
|
||||
// This function is used as cli tool.
|
||||
const main = async function () {
|
||||
export const generateDefaultPackageConfig = async function (
|
||||
additionalArguments: {
|
||||
help: string;
|
||||
type: "string" | "number";
|
||||
name: string | string;
|
||||
defaultValue?: any;
|
||||
}[] = []
|
||||
) {
|
||||
const parser = new ArgumentParser({
|
||||
version: "1.0.0",
|
||||
addHelp: true,
|
||||
description: "Command Line interface, determines the available Packages."
|
||||
});
|
||||
|
||||
const parser = new ArgumentParser({
|
||||
version: "1.0.0",
|
||||
addHelp: true,
|
||||
description: "Command Line interface, determines the available Packages."
|
||||
let opts: {
|
||||
modules: string;
|
||||
};
|
||||
|
||||
for (const arg of additionalArguments) {
|
||||
parser.addArgument(arg.name, {
|
||||
help: arg.help,
|
||||
defaultValue: arg.defaultValue,
|
||||
type: arg.type
|
||||
});
|
||||
}
|
||||
|
||||
let opts: {
|
||||
modules: string,
|
||||
};
|
||||
|
||||
try{
|
||||
// Try to read in the default config file.
|
||||
opts = JSON.parse(await readFile("./nopeconfig.json", {
|
||||
encoding: "utf-8"
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
opts = {} as any;
|
||||
}
|
||||
|
||||
parser.addArgument(
|
||||
["-f", "--file"],
|
||||
{
|
||||
help: "File containing containing the package definitions.",
|
||||
defaultValue: "./config/settings.json",
|
||||
type: "string",
|
||||
dest: "file"
|
||||
}
|
||||
try {
|
||||
// Try to read in the default config file.
|
||||
opts = JSON.parse(
|
||||
await readFile("./nopeconfig.json", {
|
||||
encoding: "utf-8"
|
||||
})
|
||||
);
|
||||
parser.addArgument(
|
||||
["-s", "--search"],
|
||||
{
|
||||
help: "Directory containing the Modules.",
|
||||
defaultValue: "not-provided",
|
||||
type: "string",
|
||||
dest: "modules"
|
||||
}
|
||||
);
|
||||
|
||||
const args = parser.parseArgs();
|
||||
} catch (error) {
|
||||
opts = {} as any;
|
||||
}
|
||||
|
||||
if (args.modules != "not-provided"){
|
||||
opts.modules = args.modules;
|
||||
}
|
||||
parser.addArgument(["-f", "--file"], {
|
||||
help: "File containing containing the package definitions.",
|
||||
defaultValue: "./config/settings.json",
|
||||
type: "string",
|
||||
dest: "file"
|
||||
});
|
||||
parser.addArgument(["-s", "--search"], {
|
||||
help: "Directory containing the Modules.",
|
||||
defaultValue: "not-provided",
|
||||
type: "string",
|
||||
dest: "modules"
|
||||
});
|
||||
|
||||
// Define a Logger
|
||||
const logger = getNopeLogger("Setup-CLI");
|
||||
const args = parser.parseArgs();
|
||||
|
||||
try {
|
||||
logger.info("Scanning " + opts.modules);
|
||||
// Write the Config.
|
||||
await writeDefaultConfig(opts.modules, args.file);
|
||||
// Inform the user.
|
||||
logger.info("Created Package-File at " + args.file);
|
||||
} catch (error) {
|
||||
logger.error("Failed generating the Config");
|
||||
logger.error(error);
|
||||
}
|
||||
if (args.modules != "not-provided") {
|
||||
opts.modules = args.modules;
|
||||
}
|
||||
|
||||
// Define a Logger
|
||||
const logger = getNopeLogger("Setup-CLI");
|
||||
|
||||
try {
|
||||
logger.info("Scanning " + opts.modules);
|
||||
// Write the Config.
|
||||
await writeDefaultConfig(opts.modules, args.file);
|
||||
// Inform the user.
|
||||
logger.info("Created Package-File at " + args.file);
|
||||
} catch (error) {
|
||||
logger.error("Failed generating the Config");
|
||||
logger.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
main().catch(e => console.error(e));
|
||||
if (require.main === module) {
|
||||
generateDefaultPackageConfig().catch((e) => console.error(e));
|
||||
}
|
||||
|
102
lib/cli/generateFolderStructure.ts
Normal file
102
lib/cli/generateFolderStructure.ts
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2021-01-18 17:24:49
|
||||
* @modify date 2021-01-18 18:45:49
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
import { ArgumentParser } from "argparse";
|
||||
import { copyFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { createPath } from "../helpers/fileMethods";
|
||||
import { getNopeLogger } from "../logger/getLogger";
|
||||
|
||||
const FOLDERS_TO_CREATE = [
|
||||
"config",
|
||||
"modules",
|
||||
"open-api",
|
||||
"pages",
|
||||
join("public", "nope")
|
||||
];
|
||||
const BASE_FOLDER = __dirname;
|
||||
const FILES_TO_COPY = [
|
||||
// Public-Files, like logo etc.
|
||||
"/public/nope/logo_light.png",
|
||||
"/public/nope/logo.png",
|
||||
// Default-Pages
|
||||
"/pages/_app.tsx",
|
||||
"/pages/index.tsx",
|
||||
// Config-Files
|
||||
"tsconfig.json",
|
||||
"tsconfigBackend.json",
|
||||
// Doc-Files:
|
||||
"jsdoc.json",
|
||||
// NextJS
|
||||
"next.config.js",
|
||||
"next-env.d.ts",
|
||||
|
||||
// Package File:
|
||||
"package.json"
|
||||
];
|
||||
|
||||
export async function generateFolderStructure(
|
||||
additionalArguments: {
|
||||
help: string;
|
||||
type: "string" | "number";
|
||||
name: string | string;
|
||||
defaultValue?: any;
|
||||
}[] = []
|
||||
) {
|
||||
const parser = new ArgumentParser({
|
||||
version: "1.0.0",
|
||||
addHelp: true,
|
||||
description: "Command Line interface, determines the available Packages."
|
||||
});
|
||||
|
||||
for (const arg of additionalArguments) {
|
||||
parser.addArgument(arg.name, {
|
||||
help: arg.help,
|
||||
defaultValue: arg.defaultValue,
|
||||
type: arg.type
|
||||
});
|
||||
}
|
||||
|
||||
parser.addArgument(["-o", "--output"], {
|
||||
help: "Output Folder",
|
||||
defaultValue: "./",
|
||||
type: "string",
|
||||
dest: "dir"
|
||||
});
|
||||
|
||||
const args = parser.parseArgs();
|
||||
|
||||
for (const folder of FOLDERS_TO_CREATE) {
|
||||
try {
|
||||
await createPath(join(args.dir, folder));
|
||||
} catch (e) {
|
||||
// Define a Logger
|
||||
const logger = getNopeLogger("cli");
|
||||
logger.error("Failed to create directory " + folder);
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of FILES_TO_COPY) {
|
||||
try {
|
||||
await copyFile(
|
||||
join(BASE_FOLDER, "..", "..", "..", file),
|
||||
join(args.dir, file)
|
||||
);
|
||||
} catch (e) {
|
||||
// Define a Logger
|
||||
const logger = getNopeLogger("cli");
|
||||
logger.error("Failed to copy file " + file);
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
generateFolderStructure();
|
||||
}
|
66
lib/cli/nope.ts
Normal file
66
lib/cli/nope.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2021-01-18 17:29:44
|
||||
* @modify date 2021-01-18 18:49:54
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
import { generateDefaultConfig } from "./generateDefaultConfig";
|
||||
import { generateDefaultPackageConfig } from "./generateDefaultPackageConfig";
|
||||
import { generateFolderStructure } from "./generateFolderStructure";
|
||||
import { NOPELOGO } from "./renderNope";
|
||||
import { readInArgs as getRunArgs, runNopeBackend } from "./runNopeBackend";
|
||||
|
||||
/**
|
||||
* Main Function.
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export async function main() {
|
||||
console.log(NOPELOGO);
|
||||
console.log("\n\n");
|
||||
|
||||
const args: {
|
||||
mode: "run" | "init" | "none" | "config";
|
||||
params: string[];
|
||||
} = {
|
||||
mode: (process.argv[2] as any) || "none",
|
||||
params: process.argv.slice(3)
|
||||
};
|
||||
|
||||
const additionalArg: any = {
|
||||
help: "Command to run the backend",
|
||||
name: "cmd",
|
||||
type: "string"
|
||||
};
|
||||
|
||||
switch (args.mode) {
|
||||
default:
|
||||
case "none":
|
||||
return;
|
||||
case "run":
|
||||
getRunArgs([additionalArg])
|
||||
.then((args) => runNopeBackend(args).catch(console.error))
|
||||
.catch(console.error);
|
||||
break;
|
||||
case "init":
|
||||
additionalArg.help = "Command to run init";
|
||||
await generateFolderStructure([additionalArg]);
|
||||
await generateDefaultConfig([additionalArg]);
|
||||
await generateDefaultPackageConfig([additionalArg]);
|
||||
|
||||
break;
|
||||
case "config":
|
||||
additionalArg.help = "Command to generate the Config of the backend";
|
||||
await generateDefaultConfig([additionalArg]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export default main;
|
||||
|
||||
// If requested As Main => Perform the Operation.
|
||||
if (require.main === module) {
|
||||
main().catch((e) => console.error(e));
|
||||
}
|
@ -1,6 +1,13 @@
|
||||
// eslint-disable-next-line quotes
|
||||
/**
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2021-01-18 18:00:39
|
||||
* @modify date 2021-01-18 18:23:06
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
export const NOPELOGO = `
|
||||
----- .---. ..----******--. -------------------.
|
||||
----- .---. ..----******--. -------------------.
|
||||
.Peeeen. =eeeP* .oPeeeeeeeeeeeeePo|-. ....-*oeeeeeeeeeeeeeeeeePP*.........-*||-
|
||||
.PeeeePo- =eeee* -eeeePn=======oPPPPPn- ....-*oeeeePPPPPPPPPPPPPPP- -**.
|
||||
.PeeeeeeP* =eeee* ------ -eeeeo. *oeeeP- oeeee-
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @author Martin Karkowski
|
||||
* @email m.karkowski@zema.de
|
||||
* @create date 2020-11-11 13:27:58
|
||||
* @modify date 2021-01-08 16:26:06
|
||||
* @modify date 2021-01-18 17:50:46
|
||||
* @desc [description]
|
||||
*/
|
||||
|
||||
@ -42,13 +42,28 @@ const layerDefaults = {
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
export async function readInArgs() {
|
||||
export async function readInArgs(
|
||||
additionalArguments: {
|
||||
help: string;
|
||||
type: "string" | "number";
|
||||
name: string | string;
|
||||
defaultValue?: any;
|
||||
}[] = []
|
||||
): Promise<any> {
|
||||
const parser = new ArgumentParser({
|
||||
version: "1.0.0",
|
||||
addHelp: true,
|
||||
description: "Command Line interface, determines the available Packages."
|
||||
});
|
||||
|
||||
for (const arg of additionalArguments) {
|
||||
parser.addArgument(arg.name, {
|
||||
help: arg.help,
|
||||
defaultValue: arg.defaultValue,
|
||||
type: arg.type
|
||||
});
|
||||
}
|
||||
|
||||
parser.addArgument(["-f", "--file"], {
|
||||
help: "File containing containing the package definitions.",
|
||||
defaultValue: "./config/settings.json",
|
||||
|
19060
package-lock.json
generated
19060
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
||||
"description": "Nodejs Backend, combining nextjs with openapi",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"nope-cli": "./bin/nope",
|
||||
"runNopeBackend": "./bin/runNopeBackend",
|
||||
"generateDefaultPackageConfig": "./bin/generateDefaultPackageConfig"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user