JHipster v7 upgrade tips

Tip submitted by @kaidohallik

Prettier for HTML

We have enabled Prettier for HTML. You can run Prettier with html enabled before upgrading and commit changes to Git to see real changes in HTML files after upgrade.

Translation directive change in Angular client

We have moved jhiTranslate directive from ng-jhipster to generated application. If you have different jhiPrefix than jhi then you may want to replace all jhiTranslate with yourprefixTranslate and commit changes to Git before upgrade to reduce changes shown after upgrade.

Files renamed in Angular client

In Angular client a lot of files renamed and tests moved next to files they are testing.

In manual upgrade instructions page is section Hints about renamed files - this can help in upgrading.

If the previous hint did not give a satisfactory result then you can do before upgrade:

  • move renamed files to their final location
  • commit changes after that

Helper node script for moving entity files

const fs = require('fs');
const path = require('path');

moveEntityFilesToV7Location = function (appDir) {
  const entityConfigDir = path.join(appDir, '.jhipster');
  fs
    .readdirSync(entityConfigDir)
    .filter(fn => fn.endsWith('.json'))
    .forEach(fn => {
      console.log(`\nProcessing ${fn}`);
      const entity = JSON.parse(fs.readFileSync(path.join(entityConfigDir, fn), 'utf8'));
      let entityFileName = kebabCase(entity.name);
      const entityClientRootFolder = entity.clientRootFolder || '';
      if (entity.angularJSSuffix) {
        entityFileName = `${entityFileName}-${kebabCase(entity.angularJSSuffix)}`;
      }
      const entityFolder = path.join(appDir, 'src/main/webapp/app/entities', entityClientRootFolder, entityFileName);
      const entityTestFolder = path.join(appDir, 'src/test/javascript/spec/app/entities', entityClientRootFolder, entityFileName);
      const entityModelFolder = path.join(appDir, 'src/main/webapp/app/shared/model', entityClientRootFolder);
      // move main files
      renameFile(entityFolder, `${entityFileName}.component.ts`, 'list');
      renameFile(entityFolder, `${entityFileName}.component.html`, 'list');
      renameFile(entityFolder, `${entityFileName}-delete-dialog.component.ts`, 'delete');
      renameFile(entityFolder, `${entityFileName}-delete-dialog.component.html`, 'delete');
      renameFile(entityFolder, `${entityFileName}-detail.component.ts`, 'detail');
      renameFile(entityFolder, `${entityFileName}-detail.component.html`, 'detail');
      renameFile(entityFolder, `${entityFileName}-update.component.ts`, 'update');
      renameFile(entityFolder, `${entityFileName}-update.component.html`, 'update');
      renameFile(entityFolder, `${entityFileName}.service.ts`, 'service');
      // route folder and file name both changed
      renameFile(entityFolder, `${entityFileName}.route.ts`, 'route', undefined, `${entityFileName}-routing.module.ts`);
      // model is moved from shared
      renameFile(entityFolder, `${entityFileName}.model.ts`, '', entityModelFolder);
      // move test files
      renameFile(entityFolder, `${entityFileName}.component.spec.ts`, 'list', entityTestFolder);
      renameFile(entityFolder, `${entityFileName}-delete-dialog.component.spec.ts`, 'delete', entityTestFolder);
      renameFile(entityFolder, `${entityFileName}-detail.component.spec.ts`, 'detail', entityTestFolder);
      renameFile(entityFolder, `${entityFileName}-update.component.spec.ts`, 'update', entityTestFolder);
      renameFile(entityFolder, `${entityFileName}.service.spec.ts`, 'service', entityTestFolder);
    });
}

renameFile = function (entityFolder, fileName, destinationFolderName, entityFromFolder, newFileName) {
  const oldFile = path.join(entityFromFolder || entityFolder, fileName);
  if (fs.existsSync(oldFile)) {
    const destinationFolder = path.join(entityFolder, destinationFolderName);
    if (!fs.existsSync(destinationFolder)) {
      console.log(`creating folder ${destinationFolder}`);
      fs.mkdirSync(destinationFolder);
    }
    const newFile = path.join(destinationFolder, newFileName || fileName);
    console.log(`moving ${oldFile} to ${newFile}`);
    fs.renameSync(oldFile, newFile);
  }
}

kebabCase = function (str) {
  return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}

// change 'appPath' to your application path
moveEntityFilesToV7Location('appPath');

// alternative usage with commandline parameter: node move-angular-entity-files-to-jhipster-v7-location.js appPath
// const folder = process.argv[2];
// console.log(`Processing folder ${folder}`);
// moveEntityFilesToV7Location(folder);

Helper node script for moving main files

const fs = require('fs');
const path = require('path');

// from test files only jest.conf is included in this files move script
moveMainFilesToV7Location = function (appDir) {
  this.appDir = appDir;
  renameFile('account/password/password-strength-bar.component.ts', 'account/password/password-strength-bar/password-strength-bar.component.ts');
  renameFile('account/password/password-strength-bar.scss', 'account/password/password-strength-bar/password-strength-bar.component.scss');
  renameFile('admin/docs/docs.scss', 'admin/docs/docs.component.scss');
  renameFile('admin/health/health-modal.component.ts', 'admin/health/modal/health-modal.component.ts');
  renameFile('admin/health/health-modal.component.html', 'admin/health/modal/health-modal.component.html');
  renameFile('admin/user-management/user-management-delete-dialog.component.ts', 'admin/user-management/delete/user-management-delete-dialog.component.ts');
  renameFile('admin/user-management/user-management-delete-dialog.component.html', 'admin/user-management/delete/user-management-delete-dialog.component.html');
  renameFile('admin/user-management/user-management-detail.component.ts', 'admin/user-management/detail/user-management-detail.component.ts');
  renameFile('admin/user-management/user-management-detail.component.html', 'admin/user-management/detail/user-management-detail.component.html');
  renameFile('admin/user-management/user-management-update.component.ts', 'admin/user-management/update/user-management-update.component.ts');
  renameFile('admin/user-management/user-management-update.component.html', 'admin/user-management/update/user-management-update.component.html');
  renameFile('admin/user-management/user-management.component.ts', 'admin/user-management/list/user-management.component.ts');
  renameFile('admin/user-management/user-management.component.html', 'admin/user-management/list/user-management.component.html');
  renameFile('blocks/config/uib-pagination.config.ts', 'config/uib-pagination.config.ts');
  renameFile('blocks/interceptor/auth-expired.interceptor.ts', 'core/interceptor/auth-expired.interceptor.ts');
  renameFile('blocks/interceptor/auth.interceptor.ts', 'core/interceptor/auth.interceptor.ts');
  renameFile('blocks/interceptor/errorhandler.interceptor.ts', 'core/interceptor/error-handler.interceptor.ts');
  renameFile('blocks/interceptor/notification.interceptor.ts', 'core/interceptor/notification.interceptor.ts');
  renameFile('core/auth/user-route-access-service.ts', 'core/auth/user-route-access.service.ts');
  renameFile('core/icons/font-awesome-icons.ts', 'config/font-awesome-icons.ts');
  renameFile('core/login/login.model.ts', 'login/login.model.ts');
  renameFile('core/login/login.service.ts', 'login/login.service.ts');
  renameFile('core/login/logout.model.ts', 'login/logout.model.ts');
  renameFile('core/language/language.constants.ts', 'config/language.constants.ts');
  renameFile('entities/entity.module.ts', 'entities/entity-routing.module.ts');
  renameFile('home/home.scss', 'home/home.component.scss');
  renameFile('layouts/navbar/navbar.scss', 'layouts/navbar/navbar.component.scss');
  renameFile('layouts/profiles/page-ribbon.scss', 'layouts/profiles/page-ribbon.component.scss');
  renameFile('shared/constants/authority.constants.ts', 'config/authority.constants.ts');
  renameFile('shared/constants/error.constants.ts', 'config/error.constants.ts');
  renameFile('shared/constants/input.constants.ts', 'config/input.constants.ts');
  renameFile('shared/constants/pagination.constants.ts', 'config/pagination.constants.ts');
  renameFile('shared/login/login.component.ts', 'login/login.component.ts');
  renameFile('shared/login/login.component.html', 'login/login.component.html');
  renameFile('shared/util/datepicker-adapter.ts', 'config/datepicker-adapter.ts');
  renameFile('shared/util/request-util.ts', 'core/request/request-util.ts');
  renameFile('app.main.ts', '../main.ts')
  renameFile('polyfills.ts', '../polyfills.ts')
  renameFile('../../../test/javascript/jest.conf.js', '../../../../jest.conf.js');

  // if you are using version greater than 7.0.0-beta.0 (generator-jhipster main branch or newer release)
  // then you may want to uncomment lines below
  //renameFile('core/user/account.model.ts', 'core/auth/account.model.ts');
  //renameFile('core/user/user.model.ts', 'entities/user/user.model.ts', true);
  //renameFile('core/user/user.service.ts', 'entities/user/user.service.ts', true);
  //renameFile('core/user/user.model.ts', 'admin/user-management/user-management.model.ts');
  //renameFile('core/user/user.service.ts', 'admin/user-management/service/user-management.service.ts');
}

renameFile = function (source, destination, copyOnly) {
  const srcDir = 'src/main/webapp/app';
  const oldFile = path.join(this.appDir, srcDir, source);
  if (fs.existsSync(oldFile)) {
    const newFile = path.join(this.appDir, srcDir, destination);
    const destinationFolder = path.dirname(newFile);
    if (!fs.existsSync(destinationFolder)) {
      console.log(`creating folder ${destinationFolder}`);
      fs.mkdirSync(destinationFolder);
    }
    if (copyOnly) {
      console.log(`copying ${oldFile} to ${newFile}`);
      fs.copyFileSync(oldFile, newFile);
    } else {
      console.log(`moving ${oldFile} to ${newFile}`);
      fs.renameSync(oldFile, newFile);
    }
  }
}

// change 'appPath' to your application path
moveMainFilesToV7Location('appPath');

// alternative usage with commandline parameter: node move-angular-main-files-to-jhipster-v7-location.js 'appPath'
// const folder = process.argv[2];
// console.log(`Processing folder ${folder}`);
// moveMainFilesToV7Location(folder);

Helper script to copy tests next to files they test

const fs = require('fs');
const path = require('path');

moveTestsNextToFilesTheyTest = function(appDir) {
  copyFiles(path.join(appDir, 'src/test/javascript/spec/app'), path.join(appDir, 'src/main/webapp/app'));
}

copyFiles = function(sourceDir, destinationDir) {
  fs.readdirSync(sourceDir).forEach(function(file) {
    const sourceFileWithPath = path.join(sourceDir, file);
    const destinationFileWithPath = path.join(destinationDir, file);
    if (fs.statSync(sourceFileWithPath).isDirectory()) {
      copyFiles(sourceFileWithPath, destinationFileWithPath);
    } else {
      fs.copyFile(sourceFileWithPath, destinationFileWithPath, (err) => {
        // if destination folder doesn't exist in sources folder then throw error
        // those cases need to be handled manually
        if (err) {
          throw err;
        }
        console.log(`${sourceFileWithPath} was copied to ${destinationFileWithPath}`);
      });
    }
  });
};

// change 'appPath' to your application path
moveTestsNextToFilesTheyTest('appPath');

// alternative usage with commandline parameter: node move-tests-next-to-files-they-test.js appPath
// const folder = process.argv[2];
// console.log(`Processing folder ${folder}`);
// moveTestsNextToFilesTheyTest(folder);