Skip to content

Commit

Permalink
refactor: Simplify SAPUI5 types update process
Browse files Browse the repository at this point in the history
  • Loading branch information
matz3 committed Aug 2, 2024
1 parent 94b05f6 commit f4585a2
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 78 deletions.
32 changes: 18 additions & 14 deletions docs/Development.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
![UI5 logo](./docs/images/UI5_logo_wide.png)

# UI5 linter Development

**Note:** This document is intended to support UI5 Linter developers and is not meant for end users of the linter!

## Metadata generation

The UI5 Linter requires metadata to accurately identify certain issues within the codebase. While the absence of this metadata does not hinder the linter's basic functionality, it may result in incomplete findings.
**Note:** This document is intended to support UI5 Linter developers and is not relevant for end users of the linter.

The extracted and generated metadata is stored within the repository under the `/resources` folder. This metadata plays a crucial role in enhancing the accuracy of the linter's analysis.

Regular updates to the metadata are necessary to ensure that the data is compatible with the corresponding UI5 type definitions.
## Updating SAPUI5 types

UI5 linter currently comes with a fixed version of the SAPUI5 types that needs to be updated manually.
An update can be performed with following command:
```sh
npm run update-pseudo-modules-info -- $DOMAIN_NAME/com/sap/ui5/dist/sapui5-sdk-dist/1.120.15/sapui5-sdk-dist-1.120.15-api-jsons.zip 1.120.15
npm run update-sapui5-types -- <domain> <version>
```

```sh
npm run update-semantic-model-info -- $DOMAIN_NAME/com/sap/ui5/dist/sapui5-sdk-dist/1.120.15/sapui5-sdk-dist-1.120.15-api-jsons.zip 1.120.15
```
**Note:**
- `domain` is the internal domain (without protocol) that hosts the SAPUI5 SDK API JSON files.
- `version` is the version of the SAPUI5 distribution.

The script updates multiple places where the corresponding SAPUI5 types are referenced or incorporated:
- `@sapui5/types` npm dependency in [package.json](../package.json)
- This package contains the TypeScript definitions of all SAPUI5 libraries and is relevant for the general TypeScript based detection.
- [`resources/api-extract.json`](../resources/api-extract.json)
- This file contains additional information that is not available or accessible via the TypeScript definitions. It is an extract from the original `api.json` files of the SAPUI5 libraries.
- [`resources/dataTypes.json`](../resources/dataTypes.json)
- This file is used to distinguish between data types and enumerations when analyzing pseudo modules.
- [`resources/overrides/library`](../resources/overrides/library)
- This folder contains additional module declarations that support detection of certain issues, for example usage of pseudo modules.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
"unit-debug": "ava debug",
"unit-update-snapshots": "ava --update-snapshots",
"unit-watch": "ava --watch",
"update-semantic-model-info": "tsx scripts/metadataProvider/createMetadataInfo.ts",
"update-pseudo-modules-info": "tsx scripts/metadataProvider/createPseudoModulesInfo.ts"
"update-sapui5-types": "tsx scripts/update-sapui5-types.ts"
},
"files": [
"CHANGELOG.md",
Expand Down
13 changes: 1 addition & 12 deletions scripts/metadataProvider/createMetadataInfo.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {writeFile} from "node:fs/promises";
import MetadataProvider from "./MetadataProvider.js";
import path from "node:path";
import {fetchAndExtractApiJsons, handleCli, cleanup, RAW_API_JSON_FILES_FOLDER} from "./helpers.js";

import {
forEachSymbol,
} from "@ui5-language-assistant/semantic-model";

async function main(apiJsonsRoot: string, sapui5Version: string) {
export default async function createMetadataInfo(apiJsonsRoot: string, sapui5Version: string) {
const metadataProvider = new MetadataProvider();
await metadataProvider.init(apiJsonsRoot, sapui5Version);

Expand All @@ -34,12 +32,3 @@ async function main(apiJsonsRoot: string, sapui5Version: string) {
JSON.stringify(apiExtract, null, 2)
);
}

// Entrypoint
await handleCli(async (url, sapui5Version) => {
await fetchAndExtractApiJsons(url);

await main(path.resolve(RAW_API_JSON_FILES_FOLDER), sapui5Version);

await cleanup();
});
18 changes: 6 additions & 12 deletions scripts/metadataProvider/createPseudoModulesInfo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {createRequire} from "module";
import {writeFile, readdir} from "node:fs/promises";
import path from "node:path";
import {fetchAndExtractApiJsons, handleCli, cleanup, RAW_API_JSON_FILES_FOLDER} from "./helpers.js";

interface apiJson {
"ui5-metadata": {
Expand All @@ -27,11 +26,11 @@ interface apiJson {

const require = createRequire(import.meta.url);

async function getPseudoModuleNames() {
const apiJsonList = await readdir(RAW_API_JSON_FILES_FOLDER);
async function getPseudoModuleNames(apiJsonsRoot: string) {
const apiJsonList = await readdir(apiJsonsRoot);

return apiJsonList.flatMap((library) => {
const libApiJson = require(path.resolve(RAW_API_JSON_FILES_FOLDER, library)) as {symbols: apiJson[]};
const libApiJson = require(path.join(apiJsonsRoot, library)) as {symbols: apiJson[]};
return libApiJson.symbols;
}).reduce((acc: Record<string, apiJson[]>, symbol) => {
if ((["datatype", "enum"].includes(symbol?.["ui5-metadata"]?.stereotype) ||
Expand Down Expand Up @@ -134,12 +133,7 @@ async function addOverrides(ui5Types: Record<string, apiJson[]>) {
);
}

// Entrypoint
await handleCli(async (url) => {
await fetchAndExtractApiJsons(url);

const pseudoModules = await getPseudoModuleNames();
export default async function createPseudoModulesInfo(apiJsonsRoot: string) {
const pseudoModules = await getPseudoModuleNames(apiJsonsRoot);
await addOverrides(pseudoModules);

await cleanup();
});
}
50 changes: 12 additions & 38 deletions scripts/metadataProvider/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {mkdir, unlink, readdir} from "node:fs/promises";
import {createWriteStream} from "node:fs";
import https from "node:https";
import {pipeline} from "node:stream/promises";
import {Readable} from "node:stream";
import {ReadableStream} from "node:stream/web";
import {finished, pipeline} from "node:stream/promises";
import path from "node:path";
import yauzl from "yauzl-promise";
import {fileURLToPath} from "node:url";

export const RAW_API_JSON_FILES_FOLDER = "tmp/apiJson";
const RAW_API_JSON_FILES_FOLDER = fileURLToPath(new URL(`../../tmp/apiJson`, import.meta.url));

export async function fetchAndExtractApiJsons(url: string) {
const response = await fetch(url);
Expand All @@ -15,23 +17,19 @@ export async function fetchAndExtractApiJsons(url: string) {

if (response.body && response.body instanceof ReadableStream) {
const zipFileName: string = url.split("/").pop()!;
const zipFile = path.resolve(RAW_API_JSON_FILES_FOLDER, zipFileName);
await mkdir(path.resolve(RAW_API_JSON_FILES_FOLDER), {recursive: true});
const zipFile = path.join(RAW_API_JSON_FILES_FOLDER, zipFileName);
await mkdir(RAW_API_JSON_FILES_FOLDER, {recursive: true});

await new Promise((resolve) => {
https.get(url, (res) => {
resolve(pipeline(res, createWriteStream(zipFile)));
});
});
await finished(Readable.fromWeb(response.body).pipe(createWriteStream(zipFile)));

const zip = await yauzl.open(zipFile);
try {
for await (const entry of zip) {
if (entry.filename.endsWith("/")) {
await mkdir(path.resolve(RAW_API_JSON_FILES_FOLDER, entry.filename));
await mkdir(path.join(RAW_API_JSON_FILES_FOLDER, entry.filename));
} else {
const readEntry = await entry.openReadStream();
const writeEntry = createWriteStream(path.resolve(RAW_API_JSON_FILES_FOLDER, entry.filename));
const writeEntry = createWriteStream(path.join(RAW_API_JSON_FILES_FOLDER, entry.filename));
await pipeline(readEntry, writeEntry);
}
}
Expand All @@ -41,6 +39,7 @@ export async function fetchAndExtractApiJsons(url: string) {

// Remove the ZIP file, so that the folder will contain only JSON files
await unlink(zipFile);
return RAW_API_JSON_FILES_FOLDER;
} else {
throw new Error(`The request to "${url}" returned a malformed response and cannot be read.`);
}
Expand All @@ -49,30 +48,5 @@ export async function fetchAndExtractApiJsons(url: string) {
export async function cleanup() {
const apiJsonList = await readdir(RAW_API_JSON_FILES_FOLDER);

await Promise.all(apiJsonList.map((library) => unlink(path.resolve(RAW_API_JSON_FILES_FOLDER, library))));
}

export async function handleCli(cb: (url: string, sapui5Version: string) => Promise<void>) {
try {
const url = process.argv[2];
let sapui5Version: string | null | undefined = process.argv[3];

if (!url) {
throw new Error("First argument \"url\" is missing");
}

if (!sapui5Version) {
// Try to extract version from url
const versionMatch = url.match(/\/\d{1}\.\d{1,3}\.\d{1,3}\//gi);
sapui5Version = versionMatch?.[0].replaceAll("/", "");
}
if (!sapui5Version) {
throw new Error("\"sapui5Version\" cannot be determined. Provide it as a second argument");
}

await cb(url, sapui5Version);
} catch (err) {
process.stderr.write(String(err));
process.exit(1);
}
await Promise.all(apiJsonList.map((library) => unlink(path.join(RAW_API_JSON_FILES_FOLDER, library))));
}
33 changes: 33 additions & 0 deletions scripts/update-sapui5-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import createMetadataInfo from "./metadataProvider/createMetadataInfo.js";
import createPseudoModulesInfo from "./metadataProvider/createPseudoModulesInfo.js";
import {cleanup, fetchAndExtractApiJsons} from "./metadataProvider/helpers.js";
import {promisify} from "node:util";
import {execFile as execFileCb} from "node:child_process";
const execFile = promisify(execFileCb);

try {
const domain = process.argv[2];
const version = process.argv[3];

if (!domain) {
throw new Error("First argument \"domain\" is missing");
}

if (!version) {
throw new Error("Second argument \"version\" is missing");
}

const url = `https://${domain}/artifactory/build-releases/com/sap/ui5/dist/sapui5-sdk-dist/${version}/sapui5-sdk-dist-${version}-api-jsons.zip`;
const apiJsonsRoot = await fetchAndExtractApiJsons(url);

await createMetadataInfo(apiJsonsRoot, version);
await createPseudoModulesInfo(apiJsonsRoot);

await cleanup();

// Update @sapui5/types npm package
await execFile("npm", ["install", "-E", `@sapui5/types@${version}`]);
} catch (err) {
process.stderr.write(String(err));
process.exit(1);
}

0 comments on commit f4585a2

Please sign in to comment.