feat(misc): remove migrations for v14 and under (#19552)

This commit is contained in:
Jason Jean 2023-10-11 10:46:17 -04:00 committed by GitHub
parent 89ad0b1dfb
commit 47e9fc8ed6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
245 changed files with 295 additions and 19209 deletions

View File

@ -315,7 +315,7 @@
"@markdoc/markdoc": "0.2.2",
"@monaco-editor/react": "^4.4.6",
"@napi-rs/canvas": "^0.1.19",
"@swc/helpers": "0.5.0",
"@swc/helpers": "~0.5.2",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",

View File

@ -1,23 +1,5 @@
{
"generators": {
"remove-typescript-plugin": {
"cli": "nx",
"version": "12.8.0-beta.0",
"description": "Remove Typescript Preprocessor Plugin",
"factory": "./src/migrations/update-12-8-0/remove-typescript-plugin"
},
"update-cypress-configs-preset": {
"cli": "nx",
"version": "14.6.1-beta.0",
"description": "Change Cypress e2e and component testing presets to use __filename instead of __dirname and include a devServerTarget for component testing.",
"factory": "./src/migrations/update-14-6-1/update-cypress-configs-presets"
},
"update-cypress-if-v10": {
"cli": "nx",
"version": "14.7.0-beta.0",
"description": "Update Cypress if using v10 to support latest component testing features",
"factory": "./src/migrations/update-14-7-0/update-cypress-version-if-10"
},
"add-cypress-inputs": {
"cli": "nx",
"version": "15.0.0-beta.0",

View File

@ -1,80 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { addProjectConfiguration, Tree, writeJson } from '@nx/devkit';
import removeTypescriptPlugin from './remove-typescript-plugin';
describe('remove typescript plugin', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'proj', {
root: 'proj',
targets: {
cypress: {
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'proj/cypress.json',
},
},
},
});
writeJson(tree, 'proj/cypress.json', {
pluginsFile: './plugins.js',
});
tree.write(
'proj/plugins.js',
`
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
const { preprocessTypescript } = require('@nx/cypress/plugins/preprocessor');
module.exports = (on, config) => {
// \`on\` is used to hook into various events Cypress emits
// \`config\` is the resolved Cypress config
// Preprocess Typescript
on('file:preprocessor', preprocessTypescript(config));
};
`
);
});
it('should remove the plugin', async () => {
await removeTypescriptPlugin(tree);
expect(tree.read('proj/plugins.js', 'utf-8')).not.toContain(
'preprocessTypescript(config)'
);
});
it('should not remove the plugin if they have a custom webpack config', async () => {
tree.write(
'proj/plugins.js',
tree
.read('proj/plugins.js', 'utf-8')
.replace(
'preprocessTypescript(config)',
'preprocessTypescript(config, webpackFunction)'
)
);
await removeTypescriptPlugin(tree);
expect(tree.read('proj/plugins.js', 'utf-8')).toContain(
'preprocessTypescript(config, webpackFunction)'
);
});
});

View File

@ -1,125 +0,0 @@
import {
applyChangesToString,
ChangeType,
formatFiles,
getProjects,
ProjectConfiguration,
readJson,
StringDeletion,
TargetConfiguration,
Tree,
} from '@nx/devkit';
import {
createSourceFile,
isCallExpression,
isExpressionStatement,
isIdentifier,
Node,
ScriptTarget,
} from 'typescript';
import { dirname, join } from 'path';
import { installedCypressVersion } from '../../utils/cypress-version';
export default async function removeTypescriptPlugin(tree: Tree) {
const cypressVersion = installedCypressVersion();
if (cypressVersion < 7) {
console.warn(
`Cypress v${cypressVersion} is installed. This migration was skipped. Please rerun this migration after updating to Cypress 7.`
);
return;
}
for (const [_, proj] of getProjects(tree)) {
const cypressTargets = getCypressTargets(proj);
if (cypressTargets.length <= 0) {
continue;
}
for (const target of cypressTargets) {
const cypressConfigs = getCypressConfigs(target);
for (const config of cypressConfigs) {
const cypressConfig = readJson(tree, config);
if (cypressConfig.pluginsFile) {
let pluginPath = join(dirname(config), cypressConfig.pluginsFile);
if (!tree.exists(pluginPath)) {
pluginPath = ['.js', '.ts']
.map((ext) => pluginPath + ext)
.find((path) => tree.exists(path));
}
removePreprocessor(tree, pluginPath);
}
}
}
}
await formatFiles(tree);
}
function removePreprocessor(tree: Tree, pluginPath: string) {
const pluginContents = tree.read(pluginPath, 'utf-8');
const sourceFile = createSourceFile(
pluginPath,
pluginContents,
ScriptTarget.ESNext,
true
);
const deletions: StringDeletion[] = [];
const callback = (node: Node) => {
// Look for the invocation of preprocessTypescript
if (
isCallExpression(node) &&
isIdentifier(node.expression) &&
node.expression.getText(sourceFile) === 'preprocessTypescript' &&
node.arguments.length < 2
) {
// Get the Statement that the function call belongs to
let n: Node = node.parent;
while (!isExpressionStatement(n) && n === sourceFile) {
n = n.parent;
}
deletions.push({
type: ChangeType.Delete,
start: n.getStart(),
length:
n.getWidth() +
(pluginContents[n.getStart() + n.getWidth()] === ';' ? 1 : 0),
});
}
};
// Call the callback for every node in the file
sourceFile.forEachChild(recurse);
function recurse(node: Node) {
callback(node);
node.forEachChild((child) => recurse(child));
}
// Remove the preprocessor from the file
tree.write(pluginPath, applyChangesToString(pluginContents, deletions));
}
function getCypressConfigs(target: TargetConfiguration): string[] {
if (!target.configurations && !target.options.cypressConfig) {
return [];
} else if (!target.configurations && target.options.cypressConfig) {
return [target.options.cypressConfig];
}
return [target.options, Object.values(target.configurations)]
.filter((options) => !!options.cypressConfig)
.map((options) => options.cypressConfig);
}
function getCypressTargets(proj: ProjectConfiguration) {
if (!proj.targets) {
return [];
}
return Object.values(proj.targets).filter(
(target) => target.executor === '@nrwl/cypress:cypress'
);
}

View File

@ -1,420 +0,0 @@
import { updateCypressConfigsPresets } from './update-cypress-configs-presets';
import { installedCypressVersion } from '../../utils/cypress-version';
import {
addProjectConfiguration,
DependencyType,
logger,
ProjectGraph,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { cypressProjectGenerator } from '../../generators/cypress-project/cypress-project';
import { libraryGenerator } from '@nx/js';
let projectGraph: ProjectGraph;
jest.mock('@nx/devkit', () => {
return {
...jest.requireActual('@nx/devkit'),
createProjectGraphAsync: jest.fn().mockImplementation(() => projectGraph),
readTargetOptions: jest.fn().mockImplementation(() => ({})),
};
});
jest.mock('../../utils/cypress-version');
describe('updateComponentTestingConfig', () => {
let tree: Tree;
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should update', async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
await setup(tree, { name: 'something' });
await updateCypressConfigsPresets(tree);
expect(
tree.read('libs/something-lib/cypress.config.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename),
});
`
);
expect(
tree.read('libs/something-lib/cypress.config-two.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename, { ctTargetName: 'ct' }),
});
`
);
expect(tree.read('apps/something-e2e/cypress.config.ts', 'utf-8'))
.toContain(`export default defineConfig({
e2e: nxE2EPreset(__filename),
});
`);
expect(tree.read('apps/something-e2e/cypress.storybook-config.ts', 'utf-8'))
.toContain(`export default defineConfig({
e2e: nxE2EStorybookPreset(__filename),
});
`);
const libProjectConfig = readProjectConfiguration(tree, 'something-lib');
expect(libProjectConfig.targets['component-test']).toEqual({
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'libs/something-lib/cypress.config.ts',
testingType: 'component',
devServerTarget: 'something-app:build',
skipServe: true,
},
});
expect(libProjectConfig.targets['ct']).toEqual({
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'libs/something-lib/cypress.config-two.ts',
testingType: 'component',
devServerTarget: 'something-app:build',
skipServe: true,
},
configurations: {
prod: {
baseUrl: 'https://example.com',
},
},
});
});
it('should list out projects when unable to update config', async () => {
const loggerSpy = jest.spyOn(logger, 'warn');
await setup(tree, { name: 'something' });
projectGraph = {
nodes: {},
dependencies: {},
};
await updateCypressConfigsPresets(tree);
expect(loggerSpy.mock.calls).toEqual([
[
'Unable to find a build target to add to the component testing target in the following projects:',
],
['- something-lib'],
[
`You can manually add the 'devServerTarget' option to the
component testing target to specify the build target to use.
The build configuration should be using @nrwl/web:webpack as the executor.
Usually this is a React app in your workspace.
Component testing will fallback to a default configuration if one isn't provided,
but might require modifications if your projects are more complex.`,
],
]);
});
it('should handle already updated config', async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
await setup(tree, { name: 'something' });
expect(async () => {
await updateCypressConfigsPresets(tree);
}).not.toThrow();
expect(tree.read('libs/something-lib/cypress.config.ts', 'utf-8'))
.toContain(`export default defineConfig({
component: nxComponentTestingPreset(__filename),
});
`);
expect(
tree.read('libs/something-lib/cypress.config-two.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename, { ctTargetName: 'ct' }),
});
`
);
expect(tree.read('apps/something-e2e/cypress.config.ts', 'utf-8'))
.toContain(`export default defineConfig({
e2e: nxE2EPreset(__filename),
});
`);
});
it('should not update if using < v10', async () => {
mockedInstalledCypressVersion.mockReturnValue(9);
await setup(tree, { name: 'something' });
await updateCypressConfigsPresets(tree);
expect(
tree.read('libs/something-lib/cypress.config.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__dirname),
});
`
);
expect(
tree.read('libs/something-lib/cypress.config-two.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__dirname),
});
`
);
expect(tree.read('apps/something-e2e/cypress.config.ts', 'utf-8'))
.toContain(`export default defineConfig({
e2e: nxE2EPreset(__dirname),
});
`);
});
it('should be idempotent', async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
await setup(tree, { name: 'something' });
await updateCypressConfigsPresets(tree);
expect(
tree.read('libs/something-lib/cypress.config.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename),
});
`
);
expect(
tree.read('libs/something-lib/cypress.config-two.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename, { ctTargetName: 'ct' }),
});
`
);
expect(tree.read('apps/something-e2e/cypress.config.ts', 'utf-8'))
.toContain(`export default defineConfig({
e2e: nxE2EPreset(__filename),
});
`);
const libProjectConfig = readProjectConfiguration(tree, 'something-lib');
expect(libProjectConfig.targets['component-test']).toEqual({
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'libs/something-lib/cypress.config.ts',
testingType: 'component',
devServerTarget: 'something-app:build',
skipServe: true,
},
});
expect(libProjectConfig.targets['ct']).toEqual({
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'libs/something-lib/cypress.config-two.ts',
testingType: 'component',
devServerTarget: 'something-app:build',
skipServe: true,
},
configurations: {
prod: {
baseUrl: 'https://example.com',
},
},
});
await updateCypressConfigsPresets(tree);
expect(
tree.read('libs/something-lib/cypress.config.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename),
});
`
);
expect(
tree.read('libs/something-lib/cypress.config-two.ts', 'utf-8')
).toContain(
`export default defineConfig({
component: nxComponentTestingPreset(__filename, { ctTargetName: 'ct' }),
});
`
);
expect(tree.read('apps/something-e2e/cypress.config.ts', 'utf-8'))
.toContain(`export default defineConfig({
e2e: nxE2EPreset(__filename),
});
`);
const libProjectConfig2 = readProjectConfiguration(tree, 'something-lib');
expect(libProjectConfig2.targets['component-test']).toEqual({
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'libs/something-lib/cypress.config.ts',
testingType: 'component',
devServerTarget: 'something-app:build',
skipServe: true,
},
});
expect(libProjectConfig2.targets['ct']).toEqual({
executor: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'libs/something-lib/cypress.config-two.ts',
testingType: 'component',
devServerTarget: 'something-app:build',
skipServe: true,
},
configurations: {
prod: {
baseUrl: 'https://example.com',
},
},
});
});
});
async function setup(tree: Tree, options: { name: string }) {
const appName = `${options.name}-app`;
const libName = `${options.name}-lib`;
const e2eName = `${options.name}-e2e`;
tree.write(
'apps/my-app/cypress.config.ts',
`import { defineConfig } from 'cypress';
import { nxComponentTestingPreset } from '@nrwl/cypress/plugins/component-testing';
export default defineConfig({
component: nxComponentTestingPreset(__dirname),
});
`
);
addProjectConfiguration(tree, appName, {
root: `apps/my-app`,
sourceRoot: `apps/${appName}/src`,
targets: {
build: {
executor: '@nrwl/web:webpack',
outputs: ['{options.outputPath}'],
options: {
compiler: 'babel',
outputPath: `dist/apps/${appName}`,
index: `apps/${appName}/src/index.html`,
baseHref: '/',
main: `apps/${appName}/src/main.tsx`,
polyfills: `apps/${appName}/src/polyfills.ts`,
tsConfig: `apps/${appName}/tsconfig.app.json`,
},
},
},
});
await cypressProjectGenerator(tree, { project: appName, name: e2eName });
const e2eProjectConfig = readProjectConfiguration(tree, e2eName);
e2eProjectConfig.targets['e2e'] = {
...e2eProjectConfig.targets['e2e'],
executor: '@nrwl/cypress:cypress',
};
e2eProjectConfig.targets['e2e'].configurations = {
...e2eProjectConfig.targets['e2e'].configurations,
sb: {
cypressConfig: `apps/${e2eName}/cypress.storybook-config.ts`,
},
};
updateProjectConfiguration(tree, e2eName, e2eProjectConfig);
tree.write(
`apps/${e2eName}/cypress.config.ts`,
`import { defineConfig } from 'cypress';
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
export default defineConfig({
e2e: nxE2EPreset(__dirname),
});
`
);
tree.write(
`apps/${e2eName}/cypress.storybook-config.ts`,
`
import { defineConfig } from 'cypress';
import { nxE2EStorybookPreset } from '@nrwl/cypress/plugins/cypress-preset';
export default defineConfig({
e2e: nxE2EStorybookPreset(__dirname),
});
`
);
// lib
await libraryGenerator(tree, { name: libName });
const libProjectConfig = readProjectConfiguration(tree, libName);
libProjectConfig.targets = {
...libProjectConfig.targets,
'component-test': {
executor: '@nrwl/cypress:cypress',
options: {
testingType: 'component',
cypressConfig: `libs/${libName}/cypress.config.ts`,
},
},
ct: {
executor: '@nrwl/cypress:cypress',
options: {
testingType: 'component',
cypressConfig: `libs/${libName}/cypress.config-two.ts`,
},
configurations: {
prod: {
baseUrl: 'https://example.com',
},
},
},
};
updateProjectConfiguration(tree, libName, libProjectConfig);
tree.write(
`libs/${libName}/cypress.config.ts`,
`import { defineConfig } from 'cypress';
import { nxComponentTestingPreset } from '@nrwl/cypress/plugins/component-testing';
export default defineConfig({
component: nxComponentTestingPreset(__dirname),
});
`
);
tree.write(
`libs/${libName}/cypress.config-two.ts`,
`import { defineConfig } from 'cypress';
import { nxComponentTestingPreset } from '@nrwl/cypress/plugins/component-testing';
export default defineConfig({
component: nxComponentTestingPreset(__dirname),
});
`
);
projectGraph = {
nodes: {
[appName]: {
name: appName,
type: 'app',
data: {
...readProjectConfiguration(tree, appName),
},
},
[e2eName]: {
name: e2eName,
type: 'e2e',
data: {
...readProjectConfiguration(tree, e2eName),
},
},
[libName]: {
name: libName,
type: 'lib',
data: {
...readProjectConfiguration(tree, libName),
},
},
} as any,
dependencies: {
[appName]: [
{ type: DependencyType.static, source: appName, target: libName },
],
[e2eName]: [
{ type: DependencyType.implicit, source: e2eName, target: libName },
],
},
};
}

View File

@ -1,170 +0,0 @@
import {
logger,
readProjectConfiguration,
stripIndents,
Tree,
updateJson,
updateProjectConfiguration,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
import * as ts from 'typescript';
import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl';
import { installedCypressVersion } from '../../utils/cypress-version';
import { findBuildConfig } from '../../utils/find-target-options';
export async function updateCypressConfigsPresets(tree: Tree) {
if (installedCypressVersion() < 10) {
return;
}
const projectsWithoutDevServerTarget = new Set<string>();
const updateTasks = [];
forEachExecutorOptions<CypressExecutorOptions>(
tree,
'@nrwl/cypress:cypress',
(options, projectName, targetName, configName) => {
if (options.cypressConfig && tree.exists(options.cypressConfig)) {
updatePreset(tree, options, targetName);
}
const projectConfig = readProjectConfiguration(tree, projectName);
const testingType =
options.testingType ||
projectConfig.targets[targetName]?.options?.testingType;
const devServerTarget =
options.devServerTarget ||
projectConfig.targets[targetName]?.options?.devServerTarget;
if (!devServerTarget && testingType === 'component') {
updateTasks.push(
addBuildTargetToConfig(
tree,
projectName,
targetName,
configName
).then((didUpdate) => {
if (!didUpdate) {
projectsWithoutDevServerTarget.add(projectName);
}
})
);
}
}
);
if (updateTasks.length > 0) {
cacheComponentTestTarget(tree);
}
await Promise.all(updateTasks);
if (projectsWithoutDevServerTarget.size > 0) {
logger.warn(
`Unable to find a build target to add to the component testing target in the following projects:`
);
logger.warn(`- ${Array.from(projectsWithoutDevServerTarget).join('\n- ')}`);
logger.warn(stripIndents`
You can manually add the 'devServerTarget' option to the
component testing target to specify the build target to use.
The build configuration should be using @nrwl/web:webpack as the executor.
Usually this is a React app in your workspace.
Component testing will fallback to a default configuration if one isn't provided,
but might require modifications if your projects are more complex.
`);
}
}
function updatePreset(
tree: Tree,
options: CypressExecutorOptions,
targetName: string | undefined
) {
let contents = tsquery.replace(
tree.read(options.cypressConfig, 'utf-8'),
'CallExpression',
(node: ts.CallExpression) => {
// technically someone could have both component and e2e in the same project.
const expression = node.expression.getText();
if (expression === 'nxE2EPreset') {
return 'nxE2EPreset(__filename)';
} else if (expression === 'nxE2EStorybookPreset') {
return 'nxE2EStorybookPreset(__filename)';
} else if (node.expression.getText() === 'nxComponentTestingPreset') {
return targetName && targetName !== 'component-test' // the default
? `nxComponentTestingPreset(__filename, { ctTargetName: '${targetName}' })`
: 'nxComponentTestingPreset(__filename)';
}
return;
}
);
tree.write(options.cypressConfig, contents);
}
async function addBuildTargetToConfig(
tree: Tree,
projectName: string,
targetName: string,
configName?: string
): Promise<boolean> {
const projectWithBuild = await findBuildConfig(tree, {
project: projectName,
validExecutorNames: new Set(['@nrwl/web:webpack']),
skipGetOptions: true,
});
// didn't find the config so can't update. consumer should collect list of them and display a warning at the end
// no reason to fail since the preset will fallback to a default config so should still keep working.
if (!projectWithBuild?.target) {
return false;
}
const projectConfig = readProjectConfiguration(tree, projectName);
// if using a custom config and the devServerTarget default args
// has a different target, then add it to the custom target config
// otherwise add it to the default options
if (
configName &&
projectWithBuild.target !==
projectConfig.targets[targetName]?.options?.devServerTarget
) {
projectConfig.targets[targetName].configurations[configName] = {
...projectConfig.targets[targetName].configurations[configName],
devServerTarget: projectWithBuild.target,
skipServe: true,
};
} else {
projectConfig.targets[targetName].options = {
...projectConfig.targets[targetName].options,
devServerTarget: projectWithBuild.target,
skipServe: true,
};
}
updateProjectConfiguration(tree, projectName, projectConfig);
return true;
}
function cacheComponentTestTarget(tree: Tree) {
updateJson(tree, 'nx.json', (json) => ({
...json,
tasksRunnerOptions: {
...json.tasksRunnerOptions,
default: {
...json.tasksRunnerOptions?.default,
options: {
...json.tasksRunnerOptions?.default?.options,
cacheableOperations: Array.from(
new Set([
...(json.tasksRunnerOptions?.default?.options
?.cacheableOperations ?? []),
'component-test',
])
),
},
},
},
}));
}
export default updateCypressConfigsPresets;

View File

@ -1,78 +0,0 @@
import { updateCypressVersionIf10 } from './update-cypress-version-if-10';
import { installedCypressVersion } from '../../utils/cypress-version';
import { readJson, Tree, updateJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
describe('Update Cypress if v10 migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'package.json',
JSON.stringify({
dependencies: {},
devDependencies: {},
})
);
});
it('should update the version if the installed version is v10', () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['cypress'] = '^10.5.0';
return json;
});
updateCypressVersionIf10(tree);
const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.devDependencies['cypress']).toBe('^10.7.0');
});
it('should not update the version < v10', () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['cypress'] = '9.0.0';
return json;
});
updateCypressVersionIf10(tree);
const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.devDependencies['cypress']).toBe('9.0.0');
});
it('should not update if the version > v10', () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['cypress'] = '11.0.0';
return json;
});
updateCypressVersionIf10(tree);
const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.devDependencies['cypress']).toBe('11.0.0');
});
it('should not update if the version is not defined', () => {
updateCypressVersionIf10(tree);
const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.devDependencies['cypress']).toBe(undefined);
});
it('should not update if v10.7.0 < version < v11', () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['cypress'] = '^10.8.0';
return json;
});
updateCypressVersionIf10(tree);
const pkgJson1 = readJson(tree, 'package.json');
expect(pkgJson1.devDependencies['cypress']).toBe('^10.8.0');
});
it('should be idempotent', () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['cypress'] = '^10.3.0';
return json;
});
updateCypressVersionIf10(tree);
const pkgJson1 = readJson(tree, 'package.json');
expect(pkgJson1.devDependencies['cypress']).toBe('^10.7.0');
updateCypressVersionIf10(tree);
const pkgJson2 = readJson(tree, 'package.json');
expect(pkgJson2.devDependencies['cypress']).toBe('^10.7.0');
});
});

View File

@ -1,46 +0,0 @@
import {
GeneratorCallback,
installPackagesTask,
readJson,
Tree,
updateJson,
} from '@nx/devkit';
// don't import from root level to prevent issue where angular isn't installed.
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
import { gte, lt } from 'semver';
export function updateCypressVersionIf10(tree: Tree): GeneratorCallback {
const installedVersion = readJson(tree, 'package.json').devDependencies?.[
'cypress'
];
if (!installedVersion) {
return;
}
const normalizedInstalledCypressVersion = checkAndCleanWithSemver(
'cypress',
installedVersion
);
// not using v10
if (
lt(normalizedInstalledCypressVersion, '10.0.0') ||
gte(normalizedInstalledCypressVersion, '11.0.0')
) {
return;
}
const ngComponentTestingVersion = '10.7.0';
if (lt(normalizedInstalledCypressVersion, ngComponentTestingVersion)) {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['cypress'] = `^${ngComponentTestingVersion}`;
return json;
});
return () => {
installPackagesTask(tree);
};
}
}
export default updateCypressVersionIf10;

View File

@ -1,23 +1,5 @@
{
"generators": {
"add-build-target-test-13-5-0": {
"version": "13.5.0-beta.0",
"cli": "nx",
"description": "add buildTarget to test-ios and test-android for detox app",
"factory": "./src/migrations/update-13-5-0/add-build-target-test-13-5-0"
},
"remove-types-detox-13-8-2": {
"version": "13.8.2-beta.0",
"cli": "nx",
"description": "remove deprecated @types/detox from package.json",
"factory": "./src/migrations/update-13-8-2/remove-types-detox-13-8-2"
},
"add-verbose-jest-config-13-10-3": {
"version": "13.10.3-beta.0",
"cli": "nx",
"description": "Update jest.config.json under detox project, add key verbsoe: true",
"factory": "./src/migrations/update-13-10-3/add-verbose-jest-config-13-10-3"
},
"update-16-0-0-add-nx-packages": {
"cli": "nx",
"version": "16.0.0-beta.1",
@ -38,152 +20,6 @@
}
},
"packageJsonUpdates": {
"12.8.0": {
"version": "12.8.0-beta.0",
"packages": {
"detox": {
"version": "18.20.2",
"alwaysAddToPackageJson": false
}
}
},
"12.10.0-beta.1": {
"version": "12.10.0-beta.1",
"packages": {
"detox": {
"version": "18.22.1",
"alwaysAddToPackageJson": false
}
}
},
"13.0.0": {
"version": "13.0.0-beta.1",
"packages": {
"detox": {
"version": "18.22.2",
"alwaysAddToPackageJson": false
}
}
},
"13.2.0": {
"version": "13.2.0-beta.0",
"packages": {
"detox": {
"version": "19.0.0",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-dom": {
"version": "5.15.0",
"alwaysAddToPackageJson": false
}
}
},
"13.5.0": {
"version": "13.5.0-beta.0",
"packages": {
"detox": {
"version": "19.4.1",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-dom": {
"version": "5.16.1",
"alwaysAddToPackageJson": false
}
}
},
"13.8.2": {
"version": "13.8.2-beta.0",
"packages": {
"detox": {
"version": "19.4.5",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-dom": {
"version": "5.16.1",
"alwaysAddToPackageJson": false
}
}
},
"13.8.6": {
"version": "13.8.6-beta.0",
"packages": {
"detox": {
"version": "19.5.1",
"alwaysAddToPackageJson": false
}
}
},
"13.9.3": {
"version": "13.9.3-beta.0",
"packages": {
"detox": {
"version": "19.5.7",
"alwaysAddToPackageJson": false
}
}
},
"13.10.3": {
"version": "13.10.3-beta.0",
"packages": {
"detox": {
"version": "19.6.5",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-dom": {
"version": "5.16.4",
"alwaysAddToPackageJson": false
}
}
},
"14.1.8": {
"version": "14.1.8-beta.0",
"packages": {
"detox": {
"version": "19.6.9",
"alwaysAddToPackageJson": false
}
}
},
"14.2.1": {
"version": "14.2.1-beta.0",
"packages": {
"detox": {
"version": "19.7.1",
"alwaysAddToPackageJson": false
}
}
},
"14.5.7": {
"version": "14.5.7-beta.0",
"packages": {
"detox": {
"version": "19.9.3",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-dom": {
"version": "5.16.5",
"alwaysAddToPackageJson": false
}
}
},
"14.6.1": {
"version": "14.6.1-beta.0",
"packages": {
"detox": {
"version": "19.10.0",
"alwaysAddToPackageJson": false
}
}
},
"14.7.4": {
"version": "14.7.4-beta.0",
"packages": {
"detox": {
"version": "19.12.1",
"alwaysAddToPackageJson": false
}
}
},
"15.0.0": {
"version": "15.0.0-beta.0",
"packages": {

View File

@ -1,37 +0,0 @@
import { addProjectConfiguration, readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-verbose-jest-config-13-10-3';
describe('Set verbose to true for jest.config.json for detox apps', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'products', {
root: 'apps/products',
sourceRoot: 'apps/products/src',
targets: {
'test-ios': {
executor: '@nrwl/detox:test',
},
},
});
});
it(`should add verbose true to jest.config.json`, async () => {
tree.write('apps/products/jest.config.json', '{}');
await update(tree);
const jestConfig = readJson(tree, 'apps/products/jest.config.json');
expect(jestConfig.verbose).toEqual(true);
});
it(`should change verbose to true in jest.config.json`, async () => {
tree.write('apps/products/jest.config.json', '{"verbose": false}');
await update(tree);
const jestConfig = readJson(tree, 'apps/products/jest.config.json');
expect(jestConfig.verbose).toEqual(true);
});
});

View File

@ -1,32 +0,0 @@
import {
Tree,
formatFiles,
getProjects,
updateJson,
ProjectConfiguration,
} from '@nx/devkit';
/**
* Update jest.config.json under detox project, add key verbsoe: true
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
projects.forEach((project) => {
if (project.targets?.['test-ios']?.executor !== '@nrwl/detox:test') return;
updateJestConfig(tree, project);
});
await formatFiles(tree);
}
function updateJestConfig(host: Tree, project: ProjectConfiguration) {
const jestConfigPath = `${project.root}/jest.config.json`;
if (!host.exists(jestConfigPath)) return;
updateJson(host, jestConfigPath, (json) => {
if (!json.verbose) {
json.verbose = true;
}
return json;
});
}

View File

@ -1,73 +0,0 @@
import { addProjectConfiguration, getProjects, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-build-target-test-13-5-0';
describe('add-e2e-targets-13-5-0', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'products-e2e', {
root: 'apps/products-e2e',
sourceRoot: 'apps/products-e2e/src',
targets: {
'test-ios': {
executor: '@nrwl/detox:test',
options: {
detoxConfiguration: 'ios.sim.debug',
},
configurations: {
production: {
detoxConfiguration: 'ios.sim.release',
},
},
},
'test-android': {
executor: '@nrwl/detox:test',
options: {
detoxConfiguration: 'android.emu.debug',
},
configurations: {
production: {
detoxConfiguration: 'android.emu.release',
},
},
},
},
});
});
it(`should update project.json with targets e2e`, async () => {
await update(tree);
getProjects(tree).forEach((project) => {
expect(project.targets['test-ios']).toEqual({
executor: '@nrwl/detox:test',
options: {
detoxConfiguration: 'ios.sim.debug',
buildTarget: 'products-e2e:build-ios',
},
configurations: {
production: {
detoxConfiguration: 'ios.sim.release',
buildTarget: 'products-e2e:build-ios:prod',
},
},
});
expect(project.targets['test-android']).toEqual({
executor: '@nrwl/detox:test',
options: {
detoxConfiguration: 'android.emu.debug',
buildTarget: 'products-e2e:build-android',
},
configurations: {
production: {
detoxConfiguration: 'android.emu.release',
buildTarget: 'products-e2e:build-android:prod',
},
},
});
});
});
});

View File

@ -1,35 +0,0 @@
import {
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
} from '@nx/devkit';
/**
* This function buildTarget to test-ios and test-android
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
for (const [name, config] of projects.entries()) {
if (config.targets?.['test-ios']?.executor === '@nrwl/detox:test') {
config.targets['test-ios'].options.buildTarget = `${name}:build-ios`;
config.targets[
'test-ios'
].configurations.production.buildTarget = `${name}:build-ios:prod`;
}
if (config.targets?.['test-android']?.executor === '@nrwl/detox:test') {
config.targets[
'test-android'
].options.buildTarget = `${name}:build-android`;
config.targets[
'test-android'
].configurations.production.buildTarget = `${name}:build-android:prod`;
}
updateProjectConfiguration(tree, name, config);
}
await formatFiles(tree);
}

View File

@ -1,35 +0,0 @@
import {
addProjectConfiguration,
readJson,
Tree,
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './remove-types-detox-13-8-2';
describe('remove-types-detox-13-8-2', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (packageJson) => {
packageJson.devDependencies['@types/detox'] = '*';
return packageJson;
});
addProjectConfiguration(tree, 'products-e2e', {
root: 'apps/products-e2e',
sourceRoot: 'apps/products-e2e/src',
targets: {
'test-ios': {
executor: '@nrwl/detox:test',
},
},
});
});
it(`should remove @types/detox from package.json`, async () => {
await update(tree);
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['@types/detox']).toBeUndefined();
});
});

View File

@ -1,26 +0,0 @@
import { getProjects, readJson, Tree, updateJson } from '@nx/devkit';
export default async function update(tree: Tree) {
const packageJson = readJson(tree, 'package.json');
if (!packageJson.devDependencies['@types/detox']) {
return;
}
const projects = getProjects(tree);
const hasDetoxProject = Array.from(projects)
.map(([_, project]) => project)
.some(
(project) =>
project.targets?.['test-ios']?.executor === '@nrwl/detox:test'
);
if (!hasDetoxProject) {
return;
}
updateJson(tree, 'package.json', (packageJson) => {
delete packageJson.devDependencies['@types/detox'];
return packageJson;
});
}

View File

@ -1,29 +1,5 @@
{
"generators": {
"add-project-root-metro-config-14-0-0": {
"version": "14.0.1-beta.0",
"cli": "nx",
"description": "Add projectRoot option in metro.config.js",
"factory": "./src/migrations/update-14-0-0/add-project-root-metro-config-14-0-0"
},
"add-eject-target-14-1-2": {
"version": "14.1.2-beta.0",
"cli": "nx",
"description": "Add target eject for expo projects in project.json",
"factory": "./src/migrations/update-14-1-2/add-eject-target-14-1-2"
},
"add-build-target-14-4-3": {
"version": "14.4.3-beta.0",
"cli": "nx",
"description": "Add target build and build-list for expo projects in project.json",
"factory": "./src/migrations/update-14-4-3/add-eas-build-target"
},
"add-update-target-14-5-1": {
"version": "14.5.1-beta.0",
"cli": "nx",
"description": "Add target update for expo projects in project.json",
"factory": "./src/migrations/update-14-5-1/add-eas-update-target"
},
"change-expo-jest-preset": {
"version": "15.0.3-beta.0",
"cli": "nx",
@ -92,391 +68,6 @@
}
},
"packageJsonUpdates": {
"13.8.6": {
"version": "13.8.6-beta.0",
"packages": {
"expo-cli": {
"version": "5.3.0",
"alwaysAddToPackageJson": false
}
}
},
"14.0.0": {
"version": "14.0.0-beta.0",
"packages": {
"expo-cli": {
"version": "5.4.0",
"alwaysAddToPackageJson": false
},
"babel-preset-expo": {
"version": "~9.0.2",
"alwaysAddToPackageJson": false,
"addToPackageJson": "devDependencies"
}
}
},
"14.0.1": {
"version": "14.0.1-beta.0",
"packages": {
"expo-cli": {
"version": "5.4.3",
"alwaysAddToPackageJson": false
}
}
},
"14.0.2": {
"version": "14.0.2-beta.0",
"packages": {
"metro-resolver": {
"version": "0.70.2",
"alwaysAddToPackageJson": false
},
"expo-dev-client": {
"version": "0.8.5",
"alwaysAddToPackageJson": false
},
"@expo/metro-config": {
"version": "0.3.16",
"alwaysAddToPackageJson": false
},
"expo-updates": {
"version": "~0.11.7",
"alwaysAddToPackageJson": false
}
}
},
"14.1.1": {
"version": "14.1.1-beta.0",
"packages": {
"expo": {
"version": "45.0.4",
"alwaysAddToPackageJson": false
},
"expo-dev-client": {
"version": "~0.9.6",
"alwaysAddToPackageJson": false
},
"expo-status-bar": {
"version": "~1.3.0",
"alwaysAddToPackageJson": false
},
"@expo/metro-config": {
"version": "0.3.17",
"alwaysAddToPackageJson": false
},
"expo-splash-screen": {
"version": "0.15.1",
"alwaysAddToPackageJson": false
},
"expo-updates": {
"version": "~0.13.1",
"alwaysAddToPackageJson": false
},
"jest-expo": {
"version": "45.0.1",
"alwaysAddToPackageJson": false
},
"expo-cli": {
"version": "5.4.6",
"alwaysAddToPackageJson": false
},
"babel-preset-expo": {
"version": "~9.1.0",
"alwaysAddToPackageJson": false
},
"react-native": {
"version": "0.68.2",
"alwaysAddToPackageJson": false
},
"@types/react-native": {
"version": "0.67.7",
"alwaysAddToPackageJson": false
},
"react-native-web": {
"version": "0.17.7",
"alwaysAddToPackageJson": false
},
"react-native-gesture-handler": {
"version": "~2.2.1",
"alwaysAddToPackageJson": false
},
"react-native-reanimated": {
"version": "~2.8.0",
"alwaysAddToPackageJson": false
},
"react-native-safe-area-context": {
"version": "4.2.4",
"alwaysAddToPackageJson": false
},
"react-native-screens": {
"version": "~3.11.1",
"alwaysAddToPackageJson": false
},
"react-native-svg": {
"version": "12.3.0",
"alwaysAddToPackageJson": false
},
"metro-resolver": {
"version": "0.70.3",
"alwaysAddToPackageJson": false
},
"@testing-library/react-native": {
"version": "9.1.0",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-native": {
"version": "4.0.5",
"alwaysAddToPackageJson": false
}
}
},
"14.1.2": {
"version": "14.1.2-beta.0",
"packages": {
"expo": {
"version": "45.0.5",
"alwaysAddToPackageJson": false
},
"expo-cli": {
"version": "5.4.9",
"alwaysAddToPackageJson": false
},
"metro-resolver": {
"version": "0.71.0",
"alwaysAddToPackageJson": false
},
"metro-babel-register": {
"version": "0.71.0",
"alwaysAddToPackageJson": false,
"addToPackageJson": "devDependencies"
},
"react-test-renderer": {
"version": "18.1.0",
"alwaysAddToPackageJson": false,
"addToPackageJson": "devDependencies"
},
"expo-updates": {
"version": "~0.13.2",
"alwaysAddToPackageJson": false
},
"@types/react-native": {
"version": "0.67.8",
"alwaysAddToPackageJson": false
}
}
},
"14.2.3": {
"version": "14.2.3-beta.0",
"packages": {
"expo-dev-client": {
"version": "~0.10.0",
"alwaysAddToPackageJson": false
},
"expo-structured-headers": {
"version": "~2.2.1",
"alwaysAddToPackageJson": false
}
}
},
"14.2.4": {
"version": "14.2.4-beta.0",
"packages": {
"expo-dev-client": {
"version": "~1.0.0",
"alwaysAddToPackageJson": false
}
}
},
"14.3.2": {
"version": "14.3.2-beta.0",
"packages": {
"expo": {
"version": "45.0.6",
"alwaysAddToPackageJson": false
},
"expo-cli": {
"version": "5.4.11",
"alwaysAddToPackageJson": false
},
"@types/react-native": {
"version": "0.68.0",
"alwaysAddToPackageJson": false
}
}
},
"14.4.3": {
"version": "14.4.3-beta.0",
"packages": {
"eas-cli": {
"version": "0.55.1",
"alwaysAddToPackageJson": false,
"addToPackageJson": "devDependencies"
},
"expo-cli": {
"version": "5.5.1",
"alwaysAddToPackageJson": false
}
}
},
"14.5.1": {
"version": "14.5.1-beta.0",
"packages": {
"expo": {
"version": "46.0.2",
"alwaysAddToPackageJson": false
},
"expo-dev-client": {
"version": "~1.1.1",
"alwaysAddToPackageJson": false
},
"expo-status-bar": {
"version": "~1.4.0",
"alwaysAddToPackageJson": false
},
"@expo/metro-config": {
"version": "0.3.21",
"alwaysAddToPackageJson": false
},
"expo-splash-screen": {
"version": "~0.16.1",
"alwaysAddToPackageJson": false
},
"expo-updates": {
"version": "~0.14.3",
"alwaysAddToPackageJson": false
},
"jest-expo": {
"version": "46.0.1",
"alwaysAddToPackageJson": false
},
"expo-cli": {
"version": "6.0.1",
"alwaysAddToPackageJson": false
},
"eas-cli": {
"version": "0.57.0",
"alwaysAddToPackageJson": false
},
"babel-preset-expo": {
"version": "~9.2.0",
"alwaysAddToPackageJson": false
},
"react-native": {
"version": "0.69.3",
"alwaysAddToPackageJson": false
},
"@types/react-native": {
"version": "0.69.5",
"alwaysAddToPackageJson": false
},
"react-native-web": {
"version": "~0.18.7",
"alwaysAddToPackageJson": false
},
"react-native-gesture-handler": {
"version": "~2.5.0",
"alwaysAddToPackageJson": false
},
"react-native-reanimated": {
"version": "~2.9.1",
"alwaysAddToPackageJson": false
},
"react-native-safe-area-context": {
"version": "4.3.1",
"alwaysAddToPackageJson": false
},
"react-native-screens": {
"version": "~3.15.0",
"alwaysAddToPackageJson": false
},
"react-native-svg": {
"version": "12.4.3",
"alwaysAddToPackageJson": false
},
"@svgr/webpack": {
"version": "^6.3.1",
"alwaysAddToPackageJson": false
},
"metro-resolver": {
"version": "0.72.0",
"alwaysAddToPackageJson": false
},
"metro-babel-register": {
"version": "0.72.0",
"alwaysAddToPackageJson": false
},
"@testing-library/react-native": {
"version": "11.0.0",
"alwaysAddToPackageJson": false
}
}
},
"14.5.2": {
"version": "14.5.2-beta.0",
"packages": {
"expo": {
"version": "46.0.9",
"alwaysAddToPackageJson": false
},
"expo-cli": {
"version": "6.0.5",
"alwaysAddToPackageJson": false
},
"eas-cli": {
"version": "1.1.1",
"alwaysAddToPackageJson": false
},
"react-native": {
"version": "0.69.4",
"alwaysAddToPackageJson": false
},
"react-native-svg": {
"version": "13.0.0",
"alwaysAddToPackageJson": false
},
"metro-resolver": {
"version": "0.72.1",
"alwaysAddToPackageJson": false
},
"@testing-library/jest-native": {
"version": "4.0.11",
"alwaysAddToPackageJson": false
},
"@expo/metro-config": {
"version": "0.3.22",
"alwaysAddToPackageJson": false
}
}
},
"14.5.3": {
"version": "14.5.3-beta.0",
"packages": {
"expo": {
"version": "46.0.10",
"alwaysAddToPackageJson": false
},
"eas-cli": {
"version": "2.1.0",
"alwaysAddToPackageJson": false
},
"react-native": {
"version": "0.69.5",
"alwaysAddToPackageJson": false
},
"@types/react-native": {
"version": "0.69.8",
"alwaysAddToPackageJson": false
},
"react-native-svg": {
"version": "13.1.0",
"alwaysAddToPackageJson": false
},
"metro-resolver": {
"version": "0.72.2",
"alwaysAddToPackageJson": false
}
}
},
"15.0.0": {
"version": "15.0.0-beta.0",
"packages": {

View File

@ -1,192 +0,0 @@
import { addProjectConfiguration, readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-project-root-metro-config-14-0-0';
describe('Add projectRoot option in metro.config.js', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'products', {
root: 'apps/products',
sourceRoot: 'apps/products/src',
targets: {
start: {
executor: '@nrwl/react-native:start',
options: {
port: 8081,
},
},
},
});
});
it(`should udpate metro.config.js and add key projectRoot`, async () => {
tree.write(
'apps/products/metro.config.js',
`
const { withNxMetro } = require('@nx/react-native');
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
// console.log(getModulesRunBeforeMainModule);
return withNxMetro(
{
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
babelTransformerPath: require.resolve('react-native-svg-transformer'),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
resolverMainFields: ['sbmodern', 'browser', 'main'],
},
},
{
// Change this to true to see debugging info.
// Useful if you have issues resolving modules
debug: false,
// all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx'
extensions: [],
}
);
})();
`
);
await update(tree);
expect(tree.read('apps/products/metro.config.js', 'utf-8'))
.toMatchInlineSnapshot(`
"const { withNxMetro } = require('@nx/react-native');
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
// console.log(getModulesRunBeforeMainModule);
return withNxMetro(
{
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
babelTransformerPath: require.resolve('react-native-svg-transformer'),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
resolverMainFields: ['sbmodern', 'browser', 'main'],
},
},
{
// Change this to true to see debugging info.
// Useful if you have issues resolving modules
projectRoot: __dirname,
watchFolders: [],
debug: false,
// all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx'
extensions: [],
}
);
})();
"
`);
});
it(`should not udpate metro.config.js if projectRoot already exists`, async () => {
tree.write(
'apps/products/metro.config.js',
`
const { withNxMetro } = require('@nx/react-native');
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
// console.log(getModulesRunBeforeMainModule);
return withNxMetro(
{
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
babelTransformerPath: require.resolve('react-native-svg-transformer'),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
resolverMainFields: ['sbmodern', 'browser', 'main'],
},
},
{
projectRoot: __dirname,
// Change this to true to see debugging info.
// Useful if you have issues resolving modules
debug: false,
// all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx'
extensions: [],
}
);
})();
`
);
await update(tree);
expect(tree.read('apps/products/metro.config.js', 'utf-8'))
.toMatchInlineSnapshot(`
"const { withNxMetro } = require('@nx/react-native');
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
// console.log(getModulesRunBeforeMainModule);
return withNxMetro(
{
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
babelTransformerPath: require.resolve('react-native-svg-transformer'),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
resolverMainFields: ['sbmodern', 'browser', 'main'],
},
},
{
projectRoot: __dirname,
// Change this to true to see debugging info.
// Useful if you have issues resolving modules
debug: false,
// all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx'
extensions: [],
}
);
})();
"
`);
});
});

View File

@ -1,43 +0,0 @@
import {
formatFiles,
getProjects,
logger,
stripIndents,
Tree,
} from '@nx/devkit';
/**
* Add projectRoot and watchFolders options in metro.config.js
* @param tree
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
projects.forEach((project) => {
const metroConfigPath = `${project.root}/metro.config.js`;
if (
project.targets?.start?.executor !== '@nrwl/react-native:start' ||
!tree.exists(metroConfigPath)
)
return;
try {
const metroConfigContent = tree.read(metroConfigPath, 'utf-8');
if (metroConfigContent.includes('projectRoot: __dirname')) {
return;
}
if (metroConfigContent.includes('projectRoot')) return;
tree.write(
metroConfigPath,
metroConfigContent.replace(
'debug: ',
'projectRoot: __dirname, watchFolders: [], debug: '
)
);
} catch {
logger.error(
stripIndents`Unable to update ${metroConfigPath} for project ${project.root}.`
);
}
});
await formatFiles(tree);
}

View File

@ -1,31 +0,0 @@
import { addProjectConfiguration, getProjects, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-eject-target-14-1-2';
describe('add-eject-target-14-1-2', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'product', {
root: 'apps/product',
sourceRoot: 'apps/product/src',
targets: {
start: {
executor: '@nrwl/expo:start',
},
},
});
});
it(`should update project.json with target eject`, async () => {
await update(tree);
getProjects(tree).forEach((project) => {
expect(project.targets['eject']).toEqual({
executor: '@nrwl/expo:eject',
options: {},
});
});
});
});

View File

@ -1,26 +0,0 @@
import {
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
} from '@nx/devkit';
/**
* Add eject to targets for expo app
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') {
config.targets['eject'] = {
executor: '@nrwl/expo:eject',
options: {},
};
}
updateProjectConfiguration(tree, name, config);
}
await formatFiles(tree);
}

View File

@ -1,41 +0,0 @@
import { addProjectConfiguration, getProjects, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-eas-build-target';
describe('add-eas-build-target', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'product', {
root: 'apps/product',
sourceRoot: 'apps/product/src',
targets: {
start: {
executor: '@nrwl/expo:start',
},
},
});
});
it(`should update project.json with target build and build-list`, async () => {
await update(tree);
getProjects(tree).forEach((project) => {
expect(project.targets['build']).toEqual({
executor: '@nrwl/expo:build',
options: {},
});
expect(project.targets['build-list']).toEqual({
executor: '@nrwl/expo:build-list',
options: {},
});
expect(project.targets['download']).toEqual({
executor: '@nrwl/expo:download',
options: {
output: 'apps/product/dist',
},
});
});
});
});

View File

@ -1,43 +0,0 @@
import {
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
} from '@nx/devkit';
import { join } from 'path';
/**
* Add eas build and build-list target for expo
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') {
if (!config.targets['build']) {
config.targets['build'] = {
executor: '@nrwl/expo:build',
options: {},
};
}
if (!config.targets['build-list']) {
config.targets['build-list'] = {
executor: '@nrwl/expo:build-list',
options: {},
};
}
if (!config.targets['download']) {
config.targets['download'] = {
executor: '@nrwl/expo:download',
options: {
output: join(config.root, 'dist'),
},
};
}
}
updateProjectConfiguration(tree, name, config);
}
await formatFiles(tree);
}

View File

@ -1,31 +0,0 @@
import { addProjectConfiguration, getProjects, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-eas-update-target';
describe('add-eas-update-target', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'product', {
root: 'apps/product',
sourceRoot: 'apps/product/src',
targets: {
start: {
executor: '@nrwl/expo:start',
},
},
});
});
it(`should update project.json with target update`, async () => {
await update(tree);
getProjects(tree).forEach((project) => {
expect(project.targets['update']).toEqual({
executor: '@nrwl/expo:update',
options: {},
});
});
});
});

View File

@ -1,27 +0,0 @@
import {
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
} from '@nx/devkit';
/**
* Add eas update target for expo
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') {
if (!config.targets['update']) {
config.targets['update'] = {
executor: '@nrwl/expo:update',
options: {},
};
}
updateProjectConfiguration(tree, name, config);
}
}
await formatFiles(tree);
}

View File

@ -7,28 +7,5 @@
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
}
},
"packageJsonUpdates": {
"13.7.0": {
"version": "13.7.0",
"packages": {
"express": {
"version": "4.17.2",
"alwaysAddToPackageJson": false
},
"@types/express": {
"version": "4.17.13",
"alwaysAddToPackageJson": false
}
}
},
"14.5.0": {
"version": "14.5.0-beta.4",
"packages": {
"express": {
"version": "^4.18.1",
"alwaysAddToPackageJson": false
}
}
}
}
"packageJsonUpdates": {}
}

View File

@ -1,77 +1,5 @@
{
"generators": {
"update-jest-preset-angular-8-4-0": {
"version": "12.1.0-beta.1",
"cli": "nx",
"description": "Update jest-preset-angular to version 8.4.0",
"factory": "./src/migrations/update-12-1-2/update-jest-preset-angular"
},
"update-ts-jest-6-5-5": {
"version": "12.1.2-beta.1",
"cli": "nx",
"description": "Replace tsConfig with tsconfig for ts-jest in jest.config.js",
"factory": "./src/migrations/update-12-1-2/update-ts-jest"
},
"support-jest-27": {
"version": "12.4.0-beta.1",
"cli": "nx",
"description": "Add testEnvironment: 'jsdom' in web apps + libraries",
"factory": "./src/migrations/update-12-4-0/add-test-environment-for-node"
},
"update-ts-jest-and-jest-preset-angular": {
"version": "12.4.0-beta.1",
"cli": "nx",
"description": "Support for Jest 27 via updating ts-jest + jest-preset-angular",
"factory": "./src/migrations/update-12-4-0/update-jest-preset-angular"
},
"update-jest-config-to-use-util": {
"version": "12.6.0-beta.0",
"cli": "nx",
"description": "Uses `getJestProjects()` to populate projects array in root level `jest.config.js` file.",
"factory": "./src/migrations/update-12-6-0/update-base-jest-config"
},
"update-ts-config-for-test-filenames": {
"version": "13.1.2-beta.0",
"cli": "nx",
"description": "Support .test. file names in tsconfigs",
"factory": "./src/migrations/update-13-1-2/update-tsconfigs-for-tests"
},
"add-missing-root-babel-config": {
"version": "13.4.4-beta.0",
"cli": "nx",
"description": "Create a root babel config file if it doesn't exist and using babel-jest in jest.config.js and add @nrwl/web as needed",
"factory": "./src/migrations/update-13-4-4/add-missing-root-babel-config"
},
"update-jest-config-extensions": {
"version": "14.0.0-beta.2",
"cli": "nx",
"description": "Update move jest config files to .ts files.",
"factory": "./src/migrations/update-14-0-0/update-jest-config-ext"
},
"update-to-export-default": {
"version": "14.1.5-beta.0",
"cli": "nx",
"description": "Update to export default in jest config and revert jest.preset.ts to jest.preset.js",
"factory": "./src/migrations/update-14-1-5/update-exports-jest-config"
},
"exclude-jest-config-from-ts-config": {
"version": "14.5.5-beta.0",
"cli": "nx",
"description": "Exclude jest.config.ts from tsconfig where missing.",
"factory": "./src/migrations/update-14-0-0/update-jest-config-ext"
},
"update-configs-jest-28": {
"version": "14.6.0-beta.0",
"cli": "nx",
"description": "Update jest configs to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28#configuration-options)",
"factory": "./src/migrations/update-14-6-0/update-configs-jest-28"
},
"update-tests-jest-28": {
"version": "14.6.0-beta.0",
"cli": "nx",
"description": "Update jest test files to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28)",
"factory": "./src/migrations/update-14-6-0/update-tests-jest-28"
},
"add-jest-inputs": {
"version": "15.0.0-beta.0",
"cli": "nx",
@ -104,133 +32,6 @@
}
},
"packageJsonUpdates": {
"12.1.0": {
"version": "12.1.0-beta.1",
"packages": {
"jest-preset-angular": {
"version": "8.4.0",
"alwaysAddToPackageJson": false
},
"ts-jest": {
"version": "26.5.5",
"alwaysAddToPackageJson": false
}
}
},
"12.4.0": {
"version": "12.4.0-beta.1",
"packages": {
"jest": {
"version": "27.0.3",
"alwaysAddToPackageJson": false
},
"ts-jest": {
"version": "27.0.3",
"alwaysAddToPackageJson": false
},
"jest-preset-angular": {
"version": "9.0.3",
"alwaysAddToPackageJson": false
}
}
},
"12.10.0": {
"version": "12.10.0-beta.1",
"packages": {
"jest": {
"version": "27.2.3",
"alwaysAddToPackageJson": false
},
"@types/jest": {
"version": "27.0.2",
"alwaysAddToPackageJson": false
},
"ts-jest": {
"version": "27.0.5",
"alwaysAddToPackageJson": false
},
"babel-jest": {
"version": "27.2.3",
"alwaysAddToPackageJson": false
}
}
},
"14.0.0": {
"version": "14.0.0-beta.2",
"packages": {
"jest": {
"version": "27.5.1",
"alwaysAddToPackageJson": false
},
"@types/jest": {
"version": "27.4.1",
"alwaysAddToPackageJson": false
},
"ts-jest": {
"version": "27.1.4",
"alwaysAddToPackageJson": false
},
"babel-jest": {
"version": "27.5.1",
"alwaysAddToPackageJson": false
}
}
},
"14.2.0": {
"version": "14.2.0-rc.2",
"packages": {
"ts-node": {
"version": "~10.8.0",
"alwaysAddToPackageJson": false
}
}
},
"14.5.5": {
"version": "14.5.5-beta.0",
"packages": {
"ts-node": {
"version": "10.9.1",
"alwaysAddToPackageJson": false
}
}
},
"14.6.0": {
"version": "14.6.0-beta.0",
"packages": {
"jest": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
},
"@types/jest": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
},
"expect": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
},
"@jest/globals": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
},
"jest-jasmine2": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
},
"jest-environment-jsdom": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
},
"ts-jest": {
"version": "~28.0.5",
"alwaysAddToPackageJson": false
},
"babel-jest": {
"version": "~28.1.1",
"alwaysAddToPackageJson": false
}
}
},
"15.0.1-beta.3": {
"version": "15.0.1-beta.3",
"packages": {

View File

@ -1,88 +0,0 @@
import {
formatFiles,
logger,
readProjectConfiguration,
stripIndents,
Tree,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { join } from 'path';
import {
addPropertyToJestConfig,
removePropertyFromJestConfig,
} from '../../utils/config/update-config';
import { JestExecutorOptions } from '../../executors/jest/schema';
function updateJestConfig(tree: Tree) {
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options, projectName) => {
const config = require(join(tree.root, options.jestConfig as string));
// migrate serializers
if (
config.snapshotSerializers &&
Array.isArray(config.snapshotSerializers)
) {
const snapshotSerializers = config.snapshotSerializers.map(
(snapshotSerializer) => {
switch (snapshotSerializer) {
case 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js':
return 'jest-preset-angular/build/serializers/no-ng-attributes';
case 'jest-preset-angular/build/AngularSnapshotSerializer.js':
return 'jest-preset-angular/build/serializers/ng-snapshot';
case 'jest-preset-angular/build/HTMLCommentSerializer.js':
return 'jest-preset-angular/build/serializers/html-comment';
default:
return snapshotSerializer;
}
}
);
try {
removePropertyFromJestConfig(
tree,
options.jestConfig as string,
'snapshotSerializers'
);
addPropertyToJestConfig(
tree,
options.jestConfig as string,
'snapshotSerializers',
snapshotSerializers
);
} catch {
logger.error(
stripIndents`Unable to update snapshotSerializers for project ${projectName}.
More information you can check online documentation https://github.com/thymikee/jest-preset-angular/blob/master/CHANGELOG.md#840-2021-03-04`
);
}
}
try {
const { sourceRoot } = readProjectConfiguration(tree, projectName);
const setupTestPath = join(sourceRoot, 'test-setup.ts');
if (tree.exists(setupTestPath)) {
const contents = tree.read(setupTestPath, 'utf-8');
tree.write(
setupTestPath,
contents.replace(
`import 'jest-preset-angular';`,
`import 'jest-preset-angular/setup-jest';`
)
);
}
} catch {
logger.error(
stripIndents`Unable to update test-setup.ts for project ${projectName}.`
);
}
}
);
}
export default async function update(tree) {
updateJestConfig(tree);
await formatFiles(tree);
}

View File

@ -1,50 +0,0 @@
import { formatFiles, logger, stripIndents, Tree } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { join } from 'path';
import { JestExecutorOptions } from '../../executors/jest/schema';
import {
addPropertyToJestConfig,
removePropertyFromJestConfig,
} from '../../utils/config/update-config';
function updateJestConfig(tree: Tree) {
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options, project) => {
if (!options.jestConfig) {
return;
}
const jestConfigPath = options.jestConfig;
const config = require(join(tree.root, jestConfigPath));
const tsJestConfig = config.globals?.['ts-jest'];
if (!(tsJestConfig && tsJestConfig.tsConfig)) {
return;
}
try {
removePropertyFromJestConfig(
tree,
jestConfigPath,
'globals.ts-jest.tsConfig'
);
addPropertyToJestConfig(
tree,
jestConfigPath,
'globals.ts-jest.tsconfig',
tsJestConfig.tsConfig
);
} catch {
logger.error(
stripIndents`Unable to update jest.config.js for project ${project}.`
);
}
}
);
}
export default async function update(tree: Tree) {
updateJestConfig(tree);
await formatFiles(tree);
}

View File

@ -1,57 +0,0 @@
import {
formatFiles,
logger,
ProjectConfiguration,
readProjectConfiguration,
stripIndents,
Tree,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { join } from 'path';
import { JestExecutorOptions } from '../../executors/jest/schema';
import { addPropertyToJestConfig } from '../../utils/config/update-config';
function updateJestConfig(tree: Tree) {
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options, project) => {
if (!options.jestConfig) {
return;
}
const jestConfigPath = options.jestConfig;
const jestConfig = require(join(tree.root, jestConfigPath));
const projectConfig = readProjectConfiguration(tree, project);
const testEnvironment = jestConfig.testEnvironment;
if (testEnvironment || !checkIfNodeProject(projectConfig)) {
return;
}
try {
addPropertyToJestConfig(
tree,
jestConfigPath,
'testEnvironment',
'node'
);
} catch {
logger.error(
stripIndents`Unable to update jest.config.js for project ${project}.`
);
}
}
);
}
export default async function update(tree: Tree) {
updateJestConfig(tree);
await formatFiles(tree);
}
function checkIfNodeProject(config: ProjectConfiguration) {
return Object.entries(config.targets).some(([targetName, targetConfig]) =>
targetConfig.executor?.includes?.('node')
);
}

View File

@ -1,148 +0,0 @@
import { formatFiles, logger, stripIndents, Tree } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { join } from 'path';
import { JestExecutorOptions } from '../../executors/jest/schema';
import {
addPropertyToJestConfig,
removePropertyFromJestConfig,
} from '../../utils/config/update-config';
function updateJestConfig(tree: Tree) {
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options, project) => {
if (!options.jestConfig) {
return;
}
const jestConfigPath = options.jestConfig;
const jestConfig = require(join(
tree.root,
jestConfigPath
)) as PartialJestConfig;
if (!usesJestPresetAngular(jestConfig)) {
return;
}
try {
updateASTTransformers(tree, jestConfigPath, jestConfig);
updateTransform(tree, jestConfigPath, jestConfig);
} catch {
logger.error(
stripIndents`Unable to update jest.config.js for project ${project}.`
);
}
}
);
}
export default async function update(tree: Tree) {
updateJestConfig(tree);
await formatFiles(tree);
}
export function updateASTTransformers(
tree: Tree,
jestConfigPath: string,
jestConfig: PartialJestConfig
) {
const newTransformers = getNewAstTransformers(
jestConfig.globals?.['ts-jest']?.astTransformers
);
if (newTransformers === null) {
removePropertyFromJestConfig(
tree,
jestConfigPath,
'globals.ts-jest.astTransformers'
);
} else {
addPropertyToJestConfig(
tree,
jestConfigPath,
'globals.ts-jest.astTransformers',
newTransformers
);
}
}
export function updateTransform(
tree: Tree,
jestConfigPath: string,
jestConfig: PartialJestConfig
) {
removePropertyFromJestConfig(tree, jestConfigPath, 'transform');
addPropertyToJestConfig(tree, jestConfigPath, 'transform', {
'^.+\\.(ts|js|html)$': 'jest-preset-angular',
});
}
interface PartialJestConfig {
globals: {
'ts-jest': {
astTransformers: ASTTransformers;
};
};
transform?: Record<string, string>;
}
interface ASTTransformer {
path: string;
options: unknown;
}
interface ASTTransformers {
before: (ASTTransformer | string)[];
after: (ASTTransformer | string)[];
afterDeclarations: (ASTTransformer | string)[];
}
export function getNewAstTransformers(
astTransformers: ASTTransformers
): ASTTransformers | null {
let result = {
before: astTransformers?.before?.filter?.(
(x) => !transformerIsFromJestPresetAngular(x)
),
after: astTransformers?.after?.filter?.(
(x) => !transformerIsFromJestPresetAngular(x)
),
afterDeclarations: astTransformers?.afterDeclarations?.filter?.(
(x) => !transformerIsFromJestPresetAngular(x)
),
};
result = {
before: result.before?.length > 0 ? result.before : undefined,
after: result.after?.length > 0 ? result.after : undefined,
afterDeclarations:
result.afterDeclarations?.length > 0
? result.afterDeclarations
: undefined,
};
if (!result.before && !result.after && !result.afterDeclarations) {
return null;
} else {
return result;
}
}
export function transformerIsFromJestPresetAngular(
transformer: ASTTransformer | string
) {
return typeof transformer === 'string'
? transformer.includes('jest-preset-angular')
: transformer.path.includes('jest-preset-angular');
}
export function usesJestPresetAngular(jestConfig: PartialJestConfig) {
const transformers = Array.isArray(
jestConfig.globals?.['ts-jest']?.astTransformers
)
? jestConfig.globals?.['ts-jest']?.astTransformers || []
: jestConfig.globals?.['ts-jest']?.astTransformers?.before || [];
return transformers.some((x) => transformerIsFromJestPresetAngular(x));
}

View File

@ -1,100 +0,0 @@
const mockGetJestProjects = jest.fn(() => []);
jest.mock('../../utils/config/get-jest-projects', () => ({
getJestProjects: mockGetJestProjects,
}));
const mockResolveConfig = jest.fn(() =>
Promise.resolve({ singleQuote: true, endOfLine: 'lf' })
);
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './update-base-jest-config';
describe('update 12.6.0', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'jest.config.js',
`module.exports = {
projects: ['<rootDir>/test-1']
}`
);
});
beforeEach(async () => {
const prettier = await import('prettier');
prettier.resolveConfig = mockResolveConfig as any;
});
test('no projects key configured', async () => {
tree.write('jest.config.js', 'module.exports = {}');
await update(tree);
const result = tree.read('jest.config.js').toString();
expect(result).toMatchInlineSnapshot(`
"const { getJestProjects } = require('@nx/jest');
module.exports = { projects: getJestProjects() };
"
`);
});
test('all jest projects covered', async () => {
mockGetJestProjects.mockImplementation(() => ['<rootDir>/test-1']);
await update(tree);
const result = tree.read('jest.config.js').toString();
expect(result).toMatchInlineSnapshot(`
"const { getJestProjects } = require('@nx/jest');
module.exports = { projects: getJestProjects() };
"
`);
});
test('some jest projects uncovered', async () => {
mockGetJestProjects.mockImplementation(() => ['<rootDir>/test-2']);
await update(tree);
const result = tree.read('jest.config.js').toString();
expect(result).toMatchInlineSnapshot(`
"const { getJestProjects } = require('@nx/jest');
module.exports = { projects: [...getJestProjects(), '<rootDir>/test-1'] };
"
`);
});
test('proper formatting with multiple uncovered jest projects', async () => {
mockGetJestProjects.mockImplementation(() => ['<rootDir>/test-2']);
tree.write(
'jest.config.js',
`
module.exports = {
projects: [
'<rootDir>/test-1',
'<rootDir>/test-2',
'<rootDir>/test-3',
'<rootDir>/test-4',
'<rootDir>/test-5'
]
}`
);
await update(tree);
const result = tree.read('jest.config.js').toString();
expect(result).toMatchInlineSnapshot(`
"const { getJestProjects } = require('@nx/jest');
module.exports = {
projects: [
...getJestProjects(),
'<rootDir>/test-1',
'<rootDir>/test-3',
'<rootDir>/test-4',
'<rootDir>/test-5',
],
};
"
`);
});
});

View File

@ -1,62 +0,0 @@
import { formatFiles, Tree } from '@nx/devkit';
import { jestConfigObject } from '../../utils/config/functions';
import { getJestProjects } from '../../utils/config/get-jest-projects';
import {
addImportStatementToJestConfig,
addPropertyToJestConfig,
removePropertyFromJestConfig,
} from '../../utils/config/update-config';
function determineUncoveredJestProjects(existingProjects: string[]) {
const coveredJestProjects = (getJestProjects() as string[]).reduce(
(acc, key) => {
acc[key] = true;
return acc;
},
{}
);
return existingProjects.filter((project) => !coveredJestProjects[project]);
}
function determineProjectsValue(uncoveredJestProjects: string[]): string {
if (!uncoveredJestProjects.length) {
return `getJestProjects()`;
}
return `[...getJestProjects(), ${uncoveredJestProjects
.map((projectName) => `'${projectName}', `)
.join('')}]`;
}
function updateBaseJestConfig(
tree: Tree,
baseJestConfigPath = 'jest.config.js'
) {
if (tree.read('/jest.config.js', 'utf-8').includes('getJestProjects()')) {
return;
}
const currentConfig = jestConfigObject(tree, baseJestConfigPath);
currentConfig.projects ??= [];
const uncoveredJestProjects = determineUncoveredJestProjects(
currentConfig.projects as string[]
);
removePropertyFromJestConfig(tree, baseJestConfigPath, 'projects');
addPropertyToJestConfig(
tree,
baseJestConfigPath,
'projects',
determineProjectsValue(uncoveredJestProjects),
{ valueAsString: true }
);
addImportStatementToJestConfig(
tree,
baseJestConfigPath,
`const { getJestProjects } = require('@nx/jest');`
);
return;
}
export default async function update(tree: Tree) {
updateBaseJestConfig(tree);
await formatFiles(tree);
}

View File

@ -1,296 +0,0 @@
import { addProjectConfiguration, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './update-tsconfigs-for-tests';
const reactTsConfigs = {
app: {
extends: './tsconfig.json',
compilerOptions: {
outDir: '../../dist/out-tsc',
types: ['node'],
},
files: [
'../../node_modules/@nrwl/react/typings/cssmodule.d.ts',
'../../node_modules/@nrwl/react/typings/image.d.ts',
],
exclude: [
'**/*.spec.ts',
'**/*_spec.ts',
'**/*.spec.tsx',
'**/*.spec.js',
'**/*.spec.jsx',
],
include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
},
lib: {
extends: './tsconfig.json',
compilerOptions: {
outDir: '../../dist/out-tsc',
types: ['node'],
},
files: [
'../../node_modules/@nrwl/react/typings/cssmodule.d.ts',
'../../node_modules/@nrwl/react/typings/image.d.ts',
],
exclude: [
'**/*.spec.ts',
'**/*_spec.ts',
'**/*.spec.tsx',
'**/*.spec.js',
'**/*.spec.jsx',
],
include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
},
spec: {
extends: './tsconfig.json',
compilerOptions: {
outDir: '../../dist/out-tsc',
module: 'commonjs',
types: ['jest', 'node'],
},
include: [
'**/*.spec.ts',
'**/*_spec.ts',
'**/*.spec.tsx',
'**/*.spec.js',
'**/*.spec.jsx',
'**/*.d.ts',
],
files: [
'../../node_modules/@nrwl/react/typings/cssmodule.d.ts',
'../../node_modules/@nrwl/react/typings/image.d.ts',
],
},
base: {
include: [],
files: [],
references: [
{
path: './tsconfig.app.json',
},
{
path: './tsconfig.lib.json',
},
{
path: './tsconfig.spec.json',
},
],
},
expectedFilesToContain: [
'**/*.spec.ts',
'**/*.test.ts',
'**/*_spec.ts',
'**/*_test.ts',
'**/*.spec.tsx',
'**/*.test.tsx',
'**/*.spec.js',
'**/*.test.js',
'**/*.spec.jsx',
'**/*.test.jsx',
],
};
const angularTsConfigs = {
app: {
extends: './tsconfig.json',
compilerOptions: {
outDir: '../../dist/out-tsc',
types: [],
},
files: ['src/main.ts', 'src/polyfills.ts'],
include: ['src/**/*.d.ts'],
exclude: ['**/*.spec.ts', '**/*_spec.ts'],
},
lib: {
extends: './tsconfig.json',
compilerOptions: {
outDir: '../../dist/out-tsc',
target: 'es2015',
declaration: true,
declarationMap: true,
inlineSources: true,
types: [],
lib: ['dom', 'es2018'],
},
exclude: ['src/test-setup.ts', '**/*.spec.ts'],
include: ['**/*.ts'],
},
spec: {
extends: './tsconfig.json',
compilerOptions: {
outDir: '../../dist/out-tsc',
module: 'commonjs',
types: ['jest', 'node'],
},
files: ['src/test-setup.ts'],
include: ['**/*.spec.ts', '**/*_spec.ts', '**/*.d.ts'],
},
expectedFilesToContain: [
'**/*.spec.ts',
'**/*.test.ts',
'**/*_spec.ts',
'**/*_test.ts',
],
};
const tsConfigLibBase = {
include: [],
files: [],
references: [
{
path: './tsconfig.lib.json',
},
{
path: './tsconfig.spec.json',
},
],
};
const tsConfigAppBase = {
include: [],
files: [],
references: [
{
path: './tsconfig.app.json',
},
{
path: './tsconfig.spec.json',
},
],
};
const tsConfigWithExclude = {
exclude: ['**/*.spec.ts', '**/*_spec.ts'],
};
[
// test TSX/JSX support
{ name: 'React App', configs: reactTsConfigs },
// test non TSX/JSX support
{ name: 'Angular App', configs: angularTsConfigs },
].forEach(({ name, configs }) => {
describe(`Jest Migration (v13.1.2): ${name}`, () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'apps/project-one/tsconfig.app.json',
String.raw`${JSON.stringify(configs.app, null, 2)}`
);
tree.write(
'apps/project-one/tsconfig.spec.json',
String.raw`${JSON.stringify(configs.spec, null, 2)}`
);
tree.write(
`apps/project-one/tsconfig.json`,
String.raw`${JSON.stringify(tsConfigAppBase, null, 2)}`
);
tree.write(
'libs/lib-one/tsconfig.lib.json',
String.raw`${JSON.stringify(configs.lib, null, 2)}`
);
tree.write(
'libs/lib-one/tsconfig.spec.json',
String.raw`${JSON.stringify(configs.spec, null, 2)}`
);
tree.write(
`libs/lib-one/tsconfig.json`,
String.raw`${JSON.stringify(tsConfigLibBase, null, 2)}`
);
addProjectConfiguration(tree, 'lib-one', {
root: 'libs/lib-one',
sourceRoot: 'libs/lib-one/src',
projectType: 'library',
targets: {
build: {
executor: '@nrwl/web:build',
},
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-one/jest.config.js',
passWithNoTests: true,
},
},
},
});
addProjectConfiguration(tree, 'project-one', {
root: 'apps/project-one',
sourceRoot: 'apps/project-one/src',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/web:build',
},
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'apps/project-one/jest.config.js',
passWithNoTests: true,
},
},
},
});
});
it('should update tsconfig.spec.json to include .test. files', async () => {
await update(tree);
const tsAppSpecConfig = JSON.parse(
tree.read('apps/project-one/tsconfig.spec.json', 'utf-8')
);
const tsLibSpecConfig = JSON.parse(
tree.read('apps/project-one/tsconfig.spec.json', 'utf-8')
);
expect(tsAppSpecConfig.include).toEqual(
expect.arrayContaining(configs.expectedFilesToContain)
);
expect(tsLibSpecConfig.include).toEqual(
expect.arrayContaining(configs.expectedFilesToContain)
);
});
it('should update tsconfig.{lib|app}.json to exclude .test. files', async () => {
await update(tree);
const tsAppConfig = JSON.parse(
tree.read('apps/project-one/tsconfig.app.json', 'utf-8')
);
const tsLibConfig = JSON.parse(
tree.read('apps/project-one/tsconfig.app.json', 'utf-8')
);
expect(tsAppConfig.exclude).toEqual(
expect.arrayContaining(configs.expectedFilesToContain)
);
expect(tsLibConfig.exclude).toEqual(
expect.arrayContaining(configs.expectedFilesToContain)
);
});
it('should not update tsconfig without spec. patterns for include or exclude', async () => {
await update(tree);
const tsConfig = JSON.parse(
tree.read('apps/project-one/tsconfig.json', 'utf-8')
);
expect(tsConfig).toEqual(tsConfigAppBase);
});
it('should update any tsconfig with spec pattern for include or exclude', async () => {
tree.write(
'apps/project-one/tsconfig.random.json',
String.raw`${JSON.stringify(tsConfigWithExclude, null, 2)}`
);
await update(tree);
const randomTsConfig = JSON.parse(
tree.read('apps/project-one/tsconfig.random.json', 'utf-8')
);
expect(randomTsConfig.exclude).toEqual([
'**/*.spec.ts',
'**/*.test.ts',
'**/*_spec.ts',
'**/*_test.ts',
]);
});
});
});

View File

@ -1,85 +0,0 @@
import {
formatFiles,
logger,
readProjectConfiguration,
stripIndents,
Tree,
updateJson,
visitNotIgnoredFiles,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { basename } from 'path';
import { JestExecutorOptions } from '../../executors/jest/schema';
function updateTsConfigsForTests(tree: Tree) {
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(jestOptions, projectName) => {
const projectConfig = readProjectConfiguration(tree, projectName);
visitNotIgnoredFiles(tree, projectConfig.root, (path) => {
const fileName = basename(path);
if (fileName.startsWith('tsconfig') && fileName.endsWith('.json')) {
updateTsConfig(tree, path);
}
});
}
);
function updateTsConfig(tree: Tree, tsconfigSpecPath: string) {
try {
updateJson<TsConfig>(tree, tsconfigSpecPath, (value) => {
if (value.include) {
value.include = makeAllPatternsFromSpecPatterns(value.include);
}
if (value.exclude) {
value.exclude = makeAllPatternsFromSpecPatterns(value.exclude);
}
return value;
});
} catch (error) {
// issue trying to parse the tsconfig file bc it's invalid JSON from template markup/comments
// ignore and move on
logger.warn(stripIndents`Unable to update ${tsconfigSpecPath}. `);
}
}
}
/**
* take an array of patterns and create patterns from those containing .spec. with .test.
* by default the pattern ** /*.spec.ts will be used if no value is passed in.
*/
function makeAllPatternsFromSpecPatterns(
specGlobs: string[] = ['**/*.spec.ts']
): string[] {
return makeUniquePatterns(
specGlobs.reduce((patterns, current) => {
patterns.push(current);
// .spec. and _spec. can used as testing file name patterns
if (current.includes('spec.')) {
patterns.push(current.replace('spec.', 'test.'));
}
return patterns;
}, [])
);
}
function makeUniquePatterns(items: string[] = []): string[] {
return [...new Set(items)];
}
export default async function update(tree: Tree) {
updateTsConfigsForTests(tree);
await formatFiles(tree);
}
interface TsConfig {
files?: string[];
include?: string[];
exclude?: string[];
references?: {
path: string;
}[];
}

View File

@ -1,113 +0,0 @@
import { addProjectConfiguration, readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-missing-root-babel-config';
describe('Jest Migration (v13.4.4)', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'package.json',
JSON.stringify({
name: 'test',
version: '',
description: '',
devDependencies: {},
})
);
tree.write(
'libs/lib-one/jest.config.js',
String.raw`module.exports = {
transform: {
'^.+\\\\.[tj]sx?$': 'babel-jest',
}
}`
);
addProjectConfiguration(tree, 'lib-one', {
root: 'libs/lib-one',
sourceRoot: 'libs/lib-one/src',
projectType: 'library',
targets: {
build: {
executor: '@nrwl/web:build',
},
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-one/jest.config.js',
passWithNoTests: true,
},
},
},
});
addProjectConfiguration(tree, 'lib-two', {
root: 'libs/lib-two',
sourceRoot: 'libs/lib-two/src',
projectType: 'library',
targets: {
build: {
executor: '@nrwl/web:build',
},
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-two/jest.config.js',
passWithNoTests: true,
},
},
},
});
});
it('should create root babel.config.json and install @nrwl/web', async () => {
await update(tree);
expect(tree.exists('babel.config.json')).toBeTruthy();
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['@nrwl/web']).toBeTruthy();
});
it('should not change anything if root babel.config.json is found', async () => {
tree.write('babel.config.json', '{"babelrcRoots": ["*"]}');
await update(tree);
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['@nrwl/web']).toBeFalsy();
});
it('should update w/ Array value for babel-jest transformer', async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'libs/lib-three/jest.config.js',
String.raw`module.exports = {
transform: {
'^.+\\\\.[tj]sx?$': ['babel-jest', {someOptionsThatDontMatter: false}],
}
}`
);
addProjectConfiguration(tree, 'lib-three', {
root: 'libs/lib-three',
sourceRoot: 'libs/lib-three/src',
projectType: 'library',
targets: {
build: {
executor: '@nrwl/web:build',
},
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-three/jest.config.js',
passWithNoTests: true,
},
},
},
});
await update(tree);
expect(tree.exists('babel.config.json')).toBeTruthy();
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['@nrwl/web']).toBeTruthy();
});
});

View File

@ -1,62 +0,0 @@
import {
addDependenciesToPackageJson,
formatFiles,
joinPathFragments,
readProjectConfiguration,
Tree,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { JestExecutorOptions } from '../../executors/jest/schema';
import { jestConfigObject } from '../../utils/config/functions';
import { nxVersion } from '../../utils/versions';
function checkIfProjectNeedsUpdate(tree: Tree): boolean {
if (tree.exists('babel.config.json')) {
// the project is already running on babel and good to go
return false;
}
let shouldUpdate = false;
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(jestOptions, projectName) => {
const projectConfig = readProjectConfiguration(tree, projectName);
const jestConfigPath = joinPathFragments(
projectConfig.root,
'jest.config.js'
);
if (!tree.exists(jestConfigPath)) {
return;
}
const config = jestConfigObject(tree, jestConfigPath);
if (config.transform) {
for (const transformer of Object.values(config.transform)) {
if (
(typeof transformer === 'string' && transformer === 'babel-jest') ||
(Array.isArray(transformer) && transformer[0] === 'babel-jest')
) {
shouldUpdate = true;
}
}
}
}
);
return shouldUpdate;
}
export default async function update(tree: Tree) {
const shouldUpdateConfigs = checkIfProjectNeedsUpdate(tree);
if (shouldUpdateConfigs) {
addDependenciesToPackageJson(tree, {}, { '@nrwl/web': nxVersion });
tree.write('babel.config.json', '{"babelrcRoots": ["*"]}');
await formatFiles(tree);
}
}

View File

@ -1,94 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Jest Migration (v14.0.0) should NOT update jest.config.ts preset 1`] = `
"/* eslint-disable */
module.exports = {
displayName: 'lib-one',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/lib-one',
};
"
`;
exports[`Jest Migration (v14.0.0) should produce the same results when running multiple times 1`] = `
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx",
],
"parserOptions": {
"project": [
"libs/my-next-proj/tsconfig.*?.json",
],
},
"rules": {},
}
`;
exports[`Jest Migration (v14.0.0) should produce the same results when running multiple times 2`] = `
"/* eslint-disable */
module.exports = {
displayName: 'lib-one',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/lib-one',
};
"
`;
exports[`Jest Migration (v14.0.0) should rename project jest.config.js to jest.config.ts 1`] = `
"/* eslint-disable */
module.exports = {
displayName: 'lib-one',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/lib-one',
};
"
`;
exports[`Jest Migration (v14.0.0) should update the excludes of next js apps using the project parser settings 1`] = `
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx",
],
"parserOptions": {
"project": [
"libs/lib-one/tsconfig.*?.json",
],
},
"rules": {},
}
`;

View File

@ -1,314 +0,0 @@
import {
readJson,
readProjectConfiguration,
Tree,
updateJson,
updateProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryGenerator as workspaceLib } from '@nx/js';
import jestInitGenerator from '../../generators/init/init';
import { updateJestConfigExt } from './update-jest-config-ext';
const setupDefaults = {
js: true,
skipPackageJson: true,
libName: 'lib-one',
setParserOptionsProject: false,
};
const oldConfig = `
module.exports = {
displayName: 'PLACE_HOLDER',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
}
},
transform: {
'^.+\\\\.[tj]sx?$': 'ts-jest'
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/PLACE_HOLDER'
};
`;
async function libSetUp(tree: Tree, options = setupDefaults) {
jestInitGenerator(tree, {
js: options.js,
skipPackageJson: options.skipPackageJson,
});
await workspaceLib(tree, {
name: options.libName,
setParserOptionsProject: options.setParserOptionsProject,
});
tree.rename(
`libs/${options.libName}/jest.config.ts`,
`libs/${options.libName}/jest.config.js`
);
tree.write(
`libs/${options.libName}/jest.config.js`,
oldConfig.replace(/PLACE_HOLDER/g, options.libName)
);
updateProjectConfiguration(tree, options.libName, {
...readProjectConfiguration(tree, options.libName),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: `libs/${options.libName}/jest.config.js`,
passWithNoTests: true,
},
configurations: {
production: {
silent: true,
},
},
},
},
});
}
describe('Jest Migration (v14.0.0)', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should rename project jest.config.js to jest.config.ts', async () => {
await libSetUp(tree);
await updateJestConfigExt(tree);
expect(tree.exists('libs/lib-one/jest.config.ts')).toBeTruthy();
expect(tree.read('libs/lib-one/jest.config.ts', 'utf-8')).toMatchSnapshot();
});
it('should rename root jest.config.js', async () => {
await libSetUp(tree);
await updateJestConfigExt(tree);
expect(tree.exists('jest.config.ts')).toBeTruthy();
expect(tree.exists('jest.preset.js')).toBeTruthy();
});
it('should NOT update jest.config.ts preset', async () => {
await libSetUp(tree);
tree.rename('libs/lib-one/jest.config.js', 'libs/lib-one/jest.config.ts');
const projectConfig = readProjectConfiguration(tree, 'lib-one');
updateProjectConfiguration(tree, 'lib-one', {
...projectConfig,
targets: {
test: {
...projectConfig.targets.test,
options: {
jestConfig: 'libs/lib-one/jest.config.ts',
passWithNoTests: true,
},
},
},
});
expect(tree.exists('libs/lib-one/jest.config.ts')).toBeTruthy();
await updateJestConfigExt(tree);
expect(tree.exists('libs/lib-one/jest.config.ts')).toBeTruthy();
expect(tree.read('libs/lib-one/jest.config.ts', 'utf-8')).toMatchSnapshot();
});
it('should only update js/ts files', async () => {
await libSetUp(tree);
tree.rename('libs/lib-one/jest.config.js', 'libs/lib-one/jest.config.ts');
updateProjectConfiguration(tree, 'lib-one', {
...readProjectConfiguration(tree, 'lib-one'),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-one/jest.config.ts',
passWithNoTests: true,
},
},
},
});
await libSetUp(tree, { ...setupDefaults, libName: 'lib-two' });
tree.delete('libs/lib-two/jest.config.ts'); // lib generator creates a ts file
tree.write('libs/lib-two/jest.config.json', '{}');
updateProjectConfiguration(tree, 'lib-two', {
...readProjectConfiguration(tree, 'lib-two'),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-two/jest.config.json',
passWithNoTests: true,
},
},
},
});
await libSetUp(tree, { ...setupDefaults, libName: 'lib-three' });
expect(tree.exists('libs/lib-one/jest.config.ts')).toBeTruthy();
await updateJestConfigExt(tree);
expect(tree.exists('libs/lib-one/jest.config.ts')).toBeTruthy();
expect(tree.exists('libs/lib-two/jest.config.ts')).toBeFalsy();
expect(tree.exists('libs/lib-two/jest.config.json')).toBeTruthy();
expect(tree.exists('libs/lib-three/jest.config.ts')).toBeTruthy();
});
it('should not throw error if file does not exit', async () => {
await libSetUp(tree);
tree.delete('libs/lib-one/jest.config.js');
await updateJestConfigExt(tree);
expect(tree.exists('libs/lib-one/jest.config.ts')).toBeFalsy();
expect(tree.exists('libs/lib-one/jest.config.js')).toBeFalsy();
});
it('should update correct tsconfigs', async () => {
await libSetUp(tree);
updateJson(tree, 'libs/lib-one/tsconfig.lib.json', (json) => {
json.exclude = ['src/**/*.spec.ts'];
return json;
});
updateJson(tree, 'libs/lib-one/tsconfig.spec.json', (json) => {
json.include = ['src/**/*.spec.ts'];
return json;
});
await updateJestConfigExt(tree);
const tsconfig = readJson(tree, 'libs/lib-one/tsconfig.json');
const libTsConfig = readJson(tree, 'libs/lib-one/tsconfig.lib.json');
const specTsConfig = readJson(tree, 'libs/lib-one/tsconfig.spec.json');
expect(tsconfig.exclude).toBeFalsy();
expect(libTsConfig.exclude).toEqual(['src/**/*.spec.ts', 'jest.config.ts']);
expect(specTsConfig.exclude).toBeFalsy();
expect(specTsConfig.include).toEqual([
'src/**/*.spec.ts',
'jest.config.ts',
]);
});
it('should add exclude to root tsconfig with no references', async () => {
await libSetUp(tree);
tree.delete('libs/lib-one/tsconfig.spec.json');
tree.delete('libs/lib-one/tsconfig.lib.json');
updateJson(tree, 'libs/lib-one/tsconfig.json', (json) => {
delete json.references;
return json;
});
await updateJestConfigExt(tree);
const tsconfig = readJson(tree, 'libs/lib-one/tsconfig.json');
expect(tsconfig.exclude).toEqual(['jest.config.ts']);
expect(tree.exists('libs/lib-one/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('libs/lib-one/tsconfig.lib.json')).toBeFalsy();
});
it('should update the excludes of next js apps using the project parser settings', async () => {
await libSetUp(tree, { ...setupDefaults, setParserOptionsProject: true });
const projectConfig = readProjectConfiguration(tree, 'lib-one');
projectConfig.targets['build'] = {
executor: '@nrwl/next:build',
options: {},
};
updateProjectConfiguration(tree, 'lib-one', projectConfig);
updateJson(tree, 'libs/lib-one/tsconfig.json', (json) => {
// simulate nextJS tsconfig;
json.exclude = ['node_modules'];
return json;
});
const esLintJson = readJson(tree, 'libs/lib-one/.eslintrc.json');
// make sure the parserOptions are set correctly
expect(esLintJson.overrides[0]).toMatchSnapshot();
await updateJestConfigExt(tree);
const tsconfigSpec = readJson(tree, 'libs/lib-one/tsconfig.spec.json');
expect(tsconfigSpec.exclude).toEqual(['node_modules']);
});
it('should produce the same results when running multiple times', async () => {
await libSetUp(tree);
updateJson(tree, 'libs/lib-one/tsconfig.lib.json', (json) => {
json.exclude = ['src/**/*.spec.ts'];
return json;
});
updateJson(tree, 'libs/lib-one/tsconfig.spec.json', (json) => {
json.include = ['src/**/*.spec.ts'];
return json;
});
await setupNextProj(tree);
const esLintJson = readJson(tree, 'libs/my-next-proj/.eslintrc.json');
// make sure the parserOptions are set correctly for next
expect(esLintJson.overrides[0]).toMatchSnapshot();
await updateJestConfigExt(tree);
assertNextProj(tree);
assertLib(tree);
await updateJestConfigExt(tree);
assertNextProj(tree);
assertLib(tree);
expect(tree.read('libs/lib-one/jest.config.ts', 'utf-8')).toMatchSnapshot();
});
});
async function setupNextProj(tree: Tree) {
await libSetUp(tree, {
...setupDefaults,
libName: 'my-next-proj',
setParserOptionsProject: true,
});
const projectConfig = readProjectConfiguration(tree, 'my-next-proj');
projectConfig.targets['build'] = {
executor: '@nrwl/next:build',
options: {},
};
updateProjectConfiguration(tree, 'my-next-proj', projectConfig);
updateJson(tree, 'libs/my-next-proj/tsconfig.json', (json) => {
// simulate nextJS tsconfig;
json.exclude = ['node_modules'];
return json;
});
}
function assertNextProj(tree: Tree) {
expect(
readJson(tree, 'libs/my-next-proj/tsconfig.spec.json').exclude
).toEqual(['node_modules']);
expect(
readJson(tree, 'libs/my-next-proj/tsconfig.spec.json').include
).toEqual(expect.arrayContaining(['jest.config.ts']));
}
function assertLib(tree: Tree) {
expect(readJson(tree, 'libs/lib-one/tsconfig.json').exclude).toBeFalsy();
expect(readJson(tree, 'libs/lib-one/tsconfig.spec.json').exclude).toBeFalsy();
expect(readJson(tree, 'libs/lib-one/tsconfig.spec.json').include).toEqual([
'src/**/*.spec.ts',
'jest.config.ts',
]);
expect(readJson(tree, 'libs/lib-one/tsconfig.lib.json').exclude).toEqual([
'src/**/*.spec.ts',
'jest.config.ts',
]);
expect(readJson(tree, 'libs/lib-one/tsconfig.lib.json').include).toEqual([
'src/**/*.ts',
]);
}

View File

@ -1,180 +0,0 @@
import {
formatFiles,
getProjects,
joinPathFragments,
logger,
ProjectConfiguration,
readJson,
stripIndents,
Tree,
updateJson,
updateProjectConfiguration,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { extname } from 'path';
import { JestExecutorOptions } from '../../executors/jest/schema';
const allowedExt = ['.ts', '.js'];
function updateTsConfig(tree: Tree, tsConfigPath: string) {
try {
updateJson(
tree,
tsConfigPath,
(json) => {
json.exclude = Array.from(
new Set([...(json.exclude || []), 'jest.config.ts'])
);
return json;
},
{
allowTrailingComma: true,
disallowComments: false,
}
);
} catch (e) {
logger.warn(
stripIndents`Nx Unable to update ${tsConfigPath}. Please manually ignore the jest.config.ts file.`
);
}
}
function addEsLintIgnoreComments(tree: Tree, filePath: string) {
if (tree.exists(filePath)) {
const contents = tree.read(filePath, 'utf-8');
if (!contents.startsWith('/* eslint-disable */')) {
tree.write(
filePath,
`/* eslint-disable */
${contents}`
);
}
}
}
function isJestConfigValid(tree: Tree, options: JestExecutorOptions) {
const configExt = extname(options.jestConfig);
if (!tree.exists(options.jestConfig) || !allowedExt.includes(configExt)) {
logger.debug(
`unable to update file because it doesn't exist or is not a js or ts file. Config: ${
options.jestConfig
}. Exists?: ${tree.exists(options.jestConfig)}`
);
return false;
}
return true;
}
function updateTsconfigSpec(
tree: Tree,
projectConfig: ProjectConfiguration,
path,
options: { isNextWithProjectParse: boolean; tsConfigPath: string } = {
isNextWithProjectParse: false,
tsConfigPath: '',
}
) {
updateJson(tree, joinPathFragments(projectConfig.root, path), (json) => {
json.include = Array.from(
new Set([...(json.include || []), 'jest.config.ts'])
);
if (options.isNextWithProjectParse) {
const tsConfig = readJson(tree, options.tsConfigPath);
const tsConfigExclude = (tsConfig.exclude || []).filter(
(e) => e !== 'jest.config.ts'
);
json.exclude = Array.from(
new Set([...(json.exclude || []), ...tsConfigExclude])
);
}
return json;
});
}
function isNextWithProjectLint(
projectConfig: ProjectConfiguration,
esLintJson: any
) {
const esLintOverrides = esLintJson?.overrides?.find((o) =>
['*.ts', '*.tsx', '*.js', '*.jsx'].every((ext) => o.files.includes(ext))
);
// check if it's a next app and has a parserOptions.project set in the eslint overrides
return !!(
projectConfig?.targets?.['build']?.executor === '@nrwl/next:build' &&
esLintOverrides?.parserOptions?.project
);
}
export async function updateJestConfigExt(tree: Tree) {
if (tree.exists('jest.config.js')) {
tree.rename('jest.config.js', 'jest.config.ts');
}
const projects = getProjects(tree);
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options, projectName, target, configuration) => {
const projectConfig = projects.get(projectName);
if (!options.jestConfig || !isJestConfigValid(tree, options)) {
return;
}
addEsLintIgnoreComments(tree, options.jestConfig);
const newJestConfigPath = options.jestConfig.replace('.js', '.ts');
tree.rename(options.jestConfig, newJestConfigPath);
const rootFiles = tree.children(projectConfig.root);
for (const fileName of rootFiles) {
if (fileName === 'tsconfig.json') {
const filePath = joinPathFragments(projectConfig.root, fileName);
const tsConfig = readJson(tree, filePath, {
allowTrailingComma: true,
disallowComments: false,
});
if (tsConfig.references) {
for (const { path } of tsConfig.references) {
// skip as editor.json should include everything anyway.
if (path.endsWith('tsconfig.editor.json')) {
continue;
}
if (path.endsWith('tsconfig.spec.json')) {
const eslintPath = joinPathFragments(
projectConfig.root,
'.eslintrc.json'
);
updateTsconfigSpec(tree, projectConfig, path, {
isNextWithProjectParse: tree.exists(eslintPath)
? isNextWithProjectLint(
projectConfig,
readJson(tree, eslintPath)
)
: false,
tsConfigPath: filePath,
});
continue;
}
updateTsConfig(tree, joinPathFragments(projectConfig.root, path));
}
} else {
updateTsConfig(tree, filePath);
}
}
}
projectConfig.targets[target].options.jestConfig = newJestConfigPath;
updateProjectConfiguration(tree, projectName, projectConfig);
}
);
await formatFiles(tree);
}
export default updateJestConfigExt;

View File

@ -1,76 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Jest Migration (v14.1.2) should convert module.exports => export default 1`] = `
"const { getJestProjects } = require('@nrwl/jest');
const nxPreset = require('@nrwl/jest/preset');
const someFn = () => ({more: 'stuff'});
module.export.abc = someFn;
export default {
...nxPreset,
more: 'stuff',
someFn,
projects: getJestProjects()
};"
`;
exports[`Jest Migration (v14.1.2) should update individual project jest configs 1`] = `
"
const nxPreset = require('@nrwl/jest/preset').default;
const someOtherImport = require('../something/else.js');
export default {
...someOtherImport,
...nxPreset,
displayName: 'lib-one',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/apps/lib-one',
transform: {
'^.+\\\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
transformIgnorePatterns: ['node_modules/(?!.*\\\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
"
`;
exports[`Jest Migration (v14.1.2) should work with multiple configurations 1`] = `
"
const nxPreset = require('@nrwl/jest/preset').default;
const someOtherImport = require('../something/else.js');
export default {
...someOtherImport,
...nxPreset,
displayName: 'lib-one',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/apps/lib-one',
transform: {
'^.+\\\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
transformIgnorePatterns: ['node_modules/(?!.*\\\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
"
`;

View File

@ -1,201 +0,0 @@
import {
readProjectConfiguration,
stripIndents,
Tree,
updateProjectConfiguration,
updateJson,
readJson,
} from '@nx/devkit';
import { createTree, createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryGenerator as workspaceLib } from '@nx/js';
import {
updateExportsJestConfig,
updateRootFiles,
updateToDefaultExport,
} from './update-exports-jest-config';
describe('Jest Migration (v14.1.2)', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should update root jest files', () => {
tree.write(
'jest.config.ts',
stripIndents`
const { getJestProjects } = require('@nrwl/jest');
module.exports = {
projects: getJestProjects()
};`
);
tree.write(
'jest.preset.ts',
stripIndents`
const nxPreset = require('@nrwl/jest/preset');
module.exports = { ...nxPreset };`
);
const status = updateRootFiles(tree);
expect(status).toEqual({ didUpdateRootPreset: true });
expect(tree.read('jest.config.ts', 'utf-8')).toEqual(stripIndents`
const { getJestProjects } = require('@nrwl/jest');
export default {
projects: getJestProjects()
};
`);
expect(tree.read('jest.preset.js', 'utf-8')).toEqual(stripIndents`
const nxPreset = require('@nrwl/jest/preset').default;
module.exports = { ...nxPreset };`);
});
it('should update individual project jest configs', async () => {
await workspaceLib(tree, { name: 'lib-one' });
const projectConfiguration = readProjectConfiguration(tree, 'lib-one');
updateProjectConfiguration(tree, 'lib-one', {
...projectConfiguration,
targets: {
...projectConfiguration.targets,
test: {
...projectConfiguration.targets.test,
executor: '@nrwl/jest:jest',
},
},
});
tree.rename('jest.preset.js', 'jest.preset.ts');
tree.write(
'libs/lib-one/jest.config.ts',
`
const nxPreset = require('@nrwl/jest/preset');
const someOtherImport = require('../something/else.js');
module.exports = {
...someOtherImport,
...nxPreset,
displayName: 'lib-one',
preset: '../../jest.preset.ts',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/apps/lib-one',
transform: {
'^.+\\\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
transformIgnorePatterns: ['node_modules/(?!.*\\\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
`
);
updateJson(tree, 'package.json', (json) => {
delete json.devDependencies['ts-node'];
return json;
});
expect(
readJson(tree, 'package.json').devDependencies['ts-node']
).toBeUndefined();
updateExportsJestConfig(tree);
const config = tree.read('libs/lib-one/jest.config.ts', 'utf-8');
expect(readJson(tree, 'package.json').devDependencies['ts-node']).toEqual(
'10.9.1'
);
expect(config).toMatchSnapshot();
});
it('should work with multiple configurations', async () => {
await workspaceLib(tree, { name: 'lib-one' });
tree.rename('jest.preset.js', 'jest.preset.ts');
updateProjectConfiguration(tree, 'lib-one', {
...readProjectConfiguration(tree, 'lib-one'),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib-one/jest.config.ts',
passWithoutTests: true,
},
configurations: {
production: {
silent: true,
},
},
},
},
});
tree.write(
'libs/lib-one/jest.config.ts',
`
const nxPreset = require('@nrwl/jest/preset');
const someOtherImport = require('../something/else.js');
module.exports = {
...someOtherImport,
...nxPreset,
displayName: 'lib-one',
preset: '../../jest.preset.ts',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/apps/lib-one',
transform: {
'^.+\\\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
transformIgnorePatterns: ['node_modules/(?!.*\\\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
`
);
updateExportsJestConfig(tree);
const config = tree.read('libs/lib-one/jest.config.ts', 'utf-8');
expect(config).toMatchSnapshot();
expect(tree.exists('jest.preset.ts')).toBeFalsy();
expect(tree.exists('jest.preset.js')).toBeTruthy();
});
it('should convert module.exports => export default', () => {
tree = createTree();
tree.write(
'jest.config.js',
stripIndents`
const { getJestProjects } = require('@nrwl/jest');
const nxPreset = require('@nrwl/jest/preset');
const someFn = () => ({more: 'stuff'});
module.export.abc = someFn;
module.exports = {
...nxPreset,
more: 'stuff',
someFn,
projects: getJestProjects()
};`
);
updateToDefaultExport(tree, 'jest.config.js');
expect(tree.read('jest.config.js', 'utf-8')).toMatchSnapshot();
});
});

View File

@ -1,93 +0,0 @@
import type { Tree } from '@nx/devkit';
import { addDependenciesToPackageJson } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { tsNodeVersion } from '../../utils/versions';
import { tsquery } from '@phenomnomnominal/tsquery';
import type { BinaryExpression } from 'typescript';
import type { JestExecutorOptions } from '../../executors/jest/schema';
export function updateExportsJestConfig(tree: Tree) {
const { didUpdateRootPreset } = updateRootFiles(tree);
let shouldInstallTsNode = false;
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options) => {
if (options.jestConfig && tree.exists(options.jestConfig)) {
if (options.jestConfig.endsWith('.ts')) {
updateToDefaultExport(tree, options.jestConfig);
shouldInstallTsNode = true;
}
const updatedImport = updateNxPresetImport(
tree.read(options.jestConfig, 'utf-8')
);
tree.write(options.jestConfig, updatedImport);
// jest.preset.ts => jest.preset.js
if (didUpdateRootPreset) {
const projectConfig = tree.read(options.jestConfig, 'utf-8');
const updatedConfig = projectConfig.replace(
/(preset:\s*['"][.\/]*)(jest\.preset\.ts)(['"])/g,
'$1jest.preset.js$3'
);
tree.write(options.jestConfig, updatedConfig);
}
}
}
);
return shouldInstallTsNode
? addDependenciesToPackageJson(tree, {}, { 'ts-node': tsNodeVersion })
: () => {};
}
export function updateRootFiles(tree: Tree): { didUpdateRootPreset: boolean } {
let didUpdateRootPreset = false;
if (tree.exists('jest.config.ts')) {
updateToDefaultExport(tree, 'jest.config.ts');
}
if (tree.exists('jest.preset.ts')) {
// fix those who ran v14 migration where this was renamed.
tree.rename('jest.preset.ts', 'jest.preset.js');
didUpdateRootPreset = true;
}
if (tree.exists('jest.preset.js')) {
const newContents = updateNxPresetImport(
tree.read('jest.preset.js', 'utf-8')
);
tree.write('jest.preset.js', newContents);
}
return {
didUpdateRootPreset,
};
}
function updateNxPresetImport(fileContents: string): string {
return fileContents.replace(
/require\(['"]@nrwl\/jest\/preset['"]\)[;\s]*?[\n\r]/g,
`require('@nrwl/jest/preset').default;
`
);
}
export function updateToDefaultExport(tree: Tree, filePath: string) {
const newConfig = tsquery.replace(
tree.read(filePath, 'utf-8'),
'ExpressionStatement BinaryExpression',
(node: BinaryExpression) => {
if (node.left.getText() === 'module.exports') {
return `export default ${node.right.getText()}`;
}
return node.getText();
}
);
tree.write(filePath, newConfig);
}
export default updateExportsJestConfig;

View File

@ -1,221 +0,0 @@
import {
readJson,
readProjectConfiguration,
updateJson,
updateProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryGenerator as workspaceLib } from '@nx/js';
import {
checkDeps,
updateConfigsJest28,
updateJestConfig,
} from './update-configs-jest-28';
const mockJestConfig = `
import { nxPreset } from '@nrwl/jest/preset'
const myGlobals = ['Math', 'Promise'];
export default {
...nxPreset,
displayName: 'test-ng-app',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
extraGlobals: ['Math', 'Something'],
extraGlobals: [],
extraGlobals: myGlobals,
timers: 'fake',
timers: 'modern',
timers: 'legacy',
timers: 'real',
testURL: 'http://localhost',
testURL: "123abc",
testURL: \`BLAH\`,
testEnvironment: 'jsdom',
testRunner: 'jest-jasmine2',
}
`;
describe('Jest Migration - jest 28 config support', () => {
it('should update "extraGlobals" config option', () => {
const actual = updateJestConfig(mockJestConfig);
expect(actual).not.toContain(`extraGlobals`);
expect(actual).toContain(`sandboxInjectedGlobals: ['Math', 'Something'],`);
expect(actual).toContain(`sandboxInjectedGlobals: [],`);
expect(actual).toContain(`sandboxInjectedGlobals: myGlobals,`);
});
it('should update "testURL" config option', () => {
const actual = updateJestConfig(mockJestConfig);
expect(actual).not.toContain(`testURL`);
expect(actual).toContain(
`testEnvironmentOptions: {url: 'http://localhost'},`
);
expect(actual).toContain(`testEnvironmentOptions: {url: "123abc"},`);
expect(actual).toContain(`testEnvironmentOptions: {url: \`BLAH\`},`);
});
it('should update "timers" config option', () => {
const actual = updateJestConfig(mockJestConfig);
expect(actual).not.toContain(`timers`);
expect(actual).toContain(`fakeTimers: { enableGlobally: false },`);
expect(actual).toContain(`fakeTimers: { enableGlobally: true },`);
expect(actual).toContain(`fakeTimers: { enableGlobally: true },`);
expect(actual).toContain(
`fakeTimers: { enableGlobally: true, legacyFakeTimers: true },`
);
});
it('should update jest-environment-jsdom if being used', async () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
`package.json`,
`{
"name": "jest-28-test",
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"jest": "^28.1.1",
"jest-environment-jsdom": "^27.1.0",
"jest-preset-angular": "^11.0.0",
"nx": "14.1.6",
"ts-jest": "^27.0.2",
"ts-node": "9.1.1",
"typescript": "~4.6.2"
},
"dependencies": {
}
}
`
);
const actual = checkDeps(tree);
expect(actual).toEqual({
'jest-environment-jsdom': '28.1.1',
});
});
it('should update jest-jasmine2 if being used as a test runner', () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
`package.json`,
`{
"name": "jest-28-test",
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"jest": "^27.1.1",
"jest-jasmine2": "^27.1.0",
"nx": "14.1.6",
"ts-jest": "^27.0.2",
"ts-node": "9.1.1",
"typescript": "~4.6.2"
},
"dependencies": {
}
}
`
);
tree.write(
'jest.preset.js',
`
const nxPreset = require('@nx/jest/preset').default;
module.exports = {
...nxPreset,
testRunner: 'jest-jasmine2',
};`
);
const actual = checkDeps(tree);
expect(actual).toEqual({
'jest-jasmine2': '28.1.1',
});
});
it('should not install deps if they are not used', () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
`package.json`,
`{
"name": "jest-28-test",
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"jest": "^27.1.0",
"nx": "14.1.6",
"ts-jest": "^27.0.2",
"ts-node": "9.1.1",
"typescript": "~4.6.2"
},
"dependencies": {
}
}
`
);
const actual = checkDeps(tree);
expect(actual).toEqual({});
});
it('should update deps from jest.config.ts', async () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await workspaceLib(tree, { name: 'my-lib', unitTestRunner: 'jest' });
const projectConfiguration = readProjectConfiguration(tree, 'my-lib');
updateProjectConfiguration(tree, 'my-lib', {
...projectConfiguration,
targets: {
...projectConfiguration.targets,
test: {
...projectConfiguration.targets.test,
executor: '@nrwl/jest:jest',
},
},
});
updateJson(tree, 'package.json', (json) => {
json.devDependencies['jest'] = '27.1.1';
json.devDependencies['jest-environment-jsdom'] = '27.1.1';
return json;
});
tree.write(
'libs/my-lib/jest.config.ts',
`
export default {
displayName: 'test-ng-app',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
useESM: true,
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\\\.(html|svg)$',
},
},
testEnvironment: 'jsdom',
testRunner: 'jest-jasmine2',
coverageDirectory: '../../coverage/apps/test-ng-app',
transform: {
'^.+\\\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
transformIgnorePatterns: [
'node_modules/(?!.*\\\\.mjs$|rxjs)',
// 'node_modules/(?!rxjs)'
],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
}`
);
updateConfigsJest28(tree);
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies).toEqual(
expect.objectContaining({
'jest-environment-jsdom': '28.1.1',
'jest-jasmine2': '28.1.1',
})
);
});
});

View File

@ -1,117 +0,0 @@
import { addDependenciesToPackageJson, readJson, Tree } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
import { isStringLiteralLike, PropertyAssignment } from 'typescript';
import { JestExecutorOptions } from '../../executors/jest/schema';
import {
findRootJestConfig,
findRootJestPreset,
} from '../../utils/config/find-root-jest-files';
const jestVersion = '28.1.1';
const JASMINE_TEST_RUNNER = /(testRunner:\s*['"`])(jest-jasmine2)(['"`])/g;
const JSDOM_TEST_ENV = /(testEnvironment:\s*['"`])(jsdom)(['"`])/g;
export function updateConfigsJest28(tree: Tree) {
let devDeps = checkDeps(tree);
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options) => {
if (options.jestConfig && tree.exists(options.jestConfig)) {
const updatedConfig = updateJestConfig(
tree.read(options.jestConfig, 'utf-8')
);
tree.write(options.jestConfig, updatedConfig);
const projectConfigCheck = testFileForDep(updatedConfig);
devDeps = { ...devDeps, ...projectConfigCheck };
}
}
);
return addDependenciesToPackageJson(tree, {}, devDeps);
}
export function updateJestConfig(config: string): string {
let content = tsquery.replace(
config,
'PropertyAssignment:has(Identifier[name="testURL"])',
(node: PropertyAssignment) => {
const value = node?.initializer?.getText();
return `testEnvironmentOptions: {url: ${value}}`;
}
);
content = tsquery.replace(
content,
'PropertyAssignment > Identifier[name="extraGlobals"]',
() => {
return 'sandboxInjectedGlobals';
}
);
return tsquery.replace(
content,
'PropertyAssignment:has(Identifier[name="timers"])',
(node: PropertyAssignment) => {
// must guard against non string properties as that means it's already been manually migrated
if (node?.initializer && isStringLiteralLike(node.initializer)) {
const value = node?.initializer.getText().trim() as
| 'fake'
| 'modern'
| 'real'
| 'legacy';
// use .includes to ignore the different quotes (' " `)
if (value.includes('fake') || value.includes('modern')) {
return `fakeTimers: { enableGlobally: true }`;
}
if (value.includes('real')) {
return `fakeTimers: { enableGlobally: false }`;
}
if (value.includes('legacy')) {
return `fakeTimers: { enableGlobally: true, legacyFakeTimers: true }`;
}
}
}
);
}
export function checkDeps(tree: Tree): Record<string, string> {
const packageJson = readJson(tree, 'package.json');
let devDeps = {};
if (packageJson.devDependencies['jest-preset-angular']) {
devDeps['jest-environment-jsdom'] = jestVersion;
}
const rootJestConfig = findRootJestConfig(tree);
if (rootJestConfig) {
const rootConfigCheck = testFileForDep(tree.read(rootJestConfig, 'utf-8'));
devDeps = { ...devDeps, ...rootConfigCheck };
}
const rootJestPreset = findRootJestPreset(tree);
if (rootJestPreset) {
const rootPresetCheck = testFileForDep(tree.read(rootJestPreset, 'utf-8'));
devDeps = { ...devDeps, ...rootPresetCheck };
}
return devDeps;
}
function testFileForDep(config: string): Record<string, string> {
const deps = {};
if (JASMINE_TEST_RUNNER.test(config)) {
deps['jest-jasmine2'] = jestVersion;
}
if (JSDOM_TEST_ENV.test(config)) {
deps['jest-environment-jsdom'] = jestVersion;
}
return deps;
}
export default updateConfigsJest28;

View File

@ -1,346 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryGenerator as workspaceLib } from '@nx/js';
import {
readProjectConfiguration,
updateProjectConfiguration,
} from 'nx/src/generators/utils/project-configuration';
import {
updateJestFnMocks,
updateJestImports,
updateJestTimers,
updateTestsJest28,
} from './update-tests-jest-28';
describe('Jest Migration - jest 28 test files', () => {
it('should convert test files', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await workspaceLib(tree, {
name: 'blah',
linter: undefined,
unitTestRunner: 'jest',
});
const projectConfiguration = readProjectConfiguration(tree, 'blah');
updateProjectConfiguration(tree, 'blah', {
...projectConfiguration,
targets: {
...projectConfiguration.targets,
test: {
...projectConfiguration.targets.test,
executor: '@nrwl/jest:jest',
},
},
});
await workspaceLib(tree, {
name: 'blah-again',
linter: undefined,
unitTestRunner: 'jest',
js: true,
});
const projectConfiguration2 = readProjectConfiguration(tree, 'blah-again');
updateProjectConfiguration(tree, 'blah-again', {
...projectConfiguration2,
targets: {
...projectConfiguration2.targets,
test: {
...projectConfiguration2.targets.test,
executor: '@nrwl/jest:jest',
},
},
});
tree.write(
'libs/blah/src/lib/something/something.spec.ts',
`
import expect from 'expect';
import { something } from './something';
import { jest } from '@jest/globals';
describe('my cool test', () => {
it('should do something', () => {
jest.useFakeTimers('modern')
const mock = jest.fn<ReturnType<typeof something>, Parameters<typeof something>>();
expect(something()).toBe(true)
})
it('should do something pt 2', () => {
jest.useFakeTimers('legacy')
const mock = jest.fn<boolean, MyType[]>();
expect(something()).toBe(true)
})
it('should do something', () => {
const mock = jest.fn<Promise<{}>, []>();
expect(something()).toBe(true)
})
})`
);
tree.write(
'libs/blah/src/lib/something/another.spec.ts',
`
import expect from 'expect';
import { something } from './something';
describe('my cool test', () => {
it('should do something', () => {
jest.useFakeTimers('modern')
const mock = jest.fn<ReturnType<typeof something>, Parameters<typeof something>>();
expect(something()).toBe(true)
})
it('should do something pt 2', () => {
jest.useFakeTimers('legacy')
const mock = jest.fn<boolean, MyType[]>();
expect(something()).toBe(true)
})
it('should do something', () => {
const mock = jest.fn<Promise<{}>, []>();
expect(something()).toBe(true)
})
})`
);
const pc = readProjectConfiguration(tree, 'blah');
pc.targets['test'].configurations = {
production: {
...pc.targets['test'].options,
ci: true,
},
};
pc.targets['another-config'] = pc.targets['test'];
updateProjectConfiguration(tree, 'blah', pc);
updateTestsJest28(tree);
expect(tree.read('libs/blah/src/lib/something/something.spec.ts', 'utf-8'))
.toEqual(`
import { expect } from 'expect';
import { something } from './something';
import { jest } from '@jest/globals';
describe('my cool test', () => {
it('should do something', () => {
jest.useFakeTimers()
const mock = jest.fn<typeof something>();
expect(something()).toBe(true)
})
it('should do something pt 2', () => {
jest.useFakeTimers({ legacyFakeTimers: true })
const mock = jest.fn<() => boolean>();
expect(something()).toBe(true)
})
it('should do something', () => {
const mock = jest.fn<() => Promise<{}>>();
expect(something()).toBe(true)
})
})`);
expect(tree.read('libs/blah/src/lib/something/another.spec.ts', 'utf-8'))
.toEqual(`
import { expect } from 'expect';
import { something } from './something';
describe('my cool test', () => {
it('should do something', () => {
jest.useFakeTimers()
const mock = jest.fn<ReturnType<typeof something>, Parameters<typeof something>>();
expect(something()).toBe(true)
})
it('should do something pt 2', () => {
jest.useFakeTimers({ legacyFakeTimers: true })
const mock = jest.fn<boolean, MyType[]>();
expect(something()).toBe(true)
})
it('should do something', () => {
const mock = jest.fn<Promise<{}>, []>();
expect(something()).toBe(true)
})
})`);
});
it('should update ts-jest/utils to jest-mock', () => {
// import { mocked } from 'ts-jest/utils' => import { mocked } from 'jest-mock';
// const { mocked } = require('ts-jest/utils'); => const { mocked } = require('jest-mock');
const actual = updateJestImports(`
import { mocked } from 'ts-jest/utils';
import { somethingElse } from 'ts-jest/utils';
const { mocked } = require('ts-jest/utils');
const { somethingElse } = require('ts-jest/utils');
import * from ts from 'typescript'
const mockTs = mocked(ts);
describe('something expected', () => {
it('should do something', () => {
const actual = somethingExpected('abc');
expect(1 + 1).toBe(2);
});
})
`);
const expected = `
import { mocked } from 'jest-mock';
import { somethingElse } from 'ts-jest/utils';
const { mocked } = require('jest-mock');
const { somethingElse } = require('ts-jest/utils');
import * from ts from 'typescript'
const mockTs = mocked(ts);
describe('something expected', () => {
it('should do something', () => {
const actual = somethingExpected('abc');
expect(1 + 1).toBe(2);
});
})
`;
expect(actual).toEqual(expected);
});
it('should update "expect" default import to named import', () => {
// import expect from 'expect' => import { expect } from 'expect'
// const expect = require('expect') => const { expect } = require('expect')
const actual = updateJestImports(`
import expect from 'expect';
const expect = require('expect');
const expect = require('something-else');
import somethingExpected from 'my-expect';
describe('something expected', () => {
it('should do something', () => {
const actual = somethingExpected('abc');
expect(1 + 1).toBe(2);
});
})
`);
const expected = `
import { expect } from 'expect';
const { expect } = require('expect');
const expect = require('something-else');
import somethingExpected from 'my-expect';
describe('something expected', () => {
it('should do something', () => {
const actual = somethingExpected('abc');
expect(1 + 1).toBe(2);
});
})
`;
expect(actual).toEqual(expected);
});
it('should update jest.useFakeTimers() to new timer api', () => {
const actual = updateJestTimers(
`
describe('some test', () => {
it('should do something', () => {
jest.useFakeTimers('modern')
})
it('should do something else', () => {
jest.useFakeTimers('legacy')
})
})
`,
false
);
expect(actual).not.toContain("jest.useFakeTimers('modern')");
expect(actual).not.toContain("jest.useFakeTimers('legacy')");
expect(actual).toContain('jest.useFakeTimers()');
expect(actual).toContain('jest.useFakeTimers({ legacyFakeTimers: true })');
});
it('should update jest timers to new timer api w/legacy timers set in config', () => {
const actual = updateJestTimers(
`
describe('some test', () => {
it('should do something', () => {
jest.useFakeTimers('modern')
})
it('should do something else', () => {
jest.useFakeTimers('legacy')
})
})
`,
true
);
// jest.useFakeTimers('modern') -> jest.useFakeTimers()
// jest.useFakeTimers('legacy') -> jest.useFakeTimers({legacyFakeTimers: true})
// if legacyFakeTimers is true in config, then
// jest.useFakeTimers('modern') -> jest.useRealTimers({legacyFakeTimers: false})
expect(actual).toEqual(`
describe('some test', () => {
it('should do something', () => {
jest.useRealTimers({ legacyFakeTimers: false })
})
it('should do something else', () => {
jest.useFakeTimers({ legacyFakeTimers: true })
})
})
`);
});
it('should update jest.fn usage', () => {
const actual = updateJestFnMocks(`
import add from './add';
describe('something', () => {
it('should do something', () => {
const noTypedMockAdd = jest.fn();
})
it('should do something', () => {
const asyncMock = jest.fn<Promise<string>, []>()
})
it('should do something', () => {
const mock = jest.fn<number, string[]>()
})
it('should do something', () => {
const mockAdd = jest.fn<ReturnType<typeof add>, Parameters<typeof add>>();
})
})
`);
expect(actual).not.toContain(
'jest.fn<ReturnType<typeof add>, Parameters<typeof add>>()'
);
expect(actual).toContain('jest.fn<typeof add>()');
expect(actual).not.toContain('jest.fn<number, []>()');
expect(actual).toContain('jest.fn<() => number>()');
expect(actual).not.toContain('jest.fn<Promise<string>, []>()');
expect(actual).toContain('jest.fn<() => Promise<string>>()');
});
it('should leave a TODO comment if it does not know how to upgrade', () => {
const actual = updateJestFnMocks(`
import add from './add';
describe('something', () => {
it('should do something', () => {
const noTypedMockAdd = jest.fn<string, MyType>();
})
})
`);
expect(actual).toContain(
'/** TODO: Update jest.fn<T>() type args for Jest v28 https://jestjs.io/docs/upgrading-to-jest28#jestfn */ jest.fn<string, MyType>();'
);
});
it('should not touch already migrate jest.fn usage', () => {
const original = `
import add from './add';
describe('something', () => {
it('should do something', () => {
const noTypedMockAdd = jest.fn();
})
it('should do something', () => {
const asyncMock = jest.fn<() => number>>()
})
it('should do something', () => {
const mock = jest.fn<() => Promise<string>>()
})
it('should do something', () => {
const mockAdd = jest.fn<typeof add>();
})
})
`;
const actual = updateJestFnMocks(original);
expect(actual).toEqual(original);
expect(actual).not.toContain(
'/** TODO: Update jest.fn() types for Jest v28 https://jestjs.io/docs/upgrading-to-jest28#jestfn'
);
});
});

View File

@ -1,202 +0,0 @@
import {
readProjectConfiguration,
Tree,
visitNotIgnoredFiles,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
import * as ts from 'typescript';
import { JestExecutorOptions } from '../../executors/jest/schema';
export function updateTestsJest28(tree: Tree) {
const testFilePatterns = /.*.(spec|test)\.(ts|js)x?/g;
const legacyTimers =
/(timers:\s*['"`]legacy['"`])|(legacyFakeTimers:\s*true)/g;
forEachExecutorOptions<JestExecutorOptions>(
tree,
'@nrwl/jest:jest',
(options, projectName) => {
const projectConfig = readProjectConfiguration(tree, projectName);
const isUsingLegacyTimers =
options.jestConfig &&
tree.exists(options.jestConfig) &&
legacyTimers.test(tree.read(options.jestConfig, 'utf-8'));
visitNotIgnoredFiles(tree, projectConfig.root, (filePath) => {
if (!filePath.match(testFilePatterns)) {
return;
}
let fileContent = tree.read(filePath, 'utf-8');
fileContent = updateJestTimers(fileContent, isUsingLegacyTimers);
if (fileContent.includes('@jest/globals')) {
fileContent = updateJestFnMocks(fileContent);
}
fileContent = updateJestImports(fileContent);
tree.write(filePath, fileContent);
});
}
);
}
/**
* jest.useFakeTimers('modern') -> jest.useFakeTimers()
* jest.useFakeTimers('legacy') -> jest.useFakeTimers({legacyFakeTimers: true})
* if legacyFakeTimers is true in config, then
* jest.useFakeTimers('modern') -> jest.useRealTimers({legacyFakeTimers: false})
*/
export function updateJestTimers(
fileContents: string,
legacyFakeTimersInConfig: boolean
) {
return tsquery.replace(
fileContents,
'CallExpression',
(node: ts.StringLiteral) => {
if (!node?.getText().startsWith('jest.useFakeTimers')) {
return;
}
const timerType = node.getText();
// will be modern or legacy with quotes
// just make sure it's included to ignore different quote types
if (timerType.includes('legacy')) {
return 'jest.useFakeTimers({ legacyFakeTimers: true })';
}
if (legacyFakeTimersInConfig) {
// using modern but have config set to legacy
return 'jest.useRealTimers({ legacyFakeTimers: false })';
}
// have to include space otherwise empty string will not remove the string literal
return 'jest.useFakeTimers()';
}
);
}
/**
* make sure using jest.fn<T>
*/
function isTypedJestFnMock(node: ts.CallExpression): boolean {
return ts.isCallExpression(node) && node.getText().startsWith('jest.fn<');
}
/**
* has 2 args where the second is a tuple or array
* i.e.
* jest.fn<Promise<string>, []>()
* jest.fn<number, MyType[]>()
* jest.fn<number, [string, number, SomeType]>()
*/
function isValid2Args(node: ts.CallExpression): boolean {
const r =
node?.typeArguments.length === 2 &&
(node.typeArguments[1]?.kind === ts.SyntaxKind.TupleType ||
node.typeArguments[1]?.kind === ts.SyntaxKind.ArrayType);
return r;
}
/**
* has 1 arg where the type is NOT a FunctionType
* if it's a function type then it's already using the correct syntax
* i.e.
* jest.fn<string>()
* jest.fn<() => Promise<string>>() is already valid, don't change it.
*/
function isValid1Arg(node: ts.CallExpression): boolean {
const r =
node?.typeArguments.length === 1 &&
node.typeArguments[0]?.kind !== ts.SyntaxKind.FunctionType &&
node.typeArguments[0]?.kind !== ts.SyntaxKind.TypeQuery;
return r;
}
/**
* has a type reference as a type args
* jest.fn<ReturnType<typeof add>, Parameters<typeof add>>();
*/
function isValidTypeRef(node: ts.CallExpression): boolean {
const r =
node.typeArguments[0].kind === ts.SyntaxKind.TypeReference &&
!!(node.typeArguments?.[0] as ts.TypeReferenceNode)?.typeArguments;
return r;
}
/**
* has valid type args. prevent converting an already converted jest.fn<T>()
*/
function isValidType(node: ts.CallExpression): boolean {
const r =
node?.typeArguments.length === 1 &&
(node.typeArguments[0]?.kind === ts.SyntaxKind.FunctionType ||
node.typeArguments[0]?.kind === ts.SyntaxKind.TypeReference ||
node.typeArguments[0]?.kind === ts.SyntaxKind.TypeQuery ||
node.parent.getText().includes('/** TODO:')); // has already been marked by a previous run.
return r;
}
/**
* this only applies to tests using @jest/globals
* jest.fn<Promise<string>, []>() -> jest.fn<() => Promise<string>>()
* jest.fn<number, string[]>() -> jest.fn<() => number>()
* jest.fn<ReturnType<typeof add>, Parameters<typeof add>>(); -> jest.fn<typeof add>()
*/
export function updateJestFnMocks(fileContents: string): string {
return tsquery.replace(
fileContents,
'CallExpression',
(node: ts.CallExpression) => {
if (!isTypedJestFnMock(node) || isValidType(node)) {
return;
}
if (isValid2Args(node) || isValid1Arg(node)) {
return `${
node.getText().split('<')[0]
}<() => ${node.typeArguments[0].getText()}>()`;
}
if (isValidTypeRef(node)) {
const innerType = (node.typeArguments[0] as ts.TypeReferenceNode)
.typeArguments;
return `${node.getText().split('<')[0]}<${innerType[0].getText()}>()`;
}
return `/** TODO: Update jest.fn<T>() type args for Jest v28 https://jestjs.io/docs/upgrading-to-jest28#jestfn */ ${node.getText()}`;
}
);
}
/**
* import expect from 'expect' -> import { expect } from 'expect'
* const expect = require('expect') -> const { expect } = require('expect')
* import { mocked } from 'ts-jest/utils' => import { mocked } from 'jest-mock';
* const { mocked } = require('ts-jest/utils'); => const { mocked } = require('jest-mock');
*/
export function updateJestImports(content: string): string {
const mockUpdatedImports = tsquery.replace(
content,
':matches(ImportDeclaration:has(Identifier[name="mocked"]) StringLiteral[value="ts-jest/utils"], VariableStatement:has(Identifier[name="mocked"]) StringLiteral[value="ts-jest/utils"])',
() => {
return "'jest-mock'";
}
);
return tsquery.replace(
mockUpdatedImports,
':matches(ImportDeclaration:has(StringLiteral[value="expect"]), VariableDeclaration:has(StringLiteral[value="expect"]))',
(node: ts.ImportDeclaration | ts.VariableDeclaration) => {
if (ts.isImportDeclaration(node)) {
return `import { expect } from 'expect';`;
}
if (ts.isVariableDeclaration(node)) {
return `{ expect } = require('expect')`; // this query doesn't capture the ; so we don't need to add it in the replace.
}
return;
}
);
}
export default updateTestsJest28;

View File

@ -1,35 +1,5 @@
{
"generators": {
"update-node-executor": {
"cli": "nx",
"version": "13.8.5-beta.1",
"description": "Renames @nrwl/js:node to @nrwl/node:node",
"factory": "./src/migrations/update-13-8-5/update-node-executor"
},
"update-swcrc": {
"cli": "nx",
"version": "13.8.5-beta.1",
"description": "Adjust .swcrc to .lib.swcrc",
"factory": "./src/migrations/update-13-8-5/update-swcrc"
},
"update-swcrc-exclude": {
"cli": "nx",
"version": "13.10.1-beta.1",
"description": "Update .lib.swcrc to exclude missing test files",
"factory": "./src/migrations/update-13-10-1/update-lib-swcrc-exclude"
},
"exclude-jest-config-swcrc": {
"cli": "nx",
"version": "14.0.0-beta.2",
"description": "Exclude jest config from .lib.swcrc",
"factory": "./src/migrations/update-14-0-0/exclude-jest-config-swcrc"
},
"update-swcrc-path": {
"cli": "nx",
"version": "14.1.5-beta.0",
"description": "Rename option swcrcPath to swcrc, and resolve relative to workspace root",
"factory": "./src/migrations/update-14-1-5/update-swcrc-path"
},
"rename-swcrc-config": {
"cli": "nx",
"version": "15.8.0-beta.0",

View File

@ -1,36 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Update .lib.swcrc exclude should update the exclude pattern 1`] = `
"{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "commonjs",
"strict": true,
"noInterop": true
},
"sourceMaps": true,
"exclude": [
"jest.config.ts",
".*\\\\.spec.tsx?$",
".*\\\\.test.tsx?$",
"./src/jest-setup.ts$",
"./**/jest-setup.ts$",
".*.js$"
]
}
"
`;

View File

@ -1,90 +0,0 @@
import {
addProjectConfiguration,
ProjectConfiguration,
readJson,
Tree,
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import updateSwcRcExclude from './update-lib-swcrc-exclude';
const projectConfig: ProjectConfiguration = {
root: 'libs/swc-lib',
sourceRoot: 'libs/swc-lib/src',
targets: {
build: {
executor: '@nrwl/js:swc',
outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/libs/swc-lib',
main: 'libs/swc-lib/src/index.ts',
tsConfig: 'libs/swc-lib/tsconfig.lib.json',
assets: ['libs/swc-lib/*.md'],
},
},
},
};
const oldSwcRc = {
jsc: {
target: 'es2017',
parser: {
syntax: 'typescript',
decorators: true,
dynamicImport: true,
},
transform: {
decoratorMetadata: true,
legacyDecorator: true,
},
keepClassNames: true,
externalHelpers: true,
loose: true,
},
module: {
type: 'commonjs',
strict: true,
noInterop: true,
},
sourceMaps: true,
exclude: [
'./src/**/.*.spec.ts$',
'./**/.*.spec.ts$',
'./src/**/jest-setup.ts$',
'./**/jest-setup.ts$',
'./**/.*.js$',
],
};
describe('Update .lib.swcrc exclude', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'swc-lib', projectConfig);
tree.write('libs/swc-lib/.lib.swcrc', JSON.stringify(oldSwcRc));
});
it('should update the exclude pattern', () => {
updateSwcRcExclude(tree);
expect(tree.read('libs/swc-lib/.lib.swcrc', 'utf-8')).toMatchSnapshot();
});
it('should NOT update the exclude pattern if not present', () => {
updateJson(tree, 'libs/swc-lib/.lib.swcrc', (json) => {
delete json.exclude;
return json;
});
const before = readJson(tree, 'libs/swc-lib/.lib.swcrc');
updateSwcRcExclude(tree);
const after = readJson(tree, 'libs/swc-lib/.lib.swcrc');
expect(after.exclude).toBeFalsy();
expect(after).toEqual(before);
});
it('should do nothing if .lib.swcrc doest not exist', () => {
tree.delete('libs/swc-lib/.lib-swcrc');
expect(() => updateSwcRcExclude(tree)).not.toThrowError();
});
});

View File

@ -1,41 +0,0 @@
import { readProjectConfiguration, Tree, updateJson } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { join } from 'path';
import { SwcExecutorOptions } from '../../utils/schema';
import { defaultExclude } from '../../utils/swc/add-swc-config';
export default function updateSwcRcExclude(tree: Tree) {
forEachExecutorOptions(
tree,
'@nrwl/js:swc',
(config: SwcExecutorOptions, projectName) => {
const projectConfig = readProjectConfiguration(tree, projectName);
const libSwcPath = join(projectConfig.root, '.lib.swcrc');
if (!tree.exists(libSwcPath)) return;
updateJson(
tree,
libSwcPath,
(json) => {
if (json.exclude) {
const excludePatterns = new Set([
...defaultExclude,
...json.exclude,
]);
// remove old patterns that are duplicate for new patterns
// defined in defaultExclude
excludePatterns.delete('./**/.*.spec.ts$');
excludePatterns.delete('./src/**/.*.spec.ts$');
excludePatterns.delete('./**/.*.js$');
excludePatterns.delete('./src/**/jest-setup.ts$');
json.exclude = [...excludePatterns];
}
return json;
},
{ expectComments: true }
);
}
);
}

View File

@ -1,59 +0,0 @@
import {
addProjectConfiguration,
readJson,
readProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './update-node-executor';
describe('Migration: rename execute to node', () => {
it(`should rename the "execute" executor to "node"`, async () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'myapp', {
root: 'apps/myapp',
sourceRoot: 'apps/myapp/src',
projectType: 'application',
targets: {
serve: {
executor: '@nrwl/js:node',
options: {},
},
},
});
const tasks = await update(tree);
expect(tasks).toBeDefined();
expect(readProjectConfiguration(tree, 'myapp')).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'myapp',
root: 'apps/myapp',
sourceRoot: 'apps/myapp/src',
projectType: 'application',
targets: {
serve: {
executor: '@nrwl/node:node',
options: {},
},
},
});
});
it(`should skip migration if no projects use @nrwl/js:node`, async () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'workspace.json',
JSON.stringify({
version: 2,
projects: {},
})
);
const tasks = await update(tree);
expect(tasks).toBeUndefined();
});
});

View File

@ -1,36 +0,0 @@
import {
addDependenciesToPackageJson,
formatFiles,
getProjects,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { nxVersion } from '@nx/workspace/src/utils/versions';
export default async function update(host: Tree) {
const projects = getProjects(host);
let installNeeded = false;
for (const [name, config] of projects.entries()) {
if (config?.targets?.serve?.executor !== '@nrwl/js:node') continue;
config.targets.serve.executor = '@nrwl/node:node';
installNeeded = true;
updateProjectConfiguration(host, name, config);
}
const task = installNeeded
? addDependenciesToPackageJson(
host,
{},
{
'@nrwl/node': nxVersion,
}
)
: undefined;
await formatFiles(host);
return task;
}

View File

@ -1,95 +0,0 @@
import {
ProjectConfiguration,
readJson,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryGenerator } from '../../generators/library/library';
import { defaultExclude } from '../../utils/swc/add-swc-config';
import update from './update-swcrc';
describe('Migration: adjust .swcrc', () => {
let tree: Tree;
let projectConfiguration: ProjectConfiguration;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, {
name: 'swc',
buildable: true,
linter: 'none',
unitTestRunner: 'none',
});
projectConfiguration = readProjectConfiguration(tree, 'swc');
updateProjectConfiguration(tree, 'swc', {
...projectConfiguration,
targets: {
...projectConfiguration.targets,
build: {
...projectConfiguration.targets['build'],
executor: '@nrwl/js:swc',
},
},
});
// re-read the project configuration
projectConfiguration = readProjectConfiguration(tree, 'swc');
});
it('should rename .swcrc to .lib.swcrc', async () => {
addSwcrc();
await update(tree);
expect(tree.exists('libs/swc/.swcrc')).toEqual(false);
expect(tree.exists('libs/swc/.lib.swcrc')).toEqual(true);
});
it('should assign default exclude if swcrc does not already have exclude', async () => {
addSwcrc();
await update(tree);
expect(readJson(tree, 'libs/swc/.lib.swcrc')['exclude']).toEqual(
defaultExclude
);
});
it('should use swcExclude (deprecated) to assign to exclude', async () => {
const swcExclude = ['./src/**/.*.spec.ts$'];
updateProjectConfiguration(tree, 'swc', {
...projectConfiguration,
targets: {
...projectConfiguration.targets,
build: {
...projectConfiguration.targets['build'],
options: {
...projectConfiguration.targets['build']['options'],
swcExclude,
},
},
},
});
addSwcrc();
await update(tree);
expect(readJson(tree, 'libs/swc/.lib.swcrc')['exclude']).toEqual(
swcExclude
);
});
it('should skip updating "exclude" if swcrc already has "exclude" field', async () => {
addSwcrc(true);
await update(tree);
expect(readJson(tree, 'libs/swc/.lib.swcrc')['exclude']).toEqual([
'./**/.*.spec.ts$',
]);
});
function addSwcrc(withExclude = false) {
tree.write(
'libs/swc/.swcrc',
JSON.stringify(withExclude ? { exclude: ['./**/.*.spec.ts$'] } : {})
);
}
});

View File

@ -1,39 +0,0 @@
import {
formatFiles,
getProjects,
readJson,
Tree,
updateJson,
} from '@nx/devkit';
import { join } from 'path';
import { defaultExclude } from '../../utils/swc/add-swc-config';
export default async function update(host: Tree) {
const projects = getProjects(host);
for (const config of projects.values()) {
if (config?.targets?.build?.executor !== '@nrwl/js:swc') continue;
const swcrcPath = join(config.root, '.swcrc');
if (!host.exists(swcrcPath)) continue;
// rename
const libSwcrcPath = join(config.root, '.lib.swcrc');
host.rename(swcrcPath, libSwcrcPath);
const swcrcContent = readJson(host, libSwcrcPath);
// skip if swcrc already has "exclude" field
if (swcrcContent['exclude']) continue;
// check swcExclude build options
const exclude =
config?.targets?.build?.options?.['swcExclude'] || defaultExclude;
updateJson(host, libSwcrcPath, (swcrc) => {
swcrc['exclude'] = exclude;
return swcrc;
});
}
await formatFiles(host);
}

View File

@ -1,36 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`JS Migration (v14.0.0) should update the exclude pattern 1`] = `
"{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "commonjs",
"strict": true,
"noInterop": true
},
"sourceMaps": true,
"exclude": [
"jest.config.js",
".*.spec.tsx?$",
".*.test.tsx?$",
"./src/jest-setup.ts$",
"./**/jest-setup.ts$",
".*.js$"
]
}
"
`;

View File

@ -1,89 +0,0 @@
import {
addProjectConfiguration,
readJson,
Tree,
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { excludeJestConfigSwcrc } from './exclude-jest-config-swcrc';
import { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
const projectConfig: ProjectConfiguration = {
root: 'libs/swc-lib',
sourceRoot: 'libs/swc-lib/src',
targets: {
build: {
executor: '@nrwl/js:swc',
outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/libs/swc-lib',
main: 'libs/swc-lib/src/index.ts',
tsConfig: 'libs/swc-lib/tsconfig.lib.json',
assets: ['libs/swc-lib/*.md'],
},
},
},
};
const oldSwcRc = {
jsc: {
target: 'es2017',
parser: {
syntax: 'typescript',
decorators: true,
dynamicImport: true,
},
transform: {
decoratorMetadata: true,
legacyDecorator: true,
},
keepClassNames: true,
externalHelpers: true,
loose: true,
},
module: {
type: 'commonjs',
strict: true,
noInterop: true,
},
sourceMaps: true,
exclude: [
'.*.spec.tsx?$',
'.*.test.tsx?$',
'./src/jest-setup.ts$',
'./**/jest-setup.ts$',
'.*.js$',
],
};
describe('JS Migration (v14.0.0)', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'swc-lib', projectConfig);
tree.write('libs/swc-lib/.lib.swcrc', JSON.stringify(oldSwcRc));
});
it('should update the exclude pattern', () => {
excludeJestConfigSwcrc(tree);
expect(tree.read('libs/swc-lib/.lib.swcrc', 'utf-8')).toMatchSnapshot();
});
it('should NOT update the exclude pattern if not present', () => {
updateJson(tree, 'libs/swc-lib/.lib.swcrc', (json) => {
delete json.exclude;
return json;
});
const before = readJson(tree, 'libs/swc-lib/.lib.swcrc');
excludeJestConfigSwcrc(tree);
const after = readJson(tree, 'libs/swc-lib/.lib.swcrc');
expect(after.exclude).toBeFalsy();
expect(after).toEqual(before);
});
it('should do nothing if .lib.swcrc doest not exist', () => {
tree.delete('libs/swc-lib/.lib-swcrc');
expect(() => excludeJestConfigSwcrc(tree)).not.toThrowError();
});
});

View File

@ -1,35 +0,0 @@
import { readProjectConfiguration, Tree, updateJson } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { SwcExecutorOptions } from '../../utils/schema';
import { join } from 'path';
export function excludeJestConfigSwcrc(tree: Tree) {
forEachExecutorOptions(
tree,
'@nrwl/js:swc',
(config: SwcExecutorOptions, projectName) => {
const projectConfig = readProjectConfiguration(tree, projectName);
const libSwcPath = join(projectConfig.root, '.lib.swcrc');
if (!tree.exists(libSwcPath)) return;
updateJson(
tree,
libSwcPath,
(json) => {
if (json.exclude) {
const excludePatterns = new Set([
'jest.config.js',
...json.exclude,
]);
json.exclude = [...excludePatterns];
}
return json;
},
{ expectComments: true }
);
}
);
}
export default excludeJestConfigSwcrc;

View File

@ -1,31 +0,0 @@
import { addProjectConfiguration, readProjectConfiguration } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import updateSwcrcPath from './update-swcrc-path';
describe('update-swcrc-path migration', () => {
it('should replace relative `swcrcPath` option with absolute `swcrc`', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'test-package', {
root: 'packages/test-package',
targets: {
build: {
executor: '@nrwl/js:swc',
options: {
swcrcPath: 'config/swcrc.json',
somethingThatShouldNotBeRemoved: true,
},
},
},
});
await updateSwcrcPath(tree);
const { targets, root } = readProjectConfiguration(tree, 'test-package');
expect(root).toBe('packages/test-package');
expect(targets.build.options.somethingThatShouldNotBeRemoved).toBeDefined();
expect(targets.build.options.swcrcPath).toBeUndefined();
expect(targets.build.options.swcrc).toBe(
'packages/test-package/config/swcrc.json'
);
});
});

View File

@ -1,46 +0,0 @@
import {
formatFiles,
joinPathFragments,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { SwcExecutorOptions } from '../../utils/schema';
type OldSwcExecutorOptions = SwcExecutorOptions & { swcrcPath?: string };
export async function updateSwcrcPath(tree: Tree) {
let changesMade = false;
forEachExecutorOptions(
tree,
'@nrwl/js:swc',
(_, projectName, targetName, configurationName) => {
const projectConfig = readProjectConfiguration(tree, projectName);
const executorOptions: OldSwcExecutorOptions = configurationName
? projectConfig.targets[targetName].configurations[configurationName]
: projectConfig.targets[targetName].options;
if (!executorOptions.swcrcPath) return;
const newSwcrcPath = joinPathFragments(
projectConfig.root,
executorOptions.swcrcPath
);
delete executorOptions.swcrcPath;
executorOptions.swcrc = newSwcrcPath;
updateProjectConfiguration(tree, projectName, projectConfig);
changesMade = true;
}
);
if (changesMade) {
await formatFiles(tree);
}
}
export default updateSwcrcPath;

View File

@ -1,53 +1,5 @@
{
"generators": {
"remove-eslint-project-config-if-no-type-checking-rules": {
"cli": "nx",
"version": "12.4.0-beta.0",
"description": "Remove ESLint parserOptions.project config if no rules requiring type-checking are in use",
"factory": "./src/migrations/update-12-4-0/remove-eslint-project-config-if-no-type-checking-rules"
},
"add-outputs": {
"cli": "nx",
"version": "12.9.0-beta.0",
"description": "Add outputs for caching",
"factory": "./src/migrations/update-12-9-0/add-outputs"
},
"remove-eslint-project-config-if-no-type-checking-rules-again": {
"cli": "nx",
"version": "12.9.0-beta.0",
"description": "Remove ESLint parserOptions.project config if no rules requiring type-checking are in use",
"factory": "./src/migrations/update-12-4-0/remove-eslint-project-config-if-no-type-checking-rules"
},
"eslint-8-updates": {
"cli": "nx",
"version": "13.3.0-beta.0",
"description": "Update eslint-rules jest.config.js in order to support ESLint v8 exports mapping, remove category field",
"factory": "./src/migrations/update-13-3-0/eslint-8-updates"
},
"add-swc-deps": {
"cli": "nx",
"version": "14.1.9-beta.0",
"description": "Adds @swc/core and @swc-node as a dev dep if you are using them",
"factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed"
},
"add-swc-deps-again": {
"cli": "nx",
"version": "14.2.3-beta.0",
"description": "Adds @swc/core and @swc-node as a dev dep if you are using them (repeated due to prior mistake)",
"factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed"
},
"experimental-to-utils-deps": {
"cli": "nx",
"version": "14.4.4",
"description": "Adds @typescript-eslint/utils as a dev dep",
"factory": "./src/migrations/update-14-4-4/experimental-to-utils-deps"
},
"experimental-to-utils-rules": {
"cli": "nx",
"version": "14.4.4",
"description": "Switch from @typescript-eslint/experimental-utils to @typescript-eslint/utils in all rules and rules.spec files",
"factory": "./src/migrations/update-14-4-4/experimental-to-utils-rules"
},
"add-eslint-inputs": {
"cli": "nx",
"version": "15.0.0-beta.0",
@ -73,166 +25,6 @@
}
},
"packageJsonUpdates": {
"12.6.0": {
"version": "12.6.0-beta.8",
"packages": {
"@typescript-eslint/parser": {
"version": "~4.28.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "~4.28.0"
}
}
},
"12.10.0": {
"version": "12.10.0-beta.1",
"packages": {
"@typescript-eslint/parser": {
"version": "~4.31.1"
},
"@typescript-eslint/eslint-plugin": {
"version": "~4.31.1"
},
"@typescript-eslint/experimental-utils": {
"version": "~4.31.1"
}
}
},
"13.0.0": {
"version": "12.10.0",
"packages": {
"@typescript-eslint/parser": {
"version": "~4.33.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "~4.33.0"
},
"@typescript-eslint/experimental-utils": {
"version": "~4.33.0"
},
"eslint": {
"version": "7.32.0"
}
}
},
"13.3.0": {
"version": "13.3.0-beta.0",
"packages": {
"@typescript-eslint/parser": {
"version": "~5.3.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "~5.3.0"
},
"@typescript-eslint/experimental-utils": {
"version": "~5.3.0"
},
"eslint": {
"version": "8.2.0"
}
}
},
"13.7.0": {
"version": "13.7.0-beta.0",
"packages": {
"@typescript-eslint/parser": {
"version": "~5.10.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "~5.10.0"
},
"@typescript-eslint/experimental-utils": {
"version": "~5.10.0"
},
"eslint": {
"version": "~8.7.0"
}
}
},
"13.10.0": {
"version": "13.10.0-beta.0",
"packages": {
"@typescript-eslint/parser": {
"version": "~5.18.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "~5.18.0"
},
"@typescript-eslint/experimental-utils": {
"version": "~5.18.0"
},
"eslint": {
"version": "~8.12.0"
}
}
},
"14.2.0": {
"version": "14.2.0-beta.0",
"packages": {
"@typescript-eslint/parser": {
"version": "~5.24.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "~5.24.0"
},
"@typescript-eslint/experimental-utils": {
"version": "~5.24.0"
},
"eslint": {
"version": "~8.15.0"
}
}
},
"14.3.7": {
"version": "14.3.6",
"packages": {
"@typescript-eslint/parser": {
"version": "^5.29.0"
},
"@typescript-eslint/eslint-plugin": {
"version": "^5.29.0"
},
"@typescript-eslint/experimental-utils": {
"version": "^5.29.0"
}
}
},
"14.4.4": {
"version": "14.4.4",
"packages": {
"@typescript-eslint/utils": {
"version": "^5.29.0"
}
}
},
"14.6.0": {
"version": "14.6.0-rc.2",
"packages": {
"@typescript-eslint/parser": {
"version": "~5.33.1"
},
"@typescript-eslint/eslint-plugin": {
"version": "~5.33.1"
},
"@typescript-eslint/utils": {
"version": "~5.33.1"
}
}
},
"14.8.0": {
"version": "14.8.0-beta.0",
"packages": {
"@typescript-eslint/parser": {
"version": "^5.36.1"
},
"@typescript-eslint/eslint-plugin": {
"version": "^5.36.1"
},
"@typescript-eslint/utils": {
"version": "^5.36.1"
}
}
},
"16.0.0": {
"version": "16.0.0-beta.0",
"packages": {

View File

@ -33,7 +33,6 @@
"eslint": "^8.0.0"
},
"dependencies": {
"@phenomnomnominal/tsquery": "~5.0.1",
"tslib": "^2.3.0",
"@nx/devkit": "file:../devkit",
"@nx/js": "file:../js",

View File

@ -1,231 +0,0 @@
import { addProjectConfiguration, readJson, Tree, writeJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import removeESLintProjectConfigIfNoTypeCheckingRules from './remove-eslint-project-config-if-no-type-checking-rules';
import type { Linter } from 'eslint';
const KNOWN_RULE_REQUIRING_TYPE_CHECKING = '@typescript-eslint/await-thenable';
describe('Remove ESLint parserOptions.project config if no rules requiring type-checking are in use', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'react-app', {
root: 'apps/react-app',
sourceRoot: 'apps/react-app/src',
projectType: 'application',
targets: {},
});
addProjectConfiguration(tree, 'workspace-lib', {
root: 'libs/workspace-lib',
sourceRoot: 'libs/workspace-lib/src',
projectType: 'library',
targets: {},
});
addProjectConfiguration(tree, 'some-lib', {
root: 'libs/some-lib',
sourceRoot: 'libs/some-lib/src',
projectType: 'library',
targets: {},
});
addProjectConfiguration(tree, 'another-lib', {
root: 'libs/another-lib',
sourceRoot: 'libs/another-lib/src',
projectType: 'library',
targets: {},
});
});
it('should not update any configs if the root .eslintrc.json contains at least one rule requiring type-checking', async () => {
const rootEslintConfig = {
root: true,
ignorePatterns: ['**/*'],
plugins: ['@nrwl/nx'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'@nrwl/nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
],
},
],
},
},
{
files: ['*.ts', '*.tsx'],
extends: ['plugin:@nrwl/nx/typescript'],
rules: {
[KNOWN_RULE_REQUIRING_TYPE_CHECKING]: 'error',
},
},
{
files: ['*.js', '*.jsx'],
extends: ['plugin:@nrwl/nx/javascript'],
rules: {},
},
],
};
writeJson(tree, '.eslintrc.json', rootEslintConfig);
const projectEslintConfig1 = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {},
},
],
};
writeJson(tree, 'apps/react-app/.eslintrc.json', projectEslintConfig1);
const projectEslintConfig2 = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {},
},
],
};
writeJson(tree, 'libs/workspace-lib/.eslintrc.json', projectEslintConfig2);
await removeESLintProjectConfigIfNoTypeCheckingRules(tree);
// No change
expect(readJson(tree, 'apps/react-app/.eslintrc.json')).toEqual(
projectEslintConfig1
);
// No change
expect(readJson(tree, 'libs/workspace-lib/.eslintrc.json')).toEqual(
projectEslintConfig1
);
});
it('should remove the parserOptions.project from any project .eslintrc.json files that do not contain any rules requiring type-checking', async () => {
// Root doesn't contain any rules requiring type-checking
const rootEslintConfig = {
root: true,
ignorePatterns: ['**/*'],
plugins: ['@nrwl/nx'],
overrides: [],
};
writeJson(tree, '.eslintrc.json', rootEslintConfig);
const projectEslintConfig1: Linter.Config = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {
[KNOWN_RULE_REQUIRING_TYPE_CHECKING]: 'error',
},
},
],
};
writeJson(tree, 'apps/react-app/.eslintrc.json', projectEslintConfig1);
const projectEslintConfig2: Linter.Config = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {
// No rules requiring type-checking
},
},
],
};
writeJson(tree, 'libs/workspace-lib/.eslintrc.json', projectEslintConfig2);
const projectEslintConfig3: Linter.Config = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {
[KNOWN_RULE_REQUIRING_TYPE_CHECKING]: 'off',
},
},
],
};
writeJson(tree, 'libs/another-lib/.eslintrc.json', projectEslintConfig3);
await removeESLintProjectConfigIfNoTypeCheckingRules(tree);
// No change - uses rule requiring type-checking
expect(readJson(tree, 'apps/react-app/.eslintrc.json')).toEqual(
projectEslintConfig1
);
// Updated - no more parserOptions.project
expect(readJson(tree, 'libs/workspace-lib/.eslintrc.json'))
.toMatchInlineSnapshot(`
{
"extends": "../../../.eslintrc.json",
"ignorePatterns": [
"!**/*",
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
],
"rules": {},
},
],
}
`);
// Updated - no more parserOptions.project
expect(readJson(tree, 'libs/another-lib/.eslintrc.json'))
.toMatchInlineSnapshot(`
{
"extends": "../../../.eslintrc.json",
"ignorePatterns": [
"!**/*",
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
],
"rules": {
"@typescript-eslint/await-thenable": "off",
},
},
],
}
`);
});
it('should not error if .eslintrc.json does not exist', async () => {
await removeESLintProjectConfigIfNoTypeCheckingRules(tree);
});
});

View File

@ -1,38 +0,0 @@
import { formatFiles, getProjects, readJson, updateJson } from '@nx/devkit';
import type { Tree } from '@nx/devkit';
import { join } from 'path';
import {
hasRulesRequiringTypeChecking,
removeParserOptionsProjectIfNotRequired,
} from '../../utils/rules-requiring-type-checking';
function updateProjectESLintConfigs(host: Tree) {
const projects = getProjects(host);
projects.forEach((p) => {
const eslintConfigPath = join(p.root, '.eslintrc.json');
if (!host.exists(eslintConfigPath)) {
return;
}
return updateJson(
host,
eslintConfigPath,
removeParserOptionsProjectIfNotRequired
);
});
}
export default async function removeESLintProjectConfigIfNoTypeCheckingRules(
host: Tree
) {
if (!host.exists('.eslintrc.json')) {
return;
}
// If the root level config uses at least one rule requiring type-checking, do not migrate any project configs
const rootESLintConfig = readJson(host, '.eslintrc.json');
if (hasRulesRequiringTypeChecking(rootESLintConfig)) {
return;
}
updateProjectESLintConfigs(host);
await formatFiles(host);
}

View File

@ -1,71 +0,0 @@
import {
addProjectConfiguration,
readProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import addOutputs from './add-outputs';
describe('addOutputs', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
const lintWithoutOutputs: TargetConfiguration = {
executor: '@nrwl/linter:eslint',
options: {},
};
const lintWithOutputs: TargetConfiguration = {
executor: '@nrwl/linter:eslint',
outputs: ['dist'],
options: {},
};
const notLint: TargetConfiguration = {
executor: '@nrwl/node:build',
options: {},
};
addProjectConfiguration(tree, 'proj', {
root: 'proj',
targets: {
lintWithoutOutputs,
lintWithOutputs,
notLint,
},
});
});
it('should add outputs to targets that do not have outputs', async () => {
await addOutputs(tree);
expect(readProjectConfiguration(tree, 'proj')).toMatchInlineSnapshot(`
{
"$schema": "../node_modules/nx/schemas/project-schema.json",
"name": "proj",
"root": "proj",
"targets": {
"lintWithOutputs": {
"executor": "@nrwl/linter:eslint",
"options": {},
"outputs": [
"dist",
],
},
"lintWithoutOutputs": {
"executor": "@nrwl/linter:eslint",
"options": {},
"outputs": [
"{options.outputFile}",
],
},
"notLint": {
"executor": "@nrwl/node:build",
"options": {},
},
},
}
`);
});
});

View File

@ -1,26 +0,0 @@
import {
formatFiles,
getProjects,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
export default async function addOutputs(tree: Tree) {
for (const [projectName, project] of getProjects(tree)) {
if (!project.targets) {
continue;
}
for (const target of Object.values(project.targets)) {
if (target.executor !== '@nrwl/linter:eslint' || target.outputs) {
continue;
}
target.outputs = ['{options.outputFile}'];
updateProjectConfiguration(tree, projectName, project);
}
}
await formatFiles(tree);
}

View File

@ -1,115 +0,0 @@
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import eslint8Updates from './eslint-8-updates';
describe('eslint8Updates()', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
`tools/eslint-rules/jest.config.js`,
`
module.exports = {
displayName: 'eslint-rules',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/tools/eslint-rules',
};
`
);
tree.write(
`tools/eslint-rules/rules/existing-rule.ts`,
`
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/existing-rule"
export const RULE_NAME = 'existing-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: \`\`,
category: 'Possible Errors',
recommended: 'error',
},
schema: [],
messages: {},
},
defaultOptions: [],
create(context) {
return {};
},
});
`
);
});
it('should add module mapping for ESLint to the jest config', async () => {
await eslint8Updates(tree);
expect(tree.read('tools/eslint-rules/jest.config.js').toString('utf-8'))
.toMatchInlineSnapshot(`
"module.exports = {
displayName: 'eslint-rules',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/tools/eslint-rules',
moduleNameMapper: {
'@eslint/eslintrc': '@eslint/eslintrc/dist/eslintrc-universal.cjs',
},
};
"
`);
});
it('should remove the category meta property from any existing workspace rules', async () => {
await eslint8Updates(tree);
expect(
tree.read(`tools/eslint-rules/rules/existing-rule.ts`).toString('utf-8')
).toMatchInlineSnapshot(`
"import { ESLintUtils } from '@typescript-eslint/experimental-utils';
// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/existing-rule"
export const RULE_NAME = 'existing-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: \`\`,
recommended: 'error',
},
schema: [],
messages: {},
},
defaultOptions: [],
create(context) {
return {};
},
});
"
`);
});
});

View File

@ -1,56 +0,0 @@
import {
ensurePackage,
formatFiles,
normalizePath,
Tree,
visitNotIgnoredFiles,
} from '@nx/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import { nxVersion } from '../../utils/versions';
export default async function eslint8Updates(tree: Tree) {
try {
const { addPropertyToJestConfig } = ensurePackage('@nx/jest', nxVersion);
const existingJestConfigPath = normalizePath(
'tools/eslint-rules/jest.config.js'
);
// Add extra config to the jest.config.js file to allow ESLint 8 exports mapping to work with jest
addPropertyToJestConfig(tree, existingJestConfigPath, 'moduleNameMapper', {
'@eslint/eslintrc': '@eslint/eslintrc/dist/eslintrc-universal.cjs',
});
visitNotIgnoredFiles(tree, 'tools/eslint-rules', (path) => {
if (!path.endsWith('.ts')) {
return;
}
const fileContents = tree.read(path).toString('utf-8');
const fileAst = tsquery.ast(fileContents);
const isESLintRuleFile =
tsquery(
fileAst,
'PropertyAccessExpression[expression.escapedText=ESLintUtils][name.escapedText=RuleCreator]'
).length > 0;
if (!isESLintRuleFile) {
return;
}
const categoryPropertyAssignmentNode = tsquery(
fileAst,
'PropertyAssignment[name.escapedText=meta] PropertyAssignment[name.escapedText=docs] PropertyAssignment[name.escapedText=category]'
)[0];
if (!categoryPropertyAssignmentNode) {
return;
}
let end = categoryPropertyAssignmentNode.getEnd();
if (fileContents.substring(end, end + 1) === ',') {
end++;
}
const updatedContents =
fileContents.slice(0, categoryPropertyAssignmentNode.getFullStart()) +
fileContents.slice(end);
tree.write(path, updatedContents);
});
await formatFiles(tree);
} catch {}
}

View File

@ -1,13 +0,0 @@
import { formatFiles, Tree } from '@nx/devkit';
import { addSwcRegisterDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies';
import { WORKSPACE_PLUGIN_DIR } from '../../generators/workspace-rules-project/workspace-rules-project';
export default async function addSwcNodeIfNeeded(tree: Tree) {
try {
if (tree.exists(WORKSPACE_PLUGIN_DIR)) {
addSwcRegisterDependencies(tree);
await formatFiles(tree);
return;
}
} catch {}
}

View File

@ -1,36 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`addTypescriptEslintUtilsIfNeeded() should add @typescript-eslint/utils if plugins folder exists 1`] = `
"{
"name": "test-name",
"dependencies": {},
"devDependencies": {
"@typescript-eslint/utils": "^5.60.1"
}
}
"
`;
exports[`addTypescriptEslintUtilsIfNeeded() should not add @typescript-eslint/utils if plugins folder or experimental-utils dont exist 1`] = `"{"name":"test-name","dependencies":{},"devDependencies":{}}"`;
exports[`addTypescriptEslintUtilsIfNeeded() should remove @typescript-eslint/experimental-utils from package.json devDependencies and add @typescript-eslint/utils 1`] = `
"{
"name": "test-name",
"dependencies": {},
"devDependencies": {
"@typescript-eslint/utils": "^5.60.1"
}
}
"
`;
exports[`addTypescriptEslintUtilsIfNeeded() should remove @typescript-eslint/experimental-utils from package.json devDependencies and add @typescript-eslint/utils 2`] = `
"{
"name": "test-name",
"dependencies": {},
"devDependencies": {
"@typescript-eslint/utils": "^5.60.1"
}
}
"
`;

View File

@ -1,51 +0,0 @@
import { updateJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { WORKSPACE_PLUGIN_DIR } from '../../generators/workspace-rules-project/workspace-rules-project';
import addTypescriptEslintUtilsIfNeeded from './experimental-to-utils-deps';
describe('addTypescriptEslintUtilsIfNeeded()', () => {
it('should remove @typescript-eslint/experimental-utils from package.json devDependencies and add @typescript-eslint/utils', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => {
json.devDependencies['@typescript-eslint/experimental-utils'] = '12.3.4';
return json;
});
await addTypescriptEslintUtilsIfNeeded(tree);
expect(tree.read(`package.json`).toString('utf-8')).toMatchSnapshot();
});
it('should remove @typescript-eslint/experimental-utils from package.json devDependencies and add @typescript-eslint/utils', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => {
json.dependencies['@typescript-eslint/experimental-utils'] = '12.3.4';
return json;
});
await addTypescriptEslintUtilsIfNeeded(tree);
expect(tree.read(`package.json`).toString('utf-8')).toMatchSnapshot();
});
it('should add @typescript-eslint/utils if plugins folder exists', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
`${WORKSPACE_PLUGIN_DIR}/rules/my-rule.ts`,
'console.log("not important");'
);
await addTypescriptEslintUtilsIfNeeded(tree);
expect(tree.read(`package.json`).toString('utf-8')).toMatchSnapshot();
});
it('should not add @typescript-eslint/utils if plugins folder or experimental-utils dont exist', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await addTypescriptEslintUtilsIfNeeded(tree);
expect(tree.read(`package.json`).toString('utf-8')).toMatchSnapshot();
});
});

View File

@ -1,45 +0,0 @@
import {
addDependenciesToPackageJson,
formatFiles,
readJson,
removeDependenciesFromPackageJson,
Tree,
} from '@nx/devkit';
import { WORKSPACE_PLUGIN_DIR } from '../../generators/workspace-rules-project/workspace-rules-project';
import { typescriptESLintVersion } from '../../utils/versions';
export default async function addTypescriptEslintUtilsIfNeeded(tree: Tree) {
try {
const packageJson = readJson(tree, 'package.json');
let removed = false;
if (packageJson.devDependencies['@typescript-eslint/experimental-utils']) {
await removeDependenciesFromPackageJson(
tree,
[],
['@typescript-eslint/experimental-utils']
);
removed = true;
}
if (packageJson.dependencies['@typescript-eslint/experimental-utils']) {
await removeDependenciesFromPackageJson(
tree,
['@typescript-eslint/experimental-utils'],
[]
);
removed = true;
}
if (removed || tree.exists(WORKSPACE_PLUGIN_DIR)) {
addDependenciesToPackageJson(
tree,
{},
{ '@typescript-eslint/utils': typescriptESLintVersion }
);
await formatFiles(tree);
}
return;
} catch {}
}

View File

@ -1,132 +0,0 @@
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import experimentalToUtilsUpdate from './experimental-to-utils-rules';
describe('experimentalToUtilsUpdate()', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
`tools/eslint-rules/rules/existing-rule.ts`,
`
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
import { rule, RULE_NAME } from './existing-rule';
// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/existing-rule"
export const RULE_NAME = 'existing-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: \`\`,
recommended: 'error',
},
schema: [],
messages: {},
},
defaultOptions: [],
create(context) {
return {};
},
});
`
);
tree.write(
`tools/eslint-rules/rules/existing-rule.spec.ts`,
`
import { TSESLint } from '@typescript-eslint/experimental-utils';
import { rule, RULE_NAME } from './existing-rule';
const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
});
ruleTester.run(RULE_NAME, rule, {
valid: [\`const example = true;\`],
invalid: [],
});
`
);
tree.write(
`tools/eslint-rules/rules/multi-import.ts`,
`
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
import { TSESLint } from '@typescript-eslint/experimental-utils';
import { rule, RULE_NAME } from './existing-rule';
// NOTE: remaining code is irrelevant for this test
`
);
});
it('should switch import from @typescript-eslint/experimental-utils to @typescript-eslint/utils in rule and spec file', async () => {
await experimentalToUtilsUpdate(tree);
expect(
tree.read(`tools/eslint-rules/rules/existing-rule.ts`).toString('utf-8')
).toMatchInlineSnapshot(`
"import { ESLintUtils } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './existing-rule';
// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/existing-rule"
export const RULE_NAME = 'existing-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: \`\`,
recommended: 'error',
},
schema: [],
messages: {},
},
defaultOptions: [],
create(context) {
return {};
},
});
"
`);
expect(
tree
.read(`tools/eslint-rules/rules/existing-rule.spec.ts`)
.toString('utf-8')
).toMatchInlineSnapshot(`
"import { TSESLint } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './existing-rule';
const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
});
ruleTester.run(RULE_NAME, rule, {
valid: [\`const example = true;\`],
invalid: [],
});
"
`);
});
it('should switch import from @typescript-eslint/experimental-utils to @typescript-eslint/utils on multiple imports', async () => {
await experimentalToUtilsUpdate(tree);
expect(
tree.read(`tools/eslint-rules/rules/multi-import.ts`).toString('utf-8')
).toMatchInlineSnapshot(`
"import { ESLintUtils } from '@typescript-eslint/utils';
import { TSESLint } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './existing-rule';
// NOTE: remaining code is irrelevant for this test
"
`);
});
});

View File

@ -1,39 +0,0 @@
import { formatFiles, Tree, visitNotIgnoredFiles } from '@nx/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
function updateFile(fileAst: any, fileContents: string): string | undefined {
const importStatement: any[] = tsquery(
fileAst,
'ImportDeclaration StringLiteral[value=@typescript-eslint/experimental-utils]'
);
if (importStatement.length === 0) {
return;
}
const contentSlices = fileContents.split(
'@typescript-eslint/experimental-utils'
);
let updatedFileContents = '';
for (let i = 0; i < contentSlices.length / 2; i++) {
updatedFileContents += `${contentSlices[i]}@typescript-eslint/utils`;
}
updatedFileContents += contentSlices[contentSlices.length - 1];
return updatedFileContents;
}
export default async function experimentalToUtilsUpdate(tree: Tree) {
try {
visitNotIgnoredFiles(tree, 'tools/eslint-rules', (path) => {
if (path.endsWith('.ts')) {
const fileContents = tree.read(path).toString('utf-8');
const fileAst = tsquery.ast(fileContents);
const updatedContents = updateFile(fileAst, fileContents);
if (updatedContents) {
tree.write(path, updatedContents);
}
}
});
await formatFiles(tree);
} catch {}
}

View File

@ -1,11 +1,5 @@
{
"generators": {
"update-to-nest-8": {
"cli": "nx",
"version": "13.2.0-beta.0",
"description": "Update Nest.js libraries",
"factory": "./src/migrations/update-13-2-0/update-to-nest-8"
},
"update-16-0-0-add-nx-packages": {
"cli": "nx",
"version": "16.0.0-beta.1",
@ -20,35 +14,6 @@
}
},
"packageJsonUpdates": {
"14.5.0": {
"version": "14.5.0-beta.1",
"packages": {
"@nestjs/common": {
"version": "^9.0.0",
"alwaysAddToPackageJson": false
},
"@nestjs/core": {
"version": "^9.0.0",
"alwaysAddToPackageJson": false
},
"@nestjs/platform-express": {
"version": "^9.0.0",
"alwaysAddToPackageJson": false
},
"@nestjs/schematics": {
"version": "^9.0.0",
"alwaysAddToPackageJson": false
},
"@nestjs/swagger": {
"version": "^6.0.0",
"alwaysAddToPackageJson": false
},
"@nestjs/testing": {
"version": "^9.0.0",
"alwaysAddToPackageJson": false
}
}
},
"16.1.0": {
"version": "16.1.0-beta.0",
"packages": {

View File

@ -31,13 +31,11 @@
},
"dependencies": {
"@nestjs/schematics": "^9.1.0",
"enquirer": "~2.3.6",
"@nx/devkit": "file:../devkit",
"@nx/js": "file:../js",
"@nx/linter": "file:../linter",
"@nx/node": "file:../node",
"@phenomnomnominal/tsquery": "~5.0.1",
"semver": "7.5.3",
"tslib": "^2.3.0"
},
"publishConfig": {

View File

@ -1,89 +0,0 @@
import { formatFiles, logger, readJson, Tree, updateJson } from '@nx/devkit';
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
import { satisfies } from 'semver';
import { sortObjectByKeys } from 'nx/src/utils/object-sort';
const nestJsSchematicsVersion = '^9.0.0';
const nestJsVersion8 = '^8.0.0';
const rxjsVersion7 = '^7.0.0';
export default async function update(tree: Tree) {
const shouldUpdate = await isUpdatable(tree);
if (!shouldUpdate) {
return;
}
updateVersion(tree);
await formatFiles(tree);
return (): void => {
logger.info(
'Please make sure to run npm install or yarn install to get the latest packages added by this migration'
);
};
}
async function isUpdatable(tree: Tree) {
const json = readJson(tree, 'package.json');
if (json.dependencies['@angular/core']) {
const rxjs = checkAndCleanWithSemver('rxjs', json.dependencies['rxjs']);
if (satisfies(rxjs, rxjsVersion7)) {
return true;
}
const { Confirm } = require('enquirer');
const prompt = new Confirm({
name: 'question',
message: 'Do you want to update to RxJS 7 + Nest 8?',
initial: true,
});
return await prompt.run();
}
return true;
}
function updateVersion(tree: Tree) {
updateJson(tree, 'package.json', (json) => {
json.dependencies = json.dependencies || {};
json.devDependencies = json.devDependencies || {};
const rxjs = checkAndCleanWithSemver('rxjs', json.dependencies['rxjs']);
json.dependencies = {
...json.dependencies,
'@nestjs/common': nestJsVersion8,
'@nestjs/core': nestJsVersion8,
rxjs: satisfies(rxjs, rxjsVersion7)
? json.dependencies['rxjs']
: rxjsVersion7,
};
if (json.dependencies['@nestjs/platform-express']) {
json.dependencies['@nestjs/platform-express'] = nestJsVersion8;
}
if (json.dependencies['@nestjs/platform-fastify']) {
json.dependencies['@nestjs/platform-fastify'] = nestJsVersion8;
}
json.devDependencies = {
...json.devDependencies,
'@nestjs/schematics': nestJsSchematicsVersion,
'@nestjs/testing': nestJsVersion8,
};
if (json.devDependencies['jasmine-marbles']) {
json.devDependencies['jasmine-marbles'] = '~0.9.1';
}
json.dependencies = sortObjectByKeys(json.dependencies);
json.devDependencies = sortObjectByKeys(json.devDependencies);
return json;
});
}

View File

@ -1,59 +1,5 @@
{
"generators": {
"fix-nextjs-lib-babel-config-12.8.0": {
"cli": "nx",
"version": "12.8.0-beta.11",
"description": "Adjust the Next.js lib babel configuration for styled-jsx",
"factory": "./src/migrations/update-12-8-0/remove-styled-jsx-babel-plugin"
},
"fix-page-dir-for-eslint": {
"cli": "nx",
"version": "12.10.0-beta.1",
"description": "Updates .eslintrc.json file to use the correct pages directory for '@next/next/no-html-link-for-pages'.",
"factory": "./src/migrations/update-12-10-0/fix-page-dir-for-eslint"
},
"update-emotion-setup-13.0.0": {
"cli": "nx",
"version": "13.0.0-beta.0",
"description": "Update tsconfig.json to use `jsxImportSource` to support css prop",
"factory": "./src/migrations/update-13-0-0/update-emotion-setup"
},
"update-to-webpack-5": {
"cli": "nx",
"version": "13.0.0-beta.1",
"description": "Set `webpack5: true` for all next.js projects.",
"factory": "./src/migrations/update-13-0-0/update-to-webpack-5"
},
"fix-less": {
"cli": "nx",
"version": "13.0.3-beta.1",
"description": "Fix setup for less stylesheets",
"factory": "./src/migrations/update-13-0-3/fix-less"
},
"add-default-development-configurations-14.0.0": {
"cli": "nx",
"version": "14.0.0-beta.0",
"description": "Add a default development configuration for build and serve targets.",
"factory": "./src/migrations/update-14-0-0/add-default-development-configurations"
},
"add-dev-output-path": {
"cli": "nx",
"version": "14.4.3-beta.0",
"description": "Add a development outputPath to avoid conflict with the production build.",
"factory": "./src/migrations/update-14-4-3/add-dev-output-path"
},
"add-gitignore-entry": {
"cli": "nx",
"version": "14.5.3-beta.0",
"description": "Add .next folder to gitignore.",
"factory": "./src/migrations/update-14-5-3/add-gitignore-entry"
},
"update-dev-output-path": {
"cli": "nx",
"version": "14.5.3-beta.0",
"description": "Update development outputPath to the project root.",
"factory": "./src/migrations/update-14-5-3/update-dev-output-path"
},
"add-style-packages": {
"cli": "nx",
"version": "15.8.8-beta.0",
@ -80,212 +26,6 @@
}
},
"packageJsonUpdates": {
"12.1.2": {
"version": "12.1.2-beta.0",
"packages": {
"next": {
"version": "10.2.0",
"alwaysAddToPackageJson": false
}
}
},
"12.6.0": {
"version": "12.6.0-beta.0",
"packages": {
"next": {
"version": "11.0.1",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "11.0.1",
"addToPackageJson": "devDependencies"
}
}
},
"update-12.7.2": {
"version": "12.7.2-beta.0",
"packages": {
"next": {
"version": "11.1.0",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "11.1.0",
"alwaysAddToPackageJson": false
}
}
},
"update-13.0.3": {
"version": "13.0.3-beta.0",
"packages": {
"next": {
"version": "12.0.0",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.0.0",
"alwaysAddToPackageJson": false
}
}
},
"13.3.0": {
"version": "13.3.0-beta.0",
"packages": {
"next": {
"version": "12.0.7",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.0.7",
"alwaysAddToPackageJson": false
}
}
},
"13.9.0": {
"version": "13.9.0-beta.0",
"packages": {
"next": {
"version": "12.1.0",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.1.0",
"alwaysAddToPackageJson": false
}
}
},
"13.10.0": {
"version": "13.10.0-beta.1",
"packages": {
"next": {
"version": "12.1.2",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.1.2",
"alwaysAddToPackageJson": false
},
"sass": {
"version": "1.49.9",
"alwaysAddToPackageJson": false
}
}
},
"14.0.0": {
"version": "14.0.0-beta.1",
"packages": {
"next": {
"version": "12.1.5",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.1.5",
"alwaysAddToPackageJson": false
}
}
},
"14.2.3": {
"version": "14.2.3-beta.0",
"packages": {
"next": {
"version": "12.1.6",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.1.6",
"alwaysAddToPackageJson": false
},
"sass": {
"version": "1.52.3",
"alwaysAddToPackageJson": false
},
"less-loader": {
"version": "11.0.0",
"alwaysAddToPackageJson": false
},
"stylus-loader": {
"version": "7.0.0",
"alwaysAddToPackageJson": false
}
}
},
"14.4.3": {
"version": "14.4.3",
"packages": {
"next": {
"version": "12.2.2",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.2.2",
"alwaysAddToPackageJson": false
},
"sass": {
"version": "1.53.0",
"alwaysAddToPackageJson": false
}
}
},
"14.5.0": {
"version": "14.5.0-beta.0",
"packages": {
"next": {
"version": "12.2.3",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.2.3",
"alwaysAddToPackageJson": false
},
"sass": {
"version": "1.54.0",
"alwaysAddToPackageJson": false
}
}
},
"14.5.3": {
"version": "14.5.3-beta.0",
"packages": {
"@emotion/server": {
"version": "11.10.0",
"alwaysAddToPackageJson": false
}
}
},
"14.6.0": {
"version": "14.6.0-beta.1",
"packages": {
"next": {
"version": "12.2.5",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.2.5",
"alwaysAddToPackageJson": false
},
"sass": {
"version": "1.54.5",
"alwaysAddToPackageJson": false
}
}
},
"14.7.10": {
"version": "14.7.10-beta.1",
"packages": {
"next": {
"version": "12.3.1",
"alwaysAddToPackageJson": false
},
"eslint-config-next": {
"version": "12.3.1",
"alwaysAddToPackageJson": false
},
"sass": {
"version": "1.55.0",
"alwaysAddToPackageJson": false
}
}
},
"15.0.4": {
"version": "15.0.4-beta.0",
"packages": {

View File

@ -1,114 +0,0 @@
import { readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { applicationGenerator } from '../../generators/application/application';
import { fixPageDirForEslint } from './fix-page-dir-for-eslint';
describe('Migration: Fix pages directory for ESLint', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await applicationGenerator(tree, {
style: 'none',
name: 'demo',
skipFormat: false,
});
});
it('should fix custom pages path', async () => {
// Config that isn't configured properly
tree.write(
'apps/demo/.eslintrc.json',
JSON.stringify({
extends: ['next'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'existing-rule': 'error',
},
},
],
})
);
await fixPageDirForEslint(tree);
const result = readJson(tree, 'apps/demo/.eslintrc.json');
expect(result.overrides[0].rules).toEqual({
'existing-rule': 'error',
'@next/next/no-html-link-for-pages': ['error', 'apps/demo/pages'],
});
});
it('should leave existing no-html-link-for-pages rule if it exists', async () => {
// Config that isn't configured properly
tree.write(
'apps/demo/.eslintrc.json',
JSON.stringify({
extends: ['next'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'@next/next/no-html-link-for-pages': [
'error',
'apps/demo/some/other/path',
],
},
},
],
})
);
await fixPageDirForEslint(tree);
const result = readJson(tree, 'apps/demo/.eslintrc.json');
expect(result.overrides[0].rules).toEqual({
'@next/next/no-html-link-for-pages': [
'error',
'apps/demo/some/other/path',
],
});
});
it('should handle custom overrides configuration', async () => {
// Config that isn't configured properly
tree.write(
'apps/demo/.eslintrc.json',
JSON.stringify({
extends: ['next', 'plugin:vue/base'],
overrides: [
{ files: ['*.vue'], rule: { 'vue/comment-directive': 'error' } },
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'@next/next/no-html-link-for-pages': [
'error',
'apps/demo/some/other/path',
],
},
},
],
})
);
await fixPageDirForEslint(tree);
const result = readJson(tree, 'apps/demo/.eslintrc.json');
expect(result.overrides).toEqual([
{ files: ['*.vue'], rule: { 'vue/comment-directive': 'error' } },
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'@next/next/no-html-link-for-pages': [
'error',
'apps/demo/some/other/path',
],
},
},
]);
});
});

View File

@ -1,45 +0,0 @@
import {
formatFiles,
getProjects,
joinPathFragments,
readJson,
Tree,
writeJson,
} from '@nx/devkit';
export async function fixPageDirForEslint(host: Tree) {
const projects = getProjects(host);
projects.forEach((project) => {
const eslintRcJson = joinPathFragments(project.root, '.eslintrc.json');
if (host.exists(eslintRcJson)) {
const config = readJson(host, eslintRcJson);
// Ignore non-nextjs projects
if (!config?.extends?.includes('next')) return;
// Find the override that handles both TS and JS files.
const commonOverride = config.overrides?.find((o) =>
['*.ts', '*.tsx', '*.js', '*.jsx'].every((ext) => o.files.includes(ext))
);
if (
commonOverride &&
!commonOverride.rules['@next/next/no-html-link-for-pages']
) {
commonOverride.rules = {
...commonOverride.rules,
'@next/next/no-html-link-for-pages': [
'error',
`${project.root}/pages`,
],
};
writeJson(host, eslintRcJson, config);
}
}
});
await formatFiles(host);
}
export default fixPageDirForEslint;

View File

@ -1,56 +0,0 @@
import { readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/linter';
import { libraryGenerator } from '../../generators/library/library';
import { removeStyledJsxBabelConfig } from './remove-styled-jsx-babel-plugin';
describe('Remove styled-jsx babel plugin for Next libs', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, {
name: 'test-lib',
style: 'styled-jsx',
skipFormat: false,
skipTsConfig: false,
linter: Linter.EsLint,
unitTestRunner: 'jest',
});
});
it('should remove styled-jsx babel plugin', async () => {
tree.write(
'libs/test-lib/.babelrc',
JSON.stringify({
presets: ['next/babel'],
plugins: ['styled-jsx/babel'],
})
);
await removeStyledJsxBabelConfig(tree);
const result = readJson(tree, 'libs/test-lib/.babelrc');
expect(result.plugins).toEqual([]);
});
it('should remove styled-jsx babel plugin but leave potentially other plugins in there', async () => {
tree.write(
'libs/test-lib/.babelrc',
JSON.stringify({
presets: ['next/babel'],
plugins: [
'some-other/plugin',
'styled-jsx/babel',
'some-storybook/plugin',
],
})
);
await removeStyledJsxBabelConfig(tree);
const result = readJson(tree, 'libs/test-lib/.babelrc');
expect(result.plugins).toEqual([
'some-other/plugin',
'some-storybook/plugin',
]);
});
});

View File

@ -1,33 +0,0 @@
import {
formatFiles,
getProjects,
Tree,
readJson,
joinPathFragments,
writeJson,
} from '@nx/devkit';
export async function removeStyledJsxBabelConfig(host: Tree) {
const projects = getProjects(host);
projects.forEach((project) => {
const babelRcPath = joinPathFragments(project.root, '.babelrc');
if (host.exists(babelRcPath)) {
const babelRcContent = readJson(host, babelRcPath);
// check whether next.js project
if (babelRcContent.presets.includes('next/babel')) {
babelRcContent.plugins = babelRcContent.plugins.filter(
(x) => x !== 'styled-jsx/babel'
);
// update .babelrc
writeJson(host, babelRcPath, babelRcContent);
}
}
});
await formatFiles(host);
}
export default removeStyledJsxBabelConfig;

View File

@ -1,85 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { addProjectConfiguration, readJson, Tree } from '@nx/devkit';
import { updateEmotionSetup } from './update-emotion-setup';
describe('Update tsconfig config for Emotion', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it(`should add jsxImportSource if it uses @emotion/react`, async () => {
addProjectConfiguration(tree, 'no-emotion-app', {
root: 'apps/no-emotion-app',
projectType: 'application',
});
addProjectConfiguration(tree, 'plain-next-app', {
root: 'apps/plain-next-app',
projectType: 'application',
});
addProjectConfiguration(tree, 'emotion-app', {
root: 'apps/emotion-app',
projectType: 'application',
});
tree.write(
'nx.json',
JSON.stringify({
projects: {
'no-emotion-app': {},
'plain-next-app': {},
'emotion-app': {},
},
})
);
tree.write('apps/no-emotion-app/.babelrc', JSON.stringify({}));
tree.write(
'apps/no-emotion-app/tsconfig.json',
JSON.stringify({ compilerOptions: {} })
);
tree.write(
'apps/plain-next-app/.babelrc',
JSON.stringify({
presets: ['@nrwl/next/babel'],
plugins: [],
})
);
tree.write(
'apps/plain-next-app/tsconfig.json',
JSON.stringify({ compilerOptions: { jsx: 'preserve' } })
);
tree.write(
'apps/emotion-app/.babelrc',
JSON.stringify({
presets: [
[
'@nrwl/next/babel',
{
'preset-react': {
runtime: 'automatic',
importSource: '@emotion/react',
},
},
],
],
plugins: ['@emotion/babel-plugin'],
})
);
tree.write(
'apps/emotion-app/tsconfig.json',
JSON.stringify({ compilerOptions: { jsx: 'preserve' } })
);
await updateEmotionSetup(tree);
expect(readJson(tree, 'apps/no-emotion-app/tsconfig.json')).toEqual({
compilerOptions: {},
});
expect(readJson(tree, 'apps/plain-next-app/tsconfig.json')).toEqual({
compilerOptions: { jsx: 'preserve' },
});
expect(readJson(tree, 'apps/emotion-app/tsconfig.json')).toEqual({
compilerOptions: { jsx: 'preserve', jsxImportSource: '@emotion/react' },
});
});
});

View File

@ -1,42 +0,0 @@
import {
formatFiles,
getProjects,
Tree,
readJson,
updateJson,
} from '@nx/devkit';
export async function updateEmotionSetup(host: Tree) {
const projects = getProjects(host);
projects.forEach((p) => {
let hasEmotion = false;
const babelrcPath = `${p.root}/.babelrc`;
const tsConfigPath = `${p.root}/tsconfig.json`;
if (host.exists(babelrcPath)) {
const babelrc = readJson(host, babelrcPath);
if (babelrc.presets) {
for (const [idx, preset] of babelrc.presets.entries()) {
if (Array.isArray(preset)) {
if (!preset[0].includes('@nrwl/next/babel')) continue;
const emotionOptions = preset[1]['preset-react'];
hasEmotion = emotionOptions?.importSource === '@emotion/react';
break;
}
}
}
}
if (hasEmotion && host.exists(tsConfigPath)) {
updateJson(host, tsConfigPath, (json) => {
json.compilerOptions.jsxImportSource = '@emotion/react';
return json;
});
}
});
await formatFiles(host);
}
export default updateEmotionSetup;

View File

@ -1,97 +0,0 @@
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { applicationGenerator } from '../../generators/application/application';
import { update } from './update-to-webpack-5';
describe('Migration: enable webpack 5', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should set webpack5 to true', async () => {
await applicationGenerator(tree, {
style: 'none',
name: 'demo',
skipFormat: false,
});
// Config that isn't configured properly
tree.write(
'apps/demo/next.config.js',
`const withNx = require('@nx/next/plugins/with-nx');
const nextConfig = {
webpack5: false,
};
module.exports = withNx(nextConfig);
`
);
await update(tree);
const result = tree.read('apps/demo/next.config.js').toString();
expect(result).toMatch(/webpack5: true/);
});
it('should fix stylus support', async () => {
await applicationGenerator(tree, {
style: 'styl',
name: 'demo',
skipFormat: false,
});
// Config that isn't configured properly
tree.write(
'apps/demo/next.config.js',
`const withNx = require('@nx/next/plugins/with-nx');
const withStylus = require('@zeit/next-stylus');
const nextConfig = {
webpack5: false,
};
module.exports = withNx(withStylus(nextConfig));
`
);
await update(tree);
const result = tree.read('apps/demo/next.config.js').toString();
expect(result).toMatch(/@nrwl\/next\/plugins\/with-stylus/);
expect(result).not.toMatch(/@zeit\/next-stylus/);
});
it('should fix less support', async () => {
await applicationGenerator(tree, {
style: 'less',
name: 'demo',
skipFormat: false,
});
// Config that isn't configured properly
tree.write(
'apps/demo/next.config.js',
`const withNx = require('@nx/next/plugins/with-nx');
const withLess = require('@zeit/next-less');
const nextConfig = {
webpack5: false,
};
module.exports = withNx(withLess(nextConfig));
`
);
await update(tree);
const result = tree.read('apps/demo/next.config.js').toString();
expect(result).toMatch(/next-with-less/);
expect(result).not.toMatch(/@zeit\/next-less/);
});
});

View File

@ -1,51 +0,0 @@
import {
addDependenciesToPackageJson,
formatFiles,
GeneratorCallback,
getProjects,
joinPathFragments,
Tree,
} from '@nx/devkit';
export async function update(host: Tree) {
const projects = getProjects(host);
let task: undefined | GeneratorCallback = undefined;
projects.forEach((project) => {
const configPath = joinPathFragments(project.root, 'next.config.js');
if (!host.exists(configPath)) return;
const content = host.read(configPath).toString();
let updated = content.replace(/webpack5: false/, 'webpack5: true');
if (content.match(/@zeit\/next-less/)) {
updated = updated.replace('@zeit/next-less', 'next-with-less');
task = addDependenciesToPackageJson(
host,
{ 'next-with-less': '1.0.1' },
{}
);
}
if (content.match(/@zeit\/next-stylus/)) {
updated = updated.replace(
'@zeit/next-stylus',
'@nrwl/next/plugins/with-stylus'
);
task = addDependenciesToPackageJson(
host,
{ 'stylus-loader': '6.2.0' },
{}
);
}
host.write(configPath, updated);
});
await formatFiles(host);
return task;
}
export default update;

View File

@ -1,40 +0,0 @@
import {
addDependenciesToPackageJson,
formatFiles,
GeneratorCallback,
getProjects,
joinPathFragments,
Tree,
} from '@nx/devkit';
export async function update(host: Tree) {
const projects = getProjects(host);
let task: undefined | GeneratorCallback = undefined;
projects.forEach((project) => {
const configPath = joinPathFragments(project.root, 'next.config.js');
if (!host.exists(configPath)) return;
const content = host.read(configPath).toString();
if (content.match(/next-with-less/)) {
const updated = content.replace(
'next-with-less',
'@nrwl/next/plugins/with-less'
);
task = addDependenciesToPackageJson(
host,
{ 'less-loader': '10.2.0' },
{}
);
host.write(configPath, updated);
}
});
await formatFiles(host);
return task;
}
export default update;

View File

@ -1,203 +0,0 @@
import {
readProjectConfiguration,
Tree,
updateJson,
updateProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { applicationGenerator } from '../../generators/application/application';
import { update } from './enable-swc';
describe('Migration: enable SWC', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should remove .babelrc file and fix jest config', async () => {
await applicationGenerator(tree, {
style: 'none',
name: 'demo',
skipFormat: false,
swc: false,
});
updateJson(tree, 'apps/demo/.babelrc', (json) => {
json.presets[0] = '@nrwl/next/babel';
return json;
});
// rename jest config to js as that was standard at this version of nx
tree.delete('apps/demo/jest.config.ts');
updateProjectConfiguration(tree, 'demo', {
...readProjectConfiguration(tree, 'demo'),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'apps/demo/jest.config.js',
passWithNoTests: true,
},
},
},
});
// Config that isn't configured properly
tree.write(
'apps/demo/jest.config.js',
`
module.exports = {
displayName: 'napp4',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/demo',
};
`
);
await update(tree);
const result = tree.read('apps/demo/jest.config.js').toString();
expect(result).toMatch(`['babel-jest', { presets: ['@nrwl/next/babel'] }]`);
expect(tree.exists('apps/demo/.babelrc')).toBe(false);
});
it('should still fix jest config when babelrc is missing', async () => {
await applicationGenerator(tree, {
style: 'none',
name: 'demo',
skipFormat: false,
swc: true,
});
// rename jest config to js as that was standard at this version of nx
tree.delete('apps/demo/jest.config.ts');
updateProjectConfiguration(tree, 'demo', {
...readProjectConfiguration(tree, 'demo'),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'apps/demo/jest.config.js',
passWithNoTests: true,
},
},
},
});
// Config that isn't configured properly
tree.write(
'apps/demo/jest.config.js',
`
module.exports = {
displayName: 'napp4',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/demo',
};
`
);
await update(tree);
const result = tree.read('apps/demo/jest.config.js').toString();
expect(result).toMatch(`['babel-jest', { presets: ['@nrwl/next/babel'] }]`);
});
it('should skip migration if the babelrc has been customized', async () => {
await applicationGenerator(tree, {
style: 'none',
name: 'demo',
skipFormat: false,
swc: false,
});
// rename jest config to js as that was standard at this version of nx
tree.rename('apps/demo/jest.config.ts', 'apps/demo/jest.config.js');
updateProjectConfiguration(tree, 'demo', {
...readProjectConfiguration(tree, 'demo'),
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'apps/demo/jest.config.js',
passWithNoTests: true,
},
},
},
});
tree.write(
'apps/demo/.babelrc',
`{
"presets": ["@nrwl/next/babel", "something-else"],
"plugins": []
}`
);
await update(tree);
expect(tree.exists('apps/demo/.babelrc')).toBe(true);
tree.write(
'apps/demo/.babelrc',
`{
"presets": ["@nrwl/next/babel"],
"plugins": ["some-plugin"]
}`
);
await update(tree);
expect(tree.exists('apps/demo/.babelrc')).toBe(true);
// No custom plugins, can migrate.
tree.write(
'apps/demo/.babelrc',
`{
"presets": ["@nrwl/next/babel"]
}`
);
await update(tree);
expect(tree.exists('apps/demo/.babelrc')).toBe(false);
});
it('should skip migration if storybook configuration is detected', async () => {
await applicationGenerator(tree, {
style: 'none',
name: 'demo',
skipFormat: false,
swc: false,
});
tree.write(
'apps/demo/.babelrc',
`{
"presets": ["@nrwl/next/babel"]
}`
);
tree.write(
'apps/demo/.storybook/main.js',
`module.exports = {
stories: []
}`
);
await update(tree);
expect(tree.exists('apps/demo/.babelrc')).toBe(true);
});
});

View File

@ -1,62 +0,0 @@
import {
formatFiles,
getProjects,
joinPathFragments,
logger,
readJson,
Tree,
} from '@nx/devkit';
export async function update(host: Tree) {
const projects = getProjects(host);
projects.forEach((project) => {
const nextConfigPath = joinPathFragments(project.root, 'next.config.js');
const jestConfigPath = joinPathFragments(project.root, 'jest.config.js');
const babelConfigPath = joinPathFragments(project.root, '.babelrc');
const storybookMainPath = joinPathFragments(
project.root,
'.storybook/main.js'
);
if (!host.exists(nextConfigPath) || !host.exists(jestConfigPath)) return;
if (host.exists(babelConfigPath)) {
if (customBabelConfig(host, babelConfigPath)) {
logger.info(
`NX Custom .babelrc file detected, skipping deletion. You can delete this file yourself to enable SWC: ${babelConfigPath}`
);
} else if (host.exists(storybookMainPath)) {
logger.info(
`NX Storybook configuration for project "${project.name}" detected, skipping deletion of .babelrc`
);
} else {
// Deleting custom babel config enables SWC
host.delete(babelConfigPath);
}
}
const content = host.read(jestConfigPath).toString();
if (content.match(/:\s+'babel-jest'/)) {
const updated = content.replace(
/:\s+'babel-jest'/,
`: ['babel-jest', { presets: ['@nrwl/next/babel'] }]`
);
host.write(jestConfigPath, updated);
}
});
await formatFiles(host);
}
function customBabelConfig(host, configPath) {
const json = readJson(host, configPath);
return !(
json.presets?.length === 1 &&
json.presets?.[0] === '@nrwl/next/babel' &&
(json.plugins?.length === 0 || !json.plugins)
);
}
export default update;

View File

@ -1,101 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { addProjectConfiguration, readProjectConfiguration } from '@nx/devkit';
import update from './add-default-development-configurations';
describe('React default development configuration', () => {
it('should add development configuration if it does not exist', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
configurations: {},
},
serve: {
executor: '@nrwl/next:server',
configurations: {},
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.defaultConfiguration).toEqual('production');
expect(config.targets.build.configurations.development).toEqual({});
expect(config.targets.serve.defaultConfiguration).toEqual('development');
expect(config.targets.serve.configurations.development).toEqual({
buildTarget: `example:build:development`,
dev: true,
});
});
it('should add development configuration if no configurations at all', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
defaultConfiguration: 'production',
configurations: { production: {} },
},
serve: {
executor: '@nrwl/next:server',
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.defaultConfiguration).toEqual('production');
expect(config.targets.build.configurations.production).toEqual({});
expect(config.targets.build.configurations.development).toEqual({});
expect(config.targets.serve.defaultConfiguration).toEqual('development');
expect(config.targets.serve.configurations.development).toEqual({
buildTarget: `example:build:development`,
dev: true,
});
});
it('should work without targets', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
projectType: 'application',
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'example',
root: 'apps/example',
projectType: 'application',
});
});
});

View File

@ -1,36 +0,0 @@
import {
formatFiles,
getProjects,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
export async function update(tree: Tree) {
const projects = getProjects(tree);
projects.forEach((config, name) => {
let shouldUpdate = false;
if (config.targets?.build?.executor === '@nrwl/next:build') {
shouldUpdate = true;
config.targets.build.defaultConfiguration ??= 'production';
config.targets.build.configurations ??= {};
config.targets.build.configurations.development ??= {};
}
if (config.targets?.serve?.executor === '@nrwl/next:server') {
shouldUpdate = true;
config.targets.serve.defaultConfiguration ??= 'development';
config.targets.serve.configurations ??= {};
config.targets.serve.configurations.development ??= {
buildTarget: `${name}:build:development`,
dev: true,
};
}
if (shouldUpdate) updateProjectConfiguration(tree, name, config);
});
await formatFiles(tree);
}
export default update;

View File

@ -1,65 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { addProjectConfiguration, readProjectConfiguration } from '@nx/devkit';
import update from './add-dev-output-path';
describe('React default development configuration', () => {
it('should add output path if it does not alreayd exist', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
sourceRoot: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
configurations: {
development: {},
},
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.configurations.development.outputPath).toEqual(
'tmp/apps/example'
);
});
it('should skip update if outputPath already exists for development', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
sourceRoot: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
configurations: {
development: { outputPath: '/tmp/some/custom/path' },
},
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.configurations.development.outputPath).toEqual(
'/tmp/some/custom/path'
);
});
});

View File

@ -1,25 +0,0 @@
import {
formatFiles,
getProjects,
joinPathFragments,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
export async function update(tree: Tree) {
const projects = getProjects(tree);
projects.forEach((config, name) => {
if (config.targets?.build?.executor === '@nrwl/next:build') {
config.targets.build.configurations ??= {};
config.targets.build.configurations.development ??= {};
config.targets.build.configurations.development.outputPath ??=
joinPathFragments('tmp', config.sourceRoot);
updateProjectConfiguration(tree, name, config);
}
});
await formatFiles(tree);
}
export default update;

View File

@ -1,9 +0,0 @@
import { formatFiles, Tree } from '@nx/devkit';
import { addGitIgnoreEntry } from '../../utils/add-gitignore-entry';
export async function update(tree: Tree) {
addGitIgnoreEntry(tree);
await formatFiles(tree);
}
export default update;

View File

@ -1,95 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { addProjectConfiguration, readProjectConfiguration } from '@nx/devkit';
import update from './update-dev-output-path';
describe('React default development configuration', () => {
it('should add output path if it does not alreayd exist', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
sourceRoot: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
configurations: {
development: {},
},
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.configurations.development.outputPath).toEqual(
'apps/example'
);
});
it('should add output path is default generated pat', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
sourceRoot: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
configurations: {
development: { outputPath: 'tmp/apps/example' },
},
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.configurations.development.outputPath).toEqual(
'apps/example'
);
});
it('should skip update if outputPath already exists for development and does not match expected path', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(
tree,
'example',
{
root: 'apps/example',
sourceRoot: 'apps/example',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/next:build',
configurations: {
development: { outputPath: '.tmp/custom' },
},
},
},
},
true
);
await update(tree);
const config = readProjectConfiguration(tree, 'example');
expect(config.targets.build.configurations.development.outputPath).toEqual(
'.tmp/custom'
);
});
});

View File

@ -1,31 +0,0 @@
import {
formatFiles,
getProjects,
joinPathFragments,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
export async function update(tree: Tree) {
const projects = getProjects(tree);
projects.forEach((config, name) => {
if (config.targets?.build?.executor === '@nrwl/next:build') {
config.targets.build.configurations ??= {};
config.targets.build.configurations.development ??= {};
if (
!config.targets.build.configurations.development.outputPath ||
config.targets.build.configurations.development.outputPath ===
joinPathFragments('tmp', config.root)
) {
config.targets.build.configurations.development.outputPath =
config.root;
updateProjectConfiguration(tree, name, config);
}
}
});
await formatFiles(tree);
}
export default update;

View File

@ -1,60 +0,0 @@
import { addProjectConfiguration, readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { updateNextEslint } from './update-next-eslint';
describe('Add next eslint 14.5.7', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'app1', {
root: 'apps/app1',
targets: {
build: {
executor: '@nrwl/next:build',
},
},
});
tree.write(
'nx.json',
JSON.stringify({
projects: {
app1: {},
},
})
);
});
it('should update "next" config ignorePattern', async () => {
tree.write(
'apps/app1/.eslintrc.json',
JSON.stringify({
extends: ['../../.eslintrc.json'],
ignorePatterns: ['!**/*'],
})
);
await updateNextEslint(tree);
const result = readJson(tree, 'apps/app1/.eslintrc.json');
expect(result.ignorePatterns).toContain('.next/**/*');
});
it('should not update "next" config ignorePattern if .next pattern already exists', async () => {
tree.write(
'apps/app1/.eslintrc.json',
JSON.stringify({
extends: ['../../.eslintrc.json'],
ignorePatterns: ['!**/*', '.next/**/*', '/foo/bar'],
})
);
const before = readJson(tree, 'apps/app1/.eslintrc.json');
await updateNextEslint(tree);
const result = readJson(tree, 'apps/app1/.eslintrc.json');
expect(result.ignorePatterns).toEqual(before.ignorePatterns);
});
});

View File

@ -1,28 +0,0 @@
import { formatFiles, getProjects, Tree, updateJson } from '@nx/devkit';
export async function updateNextEslint(host: Tree) {
const projects = getProjects(host);
projects.forEach((project) => {
if (project.targets?.build?.executor !== '@nrwl/next:build') return;
const eslintPath = `${project.root}/.eslintrc.json`;
if (!host.exists(eslintPath)) return;
updateJson(host, eslintPath, (eslintConfig) => {
const nextIgnorePattern = '.next/**/*';
if (eslintConfig.ignorePatterns.indexOf(nextIgnorePattern) < 0) {
eslintConfig.ignorePatterns = [
...eslintConfig.ignorePatterns,
nextIgnorePattern,
];
}
return eslintConfig;
});
});
await formatFiles(host);
}
export default updateNextEslint;

View File

@ -1,35 +1,5 @@
{
"generators": {
"remove-webpack-5-packages": {
"cli": "nx",
"version": "13.0.0-beta.1",
"description": "Remove packages installed by Nx 12's `@nrwl/node:webpack5` generator.",
"factory": "./src/migrations/update-13-0-0/remove-webpack-5-packages-13-0-0"
},
"rename-build-to-webpack": {
"cli": "nx",
"version": "13.8.5-beta.1",
"description": "Renames @nrwl/node:build to @nrwl/node:webpack",
"factory": "./src/migrations/update-13-8-5/rename-build-to-webpack"
},
"rename-execute-to-node": {
"cli": "nx",
"version": "13.8.5-beta.1",
"description": "Renames @nrwl/node:execute to @nrwl/node:node",
"factory": "./src/migrations/update-13-8-5/rename-execute-to-node"
},
"update-package-to-tsc": {
"cli": "nx",
"version": "13.8.5-beta.1",
"description": "Renames @nrwl/node:package to @nrwl/js:tsc",
"factory": "./src/migrations/update-13-8-5/update-package-to-tsc"
},
"update-webpack-executor": {
"cli": "nx",
"version": "14.7.6-beta.1",
"description": "Update usages of webpack executors to @nrwl/webpack",
"factory": "./src/migrations/update-14-7-6/update-webpack-executor"
},
"update-16-0-0-add-nx-packages": {
"cli": "nx",
"version": "16.0.0-beta.1",
@ -55,24 +25,5 @@
"implementation": "./src/migrations/update-16-4-0/replace-node-executor"
}
},
"packageJsonUpdates": {
"14.3.7": {
"version": "14.3.7-beta.0",
"packages": {
"@types/node": {
"version": "18.0.0",
"alwaysAddToPackageJson": false
}
}
},
"14.5.5": {
"version": "14.5.5-beta.0",
"packages": {
"@types/node": {
"version": "18.7.1",
"alwaysAddToPackageJson": false
}
}
}
}
"packageJsonUpdates": {}
}

View File

@ -34,8 +34,7 @@
"@nx/devkit": "file:../devkit",
"@nx/jest": "file:../jest",
"@nx/js": "file:../js",
"@nx/linter": "file:../linter",
"@nx/workspace": "file:../workspace"
"@nx/linter": "file:../linter"
},
"publishConfig": {
"access": "public"

View File

@ -1,52 +0,0 @@
import {
addProjectConfiguration,
readJson,
readProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import subject from './remove-deprecated-options-13-0-0';
describe('Migration: Remove deprecated options', () => {
it(`should remove deprecated node build options`, async () => {
let tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'myapp', {
root: 'apps/myapp',
sourceRoot: 'apps/myapp/src',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/node:build',
options: {
showCircularDependencies: false,
},
configurations: {
production: {
showCircularDependencies: true,
},
},
},
},
});
await subject(tree);
expect(readProjectConfiguration(tree, 'myapp')).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'myapp',
root: 'apps/myapp',
sourceRoot: 'apps/myapp/src',
projectType: 'application',
targets: {
build: {
executor: '@nrwl/node:build',
options: {},
configurations: {
production: {},
},
},
},
});
});
});

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