feat(expo): use helper to determine project name and root in project generators (#18678)

This commit is contained in:
Leosvel Pérez Espinosa 2023-08-22 15:25:54 +01:00 committed by GitHub
parent 6640c32748
commit a668e0b7d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 156 additions and 74 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "application", "name": "application",
"factory": "./src/generators/application/application#expoApplicationGenerator", "factory": "./src/generators/application/application#expoApplicationGeneratorInternal",
"schema": { "schema": {
"cli": "nx", "cli": "nx",
"$id": "NxExpoApplication", "$id": "NxExpoApplication",
@ -22,7 +22,8 @@
"description": "The name of the application.", "description": "The name of the application.",
"type": "string", "type": "string",
"$default": { "$source": "argv", "index": 0 }, "$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the application?" "x-prompt": "What name would you like to use for the application?",
"pattern": "^[a-zA-Z][^:]*$"
}, },
"displayName": { "displayName": {
"description": "The display name to show in the application. Defaults to name.", "description": "The display name to show in the application. Defaults to name.",
@ -33,6 +34,11 @@
"type": "string", "type": "string",
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"skipFormat": { "skipFormat": {
"description": "Skip formatting files", "description": "Skip formatting files",
"type": "boolean", "type": "boolean",
@ -90,7 +96,7 @@
"aliases": ["app"], "aliases": ["app"],
"x-type": "application", "x-type": "application",
"description": "Create an application", "description": "Create an application",
"implementation": "/packages/expo/src/generators/application/application#expoApplicationGenerator.ts", "implementation": "/packages/expo/src/generators/application/application#expoApplicationGeneratorInternal.ts",
"hidden": false, "hidden": false,
"path": "/packages/expo/src/generators/application/schema.json", "path": "/packages/expo/src/generators/application/schema.json",
"type": "generator" "type": "generator"

View File

@ -1,6 +1,6 @@
{ {
"name": "library", "name": "library",
"factory": "./src/generators/library/library#expoLibraryGenerator", "factory": "./src/generators/library/library#expoLibraryGeneratorInternal",
"schema": { "schema": {
"cli": "nx", "cli": "nx",
"$id": "NxExpoLibrary", "$id": "NxExpoLibrary",
@ -19,13 +19,18 @@
"description": "Library name", "description": "Library name",
"$default": { "$source": "argv", "index": 0 }, "$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the library?", "x-prompt": "What name would you like to use for the library?",
"pattern": "^[a-zA-Z].*$" "pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$"
}, },
"directory": { "directory": {
"type": "string", "type": "string",
"description": "A directory where the lib is placed.", "description": "A directory where the lib is placed.",
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"linter": { "linter": {
"description": "The tool to use for running lint checks.", "description": "The tool to use for running lint checks.",
"type": "string", "type": "string",
@ -101,7 +106,7 @@
"aliases": ["lib"], "aliases": ["lib"],
"x-type": "library", "x-type": "library",
"description": "Create a library", "description": "Create a library",
"implementation": "/packages/expo/src/generators/library/library#expoLibraryGenerator.ts", "implementation": "/packages/expo/src/generators/library/library#expoLibraryGeneratorInternal.ts",
"hidden": false, "hidden": false,
"path": "/packages/expo/src/generators/library/schema.json", "path": "/packages/expo/src/generators/library/schema.json",
"type": "generator" "type": "generator"

View File

@ -155,4 +155,42 @@ describe('expo', () => {
); );
}).not.toThrow(); }).not.toThrow();
}); });
it('should support generating projects with the new name and root format', () => {
const appName = uniq('app1');
const libName = uniq('@my-org/lib1');
runCLI(
`generate @nx/expo:application ${appName} --project-name-and-root-format=as-provided --no-interactive`
);
// check files are generated without the layout directory ("apps/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${appName}/src/app/App.tsx`);
// check tests pass
const appTestResult = runCLI(`test ${appName}`);
expect(appTestResult).toContain(
`Successfully ran target test for project ${appName}`
);
// assert scoped project names are not supported when --project-name-and-root-format=derived
expect(() =>
runCLI(
`generate @nx/expo:library ${libName} --buildable --project-name-and-root-format=derived`
)
).toThrow();
runCLI(
`generate @nx/expo:library ${libName} --buildable --project-name-and-root-format=as-provided`
);
// check files are generated without the layout directory ("libs/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${libName}/src/index.ts`);
// check tests pass
const libTestResult = runCLI(`test ${libName}`);
expect(libTestResult).toContain(
`Successfully ran target test for project ${libName}`
);
});
}); });

View File

@ -38,14 +38,14 @@
"hidden": true "hidden": true
}, },
"application": { "application": {
"factory": "./src/generators/application/application#expoApplicationGenerator", "factory": "./src/generators/application/application#expoApplicationGeneratorInternal",
"schema": "./src/generators/application/schema.json", "schema": "./src/generators/application/schema.json",
"aliases": ["app"], "aliases": ["app"],
"x-type": "application", "x-type": "application",
"description": "Create an application" "description": "Create an application"
}, },
"library": { "library": {
"factory": "./src/generators/library/library#expoLibraryGenerator", "factory": "./src/generators/library/library#expoLibraryGeneratorInternal",
"schema": "./src/generators/library/schema.json", "schema": "./src/generators/library/schema.json",
"aliases": ["lib"], "aliases": ["lib"],
"x-type": "library", "x-type": "library",

View File

@ -23,7 +23,17 @@ export async function expoApplicationGenerator(
host: Tree, host: Tree,
schema: Schema schema: Schema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const options = normalizeOptions(host, schema); return await expoApplicationGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...schema,
});
}
export async function expoApplicationGeneratorInternal(
host: Tree,
schema: Schema
): Promise<GeneratorCallback> {
const options = await normalizeOptions(host, schema);
createApplicationFiles(host, options); createApplicationFiles(host, options);
addProject(host, options); addProject(host, options);

View File

@ -11,8 +11,9 @@ export async function addDetox(host: Tree, options: NormalizedSchema) {
return detoxApplicationGenerator(host, { return detoxApplicationGenerator(host, {
...options, ...options,
linter: Linter.EsLint, linter: Linter.EsLint,
e2eName: `${options.name}-e2e`, e2eName: `${options.projectName}-e2e`,
e2eDirectory: options.directory, e2eDirectory: `${options.appProjectRoot}-e2e`,
projectNameAndRootFormat: 'as-provided',
appProject: options.projectName, appProject: options.projectName,
appDisplayName: options.displayName, appDisplayName: options.displayName,
appName: options.name, appName: options.name,

View File

@ -38,7 +38,7 @@ function getTargets(options: NormalizedSchema) {
architect.serve = { architect.serve = {
executor: 'nx:run-commands', executor: 'nx:run-commands',
options: { options: {
command: `nx start ${options.name}`, command: `nx start ${options.projectName}`,
}, },
}; };

View File

@ -11,7 +11,7 @@ describe('Normalize Options', () => {
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
}); });
it('should normalize options with name in kebab case', () => { it('should normalize options with name in kebab case', async () => {
const schema: Schema = { const schema: Schema = {
name: 'my-app', name: 'my-app',
linter: Linter.EsLint, linter: Linter.EsLint,
@ -20,7 +20,7 @@ describe('Normalize Options', () => {
js: true, js: true,
unitTestRunner: 'jest', unitTestRunner: 'jest',
}; };
const options = normalizeOptions(appTree, schema); const options = await normalizeOptions(appTree, schema);
expect(options).toEqual({ expect(options).toEqual({
appProjectRoot: 'apps/my-app', appProjectRoot: 'apps/my-app',
className: 'MyApp', className: 'MyApp',
@ -29,6 +29,7 @@ describe('Normalize Options', () => {
name: 'my-app', name: 'my-app',
parsedTags: [], parsedTags: [],
projectName: 'my-app', projectName: 'my-app',
projectNameAndRootFormat: 'derived',
linter: Linter.EsLint, linter: Linter.EsLint,
e2eTestRunner: 'none', e2eTestRunner: 'none',
unitTestRunner: 'jest', unitTestRunner: 'jest',
@ -37,7 +38,7 @@ describe('Normalize Options', () => {
}); });
}); });
it('should normalize options with name in camel case', () => { it('should normalize options with name in camel case', async () => {
const schema: Schema = { const schema: Schema = {
name: 'myApp', name: 'myApp',
linter: Linter.EsLint, linter: Linter.EsLint,
@ -46,7 +47,7 @@ describe('Normalize Options', () => {
js: true, js: true,
unitTestRunner: 'jest', unitTestRunner: 'jest',
}; };
const options = normalizeOptions(appTree, schema); const options = await normalizeOptions(appTree, schema);
expect(options).toEqual({ expect(options).toEqual({
appProjectRoot: 'apps/my-app', appProjectRoot: 'apps/my-app',
className: 'MyApp', className: 'MyApp',
@ -55,6 +56,7 @@ describe('Normalize Options', () => {
name: 'my-app', name: 'my-app',
parsedTags: [], parsedTags: [],
projectName: 'my-app', projectName: 'my-app',
projectNameAndRootFormat: 'derived',
linter: Linter.EsLint, linter: Linter.EsLint,
e2eTestRunner: 'none', e2eTestRunner: 'none',
skipFormat: false, skipFormat: false,
@ -63,7 +65,7 @@ describe('Normalize Options', () => {
}); });
}); });
it('should normalize options with directory', () => { it('should normalize options with directory', async () => {
const schema: Schema = { const schema: Schema = {
name: 'my-app', name: 'my-app',
directory: 'directory', directory: 'directory',
@ -73,7 +75,7 @@ describe('Normalize Options', () => {
js: true, js: true,
unitTestRunner: 'jest', unitTestRunner: 'jest',
}; };
const options = normalizeOptions(appTree, schema); const options = await normalizeOptions(appTree, schema);
expect(options).toEqual({ expect(options).toEqual({
appProjectRoot: 'apps/directory/my-app', appProjectRoot: 'apps/directory/my-app',
className: 'MyApp', className: 'MyApp',
@ -83,6 +85,7 @@ describe('Normalize Options', () => {
directory: 'directory', directory: 'directory',
parsedTags: [], parsedTags: [],
projectName: 'directory-my-app', projectName: 'directory-my-app',
projectNameAndRootFormat: 'derived',
e2eTestRunner: 'none', e2eTestRunner: 'none',
unitTestRunner: 'jest', unitTestRunner: 'jest',
linter: Linter.EsLint, linter: Linter.EsLint,
@ -91,7 +94,7 @@ describe('Normalize Options', () => {
}); });
}); });
it('should normalize options that has directory in its name', () => { it('should normalize options that has directory in its name', async () => {
const schema: Schema = { const schema: Schema = {
name: 'directory/my-app', name: 'directory/my-app',
linter: Linter.EsLint, linter: Linter.EsLint,
@ -100,7 +103,7 @@ describe('Normalize Options', () => {
js: true, js: true,
unitTestRunner: 'jest', unitTestRunner: 'jest',
}; };
const options = normalizeOptions(appTree, schema); const options = await normalizeOptions(appTree, schema);
expect(options).toEqual({ expect(options).toEqual({
appProjectRoot: 'apps/directory/my-app', appProjectRoot: 'apps/directory/my-app',
className: 'DirectoryMyApp', className: 'DirectoryMyApp',
@ -109,6 +112,7 @@ describe('Normalize Options', () => {
name: 'directory/my-app', name: 'directory/my-app',
parsedTags: [], parsedTags: [],
projectName: 'directory-my-app', projectName: 'directory-my-app',
projectNameAndRootFormat: 'derived',
e2eTestRunner: 'none', e2eTestRunner: 'none',
unitTestRunner: 'jest', unitTestRunner: 'jest',
linter: Linter.EsLint, linter: Linter.EsLint,
@ -117,7 +121,7 @@ describe('Normalize Options', () => {
}); });
}); });
it('should normalize options with display name', () => { it('should normalize options with display name', async () => {
const schema: Schema = { const schema: Schema = {
name: 'my-app', name: 'my-app',
displayName: 'My App', displayName: 'My App',
@ -127,7 +131,7 @@ describe('Normalize Options', () => {
js: true, js: true,
unitTestRunner: 'jest', unitTestRunner: 'jest',
}; };
const options = normalizeOptions(appTree, schema); const options = await normalizeOptions(appTree, schema);
expect(options).toEqual({ expect(options).toEqual({
appProjectRoot: 'apps/my-app', appProjectRoot: 'apps/my-app',
className: 'MyApp', className: 'MyApp',
@ -136,6 +140,7 @@ describe('Normalize Options', () => {
name: 'my-app', name: 'my-app',
parsedTags: [], parsedTags: [],
projectName: 'my-app', projectName: 'my-app',
projectNameAndRootFormat: 'derived',
e2eTestRunner: 'none', e2eTestRunner: 'none',
unitTestRunner: 'jest', unitTestRunner: 'jest',
linter: Linter.EsLint, linter: Linter.EsLint,

View File

@ -1,4 +1,5 @@
import { getWorkspaceLayout, joinPathFragments, names, Tree } from '@nx/devkit'; import { names, Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema'; import { Schema } from '../schema';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
@ -9,39 +10,34 @@ export interface NormalizedSchema extends Schema {
parsedTags: string[]; parsedTags: string[];
} }
export function normalizeOptions( export async function normalizeOptions(
host: Tree, host: Tree,
options: Schema options: Schema
): NormalizedSchema { ): Promise<NormalizedSchema> {
const { fileName, className } = names(options.name); const {
const { appsDir } = getWorkspaceLayout(host); projectName: appProjectName,
names: projectNames,
const directoryName = options.directory projectRoot: appProjectRoot,
? names(options.directory).fileName projectNameAndRootFormat,
: ''; } = await determineProjectNameAndRootOptions(host, {
const projectDirectory = directoryName name: options.name,
? `${directoryName}/${fileName}` projectType: 'application',
: fileName; directory: options.directory,
projectNameAndRootFormat: options.projectNameAndRootFormat,
const appProjectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); callingGenerator: '@nx/expo:application',
});
const appProjectRoot = joinPathFragments(appsDir, projectDirectory); options.projectNameAndRootFormat = projectNameAndRootFormat;
const { className } = names(options.name);
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
: []; : [];
/**
* if options.name is "my-app"
* name: "my-app", className: 'MyApp', lowerCaseName: 'myapp', displayName: 'MyApp', projectName: 'my-app', appProjectRoot: 'apps/my-app', androidProjectRoot: 'apps/my-app/android', iosProjectRoot: 'apps/my-app/ios'
* if options.name is "myApp"
* name: "my-app", className: 'MyApp', lowerCaseName: 'myapp', displayName: 'MyApp', projectName: 'my-app', appProjectRoot: 'apps/my-app', androidProjectRoot: 'apps/my-app/android', iosProjectRoot: 'apps/my-app/ios'
*/
return { return {
...options, ...options,
unitTestRunner: options.unitTestRunner || 'jest', unitTestRunner: options.unitTestRunner || 'jest',
e2eTestRunner: options.e2eTestRunner || 'detox', e2eTestRunner: options.e2eTestRunner || 'detox',
name: fileName, name: projectNames.projectSimpleName,
className, className,
lowerCaseName: className.toLowerCase(), lowerCaseName: className.toLowerCase(),
displayName: options.displayName || className, displayName: options.displayName || className,

View File

@ -1,4 +1,5 @@
import { Linter } from '@nx/linter'; import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
import type { Linter } from '@nx/linter';
export interface Schema { export interface Schema {
name: string; name: string;
@ -6,6 +7,7 @@ export interface Schema {
style?: string; style?: string;
skipFormat: boolean; // default is false skipFormat: boolean; // default is false
directory?: string; directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
tags?: string; tags?: string;
unitTestRunner: 'jest' | 'none'; // default is jest unitTestRunner: 'jest' | 'none'; // default is jest
pascalCaseFiles?: boolean; pascalCaseFiles?: boolean;

View File

@ -22,7 +22,8 @@
"$source": "argv", "$source": "argv",
"index": 0 "index": 0
}, },
"x-prompt": "What name would you like to use for the application?" "x-prompt": "What name would you like to use for the application?",
"pattern": "^[a-zA-Z][^:]*$"
}, },
"displayName": { "displayName": {
"description": "The display name to show in the application. Defaults to name.", "description": "The display name to show in the application. Defaults to name.",
@ -33,6 +34,11 @@
"type": "string", "type": "string",
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"skipFormat": { "skipFormat": {
"description": "Skip formatting files", "description": "Skip formatting files",
"type": "boolean", "type": "boolean",

View File

@ -1,5 +1,5 @@
import { getWorkspaceLayout, joinPathFragments, names, Tree } from '@nx/devkit'; import { Tree } from '@nx/devkit';
import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema'; import { Schema } from '../schema';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
@ -7,41 +7,39 @@ export interface NormalizedSchema extends Schema {
fileName: string; fileName: string;
projectRoot: string; projectRoot: string;
routePath: string; routePath: string;
projectDirectory: string;
parsedTags: string[]; parsedTags: string[];
appMain: string; appMain: string;
} }
export function normalizeOptions( export async function normalizeOptions(
host: Tree, host: Tree,
options: Schema options: Schema
): NormalizedSchema { ): Promise<NormalizedSchema> {
const name = names(options.name).fileName; const {
const projectDirectory = options.directory projectName,
? `${names(options.directory).fileName}/${name}` names: projectNames,
: name; projectRoot,
importPath,
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); } = await determineProjectNameAndRootOptions(host, {
const fileName = projectName; name: options.name,
const { libsDir } = getWorkspaceLayout(host); projectType: 'library',
const projectRoot = joinPathFragments(libsDir, projectDirectory); directory: options.directory,
importPath: options.importPath,
projectNameAndRootFormat: options.projectNameAndRootFormat,
callingGenerator: '@nx/expo:library',
});
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
: []; : [];
const importPath =
options.importPath || getImportPath(host, projectDirectory);
const appMain = options.js ? 'src/index.js' : 'src/index.ts'; const appMain = options.js ? 'src/index.js' : 'src/index.ts';
const normalized: NormalizedSchema = { const normalized: NormalizedSchema = {
...options, ...options,
fileName, fileName: projectName,
routePath: `/${name}`, routePath: `/${projectNames.projectSimpleName}`,
name: projectName, name: projectName,
projectRoot, projectRoot,
projectDirectory,
parsedTags, parsedTags,
importPath, importPath,
appMain, appMain,

View File

@ -5,7 +5,6 @@ import {
formatFiles, formatFiles,
generateFiles, generateFiles,
GeneratorCallback, GeneratorCallback,
getWorkspaceLayout,
joinPathFragments, joinPathFragments,
names, names,
offsetFromRoot, offsetFromRoot,
@ -32,7 +31,17 @@ export async function expoLibraryGenerator(
host: Tree, host: Tree,
schema: Schema schema: Schema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const options = normalizeOptions(host, schema); return await expoLibraryGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...schema,
});
}
export async function expoLibraryGeneratorInternal(
host: Tree,
schema: Schema
): Promise<GeneratorCallback> {
const options = await normalizeOptions(host, schema);
if (options.publishable === true && !schema.importPath) { if (options.publishable === true && !schema.importPath) {
throw new Error( throw new Error(
`For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)` `For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)`
@ -105,7 +114,6 @@ async function addProject(host: Tree, options: NormalizedSchema) {
nxVersion nxVersion
); );
const { libsDir } = getWorkspaceLayout(host);
const external = [ const external = [
'react/jsx-runtime', 'react/jsx-runtime',
'react-native', 'react-native',
@ -117,7 +125,7 @@ async function addProject(host: Tree, options: NormalizedSchema) {
executor: '@nx/rollup:rollup', executor: '@nx/rollup:rollup',
outputs: ['{options.outputPath}'], outputs: ['{options.outputPath}'],
options: { options: {
outputPath: `dist/${libsDir}/${options.projectDirectory}`, outputPath: `dist/${options.projectRoot}`,
tsConfig: `${options.projectRoot}/tsconfig.lib.json`, tsConfig: `${options.projectRoot}/tsconfig.lib.json`,
project: `${options.projectRoot}/package.json`, project: `${options.projectRoot}/package.json`,
entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`), entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`),

View File

@ -1,4 +1,5 @@
import { Linter } from '@nx/linter'; import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
import type { Linter } from '@nx/linter';
/** /**
* Same as the @nx/react library schema, except it removes keys: style, component, routing, appProject * Same as the @nx/react library schema, except it removes keys: style, component, routing, appProject
@ -6,6 +7,7 @@ import { Linter } from '@nx/linter';
export interface Schema { export interface Schema {
name: string; name: string;
directory?: string; directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
skipTsConfig: boolean; // default is false skipTsConfig: boolean; // default is false
skipFormat: boolean; // default is false skipFormat: boolean; // default is false
tags?: string; tags?: string;

View File

@ -19,13 +19,18 @@
"index": 0 "index": 0
}, },
"x-prompt": "What name would you like to use for the library?", "x-prompt": "What name would you like to use for the library?",
"pattern": "^[a-zA-Z].*$" "pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$"
}, },
"directory": { "directory": {
"type": "string", "type": "string",
"description": "A directory where the lib is placed.", "description": "A directory where the lib is placed.",
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"linter": { "linter": {
"description": "The tool to use for running lint checks.", "description": "The tool to use for running lint checks.",
"type": "string", "type": "string",