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",
|
"version": "16.0.0-beta.1",
|
||||||
"description": "Replace @nrwl/nest with @nx/nest",
|
"description": "Replace @nrwl/nest with @nx/nest",
|
||||||
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
|
"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": {
|
"packageJsonUpdates": {
|
||||||
|
|||||||
@ -35,7 +35,8 @@
|
|||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
"@nx/linter": "file:../linter",
|
"@nx/linter": "file:../linter",
|
||||||
"@nx/node": "file:../node"
|
"@nx/node": "file:../node",
|
||||||
|
"@phenomnomnominal/tsquery": "~5.0.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"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