Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more workflows support to github-build-matrix and migrate angular workflow #27624

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading