Skip to content

feat(generator-adp): Make possible to configure the Adaptation project generator from a json. #3188

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

avasilev-sap
Copy link

Feat for #3015 .

  • Introduce the json workflow - we can start the yeoman generator from the CLI passing a json string as an argument to the command. The workflow bypasses all prompts, and the project is generated without any additional configuration.
  • Add unit + integration tests.

Testing done:

  • Manually verified after installing the generator locally and creating a project with the following configuration:
    yo @sap-ux/adp '{ "targetFolder": "/Users/I762682/Workspace/adaptations", "application": "sap.ui.demoapps.rta.fe", "system": "U1Y_099", "client": "099", "username": "VASILEVATA", "password": "...", "projectName": "test.json.flow", "applicationTitle": "Test json flow", "namespace": "customer.test.json.flow" }'. After the project creation the adaptation editor is started successfully.

@avasilev-sap avasilev-sap added the generator-adp @sap-ux/generator-adp label Apr 25, 2025
@avasilev-sap avasilev-sap requested a review from nikmace April 25, 2025 09:46
@avasilev-sap avasilev-sap self-assigned this Apr 25, 2025
@avasilev-sap avasilev-sap requested review from a team as code owners April 25, 2025 09:46
Copy link

changeset-bot bot commented Apr 25, 2025

⚠️ No Changeset found

Latest commit: 0131b29

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

cla-assistant bot commented Apr 25, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

@lfindlaysap lfindlaysap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@avasilev-sap, I've added a few suggestions.

Copy link
Contributor

@nikmace nikmace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the code looks good. There are 4 issues in the Sonar that need to be fixed (1 of them should be accepted though). Also, the text on which Louise requested changes.

Comment on lines +347 to +372
private async _validateJsonInput({
projectName,
targetFolder,
namespace,
system
}: {
projectName: string;
targetFolder: string;
namespace: string;
system: string;
}): Promise<void> {
let validationResult = validateProjectName(projectName, targetFolder, this.isCustomerBase);
if (isString(validationResult)) {
throw new Error(validationResult);
}

validationResult = validateNamespaceAdp(namespace, projectName, this.isCustomerBase);
if (isString(validationResult)) {
throw new Error(validationResult);
}

const systemEndpoint = await this.systemLookup.getSystemByName(system);
if (!systemEndpoint) {
throw new Error(t('error.systemNotFound', { systemName: system }));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part of the code could be extracted into the existing validators.ts file to not clutter the generator code here. This will be better for tests as I will explain below in the in my other comment.

I think it would be fine instead of an object to pass all of the properties inline, since they are 4-5. We also need to pass the systemLookup instance and fix the JSDoc afterwards.

/**
 * Validates input fields for project name, namespace, and system.
 *
 * @param {string} projectName - Name of the project.
 * @param {string} targetFolder - Path to the target folder.
 * @param {string} namespace - Namespace of the application.
 * @param {string} system - Selected system name.
 * @param {SystemLookup} systemLookup - Instance of system lookup service.
 */
export function validateJsonInput(projectName: string, targetFolder: string, namespace: string, system: string, systemLookup: SystemLookup) {}

Also, suggesting to modify the translation parameter systemName to just system, that way you will just do this:

throw new Error(t('error.systemNotFound', { system }));

// Application config
application: baseApplicationName,
applicationTitle,
applicationComponentHierarchy = '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is never used in this code, so it could be removed for the time being, at least until this PR goes in: #3146. It should also be removed from ConfigAnswers interface.

}

const {
// System config
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are not needed.

Comment on lines +29 to +41
export function parseJsonInput(jsonString: string): Record<string, string> | undefined {
try {
const parsed = JSON.parse(jsonString);

if (!isRecordOfStrings(parsed)) {
return;
}

return parsed;
} catch (error) {
// Do nothing.
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to have an interface for this instead of using a Record<string, string> so we know exactly what could come in this JSON? Also, we should explicitly return undefined.

Comment on lines +3 to +10
/**
* Returns the first argument from a list of CLI arguments. If the first argument
* is not a string returns empty string.
*
* @param args The list of CLI command arguments.
* @returns The first parameter in the argument's list as string.
*/
export function getFirstArgAsString(args: string | string[]): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the JSDocs should look the same or be similar in terms of structure.

Although this is optional and the benefits are not so much, I try to put the types for the properties in the JSDoc (see args).
Also, after the @param {type} property usually comes a dash ("-").

All of the above make the comments more sophisticated, helping both humans and machines understand your code.

Suggested change
/**
* Returns the first argument from a list of CLI arguments. If the first argument
* is not a string returns empty string.
*
* @param args The list of CLI command arguments.
* @returns The first parameter in the argument's list as string.
*/
export function getFirstArgAsString(args: string | string[]): string {
/**
* Returns the first argument from a list of CLI arguments. If the first argument
* is not a string returns empty string.
*
* @param {string | string[]} args - The list of CLI command arguments.
* @returns {string} The first parameter in the argument's list as string.
*/
export function getFirstArgAsString(args: string | string[]): string {

Comment on lines +298 to +360
it('should throw an error when the project name in the json input is not valid', async () => {
const jsonInput = {
system: 'urlA',
username: 'user1',
password: 'pass1',
client: '010',
application: 'sap.ui.demoapps.f1',
projectName: 'Invalid project name',
targetFolder: testOutputDir
};
const jsonInputString = JSON.stringify(jsonInput);

const runContext = yeomanTest
.create(adpGenerator, { resolved: generatorPath }, { cwd: testOutputDir })
.withArguments([jsonInputString]);

await expect(runContext.run()).rejects.toThrow(
t('adp.projectNameUppercaseError', { ns: PROJECT_INPUT_VALIDATOR_NS })
);
});

it('should throw an error when the namespace in the json input is not valid', async () => {
const jsonInput = {
system: 'urlA',
username: 'user1',
password: 'pass1',
client: '010',
application: 'sap.ui.demoapps.f1',
projectName: 'failure',
namespace: 'customer.invalid namespace',
targetFolder: testOutputDir
};
const jsonInputString = JSON.stringify(jsonInput);

const runContext = yeomanTest
.create(adpGenerator, { resolved: generatorPath }, { cwd: testOutputDir })
.withArguments([jsonInputString]);

await expect(runContext.run()).rejects.toThrow(
t('adp.namespaceValidationError', { ns: PROJECT_INPUT_VALIDATOR_NS })
);
});

it('should throw an error when the system is not found in the json workflow', async () => {
const jsonInput = {
system: 'nonexistent system',
username: 'user1',
password: 'pass1',
client: '010',
application: 'sap.ui.demoapps.f1',
projectName: 'failure',
namespace: 'customer.my.app',
applicationTitle: 'My app title',
targetFolder: testOutputDir
};
const jsonInputString = JSON.stringify(jsonInput);

const runContext = yeomanTest
.create(adpGenerator, { resolved: generatorPath }, { cwd: testOutputDir })
.withArguments([jsonInputString]);

await expect(runContext.run()).rejects.toThrow(t('error.systemNotFound', { systemName: jsonInput.system }));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you extract the validations in a separate file, you will no longer need these tests. Just mock the validateJsonInput function. There should not be so many integration tests just to check if something breaks in the writing phase. We already have one test above which checks if there is an error thrown during the writing phase, no need for more in my opinion. To make sure the test works, you have added a successful integration test that generates a project from JSON inpu,t and that is enough.

Comment on lines +34 to +38
import { getFirstArgAsString, parseJsonInput } from '../utils/parse-json-arg';
import type { AbapServiceProvider } from '@sap-ux/axios-extension';
import { getDefaultNamespace, getDefaultProjectName } from './questions/helper/default-values';
import { validateNamespaceAdp, validateProjectName } from '@sap-ux/project-input-validator';
import { isString } from '../utils/type-guards';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please reorder the imports so they are like this:
First npm packages -> then monorepo packages -> local imports

I like to order them in a certain way but this is optional:
Screenshot 2025-04-25 at 15 25 16

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generator-adp @sap-ux/generator-adp
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants