feat(core): move tsconfig.base.json to @nrwl/js:init (#14467)

This commit is contained in:
Emily Xiong 2023-02-16 11:17:30 -05:00 committed by GitHub
parent f913b905a1
commit a97212b601
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
182 changed files with 1565 additions and 785 deletions

View File

@ -1190,14 +1190,14 @@ Detect workspace scope from the package.json name
### ensurePackage ### ensurePackage
**ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `Promise`<`void`\> **ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `void`
Ensure that dependencies and devDependencies from package.json are installed at the required versions. Ensure that dependencies and devDependencies from package.json are installed at the required versions.
For example: For example:
```typescript ```typescript
ensurePackage(tree, {}, { '@nrwl/jest': nxVersion }); ensurePackage(tree, '@nrwl/jest', nxVersion);
``` ```
This will check that @nrwl/jest@<nxVersion> exists in devDependencies. This will check that @nrwl/jest@<nxVersion> exists in devDependencies.
@ -1217,7 +1217,7 @@ When running with --dryRun, the function will throw when dependencies are missin
#### Returns #### Returns
`Promise`<`void`\> `void`
--- ---

View File

@ -1088,7 +1088,7 @@
"type": "generator" "type": "generator"
}, },
"/packages/js/generators/init": { "/packages/js/generators/init": {
"description": "Init placeholder.", "description": "Initialize a TS/JS workspace.",
"file": "generated/packages/js/generators/init.json", "file": "generated/packages/js/generators/init.json",
"hidden": true, "hidden": true,
"name": "init", "name": "init",

View File

@ -1070,7 +1070,7 @@
"type": "generator" "type": "generator"
}, },
{ {
"description": "Init placeholder.", "description": "Initialize a TS/JS workspace.",
"file": "generated/packages/js/generators/init.json", "file": "generated/packages/js/generators/init.json",
"hidden": true, "hidden": true,
"name": "init", "name": "init",

View File

@ -1190,14 +1190,14 @@ Detect workspace scope from the package.json name
### ensurePackage ### ensurePackage
**ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `Promise`<`void`\> **ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `void`
Ensure that dependencies and devDependencies from package.json are installed at the required versions. Ensure that dependencies and devDependencies from package.json are installed at the required versions.
For example: For example:
```typescript ```typescript
ensurePackage(tree, {}, { '@nrwl/jest': nxVersion }); ensurePackage(tree, '@nrwl/jest', nxVersion);
``` ```
This will check that @nrwl/jest@<nxVersion> exists in devDependencies. This will check that @nrwl/jest@<nxVersion> exists in devDependencies.
@ -1217,7 +1217,7 @@ When running with --dryRun, the function will throw when dependencies are missin
#### Returns #### Returns
`Promise`<`void`\> `void`
--- ---

View File

@ -1190,14 +1190,14 @@ Detect workspace scope from the package.json name
### ensurePackage ### ensurePackage
**ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `Promise`<`void`\> **ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `void`
Ensure that dependencies and devDependencies from package.json are installed at the required versions. Ensure that dependencies and devDependencies from package.json are installed at the required versions.
For example: For example:
```typescript ```typescript
ensurePackage(tree, {}, { '@nrwl/jest': nxVersion }); ensurePackage(tree, '@nrwl/jest', nxVersion);
``` ```
This will check that @nrwl/jest@<nxVersion> exists in devDependencies. This will check that @nrwl/jest@<nxVersion> exists in devDependencies.
@ -1217,7 +1217,7 @@ When running with --dryRun, the function will throw when dependencies are missin
#### Returns #### Returns
`Promise`<`void`\> `void`
--- ---

View File

@ -28,8 +28,13 @@
}, },
"skipPackageJson": { "skipPackageJson": {
"type": "boolean", "type": "boolean",
"description": "Do not add dependencies to `package.json`.", "default": false,
"default": false "description": "Do not add dependencies to `package.json`."
},
"js": {
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript"
} }
}, },
"required": [], "required": [],

View File

@ -7,11 +7,25 @@
"cli": "nx", "cli": "nx",
"title": "Init nrwl/js", "title": "Init nrwl/js",
"description": "Init generator placeholder for nrwl/js.", "description": "Init generator placeholder for nrwl/js.",
"properties": {
"js": {
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript"
},
"skipFormat": {
"type": "boolean",
"aliases": ["skip-format"],
"description": "Skip formatting files.",
"default": true,
"x-priority": "internal"
}
},
"presets": [] "presets": []
}, },
"aliases": ["lib"], "aliases": ["lib"],
"x-type": "init", "x-type": "init",
"description": "Init placeholder.", "description": "Initialize a TS/JS workspace.",
"hidden": true, "hidden": true,
"implementation": "/packages/js/src/generators/init/init#initGenerator.ts", "implementation": "/packages/js/src/generators/init/init#initGenerator.ts",
"path": "/packages/js/src/generators/init/schema.json", "path": "/packages/js/src/generators/init/schema.json",

View File

@ -76,12 +76,6 @@
"default": false, "default": false,
"x-priority": "internal" "x-priority": "internal"
}, },
"skipWorkspaceJson": {
"description": "Skip updating `workspace.json` with default options based on values provided to this app (e.g. `babel`, `style`).",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "none"],

View File

@ -52,6 +52,11 @@
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"hidden": true "hidden": true
},
"js": {
"type": "boolean",
"description": "Generate JavaScript story files rather than TypeScript story files.",
"default": false
} }
}, },
"presets": [] "presets": []

View File

@ -35,7 +35,8 @@
"skipPackageJson": { "skipPackageJson": {
"description": "Do not add dependencies to `package.json`.", "description": "Do not add dependencies to `package.json`.",
"type": "boolean", "type": "boolean",
"default": false "default": false,
"x-priority": "internal"
}, },
"skipBabelConfig": { "skipBabelConfig": {
"description": "Do not generate a root babel.config.json (if babel is not needed).", "description": "Do not generate a root babel.config.json (if babel is not needed).",

View File

@ -0,0 +1,228 @@
import {
checkFilesExist,
cleanupProject,
packageInstall,
readJson,
runCLI,
runCreateWorkspace,
uniq,
} from '@nrwl/e2e/utils';
describe('create-nx-workspace --preset=npm', () => {
let wsName;
beforeEach(() => {
wsName = uniq('npm');
runCreateWorkspace(wsName, {
preset: 'npm',
});
});
afterEach(() => cleanupProject());
it('should add angular application', () => {
packageInstall('@nrwl/angular', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
}, 1_000_000);
it('should add angular library', () => {
packageInstall('@nrwl/angular', wsName);
const libName = uniq('lib');
expect(() => {
runCLI(`generate @nrwl/angular:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
}, 1_000_000);
it('should add workspace library', () => {
packageInstall('@nrwl/workspace', wsName);
const libName = uniq('lib');
expect(() =>
runCLI(`generate @nrwl/workspace:library ${libName} --no-interactive`)
).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add js library', () => {
packageInstall('@nrwl/js', wsName);
const libName = uniq('lib');
expect(() =>
runCLI(`generate @nrwl/js:library ${libName} --no-interactive`)
).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add web application', () => {
packageInstall('@nrwl/web', wsName);
const appName = uniq('my-app');
expect(() =>
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`)
).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
it('should add react application', () => {
packageInstall('@nrwl/react', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/react:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
it('should add react library', () => {
packageInstall('@nrwl/react', wsName);
const libName = uniq('lib');
expect(() => {
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add next application', () => {
packageInstall('@nrwl/next', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
it('should add next library', () => {
packageInstall('@nrwl/next', wsName);
const libName = uniq('lib');
expect(() => {
runCLI(`generate @nrwl/next:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add react-native application', () => {
packageInstall('@nrwl/react-native', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/react-native:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
it('should add react-native library', () => {
packageInstall('@nrwl/react-native', wsName);
const libName = uniq('lib');
expect(() => {
runCLI(`generate @nrwl/react-native:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add node application', () => {
packageInstall('@nrwl/node', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/node:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
it('should add node library', () => {
packageInstall('@nrwl/node', wsName);
const libName = uniq('lib');
expect(() => {
runCLI(`generate @nrwl/node:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add nest application', () => {
packageInstall('@nrwl/nest', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/nest:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
it('should add nest library', () => {
packageInstall('@nrwl/nest', wsName);
const libName = uniq('lib');
expect(() => {
runCLI(`generate @nrwl/nest:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add express application', () => {
packageInstall('@nrwl/express', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(`generate @nrwl/express:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
});

View File

@ -129,6 +129,7 @@ describe('create-nx-workspace', () => {
}); });
expectNoAngularDevkit(); expectNoAngularDevkit();
checkFilesDoNotExist('tsconfig.base.json');
}); });
it('should be able to create an empty workspace with ts/js capabilities', () => { it('should be able to create an empty workspace with ts/js capabilities', () => {

View File

@ -39,6 +39,7 @@
"@nrwl/cypress": "file:../cypress", "@nrwl/cypress": "file:../cypress",
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest", "@nrwl/jest": "file:../jest",
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter", "@nrwl/linter": "file:../linter",
"@nrwl/webpack": "file:../webpack", "@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace", "@nrwl/workspace": "file:../workspace",

View File

@ -1,7 +1,6 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { joinPathFragments } from '@nrwl/devkit'; import { joinPathFragments } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { tsquery } from '@phenomnomnominal/tsquery';
export function convertToStandaloneApp(tree: Tree, options: NormalizedSchema) { export function convertToStandaloneApp(tree: Tree, options: NormalizedSchema) {
const pathToAppModule = joinPathFragments( const pathToAppModule = joinPathFragments(
@ -24,6 +23,7 @@ function updateMainEntrypoint(
) { ) {
let routerModuleSetup: string; let routerModuleSetup: string;
if (options.routing) { if (options.routing) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const appModuleContents = tree.read(pathToAppModule, 'utf-8'); const appModuleContents = tree.read(pathToAppModule, 'utf-8');
const ast = tsquery.ast(appModuleContents); const ast = tsquery.ast(appModuleContents);
@ -73,6 +73,7 @@ function updateAppComponent(tree: Tree, options: NormalizedSchema) {
); );
const appComponentContents = tree.read(pathToAppComponent, 'utf-8'); const appComponentContents = tree.read(pathToAppComponent, 'utf-8');
const { tsquery } = require('@phenomnomnominal/tsquery');
const ast = tsquery.ast(appComponentContents); const ast = tsquery.ast(appComponentContents);
const COMPONENT_DECORATOR_SELECTOR = const COMPONENT_DECORATOR_SELECTOR =
'Decorator > CallExpression:has(Identifier[name=Component]) ObjectLiteralExpression'; 'Decorator > CallExpression:has(Identifier[name=Component]) ObjectLiteralExpression';

View File

@ -1,6 +1,5 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { generateFiles, joinPathFragments } from '@nrwl/devkit'; import { generateFiles, joinPathFragments } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
export function createFiles(tree: Tree, options: NormalizedSchema) { export function createFiles(tree: Tree, options: NormalizedSchema) {

View File

@ -1,22 +1,26 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import * as ts from 'typescript';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import { import {
addImportToTestBed, addImportToTestBed,
replaceIntoToTestBed, replaceIntoToTestBed,
} from '../../../../utils/nx-devkit/ast-utils'; } from '../../../../utils/nx-devkit/ast-utils';
let tsModule: typeof import('typescript');
export function updateComponentSpec(host: Tree, options: NormalizedSchema) { export function updateComponentSpec(host: Tree, options: NormalizedSchema) {
if (!tsModule) {
tsModule = require('typescript');
}
if (options.skipTests !== true) { if (options.skipTests !== true) {
const componentSpecPath = `${options.appProjectRoot}/src/app/app.component.spec.ts`; const componentSpecPath = `${options.appProjectRoot}/src/app/app.component.spec.ts`;
const componentSpecSource = host.read(componentSpecPath, 'utf-8'); const componentSpecSource = host.read(componentSpecPath, 'utf-8');
let componentSpecSourceFile = ts.createSourceFile( let componentSpecSourceFile = tsModule.createSourceFile(
componentSpecPath, componentSpecPath,
componentSpecSource, componentSpecSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -9,13 +9,13 @@ import {
updateJson, updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { replaceAppNameWithPath } from '@nrwl/workspace/src/utils/cli-config-utils'; import { replaceAppNameWithPath } from '@nrwl/workspace/src/utils/cli-config-utils';
import { getRelativePathToRootTsConfig } from '@nrwl/js';
import { E2eTestRunner, UnitTestRunner } from '../../../../utils/test-runners'; import { E2eTestRunner, UnitTestRunner } from '../../../../utils/test-runners';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { import {
createTsConfig, createTsConfig,
extractTsConfigBase, extractTsConfigBase,
} from '../../../utils/create-ts-config'; } from '../../../utils/create-ts-config';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
export function updateConfigFiles(host: Tree, options: NormalizedSchema) { export function updateConfigFiles(host: Tree, options: NormalizedSchema) {
if (!options.rootProject) { if (!options.rootProject) {

View File

@ -6,7 +6,7 @@ import {
updateJson, updateJson,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
export function updateE2eProject(tree: Tree, options: NormalizedSchema) { export function updateE2eProject(tree: Tree, options: NormalizedSchema) {

View File

@ -1,5 +1,6 @@
import { import {
formatFiles, formatFiles,
GeneratorCallback,
installPackagesTask, installPackagesTask,
moveFilesToNewDirectory, moveFilesToNewDirectory,
readNxJson, readNxJson,
@ -38,7 +39,7 @@ import { lt } from 'semver';
export async function applicationGenerator( export async function applicationGenerator(
tree: Tree, tree: Tree,
schema: Partial<Schema> schema: Partial<Schema>
) { ): Promise<GeneratorCallback> {
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree); const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
if (lt(installedAngularVersionInfo.version, '14.1.0') && schema.standalone) { if (lt(installedAngularVersionInfo.version, '14.1.0') && schema.standalone) {

View File

@ -1,7 +1,6 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { joinPathFragments } from '@nrwl/devkit'; import { joinPathFragments } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { tsquery } from '@phenomnomnominal/tsquery';
export function convertToStandaloneApp(tree: Tree, options: NormalizedSchema) { export function convertToStandaloneApp(tree: Tree, options: NormalizedSchema) {
const pathToAppModule = joinPathFragments( const pathToAppModule = joinPathFragments(
@ -22,6 +21,7 @@ function updateMainEntrypoint(
tree: Tree, tree: Tree,
pathToAppModule: string pathToAppModule: string
) { ) {
const { tsquery } = require('@phenomnomnominal/tsquery');
let routerModuleSetup: string; let routerModuleSetup: string;
if (options.routing) { if (options.routing) {
const appModuleContents = tree.read(pathToAppModule, 'utf-8'); const appModuleContents = tree.read(pathToAppModule, 'utf-8');
@ -63,6 +63,7 @@ bootstrapApplication(AppComponent${
}).catch((err) => console.error(err));`; }).catch((err) => console.error(err));`;
function updateAppComponent(tree: Tree, options: NormalizedSchema) { function updateAppComponent(tree: Tree, options: NormalizedSchema) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const pathToAppComponent = joinPathFragments( const pathToAppComponent = joinPathFragments(
options.appProjectRoot, options.appProjectRoot,
'src/app/app.component.ts' 'src/app/app.component.ts'

View File

@ -1,6 +1,6 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { generateFiles, joinPathFragments } from '@nrwl/devkit'; import { generateFiles, joinPathFragments } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
export function createFiles(tree: Tree, options: NormalizedSchema) { export function createFiles(tree: Tree, options: NormalizedSchema) {

View File

@ -2,9 +2,10 @@ import type { Tree } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import * as ts from 'typescript';
import { addImportToModule } from '../../../utils/nx-devkit/ast-utils'; import { addImportToModule } from '../../../utils/nx-devkit/ast-utils';
let tsModule: typeof import('typescript');
export function addRouterRootConfiguration( export function addRouterRootConfiguration(
host: Tree, host: Tree,
options: NormalizedSchema options: NormalizedSchema
@ -12,10 +13,13 @@ export function addRouterRootConfiguration(
const modulePath = `${options.appProjectRoot}/src/app/app.module.ts`; const modulePath = `${options.appProjectRoot}/src/app/app.module.ts`;
const moduleSource = host.read(modulePath, 'utf-8'); const moduleSource = host.read(modulePath, 'utf-8');
let sourceFile = ts.createSourceFile( if (!tsModule) {
tsModule = require('typescript');
}
let sourceFile = tsModule.createSourceFile(
modulePath, modulePath,
moduleSource, moduleSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -1,16 +1,21 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import * as ts from 'typescript';
import { replaceNodeValue } from '@nrwl/workspace/src/utilities/ast-utils'; import { replaceNodeValue } from '@nrwl/workspace/src/utilities/ast-utils';
import { getDecoratorPropertyValueNode } from '../../../utils/nx-devkit/ast-utils'; import { getDecoratorPropertyValueNode } from '../../../utils/nx-devkit/ast-utils';
import { nrwlHomeTemplate } from './nrwl-home-tpl'; import { nrwlHomeTemplate } from './nrwl-home-tpl';
export function updateAppComponentTemplate( let tsModule: typeof import('typescript');
export async function updateAppComponentTemplate(
host: Tree, host: Tree,
options: NormalizedSchema options: NormalizedSchema
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const content = options.routing const content = options.routing
? `${nrwlHomeTemplate.getSelector( ? `${nrwlHomeTemplate.getSelector(
options.prefix options.prefix
@ -35,10 +40,10 @@ export function updateAppComponentTemplate(
replaceNodeValue( replaceNodeValue(
host, host,
ts.createSourceFile( tsModule.createSourceFile(
componentPath, componentPath,
host.read(componentPath, 'utf-8'), host.read(componentPath, 'utf-8'),
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
), ),
componentPath, componentPath,

View File

@ -1,22 +1,26 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema';
import * as ts from 'typescript';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import { import {
addImportToTestBed, addImportToTestBed,
replaceIntoToTestBed, replaceIntoToTestBed,
} from '../../../utils/nx-devkit/ast-utils'; } from '../../../utils/nx-devkit/ast-utils';
import type { NormalizedSchema } from './normalized-schema';
let tsModule: typeof import('typescript');
export function updateComponentSpec(host: Tree, options: NormalizedSchema) { export function updateComponentSpec(host: Tree, options: NormalizedSchema) {
if (!tsModule) {
tsModule = require('typescript');
}
if (options.skipTests !== true) { if (options.skipTests !== true) {
const componentSpecPath = `${options.appProjectRoot}/src/app/app.component.spec.ts`; const componentSpecPath = `${options.appProjectRoot}/src/app/app.component.spec.ts`;
const componentSpecSource = host.read(componentSpecPath, 'utf-8'); const componentSpecSource = host.read(componentSpecPath, 'utf-8');
let componentSpecSourceFile = ts.createSourceFile( let componentSpecSourceFile = tsModule.createSourceFile(
componentSpecPath, componentSpecPath,
componentSpecSource, componentSpecSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -9,10 +9,10 @@ import {
updateJson, updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { replaceAppNameWithPath } from '@nrwl/workspace/src/utils/cli-config-utils'; import { replaceAppNameWithPath } from '@nrwl/workspace/src/utils/cli-config-utils';
import { getRelativePathToRootTsConfig } from '@nrwl/js';
import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners'; import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { createTsConfig } from '../../utils/create-ts-config'; import { createTsConfig } from '../../utils/create-ts-config';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
export function updateConfigFiles(host: Tree, options: NormalizedSchema) { export function updateConfigFiles(host: Tree, options: NormalizedSchema) {
updateTsConfigOptions(host, options); updateTsConfigOptions(host, options);

View File

@ -6,7 +6,7 @@ import {
updateJson, updateJson,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
export function updateE2eProject(tree: Tree, options: NormalizedSchema) { export function updateE2eProject(tree: Tree, options: NormalizedSchema) {

View File

@ -1,7 +1,6 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import * as ts from 'typescript';
import { import {
addGlobal, addGlobal,
replaceNodeValue, replaceNodeValue,
@ -9,10 +8,16 @@ import {
import { getDecoratorPropertyValueNode } from '../../../utils/nx-devkit/ast-utils'; import { getDecoratorPropertyValueNode } from '../../../utils/nx-devkit/ast-utils';
import { nrwlHomeTemplate } from './nrwl-home-tpl'; import { nrwlHomeTemplate } from './nrwl-home-tpl';
let tsModule: typeof import('typescript');
export function updateNxComponentTemplate( export function updateNxComponentTemplate(
host: Tree, host: Tree,
options: NormalizedSchema options: NormalizedSchema
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const componentPath = `${options.appProjectRoot}/src/app/nx-welcome.component.ts`; const componentPath = `${options.appProjectRoot}/src/app/nx-welcome.component.ts`;
const templateNodeValue = getDecoratorPropertyValueNode( const templateNodeValue = getDecoratorPropertyValueNode(
host, host,
@ -24,10 +29,10 @@ export function updateNxComponentTemplate(
replaceNodeValue( replaceNodeValue(
host, host,
ts.createSourceFile( tsModule.createSourceFile(
componentPath, componentPath,
host.read(componentPath, 'utf-8'), host.read(componentPath, 'utf-8'),
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
), ),
componentPath, componentPath,
@ -36,10 +41,10 @@ export function updateNxComponentTemplate(
); );
// Fixing extra comma issue `,,` // Fixing extra comma issue `,,`
let sourceFile = ts.createSourceFile( let sourceFile = tsModule.createSourceFile(
componentPath, componentPath,
host.read(componentPath, 'utf-8'), host.read(componentPath, 'utf-8'),
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
const componentFile = host const componentFile = host
@ -56,10 +61,10 @@ export function updateNxComponentTemplate(
}); });
// Add ESLint ignore to pass the lint step // Add ESLint ignore to pass the lint step
sourceFile = ts.createSourceFile( sourceFile = tsModule.createSourceFile(
componentPath, componentPath,
host.read(componentPath, 'utf-8'), host.read(componentPath, 'utf-8'),
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
addGlobal(host, sourceFile, componentPath, '/* eslint-disable */'); addGlobal(host, sourceFile, componentPath, '/* eslint-disable */');

View File

@ -8,7 +8,7 @@ export async function angularChangeStorybookTargestGenerator(
tree: Tree, tree: Tree,
schema: Schema schema: Schema
) { ) {
await ensurePackage(tree, '@nrwl/storybook', nxVersion); ensurePackage(tree, '@nrwl/storybook', nxVersion);
const { changeStorybookTargetsGenerator } = await import('@nrwl/storybook'); const { changeStorybookTargetsGenerator } = await import('@nrwl/storybook');
await changeStorybookTargetsGenerator(tree); await changeStorybookTargetsGenerator(tree);

View File

@ -1,5 +1,4 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import type { StringLiteral } from 'typescript'; import type { StringLiteral } from 'typescript';
import { getRelativeImportToFile } from '../../utils/path'; import { getRelativeImportToFile } from '../../utils/path';
@ -12,6 +11,7 @@ export function shouldExportInEntryPoint(
return false; return false;
} }
const { tsquery } = require('@phenomnomnominal/tsquery');
const moduleImportPath = getRelativeImportToFile(entryPoint, modulePath); const moduleImportPath = getRelativeImportToFile(entryPoint, modulePath);
const entryPointContent = tree.read(entryPoint, 'utf-8'); const entryPointContent = tree.read(entryPoint, 'utf-8');
const entryPointAst = tsquery.ast(entryPointContent); const entryPointAst = tsquery.ast(entryPointContent);

View File

@ -1,10 +1,10 @@
import type { SourceFile } from 'typescript'; import type { SourceFile } from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
export function checkOutputNameMatchesProjectName( export function checkOutputNameMatchesProjectName(
ast: SourceFile, ast: SourceFile,
projectName: string projectName: string
) { ) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const OUTPUT_SELECTOR = const OUTPUT_SELECTOR =
'PropertyAssignment:has(Identifier[name=output]) > ObjectLiteralExpression:has(PropertyAssignment:has(Identifier[name=uniqueName]))'; 'PropertyAssignment:has(Identifier[name=output]) > ObjectLiteralExpression:has(PropertyAssignment:has(Identifier[name=uniqueName]))';
const UNIQUENAME_SELECTOR = const UNIQUENAME_SELECTOR =

View File

@ -1,7 +1,7 @@
import type { SourceFile, Node } from 'typescript'; import type { SourceFile, Node } from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
export function checkSharedNpmPackagesMatchExpected(ast: SourceFile) { export function checkSharedNpmPackagesMatchExpected(ast: SourceFile) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const SHARE_HELPER_SELECTOR = const SHARE_HELPER_SELECTOR =
'PropertyAssignment:has(Identifier[name=shared]) > CallExpression:has(Identifier[name=share])'; 'PropertyAssignment:has(Identifier[name=shared]) > CallExpression:has(Identifier[name=share])';
const SHARED_PACKAGE_CONFIG_SELECTOR = const SHARED_PACKAGE_CONFIG_SELECTOR =

View File

@ -1,5 +1,4 @@
import type { SourceFile } from 'typescript'; import type { SourceFile } from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
export type IsHostRemoteConfigResult = 'host' | 'remote' | 'both' | false; export type IsHostRemoteConfigResult = 'host' | 'remote' | 'both' | false;
@ -12,6 +11,7 @@ const PROPERTY_SELECTOR = 'ObjectLiteralExpression > PropertyAssignment';
export function isHostRemoteConfig(ast: SourceFile): IsHostRemoteConfigResult { export function isHostRemoteConfig(ast: SourceFile): IsHostRemoteConfigResult {
let isHost = false; let isHost = false;
let isRemote = false; let isRemote = false;
const { tsquery } = require('@phenomnomnominal/tsquery');
const remotesNodes = tsquery(ast, REMOTES_EXPRESSION_SELECTOR, { const remotesNodes = tsquery(ast, REMOTES_EXPRESSION_SELECTOR, {
visitAllChildren: true, visitAllChildren: true,
@ -33,6 +33,7 @@ export function isHostRemoteConfig(ast: SourceFile): IsHostRemoteConfigResult {
} }
export function getRemotesFromHost(ast: SourceFile) { export function getRemotesFromHost(ast: SourceFile) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const remotesObjectNodes = tsquery(ast, REMOTES_EXPRESSION_SELECTOR, { const remotesObjectNodes = tsquery(ast, REMOTES_EXPRESSION_SELECTOR, {
visitAllChildren: true, visitAllChildren: true,
}); });
@ -62,6 +63,7 @@ export function getRemotesFromHost(ast: SourceFile) {
} }
export function getExposedModulesFromRemote(ast: SourceFile) { export function getExposedModulesFromRemote(ast: SourceFile) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const exposesObjectNodes = tsquery(ast, EXPOSES_EXPRESSION_SELECTOR, { const exposesObjectNodes = tsquery(ast, EXPOSES_EXPRESSION_SELECTOR, {
visitAllChildren: true, visitAllChildren: true,
}); });

View File

@ -1,5 +1,4 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
export function parseASTOfWebpackConfig( export function parseASTOfWebpackConfig(
tree: Tree, tree: Tree,
@ -10,6 +9,7 @@ export function parseASTOfWebpackConfig(
`Cannot migrate webpack config at \`${pathToWebpackConfig}\` as it does not exist. Please ensure this file exists and that the path to the file is correct.` `Cannot migrate webpack config at \`${pathToWebpackConfig}\` as it does not exist. Please ensure this file exists and that the path to the file is correct.`
); );
} }
const { tsquery } = require('@phenomnomnominal/tsquery');
const source = tree.read(pathToWebpackConfig, 'utf-8'); const source = tree.read(pathToWebpackConfig, 'utf-8');
return tsquery.ast(source); return tsquery.ast(source);

View File

@ -10,6 +10,7 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { jestInitGenerator } from '@nrwl/jest'; import { jestInitGenerator } from '@nrwl/jest';
import { Linter } from '@nrwl/linter'; import { Linter } from '@nrwl/linter';
import { initGenerator as jsInitGenerator } from '@nrwl/js';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { backwardCompatibleVersions } from '../../../utils/backward-compatible-versions'; import { backwardCompatibleVersions } from '../../../utils/backward-compatible-versions';
import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners'; import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
@ -26,6 +27,12 @@ export async function angularInitGenerator(
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const options = normalizeOptions(rawOptions); const options = normalizeOptions(rawOptions);
setDefaults(host, options); setDefaults(host, options);
await jsInitGenerator(host, {
js: false,
skipFormat: true,
});
const tasks: GeneratorCallback[] = [];
const peerDepsToInstall = [ const peerDepsToInstall = [
'@angular-devkit/core', '@angular-devkit/core',
@ -44,18 +51,22 @@ export async function angularInitGenerator(
} }
}); });
const depsTask = !options.skipPackageJson if (!options.skipPackageJson) {
? updateDependencies(host) tasks.push(updateDependencies(host));
: () => {}; }
const unitTestTask = await addUnitTestRunner(host, options); const unitTestTask = await addUnitTestRunner(host, options);
tasks.push(unitTestTask);
const e2eTask = addE2ETestRunner(host, options); const e2eTask = addE2ETestRunner(host, options);
tasks.push(e2eTask);
addGitIgnoreEntry(host, '.angular'); addGitIgnoreEntry(host, '.angular');
if (!options.skipFormat) { if (!options.skipFormat) {
await formatFiles(host); await formatFiles(host);
} }
return runTasksInSerial(depsTask, unitTestTask, e2eTask); return runTasksInSerial(...tasks);
} }
function normalizeOptions(options: Schema): Required<Schema> { function normalizeOptions(options: Schema): Required<Schema> {

View File

@ -10,6 +10,7 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { jestInitGenerator } from '@nrwl/jest'; import { jestInitGenerator } from '@nrwl/jest';
import { Linter } from '@nrwl/linter'; import { Linter } from '@nrwl/linter';
import { initGenerator as jsInitGenerator } from '@nrwl/js';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { join } from 'path'; import { join } from 'path';
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners'; import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
@ -68,19 +69,28 @@ export async function angularInitGenerator(
const options = normalizeOptions(rawOptions); const options = normalizeOptions(rawOptions);
setDefaults(tree, options); setDefaults(tree, options);
await jsInitGenerator(tree, {
js: false,
skipFormat: true,
});
const depsTask = !options.skipPackageJson const tasks: GeneratorCallback[] = [];
? updateDependencies(tree)
: () => {}; if (!options.skipPackageJson) {
tasks.push(updateDependencies(tree));
}
const unitTestTask = await addUnitTestRunner(tree, options); const unitTestTask = await addUnitTestRunner(tree, options);
tasks.push(unitTestTask);
const e2eTask = addE2ETestRunner(tree, options); const e2eTask = addE2ETestRunner(tree, options);
tasks.push(e2eTask);
addGitIgnoreEntry(tree, '.angular'); addGitIgnoreEntry(tree, '.angular');
if (!options.skipFormat) { if (!options.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
} }
return runTasksInSerial(depsTask, unitTestTask, e2eTask); return runTasksInSerial(...tasks);
} }
function normalizeOptions(options: Schema): Required<Schema> { function normalizeOptions(options: Schema): Required<Schema> {

View File

@ -6,7 +6,6 @@ import {
offsetFromRoot, offsetFromRoot,
readProjectConfiguration, readProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils'; import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import { v14TestFile } from './v14-test-file'; import { v14TestFile } from './v14-test-file';
@ -76,6 +75,7 @@ function isUsingConfigSetInBaseKarmaConfig(tree: Tree) {
if (!tree.exists('karma.conf.js')) { if (!tree.exists('karma.conf.js')) {
return false; return false;
} }
const { tsquery } = require('@phenomnomnominal/tsquery');
const CONFIG_SET_SELECTOR = const CONFIG_SET_SELECTOR =
'PropertyAccessExpression:has(Identifier[name=config], Identifier[name=set])'; 'PropertyAccessExpression:has(Identifier[name=config], Identifier[name=set])';

View File

@ -1,5 +1,5 @@
import { Tree, updateJson } from '@nrwl/devkit'; import { Tree, updateJson } from '@nrwl/devkit';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript'; import { getRootTsConfigPathInTree } from '@nrwl/js';
import { NormalizedGeneratorOptions } from '../schema'; import { NormalizedGeneratorOptions } from '../schema';
export function addPathMapping( export function addPathMapping(

View File

@ -1,10 +1,11 @@
import { names, Tree } from '@nrwl/devkit'; import { names, Tree } from '@nrwl/devkit';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import * as ts from 'typescript';
import { addImportToModule } from '../../../utils/nx-devkit/ast-utils'; import { addImportToModule } from '../../../utils/nx-devkit/ast-utils';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { addRoute } from '../../../utils/nx-devkit/route-utils'; import { addRoute } from '../../../utils/nx-devkit/route-utils';
let tsModule: typeof import('typescript');
export function addChildren( export function addChildren(
tree: Tree, tree: Tree,
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
@ -12,16 +13,19 @@ export function addChildren(
if (!tree.exists(options.parent)) { if (!tree.exists(options.parent)) {
throw new Error(`Cannot find '${options.parent}'`); throw new Error(`Cannot find '${options.parent}'`);
} }
if (!tsModule) {
tsModule = require('typescript');
}
const routeFileSource = tree.read(options.parent, 'utf-8'); const routeFileSource = tree.read(options.parent, 'utf-8');
const constName = options.standalone const constName = options.standalone
? `${names(options.name).propertyName}Routes` ? `${names(options.name).propertyName}Routes`
: `${names(options.fileName).propertyName}Routes`; : `${names(options.fileName).propertyName}Routes`;
const importPath = options.importPath; const importPath = options.importPath;
let sourceFile = ts.createSourceFile( let sourceFile = tsModule.createSourceFile(
options.parent, options.parent,
routeFileSource, routeFileSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -1,14 +1,18 @@
import { joinPathFragments, names, Tree } from '@nrwl/devkit'; import { joinPathFragments, names, Tree } from '@nrwl/devkit';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import * as ts from 'typescript';
import { addImportToModule } from '../../../utils/nx-devkit/ast-utils'; import { addImportToModule } from '../../../utils/nx-devkit/ast-utils';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { dirname } from 'path'; import { dirname } from 'path';
let tsModule: typeof import('typescript');
export function addLazyLoadedRouterConfiguration( export function addLazyLoadedRouterConfiguration(
tree: Tree, tree: Tree,
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const constName = `${names(options.fileName).propertyName}Routes`; const constName = `${names(options.fileName).propertyName}Routes`;
tree.write( tree.write(
joinPathFragments(dirname(options.modulePath), 'lib.routes.ts'), joinPathFragments(dirname(options.modulePath), 'lib.routes.ts'),
@ -18,10 +22,10 @@ export const ${constName}: Route[] = [/* {path: '', pathMatch: 'full', component
); );
const routeFileSource = tree.read(options.modulePath, 'utf-8'); const routeFileSource = tree.read(options.modulePath, 'utf-8');
let sourceFile = ts.createSourceFile( let sourceFile = tsModule.createSourceFile(
options.modulePath, options.modulePath,
routeFileSource, routeFileSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
sourceFile = addImportToModule( sourceFile = addImportToModule(

View File

@ -1,8 +1,9 @@
import { names, Tree } from '@nrwl/devkit'; import { names, Tree } from '@nrwl/devkit';
import * as ts from 'typescript';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { addRoute } from '../../../utils/nx-devkit/route-utils'; import { addRoute } from '../../../utils/nx-devkit/route-utils';
let tsModule: typeof import('typescript');
export function addLoadChildren( export function addLoadChildren(
tree: Tree, tree: Tree,
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
@ -10,12 +11,15 @@ export function addLoadChildren(
if (!tree.exists(options.parent)) { if (!tree.exists(options.parent)) {
throw new Error(`Cannot find '${options.parent}'`); throw new Error(`Cannot find '${options.parent}'`);
} }
if (!tsModule) {
tsModule = require('typescript');
}
const moduleSource = tree.read(options.parent, 'utf-8'); const moduleSource = tree.read(options.parent, 'utf-8');
const sourceFile = ts.createSourceFile( const sourceFile = tsModule.createSourceFile(
options.parent, options.parent,
moduleSource, moduleSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -1,21 +1,25 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { joinPathFragments, names } from '@nrwl/devkit'; import { joinPathFragments, names } from '@nrwl/devkit';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import * as ts from 'typescript';
import { addImportToModule } from '../../../utils/nx-devkit/ast-utils'; import { addImportToModule } from '../../../utils/nx-devkit/ast-utils';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { dirname } from 'path'; import { dirname } from 'path';
let tsModule: typeof import('typescript');
export function addRouterConfiguration( export function addRouterConfiguration(
tree: Tree, tree: Tree,
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const constName = `${names(options.fileName).propertyName}Routes`; const constName = `${names(options.fileName).propertyName}Routes`;
const moduleSource = tree.read(options.modulePath, 'utf-8'); const moduleSource = tree.read(options.modulePath, 'utf-8');
let moduleSourceFile = ts.createSourceFile( let moduleSourceFile = tsModule.createSourceFile(
options.modulePath, options.modulePath,
moduleSource, moduleSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -8,7 +8,7 @@ import {
Tree, Tree,
updateJson, updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
import { replaceAppNameWithPath } from '@nrwl/workspace/src/utils/cli-config-utils'; import { replaceAppNameWithPath } from '@nrwl/workspace/src/utils/cli-config-utils';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { updateNgPackage } from './update-ng-package'; import { updateNgPackage } from './update-ng-package';

View File

@ -1,38 +1,12 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { joinPathFragments, updateJson } from '@nrwl/devkit'; import { updateJson } from '@nrwl/devkit';
import { import { getRelativePathToRootTsConfig } from '@nrwl/js';
getRelativePathToRootTsConfig,
getRootTsConfigPathInTree,
} from '@nrwl/workspace/src/utilities/typescript';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { import {
createTsConfig, createTsConfig,
extractTsConfigBase, extractTsConfigBase,
} from '../../utils/create-ts-config'; } from '../../utils/create-ts-config';
function updateRootConfig(
host: Tree,
options: NormalizedSchema['libraryOptions']
) {
updateJson(host, getRootTsConfigPathInTree(host), (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.ts'),
];
return json;
});
}
function updateProjectConfig( function updateProjectConfig(
host: Tree, host: Tree,
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
@ -82,7 +56,6 @@ export function updateTsConfig(
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
) { ) {
extractTsConfigBase(host); extractTsConfigBase(host);
updateRootConfig(host, options);
updateProjectConfig(host, options); updateProjectConfig(host, options);
updateProjectIvyConfig(host, options); updateProjectIvyConfig(host, options);
} }

View File

@ -1,6 +1,7 @@
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
formatFiles, formatFiles,
GeneratorCallback,
installPackagesTask, installPackagesTask,
moveFilesToNewDirectory, moveFilesToNewDirectory,
removeDependenciesFromPackageJson, removeDependenciesFromPackageJson,
@ -8,6 +9,7 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { jestProjectGenerator } from '@nrwl/jest'; import { jestProjectGenerator } from '@nrwl/jest';
import { Linter } from '@nrwl/linter'; import { Linter } from '@nrwl/linter';
import { updateRootTsConfig } from '@nrwl/js';
import { lt } from 'semver'; import { lt } from 'semver';
import init from '../../generators/init/init'; import init from '../../generators/init/init';
import { E2eTestRunner } from '../../utils/test-runners'; import { E2eTestRunner } from '../../utils/test-runners';
@ -30,7 +32,10 @@ import { updateProject } from './lib/update-project';
import { updateTsConfig } from './lib/update-tsconfig'; import { updateTsConfig } from './lib/update-tsconfig';
import { Schema } from './schema'; import { Schema } from './schema';
export async function libraryGenerator(tree: Tree, schema: Schema) { export async function libraryGenerator(
tree: Tree,
schema: Schema
): Promise<GeneratorCallback> {
// Do some validation checks // Do some validation checks
if (!schema.routing && schema.lazy) { if (!schema.routing && schema.lazy) {
throw new Error(`To use "--lazy" option, "--routing" must also be set.`); throw new Error(`To use "--lazy" option, "--routing" must also be set.`);
@ -56,7 +61,7 @@ export async function libraryGenerator(tree: Tree, schema: Schema) {
} }
const options = normalizeOptions(tree, schema); const options = normalizeOptions(tree, schema);
const { libraryOptions, componentOptions } = options; const { libraryOptions } = options;
await init(tree, { await init(tree, {
...libraryOptions, ...libraryOptions,
@ -86,6 +91,8 @@ export async function libraryGenerator(tree: Tree, schema: Schema) {
libraryOptions.projectRoot libraryOptions.projectRoot
); );
} }
const { updateProject } = await import('./lib/update-project');
await updateProject(tree, libraryOptions); await updateProject(tree, libraryOptions);
updateTsConfig(tree, libraryOptions); updateTsConfig(tree, libraryOptions);
await addUnitTestRunner(tree, libraryOptions); await addUnitTestRunner(tree, libraryOptions);
@ -122,6 +129,8 @@ export async function libraryGenerator(tree: Tree, schema: Schema) {
addBuildableLibrariesPostCssDependencies(tree); addBuildableLibrariesPostCssDependencies(tree);
} }
updateRootTsConfig(tree, { ...libraryOptions, js: false });
if (!libraryOptions.skipFormat) { if (!libraryOptions.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
} }

View File

@ -8,7 +8,7 @@ import {
offsetFromRoot, offsetFromRoot,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript'; import { getRootTsConfigPathInTree } from '@nrwl/js';
import { basename } from 'path'; import { basename } from 'path';
import type { Logger, ProjectMigrationInfo } from '../../utilities'; import type { Logger, ProjectMigrationInfo } from '../../utilities';
import { BuilderMigrator } from './builder.migrator'; import { BuilderMigrator } from './builder.migrator';

View File

@ -9,7 +9,7 @@ import {
updateJson, updateJson,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript'; import { getRootTsConfigPathInTree } from '@nrwl/js';
import { basename } from 'path'; import { basename } from 'path';
import { addBuildableLibrariesPostCssDependencies } from '../../../utils/dependencies'; import { addBuildableLibrariesPostCssDependencies } from '../../../utils/dependencies';
import type { Logger, ProjectMigrationInfo } from '../../utilities'; import type { Logger, ProjectMigrationInfo } from '../../utilities';

View File

@ -5,7 +5,7 @@ import {
updateJson, updateJson,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript'; import { getRootTsConfigPathInTree } from '@nrwl/js';
import { basename } from 'path'; import { basename } from 'path';
import type { GeneratorOptions } from '../../schema'; import type { GeneratorOptions } from '../../schema';
import type { import type {

View File

@ -22,8 +22,7 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { Linter, lintProjectGenerator } from '@nrwl/linter'; import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript'; import { getRootTsConfigPathInTree } from '@nrwl/js';
import { tsquery } from '@phenomnomnominal/tsquery';
import { basename, relative } from 'path'; import { basename, relative } from 'path';
import type { import type {
Node, Node,
@ -578,6 +577,7 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
} }
private updateCypress10ConfigFile(configFilePath: string): void { private updateCypress10ConfigFile(configFilePath: string): void {
const { tsquery } = require('@phenomnomnominal/tsquery');
this.cypressPreset = nxE2EPreset(configFilePath); this.cypressPreset = nxE2EPreset(configFilePath);
const fileContent = this.tree.read(configFilePath, 'utf-8'); const fileContent = this.tree.read(configFilePath, 'utf-8');
@ -657,6 +657,7 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
recorder: FileChangeRecorder, recorder: FileChangeRecorder,
{ ...globalConfig }: CypressCommonConfig { ...globalConfig }: CypressCommonConfig
): void { ): void {
const { tsquery } = require('@phenomnomnominal/tsquery');
const e2eConfig = {}; const e2eConfig = {};
const presetSpreadAssignment = `...nxE2EPreset(__dirname),`; const presetSpreadAssignment = `...nxE2EPreset(__dirname),`;
if (!e2eNode) { if (!e2eNode) {

View File

@ -13,7 +13,7 @@ import { Linter, lintInitGenerator } from '@nrwl/linter';
import { DEFAULT_NRWL_PRETTIER_CONFIG } from '@nrwl/workspace/src/generators/new/generate-workspace-files'; import { DEFAULT_NRWL_PRETTIER_CONFIG } from '@nrwl/workspace/src/generators/new/generate-workspace-files';
import { deduceDefaultBase } from '@nrwl/workspace/src/utilities/default-base'; import { deduceDefaultBase } from '@nrwl/workspace/src/utilities/default-base';
import { resolveUserExistingPrettierConfig } from '@nrwl/workspace/src/utilities/prettier'; import { resolveUserExistingPrettierConfig } from '@nrwl/workspace/src/utilities/prettier';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript'; import { getRootTsConfigPathInTree } from '@nrwl/js';
import { prettierVersion } from '@nrwl/workspace/src/utils/versions'; import { prettierVersion } from '@nrwl/workspace/src/utils/versions';
import { toNewFormat } from 'nx/src/adapter/angular-json'; import { toNewFormat } from 'nx/src/adapter/angular-json';
import { angularDevkitVersion, nxVersion } from '../../../utils/versions'; import { angularDevkitVersion, nxVersion } from '../../../utils/versions';

View File

@ -8,11 +8,13 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
import { tsquery } from '@phenomnomnominal/tsquery'; import { tsquery } from '@phenomnomnominal/tsquery';
import * as ts from 'typescript'; import type * as ts from 'typescript';
import { ArrayLiteralExpression } from 'typescript'; import { ArrayLiteralExpression } from 'typescript';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import { addRoute } from '../../../utils/nx-devkit/route-utils'; import { addRoute } from '../../../utils/nx-devkit/route-utils';
let tsModule: typeof import('typescript');
export function checkIsCommaNeeded(mfRemoteText: string) { export function checkIsCommaNeeded(mfRemoteText: string) {
const remoteText = mfRemoteText.replace(/\s+/g, ''); const remoteText = mfRemoteText.replace(/\s+/g, '');
return !remoteText.endsWith(',]') return !remoteText.endsWith(',]')
@ -118,6 +120,9 @@ function addLazyLoadedRouteToHostAppModule(
options: Schema, options: Schema,
hostFederationType: 'dynamic' | 'static' hostFederationType: 'dynamic' | 'static'
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const hostAppConfig = readProjectConfiguration(tree, options.host); const hostAppConfig = readProjectConfiguration(tree, options.host);
const pathToHostRootRouting = `${hostAppConfig.sourceRoot}/app/app.routes.ts`; const pathToHostRootRouting = `${hostAppConfig.sourceRoot}/app/app.routes.ts`;
@ -128,10 +133,10 @@ function addLazyLoadedRouteToHostAppModule(
const hostRootRoutingFile = tree.read(pathToHostRootRouting, 'utf-8'); const hostRootRoutingFile = tree.read(pathToHostRootRouting, 'utf-8');
let sourceFile = ts.createSourceFile( let sourceFile = tsModule.createSourceFile(
pathToHostRootRouting, pathToHostRootRouting,
hostRootRoutingFile, hostRootRoutingFile,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -1,9 +1,9 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { joinPathFragments, readProjectConfiguration } from '@nrwl/devkit'; import { joinPathFragments, readProjectConfiguration } from '@nrwl/devkit';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
import { tsquery } from '@phenomnomnominal/tsquery';
export function updateAppModule(tree: Tree, schema: Schema) { export function updateAppModule(tree: Tree, schema: Schema) {
const { tsquery } = require('@phenomnomnominal/tsquery');
// read the content of app module // read the content of app module
const projectConfig = readProjectConfiguration(tree, schema.project); const projectConfig = readProjectConfiguration(tree, schema.project);
const pathToAppModule = joinPathFragments( const pathToAppModule = joinPathFragments(

View File

@ -48,7 +48,7 @@ describe('angularStories generator: libraries', () => {
beforeEach(async () => { beforeEach(async () => {
tree = await createStorybookTestWorkspaceForLib(libName); tree = await createStorybookTestWorkspaceForLib(libName);
await ensurePackage(tree, '@nrwl/storybook', nxVersion); ensurePackage(tree, '@nrwl/storybook', nxVersion);
cypressProjectGenerator = await ( cypressProjectGenerator = await (
await import('@nrwl/storybook') await import('@nrwl/storybook')
).cypressProjectGenerator; ).cypressProjectGenerator;

View File

@ -6,7 +6,7 @@ export async function generateStorybookConfiguration(
tree: Tree, tree: Tree,
options: StorybookConfigurationOptions options: StorybookConfigurationOptions
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/storybook', nxVersion); ensurePackage(tree, '@nrwl/storybook', nxVersion);
const { configurationGenerator } = await import('@nrwl/storybook'); const { configurationGenerator } = await import('@nrwl/storybook');
return await configurationGenerator(tree, { return await configurationGenerator(tree, {
name: options.name, name: options.name,

View File

@ -1,9 +1,9 @@
import type { Tree } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit';
import { writeJson } from '@nrwl/devkit'; import { writeJson } from '@nrwl/devkit';
import { tsConfigBaseOptions } from '@nrwl/workspace/src/utils/create-ts-config'; import { tsConfigBaseOptions } from '@nrwl/js';
import { getInstalledAngularMajorVersion } from './version-utils'; import { getInstalledAngularMajorVersion } from './version-utils';
export { extractTsConfigBase } from '@nrwl/workspace/src/utils/create-ts-config'; export { extractTsConfigBase } from '@nrwl/js';
export function createTsConfig( export function createTsConfig(
host: Tree, host: Tree,

View File

@ -6,7 +6,6 @@ import {
visitNotIgnoredFiles, visitNotIgnoredFiles,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { findNodes } from 'nx/src/utils/typescript'; import { findNodes } from 'nx/src/utils/typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
import { extname } from 'path'; import { extname } from 'path';
import type { import type {
ClassDeclaration, ClassDeclaration,
@ -79,6 +78,7 @@ export function getModuleFilePaths(
} }
function hasNgModule(tree: Tree, filePath: string): boolean { function hasNgModule(tree: Tree, filePath: string): boolean {
const { tsquery } = require('@phenomnomnominal/tsquery');
const fileContent = tree.read(filePath, 'utf-8'); const fileContent = tree.read(filePath, 'utf-8');
const ast = tsquery.ast(fileContent); const ast = tsquery.ast(fileContent);
const ngModule = tsquery( const ngModule = tsquery(

View File

@ -4,7 +4,7 @@ import {
readProjectConfiguration, readProjectConfiguration,
updateJson, updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
export function updateTsConfig(tree: Tree, project: string): void { export function updateTsConfig(tree: Tree, project: string): void {
const { root } = readProjectConfiguration(tree, project); const { root } = readProjectConfiguration(tree, project);

View File

@ -1,7 +1,7 @@
import * as ts from 'typescript'; import type * as ts from 'typescript';
import { findNodes } from 'nx/src/utils/typescript'; import { findNodes } from 'nx/src/utils/typescript';
import { getSourceNodes } from '@nrwl/workspace/src/utilities/typescript/get-source-nodes'; import { getSourceNodes } from '@nrwl/workspace/src/utilities/typescript/get-source-nodes';
import * as path from 'path'; import { dirname, join } from 'path';
import { names, readProjectConfiguration, Tree } from '@nrwl/devkit'; import { names, readProjectConfiguration, Tree } from '@nrwl/devkit';
import { import {
getImport, getImport,
@ -9,7 +9,8 @@ import {
removeChange, removeChange,
replaceChange, replaceChange,
} from '@nrwl/workspace/src/utilities/ast-utils'; } from '@nrwl/workspace/src/utilities/ast-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
let tsModule: typeof import('typescript');
type DecoratorName = 'Component' | 'Directive' | 'NgModule' | 'Pipe'; type DecoratorName = 'Component' | 'Directive' | 'NgModule' | 'Pipe';
@ -17,10 +18,13 @@ function _angularImportsFromNode(
node: ts.ImportDeclaration, node: ts.ImportDeclaration,
_sourceFile: ts.SourceFile _sourceFile: ts.SourceFile
): { [name: string]: string } { ): { [name: string]: string } {
if (!tsModule) {
tsModule = require('typescript');
}
const ms = node.moduleSpecifier; const ms = node.moduleSpecifier;
let modulePath: string; let modulePath: string;
switch (ms.kind) { switch (ms.kind) {
case ts.SyntaxKind.StringLiteral: case tsModule.SyntaxKind.StringLiteral:
modulePath = (ms as ts.StringLiteral).text; modulePath = (ms as ts.StringLiteral).text;
break; break;
default: default:
@ -37,7 +41,7 @@ function _angularImportsFromNode(
return {}; return {};
} else if (node.importClause.namedBindings) { } else if (node.importClause.namedBindings) {
const nb = node.importClause.namedBindings; const nb = node.importClause.namedBindings;
if (nb.kind == ts.SyntaxKind.NamespaceImport) { if (nb.kind == tsModule.SyntaxKind.NamespaceImport) {
// This is of the form `import * as name from 'path'`. Return `name.`. // This is of the form `import * as name from 'path'`. Return `name.`.
return { return {
[`${(nb as ts.NamespaceImport).name.text}.`]: modulePath, [`${(nb as ts.NamespaceImport).name.text}.`]: modulePath,
@ -84,9 +88,12 @@ export function getDecoratorMetadata(
identifier: string, identifier: string,
module: string module: string
): ts.Node[] { ): ts.Node[] {
if (!tsModule) {
tsModule = require('typescript');
}
const angularImports: { [name: string]: string } = findNodes( const angularImports: { [name: string]: string } = findNodes(
source, source,
ts.SyntaxKind.ImportDeclaration tsModule.SyntaxKind.ImportDeclaration
) )
.map((node: ts.ImportDeclaration) => _angularImportsFromNode(node, source)) .map((node: ts.ImportDeclaration) => _angularImportsFromNode(node, source))
.reduce( .reduce(
@ -106,13 +113,14 @@ export function getDecoratorMetadata(
return getSourceNodes(source) return getSourceNodes(source)
.filter((node) => { .filter((node) => {
return ( return (
node.kind == ts.SyntaxKind.Decorator && node.kind == tsModule.SyntaxKind.Decorator &&
(node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression (node as ts.Decorator).expression.kind ==
tsModule.SyntaxKind.CallExpression
); );
}) })
.map((node) => (node as ts.Decorator).expression as ts.CallExpression) .map((node) => (node as ts.Decorator).expression as ts.CallExpression)
.filter((expr) => { .filter((expr) => {
if (expr.expression.kind == ts.SyntaxKind.Identifier) { if (expr.expression.kind == tsModule.SyntaxKind.Identifier) {
const id = expr.expression as ts.Identifier; const id = expr.expression as ts.Identifier;
return ( return (
@ -120,12 +128,12 @@ export function getDecoratorMetadata(
angularImports[id.getFullText(source)] === module angularImports[id.getFullText(source)] === module
); );
} else if ( } else if (
expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression expr.expression.kind == tsModule.SyntaxKind.PropertyAccessExpression
) { ) {
// This covers foo.NgModule when importing * as foo. // This covers foo.NgModule when importing * as foo.
const paExpr = expr.expression as ts.PropertyAccessExpression; const paExpr = expr.expression as ts.PropertyAccessExpression;
// If the left expression is not an identifier, just give up at that point. // If the left expression is not an identifier, just give up at that point.
if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) { if (paExpr.expression.kind !== tsModule.SyntaxKind.Identifier) {
return false; return false;
} }
@ -140,7 +148,7 @@ export function getDecoratorMetadata(
.filter( .filter(
(expr) => (expr) =>
expr.arguments[0] && expr.arguments[0] &&
expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression expr.arguments[0].kind == tsModule.SyntaxKind.ObjectLiteralExpression
) )
.map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression); .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);
} }
@ -160,19 +168,22 @@ function _addSymbolToDecoratorMetadata(
if (!node) { if (!node) {
return source; return source;
} }
if (!tsModule) {
tsModule = require('typescript');
}
// Get all the children property assignment of object literals. // Get all the children property assignment of object literals.
const matchingProperties: ts.ObjectLiteralElement[] = ( const matchingProperties: ts.ObjectLiteralElement[] = (
node as ts.ObjectLiteralExpression node as ts.ObjectLiteralExpression
).properties ).properties
.filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment) .filter((prop) => prop.kind == tsModule.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals // Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions). // (but not expressions).
.filter((prop: ts.PropertyAssignment) => { .filter((prop: ts.PropertyAssignment) => {
const name = prop.name; const name = prop.name;
switch (name.kind) { switch (name.kind) {
case ts.SyntaxKind.Identifier: case tsModule.SyntaxKind.Identifier:
return (name as ts.Identifier).getText(source) == metadataField; return (name as ts.Identifier).getText(source) == metadataField;
case ts.SyntaxKind.StringLiteral: case tsModule.SyntaxKind.StringLiteral:
return (name as ts.StringLiteral).text == metadataField; return (name as ts.StringLiteral).text == metadataField;
} }
@ -211,7 +222,9 @@ function _addSymbolToDecoratorMetadata(
const assignment = matchingProperties[0] as ts.PropertyAssignment; const assignment = matchingProperties[0] as ts.PropertyAssignment;
// If it's not an array, nothing we can do really. // If it's not an array, nothing we can do really.
if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) { if (
assignment.initializer.kind !== tsModule.SyntaxKind.ArrayLiteralExpression
) {
return source; return source;
} }
@ -244,7 +257,7 @@ function _addSymbolToDecoratorMetadata(
let toInsert: string; let toInsert: string;
let position = node.getEnd(); let position = node.getEnd();
if (!isArray && node.kind == ts.SyntaxKind.ObjectLiteralExpression) { if (!isArray && node.kind == tsModule.SyntaxKind.ObjectLiteralExpression) {
// We haven't found the field in the metadata declaration. Insert a new // We haven't found the field in the metadata declaration. Insert a new
// field. // field.
const expr = node as ts.ObjectLiteralExpression; const expr = node as ts.ObjectLiteralExpression;
@ -264,7 +277,10 @@ function _addSymbolToDecoratorMetadata(
toInsert = `, ${metadataField}: [${expression}]`; toInsert = `, ${metadataField}: [${expression}]`;
} }
} }
} else if (!isArray && node.kind == ts.SyntaxKind.ArrayLiteralExpression) { } else if (
!isArray &&
node.kind == tsModule.SyntaxKind.ArrayLiteralExpression
) {
// We found the field but it's empty. Insert it just before the `]`. // We found the field but it's empty. Insert it just before the `]`.
position--; position--;
toInsert = `${expression}`; toInsert = `${expression}`;
@ -398,17 +414,22 @@ export function addImportToTestBed(
specPath: string, specPath: string,
symbolName: string symbolName: string
): ts.SourceFile { ): ts.SourceFile {
if (!tsModule) {
tsModule = require('typescript');
}
const allCalls: ts.CallExpression[] = <any>( const allCalls: ts.CallExpression[] = <any>(
findNodes(source, ts.SyntaxKind.CallExpression) findNodes(source, tsModule.SyntaxKind.CallExpression)
); );
const configureTestingModuleObjectLiterals = allCalls const configureTestingModuleObjectLiterals = allCalls
.filter((c) => c.expression.kind === ts.SyntaxKind.PropertyAccessExpression) .filter(
(c) => c.expression.kind === tsModule.SyntaxKind.PropertyAccessExpression
)
.filter( .filter(
(c: any) => c.expression.name.getText(source) === 'configureTestingModule' (c: any) => c.expression.name.getText(source) === 'configureTestingModule'
) )
.map((c) => .map((c) =>
c.arguments[0].kind === ts.SyntaxKind.ObjectLiteralExpression c.arguments[0].kind === tsModule.SyntaxKind.ObjectLiteralExpression
? c.arguments[0] ? c.arguments[0]
: null : null
); );
@ -434,17 +455,22 @@ export function addDeclarationsToTestBed(
specPath: string, specPath: string,
symbolName: string[] symbolName: string[]
): ts.SourceFile { ): ts.SourceFile {
if (!tsModule) {
tsModule = require('typescript');
}
const allCalls: ts.CallExpression[] = <any>( const allCalls: ts.CallExpression[] = <any>(
findNodes(source, ts.SyntaxKind.CallExpression) findNodes(source, tsModule.SyntaxKind.CallExpression)
); );
const configureTestingModuleObjectLiterals = allCalls const configureTestingModuleObjectLiterals = allCalls
.filter((c) => c.expression.kind === ts.SyntaxKind.PropertyAccessExpression) .filter(
(c) => c.expression.kind === tsModule.SyntaxKind.PropertyAccessExpression
)
.filter( .filter(
(c: any) => c.expression.name.getText(source) === 'configureTestingModule' (c: any) => c.expression.name.getText(source) === 'configureTestingModule'
) )
.map((c) => .map((c) =>
c.arguments[0].kind === ts.SyntaxKind.ObjectLiteralExpression c.arguments[0].kind === tsModule.SyntaxKind.ObjectLiteralExpression
? c.arguments[0] ? c.arguments[0]
: null : null
); );
@ -471,17 +497,22 @@ export function replaceIntoToTestBed(
newSymbol: string, newSymbol: string,
previousSymbol: string previousSymbol: string
): ts.SourceFile { ): ts.SourceFile {
if (!tsModule) {
tsModule = require('typescript');
}
const allCalls: ts.CallExpression[] = <any>( const allCalls: ts.CallExpression[] = <any>(
findNodes(source, ts.SyntaxKind.CallExpression) findNodes(source, tsModule.SyntaxKind.CallExpression)
); );
const configureTestingModuleObjectLiterals = allCalls const configureTestingModuleObjectLiterals = allCalls
.filter((c) => c.expression.kind === ts.SyntaxKind.PropertyAccessExpression) .filter(
(c) => c.expression.kind === tsModule.SyntaxKind.PropertyAccessExpression
)
.filter( .filter(
(c: any) => c.expression.name.getText(source) === 'configureTestingModule' (c: any) => c.expression.name.getText(source) === 'configureTestingModule'
) )
.map((c) => .map((c) =>
c.arguments[0].kind === ts.SyntaxKind.ObjectLiteralExpression c.arguments[0].kind === tsModule.SyntaxKind.ObjectLiteralExpression
? c.arguments[0] ? c.arguments[0]
: null : null
); );
@ -560,6 +591,9 @@ export function addRouteToNgModule(
function getListOfRoutes( function getListOfRoutes(
source: ts.SourceFile source: ts.SourceFile
): ts.NodeArray<ts.Expression> | null { ): ts.NodeArray<ts.Expression> | null {
if (!tsModule) {
tsModule = require('typescript');
}
const imports: any = getMatchingProperty( const imports: any = getMatchingProperty(
source, source,
'imports', 'imports',
@ -567,11 +601,13 @@ function getListOfRoutes(
'@angular/core' '@angular/core'
); );
if (imports?.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression) { if (
imports?.initializer.kind === tsModule.SyntaxKind.ArrayLiteralExpression
) {
const a = imports.initializer as ts.ArrayLiteralExpression; const a = imports.initializer as ts.ArrayLiteralExpression;
for (const e of a.elements) { for (const e of a.elements) {
if (e.kind === ts.SyntaxKind.CallExpression) { if (e.kind === tsModule.SyntaxKind.CallExpression) {
const ee = e as ts.CallExpression; const ee = e as ts.CallExpression;
const text = ee.expression.getText(source); const text = ee.expression.getText(source);
if ( if (
@ -580,13 +616,13 @@ function getListOfRoutes(
ee.arguments.length > 0 ee.arguments.length > 0
) { ) {
const routes = ee.arguments[0]; const routes = ee.arguments[0];
if (routes.kind === ts.SyntaxKind.ArrayLiteralExpression) { if (routes.kind === tsModule.SyntaxKind.ArrayLiteralExpression) {
return (routes as ts.ArrayLiteralExpression).elements; return (routes as ts.ArrayLiteralExpression).elements;
} else if (routes.kind === ts.SyntaxKind.Identifier) { } else if (routes.kind === tsModule.SyntaxKind.Identifier) {
// find the array expression // find the array expression
const variableDeclarations = findNodes( const variableDeclarations = findNodes(
source, source,
ts.SyntaxKind.VariableDeclaration tsModule.SyntaxKind.VariableDeclaration
) as ts.VariableDeclaration[]; ) as ts.VariableDeclaration[];
const routesDeclaration = variableDeclarations.find((x) => { const routesDeclaration = variableDeclarations.find((x) => {
@ -611,6 +647,7 @@ export function addProviderToBootstrapApplication(
filePath: string, filePath: string,
providerToAdd: string providerToAdd: string
) { ) {
const { tsquery } = require('@phenomnomnominal/tsquery');
const PROVIDERS_ARRAY_SELECTOR = const PROVIDERS_ARRAY_SELECTOR =
'CallExpression:has(Identifier[name=bootstrapApplication]) ObjectLiteralExpression > PropertyAssignment:has(Identifier[name=providers]) > ArrayLiteralExpression'; 'CallExpression:has(Identifier[name=bootstrapApplication]) ObjectLiteralExpression > PropertyAssignment:has(Identifier[name=providers]) > ArrayLiteralExpression';
@ -711,6 +748,9 @@ export function readBootstrapInfo(
bootstrapComponentClassName: string; bootstrapComponentClassName: string;
bootstrapComponentFileName: string; bootstrapComponentFileName: string;
} { } {
if (!tsModule) {
tsModule = require('typescript');
}
const config = readProjectConfiguration(host, app); const config = readProjectConfiguration(host, app);
let mainPath; let mainPath;
@ -725,10 +765,10 @@ export function readBootstrapInfo(
} }
const mainSource = host.read(mainPath)!.toString('utf-8'); const mainSource = host.read(mainPath)!.toString('utf-8');
const main = ts.createSourceFile( const main = tsModule.createSourceFile(
mainPath, mainPath,
mainSource, mainSource,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
const moduleImports = getImport( const moduleImports = getImport(
@ -743,19 +783,16 @@ export function readBootstrapInfo(
b.endsWith('Module') b.endsWith('Module')
)[0]; )[0];
const modulePath = `${path.join( const modulePath = `${join(dirname(mainPath), moduleImport.moduleSpec)}.ts`;
path.dirname(mainPath),
moduleImport.moduleSpec
)}.ts`;
if (!host.exists(modulePath)) { if (!host.exists(modulePath)) {
throw new Error(`Cannot find '${modulePath}'`); throw new Error(`Cannot find '${modulePath}'`);
} }
const moduleSourceText = host.read(modulePath)!.toString('utf-8'); const moduleSourceText = host.read(modulePath)!.toString('utf-8');
const moduleSource = ts.createSourceFile( const moduleSource = tsModule.createSourceFile(
modulePath, modulePath,
moduleSourceText, moduleSourceText,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
@ -763,8 +800,8 @@ export function readBootstrapInfo(
moduleSource, moduleSource,
moduleClassName moduleClassName
); );
const bootstrapComponentFileName = `./${path.join( const bootstrapComponentFileName = `./${join(
path.dirname(moduleImport.moduleSpec), dirname(moduleImport.moduleSpec),
`${ `${
names( names(
bootstrapComponentClassName.substring( bootstrapComponentClassName.substring(
@ -793,11 +830,14 @@ export function getDecoratorPropertyValueNode(
property: string, property: string,
module: string module: string
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const moduleSourceText = host.read(modulePath)!.toString('utf-8'); const moduleSourceText = host.read(modulePath)!.toString('utf-8');
const moduleSource = ts.createSourceFile( const moduleSource = tsModule.createSourceFile(
modulePath, modulePath,
moduleSourceText, moduleSourceText,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
const templateNode = getMatchingProperty( const templateNode = getMatchingProperty(
@ -815,17 +855,20 @@ function getMatchingObjectLiteralElement(
source: ts.SourceFile, source: ts.SourceFile,
property: string property: string
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
return ( return (
(node as ts.ObjectLiteralExpression).properties (node as ts.ObjectLiteralExpression).properties
.filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment) .filter((prop) => prop.kind == tsModule.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals // Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions). // (but not expressions).
.filter((prop: ts.PropertyAssignment) => { .filter((prop: ts.PropertyAssignment) => {
const name = prop.name; const name = prop.name;
switch (name.kind) { switch (name.kind) {
case ts.SyntaxKind.Identifier: case tsModule.SyntaxKind.Identifier:
return (name as ts.Identifier).getText(source) === property; return (name as ts.Identifier).getText(source) === property;
case ts.SyntaxKind.StringLiteral: case tsModule.SyntaxKind.StringLiteral:
return (name as ts.StringLiteral).text === property; return (name as ts.StringLiteral).text === property;
} }
return false; return false;
@ -834,15 +877,18 @@ function getMatchingObjectLiteralElement(
} }
export function getTsSourceFile(host: Tree, path: string): ts.SourceFile { export function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
if (!tsModule) {
tsModule = require('typescript');
}
const buffer = host.read(path); const buffer = host.read(path);
if (!buffer) { if (!buffer) {
throw new Error(`Could not read TS file (${path}).`); throw new Error(`Could not read TS file (${path}).`);
} }
const content = buffer.toString(); const content = buffer.toString();
const source = ts.createSourceFile( const source = tsModule.createSourceFile(
path, path,
content, content,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -1,9 +1,9 @@
import { Tree } from '@nrwl/devkit'; import { Tree } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import * as ts from 'typescript';
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils'; import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import { addRouteToNgModule } from './ast-utils'; import { addRouteToNgModule } from './ast-utils';
let tsModule: typeof import('typescript');
export function addRoute( export function addRoute(
tree: Tree, tree: Tree,
routesFile: string, routesFile: string,
@ -17,14 +17,18 @@ export function addRoute(
`Path to parent routing declaration (${routesFile}) does not exist. Please ensure path is correct.` `Path to parent routing declaration (${routesFile}) does not exist. Please ensure path is correct.`
); );
} }
if (!tsModule) {
tsModule = require('typescript');
}
const { tsquery } = require('@phenomnomnominal/tsquery');
let routesFileContents = tree.read(routesFile, 'utf-8'); let routesFileContents = tree.read(routesFile, 'utf-8');
if (!lazy) { if (!lazy) {
let parentSourceFile = ts.createSourceFile( let parentSourceFile = tsModule.createSourceFile(
routesFile, routesFile,
routesFileContents, routesFileContents,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
@ -51,10 +55,10 @@ export function addRoute(
if (!isRoutesArray) { if (!isRoutesArray) {
if (routesFileContents.includes('@NgModule')) { if (routesFileContents.includes('@NgModule')) {
const sourceFile = ts.createSourceFile( const sourceFile = tsModule.createSourceFile(
routesFile, routesFile,
routesFileContents, routesFileContents,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
@ -91,6 +95,7 @@ export function addProviderToRoute(
); );
} }
const { tsquery } = require('@phenomnomnominal/tsquery');
let routesFileContents = tree.read(routesFile, 'utf-8'); let routesFileContents = tree.read(routesFile, 'utf-8');
const ast = tsquery.ast(routesFileContents); const ast = tsquery.ast(routesFileContents);

View File

@ -35,6 +35,7 @@
}, },
"dependencies": { "dependencies": {
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter", "@nrwl/linter": "file:../linter",
"@nrwl/workspace": "file:../workspace", "@nrwl/workspace": "file:../workspace",
"@phenomnomnominal/tsquery": "4.1.1", "@phenomnomnominal/tsquery": "4.1.1",

View File

@ -5,6 +5,7 @@ import {
extractLayoutDirectory, extractLayoutDirectory,
formatFiles, formatFiles,
generateFiles, generateFiles,
GeneratorCallback,
getProjects, getProjects,
getWorkspaceLayout, getWorkspaceLayout,
joinPathFragments, joinPathFragments,
@ -20,7 +21,10 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { Linter, lintProjectGenerator } from '@nrwl/linter'; import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import {
initGenerator as jsInitGenerator,
getRelativePathToRootTsConfig,
} from '@nrwl/js';
import { import {
globalJavaScriptOverrides, globalJavaScriptOverrides,
globalTypeScriptOverrides, globalTypeScriptOverrides,
@ -261,7 +265,11 @@ export async function addLinter(host: Tree, options: CypressProjectSchema) {
export async function cypressProjectGenerator(host: Tree, schema: Schema) { export async function cypressProjectGenerator(host: Tree, schema: Schema) {
const options = normalizeOptions(host, schema); const options = normalizeOptions(host, schema);
const tasks = []; await jsInitGenerator(host, {
js: schema.js,
skipFormat: true,
});
const tasks: GeneratorCallback[] = [];
const cypressVersion = installedCypressVersion(); const cypressVersion = installedCypressVersion();
// if there is an installed cypress version, then we don't call // if there is an installed cypress version, then we don't call
// init since we want to keep the existing version that is installed // init since we want to keep the existing version that is installed

View File

@ -8,14 +8,11 @@ import {
visitNotIgnoredFiles, visitNotIgnoredFiles,
normalizePath, normalizePath,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import { basename, dirname, extname, relative } from 'path'; import { basename, dirname, extname, relative } from 'path';
import { import type { StringLiteral } from 'typescript';
isCallExpression,
isExportDeclaration, let tsModule: typeof import('typescript');
isImportDeclaration, let tsquery: typeof import('@phenomnomnominal/tsquery').tsquery;
StringLiteral,
} from 'typescript';
const validFilesEndingsToUpdate = [ const validFilesEndingsToUpdate = [
'.js', '.js',
@ -272,6 +269,14 @@ export function updateImports(
oldImportPath: string, oldImportPath: string,
newImportPath: string newImportPath: string
) { ) {
if (!tsquery) {
tsquery = require('@phenomnomnominal/tsquery').tsquery;
}
if (!tsModule) {
tsModule = require('typescript');
}
const { isCallExpression, isExportDeclaration, isImportDeclaration } =
tsModule;
const endOfImportSelector = `StringLiteral[value=/${oldImportPath}$/]`; const endOfImportSelector = `StringLiteral[value=/${oldImportPath}$/]`;
const fileContent = tree.read(filePath, 'utf-8'); const fileContent = tree.read(filePath, 'utf-8');
const newContent = tsquery.replace( const newContent = tsquery.replace(

View File

@ -15,7 +15,6 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils';
import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl'; import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
import { cypressVersion } from '../../utils/versions';
import { import {
addConfigToTsConfig, addConfigToTsConfig,
createNewCypressConfig, createNewCypressConfig,

View File

@ -27,6 +27,7 @@
"dependencies": { "dependencies": {
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest", "@nrwl/jest": "file:../jest",
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter", "@nrwl/linter": "file:../linter",
"@nrwl/react": "file:../react", "@nrwl/react": "file:../react",
"@nrwl/workspace": "file:../workspace" "@nrwl/workspace": "file:../workspace"

View File

@ -6,7 +6,7 @@ import {
toJS, toJS,
Tree, Tree,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
import { join } from 'path'; import { join } from 'path';
import { NormalizedSchema } from './normalize-options'; import { NormalizedSchema } from './normalize-options';

View File

@ -459,27 +459,27 @@ describe('ensurePackage', () => {
it('should return successfully when package is present', async () => { it('should return successfully when package is present', async () => {
writeJson(tree, 'package.json', {}); writeJson(tree, 'package.json', {});
await expect( expect(
ensurePackage(tree, '@nrwl/devkit', '>=15.0.0', { ensurePackage(tree, '@nrwl/devkit', '>=15.0.0', {
throwOnMissing: true, throwOnMissing: true,
}) })
).resolves.toBeUndefined(); // return void ).toBeUndefined(); // return void
}); });
it('should throw when dependencies are missing', async () => { it('should throw when dependencies are missing', async () => {
writeJson(tree, 'package.json', {}); writeJson(tree, 'package.json', {});
await expect(() => expect(() =>
ensurePackage(tree, '@nrwl/does-not-exist', '>=15.0.0', { ensurePackage(tree, '@nrwl/does-not-exist', '>=15.0.0', {
throwOnMissing: true, throwOnMissing: true,
}) })
).rejects.toThrow(/-D( -W)? @nrwl\/does-not-exist@>=15.0.0/); ).toThrow(/-D( -W)? @nrwl\/does-not-exist@>=15.0.0/);
await expect(() => expect(() =>
ensurePackage(tree, '@nrwl/does-not-exist', '>=15.0.0', { ensurePackage(tree, '@nrwl/does-not-exist', '>=15.0.0', {
dev: false, dev: false,
throwOnMissing: true, throwOnMissing: true,
}) })
).rejects.toThrow('@nrwl/does-not-exist@>=15.0.0'); ).toThrow('@nrwl/does-not-exist@>=15.0.0');
}); });
}); });

View File

@ -372,7 +372,7 @@ function requiresRemovingOfPackages(
* *
* For example: * For example:
* ```typescript * ```typescript
* ensurePackage(tree, {}, { '@nrwl/jest': nxVersion }) * ensurePackage(tree, '@nrwl/jest', nxVersion)
* ``` * ```
* This will check that @nrwl/jest@<nxVersion> exists in devDependencies. * This will check that @nrwl/jest@<nxVersion> exists in devDependencies.
* If it exists then function returns, otherwise it will install the package before continuing. * If it exists then function returns, otherwise it will install the package before continuing.
@ -382,9 +382,8 @@ function requiresRemovingOfPackages(
* @param pkg the package to check (e.g. @nrwl/jest) * @param pkg the package to check (e.g. @nrwl/jest)
* @param requiredVersion the version or semver range to check (e.g. ~1.0.0, >=1.0.0 <2.0.0) * @param requiredVersion the version or semver range to check (e.g. ~1.0.0, >=1.0.0 <2.0.0)
* @param {EnsurePackageOptions} options * @param {EnsurePackageOptions} options
* @returns {Promise<void>}
*/ */
export async function ensurePackage( export function ensurePackage(
tree: Tree, tree: Tree,
pkg: string, pkg: string,
requiredVersion: string, requiredVersion: string,
@ -392,7 +391,7 @@ export async function ensurePackage(
dev?: boolean; dev?: boolean;
throwOnMissing?: boolean; throwOnMissing?: boolean;
} = {} } = {}
): Promise<void> { ): void {
// Read package and version from root package.json file. // Read package and version from root package.json file.
const dev = options.dev ?? true; const dev = options.dev ?? true;
const throwOnMissing = const throwOnMissing =

View File

@ -29,6 +29,7 @@
"@nrwl/detox": "file:../detox", "@nrwl/detox": "file:../detox",
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest", "@nrwl/jest": "file:../jest",
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter", "@nrwl/linter": "file:../linter",
"@nrwl/react": "file:../react", "@nrwl/react": "file:../react",
"@nrwl/webpack": "file:../webpack", "@nrwl/webpack": "file:../webpack",

View File

@ -2,6 +2,7 @@ import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
convertNxGenerator, convertNxGenerator,
formatFiles, formatFiles,
GeneratorCallback,
removeDependenciesFromPackageJson, removeDependenciesFromPackageJson,
Tree, Tree,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
@ -34,6 +35,7 @@ import {
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { jestInitGenerator } from '@nrwl/jest'; import { jestInitGenerator } from '@nrwl/jest';
import { detoxInitGenerator } from '@nrwl/detox'; import { detoxInitGenerator } from '@nrwl/detox';
import { initGenerator as jsInitGenerator } from '@nrwl/js';
import { addGitIgnoreEntry } from './lib/add-git-ignore-entry'; import { addGitIgnoreEntry } from './lib/add-git-ignore-entry';
import { initRootBabelConfig } from './lib/init-root-babel-config'; import { initRootBabelConfig } from './lib/init-root-babel-config';
@ -41,8 +43,12 @@ import { initRootBabelConfig } from './lib/init-root-babel-config';
export async function expoInitGenerator(host: Tree, schema: Schema) { export async function expoInitGenerator(host: Tree, schema: Schema) {
addGitIgnoreEntry(host); addGitIgnoreEntry(host);
initRootBabelConfig(host); initRootBabelConfig(host);
await jsInitGenerator(host, {
js: schema.js,
skipFormat: true,
});
const tasks = []; const tasks: GeneratorCallback[] = [];
if (!schema.skipPackageJson) { if (!schema.skipPackageJson) {
tasks.push(moveDependency(host)); tasks.push(moveDependency(host));
@ -50,7 +56,7 @@ export async function expoInitGenerator(host: Tree, schema: Schema) {
} }
if (!schema.unitTestRunner || schema.unitTestRunner === 'jest') { if (!schema.unitTestRunner || schema.unitTestRunner === 'jest') {
const jestTask = jestInitGenerator(host, {}); const jestTask = await jestInitGenerator(host, {});
tasks.push(jestTask); tasks.push(jestTask);
} }

View File

@ -3,4 +3,5 @@ export interface Schema {
skipFormat?: boolean; skipFormat?: boolean;
e2eTestRunner?: 'detox' | 'none'; e2eTestRunner?: 'detox' | 'none';
skipPackageJson?: boolean; // default is false skipPackageJson?: boolean; // default is false
js?: boolean;
} }

View File

@ -25,8 +25,13 @@
}, },
"skipPackageJson": { "skipPackageJson": {
"type": "boolean", "type": "boolean",
"description": "Do not add dependencies to `package.json`.", "default": false,
"default": false "description": "Do not add dependencies to `package.json`."
},
"js": {
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript"
} }
}, },
"required": [] "required": []

View File

@ -1,4 +1,5 @@
import { import {
getImportPath,
getWorkspaceLayout, getWorkspaceLayout,
joinPathFragments, joinPathFragments,
names, names,
@ -34,7 +35,8 @@ export function normalizeOptions(
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
: []; : [];
const importPath = options.importPath || `@${npmScope}/${projectDirectory}`; const importPath =
options.importPath || getImportPath(npmScope, projectDirectory);
const appMain = options.js ? 'src/index.js' : 'src/index.ts'; const appMain = options.js ? 'src/index.js' : 'src/index.ts';

View File

@ -14,6 +14,7 @@ import {
updateJson, updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { updateRootTsConfig } from '@nrwl/js';
import init from '../init/init'; import init from '../init/init';
import { addLinting } from '../../utils/add-linting'; import { addLinting } from '../../utils/add-linting';
@ -52,7 +53,7 @@ export async function expoLibraryGenerator(
); );
if (!options.skipTsConfig) { if (!options.skipTsConfig) {
updateBaseTsConfig(host, options); updateRootTsConfig(host, options);
} }
const jestTask = await addJest( const jestTask = await addJest(
@ -131,31 +132,6 @@ function updateTsConfig(tree: Tree, options: NormalizedSchema) {
); );
} }
function updateBaseTsConfig(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.`
);
}
const { libsDir } = getWorkspaceLayout(host);
c.paths[options.importPath] = [
maybeJs(
options,
joinPathFragments(libsDir, `${options.projectDirectory}/src/index.ts`)
),
];
return json;
});
}
function createFiles(host: Tree, options: NormalizedSchema) { function createFiles(host: Tree, options: NormalizedSchema) {
generateFiles( generateFiles(
host, host,

View File

@ -37,6 +37,7 @@
"@jest/reporters": "28.1.1", "@jest/reporters": "28.1.1",
"@jest/test-result": "28.1.1", "@jest/test-result": "28.1.1",
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/js": "file:../js",
"@phenomnomnominal/tsquery": "4.1.1", "@phenomnomnominal/tsquery": "4.1.1",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"dotenv": "~10.0.0", "dotenv": "~10.0.0",

View File

@ -19,7 +19,7 @@ describe('jest', () => {
}); });
it('should generate files with --js flag', async () => { it('should generate files with --js flag', async () => {
jestInitGenerator(tree, { js: true }); await jestInitGenerator(tree, { js: true });
expect(tree.exists('jest.config.js')).toBeTruthy(); expect(tree.exists('jest.config.js')).toBeTruthy();
expect( expect(
@ -31,7 +31,7 @@ describe('jest', () => {
}); });
it('should generate files ', async () => { it('should generate files ', async () => {
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
expect(tree.exists('jest.config.ts')).toBeTruthy(); expect(tree.exists('jest.config.ts')).toBeTruthy();
expect( expect(
@ -64,7 +64,7 @@ export default {
} }
`; `;
tree.write('jest.config.ts', expected); tree.write('jest.config.ts', expected);
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
expect(tree.read('jest.config.ts', 'utf-8')).toEqual(expected); expect(tree.read('jest.config.ts', 'utf-8')).toEqual(expected);
}); });
@ -75,7 +75,7 @@ export default {
return json; return json;
}); });
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
const productionFileSet = readJson<NxJsonConfiguration>(tree, 'nx.json') const productionFileSet = readJson<NxJsonConfiguration>(tree, 'nx.json')
.namedInputs.production; .namedInputs.production;
@ -98,7 +98,7 @@ export default {
return json; return json;
}); });
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
let nxJson: NxJsonConfiguration; let nxJson: NxJsonConfiguration;
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => { updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
@ -119,12 +119,12 @@ export default {
nxJson = json; nxJson = json;
return json; return json;
}); });
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
expect(readJson<NxJsonConfiguration>(tree, 'nx.json')).toEqual(nxJson); expect(readJson<NxJsonConfiguration>(tree, 'nx.json')).toEqual(nxJson);
}); });
it('should add dependencies', async () => { it('should add dependencies', async () => {
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies.jest).toBeDefined(); expect(packageJson.devDependencies.jest).toBeDefined();
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined(); expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
@ -133,46 +133,46 @@ export default {
expect(packageJson.devDependencies['ts-node']).toBeDefined(); expect(packageJson.devDependencies['ts-node']).toBeDefined();
}); });
it('should make js jest files', () => { it('should make js jest files', async () => {
jestInitGenerator(tree, { js: true }); await jestInitGenerator(tree, { js: true });
expect(tree.exists('jest.config.js')).toBeTruthy(); expect(tree.exists('jest.config.js')).toBeTruthy();
expect(tree.exists('jest.preset.js')).toBeTruthy(); expect(tree.exists('jest.preset.js')).toBeTruthy();
}); });
describe('Deprecated: --babelJest', () => { describe('Deprecated: --babelJest', () => {
it('should add babel dependencies', async () => { it('should add babel dependencies', async () => {
jestInitGenerator(tree, { babelJest: true }); await jestInitGenerator(tree, { babelJest: true });
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['babel-jest']).toBeDefined(); expect(packageJson.devDependencies['babel-jest']).toBeDefined();
}); });
}); });
describe('--compiler', () => { describe('--compiler', () => {
it('should support tsc compiler', () => { it('should support tsc compiler', async () => {
jestInitGenerator(tree, { compiler: 'tsc' }); await jestInitGenerator(tree, { compiler: 'tsc' });
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['ts-jest']).toBeDefined(); expect(packageJson.devDependencies['ts-jest']).toBeDefined();
}); });
it('should support babel compiler', () => { it('should support babel compiler', async () => {
jestInitGenerator(tree, { compiler: 'babel' }); await jestInitGenerator(tree, { compiler: 'babel' });
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['babel-jest']).toBeDefined(); expect(packageJson.devDependencies['babel-jest']).toBeDefined();
}); });
it('should support swc compiler', () => { it('should support swc compiler', async () => {
jestInitGenerator(tree, { compiler: 'swc' }); await jestInitGenerator(tree, { compiler: 'swc' });
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['@swc/jest']).toBeDefined(); expect(packageJson.devDependencies['@swc/jest']).toBeDefined();
}); });
}); });
describe('root project', () => { describe('root project', () => {
it('should not add a monorepo jest.config.ts to the project', () => { it('should not add a monorepo jest.config.ts to the project', async () => {
jestInitGenerator(tree, { rootProject: true }); await jestInitGenerator(tree, { rootProject: true });
expect(tree.exists('jest.config.ts')).toBeFalsy(); expect(tree.exists('jest.config.ts')).toBeFalsy();
}); });
it('should rename the project jest.config.ts to project jest config', () => { it('should rename the project jest.config.ts to project jest config', async () => {
addProjectConfiguration(tree, 'my-project', { addProjectConfiguration(tree, 'my-project', {
root: '.', root: '.',
name: 'my-project', name: 'my-project',
@ -202,7 +202,7 @@ export default {
}; };
` `
); );
jestInitGenerator(tree, { rootProject: false }); await jestInitGenerator(tree, { rootProject: false });
expect(tree.exists('jest.config.app.ts')).toBeTruthy(); expect(tree.exists('jest.config.app.ts')).toBeTruthy();
expect(tree.read('jest.config.ts', 'utf-8')) expect(tree.read('jest.config.ts', 'utf-8'))
.toEqual(`import { getJestProjects } from '@nrwl/jest'; .toEqual(`import { getJestProjects } from '@nrwl/jest';
@ -221,7 +221,7 @@ projects: getJestProjects()
`); `);
}); });
it('should work with --js', () => { it('should work with --js', async () => {
addProjectConfiguration(tree, 'my-project', { addProjectConfiguration(tree, 'my-project', {
root: '.', root: '.',
name: 'my-project', name: 'my-project',
@ -251,7 +251,7 @@ module.exports = {
}; };
` `
); );
jestInitGenerator(tree, { js: true, rootProject: false }); await jestInitGenerator(tree, { js: true, rootProject: false });
expect(tree.exists('jest.config.app.js')).toBeTruthy(); expect(tree.exists('jest.config.app.js')).toBeTruthy();
expect(tree.read('jest.config.js', 'utf-8')) expect(tree.read('jest.config.js', 'utf-8'))
.toEqual(`const { getJestProjects } = require('@nrwl/jest'); .toEqual(`const { getJestProjects } = require('@nrwl/jest');
@ -275,7 +275,7 @@ projects: getJestProjects()
}); });
it('should add the jest extension to the recommended property', async () => { it('should add the jest extension to the recommended property', async () => {
jestInitGenerator(tree, {}); await jestInitGenerator(tree, {});
const extensionsJson = readJson(tree, '.vscode/extensions.json'); const extensionsJson = readJson(tree, '.vscode/extensions.json');
expect(extensionsJson).toMatchInlineSnapshot(` expect(extensionsJson).toMatchInlineSnapshot(`
Object { Object {

View File

@ -11,6 +11,8 @@ import {
updateNxJson, updateNxJson,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { initGenerator as jsInitGenerator } from '@nrwl/js';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { findRootJestConfig } from '../../utils/config/find-root-jest-files'; import { findRootJestConfig } from '../../utils/config/find-root-jest-files';
import { import {
babelJestVersion, babelJestVersion,
@ -184,18 +186,27 @@ function updateExtensions(host: Tree) {
}); });
} }
export function jestInitGenerator(tree: Tree, schema: JestInitSchema) { export async function jestInitGenerator(
tree: Tree,
schema: JestInitSchema
): Promise<GeneratorCallback> {
const options = normalizeOptions(schema); const options = normalizeOptions(schema);
await jsInitGenerator(tree, {
js: schema.js,
skipFormat: true,
});
const tasks: GeneratorCallback[] = [];
createJestConfig(tree, options); createJestConfig(tree, options);
let installTask: GeneratorCallback = () => {};
if (!options.skipPackageJson) { if (!options.skipPackageJson) {
removeDependenciesFromPackageJson(tree, ['@nrwl/jest'], []); removeDependenciesFromPackageJson(tree, ['@nrwl/jest'], []);
installTask = updateDependencies(tree, options); const installTask = updateDependencies(tree, options);
tasks.push(installTask);
} }
updateExtensions(tree); updateExtensions(tree);
return installTask; return runTasksInSerial(...tasks);
} }
function normalizeOptions(options: JestInitSchema) { function normalizeOptions(options: JestInitSchema) {

View File

@ -4,7 +4,12 @@ import { createFiles } from './lib/create-files';
import { updateTsConfig } from './lib/update-tsconfig'; import { updateTsConfig } from './lib/update-tsconfig';
import { updateWorkspace } from './lib/update-workspace'; import { updateWorkspace } from './lib/update-workspace';
import { JestProjectSchema } from './schema'; import { JestProjectSchema } from './schema';
import { formatFiles, Tree, convertNxGenerator } from '@nrwl/devkit'; import {
formatFiles,
Tree,
convertNxGenerator,
GeneratorCallback,
} from '@nrwl/devkit';
const schemaDefaults = { const schemaDefaults = {
setupFile: 'none', setupFile: 'none',
@ -51,9 +56,9 @@ function normalizeOptions(options: JestProjectSchema) {
export async function jestProjectGenerator( export async function jestProjectGenerator(
tree: Tree, tree: Tree,
schema: JestProjectSchema schema: JestProjectSchema
) { ): Promise<GeneratorCallback> {
const options = normalizeOptions(schema); const options = normalizeOptions(schema);
const installTask = init(tree, options); const installTask = await init(tree, options);
checkForTestTarget(tree, options); checkForTestTarget(tree, options);
createFiles(tree, options); createFiles(tree, options);

View File

@ -1,9 +1,11 @@
import * as ts from 'typescript'; import type * as ts from 'typescript';
import { applyChangesToString, ChangeType, Tree } from '@nrwl/devkit'; import { applyChangesToString, ChangeType, Tree } from '@nrwl/devkit';
import { Config } from '@jest/types'; import { Config } from '@jest/types';
import { createContext, runInContext } from 'vm'; import { createContext, runInContext } from 'vm';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
let tsModule: typeof import('typescript');
function makeTextToInsert( function makeTextToInsert(
value: unknown, value: unknown,
precedingCommaNeeded: boolean precedingCommaNeeded: boolean
@ -15,8 +17,12 @@ function findPropertyAssignment(
object: ts.ObjectLiteralExpression, object: ts.ObjectLiteralExpression,
propertyName: string propertyName: string
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
return object.properties.find((prop) => { return object.properties.find((prop) => {
if (!ts.isPropertyAssignment(prop)) { if (!tsModule.isPropertyAssignment(prop)) {
return false; return false;
} }
const propNameText = prop.name.getText(); const propNameText = prop.name.getText();
@ -35,6 +41,11 @@ export function addOrUpdateProperty(
value: unknown, value: unknown,
path: string path: string
) { ) {
if (!tsModule) {
tsModule = require('typescript');
}
const { SyntaxKind } = tsModule;
const propertyName = properties.shift(); const propertyName = properties.shift();
const propertyAssignment = findPropertyAssignment(object, propertyName); const propertyAssignment = findPropertyAssignment(object, propertyName);
@ -42,10 +53,10 @@ export function addOrUpdateProperty(
if (propertyAssignment) { if (propertyAssignment) {
if ( if (
propertyAssignment.initializer.kind === ts.SyntaxKind.StringLiteral || propertyAssignment.initializer.kind === SyntaxKind.StringLiteral ||
propertyAssignment.initializer.kind === ts.SyntaxKind.NumericLiteral || propertyAssignment.initializer.kind === SyntaxKind.NumericLiteral ||
propertyAssignment.initializer.kind === ts.SyntaxKind.FalseKeyword || propertyAssignment.initializer.kind === SyntaxKind.FalseKeyword ||
propertyAssignment.initializer.kind === ts.SyntaxKind.TrueKeyword propertyAssignment.initializer.kind === SyntaxKind.TrueKeyword
) { ) {
const updatedContents = applyChangesToString(originalContents, [ const updatedContents = applyChangesToString(originalContents, [
{ {
@ -65,8 +76,7 @@ export function addOrUpdateProperty(
} }
if ( if (
propertyAssignment.initializer.kind === propertyAssignment.initializer.kind === SyntaxKind.ArrayLiteralExpression
ts.SyntaxKind.ArrayLiteralExpression
) { ) {
const arrayLiteral = const arrayLiteral =
propertyAssignment.initializer as ts.ArrayLiteralExpression; propertyAssignment.initializer as ts.ArrayLiteralExpression;
@ -106,8 +116,7 @@ export function addOrUpdateProperty(
return; return;
} }
} else if ( } else if (
propertyAssignment.initializer.kind === propertyAssignment.initializer.kind === SyntaxKind.ObjectLiteralExpression
ts.SyntaxKind.ObjectLiteralExpression
) { ) {
return addOrUpdateProperty( return addOrUpdateProperty(
tree, tree,
@ -143,6 +152,10 @@ export function removeProperty(
object: ts.ObjectLiteralExpression, object: ts.ObjectLiteralExpression,
properties: string[] properties: string[]
): ts.PropertyAssignment | null { ): ts.PropertyAssignment | null {
if (!tsModule) {
tsModule = require('typescript');
}
const propertyName = properties.shift(); const propertyName = properties.shift();
const propertyAssignment = findPropertyAssignment(object, propertyName); const propertyAssignment = findPropertyAssignment(object, propertyName);
@ -150,7 +163,7 @@ export function removeProperty(
if ( if (
properties.length > 0 && properties.length > 0 &&
propertyAssignment.initializer.kind === propertyAssignment.initializer.kind ===
ts.SyntaxKind.ObjectLiteralExpression tsModule.SyntaxKind.ObjectLiteralExpression
) { ) {
return removeProperty( return removeProperty(
propertyAssignment.initializer as ts.ObjectLiteralExpression, propertyAssignment.initializer as ts.ObjectLiteralExpression,
@ -164,20 +177,28 @@ export function removeProperty(
} }
function isModuleExport(node: ts.Statement) { function isModuleExport(node: ts.Statement) {
if (!tsModule) {
tsModule = require('typescript');
}
return ( return (
ts.isExpressionStatement(node) && tsModule.isExpressionStatement(node) &&
node.expression?.kind && node.expression?.kind &&
ts.isBinaryExpression(node.expression) && tsModule.isBinaryExpression(node.expression) &&
node.expression.left.getText() === 'module.exports' && node.expression.left.getText() === 'module.exports' &&
node.expression.operatorToken?.kind === ts.SyntaxKind.EqualsToken node.expression.operatorToken?.kind === tsModule.SyntaxKind.EqualsToken
); );
} }
function isDefaultExport(node: ts.Statement) { function isDefaultExport(node: ts.Statement) {
if (!tsModule) {
tsModule = require('typescript');
}
return ( return (
ts.isExportAssignment(node) && tsModule.isExportAssignment(node) &&
node.expression?.kind && node.expression?.kind &&
ts.isObjectLiteralExpression(node.expression) && tsModule.isObjectLiteralExpression(node.expression) &&
node.getText().startsWith('export default') node.getText().startsWith('export default')
); );
} }
@ -188,10 +209,14 @@ function isDefaultExport(node: ts.Statement) {
export function jestConfigObjectAst( export function jestConfigObjectAst(
fileContent: string fileContent: string
): ts.ObjectLiteralExpression { ): ts.ObjectLiteralExpression {
const sourceFile = ts.createSourceFile( if (!tsModule) {
tsModule = require('typescript');
}
const sourceFile = tsModule.createSourceFile(
'jest.config.ts', 'jest.config.ts',
fileContent, fileContent,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );
@ -200,7 +225,7 @@ export function jestConfigObjectAst(
); );
let ast: ts.ObjectLiteralExpression; let ast: ts.ObjectLiteralExpression;
if (ts.isExpressionStatement(exportStatement)) { if (tsModule.isExpressionStatement(exportStatement)) {
const moduleExports = exportStatement.expression as ts.BinaryExpression; const moduleExports = exportStatement.expression as ts.BinaryExpression;
if (!moduleExports) { if (!moduleExports) {
throw new Error( throw new Error(
@ -211,7 +236,7 @@ export function jestConfigObjectAst(
} }
ast = moduleExports.right as ts.ObjectLiteralExpression; ast = moduleExports.right as ts.ObjectLiteralExpression;
} else if (ts.isExportAssignment(exportStatement)) { } else if (tsModule.isExportAssignment(exportStatement)) {
const defaultExport = const defaultExport =
exportStatement.expression as ts.ObjectLiteralExpression; exportStatement.expression as ts.ObjectLiteralExpression;
@ -233,7 +258,7 @@ export function jestConfigObjectAst(
); );
} }
if (!ts.isObjectLiteralExpression(ast)) { if (!tsModule.isObjectLiteralExpression(ast)) {
throw new Error( throw new Error(
`The 'export default' or 'module.exports' expression is not an object literal.` `The 'export default' or 'module.exports' expression is not an object literal.`
); );

View File

@ -14,7 +14,7 @@
"schema": "./src/generators/init/schema.json", "schema": "./src/generators/init/schema.json",
"aliases": ["lib"], "aliases": ["lib"],
"x-type": "init", "x-type": "init",
"description": "Init placeholder.", "description": "Initialize a TS/JS workspace.",
"hidden": true "hidden": true
}, },
"convert-to-swc": { "convert-to-swc": {
@ -38,7 +38,7 @@
"schema": "./src/generators/init/schema.json", "schema": "./src/generators/init/schema.json",
"aliases": ["lib"], "aliases": ["lib"],
"x-type": "init", "x-type": "init",
"description": "Init placeholder.", "description": "Initialize a TS/JS workspace.",
"hidden": true "hidden": true
}, },
"convert-to-swc": { "convert-to-swc": {

View File

@ -40,7 +40,6 @@
"@babel/preset-typescript": "^7.15.0", "@babel/preset-typescript": "^7.15.0",
"@babel/runtime": "^7.14.8", "@babel/runtime": "^7.14.8",
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/linter": "file:../linter",
"@nrwl/workspace": "file:../workspace", "@nrwl/workspace": "file:../workspace",
"babel-plugin-const-enum": "^1.0.1", "babel-plugin-const-enum": "^1.0.1",
"babel-plugin-macros": "^2.8.0", "babel-plugin-macros": "^2.8.0",

View File

@ -2,7 +2,7 @@ import { readProjectConfiguration, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { join } from 'path'; import { join } from 'path';
import { LibraryGeneratorSchema } from '../../utils/schema'; import { LibraryGeneratorSchema } from '../../utils/schema';
import { libraryGenerator } from '../library/library'; import { libraryGenerator as jsLibraryGenerator } from '../library/library';
import { convertToSwcGenerator } from './convert-to-swc'; import { convertToSwcGenerator } from './convert-to-swc';
describe('convert to swc', () => { describe('convert to swc', () => {
@ -28,7 +28,7 @@ describe('convert to swc', () => {
}); });
it('should convert tsc to swc', async () => { it('should convert tsc to swc', async () => {
await libraryGenerator(tree, { await jsLibraryGenerator(tree, {
...defaultLibGenerationOptions, ...defaultLibGenerationOptions,
name: 'tsc-lib', name: 'tsc-lib',
buildable: true, buildable: true,

View File

@ -1,10 +1,33 @@
import { convertNxGenerator, logger } from '@nrwl/devkit'; import {
convertNxGenerator,
ensurePackage,
formatFiles,
generateFiles,
joinPathFragments,
Tree,
} from '@nrwl/devkit';
import { getRootTsConfigFileName } from '../../utils/typescript/ts-config';
import { typescriptVersion } from '../../utils/versions';
import { InitSchema } from './schema';
export async function initGenerator() { export async function initGenerator(
logger.info( host: Tree,
'This is a placeholder for @nrwl/js:init generator. If you want to create a library, use @nrwl/js:lib instead' schema: InitSchema
); ): Promise<void> {
if (!schema.js) {
ensurePackage(host, 'typescript', typescriptVersion);
}
// add tsconfig.base.json
if (!getRootTsConfigFileName()) {
generateFiles(host, joinPathFragments(__dirname, './files'), '.', {});
}
if (!schema.skipFormat) {
await formatFiles(host);
}
} }
export default initGenerator; export default initGenerator;
export const initSchematic = convertNxGenerator(initGenerator); export const initSchematic = convertNxGenerator(initGenerator);

View File

@ -0,0 +1,4 @@
export interface InitSchema {
js?: boolean;
skipFormat?: boolean;
}

View File

@ -3,5 +3,19 @@
"$id": "NxTypescriptInit", "$id": "NxTypescriptInit",
"cli": "nx", "cli": "nx",
"title": "Init nrwl/js", "title": "Init nrwl/js",
"description": "Init generator placeholder for nrwl/js." "description": "Init generator placeholder for nrwl/js.",
"properties": {
"js": {
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript"
},
"skipFormat": {
"type": "boolean",
"aliases": ["skip-format"],
"description": "Skip formatting files.",
"default": true,
"x-priority": "internal"
}
}
} }

View File

@ -19,12 +19,11 @@ import {
writeJson, writeJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getImportPath } from 'nx/src/utils/path'; import { getImportPath } from 'nx/src/utils/path';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { import {
getRelativePathToRootTsConfig, getRelativePathToRootTsConfig,
getRootTsConfigPathInTree, updateRootTsConfig,
} from '@nrwl/workspace/src/utilities/typescript'; } from '../../utils/typescript/ts-config';
import { join } from 'path'; import { join } from 'path';
import { addMinimalPublishScript } from '../../utils/minimal-publish-script'; import { addMinimalPublishScript } from '../../utils/minimal-publish-script';
import { LibraryGeneratorSchema } from '../../utils/schema'; import { LibraryGeneratorSchema } from '../../utils/schema';
@ -35,6 +34,7 @@ import {
nxVersion, nxVersion,
typesNodeVersion, typesNodeVersion,
} from '../../utils/versions'; } from '../../utils/versions';
import jsInitGenerator from '../init/init';
export async function libraryGenerator( export async function libraryGenerator(
tree: Tree, tree: Tree,
@ -54,6 +54,10 @@ export async function projectGenerator(
destinationDir: string, destinationDir: string,
filesDir: string filesDir: string
) { ) {
await jsInitGenerator(tree, {
js: schema.js,
skipFormat: true,
});
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
const options = normalizeOptions(tree, schema, destinationDir); const options = normalizeOptions(tree, schema, destinationDir);
@ -64,7 +68,7 @@ export async function projectGenerator(
tasks.push(addProjectDependencies(tree, options)); tasks.push(addProjectDependencies(tree, options));
if (options.bundler === 'vite') { if (options.bundler === 'vite') {
await ensurePackage(tree, '@nrwl/vite', nxVersion); ensurePackage(tree, '@nrwl/vite', nxVersion);
// nx-ignore-next-line // nx-ignore-next-line
const { viteConfigurationGenerator } = require('@nrwl/vite'); const { viteConfigurationGenerator } = require('@nrwl/vite');
const viteTask = await viteConfigurationGenerator(tree, { const viteTask = await viteConfigurationGenerator(tree, {
@ -77,10 +81,6 @@ export async function projectGenerator(
tasks.push(viteTask); tasks.push(viteTask);
} }
if (!schema.skipTsConfig) {
updateRootTsConfig(tree, options);
}
if (schema.bundler === 'webpack' || schema.bundler === 'rollup') { if (schema.bundler === 'webpack' || schema.bundler === 'rollup') {
ensureBabelRootConfigExists(tree); ensureBabelRootConfigExists(tree);
} }
@ -99,7 +99,7 @@ export async function projectGenerator(
options.unitTestRunner === 'vitest' && options.unitTestRunner === 'vitest' &&
options.bundler !== 'vite' // Test would have been set up already options.bundler !== 'vite' // Test would have been set up already
) { ) {
await ensurePackage(tree, '@nrwl/vite', nxVersion); ensurePackage(tree, '@nrwl/vite', nxVersion);
// nx-ignore-next-line // nx-ignore-next-line
const { vitestGenerator } = require('@nrwl/vite'); const { vitestGenerator } = require('@nrwl/vite');
const vitestTask = await vitestGenerator(tree, { const vitestTask = await vitestGenerator(tree, {
@ -110,6 +110,10 @@ export async function projectGenerator(
tasks.push(vitestTask); tasks.push(vitestTask);
} }
if (!schema.skipTsConfig) {
updateRootTsConfig(tree, options);
}
if (!options.skipFormat) { if (!options.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
} }
@ -191,10 +195,12 @@ function addProject(
} }
} }
export function addLint( export async function addLint(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
ensurePackage(tree, '@nrwl/linter', nxVersion);
const { lintProjectGenerator } = require('@nrwl/linter');
return lintProjectGenerator(tree, { return lintProjectGenerator(tree, {
project: options.name, project: options.name,
linter: options.linter, linter: options.linter,
@ -320,7 +326,7 @@ async function addJest(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/jest', nxVersion); ensurePackage(tree, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = require('@nrwl/jest'); const { jestProjectGenerator } = require('@nrwl/jest');
return await jestProjectGenerator(tree, { return await jestProjectGenerator(tree, {
...options, ...options,
@ -372,6 +378,7 @@ function normalizeOptions(
options.buildable = true; options.buildable = true;
} }
const { Linter } = require('@nrwl/linter');
if (options.config === 'npm-scripts') { if (options.config === 'npm-scripts') {
options.unitTestRunner = 'none'; options.unitTestRunner = 'none';
options.linter = Linter.None; options.linter = Linter.None;
@ -435,30 +442,6 @@ function getCaseAwareFileName(options: {
return options.pascalCaseFiles ? normalized.className : normalized.fileName; return options.pascalCaseFiles ? normalized.className : normalized.fileName;
} }
function updateRootTsConfig(host: Tree, options: NormalizedSchema) {
updateJson(host, getRootTsConfigPathInTree(host), (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;
});
}
function addProjectDependencies( function addProjectDependencies(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedSchema

View File

@ -1,10 +1,13 @@
export * from './utils/typescript/load-ts-transformers'; export * from './utils/typescript/load-ts-transformers';
export * from './utils/typescript/print-diagnostics'; export * from './utils/typescript/print-diagnostics';
export * from './utils/typescript/run-type-check'; export * from './utils/typescript/run-type-check';
export * from './utils/compiler-helper-dependency';
export * from './utils/typescript/ts-config';
export * from './utils/typescript/create-ts-config';
export * from './utils/package-json'; export * from './utils/package-json';
export * from './utils/assets'; export * from './utils/assets';
export * from './utils/package-json/update-package-json'; export * from './utils/package-json/update-package-json';
export { libraryGenerator } from './generators/library/library'; export { libraryGenerator } from './generators/library/library';
export { initGenerator } from './generators/init/init';
export { createLockFile } from 'nx/src/lock-file/lock-file'; export { createLockFile } from 'nx/src/lock-file/lock-file';
export { createPackageJson } from 'nx/src/utils/create-package-json'; export { createPackageJson } from 'nx/src/utils/create-package-json';

View File

@ -15,9 +15,9 @@ describe('Migration: adjust .swcrc', () => {
let tree: Tree; let tree: Tree;
let projectConfiguration: ProjectConfiguration; let projectConfiguration: ProjectConfiguration;
beforeEach(() => { beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'swc', name: 'swc',
buildable: true, buildable: true,
linter: 'none', linter: 'none',

View File

@ -5,8 +5,8 @@ import {
readJsonFile, readJsonFile,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { join } from 'path'; import { join } from 'path';
import { readTsConfig } from './typescript/ts-config';
import { ExecutorOptions, SwcExecutorOptions } from './schema'; import { ExecutorOptions, SwcExecutorOptions } from './schema';
import { getSwcrcPath } from './swc/get-swcrc-path'; import { getSwcrcPath } from './swc/get-swcrc-path';

View File

@ -1,5 +1,5 @@
// nx-ignore-next-line // nx-ignore-next-line
const { Linter } = require('@nrwl/linter'); const { Linter } = require('@nrwl/linter'); // use require to import to avoid circular dependency
import type { import type {
AssetGlob, AssetGlob,
FileInputOutput, FileInputOutput,

View File

@ -1,9 +1,9 @@
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import * as path from 'path'; import * as path from 'path';
import type { BuilderProgram, Diagnostic, Program } from 'typescript'; import type { BuilderProgram, Diagnostic, Program } from 'typescript';
import { codeFrameColumns } from 'nx/src/utils/code-frames'; import { codeFrameColumns } from 'nx/src/utils/code-frames';
import { highlight } from '../code-frames/highlight'; import { highlight } from '../code-frames/highlight';
import { readTsConfig } from '../../utils/typescript/ts-config';
export interface TypeCheckResult { export interface TypeCheckResult {
warnings?: string[]; warnings?: string[];

View File

@ -0,0 +1,97 @@
import {
joinPathFragments,
offsetFromRoot,
Tree,
updateJson,
workspaceRoot,
} from '@nrwl/devkit';
import { existsSync } from 'fs';
import { dirname, join } from 'path';
let tsModule: typeof import('typescript');
export function readTsConfig(tsConfigPath: string) {
if (!tsModule) {
tsModule = require('typescript');
}
const readResult = tsModule.readConfigFile(
tsConfigPath,
tsModule.sys.readFile
);
return tsModule.parseJsonConfigFileContent(
readResult.config,
tsModule.sys,
dirname(tsConfigPath)
);
}
export function getRootTsConfigPathInTree(tree: Tree): string | null {
for (const path of ['tsconfig.base.json', 'tsconfig.json']) {
if (tree.exists(path)) {
return path;
}
}
return 'tsconfig.base.json';
}
export function getRelativePathToRootTsConfig(
tree: Tree,
targetPath: string
): string {
return offsetFromRoot(targetPath) + getRootTsConfigPathInTree(tree);
}
export function getRootTsConfigFileName(): string | null {
for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) {
const tsConfigPath = join(workspaceRoot, tsConfigName);
if (existsSync(tsConfigPath)) {
return tsConfigName;
}
}
return null;
}
export function getRootTsConfigPath(): string | null {
const tsConfigFileName = getRootTsConfigFileName();
return tsConfigFileName ? join(workspaceRoot, tsConfigFileName) : null;
}
export function updateRootTsConfig(
host: Tree,
options: {
name: string;
importPath?: string;
projectRoot: string;
js?: boolean;
}
) {
if (!options.importPath) {
throw new Error(
`Unable to update ${options.name} using the import path "${options.importPath}". Make sure to specify a valid import path one.`
);
}
updateJson(host, getRootTsConfigPathInTree(host), (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;
});
}

View File

@ -6,3 +6,4 @@ export const esbuildVersion = '^0.17.5';
export const swcCliVersion = '~0.1.55'; export const swcCliVersion = '~0.1.55';
export const swcHelpersVersion = '~0.4.11'; export const swcHelpersVersion = '~0.4.11';
export const typesNodeVersion = '18.7.1'; export const typesNodeVersion = '18.7.1';
export const typescriptVersion = '~4.8.2';

View File

@ -34,6 +34,7 @@
}, },
"dependencies": { "dependencies": {
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/js": "file:../js",
"@phenomnomnominal/tsquery": "4.1.1", "@phenomnomnominal/tsquery": "4.1.1",
"tmp": "~0.2.1", "tmp": "~0.2.1",
"tslib": "^2.3.0" "tslib": "^2.3.0"

View File

@ -13,7 +13,7 @@ import {
updateJson, updateJson,
updateNxJson, updateNxJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/js';
import { join } from 'path'; import { join } from 'path';
import { workspaceLintPluginDir } from '../../utils/workspace-lint-rules'; import { workspaceLintPluginDir } from '../../utils/workspace-lint-rules';
import { swcCoreVersion, swcNodeVersion } from 'nx/src/utils/versions'; import { swcCoreVersion, swcNodeVersion } from 'nx/src/utils/versions';
@ -24,7 +24,7 @@ export const WORKSPACE_RULES_PROJECT_NAME = 'eslint-rules';
export const WORKSPACE_PLUGIN_DIR = 'tools/eslint-rules'; export const WORKSPACE_PLUGIN_DIR = 'tools/eslint-rules';
export async function lintWorkspaceRulesProjectGenerator(tree: Tree) { export async function lintWorkspaceRulesProjectGenerator(tree: Tree) {
await ensurePackage(tree, '@nrwl/jest/', nxVersion); ensurePackage(tree, '@nrwl/jest/', nxVersion);
const { addPropertyToJestConfig, jestProjectGenerator } = await import( const { addPropertyToJestConfig, jestProjectGenerator } = await import(
'@nrwl/jest' '@nrwl/jest'
); );

View File

@ -10,7 +10,7 @@ import { nxVersion } from '../../utils/versions';
export default async function eslint8Updates(tree: Tree) { export default async function eslint8Updates(tree: Tree) {
try { try {
await ensurePackage(tree, '@nrwl/jest/', nxVersion); ensurePackage(tree, '@nrwl/jest/', nxVersion);
const { addPropertyToJestConfig } = await import('@nrwl/jest'); const { addPropertyToJestConfig } = await import('@nrwl/jest');
const existingJestConfigPath = normalizePath( const existingJestConfigPath = normalizePath(
'tools/eslint-rules/jest.config.js' 'tools/eslint-rules/jest.config.js'

View File

@ -3,19 +3,23 @@ import {
addGlobal, addGlobal,
removeChange, removeChange,
} from '@nrwl/workspace/src/utilities/ast-utils'; } from '@nrwl/workspace/src/utilities/ast-utils';
import * as ts from 'typescript';
import type { NormalizedOptions } from '../schema'; import type { NormalizedOptions } from '../schema';
let tsModule: typeof import('typescript');
export function addExportsToBarrelFile( export function addExportsToBarrelFile(
tree: Tree, tree: Tree,
options: NormalizedOptions options: NormalizedOptions
): void { ): void {
if (!tsModule) {
tsModule = require('typescript');
}
const indexPath = `${options.projectRoot}/src/index.ts`; const indexPath = `${options.projectRoot}/src/index.ts`;
const indexContent = tree.read(indexPath, 'utf-8'); const indexContent = tree.read(indexPath, 'utf-8');
let sourceFile = ts.createSourceFile( let sourceFile = tsModule.createSourceFile(
indexPath, indexPath,
indexContent, indexContent,
ts.ScriptTarget.Latest, tsModule.ScriptTarget.Latest,
true true
); );

View File

@ -1,7 +1,6 @@
import type { GeneratorCallback, Tree } from '@nrwl/devkit'; import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { convertNxGenerator, formatFiles } from '@nrwl/devkit'; import { convertNxGenerator, formatFiles } from '@nrwl/devkit';
import { libraryGenerator as jsLibraryGenerator } from '@nrwl/js'; import { libraryGenerator as jsLibraryGenerator } from '@nrwl/js';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { addDependencies } from '../init/lib'; import { addDependencies } from '../init/lib';
import { import {
addExportsToBarrelFile, addExportsToBarrelFile,
@ -19,10 +18,7 @@ export async function libraryGenerator(
rawOptions: LibraryGeneratorOptions rawOptions: LibraryGeneratorOptions
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const options = normalizeOptions(tree, rawOptions); const options = normalizeOptions(tree, rawOptions);
const jsLibraryTask = await jsLibraryGenerator( await jsLibraryGenerator(tree, toJsLibraryGeneratorOptions(options));
tree,
toJsLibraryGeneratorOptions(options)
);
const installDepsTask = addDependencies(tree); const installDepsTask = addDependencies(tree);
deleteFiles(tree, options); deleteFiles(tree, options);
createFiles(tree, options); createFiles(tree, options);
@ -34,7 +30,7 @@ export async function libraryGenerator(
await formatFiles(tree); await formatFiles(tree);
} }
return runTasksInSerial(jsLibraryTask, installDepsTask); return installDepsTask;
} }
export default libraryGenerator; export default libraryGenerator;

View File

@ -6,7 +6,10 @@ import {
Tree, Tree,
updateJson, updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { extendReactEslintJson, extraEslintDependencies } from '@nrwl/react'; import {
extendReactEslintJson,
extraEslintDependencies,
} from '@nrwl/react/src/utils/lint';
import { NormalizedSchema } from './normalize-options'; import { NormalizedSchema } from './normalize-options';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';

View File

@ -1,11 +1,12 @@
import { join } from 'path'; import { join } from 'path';
import { generateFiles, names, toJS, Tree } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/js';
import { NormalizedSchema } from './normalize-options'; import { NormalizedSchema } from './normalize-options';
import { import {
createAppJsx, createAppJsx,
createStyleRules, createStyleRules,
} from './create-application-files.helpers'; } from './create-application-files.helpers';
import { generateFiles, names, toJS, Tree } from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
export function createApplicationFiles(host: Tree, options: NormalizedSchema) { export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
const templateVariables = { const templateVariables = {

Some files were not shown because too many files have changed in this diff Show More