generated from UI5/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Add manifest validation tool #93
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
dimovpetar
wants to merge
31
commits into
UI5:main
Choose a base branch
from
dimovpetar:feat_run_manifest_validation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
90dacb2
feat: Add manifest validation tool
dimovpetar fc51780
refactor: Move manifest schema fetching to ui5Manifest.ts
dimovpetar e32b101
test(index.ts): Add tests
dimovpetar a5992fc
refactor(ui5Manifest.ts): Cache the schema
dimovpetar e5ecb59
docs(README.md): List run_manifest_validation
dimovpetar 7ef41ca
refactor(ui5Manifest): Add cache
dimovpetar b2ed9a3
refactor: Improve error handling
dimovpetar e9009eb
test(runValidation): Add tests
dimovpetar 51e2239
refactor: Add comment containing link to AdaptiveCards issue
dimovpetar 0acf46b
fix(package.json): List ajv as dependency
dimovpetar 67db4fb
fix(runValidation): Resolve meta schemas paths using import.meta.resolve
dimovpetar 86873ea
fix(runValidation): Throw error if manifest path is not absolute
dimovpetar 3b7ab28
fix(run_manifest_validation): Normalize manifest path
dimovpetar 626675b
refactor: Fetch concrete manifest schema
dimovpetar 71032b2
fix(runValidation): Properly release mutex
dimovpetar ee562f3
fix(ui5Manifest): Throw errors for versions lt 1.68.0
dimovpetar e633b9d
fix(runValidation): Include ajv-formats
dimovpetar 86ec801
refactor(ui5Manifest): Enhance more errors with supported versions info
dimovpetar 595268b
test(runValidation): Increase coverage for external schemas
dimovpetar d1b8290
refactor(runValidation): Add explanation why meta schemas are needed
dimovpetar 9bae0aa
refactor(runValidation): Add explanation for unicodeRegExp setting
dimovpetar 615ef9e
refactor(runValidation): Add coment why strict flag of ajv is off
dimovpetar 7e58636
refactor(ui5Manifest): Add comments for lowest supported version 1.68.0
dimovpetar 31e4aac
fix(run_manifest_validation): Mark the tool as read-only
dimovpetar e41177c
refactor(runValidation): Remove undefined properties from the result
dimovpetar a068ff0
fix(schema.ts): Resolve ts error
dimovpetar 2f3e782
docs(architecture.md): Describe all cards tools
dimovpetar e68d9e1
fix(ui5Manifest.ts): Avoid duplicate entries
dimovpetar 02c51e3
refactor: Use new UI5 Manifest repo URL
matz3 c7e5dbe
test: Add test for formatters
dimovpetar e4d45f9
docs: Improve wording
dimovpetar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import runValidation from "./runValidation.js"; | ||
| import {inputSchema, outputSchema} from "./schema.js"; | ||
| import {getLogger} from "@ui5/logger"; | ||
| import Context from "../../Context.js"; | ||
| import {RegisterTool} from "../../registerTools.js"; | ||
|
|
||
| const log = getLogger("tools:run_manifest_validation"); | ||
|
|
||
| export default function registerTool(registerTool: RegisterTool, context: Context) { | ||
| registerTool("run_manifest_validation", { | ||
| title: "Manifest Validation", | ||
| description: | ||
| "Validates UI5 manifest file. " + | ||
| "After making changes, you should always run the validation again " + | ||
| "to verify that no new problems have been introduced.", | ||
| annotations: { | ||
| title: "Manifest Validation", | ||
| readOnlyHint: true, | ||
| }, | ||
| inputSchema, | ||
| outputSchema, | ||
| }, async ({manifestPath}) => { | ||
| log.info(`Running manifest validation on ${manifestPath}...`); | ||
|
|
||
| const normalizedManifestPath = await context.normalizePath(manifestPath); | ||
| const result = await runValidation(normalizedManifestPath); | ||
|
|
||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: JSON.stringify(result), | ||
| }], | ||
| structuredContent: result, | ||
| }; | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| import {fetchCdn} from "../../utils/cdnHelper.js"; | ||
| import {RunSchemaValidationResult} from "./schema.js"; | ||
| import Ajv2020, {AnySchemaObject} from "ajv/dist/2020.js"; | ||
| import addFormats from "ajv-formats"; | ||
| import {readFile} from "fs/promises"; | ||
| import {getLogger} from "@ui5/logger"; | ||
| import {InvalidInputError} from "../../utils.js"; | ||
| import {getManifestSchema, getManifestVersion} from "../../utils/ui5Manifest.js"; | ||
| import {Mutex} from "async-mutex"; | ||
| import {fileURLToPath} from "url"; | ||
| import {isAbsolute} from "path"; | ||
|
|
||
| const log = getLogger("tools:run_manifest_validation:runValidation"); | ||
| const schemaCache = new Map<string, AnySchemaObject>(); | ||
| const fetchSchemaMutex = new Mutex(); | ||
|
|
||
| const AJV_SCHEMA_PATHS = { | ||
| draft06: fileURLToPath(import.meta.resolve("ajv/dist/refs/json-schema-draft-06.json")), | ||
| draft07: fileURLToPath(import.meta.resolve("ajv/dist/refs/json-schema-draft-07.json")), | ||
| } as const; | ||
|
|
||
| async function createUI5ManifestValidateFunction(ui5Schema: object) { | ||
| try { | ||
| const ajv = new Ajv2020.default({ | ||
| // Collect all errors, not just the first one | ||
| allErrors: true, | ||
| // Allow additional properties that are not in schema such as "i18n", | ||
| // otherwise compilation fails | ||
| strict: false, | ||
| // Don't use Unicode-aware regular expressions, | ||
| // otherwise compilation fails with "Invalid escape" errors | ||
| unicodeRegExp: false, | ||
| loadSchema: async (uri) => { | ||
| const release = await fetchSchemaMutex.acquire(); | ||
|
|
||
| try { | ||
| if (schemaCache.has(uri)) { | ||
| log.info(`Loading cached schema: ${uri}`); | ||
dimovpetar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return schemaCache.get(uri)!; | ||
| } | ||
|
|
||
| log.info(`Loading external schema: ${uri}`); | ||
| const schema = await fetchCdn(uri) as AnySchemaObject; | ||
|
|
||
| // Special handling for Adaptive Card schema to fix unsupported "id" property | ||
| // According to the JSON Schema spec Draft 06 (used by Adaptive Card schema), | ||
| // "$id" should be used instead of "id" | ||
| // See https://github.com/microsoft/AdaptiveCards/issues/9274 | ||
| if (uri.includes("adaptive-card.json") && typeof schema.id === "string") { | ||
| schema.$id = schema.id; | ||
dimovpetar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| delete schema.id; | ||
| } | ||
|
|
||
| schemaCache.set(uri, schema); | ||
|
|
||
| return schema; | ||
| } catch (error) { | ||
| log.warn(`Failed to load external schema ${uri}:` + | ||
| `${error instanceof Error ? error.message : String(error)}`); | ||
|
|
||
| throw error; | ||
| } finally { | ||
| release(); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| addFormats.default(ajv); | ||
dimovpetar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const draft06MetaSchema = JSON.parse( | ||
dimovpetar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| await readFile(AJV_SCHEMA_PATHS.draft06, "utf-8") | ||
| ) as AnySchemaObject; | ||
| const draft07MetaSchema = JSON.parse( | ||
| await readFile(AJV_SCHEMA_PATHS.draft07, "utf-8") | ||
| ) as AnySchemaObject; | ||
|
|
||
| // Add meta-schemas for draft-06 and draft-07. | ||
| // These are required to support schemas that reference these drafts, | ||
| // for example the Adaptive Card schema and some sap.bpa.task properties. | ||
| ajv.addMetaSchema(draft06MetaSchema, "http://json-schema.org/draft-06/schema#"); | ||
| ajv.addMetaSchema(draft07MetaSchema, "http://json-schema.org/draft-07/schema#"); | ||
|
|
||
| const validate = await ajv.compileAsync(ui5Schema); | ||
|
|
||
| return validate; | ||
| } catch (error) { | ||
| throw new Error(`Failed to create UI5 manifest validate function: ` + | ||
| `${error instanceof Error ? error.message : String(error)}`); | ||
| } | ||
| } | ||
|
|
||
| async function readManifest(path: string) { | ||
| let content: string; | ||
| let json: object; | ||
|
|
||
| if (!isAbsolute(path)) { | ||
| throw new InvalidInputError(`The manifest path must be absolute: '${path}'`); | ||
| } | ||
|
|
||
| try { | ||
| content = await readFile(path, "utf-8"); | ||
| } catch (error) { | ||
| throw new InvalidInputError(`Failed to read manifest file at ${path}: ` + | ||
| `${error instanceof Error ? error.message : String(error)}`); | ||
| } | ||
|
|
||
| try { | ||
| json = JSON.parse(content) as object; | ||
| } catch (error) { | ||
| throw new InvalidInputError(`Failed to parse manifest file at ${path} as JSON: ` + | ||
| `${error instanceof Error ? error.message : String(error)}`); | ||
| } | ||
|
|
||
| return json; | ||
| } | ||
|
|
||
| export default async function runValidation(manifestPath: string): Promise<RunSchemaValidationResult> { | ||
| log.info(`Starting manifest validation for file: ${manifestPath}`); | ||
|
|
||
| const manifest = await readManifest(manifestPath); | ||
| const manifestVersion = await getManifestVersion(manifest); | ||
| log.info(`Using manifest version: ${manifestVersion}`); | ||
| const ui5ManifestSchema = await getManifestSchema(manifestVersion); | ||
| const validate = await createUI5ManifestValidateFunction(ui5ManifestSchema); | ||
| const isValid = validate(manifest); | ||
|
|
||
| if (isValid) { | ||
| log.info("Manifest validation successful"); | ||
|
|
||
| return { | ||
| isValid: true, | ||
| errors: [], | ||
| }; | ||
| } | ||
|
|
||
| // Map AJV errors to our schema format | ||
| const validationErrors = validate.errors ?? []; | ||
| const errors = validationErrors.map((error): RunSchemaValidationResult["errors"][number] => { | ||
| return { | ||
| keyword: error.keyword ?? "", | ||
| instancePath: error.instancePath ?? "", | ||
| schemaPath: error.schemaPath ?? "", | ||
| params: error.params ?? {}, | ||
| propertyName: error.propertyName, | ||
| message: error.message, | ||
| }; | ||
| }); | ||
|
|
||
| log.info(`Manifest validation failed with ${errors.length} error(s)`); | ||
|
|
||
| return { | ||
| isValid: false, | ||
| errors: errors, | ||
| }; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.