feat(js): add library and app generators

This commit is contained in:
Victor Savkin 2021-11-10 15:50:27 -05:00 committed by Victor Savkin
parent 6e801a1e9b
commit fcecebeb3a
40 changed files with 2601 additions and 46 deletions

View File

@ -222,8 +222,8 @@ The type must be one of the following:
The scope must be one of the following: The scope must be one of the following:
- angular - anything Angular specific
- core - anything Nx core specific - core - anything Nx core specific
- js - anything related to @nrwl/js package or general js/ts support
- nxdev - anything related to docs infrastructure - nxdev - anything related to docs infrastructure
- nextjs - anything Next specific - nextjs - anything Next specific
- nest - anything Nest specific - nest - anything Nest specific
@ -234,6 +234,7 @@ The scope must be one of the following:
- storybook - anything Storybook specific - storybook - anything Storybook specific
- testing - anything testing specific (e.g., jest or cypress) - testing - anything testing specific (e.g., jest or cypress)
- repo - anything related to managing the repo itself - repo - anything related to managing the repo itself
- angular - anything Angular specific
- misc - misc stuff - misc - misc stuff
- devkit - devkit-related changes - devkit - devkit-related changes

View File

@ -0,0 +1,161 @@
# @nrwl/js:application
Create a application
## Usage
```bash
nx generate application ...
```
```bash
nx g app ... # same
```
By default, Nx will search for `application` in the default collection provisioned in `angular.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/js:application ...
```
Show what will be generated without writing to disk:
```bash
nx g application ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### name (_**required**_)
Type: `string`
Library name
### compiler
Default: `tsc`
Type: `string`
Possible values: `tsc`, `swc`
The compiler used by the build and test targets
### config
Default: `project`
Type: `string`
Possible values: `workspace`, `project`, `npm-scripts`
Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts
### directory
Type: `string`
A directory where the lib is placed
### importPath
Type: `string`
The library name used to import it, like @myorg/my-awesome-lib
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files
### linter
Default: `eslint`
Type: `string`
Possible values: `eslint`, `none`
The tool to use for running lint checks.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `true`
Type: `boolean`
Whether to enable tsconfig strict mode or not.
### tags
Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -0,0 +1,169 @@
# @nrwl/js:library
Create a library
## Usage
```bash
nx generate library ...
```
```bash
nx g lib ... # same
```
By default, Nx will search for `library` in the default collection provisioned in `angular.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/js:library ...
```
Show what will be generated without writing to disk:
```bash
nx g library ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### name (_**required**_)
Type: `string`
Library name
### buildable
Default: `false`
Type: `boolean`
Generate a buildable library.
### compiler
Default: `tsc`
Type: `string`
Possible values: `tsc`, `swc`
The compiler used by the build and test targets
### config
Default: `project`
Type: `string`
Possible values: `workspace`, `project`, `npm-scripts`
Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts
### directory
Type: `string`
A directory where the lib is placed
### importPath
Type: `string`
The library name used to import it, like @myorg/my-awesome-lib
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files
### linter
Default: `eslint`
Type: `string`
Possible values: `eslint`, `none`
The tool to use for running lint checks.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `true`
Type: `boolean`
Whether to enable tsconfig strict mode or not.
### tags
Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -359,6 +359,16 @@
"name": "js", "name": "js",
"id": "js", "id": "js",
"itemList": [ "itemList": [
{
"name": "application generator",
"id": "application",
"file": "angular/api-js/generators/application"
},
{
"name": "library generator",
"id": "library",
"file": "angular/api-js/generators/library"
},
{ {
"name": "tsc executor", "name": "tsc executor",
"id": "tsc", "id": "tsc",
@ -1673,6 +1683,16 @@
"name": "js", "name": "js",
"id": "js", "id": "js",
"itemList": [ "itemList": [
{
"name": "application generator",
"id": "application",
"file": "react/api-js/generators/application"
},
{
"name": "library generator",
"id": "library",
"file": "react/api-js/generators/library"
},
{ {
"name": "tsc executor", "name": "tsc executor",
"id": "tsc", "id": "tsc",
@ -2951,6 +2971,16 @@
"name": "js", "name": "js",
"id": "js", "id": "js",
"itemList": [ "itemList": [
{
"name": "application generator",
"id": "application",
"file": "node/api-js/generators/application"
},
{
"name": "library generator",
"id": "library",
"file": "node/api-js/generators/library"
},
{ {
"name": "tsc executor", "name": "tsc executor",
"id": "tsc", "id": "tsc",

View File

@ -0,0 +1,161 @@
# @nrwl/js:application
Create a application
## Usage
```bash
nx generate application ...
```
```bash
nx g app ... # same
```
By default, Nx will search for `application` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/js:application ...
```
Show what will be generated without writing to disk:
```bash
nx g application ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### name (_**required**_)
Type: `string`
Library name
### compiler
Default: `tsc`
Type: `string`
Possible values: `tsc`, `swc`
The compiler used by the build and test targets
### config
Default: `project`
Type: `string`
Possible values: `workspace`, `project`, `npm-scripts`
Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts
### directory
Type: `string`
A directory where the lib is placed
### importPath
Type: `string`
The library name used to import it, like @myorg/my-awesome-lib
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files
### linter
Default: `eslint`
Type: `string`
Possible values: `eslint`, `none`
The tool to use for running lint checks.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `true`
Type: `boolean`
Whether to enable tsconfig strict mode or not.
### tags
Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -0,0 +1,169 @@
# @nrwl/js:library
Create a library
## Usage
```bash
nx generate library ...
```
```bash
nx g lib ... # same
```
By default, Nx will search for `library` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/js:library ...
```
Show what will be generated without writing to disk:
```bash
nx g library ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### name (_**required**_)
Type: `string`
Library name
### buildable
Default: `false`
Type: `boolean`
Generate a buildable library.
### compiler
Default: `tsc`
Type: `string`
Possible values: `tsc`, `swc`
The compiler used by the build and test targets
### config
Default: `project`
Type: `string`
Possible values: `workspace`, `project`, `npm-scripts`
Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts
### directory
Type: `string`
A directory where the lib is placed
### importPath
Type: `string`
The library name used to import it, like @myorg/my-awesome-lib
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files
### linter
Default: `eslint`
Type: `string`
Possible values: `eslint`, `none`
The tool to use for running lint checks.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `true`
Type: `boolean`
Whether to enable tsconfig strict mode or not.
### tags
Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -0,0 +1,161 @@
# @nrwl/js:application
Create a application
## Usage
```bash
nx generate application ...
```
```bash
nx g app ... # same
```
By default, Nx will search for `application` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/js:application ...
```
Show what will be generated without writing to disk:
```bash
nx g application ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### name (_**required**_)
Type: `string`
Library name
### compiler
Default: `tsc`
Type: `string`
Possible values: `tsc`, `swc`
The compiler used by the build and test targets
### config
Default: `project`
Type: `string`
Possible values: `workspace`, `project`, `npm-scripts`
Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts
### directory
Type: `string`
A directory where the lib is placed
### importPath
Type: `string`
The library name used to import it, like @myorg/my-awesome-lib
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files
### linter
Default: `eslint`
Type: `string`
Possible values: `eslint`, `none`
The tool to use for running lint checks.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `true`
Type: `boolean`
Whether to enable tsconfig strict mode or not.
### tags
Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -0,0 +1,169 @@
# @nrwl/js:library
Create a library
## Usage
```bash
nx generate library ...
```
```bash
nx g lib ... # same
```
By default, Nx will search for `library` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/js:library ...
```
Show what will be generated without writing to disk:
```bash
nx g library ... --dry-run
```
### Examples
Generate libs/myapp/mylib:
```bash
nx g lib mylib --directory=myapp
```
## Options
### name (_**required**_)
Type: `string`
Library name
### buildable
Default: `false`
Type: `boolean`
Generate a buildable library.
### compiler
Default: `tsc`
Type: `string`
Possible values: `tsc`, `swc`
The compiler used by the build and test targets
### config
Default: `project`
Type: `string`
Possible values: `workspace`, `project`, `npm-scripts`
Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts
### directory
Type: `string`
A directory where the lib is placed
### importPath
Type: `string`
The library name used to import it, like @myorg/my-awesome-lib
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files
### linter
Default: `eslint`
Type: `string`
Possible values: `eslint`, `none`
The tool to use for running lint checks.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `true`
Type: `boolean`
Whether to enable tsconfig strict mode or not.
### tags
Type: `string`
Add tags to the library (used for linting)
### testEnvironment
Default: `jsdom`
Type: `string`
Possible values: `jsdom`, `node`
The test environment to use if unitTestRunner is set to jest
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -14,7 +14,7 @@
"command": "yarn e2e-build-package-publish" "command": "yarn e2e-build-package-publish"
}, },
{ {
"command": "nx run-e2e-tests e2e-jest" "command": "nx run-e2e-tests e2e-js"
} }
], ],
"parallel": false "parallel": false

View File

@ -1,42 +1,97 @@
import { import {
checkFilesExist, checkFilesExist,
ensureNxProject, newProject,
readJson, readJson,
runNxCommandAsync, runCLI,
runCLIAsync,
runCommand,
uniq, uniq,
} from '@nrwl/nx-plugin/testing'; updateFile,
describe('js e2e', () => { } from '../../utils';
it('should create js', async () => {
const plugin = uniq('js');
ensureNxProject('@nrwl/js', 'dist//js');
await runNxCommandAsync(`generate @nrwl/js:js ${plugin}`);
const result = await runNxCommandAsync(`build ${plugin}`); describe('js e2e', () => {
expect(result.stdout).toContain('Executor ran'); it('should create libs and apps with npm scripts', () => {
const scope = newProject();
const npmScriptsLib = uniq('npmscriptslib');
runCLI(`generate @nrwl/js:lib ${npmScriptsLib} --config=npm-scripts`);
const libPackageJson = readJson(`libs/${npmScriptsLib}/package.json`);
expect(libPackageJson.scripts.test).toBeDefined();
expect(libPackageJson.scripts.build).toBeDefined();
expect(runCLI(`test ${npmScriptsLib}`)).toContain('implement test');
expect(runCLI(`test ${npmScriptsLib}`)).toContain('match the cache');
const npmScriptsApp = uniq('npmscriptsapp');
runCLI(`generate @nrwl/js:app ${npmScriptsApp} --config=npm-scripts`);
const appPackageJson = readJson(`apps/${npmScriptsApp}/package.json`);
expect(appPackageJson.scripts.test).toBeDefined();
expect(appPackageJson.scripts.build).toBeDefined();
expect(runCLI(`test ${npmScriptsApp}`)).toContain('implement test');
expect(runCLI(`test ${npmScriptsApp}`)).toContain('match the cache');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[`@${scope}/${npmScriptsLib}`]: [`libs/${npmScriptsLib}/src/index.ts`],
});
}, 120000); }, 120000);
describe('--directory', () => { it('should create libs and apps with js executors (--compiler=tsc)', async () => {
it('should create src in the specified directory', async () => { const scope = newProject();
const plugin = uniq('js'); const lib = uniq('lib');
ensureNxProject('@nrwl/js', 'dist//js'); runCLI(`generate @nrwl/js:lib ${lib} --buildable --compiler=tsc`);
await runNxCommandAsync( const libPackageJson = readJson(`libs/${lib}/package.json`);
`generate @nrwl/js:js ${plugin} --directory subdir` expect(libPackageJson.scripts).toBeUndefined();
); expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain(
expect(() => 'Ran all test suites'
checkFilesExist(`libs/subdir/${plugin}/src/index.ts`) );
).not.toThrow(); expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain(
}, 120000); 'match the cache'
}); );
describe('--tags', () => { expect(runCLI(`build ${lib}`)).toContain('Done compiling TypeScript files');
it('should add tags to the project', async () => { checkFilesExist(
const plugin = uniq('js'); `dist/libs/${lib}/package.json`,
ensureNxProject('@nrwl/js', 'dist//js'); `dist/libs/${lib}/src/index.js`,
await runNxCommandAsync( `dist/libs/${lib}/src/lib/${lib}.js`
`generate @nrwl/js:js ${plugin} --tags e2etag,e2ePackage` );
);
const project = readJson(`libs/${plugin}/project.json`); const app = uniq('app');
expect(project.tags).toEqual(['e2etag', 'e2ePackage']); runCLI(`generate @nrwl/js:app ${app} --buildable --compiler=tsc`);
}, 120000); const appPackageJson = readJson(`apps/${app}/package.json`);
}); expect(appPackageJson.scripts).toBeUndefined();
expect((await runCLIAsync(`test ${app}`)).combinedOutput).toContain(
'Ran all test suites'
);
expect((await runCLIAsync(`test ${app}`)).combinedOutput).toContain(
'match the cache'
);
expect(runCLI(`build ${app}`)).toContain('Done compiling TypeScript files');
checkFilesExist(
`dist/apps/${app}/package.json`,
`dist/apps/${app}/src/index.js`,
`dist/apps/${app}/src/app/${app}.js`
);
expect(runCommand(`node dist/apps/${app}/src/index.js`)).toContain(
`Running ${app}`
);
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[`@${scope}/${lib}`]: [`libs/${lib}/src/index.ts`],
});
updateFile(`apps/${app}/src/index.ts`, () => {
return `
import { ${lib} } from '@${scope}/${lib}'
console.log('Running ' + ${lib}())
`;
});
const output = runCLI(`build ${app}`);
expect(output).toContain('1 task(s) that it depends on');
expect(output).toContain('Done compiling TypeScript files');
// expect(runCommand(`node dist/apps/${app}/src/index.js`)).toContain(`Running ${lib}`)
}, 120000);
}); });

View File

@ -10,7 +10,7 @@ describe('Nx Plugins', () => {
beforeAll(() => newProject()); beforeAll(() => newProject());
afterAll(() => removeProject({ onlyOnCI: true })); afterAll(() => removeProject({ onlyOnCI: true }));
it('vvvshould use plugins defined in nx.json', () => { it('should use plugins defined in nx.json', () => {
const nxJson = readJson('nx.json'); const nxJson = readJson('nx.json');
nxJson.plugins = ['./tools/plugin']; nxJson.plugins = ['./tools/plugin'];
updateFile('nx.json', JSON.stringify(nxJson)); updateFile('nx.json', JSON.stringify(nxJson));

View File

@ -1,6 +1,36 @@
{ {
"$schema": "http://json-schema.org/schema", "name": "nx/js",
"name": "js", "version": "0.1",
"version": "0.0.1", "schematics": {
"generators": {} "library": {
"factory": "./src/generators/library/library#librarySchematic",
"schema": "./src/generators/library/schema.json",
"aliases": ["lib"],
"x-type": "library",
"description": "Create a library"
},
"application": {
"factory": "./src/generators/application/application#applicationSchematic",
"schema": "./src/generators/application/schema.json",
"aliases": ["app"],
"x-type": "application",
"description": "Create a application"
}
},
"generators": {
"library": {
"factory": "./src/generators/library/library#libraryGenerator",
"schema": "./src/generators/library/schema.json",
"aliases": ["lib"],
"x-type": "library",
"description": "Create a library"
},
"application": {
"factory": "./src/generators/application/application#applicationGenerator",
"schema": "./src/generators/application/schema.json",
"aliases": ["app"],
"x-type": "application",
"description": "Create a application"
}
}
} }

View File

@ -6,6 +6,8 @@
"executors": "./executors.json", "executors": "./executors.json",
"dependencies": { "dependencies": {
"@nrwl/workspace": "*", "@nrwl/workspace": "*",
"@nrwl/devkit": "*" "@nrwl/devkit": "*",
"@nrwl/jest": "*",
"@nrwl/linter": "*"
} }
} }

View File

@ -0,0 +1,75 @@
import { readJson, readProjectConfiguration, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import applicationGenerator from './application';
import { Schema } from '../../utils/schema';
// most of the functionality is tested via the library generator because the library and application share
// most of the code
// only testing the difference here
describe('app', () => {
let tree: Tree;
const defaultOptions: Omit<Schema, 'name'> = {
skipTsConfig: false,
unitTestRunner: 'jest',
skipFormat: false,
linter: 'eslint',
testEnvironment: 'jsdom',
js: false,
pascalCaseFiles: false,
strict: true,
config: 'project',
compiler: 'tsc',
};
beforeEach(() => {
tree = createTreeWithEmptyWorkspace(2);
});
it('should generate an empty ts app using --config=npm-scripts', async () => {
await applicationGenerator(tree, {
...defaultOptions,
name: 'myApp',
config: 'npm-scripts',
});
expect(readJson(tree, '/apps/my-app/package.json')).toEqual({
name: '@proj/my-app',
version: '0.0.1',
type: 'commonjs',
scripts: {
build: "echo 'implement build'",
test: "echo 'implement test'",
},
});
expect(tree.exists('apps/my-app/src/index.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/my-app.ts')).toBeTruthy();
// unitTestRunner property is ignored.
// It only works with our executors.
expect(tree.exists('apps/my-app/src/app/my-app.spec.ts')).toBeFalsy();
const workspaceJson = readJson(tree, '/workspace.json');
// Blocked on Craigory merging optional config PR
// expect(workspaceJson.projects['my-app']).toBeUndefined();
// expect(tree.exists('apps/my-app/project.json')).toBeFalsy();
});
it('should generate an empty ts app using --config=project', async () => {
await applicationGenerator(tree, {
...defaultOptions,
name: 'my-app',
config: 'project',
});
const projectConfig = readProjectConfiguration(tree, 'my-app');
expect(projectConfig.root).toEqual('apps/my-app');
expect(projectConfig.targets.build).toEqual({
executor: '@nrwl/js:tsc',
options: {
assets: ['apps/my-app/*.md'],
main: 'apps/my-app/src/index.ts',
outputPath: 'dist/apps/my-app',
tsConfig: 'apps/my-app/tsconfig.app.json',
},
outputs: ['{options.outputPath}'],
});
});
});

View File

@ -0,0 +1,18 @@
import { convertNxGenerator, getWorkspaceLayout, Tree } from '@nrwl/devkit';
import { join } from 'path';
import { projectGenerator } from '../../utils/project-generator';
import { Schema } from '../../utils/schema';
export async function applicationGenerator(tree: Tree, schema: Schema) {
const { appsDir } = getWorkspaceLayout(tree);
return projectGenerator(
tree,
{ ...schema, buildable: true, skipTsConfig: true },
appsDir,
join(__dirname, './files'),
'application'
);
}
export default applicationGenerator;
export const applicationSchematic = convertNxGenerator(applicationGenerator);

View File

@ -0,0 +1,19 @@
# <%= name %>
This application was generated with [Nx](https://nx.dev).
## Building
Run `<%= cliCommand %> build <%= name %>` to build the application.
## Starting
Run `<%= cliCommand %> start <%= name %>` to start/run the application.
<% if (hasUnitTestRunner) { %>
## Running unit tests
Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io).
<% } %>

View File

@ -0,0 +1,5 @@
{
"name": "<%= importPath %>",
"version": "0.0.1",
"type": "commonjs"
}

View File

@ -0,0 +1,7 @@
import { <%= propertyName %> } from './<%= fileName %>';
describe('<%= propertyName %>', () => {
it('should work', () => {
expect(<%= propertyName %>()).toEqual('<%= name %>');
})
})

View File

@ -0,0 +1,3 @@
export function <%= propertyName %>(): string {
return '<%= name %>';
}

View File

@ -0,0 +1,3 @@
import {<%= propertyName %>} from './app/<%= fileName %>';
console.log(`Running ${<%= propertyName %>()}`)

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
"declaration": true,
"types": []
},
"include": ["**/*.ts"<% if (js) { %>, "**/*.js"<% } %>],
"exclude": ["**/*.spec.ts"<% if (js) { %>, "**/*.spec.js"<% } %>]
}

View File

@ -0,0 +1,14 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS"<% if (js) { %>,
"allowJs": true<% } %>
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -0,0 +1,99 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "NxTypescriptLibrary",
"cli": "nx",
"title": "Create a TypeScript Library",
"type": "object",
"examples": [
{
"command": "g lib mylib --directory=myapp",
"description": "Generate libs/myapp/mylib"
}
],
"properties": {
"name": {
"type": "string",
"description": "Library name",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use for the library?",
"pattern": "^[a-zA-Z].*$"
},
"directory": {
"type": "string",
"description": "A directory where the lib is placed"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "eslint"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for unit tests",
"default": "jest"
},
"tags": {
"type": "string",
"description": "Add tags to the library (used for linting)"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipTsConfig": {
"type": "boolean",
"description": "Do not update tsconfig.json for development experience.",
"default": false
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"description": "The test environment to use if unitTestRunner is set to jest",
"default": "jsdom"
},
"importPath": {
"type": "string",
"description": "The library name used to import it, like @myorg/my-awesome-lib"
},
"pascalCaseFiles": {
"type": "boolean",
"description": "Use pascal case file names.",
"alias": "P",
"default": false
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files",
"default": false
},
"strict": {
"type": "boolean",
"description": "Whether to enable tsconfig strict mode or not.",
"default": true
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
},
"config": {
"type": "string",
"enum": ["workspace", "project", "npm-scripts"],
"default": "project",
"description": "Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts"
},
"compiler": {
"type": "string",
"enum": ["tsc", "swc"],
"default": "tsc",
"description": "The compiler used by the build and test targets"
}
},
"required": ["name"]
}

View File

@ -0,0 +1,19 @@
# <%= name %>
This library was generated with [Nx](https://nx.dev).
<% if (buildable) { %>
## Building
Run `<%= cliCommand %> build <%= name %>` to build the library.
<% } %>
<% if (hasUnitTestRunner) { %>
## Running unit tests
Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io).
<% } %>

View File

@ -0,0 +1,5 @@
{
"name": "<%= importPath %>",
"version": "0.0.1",
"type": "commonjs"
}

View File

@ -0,0 +1 @@
export * from './lib/<%= fileName %>';

View File

@ -0,0 +1,7 @@
import { <%= propertyName %> } from './<%= fileName %>';
describe('<%= propertyName %>', () => {
it('should work', () => {
expect(<%= propertyName %>()).toEqual('<%= name %>');
})
})

View File

@ -0,0 +1,3 @@
export function <%= propertyName %>(): string {
return '<%= name %>';
}

View File

@ -0,0 +1,14 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS"<% if (js) { %>,
"allowJs": true<% } %>
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
"declaration": true,
"types": []
},
"include": ["**/*.ts"<% if (js) { %>, "**/*.js"<% } %>],
"exclude": ["**/*.spec.ts"<% if (js) { %>, "**/*.spec.js"<% } %>]
}

View File

@ -0,0 +1,684 @@
import {
getProjects,
readJson,
readProjectConfiguration,
Tree,
updateJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { Schema } from '../../utils/schema';
import libraryGenerator from './library';
describe('lib', () => {
let tree: Tree;
const defaultOptions: Omit<Schema, 'name'> = {
skipTsConfig: false,
unitTestRunner: 'jest',
skipFormat: false,
linter: 'eslint',
testEnvironment: 'jsdom',
js: false,
pascalCaseFiles: false,
strict: true,
config: 'project',
};
beforeEach(() => {
tree = createTreeWithEmptyWorkspace(2);
});
describe('configs', () => {
it('should generate an empty ts lib using --config=npm-scripts', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
config: 'npm-scripts',
});
expect(readJson(tree, '/libs/my-lib/package.json')).toEqual({
name: '@proj/my-lib',
version: '0.0.1',
type: 'commonjs',
scripts: {
build: "echo 'implement build'",
test: "echo 'implement test'",
},
});
expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.ts')).toBeTruthy();
// unitTestRunner property is ignored.
// It only works with our executors.
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeFalsy();
const workspaceJson = readJson(tree, '/workspace.json');
// Blocked on Craigory merging optional config PR
// expect(workspaceJson.projects['my-lib']).toBeUndefined();
// expect(tree.exists('libs/my-lib/project.json')).toBeFalsy();
});
it('should generate an empty ts lib using --config=project', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'my-lib',
config: 'project',
});
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib');
expect(workspaceJsonEntry).toEqual('libs/my-lib');
});
it('should generate an empty ts lib using --config=workspace', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'my-lib',
config: 'workspace',
});
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib');
expect(projectConfig).toMatchObject(workspaceJsonEntry);
});
});
describe('shared options', () => {
describe('not-nested', () => {
it('should update tags', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
tags: 'one,two',
});
const projects = Object.fromEntries(getProjects(tree));
expect(projects).toMatchObject({
'my-lib': {
tags: ['one', 'two'],
},
});
});
it('should update root tsconfig.json', async () => {
await libraryGenerator(tree, { ...defaultOptions, name: 'myLib' });
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
'libs/my-lib/src/index.ts',
]);
});
it('should update root tsconfig.json (no existing path mappings)', async () => {
updateJson(tree, 'tsconfig.base.json', (json) => {
json.compilerOptions.paths = undefined;
return json;
});
await libraryGenerator(tree, { ...defaultOptions, name: 'myLib' });
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
'libs/my-lib/src/index.ts',
]);
});
it('should create a local tsconfig.json', async () => {
await libraryGenerator(tree, { ...defaultOptions, name: 'myLib' });
const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.json');
expect(tsconfigJson).toMatchInlineSnapshot(`
Object {
"compilerOptions": Object {
"forceConsistentCasingInFileNames": true,
"module": "CommonJS",
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"strict": true,
},
"extends": "../../tsconfig.base.json",
"files": Array [],
"include": Array [],
"references": Array [
Object {
"path": "./tsconfig.lib.json",
},
Object {
"path": "./tsconfig.spec.json",
},
],
}
`);
});
});
describe('nested', () => {
it('should update tags', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
tags: 'one',
});
let projects = Object.fromEntries(getProjects(tree));
expect(projects).toMatchObject({
'my-dir-my-lib': {
tags: ['one'],
},
});
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib2',
directory: 'myDir',
tags: 'one,two',
simpleModuleName: true,
});
projects = Object.fromEntries(getProjects(tree));
expect(projects).toMatchObject({
'my-dir-my-lib': {
tags: ['one'],
},
'my-dir-my-lib2': {
tags: ['one', 'two'],
},
});
});
it('should generate files', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
});
expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.ts')
).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy();
expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy();
expect(tree.exists(`libs/my-dir/my-lib/package.json`)).toBeFalsy();
});
it('should update workspace.json', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
config: 'workspace',
});
const workspaceJson = readJson(tree, '/workspace.json');
expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual(
'libs/my-dir/my-lib'
);
});
it('should update tsconfig.json', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
});
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
expect(
tsconfigJson.compilerOptions.paths['@proj/my-dir/my-lib']
).toEqual(['libs/my-dir/my-lib/src/index.ts']);
expect(
tsconfigJson.compilerOptions.paths['my-dir-my-lib/*']
).toBeUndefined();
});
it('should create a local tsconfig.json', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
});
const tsconfigJson = readJson(tree, 'libs/my-dir/my-lib/tsconfig.json');
expect(tsconfigJson.references).toEqual([
{
path: './tsconfig.lib.json',
},
{
path: './tsconfig.spec.json',
},
]);
});
});
describe('--strict', () => {
it('should update the projects tsconfig with strict false', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
strict: false,
});
const tsconfigJson = readJson(tree, '/libs/my-lib/tsconfig.json');
expect(tsconfigJson.compilerOptions?.strict).not.toBeDefined();
expect(
tsconfigJson.compilerOptions?.forceConsistentCasingInFileNames
).not.toBeDefined();
expect(
tsconfigJson.compilerOptions?.noImplicitReturns
).not.toBeDefined();
expect(
tsconfigJson.compilerOptions?.noFallthroughCasesInSwitch
).not.toBeDefined();
});
it('should default to strict true', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
});
const tsconfigJson = readJson(tree, '/libs/my-lib/tsconfig.json');
expect(tsconfigJson.compilerOptions.strict).toBeTruthy();
expect(
tsconfigJson.compilerOptions.forceConsistentCasingInFileNames
).toBeTruthy();
expect(tsconfigJson.compilerOptions.noImplicitReturns).toBeTruthy();
expect(
tsconfigJson.compilerOptions.noFallthroughCasesInSwitch
).toBeTruthy();
});
});
describe('--importPath', () => {
it('should update the tsconfig with the given import path', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
importPath: '@myorg/lib',
});
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
expect(tsconfigJson.compilerOptions.paths['@myorg/lib']).toBeDefined();
});
it('should fail if the same importPath has already been used', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib1',
importPath: '@myorg/lib',
});
try {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib2',
importPath: '@myorg/lib',
});
} catch (e) {
expect(e.message).toContain(
'You already have a library using the import path'
);
}
expect.assertions(1);
});
});
describe('--pascalCaseFiles', () => {
it('should generate files with upper case names', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
pascalCaseFiles: true,
});
expect(tree.exists('libs/my-lib/src/lib/MyLib.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/MyLib.spec.ts')).toBeTruthy();
});
it('should generate files with upper case names for nested libs as well', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
pascalCaseFiles: true,
});
expect(
tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.spec.ts')
).toBeTruthy();
});
});
});
describe('--linter', () => {
it('should add eslint dependencies', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
});
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['eslint']).toBeDefined();
expect(packageJson.devDependencies['@nrwl/linter']).toBeDefined();
expect(
packageJson.devDependencies['@nrwl/eslint-plugin-nx']
).toBeDefined();
});
describe('not nested', () => {
it('should update configuration', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
});
expect(readProjectConfiguration(tree, 'my-lib').targets.lint).toEqual({
executor: '@nrwl/linter:eslint',
outputs: ['{options.outputFile}'],
options: {
lintFilePatterns: ['libs/my-lib/**/*.ts'],
},
});
});
it('should create a local .eslintrc.json', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
});
const eslintJson = readJson(tree, 'libs/my-lib/.eslintrc.json');
expect(eslintJson).toMatchInlineSnapshot(`
Object {
"extends": Array [
"../../.eslintrc.json",
],
"ignorePatterns": Array [
"!**/*",
],
"overrides": Array [
Object {
"files": Array [
"*.ts",
"*.tsx",
"*.js",
"*.jsx",
],
"rules": Object {},
},
Object {
"files": Array [
"*.ts",
"*.tsx",
],
"rules": Object {},
},
Object {
"files": Array [
"*.js",
"*.jsx",
],
"rules": Object {},
},
],
}
`);
});
});
describe('nested', () => {
it('should update configuration', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
});
expect(
readProjectConfiguration(tree, 'my-dir-my-lib').targets.lint
).toEqual({
executor: '@nrwl/linter:eslint',
outputs: ['{options.outputFile}'],
options: {
lintFilePatterns: ['libs/my-dir/my-lib/**/*.ts'],
},
});
});
it('should create a local .eslintrc.json', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
});
const eslintJson = readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json');
expect(eslintJson).toMatchInlineSnapshot(`
Object {
"extends": Array [
"../../../.eslintrc.json",
],
"ignorePatterns": Array [
"!**/*",
],
"overrides": Array [
Object {
"files": Array [
"*.ts",
"*.tsx",
"*.js",
"*.jsx",
],
"rules": Object {},
},
Object {
"files": Array [
"*.ts",
"*.tsx",
],
"rules": Object {},
},
Object {
"files": Array [
"*.js",
"*.jsx",
],
"rules": Object {},
},
],
}
`);
});
});
describe('--js flag', () => {
it('should generate js files instead of ts files', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
js: true,
});
expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy();
});
it('should update tsconfig.json with compilerOptions.allowJs: true', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
js: true,
});
expect(
readJson(tree, 'libs/my-lib/tsconfig.json').compilerOptions.allowJs
).toBeTruthy();
});
it('should update tsconfig.lib.json include with **/*.js glob', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
js: true,
});
expect(readJson(tree, 'libs/my-lib/tsconfig.lib.json').include).toEqual(
['**/*.ts', '**/*.js']
);
});
it('should update root tsconfig.json with a js file path', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
js: true,
});
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
'libs/my-lib/src/index.js',
]);
});
it('should generate js files for nested libs as well', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
js: true,
});
expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js')
).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.js')
).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy();
});
it('should configure the project for linting js files', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
js: true,
});
expect(
readProjectConfiguration(tree, 'my-dir-my-lib').targets.lint.options
.lintFilePatterns
).toEqual(['libs/my-dir/my-lib/**/*.js']);
expect(readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json'))
.toMatchInlineSnapshot(`
Object {
"extends": Array [
"../../../.eslintrc.json",
],
"ignorePatterns": Array [
"!**/*",
],
"overrides": Array [
Object {
"files": Array [
"*.ts",
"*.tsx",
"*.js",
"*.jsx",
],
"rules": Object {},
},
Object {
"files": Array [
"*.ts",
"*.tsx",
],
"rules": Object {},
},
Object {
"files": Array [
"*.js",
"*.jsx",
],
"rules": Object {},
},
],
}
`);
});
});
});
describe('--unit-test-runner jest', () => {
it('should generate test configuration', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
unitTestRunner: 'jest',
});
expect(tree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy();
expect(tree.exists('libs/my-lib/jest.config.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeTruthy();
const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.targets.test).toBeDefined();
expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.read(`libs/my-lib/jest.config.js`, 'utf-8'))
.toMatchInlineSnapshot(`
"module.exports = {
displayName: 'my-lib',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
}
},
transform: {
'^.+\\\\\\\\.[tj]sx?$': 'ts-jest'
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/my-lib'
};
"
`);
const readme = tree.read('libs/my-lib/README.md', 'utf-8');
expect(readme).toContain('nx test my-lib');
});
describe('--buildable', () => {
it('should generate the build target', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
buildable: true,
compiler: 'tsc',
});
const config = readProjectConfiguration(tree, 'my-lib');
expect(config.targets.build).toEqual({
executor: '@nrwl/js:tsc',
options: {
assets: ['libs/my-lib/*.md'],
main: 'libs/my-lib/src/index.ts',
outputPath: 'dist/libs/my-lib',
tsConfig: 'libs/my-lib/tsconfig.lib.json',
},
outputs: ['{options.outputPath}'],
});
});
it('should generate a package.json file', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
buildable: true,
compiler: 'tsc',
});
expect(tree.exists('libs/my-lib/package.json')).toBeTruthy();
});
});
});
});

View File

@ -0,0 +1,24 @@
import {
convertNxGenerator,
getWorkspaceLayout,
joinPathFragments,
Tree,
updateJson,
} from '@nrwl/devkit';
import { join } from 'path';
import { projectGenerator } from '../../utils/project-generator';
import { Schema } from '../../utils/schema';
export async function libraryGenerator(tree: Tree, schema: Schema) {
const { libsDir } = getWorkspaceLayout(tree);
return projectGenerator(
tree,
schema,
libsDir,
join(__dirname, './files'),
'library'
);
}
export default libraryGenerator;
export const librarySchematic = convertNxGenerator(libraryGenerator);

View File

@ -0,0 +1,104 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "NxTypescriptLibrary",
"cli": "nx",
"title": "Create a TypeScript Library",
"type": "object",
"examples": [
{
"command": "g lib mylib --directory=myapp",
"description": "Generate libs/myapp/mylib"
}
],
"properties": {
"name": {
"type": "string",
"description": "Library name",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use for the library?",
"pattern": "^[a-zA-Z].*$"
},
"directory": {
"type": "string",
"description": "A directory where the lib is placed"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "eslint"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for unit tests",
"default": "jest"
},
"tags": {
"type": "string",
"description": "Add tags to the library (used for linting)"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipTsConfig": {
"type": "boolean",
"description": "Do not update tsconfig.json for development experience.",
"default": false
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"description": "The test environment to use if unitTestRunner is set to jest",
"default": "jsdom"
},
"importPath": {
"type": "string",
"description": "The library name used to import it, like @myorg/my-awesome-lib"
},
"pascalCaseFiles": {
"type": "boolean",
"description": "Use pascal case file names.",
"alias": "P",
"default": false
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files",
"default": false
},
"strict": {
"type": "boolean",
"description": "Whether to enable tsconfig strict mode or not.",
"default": true
},
"buildable": {
"type": "boolean",
"default": false,
"description": "Generate a buildable library."
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
},
"config": {
"type": "string",
"enum": ["workspace", "project", "npm-scripts"],
"default": "project",
"description": "Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts"
},
"compiler": {
"type": "string",
"enum": ["tsc", "swc"],
"default": "tsc",
"description": "The compiler used by the build and test targets"
}
},
"required": ["name"]
}

View File

@ -0,0 +1,299 @@
import {
addProjectConfiguration,
formatFiles,
generateFiles,
GeneratorCallback,
getWorkspaceLayout,
joinPathFragments,
names,
offsetFromRoot,
ProjectConfiguration,
toJS,
Tree,
updateJson,
} from '@nrwl/devkit';
import { join } from 'path';
import { Schema } from './schema';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
// nx-ignore-next-line
const { jestProjectGenerator } = require('@nrwl/jest');
// nx-ignore-next-line
const { lintProjectGenerator, Linter } = require('@nrwl/linter');
export async function projectGenerator(
tree: Tree,
schema: Schema,
destinationDir: string,
filesDir: string,
projectType: 'library' | 'application'
) {
const options = normalizeOptions(tree, schema, destinationDir);
createFiles(tree, options, filesDir);
addProject(tree, options, destinationDir, projectType);
if (!schema.skipTsConfig) {
updateRootTsConfig(tree, options);
}
const tasks: GeneratorCallback[] = [];
if (options.linter !== 'none') {
const lintCallback = await addLint(tree, options);
tasks.push(lintCallback);
}
if (options.unitTestRunner === 'jest') {
const jestCallback = await addJest(tree, options);
tasks.push(jestCallback);
}
if (!options.skipFormat) {
await formatFiles(tree);
}
return runTasksInSerial(...tasks);
}
export interface NormalizedSchema extends Schema {
name: string;
fileName: string;
projectRoot: string;
projectDirectory: string;
parsedTags: string[];
importPath?: string;
}
function addProject(
tree: Tree,
options: NormalizedSchema,
destinationDir: string,
projectType: 'library' | 'application'
) {
const projectConfiguration: ProjectConfiguration = {
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: projectType,
targets: {},
tags: options.parsedTags,
};
if (options.buildable && options.config != 'npm-scripts') {
if (options.compiler === 'tsc') {
projectConfiguration.targets.build = {
executor: '@nrwl/js:tsc',
outputs: ['{options.outputPath}'],
options: {
outputPath: `dist/${destinationDir}/${options.projectDirectory}`,
main:
`${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'),
tsConfig: `${options.projectRoot}/${
projectType === 'library'
? 'tsconfig.lib.json'
: 'tsconfig.app.json'
}`,
assets: [`${options.projectRoot}/*.md`],
},
};
} else {
throw new Error(`Compiler ${options.compiler} is not supported`);
}
}
if (options.config === 'workspace') {
addProjectConfiguration(tree, options.name, projectConfiguration, false);
} else if (options.config === 'project') {
addProjectConfiguration(tree, options.name, projectConfiguration, true);
} else {
addProjectConfiguration(
tree,
options.name,
{
root: projectConfiguration.root,
tags: projectConfiguration.tags,
},
true
);
}
}
export function addLint(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
return lintProjectGenerator(tree, {
project: options.name,
linter: options.linter,
skipFormat: true,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
],
eslintFilePatterns: [
`${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
],
setParserOptionsProject: options.setParserOptionsProject,
});
}
function updateTsConfig(tree: Tree, options: NormalizedSchema) {
updateJson(tree, join(options.projectRoot, 'tsconfig.json'), (json) => {
if (options.strict) {
json.compilerOptions = {
...json.compilerOptions,
forceConsistentCasingInFileNames: true,
strict: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
}
return json;
});
}
function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) {
const { className, name, propertyName } = names(options.name);
generateFiles(tree, filesDir, options.projectRoot, {
...options,
dot: '.',
className,
name,
propertyName,
js: !!options.js,
cliCommand: 'nx',
strict: undefined,
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot),
buildable: options.buildable === true,
hasUnitTestRunner: options.unitTestRunner !== 'none',
});
if (options.unitTestRunner === 'none') {
tree.delete(
join(options.projectRoot, 'src/lib', `${options.fileName}.spec.ts`)
);
tree.delete(
join(options.projectRoot, 'src/app', `${options.fileName}.spec.ts`)
);
}
if (options.js) {
toJS(tree);
}
const packageJsonPath = join(options.projectRoot, 'package.json');
if (options.config === 'npm-scripts') {
updateJson(tree, packageJsonPath, (json) => {
json.scripts = {
build: "echo 'implement build'",
test: "echo 'implement test'",
};
return json;
});
} else if (!options.buildable) {
tree.delete(packageJsonPath);
}
updateTsConfig(tree, options);
}
async function addJest(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
return await jestProjectGenerator(tree, {
project: options.name,
setupFile: 'none',
supportTsx: true,
skipSerializers: true,
testEnvironment: options.testEnvironment,
skipFormat: true,
});
}
function normalizeOptions(
tree: Tree,
options: Schema,
destinationDir: string
): NormalizedSchema {
if (options.config === 'npm-scripts') {
options.unitTestRunner = 'none';
options.linter = Linter.None;
options.buildable = false;
}
const name = names(options.name).fileName;
const projectDirectory = options.directory
? `${names(options.directory).fileName}/${name}`
: name;
if (!options.unitTestRunner && options.config !== 'npm-scripts') {
options.unitTestRunner = 'jest';
}
if (!options.linter && options.config !== 'npm-scripts') {
options.linter = Linter.EsLint;
}
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = getCaseAwareFileName({
fileName: options.simpleModuleName ? name : projectName,
pascalCaseFiles: options.pascalCaseFiles,
});
const { npmScope } = getWorkspaceLayout(tree);
const projectRoot = joinPathFragments(destinationDir, projectDirectory);
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const defaultImportPath = `@${npmScope}/${projectDirectory}`;
const importPath = options.importPath || defaultImportPath;
return {
...options,
fileName,
name: projectName,
projectRoot,
projectDirectory,
parsedTags,
importPath,
};
}
function getCaseAwareFileName(options: {
pascalCaseFiles: boolean;
fileName: string;
}) {
const normalized = names(options.fileName);
return options.pascalCaseFiles ? normalized.className : normalized.fileName;
}
function updateRootTsConfig(host: Tree, options: NormalizedSchema) {
updateJson(host, 'tsconfig.base.json', (json) => {
const c = json.compilerOptions;
c.paths = c.paths || {};
delete c.paths[options.name];
if (c.paths[options.importPath]) {
throw new Error(
`You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.`
);
}
c.paths[options.importPath] = [
joinPathFragments(
options.projectRoot,
'./src',
'index.' + (options.js ? 'js' : 'ts')
),
];
return json;
});
}

22
packages/js/src/utils/schema.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
// nx-ignore-next-line
const { Linter } = require('@nrwl/linter');
export interface Schema {
name: string;
directory?: string;
skipFormat?: boolean;
tags?: string;
simpleModuleName?: boolean;
skipTsConfig?: boolean;
unitTestRunner?: 'jest' | 'none';
linter?: Linter;
testEnvironment?: 'jsdom' | 'node';
importPath?: string;
js?: boolean;
pascalCaseFiles?: boolean;
strict?: boolean;
buildable?: boolean;
setParserOptionsProject?: boolean;
config?: 'workspace' | 'project' | 'npm-scripts';
compiler?: 'tsc' | 'swc';
}

View File

@ -0,0 +1 @@
export const nxVersion = '*';

View File

@ -25,7 +25,7 @@ import { createSchemaFlattener, SchemaFlattener } from './schema-flattener';
/** /**
* @WhatItDoes: Generates default documentation from the schematics' schema. * @WhatItDoes: Generates default documentation from the schematics' schema.
* We need to construct an Array of objects containing all the information * We need to construct an Array of objects containing all the information
* of the schematics and their associates schema info. It should be easily * of the schematics and their associated schema info. It should be easily
* parsable in order to be used in a rendering process using template. This * parsable in order to be used in a rendering process using template. This
* in order to generate a markdown file for each available schematic. * in order to generate a markdown file for each available schematic.
*/ */

View File

@ -69,7 +69,7 @@ async function publishPackage(packagePath: string, npmMajorVersion: number) {
// NPM@7 requires a token to publish, thus, is just a matter of fake a token to bypass npm. // NPM@7 requires a token to publish, thus, is just a matter of fake a token to bypass npm.
// See: https://twitter.com/verdaccio_npm/status/1357798427283910660 // See: https://twitter.com/verdaccio_npm/status/1357798427283910660
if (npmMajorVersion === 7) { if (npmMajorVersion >= 7) {
writeFileSync( writeFileSync(
`${packagePath}/.npmrc`, `${packagePath}/.npmrc`,
`registry=${ `registry=${
@ -88,6 +88,7 @@ async function publishPackage(packagePath: string, npmMajorVersion: number) {
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
process.exit(1);
} }
} }

View File

@ -18,7 +18,7 @@ npx nx run-many --target=build --all --parallel || { echo 'Build failed' ; exit
cd build/packages cd build/packages
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox}/src/utils/versions.js sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox,js}/src/utils/versions.js
sed -i "" "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json sed -i "" "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json
sed -i "" "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
@ -29,7 +29,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
else else
sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox}/src/utils/versions.js sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox,js}/src/utils/versions.js
sed -i "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json sed -i "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json
sed -i "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js

View File

@ -18,6 +18,7 @@
"e2e-detox": "e2e/detox", "e2e-detox": "e2e/detox",
"e2e-gatsby": "e2e/gatsby", "e2e-gatsby": "e2e/gatsby",
"e2e-jest": "e2e/jest", "e2e-jest": "e2e/jest",
"e2e-js": "e2e/js",
"e2e-linter": "e2e/linter", "e2e-linter": "e2e/linter",
"e2e-next": "e2e/next", "e2e-next": "e2e/next",
"e2e-node": "e2e/node", "e2e-node": "e2e/node",
@ -35,7 +36,6 @@
"gatsby": "packages/gatsby", "gatsby": "packages/gatsby",
"jest": "packages/jest", "jest": "packages/jest",
"js": "packages/js", "js": "packages/js",
"e2e-js": "e2e/js",
"linter": "packages/linter", "linter": "packages/linter",
"nest": "packages/nest", "nest": "packages/nest",
"next": "packages/next", "next": "packages/next",