Skip to content

Commit

Permalink
Implement generator-base-entity-changes (#23357)
Browse files Browse the repository at this point in the history
* implement base-entity-changelog

* add config changes

* add base-entity-changes to list

* update snapshot

* update list snapshot
  • Loading branch information
mshima authored Aug 30, 2023
1 parent d00aef1 commit 1873b1d
Show file tree
Hide file tree
Showing 35 changed files with 640 additions and 627 deletions.
4 changes: 2 additions & 2 deletions generators/base-application/generator.mts
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@ export default class BaseApplicationGenerator<
}

/**
* @private
* @protected
*/
getTaskFirstArgForPriority(priorityName): any {
protected getTaskFirstArgForPriority(priorityName): any {
if (
![
LOADING,
Expand Down
9 changes: 5 additions & 4 deletions generators/base-application/tasks.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ControlTaskParam, BaseGeneratorDefinition, SourceTaskParam, GenericSour
import { CommonClientServerApplication } from './types.mjs';
import { Entity, Field, Relationship } from './types/index.mjs';
import { ClientSourceType } from '../client/types.mjs';
import { BaseChangelog } from '../base-entity-changes/types.js';

export type GenericApplicationDefinition<ApplicationType = CommonClientServerApplication> = {
applicationType: ApplicationType;
Expand Down Expand Up @@ -66,16 +67,16 @@ export type BaseApplicationGeneratorDefinition<
| 'loadingTaskParam'
| 'preparingTaskParam'
| 'defaultTaskParam'
| 'writingTaskParam'
| 'postWritingTaskParam'
| 'preConflictsTaskParam'
| 'installTaskParam'
| 'postInstallTaskParam'
| 'endTaskParam',
ApplicationTaskParam<Definition>
> &
Record<'writingTaskParam', ApplicationTaskParam<Definition> & { configChanges?: Record<string, { newValue: any; oldValue: any }> }> &
// Add entities to existing priorities
Record<'defaultTaskParam', EntitiesTaskParam<Definition>> &
Record<'defaultTaskParam', EntitiesTaskParam<Definition> & { entityChanges?: BaseChangelog[] }> &
// Add application and control to new priorities
Record<
| 'configuringEachEntityTaskParam'
Expand All @@ -96,6 +97,6 @@ export type BaseApplicationGeneratorDefinition<
preparingEachEntityFieldTaskParam: PreparingEachEntityFieldTaskParam<Definition>;
preparingEachEntityRelationshipTaskParam: PreparingEachEntityRelationshipTaskParam<Definition>;
postPreparingEachEntityTaskParam: EachEntityTaskParam<Definition>;
writingEntitiesTaskParam: EntitiesTaskParam<Definition>;
postWritingEntitiesTaskParam: SourceTaskParam<Definition> & EntitiesTaskParam<Definition>;
writingEntitiesTaskParam: EntitiesTaskParam<Definition> & { entityChanges?: BaseChangelog[] };
postWritingEntitiesTaskParam: SourceTaskParam<Definition> & EntitiesTaskParam<Definition> & { entityChanges?: BaseChangelog[] };
};
26 changes: 24 additions & 2 deletions generators/base-core/generator.mts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { basename, join as joinPath, dirname, relative, isAbsolute, join, extname } from 'path';
import { createHash } from 'crypto';
import { fileURLToPath } from 'url';
import { statSync, rmSync, existsSync } from 'fs';
import { statSync, rmSync, existsSync, readFileSync } from 'fs';
import assert from 'assert';
import { requireNamespace } from '@yeoman/namespace';
import chalk from 'chalk';
Expand Down Expand Up @@ -51,7 +51,7 @@ import { CommonClientServerApplication, type BaseApplication } from '../base-app
import { GENERATOR_BOOTSTRAP } from '../generator-list.mjs';
import NeedleApi from '../needle-api.mjs';
import command from '../base/command.mjs';
import { GENERATOR_JHIPSTER } from '../generator-constants.mjs';
import { GENERATOR_JHIPSTER, YO_RC_FILE } from '../generator-constants.mjs';

const { merge } = _;
const { INITIALIZING, PROMPTING, CONFIGURING, COMPOSING, LOADING, PREPARING, DEFAULT, WRITING, POST_WRITING, INSTALL, POST_INSTALL, END } =
Expand Down Expand Up @@ -225,6 +225,28 @@ export default class CoreGenerator extends YeomanGenerator<JHipsterGeneratorOpti
const source = this.sharedData.getSource();
return [{ control, source }];
}
if (priorityName === WRITING) {
if (existsSync(this.destinationPath(YO_RC_FILE))) {
try {
const oldConfig = JSON.parse(readFileSync(this.destinationPath(YO_RC_FILE)).toString())[GENERATOR_JHIPSTER];
const newConfig: any = this.config.getAll();
const keys = [...new Set([...Object.keys(oldConfig), ...Object.keys(newConfig)])];
const configChanges = Object.fromEntries(
keys
.filter(key =>
Array.isArray(newConfig[key])
? newConfig[key].length === oldConfig[key].length &&
newConfig[key].find((element, index) => element !== oldConfig[key][index])
: newConfig[key] !== oldConfig[key],
)
.map(key => [key, { newValue: newConfig[key], oldValue: oldConfig[key] }]),
);
return [{ control, configChanges }];
} catch {
// Fail to parse
}
}
}
return [{ control }];
}

Expand Down
178 changes: 178 additions & 0 deletions generators/base-entity-changes/generator.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* Copyright 2013-2023 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { existsSync, readFileSync } from 'fs';
import GeneratorBaseApplication from '../base-application/index.mjs';
import { PRIORITY_NAMES } from '../base-application/priorities.mjs';
import { loadEntitiesAnnotations, loadEntitiesOtherSide } from '../base-application/support/index.mjs';
import { relationshipEquals, relationshipNeedsForeignKeyRecreationOnly } from '../liquibase/support/index.mjs';
import { addEntitiesOtherRelationships } from '../server/support/index.mjs';
import type { BaseChangelog } from './types.js';

const { DEFAULT, WRITING_ENTITIES, POST_WRITING_ENTITIES } = PRIORITY_NAMES;

const baseChangelog: () => Omit<BaseChangelog, 'changelogDate' | 'entityName' | 'entity'> = () => ({
newEntity: false,
changedEntity: false,
incremental: false,
previousEntity: undefined,
addedFields: [],
removedFields: [],
addedRelationships: [],
removedRelationships: [],
relationshipsToRecreateForeignKeysOnly: [],
changelogData: {},
});

/**
* This is the base class for a generator for every generator.
*/
export default abstract class GeneratorBaseEntityChanges extends GeneratorBaseApplication {
recreateInitialChangelog!: boolean;
private entityChanges!: any[];

abstract isChangelogNew({ entityName, changelogDate }): boolean;

protected getTaskFirstArgForPriority(priorityName): any {
const firstArg = super.getTaskFirstArgForPriority(priorityName);
if ([DEFAULT, WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) {
this.entityChanges = this.generateIncrementalChanges();
}
if ([DEFAULT].includes(priorityName)) {
return { ...firstArg, entityChanges: this.entityChanges };
}
if ([WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) {
// const { entities = [] } = this.options;
// const filteredEntities = data.entities.filter(entity => entities.includes(entity.name));
return { ...firstArg, entityChanges: this.entityChanges };
}
return firstArg;
}

/**
* Generate changelog from differences between the liquibase entity and current entity.
*/
protected generateIncrementalChanges(): BaseChangelog[] {
const recreateInitialChangelog = this.recreateInitialChangelog;
const { generateBuiltInUserEntity, incrementalChangelog } = this.sharedData.getApplication();
const entityNames = this.getExistingEntityNames();

const entitiesByName = Object.fromEntries(entityNames.map(entityName => [entityName, this.sharedData.getEntity(entityName)]));
const entitiesWithExistingChangelog = entityNames.filter(
entityName => !this.isChangelogNew({ entityName, changelogDate: entitiesByName[entityName].changelogDate }),
);
const previousEntitiesByName = Object.fromEntries(
entityNames
.filter(entityName => existsSync(this.getEntityConfigPath(entityName)))
.map(entityName => [
entityName,
{ name: entityName, ...JSON.parse(readFileSync(this.getEntityConfigPath(entityName)).toString()) },
]),
);
if (generateBuiltInUserEntity) {
const user = this.sharedData.getEntity('User');
previousEntitiesByName.User = user;
}

const entities: any[] = Object.values(previousEntitiesByName);
loadEntitiesAnnotations(entities);
loadEntitiesOtherSide(entities);
addEntitiesOtherRelationships(entities);

// Compare entity changes and create changelogs
return entityNames.map(entityName => {
const newConfig: any = entitiesByName[entityName];
const newFields: any[] = (newConfig.fields || []).filter((field: any) => !field.transient);
const newRelationships: any[] = newConfig.relationships || [];

const oldConfig: any = previousEntitiesByName[entityName];

if (!oldConfig || recreateInitialChangelog || !incrementalChangelog || !entitiesWithExistingChangelog.includes(entityName)) {
return {
...baseChangelog(),
incremental: newConfig.incrementalChangelog,
changelogDate: newConfig.changelogDate,
newEntity: true,
entity: newConfig,
entityName,
};
}

(this as any)._debug(`Calculating diffs for ${entityName}`);

const oldFields: any[] = (oldConfig.fields || []).filter((field: any) => !field.transient);
const oldFieldNames: string[] = oldFields.filter(field => !field.id).map(field => field.fieldName);
const newFieldNames: string[] = newFields.filter(field => !field.id).map(field => field.fieldName);

// Calculate new fields
const addedFieldNames = newFieldNames.filter(fieldName => !oldFieldNames.includes(fieldName));
const addedFields = addedFieldNames.map(fieldName => newFields.find(field => fieldName === field.fieldName));
// Calculate removed fields
const removedFieldNames = oldFieldNames.filter(fieldName => !newFieldNames.includes(fieldName));
const removedFields = removedFieldNames.map(fieldName => oldFields.find(field => fieldName === field.fieldName));

const oldRelationships: any[] = oldConfig.relationships || [];

// Calculate changed/newly added relationships
const addedRelationships = newRelationships.filter(
newRelationship =>
// id changes are not supported
!newRelationship.id &&
// check if the same relationship wasn't already part of the old config
!oldRelationships.some(oldRelationship => relationshipEquals(oldRelationship, newRelationship)),
);

// Calculate to be removed relationships
const removedRelationships = oldRelationships.filter(
oldRelationship =>
// id changes are not supported
!oldRelationship.id &&
// check if there are relationships not anymore in the new config
!newRelationships.some(newRelationship => relationshipEquals(newRelationship, oldRelationship)),
);

// calcualte relationships that only need a foreign key recreation from the ones that are added
// we need both the added and the removed ones here
const relationshipsToRecreateForeignKeysOnly = addedRelationships
.filter(addedRelationship =>
removedRelationships.some(removedRelationship =>
relationshipNeedsForeignKeyRecreationOnly(removedRelationship, addedRelationship),
),
)
.concat(
removedRelationships.filter(removedRelationship =>
addedRelationships.some(addedRelationship => relationshipNeedsForeignKeyRecreationOnly(addedRelationship, removedRelationship)),
),
);

return {
...baseChangelog(),
previousEntity: oldConfig,
entity: newConfig,
incremental: true,
changedEntity: true,
entityName,
addedFields,
removedFields,
addedRelationships,
removedRelationships,
relationshipsToRecreateForeignKeysOnly,
};
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { default } from './generator.mjs';
export { addEntityFiles, updateEntityFiles, updateConstraintsFiles, updateMigrateFiles, fakeFiles } from './files.mjs';
1 change: 1 addition & 0 deletions generators/base-entity-changes/types-export.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { default } from './index.mjs';
18 changes: 18 additions & 0 deletions generators/base-entity-changes/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type BaseChangelog = {
newEntity: boolean;
changedEntity: boolean;
incremental: boolean;

entityName: string;
entity: any;

changelogDate?: string;
previousEntity?: any;

addedFields: any[];
removedFields: any[];
addedRelationships: any[];
removedRelationships: any[];
relationshipsToRecreateForeignKeysOnly: any[];
changelogData: any;
};
8 changes: 8 additions & 0 deletions generators/entities/__snapshots__/generator.spec.mts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,15 @@ exports[`generator - entities regenerating all entities should match source call
"addLiquibaseChangelog": [
{
"changelogName": "20160926101210_added_entity_Foo",
"section": "base",
},
{
"changelogName": "20160926101211_added_entity_Bar",
"section": "base",
},
{
"changelogName": "20160926101212_added_entity_Skip",
"section": "base",
},
],
}
Expand Down Expand Up @@ -661,12 +664,15 @@ exports[`generator - entities regenerating selected entities with writeEveryEnti
"addLiquibaseChangelog": [
{
"changelogName": "20160926101210_added_entity_Foo",
"section": "base",
},
{
"changelogName": "20160926101211_added_entity_Bar",
"section": "base",
},
{
"changelogName": "20160926101212_added_entity_Skip",
"section": "base",
},
],
}
Expand Down Expand Up @@ -902,9 +908,11 @@ exports[`generator - entities regenerating some entities should match source cal
"addLiquibaseChangelog": [
{
"changelogName": "20160926101210_added_entity_Foo",
"section": "base",
},
{
"changelogName": "20160926101211_added_entity_Bar",
"section": "base",
},
],
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exports[`generator - entity --single-entity when regenerating with default confi
"addLiquibaseChangelog": [
{
"changelogName": "20160926101210_added_entity_Foo",
"section": "base",
},
],
}
Expand Down
Loading

0 comments on commit 1873b1d

Please sign in to comment.