Skip to content

Commit

Permalink
sort pom file using transform
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima committed Aug 1, 2024
1 parent 8d0d5bd commit 6909fc4
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 115 deletions.
31 changes: 29 additions & 2 deletions generators/maven/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
* limitations under the License.
*/
/* eslint-disable consistent-return */
import assert from 'assert/strict';
import assert from 'node:assert/strict';
import { basename } from 'node:path';
import { passthrough } from '@yeoman/transform';
import { isFileStateModified } from 'mem-fs-editor/state';

import BaseApplicationGenerator from '../base-application/index.js';

import { type GeneratorDefinition as SpringBootGeneratorDefinition } from '../server/index.js';
import files from './files.js';
import { MAVEN } from './constants.js';
import cleanupOldServerFilesTask from './cleanup.js';
import { createPomStorage, type PomStorage } from './support/index.js';
import { createPomStorage, sortPomFile, type PomStorage } from './support/index.js';

export default class MavenGenerator extends BaseApplicationGenerator<SpringBootGeneratorDefinition> {
pomStorage!: PomStorage;
Expand Down Expand Up @@ -127,6 +130,30 @@ export default class MavenGenerator extends BaseApplicationGenerator<SpringBootG
return this.delegateTasksToBlueprint(() => this.preparing);
}

get default() {
return this.asDefaultTaskGroup({
queueTranslateTransform() {
if (this.sortMavenPom) {
this.queueTransformStream(
{
name: 'translating angular application',
filter: file =>
isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && basename(file.path) === 'pom.xml',
refresh: false,
},
passthrough(file => {
file.contents = Buffer.from(sortPomFile(file.contents.toString()));
}),
);
}
},
});
}

get [BaseApplicationGenerator.DEFAULT]() {
return this.delegateTasksToBlueprint(() => this.default);
}

get writing() {
return this.asWritingTaskGroup({
cleanupOldServerFilesTask,
Expand Down
134 changes: 134 additions & 0 deletions generators/maven/internal/pom-project-sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Copyright 2013-2024 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 sortKeys from 'sort-keys';

import { MavenArtifact, MavenProfile } from '../types.js';

const rootOrder = [
'modelVersion',
'groupId',
'artifactId',
'version',
'packaging',
'name',
'description',
'parent',
'repositories',
'pluginRepositories',
'distributionManagement',
'properties',
'dependencyManagement',
'dependencies',
'build',
'profiles',
];

const propertiesOrder = [
'maven.version',
'java.version',
'node.version',
'npm.version',
'project.build.sourceEncoding',
'project.reporting.outputEncoding',
'maven.build.timestamp.format',
'maven.compiler.source',
'maven.compiler.target',
'start-class',
'argLine',
'm2e.apt.activation',
'run.addResources',
'jhipster-dependencies.version',
'spring-boot.version',
];

const groupIdOrder = ['tech.jhipster', 'org.springframework.boot', 'org.springframework.security', 'org.springdoc'];

const sortSection = section => {
return Object.fromEntries(
Object.entries(section).sort(([key1, value1], [key2, value2]) => {
if (typeof value1 === typeof value2) key1.localeCompare(key2);
if (typeof value1 === 'string') return -1;
if (typeof value2 === 'string') return 1;
return 0;
}),
);
};

const isComment = name => name.startsWith('#');

const toMaxInt = nr => (nr === -1 ? Number.MAX_SAFE_INTEGER : nr);

const sortWithTemplate = (template: string[], a: string, b: string) => {
if (isComment(a)) return -1;
if (isComment(b)) return 1;
const indexOfA = toMaxInt(template.findIndex(item => item === a));
const indexOfB = toMaxInt(template.findIndex(item => item === b));
if (indexOfA === indexOfB) {
return a.localeCompare(b);
}
return indexOfA - indexOfB;
};

const comparator = (order: string[]) => (a: string, b: string) => sortWithTemplate(order, a, b);

const sortProperties = properties => sortKeys(properties, { compare: comparator(propertiesOrder) });

const sortArtifacts = (artifacts: MavenArtifact[]) =>
artifacts.sort((a: MavenArtifact, b: MavenArtifact) => {
if (a.groupId !== b.groupId) {
if (a.groupId === undefined) {
return -1;
}
if (b.groupId === undefined) {
return 1;
}
const groupIdCompared = sortWithTemplate(groupIdOrder, a.groupId, b.groupId);
if (groupIdCompared) return groupIdCompared;
}
return a.artifactId.localeCompare(b.artifactId);
});

const sortProfiles = (profiles: MavenProfile[]) => profiles.sort((a, b) => a.id?.localeCompare(b.id) ?? 1);

export const sortPomProject = project => {
project = sortKeys(project, { compare: comparator(rootOrder) });
if (project.properties) {
project.properties = sortProperties(project.properties);
}
if (Array.isArray(project.dependencies?.dependency)) {
project.dependencies.dependency = sortArtifacts(project.dependencies.dependency);
}
if (Array.isArray(project.dependencyManagement?.dependencies?.dependency)) {
project.dependencyManagement.dependencies.dependency = sortArtifacts(project.dependencyManagement.dependencies.dependency);
}
if (project.build) {
project.build = sortSection(project.build);

if (Array.isArray(project.build.plugins?.plugin)) {
project.build.plugins.plugin = sortArtifacts(project.build.plugins.plugin);
}
if (Array.isArray(project.build.pluginManagement?.plugins?.plugin)) {
project.build.pluginManagement.plugins.plugin = sortArtifacts(project.build.pluginManagement.plugins.plugin);
}
}
if (Array.isArray(project.profiles?.profile)) {
project.profiles.profile = sortProfiles(project.profiles.profile);
}
return project;
};
4 changes: 2 additions & 2 deletions generators/maven/internal/xml-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ const defaultXmlCommonOptions: Partial<X2jOptions & XmlBuilderOptions> = {
parseAttributeValue: false,
};

const defaultXmlParserOptions: Partial<X2jOptions> = {
export const defaultXmlParserOptions: Partial<X2jOptions> = {
...defaultXmlCommonOptions,
};

const defaultXmlBuildOptions: Partial<XmlBuilderOptions> = {
export const defaultXmlBuildOptions: Partial<XmlBuilderOptions> = {
...defaultXmlCommonOptions,
suppressBooleanAttributes: false,
suppressEmptyNode: true,
Expand Down
1 change: 1 addition & 0 deletions generators/maven/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@

export * from './dependabot-maven.js';
export { default as PomStorage } from './pom-store.js';
export * from './pom-file-sort.js';
export * from './pom-store.js';
33 changes: 33 additions & 0 deletions generators/maven/support/pom-file-sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2013-2024 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 { XMLParser, XMLBuilder, XmlBuilderOptions, X2jOptions } from 'fast-xml-parser';

import { defaultXmlBuildOptions, defaultXmlParserOptions } from '../internal/xml-store.js';
import { sortPomProject } from '../internal/pom-project-sort.js';

type SortPomFileOptions = { xmlParserOptions?: Partial<X2jOptions>; xmlBuildOptions?: Partial<XmlBuilderOptions> };

export const sortPomFile = (pomFile: string, { xmlParserOptions, xmlBuildOptions }: SortPomFileOptions = {}): string => {
const parser = new XMLParser({ ...defaultXmlParserOptions, ...xmlParserOptions });
const pomObject = parser.parse(pomFile);
pomObject.project = sortPomProject(pomObject.project);

const builder = new XMLBuilder({ ...defaultXmlBuildOptions, ...xmlBuildOptions });
return builder.build(pomObject);
};
113 changes: 2 additions & 111 deletions generators/maven/support/pom-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/

import { set, get } from 'lodash-es';
import sortKeys from 'sort-keys';

import CoreGenerator from '../../base-core/index.js';
import XmlStorage from '../internal/xml-store.js';
Expand All @@ -32,80 +31,14 @@ import {
MavenProperty,
MavenRepository,
} from '../types.js';

const rootOrder = [
'modelVersion',
'groupId',
'artifactId',
'version',
'packaging',
'name',
'description',
'parent',
'repositories',
'pluginRepositories',
'distributionManagement',
'properties',
'dependencyManagement',
'dependencies',
'build',
'profiles',
];

const propertiesOrder = [
'maven.version',
'java.version',
'node.version',
'npm.version',
'project.build.sourceEncoding',
'project.reporting.outputEncoding',
'maven.build.timestamp.format',
'maven.compiler.source',
'maven.compiler.target',
'start-class',
'argLine',
'm2e.apt.activation',
'run.addResources',
'jhipster-dependencies.version',
'spring-boot.version',
];
import { sortPomProject } from '../internal/pom-project-sort.js';

const formatFirstXmlLevel = content =>
content.replace(
/(\n {4}<(?:groupId|distributionManagement|repositories|pluginRepositories|properties|dependencyManagement|dependencies|build|profiles)>)/g,
'\n$1',
);

const sortSection = section => {
return Object.fromEntries(
Object.entries(section).sort(([key1, value1], [key2, value2]) => {
if (typeof value1 === typeof value2) key1.localeCompare(key2);
if (typeof value1 === 'string') return -1;
if (typeof value2 === 'string') return 1;
return 0;
}),
);
};

const isComment = name => name.startsWith('#');

const toMaxInt = nr => (nr === -1 ? Number.MAX_SAFE_INTEGER : nr);

const sortWithTemplate = (template: string[], a: string, b: string) => {
if (isComment(a)) return -1;
if (isComment(b)) return 1;
const indexOfA = toMaxInt(template.findIndex(item => item === a));
const indexOfB = toMaxInt(template.findIndex(item => item === b));
if (indexOfA === indexOfB) {
return a.localeCompare(b);
}
return indexOfA - indexOfB;
};

const comparator = (order: string[]) => (a: string, b: string) => sortWithTemplate(order, a, b);

const sortProperties = properties => sortKeys(properties, { compare: comparator(propertiesOrder) });

const artifactEquals = (a: MavenArtifact, b: MavenArtifact) => {
return a.groupId === b.groupId && a.artifactId === b.artifactId;
};
Expand Down Expand Up @@ -154,25 +87,6 @@ const ensureProfile = (project, profileId: string) => {
return appendOrGet(profileArray, { id: profileId }, idEquals);
};

const groupIdOrder = ['tech.jhipster', 'org.springframework.boot', 'org.springframework.security', 'org.springdoc'];

const sortArtifacts = (artifacts: MavenArtifact[]) =>
artifacts.sort((a: MavenArtifact, b: MavenArtifact) => {
if (a.groupId !== b.groupId) {
if (a.groupId === undefined) {
return -1;
}
if (b.groupId === undefined) {
return 1;
}
const groupIdCompared = sortWithTemplate(groupIdOrder, a.groupId, b.groupId);
if (groupIdCompared) return groupIdCompared;
}
return a.artifactId.localeCompare(b.artifactId);
});

const sortProfiles = (profiles: MavenProfile[]) => profiles.sort((a, b) => a.id?.localeCompare(b.id) ?? 1);

const ensureChildPath = (node: any, childPath) => {
let child = get(node, childPath);
if (child) return child;
Expand Down Expand Up @@ -334,30 +248,7 @@ export default class PomStorage extends XmlStorage {

protected sort() {
if (this.store.project) {
const project = sortKeys(this.store.project, { compare: comparator(rootOrder) });
this.store.project = project;
if (project.properties) {
project.properties = sortProperties(project.properties);
}
if (Array.isArray(project.dependencies?.dependency)) {
project.dependencies.dependency = sortArtifacts(project.dependencies.dependency);
}
if (Array.isArray(project.dependencyManagement?.dependencies?.dependency)) {
project.dependencyManagement.dependencies.dependency = sortArtifacts(project.dependencyManagement.dependencies.dependency);
}
if (project.build) {
project.build = sortSection(project.build);

if (Array.isArray(project.build.plugins?.plugin)) {
project.build.plugins.plugin = sortArtifacts(project.build.plugins.plugin);
}
if (Array.isArray(project.build.pluginManagement?.plugins?.plugin)) {
project.build.pluginManagement.plugins.plugin = sortArtifacts(project.build.pluginManagement.plugins.plugin);
}
}
if (Array.isArray(project.profiles?.profile)) {
project.profiles.profile = sortProfiles(project.profiles.profile);
}
this.store.project = sortPomProject(this.store.project);
}
}
}
Expand Down

0 comments on commit 6909fc4

Please sign in to comment.