Skip to content

Commit

Permalink
feat(ui5-writer): abstract functions (#1040)
Browse files Browse the repository at this point in the history
* feat(ui5-writer): abstract functions

* feat(ui5-writer): changeset

* feat(ui5-writer): remove unused import

* feat(ui5-writer): add project access test

* feat(ui5-writer): make getFilePaths async

* Revert "feat(ui5-writer): make getFilePaths async"

This reverts commit 9debc7e.

* feat(ui5-writer): make getFilePaths async

* Linting auto fix commit

* feat(ui5-writer): fix import

* feat(ui5-writer): pnpm lock yaml

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
cianmSAP and github-actions[bot] authored Jun 1, 2023
1 parent 4acfbcb commit 31207b9
Show file tree
Hide file tree
Showing 18 changed files with 225 additions and 176 deletions.
7 changes: 7 additions & 0 deletions .changeset/beige-news-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sap-ux/project-access': minor
'@sap-ux/ui5-application-writer': minor
'@sap-ux/ui5-config': minor
---

abstract ui5-app-writer functions into appropriate modules
1 change: 0 additions & 1 deletion packages/fiori-elements-writer/test/lrop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
projectChecks,
updatePackageJSONDependencyToUseLocalPath
} from './common';
import { UI5_DEFAULT } from '@sap-ux/ui5-application-writer/src/data/defaults';

const TEST_NAME = 'lropTemplates';
if (debug?.enabled) {
Expand Down
21 changes: 21 additions & 0 deletions packages/project-access/src/file/file-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Editor, FileMap } from 'mem-fs-editor';
import { basename, dirname, extname, join } from 'path';
import { default as find } from 'findit2';
import { fileExists } from './file-access';
import { promises as fs } from 'fs';

/**
* Get deleted and modified files from mem-fs editor filtered by query and 'by' (name|extension).
Expand Down Expand Up @@ -136,3 +137,23 @@ export async function findFileUp(fileName: string, startPath: string, fs?: Edito
return dirname(startPath) !== startPath ? findFileUp(fileName, dirname(startPath), fs) : undefined;
}
}

/**
* @description Returns a flat list of all file paths under a directory tree,
* recursing through all subdirectories
* @param {string} dir - the directory to walk
* @returns {string[]} - array of file path strings
* @throws if an error occurs reading a file path
*/
export async function getFilePaths(dir: string): Promise<string[] | []> {
const entries = await fs.readdir(dir);

const filePathsPromises = entries.map(async (entry) => {
const entryPath = join(dir, entry);
const isDirectory = (await fs.stat(entryPath)).isDirectory();
return isDirectory ? getFilePaths(entryPath) : entryPath;
});

const filePaths = await Promise.all(filePathsPromises);
return ([] as string[]).concat(...filePaths);
}
2 changes: 1 addition & 1 deletion packages/project-access/src/file/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { fileExists, readFile, readJSON } from './file-access';
export { findBy, findFiles, findFilesByExtension, findFileUp } from './file-search';
export { findBy, findFiles, findFilesByExtension, findFileUp, getFilePaths } from './file-search';
1 change: 1 addition & 0 deletions packages/project-access/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export {
loadModuleFromProject,
readUi5Yaml
} from './project';
export { getFilePaths } from './file';
export * from './types';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join } from 'path';
import { findFiles, findFilesByExtension, findFileUp } from '../../src/file';
import { findFiles, findFilesByExtension, findFileUp, getFilePaths } from '../../src/file';
import { create as createStorage } from 'mem-fs';
import { create } from 'mem-fs-editor';

Expand Down Expand Up @@ -91,4 +91,24 @@ describe('findFiles', () => {
expect(await findFileUp(file, root, fs)).toBe(join(root, file));
});
});

describe('getFilePaths', () => {
test('files in the file folder', async () => {
const expectedPaths = [
expect.stringContaining(join('file/childA/child')),
expect.stringContaining(join('file/childA/child.extension')),
expect.stringContaining(join('file/childA/firstchild')),
expect.stringContaining(join('file/childB/child')),
expect.stringContaining(join('file/childC/child')),
expect.stringContaining(join('file/childC/nested1/nochild')),
expect.stringContaining(join('file/childC/nested2/child')),
expect.stringContaining(join('file/root.extension')),
expect.stringContaining(join('file/rootfile'))
];

const filePaths = await getFilePaths(root);

expect(filePaths).toEqual(expect.arrayContaining(expectedPaths));
});
});
});
76 changes: 1 addition & 75 deletions packages/ui5-application-writer/src/data/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UI5_DEFAULT, getEsmTypesVersion, getTypesVersion } from '@sap-ux/ui5-config';
import type { App, AppOptions, Package, UI5, UI5Framework } from '../types';
import versionToManifestDescMapping from '@ui5/manifest/mapping.json'; // from https://github.com/SAP/ui5-manifest/blob/master/mapping.json
import { getUI5Libs } from './ui5Libs';
Expand Down Expand Up @@ -29,42 +30,6 @@ export function packageDefaults(version?: string, description?: string): Partial
};
}

/**
* Merges two objects. All properties from base and from extension will be present.
* Overlapping properties will be used from extension. Arrays will be concatenated and de-duped.
*
* @param base - any object definition
* @param extension - another object definition
* @returns - a merged package defintion
*/
export function mergeObjects<B, E>(base: B, extension: E): B & E {
return merge({}, base, extension, (objValue: unknown, srcValue: unknown) => {
// merge and de-dup arrays
if (objValue instanceof Array && srcValue instanceof Array) {
return [...new Set([...objValue, ...srcValue])];
} else {
return undefined;
}
});
}

export const enum UI5_DEFAULT {
DEFAULT_UI5_VERSION = '',
DEFAULT_LOCAL_UI5_VERSION = '1.95.0',
MIN_UI5_VERSION = '1.60.0',
MIN_LOCAL_SAPUI5_VERSION = '1.76.0',
MIN_LOCAL_OPENUI5_VERSION = '1.52.5',
SAPUI5_CDN = 'https://ui5.sap.com',
OPENUI5_CDN = 'https://openui5.hana.ondemand.com',
TYPES_VERSION_SINCE = '1.76.0',
TYPES_VERSION_BEST_MIN = '1.102.0',
TYPES_VERSION_PREVIOUS = '1.71.15',
TYPES_VERSION_BEST = '1.108.0',
ESM_TYPES_VERSION_SINCE = '1.94.0',
MANIFEST_VERSION = '1.12.0',
BASE_COMPONENT = 'sap/ui/core/UIComponent'
}

/**
* Returns an app instance merged with default properties.
*
Expand Down Expand Up @@ -120,45 +85,6 @@ export function mergeUi5(ui5: Partial<UI5>, options?: Partial<AppOptions>): UI5
return Object.assign({}, ui5, merged) as UI5;
}

/**
* Get the best types version for the given minUI5Version for https://www.npmjs.com/package/@sapui5/ts-types where specific versions are missing.
*
* @param minUI5Version the minimum UI5 version that needs to be supported
* @returns semantic version representing the types version.
*/
export function getTypesVersion(minUI5Version?: string) {
const version = semVer.coerce(minUI5Version);
if (!version) {
return `~${UI5_DEFAULT.TYPES_VERSION_BEST}`;
} else if (semVer.gte(version, UI5_DEFAULT.TYPES_VERSION_BEST)) {
return `~${UI5_DEFAULT.TYPES_VERSION_BEST}`;
} else {
return semVer.gte(version, UI5_DEFAULT.TYPES_VERSION_SINCE)
? `~${semVer.major(version)}.${semVer.minor(version)}.${semVer.patch(version)}`
: UI5_DEFAULT.TYPES_VERSION_PREVIOUS;
}
}

/**
* Get the best types version for the given minUI5Version within a selective range, starting at 1.90.0
* for https://www.npmjs.com/package/@sapui5/ts-types-esm
* For the latest versions the LTS S/4 on-premise version (1.102.x) is used, for anything before we
* match the versions as far back as available.
*
* @param minUI5Version the minimum UI5 version that needs to be supported
* @returns semantic version representing the types version.
*/
export function getEsmTypesVersion(minUI5Version?: string) {
const version = semVer.coerce(minUI5Version);
if (!version || semVer.gte(version, UI5_DEFAULT.TYPES_VERSION_BEST_MIN)) {
return `~${UI5_DEFAULT.TYPES_VERSION_BEST}`;
} else {
return semVer.gte(version, UI5_DEFAULT.ESM_TYPES_VERSION_SINCE)
? `~${semVer.major(version)}.${semVer.minor(version)}.0`
: `~${UI5_DEFAULT.ESM_TYPES_VERSION_SINCE}`;
}
}

/**
* Gets the miminum UI5 version based on the specified version.
*
Expand Down
3 changes: 2 additions & 1 deletion packages/ui5-application-writer/src/data/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { App, UI5, AppOptions, Package, Ui5App } from '../types';
import { mergeApp, packageDefaults, mergeUi5, mergeObjects, getSpecTagVersion } from './defaults';
import { mergeObjects } from '@sap-ux/ui5-config';
import { mergeApp, packageDefaults, mergeUi5, getSpecTagVersion } from './defaults';
import { validate } from './validators';

/**
Expand Down
17 changes: 0 additions & 17 deletions packages/ui5-application-writer/src/files.ts

This file was deleted.

7 changes: 3 additions & 4 deletions packages/ui5-application-writer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { create as createStorage } from 'mem-fs';
import type { Editor } from 'mem-fs-editor';
import { create } from 'mem-fs-editor';
import type { App, AppOptions, Package, UI5 } from './types';
import { UI5Config } from '@sap-ux/ui5-config';
import { UI5Config, getEsmTypesVersion } from '@sap-ux/ui5-config';
import type { Manifest } from '@sap-ux/project-access';
import { mergeWithDefaults } from './data';
import { ui5TSSupport } from './data/ui5Libs';
import { applyOptionalFeatures, enableTypescript as enableTypescriptOption } from './options';
import { Ui5App } from './types';
import { getEsmTypesVersion } from './data/defaults';

/**
* Writes the template to the memfs editor instance.
Expand Down Expand Up @@ -60,7 +59,7 @@ async function generate(basePath: string, ui5AppConfig: Ui5App, fs?: Editor): Pr
ui5LocalConfig.addFioriToolsAppReloadMiddleware();

// Add optional features
applyOptionalFeatures(ui5App, fs, basePath, tmplPath, [ui5Config, ui5LocalConfig]);
await applyOptionalFeatures(ui5App, fs, basePath, tmplPath, [ui5Config, ui5LocalConfig]);

// write ui5 yamls
fs.write(ui5ConfigPath, ui5Config.toString());
Expand Down Expand Up @@ -122,7 +121,7 @@ async function enableTypescript(basePath: string, fs?: Editor): Promise<Editor>
typesVersion: getEsmTypesVersion(manifest['sap.ui5']?.dependencies?.minUI5Version)
}
};
enableTypescriptOption({ basePath, fs, ui5Configs: [ui5Config], tmplPath, ui5App }, true);
await enableTypescriptOption({ basePath, fs, ui5Configs: [ui5Config], tmplPath, ui5App }, true);

fs.write(ui5ConfigPath, ui5Config.toString());

Expand Down
39 changes: 20 additions & 19 deletions packages/ui5-application-writer/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { join } from 'path';
import type { Editor } from 'mem-fs-editor';
import { render } from 'ejs';
import type { Ui5App } from './types';
import { getFilePaths } from './files';
import { getFilePaths } from '@sap-ux/project-access';
import type { UI5Config } from '@sap-ux/ui5-config';
import { ui5NPMSupport, ui5TSSupport } from './data/ui5Libs';
import { mergeObjects, UI5_DEFAULT } from './data/defaults';
import { mergeObjects, UI5_DEFAULT } from '@sap-ux/ui5-config';

/**
* Input required to enable optional features.
Expand All @@ -28,9 +28,10 @@ export interface FeatureInput {
* @param input.basePath project base path
* @param input.tmplPath template basepath
*/
function copyTemplates(name: string, { ui5App, fs, basePath, tmplPath }: FeatureInput) {
async function copyTemplates(name: string, { ui5App, fs, basePath, tmplPath }: FeatureInput) {
const optTmplDirPath = join(tmplPath, 'optional', `${name}`);
const optTmplFilePaths = getFilePaths(optTmplDirPath);
const optTmplFilePaths = await getFilePaths(optTmplDirPath);

optTmplFilePaths.forEach((optTmplFilePath) => {
const relPath = optTmplFilePath.replace(optTmplDirPath, '');
const outPath = join(basePath, relPath);
Expand All @@ -51,13 +52,13 @@ function copyTemplates(name: string, { ui5App, fs, basePath, tmplPath }: Feature
/**
* Factory functions for applying optional features.
*/
const factories: { [key: string]: (input: FeatureInput) => void } = {
codeAssist: (input: FeatureInput) => copyTemplates('codeAssist', input),
eslint: (input: FeatureInput) => copyTemplates('eslint', input),
loadReuseLibs: (input: FeatureInput) => copyTemplates('loadReuseLibs', input),
sapux: (input: FeatureInput) => copyTemplates('sapux', input),
typescript: enableTypescript,
npmPackageConsumption: enableNpmPackageConsumption
const factories: { [key: string]: (input: FeatureInput) => Promise<void> } = {
codeAssist: async (input: FeatureInput) => await copyTemplates('codeAssist', input),
eslint: async (input: FeatureInput) => await copyTemplates('eslint', input),
loadReuseLibs: async (input: FeatureInput) => await copyTemplates('loadReuseLibs', input),
sapux: async (input: FeatureInput) => await copyTemplates('sapux', input),
typescript: async (input: FeatureInput) => await enableTypescript(input),
npmPackageConsumption: async (input: FeatureInput) => await enableNpmPackageConsumption(input)
};

/**
Expand All @@ -66,9 +67,9 @@ const factories: { [key: string]: (input: FeatureInput) => void } = {
* @param input Input required to enable the optional typescript features
* @param keepOldComponent if set to true then the old Component.js will be renamed but kept.
*/
export function enableTypescript(input: FeatureInput, keepOldComponent: boolean = false) {
export async function enableTypescript(input: FeatureInput, keepOldComponent: boolean = false) {
input.ui5App.app.baseComponent = input.ui5App.app.baseComponent ?? UI5_DEFAULT.BASE_COMPONENT;
copyTemplates('typescript', input);
await copyTemplates('typescript', input);
input.ui5Configs.forEach((ui5Config) => {
ui5Config.addCustomMiddleware([ui5TSSupport.middleware]);
ui5Config.addCustomTasks([ui5TSSupport.task]);
Expand All @@ -86,8 +87,8 @@ export function enableTypescript(input: FeatureInput, keepOldComponent: boolean
*
* @param input Input required to enable the optional npm modules import
*/
export function enableNpmPackageConsumption(input: FeatureInput) {
copyTemplates('npmPackageConsumption', input);
export async function enableNpmPackageConsumption(input: FeatureInput) {
await copyTemplates('npmPackageConsumption', input);
input.ui5Configs.forEach((ui5Config) => {
ui5Config.addCustomMiddleware([ui5NPMSupport.middleware]);
ui5Config.addCustomTasks([ui5NPMSupport.task]);
Expand All @@ -103,18 +104,18 @@ export function enableNpmPackageConsumption(input: FeatureInput) {
* @param tmplPath template basepath
* @param ui5Configs available UI5 configs
*/
export function applyOptionalFeatures(
export async function applyOptionalFeatures(
ui5App: Ui5App,
fs: Editor,
basePath: string,
tmplPath: string,
ui5Configs: UI5Config[]
) {
if (ui5App.appOptions) {
Object.entries(ui5App.appOptions).forEach(([key, value]) => {
for (const [key, value] of Object.entries(ui5App.appOptions)) {
if (value === true) {
factories[key]?.({ ui5App, fs, basePath, tmplPath, ui5Configs });
await factories[key]?.({ ui5App, fs, basePath, tmplPath, ui5Configs });
}
});
}
}
}
49 changes: 3 additions & 46 deletions packages/ui5-application-writer/test/data.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UI5_DEFAULT, mergeUi5, defaultUI5Libs, mergeApp, getSpecTagVersion, mergeObjects } from '../src/data/defaults';
import { UI5_DEFAULT } from '@sap-ux/ui5-config';
import { mergeUi5, defaultUI5Libs, mergeApp, getSpecTagVersion } from '../src/data/defaults';
import { mergeWithDefaults } from '../src/data/index';
import type { App, Package, UI5, Ui5App } from '../src/types';
import type { App, UI5, Ui5App } from '../src/types';

const mockSpecVersions = JSON.stringify({ latest: '1.102.3', 'UI5-1.71': '1.71.64', 'UI5-1.92': '1.92.1' });
jest.mock('child_process', () => ({
Expand All @@ -15,50 +16,6 @@ jest.mock('child_process', () => ({
})
}));

describe('mergeObjects', () => {
const base: Partial<Package> = {
scripts: {
first: 'first'
},
ui5: {
dependencies: ['module-1']
}
};

test('additional ui5 dependencies (array merge)', () => {
const extension: Package = {
name: 'test',
ui5: {
dependencies: ['module-2']
}
};
const merged = mergeObjects(base, extension);
expect(merged.ui5?.dependencies).toStrictEqual(['module-1', 'module-2']);
});

test('duplicated ui5 dependencies (array merge)', () => {
const extension: Package = {
name: 'test',
ui5: {
dependencies: ['module-1', 'module-2']
}
};
const merged = mergeObjects(base, extension);
expect(merged.ui5?.dependencies).toStrictEqual(['module-1', 'module-2']);
});

test('overwrite property', () => {
const extension: Package = {
name: 'test',
scripts: {
first: 'second'
}
};
const merged = mergeObjects(base, extension);
expect(merged.scripts?.first).toBe(extension.scripts?.first);
});
});

describe('Setting defaults', () => {
const testData: { input: Partial<UI5>; expected: UI5 }[] = [
// 0
Expand Down
Loading

0 comments on commit 31207b9

Please sign in to comment.