Skip to content

Commit

Permalink
add more workflows support to github-build-matrix and migrate angular…
Browse files Browse the repository at this point in the history
… workflow (#27624)
  • Loading branch information
mshima authored Oct 21, 2024
1 parent 2081ef1 commit 605b272
Show file tree
Hide file tree
Showing 12 changed files with 1,140 additions and 73 deletions.
894 changes: 894 additions & 0 deletions .blueprint/github-build-matrix/__snapshots__/generator.spec.ts.snap

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion .blueprint/github-build-matrix/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { JHipsterCommandDefinition } from '../../generators/index.js';

export const workflowChoices = ['angular', 'devserver', 'react', 'docker-compose-integration', 'vue'] as const;
export const eventNameChoices = ['push', 'pull_request', 'daily'] as const;

export default {
configs: {
workflow: {
Expand All @@ -8,7 +11,14 @@ export default {
type: String,
},
scope: 'generator',
choices: ['testcontainers', 'dev-server'],
choices: workflowChoices,
},
eventName: {
cli: {
type: String,
},
scope: 'generator',
choices: eventNameChoices,
},
},
} as const satisfies JHipsterCommandDefinition;
30 changes: 30 additions & 0 deletions .blueprint/github-build-matrix/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { basename, dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { before, describe, expect, it } from 'esmocha';
import { defaultHelpers as helpers, runResult } from '../../lib/testing/index.js';
import { shouldSupportFeatures } from '../../test/support/index.js';
import Generator from './generator.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const generator = basename(__dirname);

describe(`generator - ${generator}`, () => {
shouldSupportFeatures(Generator);

for (const workflow of ['angular', 'devserver', 'react', 'docker-compose-integration', 'vue']) {
describe(`with ${workflow}`, () => {
before(async () => {
await helpers.runJHipster(join(__dirname, 'index.ts'), { useEnvironmentBuilder: true }).withArguments(workflow);
});

it('should set workflow value', () => {
expect((runResult.generator as any).workflow).toBe(workflow);
});
it('should match matrix value', () => {
expect((runResult.generator as any).matrix).toMatchSnapshot();
});
});
}
});
107 changes: 91 additions & 16 deletions .blueprint/github-build-matrix/generator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import BaseGenerator from '../../generators/base/index.js';
import { setGithubTaskOutput } from '../../lib/testing/index.js';
import { getGithubOutputFile, setGithubTaskOutput } from '../../lib/testing/index.js';
import { getPackageRoot } from '../../lib/index.js';
import { BUILD_JHIPSTER_BOM, JHIPSTER_BOM_BRANCH, JHIPSTER_BOM_CICD_VERSION } from '../../test-integration/integration-test-constants.js';
import type { GitHubMatrix, GitHubMatrixRecord } from './support/github-ci-matrix.js';
import { convertToGitHubMatrix } from './support/github-ci-matrix.js';
import { dockerComposeMatrix } from './samples/docker-compose-integration.js';
import { getGitChanges } from './support/git-changes.js';
import { devServerMatrix } from './samples/dev-server.js';
import type { eventNameChoices, workflowChoices } from './command.js';

type JHipsterGitHubMatrix = GitHubMatrix & {
name: string;
'app-sample'?: string;
'build-jhipster-bom'?: boolean;
'gradle-cache'?: boolean;
'jhipster-bom-cicd-version'?: string;
'jhipster-bom-branch'?: string;
'sonar-analyse'?: 'true' | 'false';
workspaces?: 'true' | 'false';
'skip-frontend-tests'?: 'true' | 'false';
'skip-backend-tests'?: 'true' | 'false';
};

type JHipsterGitHubInputMatrix = JHipsterGitHubMatrix & {
generatorOptions: Record<string, any>;
};

export default class extends BaseGenerator {
workflow;
workflow!: (typeof workflowChoices)[number];
eventName?: (typeof eventNameChoices)[number];
matrix!: string;

constructor(args, opts, features) {
super(args, opts, { queueCommandTasks: true, ...features, jhipsterBootstrap: false });
Expand All @@ -15,23 +40,73 @@ export default class extends BaseGenerator {
get [BaseGenerator.WRITING]() {
return this.asWritingTaskGroup({
async buildMatrix() {
// Push events requires a base commit for diff. Diff cannot be checked by @~1 if PR was merged with a rebase.
const useChanges = this.eventName === 'pull_request';
const changes = await getGitChanges({ allTrue: !useChanges });
const { base, common, devBlueprint, client, e2e, java, workspaces } = changes;
const hasWorkflowChanges = changes[`${this.workflow}Workflow`];

let matrix: GitHubMatrixRecord = {};
let randomEnvironment = false;
if (this.workflow === 'docker-compose-integration') {
setGithubTaskOutput('matrix', JSON.stringify(convertToGitHubMatrix(dockerComposeMatrix), null, 2));
} else if (this.workflow === 'dev-server') {
const { devBlueprint, devserverCi, client, angular, react, vue } = await getGitChanges();
const matrix = {};
if (devBlueprint || devserverCi || client || angular) {
Object.assign(matrix, devServerMatrix.angular);
}
if (devBlueprint || devserverCi || client || react) {
Object.assign(matrix, devServerMatrix.react);
matrix = dockerComposeMatrix;
} else if (this.workflow === 'devserver') {
if (devBlueprint || hasWorkflowChanges || client) {
matrix = { ...devServerMatrix.angular, ...devServerMatrix.react, ...devServerMatrix.vue };
} else {
for (const client of ['angular', 'react', 'vue']) {
if (changes[client]) {
Object.assign(matrix, devServerMatrix[client]);
}
}
}
if (devBlueprint || devserverCi || client || vue) {
Object.assign(matrix, devServerMatrix.vue);
} else if (['angular', 'react', 'vue'].includes(this.workflow)) {
const hasClientFrameworkChanges = changes[this.workflow];
const enableAllTests = base || common || hasWorkflowChanges || devBlueprint;
const enableBackendTests = enableAllTests || java;
const enableFrontendTests = enableAllTests || client || hasClientFrameworkChanges;
const enableE2eTests = enableBackendTests || enableFrontendTests || e2e || workspaces;
const enableAnyTest = enableE2eTests;

randomEnvironment = true;
if (enableAnyTest) {
const content = await readFile(join(getPackageRoot(), `test-integration/workflow-samples/${this.workflow}.json`));
const parsed: { include: JHipsterGitHubInputMatrix[] } = JSON.parse(content.toString());
matrix = Object.fromEntries(
parsed.include.map((sample): [string, JHipsterGitHubMatrix] => {
const { 'job-name': jobName = sample.name, 'sonar-analyse': sonarAnalyse, generatorOptions } = sample;
const enableSonar = sonarAnalyse === 'true';
const workspaces = generatorOptions?.workspaces ? 'true' : 'false';
if (enableSonar && workspaces === 'true') {
throw new Error('Sonar is not supported with workspaces');
}
return [
jobName,
{
// Force tests if sonar is enabled
'skip-backend-tests': `${!(enableBackendTests || enableSonar)}`,
// Force tests if sonar is enabled
'skip-frontend-tests': `${!(enableFrontendTests || enableSonar)}`,
'gradle-cache': generatorOptions?.workspaces || jobName.includes('gradle') ? true : undefined,
...sample,
sample: jobName,
workspaces,
'build-jhipster-bom': BUILD_JHIPSTER_BOM,
'jhipster-bom-branch': BUILD_JHIPSTER_BOM ? JHIPSTER_BOM_BRANCH : undefined,
'jhipster-bom-cicd-version': BUILD_JHIPSTER_BOM ? JHIPSTER_BOM_CICD_VERSION : undefined,
},
];
}),
);
}
const githubMatrix = convertToGitHubMatrix(matrix);
setGithubTaskOutput('matrix', JSON.stringify(githubMatrix, null, 2));
setGithubTaskOutput('empty-matrix', githubMatrix.include.length === 0);
}

const { useVersionPlaceholders } = this;
this.matrix = JSON.stringify(convertToGitHubMatrix(matrix, { randomEnvironment, useVersionPlaceholders }), null, 2);
const githubOutputFile = getGithubOutputFile();
this.log.info('matrix', this.matrix);
if (githubOutputFile) {
setGithubTaskOutput('matrix', this.matrix);
}
},
});
Expand Down
35 changes: 24 additions & 11 deletions .blueprint/github-build-matrix/support/git-changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,38 @@ import { fileURLToPath } from 'url';
import { minimatch } from 'minimatch';
import { simpleGit } from 'simple-git';

export const getGitChanges = async () => {
const git = simpleGit({ baseDir: fileURLToPath(new URL('../../', import.meta.url).href) });
const summary = await git.diffSummary({ '@~1': null });
const files = summary.files.map(({ file }) => file);
const hasPatternChanges = (pattern: string) => files.some(file => minimatch(file, pattern, { dot: true }));
export const getGitChanges = async (options: { allTrue?: boolean } = {}) => {
let hasPatternChanges;
if (options.allTrue) {
hasPatternChanges = () => true;
} else {
const git = simpleGit({ baseDir: fileURLToPath(new URL('../../', import.meta.url).href) });
const summary = await git.diffSummary({ '@~1': null });
const files = summary.files.map(({ file }) => file);
hasPatternChanges = (pattern: string) => files.some(file => minimatch(file, pattern, { dot: true }));
}

const hasClientWorkflowChanges = (client: 'angular' | 'react' | 'vue') =>
hasPatternChanges(`.github/workflows/${client}.yml`) || hasPatternChanges(`test-integration/workflow-samples/${client}.json`);
return {
files,
hasPatternChanges,
angular: hasPatternChanges('generators/angular/**'),
angularWorkflow: hasClientWorkflowChanges('angular'),
base:
hasPatternChanges('lib/**') ||
hasPatternChanges('generators/*') ||
hasPatternChanges('generators/{base*,bootstrap*,git,jdl,project-name}/**'),
ci: hasPatternChanges('.github/{actions,workflows}/**'),
devBlueprint: hasPatternChanges('.blueprint/**'),
ci: hasPatternChanges('.github/{actions,workflows}/**') || hasPatternChanges('generators/{docker-compose,kubernetes*,workspaces}/**'),
devserverCi: hasPatternChanges('.github/workflows/devserver.yml'),
common: hasPatternChanges('generators/{app,common,cypress,docker,languages}/**'),
devserverWorkflow: hasPatternChanges('.github/workflows/devserver.yml'),
common: hasPatternChanges('generators/{app,common,docker,languages}/**'),
client: hasPatternChanges('generators/{client,init,javascript}/**'),
angular: hasPatternChanges('generators/angular/**'),
e2e: hasPatternChanges('generators/cypress/**'),
java: hasPatternChanges('generators/{cucumber,feign-client,gatling,gradle,java,liquibase,maven,server,spring*}/**'),
react: hasPatternChanges('generators/react/**'),
reactWorkflow: hasClientWorkflowChanges('react'),
workspaces: hasPatternChanges('generators/{docker-compose,kubernetes*,workspaces}/**'),
vue: hasPatternChanges('generators/vue/**'),
java: hasPatternChanges('generators/{cucumber,feign-client,gatling,gradle,java,liquibase,maven,server,spring*}/**'),
vueWorkflow: hasClientWorkflowChanges('vue'),
};
};
63 changes: 53 additions & 10 deletions .blueprint/github-build-matrix/support/github-ci-matrix.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,83 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { RECOMMENDED_JAVA_VERSION, RECOMMENDED_NODE_VERSION } from '../../../generators/index.js';
import { getPackageRoot } from '../../../lib/index.js';
import { JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, NODE_VERSION } from '../../../generators/generator-constants.js';

type GitHubMatrix = {
export type GitHubMatrix = {
os: string;
'node-version': string;
'java-version': string;
'default-environment': string;
'job-name': string;
sample: string;
args: string;
args?: string;
'jwt-secret-key'?: string;
};

type GitHubMatrixOutput = {
export type GitHubMatrixRecord = Record<string, Partial<Omit<GitHubMatrix, 'job-name'>> & { disabled?: boolean }>;

export type GitHubMatrixOutput = {
include: GitHubMatrix[];
};

const NPM_VERSION = JSON.parse(readFileSync(join(getPackageRoot(), 'generators/common/resources/package.json'), 'utf-8')).devDependencies
.npm;

export const defaultEnvironment = {
os: 'ubuntu-latest',
'node-version': RECOMMENDED_NODE_VERSION,
'java-version': RECOMMENDED_JAVA_VERSION,
'npm-version': NPM_VERSION,
'default-environment': 'prod',
'jwt-secret-key':
'ZjY4MTM4YjI5YzMwZjhjYjI2OTNkNTRjMWQ5Y2Q0Y2YwOWNmZTE2NzRmYzU3NTMwM2NjOTE3MTllOTM3MWRkMzcyYTljMjVmNmQ0Y2MxOTUzODc0MDhhMTlkMDIxMzI2YzQzZDM2ZDE3MmQ3NjVkODk3OTVmYzljYTQyZDNmMTQ=',
};

export const defaultEnvironmentMatrix = {
os: ['ubuntu-latest'],
'node-version': [RECOMMENDED_NODE_VERSION],
'java-version': [RECOMMENDED_JAVA_VERSION],
'npm-version': [NPM_VERSION],
'default-environment': ['prod'],
};

export const convertToGitHubMatrix = (matrix: Record<string, any>): GitHubMatrixOutput => {
const randomReproducibleValue = <Choice = any>(seed: string, choices: Choice[], options?: { useVersionPlaceholders?: boolean }): Choice => {
const { useVersionPlaceholders } = options ?? {};
const index = createHash('shake256', { outputLength: 1 }).update(seed, 'utf8').digest('binary').charCodeAt(0) % choices.length;
if (useVersionPlaceholders) {
return `[${index}]` as any;
}
return choices[index];
};

const randomEnvironmentMatrix = (key: string, options: { useVersionPlaceholders?: boolean }) => {
const { useVersionPlaceholders } = options;
const javaVersion = randomReproducibleValue(`java-${key}`, [JAVA_VERSION, ...JAVA_COMPATIBLE_VERSIONS], { useVersionPlaceholders });
const nodeVersion = randomReproducibleValue(`node-${key}`, [NODE_VERSION, '18', '20'], { useVersionPlaceholders });
return {
'job-name': `${key} (n${nodeVersion}/j${javaVersion})`,
'java-version': javaVersion,
'node-version': nodeVersion,
};
};

export const convertToGitHubMatrix = (
matrix: GitHubMatrixRecord,
options?: { randomEnvironment?: boolean; useVersionPlaceholders?: boolean },
): GitHubMatrixOutput => {
const { randomEnvironment, useVersionPlaceholders } = options ?? {};
return {
include: Object.entries(matrix).map(([key, value]) => ({
'job-name': key,
sample: key,
...defaultEnvironment,
...value,
})),
include: Object.entries(matrix)
.filter(([_key, value]) => !value.disabled)
.map(([key, value]) => ({
'job-name': key,
sample: key,
...defaultEnvironment,
...(useVersionPlaceholders ? { 'java-version': 'JAVA-VERSION', 'node-version': 'NODE-VERSION', 'npm-version': 'NPM-VERSION' } : {}),
...value,
...(randomEnvironment ? randomEnvironmentMatrix(key, { useVersionPlaceholders }) : {}),
})),
};
};
Loading

0 comments on commit 605b272

Please sign in to comment.