feat(nest): add v10 migrations for tsconfig & CacheModule (#17741)
This commit is contained in:
parent
5fa6e487eb
commit
62651a52dc
@ -11,6 +11,12 @@
|
||||
"version": "16.0.0-beta.1",
|
||||
"description": "Replace @nrwl/nest with @nx/nest",
|
||||
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
|
||||
},
|
||||
"update-16-4-0-support-nestjs-10": {
|
||||
"cli": "nx",
|
||||
"version": "16.4.0-beta.16",
|
||||
"description": "Update TsConfig target to es2021 and CacheModule if being used. Read more at https://docs.nestjs.com/migration-guide",
|
||||
"implementation": "./src/migrations/update-16-4-0-cache-manager/nestjs-10-updates"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -35,7 +35,8 @@
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/linter": "file:../linter",
|
||||
"@nx/node": "file:../node"
|
||||
"@nx/node": "file:../node",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
import {
|
||||
Tree,
|
||||
addDependenciesToPackageJson,
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
getProjects,
|
||||
joinPathFragments,
|
||||
updateJson,
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import {
|
||||
ImportDeclaration,
|
||||
VariableStatement,
|
||||
ScriptTarget,
|
||||
isVariableStatement,
|
||||
} from 'typescript';
|
||||
|
||||
const JS_TS_FILE_MATCHER = /\.[jt]sx?$/;
|
||||
|
||||
const importMatch =
|
||||
':matches(ImportDeclaration, VariableStatement):has(Identifier[name="CacheModule"], Identifier[name="CacheModule"]):has(StringLiteral[value="@nestjs/common"])';
|
||||
|
||||
export async function updateNestJs10(tree: Tree) {
|
||||
const nestProjects = await getNestProejcts();
|
||||
if (nestProjects.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let installCacheModuleDeps = false;
|
||||
const projects = getProjects(tree);
|
||||
|
||||
for (const projectName of nestProjects) {
|
||||
const projectConfig = projects.get(projectName);
|
||||
const tsConfig =
|
||||
projectConfig.targets?.build?.options?.tsConfig ??
|
||||
joinPathFragments(
|
||||
projectConfig.root,
|
||||
projectConfig.projectType === 'application'
|
||||
? 'tsconfig.app.json'
|
||||
: 'tsconfig.lib.json'
|
||||
);
|
||||
|
||||
if (tree.exists(tsConfig)) {
|
||||
updateTsConfigTarget(tree, tsConfig);
|
||||
}
|
||||
|
||||
visitNotIgnoredFiles(tree, projectConfig.root, (filePath) => {
|
||||
if (!JS_TS_FILE_MATCHER.test(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
installCacheModuleDeps =
|
||||
updateCacheManagerImport(tree, filePath) || installCacheModuleDeps;
|
||||
});
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
|
||||
return installCacheModuleDeps
|
||||
? addDependenciesToPackageJson(
|
||||
tree,
|
||||
{
|
||||
'@nestjs/cache-manager': '^2.0.0',
|
||||
'cache-manager': '^5.2.3',
|
||||
},
|
||||
{}
|
||||
)
|
||||
: () => {};
|
||||
}
|
||||
|
||||
async function getNestProejcts(): Promise<string[]> {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
|
||||
return Object.entries(projectGraph.dependencies)
|
||||
.filter(([node, dep]) =>
|
||||
dep.some(
|
||||
({ target }) =>
|
||||
!projectGraph.externalNodes?.[node] && target === 'npm:@nestjs/common'
|
||||
)
|
||||
)
|
||||
.map(([projectName]) => projectName);
|
||||
}
|
||||
|
||||
// change import { CacheModule } from '@nestjs/common';
|
||||
// to import { CacheModule } from '@nestjs/cache-manager';
|
||||
export function updateCacheManagerImport(
|
||||
tree: Tree,
|
||||
filePath: string
|
||||
): boolean {
|
||||
const content = tree.read(filePath, 'utf-8');
|
||||
|
||||
const updated = tsquery.replace(
|
||||
content,
|
||||
importMatch,
|
||||
|
||||
(node: ImportDeclaration | VariableStatement) => {
|
||||
const text = node.getText();
|
||||
return `${text.replace('CacheModule', '')}\n${
|
||||
isVariableStatement(node)
|
||||
? "const { CacheModule } = require('@nestjs/cache-manager')"
|
||||
: "import { CacheModule } from '@nestjs/cache-manager';"
|
||||
}`;
|
||||
}
|
||||
);
|
||||
|
||||
if (updated !== content) {
|
||||
tree.write(filePath, updated);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTsConfigTarget(tree: Tree, tsConfigPath: string) {
|
||||
updateJson(tree, tsConfigPath, (json) => {
|
||||
if (!json.compilerOptions.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedTargetName = json.compilerOptions.target.toUpperCase();
|
||||
// es6 isn't apart of the ScriptTarget enum but is a valid tsconfig target in json file
|
||||
const existingTarget =
|
||||
normalizedTargetName === 'ES6'
|
||||
? ScriptTarget.ES2015
|
||||
: (ScriptTarget[normalizedTargetName] as unknown as ScriptTarget);
|
||||
|
||||
if (existingTarget < ScriptTarget.ES2021) {
|
||||
json.compilerOptions.target = 'es2021';
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
export default updateNestJs10;
|
||||
@ -0,0 +1,242 @@
|
||||
import {
|
||||
ProjectConfiguration,
|
||||
ProjectGraph,
|
||||
Tree,
|
||||
addProjectConfiguration,
|
||||
readJson,
|
||||
} from '@nx/devkit';
|
||||
import {
|
||||
updateNestJs10,
|
||||
updateCacheManagerImport,
|
||||
updateTsConfigTarget,
|
||||
} from './nestjs-10-updates';
|
||||
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
|
||||
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual('@nx/devkit'),
|
||||
createProjectGraphAsync: () => Promise.resolve(projectGraph),
|
||||
}));
|
||||
describe('nestjs 10 migration changes', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update nestjs project', async () => {
|
||||
tree.write(
|
||||
'apps/app1/main.ts',
|
||||
`
|
||||
/**
|
||||
* This is not a production server yet!
|
||||
* This is only a minimal backend to get started.
|
||||
*/
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { CacheModule } from '@nestjs/common';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const globalPrefix = 'api';
|
||||
app.setGlobalPrefix(globalPrefix);
|
||||
const port = process.env.PORT || 3000;
|
||||
await app.listen(port);
|
||||
Logger.log('🚀 Application is running on: http://localhost:' + port + '/' + globalPrefix);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
`
|
||||
);
|
||||
|
||||
tree.write(
|
||||
'apps/app1/tsconfig.app.json',
|
||||
JSON.stringify({
|
||||
extends: './tsconfig.json',
|
||||
compilerOptions: {
|
||||
outDir: '../../dist/out-tsc',
|
||||
module: 'commonjs',
|
||||
types: ['node'],
|
||||
emitDecoratorMetadata: true,
|
||||
target: 'es2015',
|
||||
},
|
||||
exclude: ['jest.config.ts', 'src/**/*.spec.ts', 'src/**/*.test.ts'],
|
||||
include: ['src/**/*.ts'],
|
||||
})
|
||||
);
|
||||
addProject(
|
||||
tree,
|
||||
'app1',
|
||||
{
|
||||
root: 'apps/app1',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nx/webpack:webpack',
|
||||
options: {
|
||||
tsConfig: 'apps/app1/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
['npm:@nestjs/common']
|
||||
);
|
||||
|
||||
await updateNestJs10(tree);
|
||||
|
||||
expect(readJson(tree, 'package.json').dependencies).toMatchInlineSnapshot(`
|
||||
{
|
||||
"@nestjs/cache-manager": "^2.0.0",
|
||||
"cache-manager": "^5.2.3",
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
readJson(tree, 'apps/app1/tsconfig.app.json').compilerOptions.target
|
||||
).toEqual('es2021');
|
||||
expect(tree.read('apps/app1/main.ts', 'utf-8')).toContain(
|
||||
"import { CacheModule } from '@nestjs/cache-manager';"
|
||||
);
|
||||
});
|
||||
|
||||
it('should work with non buildable lib', async () => {
|
||||
tree.write(
|
||||
'libs/lib1/src/lib/lib1.module.ts',
|
||||
`
|
||||
import { Module, CacheModule } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
controllers: [],
|
||||
providers: [],
|
||||
exports: [],
|
||||
imports: [CacheModule.register()],
|
||||
})
|
||||
export class LibOneModule {}
|
||||
`
|
||||
);
|
||||
|
||||
tree.write(
|
||||
'libs/lib1/tsconfig.lib.json',
|
||||
JSON.stringify({
|
||||
extends: './tsconfig.json',
|
||||
compilerOptions: {
|
||||
outDir: '../../dist/out-tsc',
|
||||
module: 'commonjs',
|
||||
types: ['node'],
|
||||
emitDecoratorMetadata: true,
|
||||
target: 'es6',
|
||||
},
|
||||
exclude: ['jest.config.ts', 'src/**/*.spec.ts', 'src/**/*.test.ts'],
|
||||
include: ['src/**/*.ts'],
|
||||
})
|
||||
);
|
||||
addProject(
|
||||
tree,
|
||||
'app1',
|
||||
{
|
||||
root: 'libs/lib1',
|
||||
targets: {},
|
||||
},
|
||||
['npm:@nestjs/common']
|
||||
);
|
||||
|
||||
await updateNestJs10(tree);
|
||||
|
||||
expect(readJson(tree, 'package.json').dependencies).toMatchInlineSnapshot(`
|
||||
{
|
||||
"@nestjs/cache-manager": "^2.0.0",
|
||||
"cache-manager": "^5.2.3",
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
readJson(tree, 'libs/lib1/tsconfig.lib.json').compilerOptions.target
|
||||
).toEqual('es2021');
|
||||
expect(tree.read('libs/lib1/src/lib/lib1.module.ts', 'utf-8')).toContain(
|
||||
"import { CacheModule } from '@nestjs/cache-manager';"
|
||||
);
|
||||
});
|
||||
|
||||
it('should update cache module import', () => {
|
||||
tree.write(
|
||||
'main.ts',
|
||||
`
|
||||
import { Module, CacheModule } from '@nestjs/common';
|
||||
const { Module, CacheModule } = require('@nestjs/common');
|
||||
`
|
||||
);
|
||||
const actual = updateCacheManagerImport(tree, 'main.ts');
|
||||
|
||||
expect(tree.read('main.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"
|
||||
import { Module, } from '@nestjs/common';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
const { Module, } = require('@nestjs/common');
|
||||
const { CacheModule } = require('@nestjs/cache-manager')
|
||||
"
|
||||
`);
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it('should NOT update cache module imports', () => {
|
||||
tree.write(
|
||||
'main.ts',
|
||||
`
|
||||
import { AnotherModule } from '@nestjs/common';
|
||||
const { AnotherModule } = require('@nestjs/common');
|
||||
`
|
||||
);
|
||||
const actual = updateCacheManagerImport(tree, 'main.ts');
|
||||
|
||||
expect(tree.read('main.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"
|
||||
import { AnotherModule } from '@nestjs/common';
|
||||
const { AnotherModule } = require('@nestjs/common');
|
||||
"
|
||||
`);
|
||||
expect(actual).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should update script target', () => {
|
||||
tree.write(
|
||||
'tsconfig.json',
|
||||
JSON.stringify({ compilerOptions: { target: 'es6' } })
|
||||
);
|
||||
updateTsConfigTarget(tree, 'tsconfig.json');
|
||||
expect(readJson(tree, 'tsconfig.json').compilerOptions.target).toBe(
|
||||
'es2021'
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT update script if over es2021', () => {
|
||||
tree.write(
|
||||
'tsconfig.json',
|
||||
JSON.stringify({ compilerOptions: { target: 'es2022' } })
|
||||
);
|
||||
updateTsConfigTarget(tree, 'tsconfig.json');
|
||||
expect(readJson(tree, 'tsconfig.json').compilerOptions.target).toBe(
|
||||
'es2022'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function addProject(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
config: ProjectConfiguration,
|
||||
dependencies: string[]
|
||||
): void {
|
||||
projectGraph = {
|
||||
dependencies: {
|
||||
[projectName]: dependencies.map((d) => ({
|
||||
source: projectName,
|
||||
target: d,
|
||||
type: 'static',
|
||||
})),
|
||||
},
|
||||
nodes: {
|
||||
[projectName]: { data: config, name: projectName, type: 'app' },
|
||||
},
|
||||
};
|
||||
addProjectConfiguration(tree, projectName, config);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user