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

feat: Add Typescript support for Adaptation Project #2812

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
94d2fa5
feat: add initial implementation
nikmace Jan 22, 2025
d9709a3
refactor: change var naming for ts
nikmace Jan 24, 2025
b2e948c
feat: add build script and map file cleaning
nikmace Jan 24, 2025
589bd44
refactor: stop passing variant id to write controller
nikmace Jan 27, 2025
c3b76fe
feat: reuse command runner and fix tests
nikmace Jan 27, 2025
d1c2a1d
test: add test for ts template writing
nikmace Jan 27, 2025
c535639
test: fix route handler tests
nikmace Jan 27, 2025
92f3f04
fix: lint errors
nikmace Jan 28, 2025
ebfd624
fix: failing test on windows
nikmace Jan 28, 2025
3691eef
chore: add cset
nikmace Jan 28, 2025
1f932eb
fix: failing test on windows
nikmace Jan 28, 2025
e05cd3d
fix: generation tests
nikmace Jan 29, 2025
e3b9ac2
test: improve route handler tests
nikmace Jan 29, 2025
b91a8d0
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Jan 29, 2025
22f5236
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Jan 29, 2025
a99c0a5
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Jan 29, 2025
912b6c4
feat: add dynamic extension for controller ext dialog
nikmace Jan 31, 2025
d956f2b
Merge branch 'feat/2811/adp-ts-support' of https://github.com/SAP/ope…
nikmace Jan 31, 2025
6c29ff4
Merge remote-tracking branch 'origin/main' into feat/2811/adp-ts-support
nikmace Jan 31, 2025
130877d
refactor: change ts template
nikmace Feb 3, 2025
6e0c3c2
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Feb 4, 2025
f5eec8c
refactor: remove unneeded code and fix tests
nikmace Feb 5, 2025
e759786
Merge branch 'feat/2811/adp-ts-support' of https://github.com/SAP/ope…
nikmace Feb 5, 2025
303b111
test: remove unused mocks
nikmace Feb 5, 2025
645e776
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Feb 6, 2025
324eac9
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Feb 11, 2025
a5968d4
refactor: change template structure
nikmace Feb 11, 2025
d9c2860
refactor: remove unneeded var
nikmace Feb 11, 2025
bd6d512
refactor: add review comment changes
nikmace Feb 14, 2025
9b61218
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Feb 14, 2025
7113ff2
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Feb 17, 2025
1f61e92
feat: enhance the adp generation command with ts
nikmace Feb 17, 2025
34c32b0
chore: add create package to cset
nikmace Feb 18, 2025
93eb118
feat: enhance dummy prompts with typescript
nikmace Feb 18, 2025
5216260
Merge branch 'main' into feat/2811/adp-ts-support
nikmace Feb 19, 2025
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
8 changes: 8 additions & 0 deletions .changeset/dry-badgers-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@sap-ux-private/preview-middleware-client': minor
'@sap-ux/adp-tooling': minor
'@sap-ux/create': minor
'@sap-ux/preview-middleware': minor
---

feat: Add Typescript support for Adaptation Project
8 changes: 5 additions & 3 deletions packages/adp-tooling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"format": "prettier --write '**/*.{js,json,ts,yaml,yml}' --ignore-path ../../.prettierignore",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "jest FIORI_TOOLS_DISABLE_SECURE_STORE=true --ci --forceExit --detectOpenHandles --colors --testPathPattern=test/unit",
"test-u": "jest FIORI_TOOLS_DISABLE_SECURE_STORE=true --ci --forceExit --detectOpenHandles --colors -u",
"test": "cross-env FIORI_TOOLS_DISABLE_SECURE_STORE=true jest --ci --forceExit --detectOpenHandles --colors --testPathPattern=test/unit",
"test-u": "cross-env FIORI_TOOLS_DISABLE_SECURE_STORE=true jest --ci --forceExit --detectOpenHandles --colors -u",
"link": "pnpm link --global",
"unlink": "pnpm unlink --global"
},
Expand All @@ -44,6 +44,7 @@
"@sap-ux/system-access": "workspace:*",
"@sap-ux/ui5-config": "workspace:*",
"@sap-ux/odata-service-writer": "workspace:*",
"@sap-ux/nodejs-utils": "workspace:*",
"@sap-ux/i18n": "workspace:*",
"adm-zip": "0.5.10",
"ejs": "3.1.10",
Expand All @@ -70,7 +71,8 @@
"express": "4.21.2",
"nock": "13.4.0",
"rimraf": "5.0.5",
"supertest": "6.3.3"
"supertest": "6.3.3",
"cross-env": "^7.0.3"
},
"engines": {
"node": ">=18.x"
Expand Down
14 changes: 13 additions & 1 deletion packages/adp-tooling/src/base/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Editor } from 'mem-fs-editor';
import { readdirSync, readFileSync } from 'fs';
import { existsSync, readdirSync, readFileSync } from 'fs';
import { join, isAbsolute, relative } from 'path';

import { UI5Config } from '@sap-ux/ui5-config';
Expand Down Expand Up @@ -53,6 +53,18 @@ export function flpConfigurationExists(basePath: string): boolean {
}
}

/**
* Checks whether TypeScript is supported in the project by verifying the existence of `tsconfig.json`.
*
* @param basePath - The base path of the project.
* @param fs - An optional `mem-fs-editor` instance to check for the file's existence.
* @returns `true` if `tsconfig.json` exists, otherwise `false`.
*/
export function isTypescriptSupported(basePath: string, fs?: Editor): boolean {
const path = join(basePath, 'tsconfig.json');
return fs ? fs.exists(path) : existsSync(path);
}

/**
* Returns the adaptation project configuration, throws an error if not found.
*
Expand Down
21 changes: 21 additions & 0 deletions packages/adp-tooling/src/base/project-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CommandRunner } from '@sap-ux/nodejs-utils';

/**
* Executes a build command in the specified project directory.
*
* This function uses the `CommandRunner` to run the build process via the command `npm run build`.
*
* @param {string} projectPath - The absolute path to the project directory where the build command will be executed.
* @returns {Promise<void>} Resolves when the build process has completed successfully.
* @throws {Error} If the build process fails or if an error occurs during cleanup.
*/
export async function runBuild(projectPath: string): Promise<void> {
const commandRunner = new CommandRunner();

try {
await commandRunner.run('npm', ['run', 'build'], { cwd: projectPath });
} catch (e) {
console.error(`Error during build and clean: ${e.message}`);
throw e;
}
}
8 changes: 8 additions & 0 deletions packages/adp-tooling/src/base/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type PromptDefaults = {
ft?: boolean;
package?: string;
transport?: string;
ts?: boolean;
};

/**
Expand Down Expand Up @@ -93,6 +94,13 @@ export async function promptGeneratorInput(
message: 'Enable Fiori tools?',
initial: defaults.ft !== false,
validate: (input) => input?.length > 0
},
{
type: 'confirm',
name: 'enableTypeScript',
message: 'Enable TypeScript?',
initial: defaults.ts !== false,
validate: (input) => input?.length > 0
}
]);

Expand Down
1 change: 1 addition & 0 deletions packages/adp-tooling/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './types';
export * from './prompts';
export * from './common';
export * from './base/cf';
export * from './base/project-builder';
export * from './base/abap/manifest-service';
export * from './base/helper';
export * from './preview/adp-preview';
Expand Down
70 changes: 47 additions & 23 deletions packages/adp-tooling/src/preview/routes-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { DirName, FileName } from '@sap-ux/project-access';
import { type CodeExtChange } from '../types';
import { ManifestService } from '../base/abap/manifest-service';
import type { DataSources } from '../base/abap/manifest-service';
import { getAdpConfig, getVariant } from '../base/helper';
import { getAdpConfig, getVariant, isTypescriptSupported } from '../base/helper';
import { createAbapServiceProvider } from '@sap-ux/system-access';

interface WriteControllerBody {
Expand Down Expand Up @@ -163,8 +163,11 @@ export default class RoutesHandler {

const project = this.util.getProject();
const sourcePath = project.getSourcePath();
const rootPath = this.util.getProject().getRootPath();
const projectName = project.getName();

const isTsSupported = isTypescriptSupported(rootPath);

const getPath = (projectPath: string, fileName: string, folder: string = DirName.Coding) =>
path.join(projectPath, DirName.Changes, folder, fileName).split(path.sep).join(path.posix.sep);

Expand All @@ -173,7 +176,8 @@ export default class RoutesHandler {
const change = JSON.parse(fileStr) as CodeExtChange;

if (change.selector.controllerName === controllerName) {
const fileName = change.content.codeRef.replace('coding/', '');
const baseFileName = change.content.codeRef.replace('coding/', '');
const fileName = isTsSupported ? baseFileName.replace('.js', '.ts') : baseFileName;
controllerPath = getPath(sourcePath, fileName);
controllerPathFromRoot = getPath(projectName, fileName);
changeFilePath = getPath(projectName, file.getName(), '');
Expand All @@ -195,7 +199,8 @@ export default class RoutesHandler {
controllerExists,
controllerPath: os.platform() === 'win32' ? `/${controllerPath}` : controllerPath,
controllerPathFromRoot,
isRunningInBAS
isRunningInBAS,
isTsSupported
});
this.logger.debug(
controllerExists
Expand All @@ -218,47 +223,37 @@ export default class RoutesHandler {
try {
const data = req.body as WriteControllerBody;

const controllerExtName = sanitize(data.controllerName);
const projectId = data.projectId;
const name = sanitize(data.controllerName);

const sourcePath = this.util.getProject().getSourcePath();
const rootPath = this.util.getProject().getRootPath();

if (!controllerExtName) {
if (!name) {
res.status(HttpStatusCodes.BAD_REQUEST).send('Controller extension name was not provided!');
this.logger.debug('Bad request. Controller extension name was not provided!');
return;
}

const isTsSupported = isTypescriptSupported(rootPath);

const fullPath = path.join(sourcePath, DirName.Changes, DirName.Coding);
const filePath = path.join(fullPath, `${controllerExtName}.js`);
const filePath = path.join(fullPath, `${name}.${isTsSupported ? 'ts' : 'js'}`);

if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}

if (fs.existsSync(filePath)) {
res.status(HttpStatusCodes.CONFLICT).send(
`Controller extension with name "${controllerExtName}" already exists`
);
this.logger.debug(`Controller extension with name "${controllerExtName}" already exists`);
res.status(HttpStatusCodes.CONFLICT).send(`Controller extension with name "${name}" already exists`);
this.logger.debug(`Controller extension with name "${name}" already exists`);
return;
}

const controllerExtPath = `${projectId}.${controllerExtName}`;

const controllerTemplateFilePath = path.join(__dirname, '../../templates/rta', TemplateFileName.Controller);

renderFile(controllerTemplateFilePath, { controllerExtPath }, {}, (err, str) => {
if (err) {
throw new Error('Error rendering template: ' + err.message);
}

fs.writeFileSync(filePath, str, { encoding: 'utf8' });
});
generateControllerFile(rootPath, filePath, name);

const message = 'Controller extension created!';
res.status(HttpStatusCodes.CREATED).send(message);
this.logger.debug(`Controller extension with name "${controllerExtName}" was created`);
this.logger.debug(`Controller extension with name "${name}" was created`);
} catch (e) {
const sanitizedMsg = sanitize(e.message);
this.logger.error(sanitizedMsg);
Expand Down Expand Up @@ -358,3 +353,32 @@ export default class RoutesHandler {
return await ManifestService.initMergedManifest(provider, basePath, variant, this.logger);
}
}

/**
* Generates a controller file for the Adaptation Project based on the project's TypeScript support.
*
* This function creates a controller file in the specified `filePath` by rendering a template.
* It determines whether to use a TypeScript or JavaScript template based on the TypeScript support of the project.
*
* @param {string} rootPath - The root directory of the project.
* @param {string} filePath - The destination path where the generated controller file should be saved.
* @param {string} name - The name of the controller extension (used in TypeScript templates).
* @throws {Error} Throws an error if rendering the template fails.
*/
function generateControllerFile(rootPath: string, filePath: string, name: string): void {
const id = getVariant(rootPath)?.id;
const isTsSupported = isTypescriptSupported(rootPath);
const tmplFileName = isTsSupported ? TemplateFileName.TSController : TemplateFileName.Controller;
const tmplPath = path.join(__dirname, '../../templates/rta', tmplFileName);
const extensionPath = `${id}.${name}`;

const templateData = isTsSupported ? { name, ns: id } : { extensionPath };

renderFile(tmplPath, templateData, {}, (err, str) => {
if (err) {
throw new Error(`Error rendering ${isTsSupported ? 'TypeScript' : 'JavaScript'} template: ${err.message}`);
}

fs.writeFileSync(filePath, str, { encoding: 'utf8' });
});
}
5 changes: 5 additions & 0 deletions packages/adp-tooling/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export interface AdpWriterConfig {
* Optional: if set to true then the generated project will be recognized by the SAP Fiori tools
*/
fioriTools?: boolean;
/**
* Optional: if set to true then the generated project will support typescript
*/
enableTypeScript?: boolean;
};
}

Expand Down Expand Up @@ -278,6 +282,7 @@ export type ParameterRules = {
export const enum TemplateFileName {
Fragment = 'fragment.xml',
Controller = 'controller.ejs',
TSController = 'ts-controller.ejs',
Annotation = 'annotation.xml'
}

Expand Down
6 changes: 4 additions & 2 deletions packages/adp-tooling/src/writer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { AdpWriterConfig, InternalInboundNavigation } from '../types';
import { enhanceManifestChangeContentWithFlpConfig } from './options';
import { writeTemplateToFolder, writeUI5Yaml, writeUI5DeployYaml } from './project-utils';

const tmplPath = join(__dirname, '../../templates/project');
const baseTmplPath = join(__dirname, '../../templates');

/**
* Set default values for optional properties.
Expand Down Expand Up @@ -60,7 +60,7 @@ export async function generate(basePath: string, config: AdpWriterConfig, fs?: E
fullConfig.app.content
);
}
writeTemplateToFolder(join(tmplPath, '**/*.*'), join(basePath), fullConfig, fs);
writeTemplateToFolder(baseTmplPath, join(basePath), fullConfig, fs);
await writeUI5DeployYaml(basePath, fullConfig, fs);
await writeUI5Yaml(basePath, fullConfig, fs);

Expand All @@ -82,6 +82,8 @@ export async function migrate(basePath: string, config: AdpWriterConfig, fs?: Ed

const fullConfig = setDefaults(config);

const tmplPath = join(baseTmplPath, 'project');

// Copy the specified files to target project
fs.copyTpl(join(tmplPath, '**/ui5.yaml'), join(basePath), fullConfig, undefined, {
globOptions: { dot: true }
Expand Down
62 changes: 53 additions & 9 deletions packages/adp-tooling/src/writer/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type {
AbapTarget,
FioriToolsProxyConfigBackend
} from '@sap-ux/ui5-config';

import type {
CustomConfig,
AdpWriterConfig,
InboundContent,
Language,
Expand Down Expand Up @@ -37,14 +37,35 @@ export function enhanceUI5Yaml(ui5Config: UI5Config, config: AdpWriterConfig) {
}

/**
* Generate the configuration for the custom tasks required for the ui5.yaml.
* Generates the configuration for the custom tasks required for the ui5.yaml.
*
* @param ui5Config configuration representing the ui5.yaml
* @param config full project configuration
* Adds a custom task for building TypeScript projects.
*
* @param {UI5Config} ui5Config - The UI5 configuration object representing the ui5.yaml.
* @param {AdpWriterConfig} config - The configuration object containing options for the adaptation project.
*/
export function enhanceUI5YamlWithCustomTask(ui5Config: UI5Config, config: AdpWriterConfig & { app: CloudApp }) {
const tasks = getAdpCloudCustomTasks(config);
ui5Config.addCustomTasks(tasks);
if (config.options?.enableTypeScript) {
ui5Config.addCustomTasks([
{
name: 'ui5-tooling-transpile-task',
afterTask: 'replaceVersion',
configuration: {
debug: true,
omitSourceMaps: true,
omitTSFromBuildResult: true,
transformModulesToUI5: {
overridesToOverride: true
}
}
}
]);
}

if (config.customConfig?.adp?.environment === 'C') {
const tasks = getAdpCloudCustomTasks(config);
ui5Config.addCustomTasks(tasks);
}
}

/**
Expand All @@ -53,13 +74,36 @@ export function enhanceUI5YamlWithCustomTask(ui5Config: UI5Config, config: AdpWr
* @param ui5Config configuration representing the ui5.yaml
* @param config full project configuration
*/
export function enhanceUI5YamlWithCustomConfig(ui5Config: UI5Config, config?: CustomConfig) {
if (config?.adp) {
const { support } = config.adp;
export function enhanceUI5YamlWithCustomConfig(ui5Config: UI5Config, config: AdpWriterConfig) {
const adp = config.customConfig?.adp;
if (adp) {
const { support } = adp;
ui5Config.addCustomConfiguration('adp', { support });
}
}

/**
* Enhances a UI5 YAML configuration with the transpile middleware for TypeScript support.
*
* @param {UI5Config} ui5Config - The UI5 configuration object representing the ui5.yaml.
* @param {AdpWriterConfig} config - The configuration object containing options for the adaptation project.
* @param {boolean} [config.options.enableTypeScript] - Flag indicating if TypeScript support is enabled.
*/
export function enhanceUI5YamlWithTranspileMiddleware(ui5Config: UI5Config, config: AdpWriterConfig) {
if (config.options?.enableTypeScript) {
ui5Config.updateCustomMiddleware({
name: 'ui5-tooling-transpile-middleware',
afterMiddleware: 'compression',
configuration: {
debug: true,
transformModulesToUI5: {
overridesToOverride: true
}
}
});
}
}

/**
* Writer configuration with deploy configuration.
*/
Expand Down
Loading